
So far DISKMON runs once and stops. From now on it is a monitor: it shows a > and waits for you to type a command, does it, and asks again. Every capability the rest of the primer adds is a new command.
And the first thing every file command needs is a way to say which file. The BDOS does not take a name as text. It takes a File Control Block.
An FCB is 36 bytes that your program owns and the BDOS reads and writes:
| Byte | Holds |
|---|---|
| 0 | The drive: 0 the current drive (the one in the prompt, 0:), 1 drive 0, 2 drive 1 |
| 1-8 | The name, in capitals, padded with spaces |
| 9-11 | The extension, padded with spaces |
| 12-31 | Filled in by the BDOS when it opens the file: the extent, the record count and the block numbers, copied from the directory entry (S7) |
| 32 | The BDOS's place in the file as it reads |
| 33-35 | A record number, for jumping straight to a record (S9) |
Bytes 1-11 are laid out exactly like the name in a directory entry (S3). Byte 0 is not: in an entry it is the status, in an FCB the drive - and one more than the drive number, so that 0 can mean "whichever is current".
To name 1:POEM.TXT, then, you fill an FCB with 02, POEM, TXT, and zeros.
The new START keeps the stack pointer XtalDOS gave it, in OLDSP, and switches to a stack of its own at 9000h. MAIN is the command loop: prompt, read a line, look at its first letter, and jump to that command. Each command ends by jumping back to MAIN, which resets the stack - so a command that gives up halfway can simply JP MAIN. Q puts the old stack pointer back and returns to the DOS.
Its buffers live from 8000h up, named once at the top with EQU: well clear of DISKMON itself, which starts at 0100h, and of the DOS, which starts at E100h.
Reading the line is GETLINE: MOS call 156 waits for a key and returns its code; DISKMON stores it and prints it, so you see what you type, until ENTER - code 13. Letters arrive as capitals. There is no rubbing out: if you mistype, press ENTER and DISKMON will answer ? and let you try again.
Three small routines take the line apart. SKIPSP steps over spaces. GETNUM reads a decimal number - you will need it from S9. And GETNAME builds an FCB: if the name starts with a digit and a colon, the drive byte is that digit plus one; the name goes in up to the ., the extension after it, and both are padded with spaces.
SHOWBLK shows bytes: an offset, eight bytes in hex, and the same eight as characters (a . for anything that is not printable). After every 128 bytes it waits for a key, so that a whole sector in Part three does not scroll past you.
The whole program. PRINT, PUTC, NEWLINE and PHEX are S4's; everything else is new or rewritten.
; DISKMON - reads and writes the Einstein's discs.
; The Assembly Disc Access Primer's program, as it stands at the end of
; the section named in this file's name.
ORG 0100H
; Buffers, clear of the program, the DOS and the MOS.
DIRBUF: EQU 8000H ; a whole directory: 4 sectors, 2048 bytes
SECBUF: EQU 8800H ; one sector: 512 bytes
RECBUF: EQU 8A00H ; one record: 128 bytes
LINE: EQU 8A80H ; the command line: 64 bytes
STACK: EQU 9000H ; DISKMON's own stack grows down from here
START: LD (OLDSP),SP
LD SP,STACK
LD HL,TITLE
CALL PRINT
; The command loop: read a line, act on its first letter.
MAIN: LD SP,STACK
LD HL,PROMPT
CALL PRINT
CALL GETLINE
CALL NEWLINE
CALL SKIPSP
OR A
JR Z,MAIN
INC HL
LD (LPTR),HL
CP 'Q'
JP Z,QUIT
CP 'F'
JP Z,CMDF
WHAT: LD HL,WHATM
DONE: CALL PRINT
JP MAIN
CRMAIN: CALL NEWLINE
JP MAIN
QUIT:
LD SP,(OLDSP)
RET
; ---------------------------------------------------------------- BDOS
; F name: build the FCB for a name and show its 36 bytes.
CMDF: LD DE,FCB
CALL GETNAME
JP C,WHAT
LD HL,FCB
LD BC,36
CALL SHOWBLK
JP MAIN
; ------------------------------------------------------ the command line
; GETLINE: read keys into LINE until ENTER, echoing them.
GETLINE: LD HL,LINE
LD (LPTR),HL
LD B,0
GL1: PUSH HL
PUSH BC
RST 8
DEFB 156 ; wait for a key
POP BC
POP HL
CP 13
JR Z,GL2
CP ' '
JR C,GL1 ; ignore other control keys
LD C,A
LD A,B
CP 60
JR NC,GL1 ; the line is full
LD A,C
LD (HL),A
INC HL
INC B
CALL PUTC
JR GL1
GL2: LD (HL),0
RET
; SKIPSP: step LPTR past spaces. HL = LPTR, A = the character there.
SKIPSP: LD HL,(LPTR)
SK1: LD A,(HL)
CP ' '
JR NZ,SK2
INC HL
JR SK1
SK2: LD (LPTR),HL
RET
; GETNUM: a decimal number from the line into HL (and A = L).
; Carry set if there was none.
GETNUM: CALL SKIPSP
CALL ISDIG
RET C
LD HL,0
GN1: PUSH HL
LD HL,(LPTR)
LD A,(HL)
CALL ISDIG
JR C,GN2
INC HL
LD (LPTR),HL
POP HL
SUB '0'
LD D,H ; HL = HL x 10 + digit
LD E,L
ADD HL,HL
ADD HL,HL
ADD HL,DE
ADD HL,HL
LD E,A
LD D,0
ADD HL,DE
JR GN1
GN2: POP HL
LD A,L
OR A ; no carry
RET
; ISDIG: carry clear if A is '0'-'9'.
ISDIG: CP '0'
RET C
CP '9'+1
CCF
RET
; GETNAME: a file name from the line - [d:]NAME[.EXT] - into the FCB at
; DE: drive byte (0 = current, d+1 if given), name and extension padded
; with spaces, the rest zero. Carry set if there was no name.
GETNAME: PUSH DE
LD H,D
LD L,E
LD (HL),0
INC HL
LD B,11
GNM1: LD (HL),' '
INC HL
DJNZ GNM1
LD B,24
GNM2: LD (HL),0
INC HL
DJNZ GNM2
CALL SKIPSP
POP DE
OR A
SCF
RET Z
INC HL ; "d:" in front?
LD A,(HL)
DEC HL
CP ':'
JR NZ,GNM3
LD A,(HL)
SUB '0'-1
LD (DE),A
INC HL
INC HL
GNM3: INC DE
LD B,8
CALL NAMEPART
LD A,(HL)
CP '.'
JR NZ,GNM4
INC HL
GNM4: LD B,3
CALL NAMEPART
LD (LPTR),HL
OR A
RET
; NAMEPART: up to B characters from HL to DE, stopping at '.', ' ' or the
; end; DE moves on B places whatever; extra characters are skipped.
NAMEPART: LD A,(HL)
OR A
JR Z,NP2
CP ' '
JR Z,NP2
CP '.'
JR Z,NP2
LD (DE),A
INC HL
INC DE
DJNZ NAMEPART
NP1: LD A,(HL) ; skip what does not fit
OR A
RET Z
CP ' '
RET Z
CP '.'
RET Z
INC HL
JR NP1
NP2: INC DE
DJNZ NP2
RET
; --------------------------------------------------------------- output
; PRINT: print the zero-terminated text at HL.
PRINT: LD A,(HL)
OR A
RET Z
CALL PUTC
INC HL
JR PRINT
; PUTC: print the character in A, keeping BC, DE and HL.
PUTC: PUSH BC
PUSH DE
PUSH HL
RST 8
DEFB 158
POP HL
POP DE
POP BC
RET
; NEWLINE: carriage return, then line feed.
NEWLINE: LD A,13
CALL PUTC
LD A,10
JP PUTC
; PHEX: print A as two hex digits.
PHEX: PUSH AF
RRCA
RRCA
RRCA
RRCA
CALL DIGIT
POP AF
DIGIT: AND 0FH
ADD A,'0'
CP '9'+1
JR C,DIG1
ADD A,7
DIG1: JP PUTC
; PHEXW: print HL as four hex digits.
PHEXW: LD A,H
CALL PHEX
LD A,L
JP PHEX
; PCHAR: print A if it is a printable character, otherwise '.'.
PCHAR: CP ' '
JR C,PC1
CP 7FH
JP C,PUTC
PC1: LD A,'.'
JP PUTC
; SHOWBLK: show BC bytes from HL, 8 to a row: offset, hex, characters.
; After every 128 bytes, if there is more, wait for a key.
SHOWBLK: LD DE,0 ; DE = the offset shown
SB1: LD A,B
OR C
RET Z
LD A,E
AND 7FH
JR NZ,SB2
LD A,D ; a new 128 bytes: pause, unless the first
OR E
JR Z,SB2
PUSH HL
LD HL,KEYM
CALL PRINT
RST 8
DEFB 156
CALL NEWLINE
POP HL
SB2: LD A,B ; this row: 8 bytes, or what is left
OR A
LD A,8
JR NZ,SB3
LD A,C
CP 8
JR C,SB3
LD A,8
SB3: LD (ROWN),A
PUSH HL
EX DE,HL
CALL PHEXW
EX DE,HL
LD A,' '
CALL PUTC
POP HL
PUSH BC
PUSH HL
LD A,(ROWN)
LD B,A
SB4: LD A,(HL)
CALL PHEX
LD A,' '
CALL PUTC
INC HL
DJNZ SB4
LD A,(ROWN) ; a short row: spaces to line up
SUB 8
NEG
JR Z,SB6
LD B,A
SB5: LD A,' '
CALL PUTC
CALL PUTC
CALL PUTC
DJNZ SB5
SB6: POP HL
LD A,(ROWN)
LD B,A
SB7: LD A,(HL)
CALL PCHAR
INC HL
DJNZ SB7
CALL NEWLINE
POP BC
PUSH HL ; the offset moves on 8
LD HL,8
ADD HL,DE
EX DE,HL
LD A,(ROWN) ; BC = BC - this row
LD L,A
LD H,0
PUSH DE
EX DE,HL
LD H,B
LD L,C
OR A
SBC HL,DE
LD B,H
LD C,L
POP DE
POP HL
JP SB1
; DIV10: HL = HL / 10, A = the remainder.
DIV10: LD B,16
XOR A
DV1: ADD HL,HL
RLA
CP 10
JR C,DV2
SUB 10
INC L
DV2: DJNZ DV1
RET
; PDEC: print HL in decimal.
PDEC: CALL DIV10
PUSH AF
LD A,H
OR L
CALL NZ,PDEC
POP AF
ADD A,'0'
JP PUTC
; ----------------------------------------------------------------- data
TITLE: DEFB 'DISKMON',13,10,0
PROMPT: DEFB '> ',0
WHATM: DEFB '?',13,10,0
KEYM: DEFB ' (KEY)',0
ROWN: DEFB 0
OLDSP: DEFW 0
LPTR: DEFW 0
FCB: DEFS 36
Run it
Use the primer's practice disc PRIMER.DSK this time (from the course download - it holds POEM.TXT and ROOMS.DAT). Copy it, put DISKMON on the copy, and run:
z80asm -o diskmon.com diskmon.asm
cp PRIMER.DSK work.dsk
python3 edisc.py add work.dsk diskmon.com DISKMON.COM
mame -rompath ~/roms einstein -window -nomaximize -ui_active -skip_gameinfo -flop1 XTALDOS.DSK -flop2 work.dsk
At 0: type 1:DISKMON, then at DISKMON's >:
F 1:POEM.TXT
F ROOMS.DAT
Q
What you should see
> F 1:POEM.TXT
0000 02 50 4F 45 4D 20 20 20 .POEM
0008 20 54 58 54 00 00 00 00 TXT....
0010 00 00 00 00 00 00 00 00 ........
0018 00 00 00 00 00 00 00 00 ........
0020 00 00 00 00 ....
> F ROOMS.DAT
0000 00 52 4F 4F 4D 53 20 20 .ROOMS
0008 20 44 41 54 00 00 00 00 DAT....
0010 00 00 00 00 00 00 00 00 ........
0018 00 00 00 00 00 00 00 00 ........
0020 00 00 00 00 ....
>
1: became 02 in byte 0; with no drive, byte 0 is 00, the current drive. Nothing has touched the disc yet: an FCB is only a description of the file you mean.
F 1:VERYLONGNAME.TEXT. NAMEPART stops after 8 and 3 characters. What will bytes 1-11 hold?F 0:POEM.TXT. What will byte 0 be, and which drive does that mean?GETNAME, the drive byte is SUB '0'-1. What would F 1:POEM.TXT show if it were SUB '0', and which drive would the BDOS then look on?| Symptom | Cause |
|---|---|
| Characters missing from what you typed | You typed while DISKMON was still printing. Wait for the > |
DISKMON is now a monitor with commands. F builds a File Control Block: drive byte (0 current, 1 drive 0, 2 drive 1), name and extension padded with spaces, and room for the BDOS's own bookkeeping.
S7 hands the FCB to the BDOS and asks it to open the file - and shows what the BDOS writes back into it.
This form is protected by reCAPTCHA, and the Google Privacy Policy and Terms of Service apply.