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
31

Appendix III: DISKMON in Full

Commands

CommandDoesSection
F nameBuild and show the FCB for a nameS6
O nameOpen; show A and the FCBS7
T nameType a text file through the BDOSS8
R name nRecord n, by read randomS9, S12
W name textWrite a new one-record fileS10
E nameEraseS11
N old newRenameS11
P name n textWrite record n, by write randomS11
S d t sShow a sector (s a number, or a letter for its ID)S13, S24
+The next sectorS14
D dDirectory, by handS16
L d nameType a file, by handS17
K offset textPut text into the sector bufferS18
M d t sWrite the sector bufferS18
U dShow a disc's boot headerS19
BMake drive 1's disc start DISKMONS20
J d name nRecord n, by handS22
G d tMove a head: Restore, SeekS25, S26
I d tA track's IDs, in the order they pass the headS27
H d t sRead a sector through the controller's portsS28
QBack to the DOS (or stop, if booted)S6, S21

Names are [d:]NAME.EXT; d is 0 or 1. The BDOS commands (F to P) need XtalDOS; booted, DISKMON refuses them.

The Program

; 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
        XOR A                   ; is there a DOS? 0005h holds JP (C3h) if so
        LD (HASDOS),A
        LD A,(0005H)
        CP 0C3H
        JR NZ,NODOS
        LD A,1
        LD (HASDOS),A
        JR MAIN
NODOS:  LD HL,NODOSM
        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
        CP 'O'
        JP Z,CMDO
        CP 'T'
        JP Z,CMDT
        CP 'R'
        JP Z,CMDR
        CP 'W'
        JP Z,CMDW
        CP 'E'
        JP Z,CMDE
        CP 'N'
        JP Z,CMDN
        CP 'P'
        JP Z,CMDP
        CP 'S'
        JP Z,CMDS
        CP '+'
        JP Z,CMDNXT
        CP 'D'
        JP Z,CMDD
        CP 'L'
        JP Z,CMDL
        CP 'K'
        JP Z,CMDK
        CP 'M'
        JP Z,CMDM
        CP 'U'
        JP Z,CMDU
        CP 'B'
        JP Z,CMDB
        CP 'J'
        JP Z,CMDJ
        CP 'G'
        JP Z,CMDG
        CP 'I'
        JP Z,CMDI
        CP 'H'
        JP Z,CMDH
WHAT:   LD HL,WHATM
DONE:   CALL PRINT
        JP MAIN
CRMAIN: CALL NEWLINE
        JP MAIN

QUIT:
        LD A,(HASDOS)
        OR A
        JR NZ,QUIT1
        LD HL,STOPM             ; no DOS to go back to
        CALL PRINT
STOP:   JR STOP
QUIT1:
        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

; O name: open the file; show what the BDOS says and the FCB after.
CMDO:   CALL NEEDDOS
        LD DE,FCB
        CALL GETNAME
        JP C,WHAT
        LD DE,FCB
        LD C,15
        CALL 5
        PUSH AF
        LD HL,OPENM
        CALL PRINT
        POP AF
        PUSH AF
        CALL PHEX
        CALL NEWLINE
        POP AF
        CP 0FFH
        LD HL,NOTFM
        JP Z,DONE
        LD HL,FCB
        LD BC,32
        CALL SHOWBLK
        JP MAIN

; T name: type a text file, a record at a time, through the BDOS.
CMDT:   CALL NEEDDOS
        CALL OPENIT
        LD DE,RECBUF            ; reads go to RECBUF
        LD C,26
        CALL 5
        LD HL,0
        LD (COUNT),HL
CMDT1:  LD DE,FCB
        LD C,20                 ; read the next record
        CALL 5
        OR A
        JR NZ,CMDT2             ; not 0: the end of the file
        LD HL,(COUNT)
        INC HL
        LD (COUNT),HL
        LD HL,RECBUF
        LD BC,128
        CALL TYPEOUT
        JR NC,CMDT1             ; carry: a 1Ah ended the text
CMDT2:  CALL NEWLINE
        LD HL,(COUNT)
        CALL PDEC
        LD HL,RECSM
        JP DONE

