-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathosWord.go
192 lines (162 loc) · 6.51 KB
/
osWord.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
package main
import (
"fmt"
"time"
)
func execOSWORD(env *environment) {
a, x, y, p := env.cpu.GetAXYP()
xy := uint16(x) + uint16(y)<<8
sendToROMs := false
switch a {
case 0x00: // Read line
/*
This routine accepts characters from the current input stream and
places them at a specified location in memory. During input the delete
code (ASCII 127) deletes the last character entered, and CTRL U (ASCII
21) deletes the entire line. The routine ends if RETURN is entered
(ASCII 13) or an ESCAPE condition occurs.
On exit C=0 indicates that RETURN (CR; ASCII code 13 or &D) ended the
line. C not equal to zero indicates that an escape condition terminated
entry. Y is set to the length of the line, excluding the CR if C=0.
*/
line, stop := env.con.readline()
if stop {
env.stop = true
return
}
// TODO: check max size
buffer := env.mem.peekWord(xy)
maxLength := env.mem.Peek(xy + 2)
minChar := env.mem.Peek(xy + 3)
maxChar := env.mem.Peek(xy + 4)
env.mem.pokeString(buffer, line, '\r', maxLength-1)
pOut := p &^ 1 // Clear carry
env.cpu.SetAXYP(1, x, uint8(len(line)), pOut)
env.vdu.mode7Reset()
env.log(fmt.Sprintf("OSWORD00('read line',BUF=0x%04x,range=%02x-%02x, maxlen=%v) => '%s'",
buffer, minChar, maxChar, maxLength, line))
case 0x01: // Read system clock
/*
This routine may be used to read the system clock (used for the TIME function
in BASIC). The five byte clock value is written to the address contained in the
X and Y registers. This clock is incremented every hundredth of a second and is
set to 0 by a hard BREAK.
*/
duration := time.Since(env.referenceTime)
ticks := duration.Milliseconds() / 10
env.mem.pokeNBytes(xy, 5, uint64(ticks))
env.log(fmt.Sprintf("OSWORD01('read system clock',BUF=0x%04x) => %v", xy, ticks&0xff_ffff_ffff))
case 0x02: // Write system clock
/*
This routine may be used to set the system clock to a five byte value contained
in memory at the address contained in the X and Y registers.
*/
ticks := env.mem.peekNBytes(xy, 5)
duration := time.Duration(ticks * 10 * uint64(time.Millisecond))
env.referenceTime = time.Now()
env.referenceTime = env.referenceTime.Add(duration * -1)
env.log(fmt.Sprintf("OSWORD02('write system clock',TICKS=%v)", ticks))
case 0x03: // Read Interval timer
/*
In addition to the clock there is an interval timer which is incremented every
hundredth of a second. The interval is stored in five bytes pointed to by X and Y.
*/
duration := time.Since(env.lastTimerUpdate)
timer := env.timer + uint64(duration.Milliseconds()/10)
env.mem.pokeNBytes(xy, 5, uint64(timer))
env.log(fmt.Sprintf("OSWORD03('read interval timer',BUF=0x%04x) => %v", xy, timer&0xff_ffff_ffff))
case 0x04: // Write interval timer
/*
On entry X and Y point to five locations which contain the new value to which the
clock is to be set. The interval timer increments and may cause an event when it
reaches zero. Thus setting the timer to &FFFFFFFFFE would cause an event
after two hundredths of a second.
*/
env.timer = env.mem.peekNBytes(xy, 5)
env.lastTimerUpdate = time.Now()
env.log(fmt.Sprintf("OSWORD04('write interval timer',TIMER=%v)", env.timer))
case 0x05: // Read I/O processor memory
/*
A byte of I/O processor memory may be read across the Tube using this call. A 32
bit address should be contained in memory at the address contained in the X and Y
registers.
On exit, The byte read will be contained in location XY+4.
*/
address := uint32(env.mem.peekWord(xy)) +
uint32(env.mem.peekWord(xy+2))<<16
value := env.mem.Peek((uint16(address)))
env.mem.Poke(xy+4, value)
env.logIO(fmt.Sprintf("OSWORD05('Read I/O processor memory',ADDRESS=0x%08x) => 0x%02x",
address, value))
case 0x06: // Write I/O processor memory
/*
This call permits I/O processor memory to be written across the Tube. A 32 bit
address is contained in the parameter block addressed by the X and Y registers
and the byte to be written should be placed in XY+4.
*/
address := uint32(env.mem.peekWord(xy)) +
uint32(env.mem.peekWord(xy+2))<<16
value := env.mem.Peek(xy + 4)
env.mem.Poke(uint16(address), value)
env.log(fmt.Sprintf("OSWORD06('Write I/O processor memory',ADDRESS=0x%08x,VAL=0x%02x)",
address, value))
case 0x07: // Sound command
/*
This routine takes an 8 byte parameter block addressed by the X and Y registers. The 8
bytes of the parameter block may be considered as the four parameters used for the SOUND
command in BASIC.
*/
channel := env.mem.peekWord(xy)
amplitude := int8(env.mem.peekWord(xy + 2))
pitch := env.mem.peekWord(xy + 4)
duration := env.mem.peekWord(xy + 6)
// TODO: play sound
env.log(fmt.Sprintf("OSWORD07('Sound command',CHAN=%v,AMPL=%v,PITCH=%v,DUR=%v)",
channel, amplitude, pitch, duration))
case 0x08: // Define an envelope
/*
The ENVELOPE parameter block should contain 14 bytes of data which correspond to the 14
parameters described in the ENVELOPE command. This call should be entered with the
parameter block address contained in the X and Y registers.
*/
number := env.mem.Peek(xy)
// TODO: define envelope
env.log(fmt.Sprintf("OSWORD08('Define envelope',NUMBER=%v)", number))
case 0x0e: // Read Real-Time clock
// See https://beebwiki.mdfs.net/OSWORD_%260E
functionCode := env.mem.Peek(xy)
t := time.Now()
switch functionCode {
case 0, 3: // Return clock value as string
value := t.Format("Mon,02 Jan 2006.15:04:05")
env.mem.pokeString(xy, value, 0, 21)
case 1, 4: // Return BCD clock value
env.mem.pokeBCD(xy+0, uint8(t.Year()%100))
env.mem.pokeBCD(xy+1, uint8(t.Month()))
env.mem.pokeBCD(xy+2, uint8(t.Day()))
env.mem.pokeBCD(xy+3, uint8(t.Weekday()+1))
env.mem.pokeBCD(xy+4, uint8(t.Hour()))
env.mem.pokeBCD(xy+5, uint8(t.Minute()))
env.mem.pokeBCD(xy+6, uint8(t.Second()))
case 2: // Convert BCD to string
// TODO: get the date from the BCD values
value := t.Format("Mon,02 Jan 2006.15:04:05")
env.mem.pokeString(xy, value, 0, 21)
default:
sendToROMs = true
}
env.log(fmt.Sprintf("OSWORD0e('Read Real-Time clock',FUNCTION=%v)", functionCode))
default:
sendToROMs = true
}
if sendToROMs {
// Send to the other ROMS if available.
env.mem.Poke(zpA, a)
env.mem.Poke(zpX, x)
env.mem.Poke(zpY, y)
env.cpu.SetAXYP(serviceOSWORD, x, y, p)
env.cpu.SetPC(procServiceRoms)
env.log(fmt.Sprintf("OSWORD%02x_to_roms(X=0x%02x,Y=0x%02x)", a, x, y))
// procServiceRoms issues a 254-Bad command if the command is not handled by any ROM
}
}