Assembly Disc Access Primer, a TatungBytes course: DISKMON reading the hidden sector IDs of the course's own practice disc, on a rainbow sunburst
← Back to Modules
module
6

The FCB: Naming a File for the BDOS

Introduction

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.

The File Control Block

An FCB is 36 bytes that your program owns and the BDOS reads and writes:

ByteHolds
0The drive: 0 the current drive (the one in the prompt, 0:), 1 drive 0, 2 drive 1
1-8The name, in capitals, padded with spaces
9-11The extension, padded with spaces
12-31Filled in by the BDOS when it opens the file: the extent, the record count and the block numbers, copied from the directory entry (S7)
32The BDOS's place in the file as it reads
33-35A 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.

DISKMON as a Monitor

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 Code

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.

Change One Thing

  • Type F 1:VERYLONGNAME.TEXT. NAMEPART stops after 8 and 3 characters. What will bytes 1-11 hold?
  • Type F 0:POEM.TXT. What will byte 0 be, and which drive does that mean?
  • In 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?

When It Goes Wrong

SymptomCause
Characters missing from what you typedYou typed while DISKMON was still printing. Wait for the >

Summary

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.

Next

S7 hands the FCB to the BDOS and asks it to open the file - and shows what the BDOS writes back into it.

Get the Newsletter

New guides, disk images and community finds, roughly once a quarter. No spam, we promise, this isn't Tatung's marketing department.
Your subscription could not be saved. Please try again.
Your subscription has been successful.

Newsletter

Subscribe to our newsletter and stay updated.

This form is protected by reCAPTCHA, and the Google Privacy Policy and Terms of Service apply.