; OPENIT: name from the line into FCB, open it; to MAIN if that fails.
OPENIT: LD DE,FCB
        CALL GETNAME
        JP C,WHAT
        LD DE,FCB
        LD C,15
        CALL 5
        CP 0FFH
        RET NZ
        LD HL,NOTFM
        JP DONE

; R name n: read record n with read random and show it.
CMDR:   CALL NEEDDOS
        CALL OPENIT
        CALL GETNUM
        JP C,WHAT
        LD (FCB+33),HL          ; the record number, low byte first
        XOR A
        LD (FCB+35),A
        LD DE,RECBUF
        LD C,26
        CALL 5
        LD DE,FCB
        LD C,33                 ; read random
        CALL 5
        OR A
        JR NZ,CMDR1
        LD HL,RECBUF
        LD BC,128
        CALL SHOWBLK
        JP MAIN
CMDR1:  CP 1
        LD HL,NORECM
        JP Z,DONE
        CP 4
        LD HL,NOEXTM
        JP Z,DONE
        PUSH AF
        LD HL,AM
        CALL PRINT
        POP AF
        CALL PHEX
        JP CRMAIN

; W name text: make a new file holding one record: the text, then
; CR LF, then 1Ah to the end of the record.
CMDW:   CALL NEEDDOS
        LD DE,FCB
        CALL GETNAME
        JP C,WHAT
        CALL TEXTREC            ; the rest of the line into RECBUF
        LD DE,FCB
        LD C,19                 ; delete any old file of this name
        CALL 5
        LD DE,FCB
        LD C,22                 ; make
        CALL 5
        CP 0FFH
        LD HL,NOMAKEM
        JP Z,DONE
        LD DE,RECBUF
        LD C,26
        CALL 5
        LD DE,FCB
        LD C,21                 ; write the record
        CALL 5
        LD DE,FCB
        LD C,16                 ; close: now the directory knows
        CALL 5
        LD HL,WROTEM
        JP DONE

; TEXTREC: RECBUF = the rest of the command line, CR, LF, then 1Ah.
TEXTREC: LD HL,RECBUF
        LD B,128
TR1:    LD (HL),1AH
        INC HL
        DJNZ TR1
        CALL SKIPSP
        LD DE,RECBUF
        LD B,125
TR2:    LD A,(HL)
        OR A
        JR Z,TR3
        LD (DE),A
        INC HL
        INC DE
        DJNZ TR2
TR3:    LD A,13
        LD (DE),A
        INC DE
        LD A,10
        LD (DE),A
        RET

; E name: erase a file.
CMDE:   CALL NEEDDOS
        LD DE,FCB
        CALL GETNAME
        JP C,WHAT
        LD DE,FCB
        LD C,19
        CALL 5
        CP 0FFH
        LD HL,NOTFM
        JP Z,DONE
        LD HL,ERASEM
        JP DONE

; N old new: rename. The new name goes in FCB bytes 16-31.
CMDN:   CALL NEEDDOS
        LD DE,FCB
        CALL GETNAME
        JP C,WHAT
        LD DE,FCB2
        CALL GETNAME
        JP C,WHAT
        LD HL,FCB2
        LD DE,FCB+16
        LD BC,16
        LDIR
        XOR A
        LD (FCB+16),A           ; the new name's drive byte: 0
        LD DE,FCB
        LD C,23
        CALL 5
        CP 0FFH
        LD HL,NOTFM
        JP Z,DONE
        LD HL,RENM
        JP DONE

; P name n text: write the text as record n, with write random.
CMDP:   CALL NEEDDOS
        CALL OPENIT
        CALL GETNUM
        JP C,WHAT
        LD (FCB+33),HL
        XOR A
        LD (FCB+35),A
        CALL TEXTREC
        LD DE,RECBUF
        LD C,26
        CALL 5
        LD DE,FCB
        LD C,34                 ; write random
        CALL 5
        LD DE,FCB
        LD C,16
        CALL 5
        LD HL,WROTEM
        JP DONE

; NEEDDOS: back to MAIN with a message if there is no DOS to call.
NEEDDOS:
        LD A,(HASDOS)
        OR A
        RET NZ
        LD HL,NEEDM
        JP DONE
        RET

