-
Notifications
You must be signed in to change notification settings - Fork 4
/
Copy pathbios.z80
455 lines (392 loc) · 8.67 KB
/
bios.z80
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
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
; bios.z80
;
; This file contains the common primitives we use for interfacing
; with CP/M or the ZX Spectrum (48k).
;
; We don't use many primitives, but we do need some facilities from
; the operating system, or the Spectrum ROM:
;
; Pause for a keypress.
;
; Clear the screen.
;
; Output a string.
;
; Allow the user to enter input
;
; Pause for a "small delay"
;
; The routines here will work for both systems, and contain the only
; platform-specific code we need.
;
IF SPECTRUM
MAX_INPUT_LENGTH: EQU 30
ELSE
MAX_INPUT_LENGTH: EQU 78
ENDIF
;
; Initialization routine for the BIOS, if needed
; {{
bios_init:
IF SPECTRUM
ld a,2
call 5633 ; ROM_OPEN_CHANNEL
call c_chan
ld a,4
call 5633 ; ROM_OPEN_CHANNEL
ENDIF
ret
; }}
;
; Delay for "a short while".
;
; This is used for showing "dots" in the SLEEP command, or when the
; grue is threatening you..
;
; {{
bios_delay:
IF SPECTRUM
PUSH_ALL
ld b,4
wait:
halt
djnz wait
POP_ALL
ret
ELSE
; Random values found by experimentation with my own system
ld hl,0xffff / 8
hl_delay_loop:
inc hl
ld de, 0xffff - 10
de_delay_loop:
inc de
ld a, d
or e
jr nz, de_delay_loop
ld a,h
or l
jr nz, hl_delay_loop
ret
ENDIF
;; }}
;
; Clear the screen.
;
; {{
bios_clear_screen:
IF SPECTRUM
PUSH_ALL
ld a,2
call 0x1601 ; ROM_OPEN_CHANNEL
call 0x0DAF ; ROM_CLS
;
; The code above will clear the screen, however we use a custom
; printing routine which has its own notion of the X,Y / AT
; coordinates to use.
;
; We explicitly reset those here, otherwise the screen will be clear,
; but printing will start at whatever spot it finished the previous
; time something was output.
;
xor a
ld hl,atflg
ld (hl),a
ld hl, row
ld (hl),a
ld hl, col
ld (hl),a
POP_ALL
ret
ELSE
ld de, cpm_clear_screen_msg
call bios_output_string
ret
cpm_clear_screen_msg:
db 27, "[2J" ; "clear"
db 27, "[H" ; "home"
db "$"
ENDIF
; }}
;
; Await a keypress. The pressed-key will be returned in the A-register.
;
; {{
bios_await_keypress:
IF SPECTRUM
push hl
ld hl, 0x5C08 ; LASTK - Set by the Spectrum ROM
ld a,255
ld (hl),a
await_key:
cp (hl)
jr z,await_key
; get the key, and return it.
ld a,(hl)
pop hl
ret
ELSE
ld c, 0x01
call 0x0005
ret
ENDIF
; }}
;
; Output a single character, stored in the E register.
;
; {{
bios_output_character:
PUSH_ALL
IF SPECTRUM
; output the character
ld a,e
RST 0x10
ELSE
ld c, 0x02
call 0x005
ENDIF
POP_ALL
ret
; }}
;
; Output a string, terminated by "$".
;
; The address of the string is stored in DE.
;
; To handle wrapping we process the string character by character,
; this is inefficient, but works fine for this use-case.
;
; {{
bios_output_string:
IF SPECTRUM
PUSH_ALL
ld a,4
call 5633 ; ROM_OPEN_CHANNEL
POP_ALL
ENDIF
PUSH_ALL
; DE has the string
; We'll work on HL, and use BC to keep track of the width
; of the string we've printed thus-far
push de
pop hl
; length will be stored here
print_reset_line:
ld bc, 0
print_char_loop:
; get a character
ld a,(hl)
inc hl
inc bc
; end of printing?
cp '$'
jr z, end_of_print
; linefeed? Reset our printing width
cp 0x0d
jr nz, not_linefeed
ld e, 0x0d
call bios_output_character
IF SPECTRUM
ELSE
ld e, 0x0a
call bios_output_character
ENDIF
jr print_reset_line
not_linefeed:
; We'll ignore the newline
cp 0x0a
jr z, print_char_loop
; is it a space?
cp ' '
jr nz, print_continues
; if our length is bigger than the width we force
; a newline, otherwise we keep going
push af
push hl
ld hl, WRAP_LOCATION
ld a,(hl)
pop hl
cp c
jr c, too_wide
pop af
print_continues:
ld e,a
call bios_output_character
jr print_char_loop
too_wide:
pop af
PUSH_ALL
ld e, 0x0d
call bios_output_character
IF SPECTRUM
ELSE
ld e, 0x0a
call bios_output_character
ENDIF
POP_ALL
jr print_reset_line
end_of_print:
POP_ALL
ret
; }}
;
; Prompt the user for a line of input.
;
; The way this works is you pass the address of a region of memory,
; in the DE register. The first byte is the length of the buffer.
;
; On return the second byte of the buffer will be populated by the
; amount of text which was read, then the input itself:
;
; DE points to the memory buffer;
;
; 0x00 - buffer size
; 0x01 - read-length
; 0x02 ... char..
; {{
bios_read_input:
IF SPECTRUM
PUSH_ALL
; Move the cursor to the bottom of the screen, and display the prompt
PUSH_ALL
call move_to_bottom
ld e, ">"
call bios_output_character
POP_ALL
push de
pop hl ; buffer -> hl
inc hl ; skip to the returned size
inc de
xor a
ld (hl),a ; size is now zero
inc hl ; point to the start of the character buffer
read_input_again:
; read a char
call bios_await_keypress
; return? Then we're done
cp 13
jp z, read_input_over
; delete? remove the last character
cp 12
jr z, backspace
; escape? trash the input and start again.
cp 7
jr z, reset_line
; too many characters?
push de
push af
ld a,(de) ; get the count of characters we've entered
cp MAX_INPUT_LENGTH ; ignore input if too long
jr nc,too_many_characters
jr z, too_many_characters
pop af
pop de
jr continue_input
too_many_characters:
; too many characters, cleanup stack and ignore the character
pop af
pop de
jr read_input_again
continue_input:
; display the new character
push de
ld e,a
call bios_output_character
pop de
; add the character to the buffer
ld (hl),a
inc hl
; increase the input count
ld a,(de)
inc a
ld (de),a
; repeat
jr read_input_again
backspace:
; remove size of the line by one
; unless it was empty
ld a,(de)
cp 0
jr z, read_input_again
dec a
ld (de),a
; remove character
dec hl
ld (hl), 0x00
PUSH_ALL
; move cursor to start
call move_to_bottom
; output spaces - to overwrite what was previously input
;
; NOTE: We assume our input was a single line.
ld b, MAX_INPUT_LENGTH+1
ld e, " "
spaces:
call bios_output_character
djnz spaces
; move to the bottom, and show the prompt again
call move_to_bottom
; show the prompt
ld e, ">"
call bios_output_character
POP_ALL
; save registers
push hl
push de
push bc
; is the input line empty? If so return,
; cleaning up the stack.
ld a,(de)
cp 0
jr nz, non_empty
pop bc
pop de
pop hl
jr read_input_again
non_empty:
; OK we're gonna show the input the user
; entered.
ld b,a
push de
pop hl
inc hl
loopy:
ld e,(hl)
call bios_output_character
inc hl
djnz loopy
pop bc
pop de
pop hl
jp read_input_again
reset_line:
; clear the screen, and restart the input process
call bios_clear_screen
POP_ALL
jp bios_read_input
read_input_over:
; move the cursor to the top of the screen, and
; prepare for output again.
call bios_clear_screen
POP_ALL
ret
move_to_bottom:
ld a,22 ; AT code.
rst 16 ; Set the cursor-coord
ld a,21 ; vertical coord.
rst 16 ; set the vertical coord
xor a ; horizontal position.
rst 16 ; set the horizontal coord.
ret
ELSE
push de
ld de, prompt_message
call bios_output_string
pop de
ld c, 0x0A
call 0x005
ret
prompt_message:
db 0x0a, 0x0d,">$"
ENDIF
; }}