; ----------------------------------------------------------------- MOS
; S d t s: read drive d, track t, sector s and show it.
CMDS:   CALL GETDTS
        JP C,WHAT
CMDS1:  CALL READSEC
        JP NZ,DISCERR
        CALL TRIES
        LD HL,SECBUF
        LD BC,512
        CALL SHOWBLK
        JP MAIN

; GETDTS: drive, track, sector from the line into SDRV, STRK, SSEC.
GETDTS: CALL GETNUM
        RET C
        LD (SDRV),A
        CALL GETNUM
        RET C
        LD (STRK),A
        CALL SKIPSP             ; a letter is an ID in itself
        CP 'A'
        JR C,GDT1
        INC HL
        LD (LPTR),HL
        LD (SSEC),A
        OR A
        RET
GDT1:
        CALL GETNUM
        RET C
        LD (SSEC),A
        RET

; READSEC: MOS call 162 - the sector in SDRV/STRK/SSEC to SECBUF.
; Z set if it was read.
READSEC: CALL SETPAD
        RST 8
        DEFB 162
        RET

; SETPAD: the MOS's scratch-pad: drive, track, sector, address.
SETPAD: LD A,(SDRV)
        LD (0FB50H),A
        LD A,(STRK)
        LD (0FB51H),A
        LD A,(SSEC)
        LD (0FB52H),A
        LD HL,SECBUF
        LD (0FB53H),HL
        RET

; +: the next sector - the next ID, then sector 0 of the next track.
CMDNXT: LD HL,SSEC
        INC (HL)
        LD A,(HL)
        CP 10
        JP C,CMDS1
        LD (HL),0
        LD HL,STRK
        INC (HL)
        JP CMDS1

; DISCERR: the MOS said no. A holds its error number.
DISCERR: PUSH AF
        LD HL,DERRM
        CALL PRINT
        POP AF
        PUSH AF
        CALL PHEX
        POP AF
        CP 3
        LD HL,NOSECM
        JP Z,DONE
        CP 1
        LD HL,BADM
        JP Z,DONE
        JP CRMAIN

; TRIES: how many of its ten tries the MOS needed.
TRIES:  LD HL,TRIESM
        CALL PRINT
        LD A,(0FB57H)
        LD B,A
        LD A,11
        SUB B
        LD L,A
        LD H,0
        CALL PDEC
        JP NEWLINE

; D d: list the files on drive d by reading the directory itself.
CMDD:   CALL GETNUM
        JP C,WHAT
        LD (SDRV),A
        CALL READDIR
        JP NZ,DISCERR
        LD IX,DIRBUF
        LD B,64
CMDD1:  PUSH BC
        LD A,(IX+0)
        CP 0E5H                 ; E5h: an empty or deleted slot
        JR Z,CMDD2
        CALL SHOWENT
CMDD2:  LD DE,32
        ADD IX,DE
        POP BC
        DJNZ CMDD1
        JP MAIN

; READDIR: track 2 sectors 0-3 of drive SDRV into DIRBUF. Z if all read.
READDIR: LD A,2
        LD (STRK),A
        LD HL,DIRBUF
        LD B,0
RD1:    PUSH BC
        PUSH HL
        LD A,B
        LD (SSEC),A
        CALL SETPAD
        POP HL
        LD (0FB53H),HL          ; this sector's place in DIRBUF
        PUSH HL
        RST 8
        DEFB 162
        POP HL
        POP BC
        RET NZ
        INC H                   ; 512 bytes on: H + 2
        INC H
        INC B
        LD A,B
        CP 4
        JR NZ,RD1
        XOR A                   ; Z: all four read
        RET

; SHOWENT: one directory entry at IX: name, extent, record count.
SHOWENT: PUSH IX
        POP HL
        INC HL
        LD B,8
        CALL PNAME
        LD A,'.'
        CALL PUTC
        LD B,3
        CALL PNAME
        BIT 7,(IX+9)
        LD A,' '
        JR Z,SE1
        LD A,'*'                ; bit 7 of byte 9: locked
SE1:    CALL PUTC
        LD HL,EXTM
        CALL PRINT
        LD L,(IX+12)
        LD H,0
        CALL PDEC
        LD HL,RECM
        CALL PRINT
        LD L,(IX+15)
        LD H,0
        CALL PDEC
        JP NEWLINE

; PNAME: print B name characters from HL, without their top bits.
PNAME:  LD A,(HL)
        AND 7FH
        CALL PUTC
        INC HL
        DJNZ PNAME
        RET

; L d name: find a file in drive d's directory and type it, following
; its blocks to the sectors - what the BDOS's open and read did for you.
CMDL:   CALL FINDSET
        XOR A
        LD (EXTENT),A
CMDL1:  CALL FINDEXT
        JR NZ,CMDL9
        LD A,(IX+15)            ; records in this extent
        LD (RLEFT),A
        LD B,8
        PUSH IX
        POP HL
        LD DE,16
        ADD HL,DE               ; HL -> the block numbers
CMDL2:  PUSH BC
        LD E,(HL)
        INC HL
        LD D,(HL)
        INC HL
        PUSH HL
        LD A,D
        OR E
        JR Z,CMDL8              ; block 0: no more blocks
        LD B,4                  ; four sectors to a block
        LD C,0
CMDL3:  PUSH BC
        PUSH DE
        LD A,(RLEFT)
        OR A
        JR Z,CMDL7
        CALL BLKSEC             ; block DE, sector C -> STRK, SSEC
        CALL READSEC
        JP NZ,DISCERR
        LD A,(RLEFT)            ; up to four records from this sector
        LD B,4
        CP B
        JR NC,CMDL4
        LD B,A
CMDL4:  LD A,(RLEFT)
        SUB B
        LD (RLEFT),A
        LD HL,0
        LD DE,128
CMDL5:  ADD HL,DE
        DJNZ CMDL5              ; HL = 128 x records
        LD B,H
        LD C,L
        LD HL,SECBUF
        CALL TYPEOUT
        JR C,CMDL6              ; 1Ah: the text has ended
        POP DE
        POP BC
        INC C
        DJNZ CMDL3
        POP HL
        POP BC
        DJNZ CMDL2
        JR CMDLX
CMDL6:  JP CRMAIN
CMDL7:  POP DE
        POP BC
CMDL8:  POP HL
        POP BC
CMDLX:  LD HL,EXTENT
        INC (HL)
        JR CMDL1
CMDL9:  LD A,(EXTENT)
        OR A
        LD HL,NOTFM
        JP Z,DONE
        JP CRMAIN

; FINDSET: drive and name from the line; read the directory.
FINDSET: CALL GETNUM
        JP C,WHAT
        LD (SDRV),A
        LD DE,FCB
        CALL GETNAME
        JP C,WHAT
        CALL READDIR
        JP NZ,DISCERR
        RET

; FINDEXT: IX -> the entry for FCB's name with extent EXTENT. Z if found.
FINDEXT: LD IX,DIRBUF
        LD B,64
FE1:    LD A,(IX+0)
        CP 0E5H
        JR Z,FE3
        LD A,(EXTENT)
        CP (IX+12)
        JR NZ,FE3
        PUSH BC
        PUSH IX
        POP HL
        INC HL
        LD DE,FCB+1
        LD B,11
FE2:    LD A,(HL)
        AND 7FH                 ; ignore the locked bit
        LD C,A
        LD A,(DE)
        CP C
        JR NZ,FE4
        INC HL
        INC DE
        DJNZ FE2
        POP BC
        XOR A                   ; Z: found
        RET
FE4:    POP BC
FE3:    LD DE,32
        ADD IX,DE
        DJNZ FE1
        OR 1                    ; NZ: not there
        RET

; BLKSEC: block DE's sector C (0-3) -> STRK, SSEC.
; Logical sector 4 x block + C, counted from track 2 sector 0;
; track = logical / 10 + 2, sector = logical mod 10.
BLKSEC: EX DE,HL
        ADD HL,HL
        ADD HL,HL
        LD B,0
        ADD HL,BC
        CALL DIV10
        LD (SSEC),A
        LD A,L
        ADD A,2
        LD (STRK),A
        RET

; K offset text: put the text into the sector buffer at offset.
CMDK:   CALL GETNUM
        JP C,WHAT
        LD DE,SECBUF
        ADD HL,DE
        EX DE,HL
        LD HL,(LPTR)
        LD A,(HL)               ; one space, then the text exactly
        CP ' '
        JR NZ,CMDK1
        INC HL
CMDK1:  LD A,(HL)
        OR A
        JR Z,CMDK2
        LD (DE),A
        INC HL
        INC DE
        JR CMDK1
CMDK2:  LD HL,SECBUF
        LD BC,128
        CALL SHOWBLK
        JP MAIN

; M d t s: write the sector buffer to drive d, track t, sector s.
CMDM:   CALL GETDTS
        JP C,WHAT
        CALL SETPAD
        RST 8
        DEFB 163
        JP NZ,DISCERR
        LD HL,WROTEM
        JP DONE

; U d: read drive d's track 0 sector 0 and show its boot header.
CMDU:   CALL GETNUM
        JP C,WHAT
        LD (SDRV),A
        XOR A
        LD (STRK),A
        LD (SSEC),A
        CALL READSEC
        JP NZ,DISCERR
        LD IX,SECBUF
        LD HL,STARTM
        CALL PRINT
        LD L,(IX+0)
        LD H,(IX+1)
        CALL PHEXW
        LD HL,ENDM
        CALL PRINT
        LD L,(IX+2)
        LD H,(IX+3)
        CALL PHEXW
        LD HL,JUMPM
        CALL PRINT
        LD L,(IX+4)
        LD H,(IX+5)
        CALL PHEXW
        LD HL,SIDEM
        CALL PRINT
        LD A,(IX+6)
        CALL PHEX
        JP CRMAIN

; B: make the disc in drive 1 start DISKMON. A header goes in the 7
; bytes before 0100h - start 00F9h, end LAST, jump 0100h, side 0 - and
; from 00F9h on, as many sectors as that takes are written to track 0.
CMDB:   LD HL,00F9H
        LD (HL),0F9H            ; start: 00F9h
        INC HL
        LD (HL),00H
        INC HL
        LD DE,LAST
        LD (HL),E               ; end: LAST
        INC HL
        LD (HL),D
        INC HL
        LD (HL),00H             ; jump: 0100h
        INC HL
        LD (HL),01H
        INC HL
        LD (HL),0               ; side: 0
        LD A,1
        LD (0FB50H),A
        XOR A
        LD (0FB51H),A
        LD HL,00F9H
        LD B,(LAST-00F9H)/512+1 ; sectors from 00F9h to LAST
        LD C,0
CMDB1:  LD A,C
        LD (0FB52H),A
        LD (0FB53H),HL
        PUSH BC
        PUSH HL
        RST 8
        DEFB 163
        POP HL
        POP BC
        JP NZ,DISCERR
        INC H
        INC H
        INC C
        DJNZ CMDB1
        LD L,(LAST-00F9H)/512+1
        LD H,0
        CALL PDEC
        LD HL,BOOTM
        JP DONE

; J d name n: record n of a file, found by hand - Blackwater's way of
; reading its data, with no DOS.
CMDJ:   CALL FINDSET
        CALL GETNUM
        JP C,WHAT
        LD (RECNO),HL
        ADD HL,HL               ; extent = record / 128
        LD A,H
        LD (EXTENT),A
        CALL FINDEXT
        LD HL,NOEXTM
        JP NZ,DONE
        LD A,(RECNO)
        AND 7FH                 ; record within the extent
        CP (IX+15)
        LD HL,NORECM
        JP NC,DONE
        LD C,A
        RRCA                    ; block index = (record mod 128) / 16
        RRCA
        RRCA
        RRCA
        AND 07H
        ADD A,A
        ADD A,16
        LD E,A
        LD D,0
        PUSH IX
        POP HL
        ADD HL,DE
        LD E,(HL)
        INC HL
        LD D,(HL)
        LD A,C                  ; sector within the block = (record mod 16) / 4
        RRCA
        RRCA
        AND 03H
        PUSH BC
        LD C,A
        CALL BLKSEC
        CALL READSEC
        POP BC
        JP NZ,DISCERR
        LD A,C                  ; place in the sector = (record mod 4) x 128
        AND 03H
        LD H,A
        LD L,0
        SRL H
        RR L
        LD DE,SECBUF
        ADD HL,DE
        LD BC,128
        CALL SHOWBLK
        JP MAIN

; ------------------------------------------------- the controller itself
; G d t: move drive d's head to track t yourself: Restore, then Seek.
CMDG:   CALL GETNUM
        JP C,WHAT
        LD (SDRV),A
        CALL GETNUM
        JP C,WHAT
        LD (STRK),A
        CALL FDCSEL
        CALL FDCHOME
        PUSH AF
        CALL FDCSEEK
        LD B,A
        IN A,(19H)              ; the track register
        LD C,A
        EI
        POP AF
        PUSH BC
        PUSH AF
        LD HL,RESTM
        CALL PRINT              ; PRINT leaves A = 0, so keep the status
        POP AF
        CALL PHEX
        POP BC
        PUSH BC
        LD HL,SEEKM
        CALL PRINT
        LD A,B
        CALL PHEX
        LD HL,TRKM
        CALL PRINT
        POP BC
        LD L,C
        LD H,0
        CALL PDEC
        CALL MOSTRK             ; tell the MOS where the head is now
        LD A,(STRK)
        LD (HL),A
        JP CRMAIN

; FDCSEL: interrupts off; select drive SDRV (port 23h: 1 drive 0, 2 drive 1).
FDCSEL: DI
        LD A,(SDRV)
        LD B,A
        INC B
        LD A,1
        JR FS2
FS1:    ADD A,A
FS2:    DJNZ FS1
        OUT (23H),A
        RET

; FDCHOME: Restore - head to track 0. A = the status after.
FDCHOME: LD A,(0FBB0H)          ; the MOS's step rate
        OR 00H
        JR FDCCMD

; FDCSEEK: Seek to track STRK. A = the status after.
FDCSEEK: LD A,(STRK)
        OUT (1BH),A             ; the track to go to, in the data register
        LD A,(0FBB0H)
        OR 10H
; FDCCMD: give the controller command A, and wait until it has done it.
FDCCMD: OUT (18H),A
        CALL BUSYON
; FDCWAIT: wait until the controller is not busy. A = its status.
FDCWAIT: IN A,(18H)
        BIT 0,A                 ; bit 0: busy
        JR NZ,FDCWAIT
        RET

; BUSYON: wait (a short while at most) for the controller to say it is
; busy - it takes a moment to start a command, as the MOS knows.
BUSYON: PUSH BC
        LD B,0
BO1:    IN A,(18H)
        BIT 0,A
        JR NZ,BO2
        DJNZ BO1
BO2:    POP BC
        RET

; MOSTRK: HL -> the MOS's record of where drive SDRV's head is (FB58h+d).
MOSTRK: LD HL,0FB58H
        LD A,(SDRV)
        LD E,A
        LD D,0
        ADD HL,DE
        RET

; PUTBACK: seek back to the track the MOS thinks the head is on.
PUTBACK: CALL MOSTRK
        LD A,(HL)
        LD (STRK),A
        CALL FDCSEEK
        EI
        RET

; I d t: the IDs on drive d's track t, in the order they pass the head,
; with Read Address - twenty of them, so you see the round twice.
CMDI:   CALL GETNUM
        JP C,WHAT
        LD (SDRV),A
        CALL GETNUM
        JP C,WHAT
        LD (STRK),A
        CALL FDCSEL
        CALL FDCHOME
        CALL FDCSEEK
        LD HL,RECBUF
        LD B,20
CMDI1:  PUSH BC
        LD A,0C0H               ; Read Address: 6 bytes from the next ID
        OUT (18H),A
        CALL BUSYON
        LD DE,RECBUF+120        ; the 6 bytes land here
        LD B,6
CMDI2:  IN A,(18H)
        BIT 1,A                 ; bit 1: a byte is waiting
        JR NZ,CMDI3
        BIT 0,A
        JR NZ,CMDI2
        JR CMDI4
CMDI3:  IN A,(1BH)
        LD (DE),A
        INC DE
        DJNZ CMDI2
CMDI4:  CALL FDCWAIT
        LD A,(RECBUF+122)       ; the third byte is the sector ID
        LD (HL),A
        INC HL
        POP BC
        DJNZ CMDI1
        CALL PUTBACK
        LD HL,IDSM
        CALL PRINT
        LD HL,RECBUF            ; as characters...
        LD B,20
CMDI5:  LD A,(HL)
        CALL PCHAR
        INC HL
        DJNZ CMDI5
        CALL NEWLINE
        LD HL,RECBUF            ; ...and in hex
        LD BC,20
        CALL SHOWBLK
        JP MAIN

; H d t s: read a sector through the controller's ports, byte by byte.
CMDH:   CALL GETDTS
        JP C,WHAT
        CALL FDCSEL
        CALL FDCHOME
        CALL FDCSEEK
        LD A,(SSEC)
        OUT (1AH),A             ; the ID to look for
        LD HL,SECBUF
        LD DE,0
        LD A,80H                ; Read Sector
        OUT (18H),A
        CALL BUSYON
CMDH1:  IN A,(18H)
        BIT 1,A
        JR NZ,CMDH2
        BIT 0,A
        JR NZ,CMDH1
        JR CMDH3
CMDH2:  IN A,(1BH)
        LD (HL),A
        INC HL
        INC DE
        JR CMDH1
CMDH3:  LD (COUNT),DE
        PUSH AF
        CALL PUTBACK
        LD HL,STATM
        CALL PRINT
        POP AF
        CALL PHEX
        LD HL,BYTESM
        CALL PRINT
        LD HL,(COUNT)
        CALL PDEC
        CALL NEWLINE
        LD HL,SECBUF
        LD BC,512
        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

; TYPEOUT: print BC bytes from HL as text, stopping at 1Ah (carry set).
TYPEOUT: LD A,(HL)
        CP 1AH
        SCF
        RET Z
        CALL PUTC
        INC HL
        DEC BC
        LD A,B
        OR C
        JR NZ,TYPEOUT
        RET                     ; carry clear: no 1Ah yet

; ----------------------------------------------------------------- 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
OPENM:  DEFB 'OPEN: ',0
NOTFM:  DEFB 'NOT FOUND',13,10,0
COUNT:  DEFW 0
RECSM:  DEFB ' RECORDS',13,10,0
AM:     DEFB 'A=',0
NOMAKEM: DEFB 'CANNOT MAKE IT',13,10,0
WROTEM: DEFB 'WRITTEN',13,10,0
FCB2:   DEFS 36
ERASEM: DEFB 'ERASED',13,10,0
RENM:   DEFB 'RENAMED',13,10,0
NORECM: DEFB 'NO SUCH RECORD',13,10,0
NOEXTM: DEFB 'NO SUCH EXTENT',13,10,0
SDRV:   DEFB 0
STRK:   DEFB 0
SSEC:   DEFB 0
DERRM:  DEFB 'DISC ERROR ',0
NOSECM: DEFB ': NO SECTOR',13,10,0
BADM:   DEFB ': BAD DATA',13,10,0
EXTM:   DEFB ' EXTENT ',0
RECM:   DEFB ' RECORDS ',0
EXTENT: DEFB 0
RLEFT:  DEFB 0
STARTM: DEFB 'START ',0
ENDM:   DEFB ' END ',0
JUMPM:  DEFB ' JUMP ',0
SIDEM:  DEFB ' SIDE ',0
BOOTM:  DEFB ' SECTORS WRITTEN TO TRACK 0',13,10,0
HASDOS: DEFB 0
NODOSM: DEFB 'NO DOS: SECTORS ONLY',13,10,0
NEEDM:  DEFB 'NEEDS THE DOS',13,10,0
STOPM:  DEFB 'STOPPED',13,10,0
RECNO:  DEFW 0
RESTM:  DEFB 'RESTORE ',0
SEEKM:  DEFB ' SEEK ',0
TRKM:   DEFB ' TRACK ',0
TRIESM: DEFB 'TRIES ',0
IDSM:   DEFB 'IDS: ',0
STATM:  DEFB 'STATUS ',0
BYTESM: DEFB ' BYTES ',0
LAST:   DEFB 0                  ; the last byte the boot loads

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.