Zapple-II/SOFTCARD80.ASM#040000

3009 lines
102 KiB
Plaintext

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; Z80 code running on Softcard
; Implements CP/M style BDOS interface
; Requires the companion SOFTCARD65 6502 code
; Assemble using Udo Munk's Z80asm.
; Tabstops every 4 chars.
; Bobbi 2019
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; References:
; 1) BDOS System Calls
; https://www.seasip.info/Cpm/bdos.html
; 2) Programmer's CP/M Handbook - Johnson-Laird
; 3) ProDOS 8 Technical Reference Manual
; http://www.easy68k.com/paulrsm/6502/PDOS8TRM.HTM
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;
; TODO: Look further down for CCP TODOs!!!!!!!
;
; BDOS TODOs
; ----------
; TODO: STAT B:*.* leaves current drive set to B! Probably other progs too.
; TODO: Size information from NAME2FCB seems to be a bit fishy! I think the
; issue here is that STAT needs to see all the extents, whereas NSWEEP
; uses the highest extent number it sees, and doesn't really care about
; the others.
; TODO: PIP has issues with multi file copy, and I think it is because it only
; closes the destination files, but not the source files. Maybe the
; solution is to close files after each read/write. The FCB keeps track
; of the position in any case and we always seek, so this should work.
; TODO: Need to implement the BIOS entry points and jump table (see MG's Ruby)
; TODO: Needs proper boot / warm boot entry points
; TODO: [ F_WRITE bug turns out to be bug in ProDOS 2.5.0a7 (SET_MARK) ??? ]
; TODO: Maybe I should eliminate use of "EX AF,AF'" in BDOS since CP/M apps
; may expect exclusive use of alternate register set.
; TODO: Implement missing system calls:
; - F_ATTRIB (needs to support wildcards, leave FCB at DMAADDR)
; - RS232 (A_READ, A_WRITE)
; - Printer (LWRITE)
; TODO: IOBYTE doesn't do anything
; TODO: User number doesn't do anything
; TODO: Software R/O disk setting is not respected
; TODO: C_WRITE - handle tabs
; Other Random TODO comments in the code
;
BDOSADDR EQU 08400H ;
STCKTOP EQU 097FFH ; Top of Z80 stack (below IOBUFs)
SOFTCARD EQU 0E400H ; Softcard in slot 4 ($C400)
OFFSET EQU 01000H ; Offset to add to Z80 addr to get 6502
; address. Correct for Z80 addr <= 0AFFFH
; 6502 zero page, in Z80 address space
CMD EQU 0F006H ; 6502 $06
AREG EQU 0F007H ; 6502 $07
XREG EQU 0F008H ; 6502 $08
YREG EQU 0F009H ; 6502 $09
ADDR EQU 0F0EBH ; 6502 $EB (LSB)
ADDRH EQU 0F0ECH ; 6502 $EC (MSB)
; Addresses of 6502 routines, in 6502 address space
COUT EQU 0FDEDH ; Print char in A
RDKEY EQU 0FD0CH ; Read key, return in A
BELL EQU 0FBE4H ; Sound the bell
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; Entry point when Z80 cold starts is 0000H
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
ORG 0000H
JP BDOSINIT ; Initialize BDOS
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; See obsolescence.wix.com/obsolescence/cpm-internals for info
; on low storage usage ...
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
IOBYTE DEFB 0 ; Maps virtual to real devices
CURDRV DEFB 0 ; LS 4 bits; Current drive 0=A: etc
; MS 4 bits: Current user number
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; BDOS entry point must be at address 0005H for CP/M compatibility
; Function to invoke is passed in C
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
BDOS JP BDOSIMP ; BDOS code is at top of memory
RVEC1 DEFW 0000H ; Restart vector 1
RVEC2 DEFW 0000H ; Restart vector 2
RVEC3 DEFW 0000H ; Restart vector 3
RVEC4 DEFW 0000H ; Restart vector 4
RVEC5 DEFW 0000H ; Restart vector 5
RVEC6 DEFW 0000H ; Restart vector 6
RVEC7 DEFW 0000H ; Restart vector 7
; Space for private BDOS data (implementation dependent) up to 005BH
DMAADDR DEFW 0080H ; DMA address defaults to FILEBUF (0080H)
LOGVEC DEFW 0000H ; Vector of logged in drives
ROVEC DEFW 0000H ; Vector of read-only drives
TEMPWORD DEFW 0000H ; Used by routines as a scratch space
TEMPBYTE DEFB 0 ; Used by routines as a scratch space
; End of private, implementation dependent space
ORG 005CH ; Standard addr of 32 byte FCB1
FCB1 ; File control block #1
FCB1DRV DEFB 00H ; FCB Drive (0 current, 1 A:, 2 B: etc)
FCB1NAM DEFM 'FILENAMEEXT' ; FCB filename and extension
FCB1EX DEFB 00H ; FCB extent field
FCB1S1 DEFB 00H ; FCB S1 field
FCB1S2 DEFB 00H ; FCB S2 field
FCB1RC DEFB 00H ; FCB RC field (# recs used this extent)
FCB1MAP ; Map of blocks in file (overlaps FCB2)
ORG 006CH ; Standard addr of 32 byte FCB2
FCB2 ; File control block #2
FCB2DRV DEFB 00H ; FCB Drive (0 current, 1 A:, 2 B: etc)
FCB2NAM DEFM 'FILENAMEEXT' ; FCB filename and extension
FCB2EX DEFB 00H ; FCB extent field
FCB2S1 DEFB 00H ; FCB S1 field
FCB2S2 DEFB 00H ; FCB S2 field
FCB2RC DEFB 00H ; FCB RC field (# recs used this extent)
FCB2MAP ; Map of blocks in file (overlaps buffer)
ORG 0080H ; Standard addr of 128 byte File Buffer
FILEBUF DEFS 128 ; Command args go here too (Pascal string)
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; The application program proper starts at 0100H
; in order to be compatible with CP/M .COM programs
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
ORG 0100H
; Print signon message using C_WRITESTR
PROGSTRT LD DE,WELCOME ; Address of string
LD C,B_C_WRTSTR ;
CALL BDOS ;
CALL CCP ; Run the CCP
; Print the alphabet using C_WRITE
LD B,'A' ; First character
L1 LD E,B ; Character to print
LD C,B_C_WRITE ;
PUSH BC ; Preserve B (and C)
CALL BDOS ;
POP BC ; Restore B (and C)
INC B ;
LD A,'Z' ; Last character
CP B ;
JP Z,S1 ;
JP L1 ;
; Loop until there is a keystroke waiting using C_STAT
S1 LD C,B_C_STAT ;
CALL BDOS ;
CP 0 ; Anything?
JR Z,S1 ; If not, loop
; Print a couple of asterisks
LD E,'*' ;
LD C,B_C_WRITE ;
CALL BDOS ;
LD E,'*' ;
LD C,B_C_WRITE ;
CALL BDOS ;
; Create FCB1 'A:TEST.TXT'
LD A,1 ; A: drive
LD (FCB1DRV),A ;
LD A,'T' ; Filename
LD (FCB1NAM),A ;
LD A,'E' ; Filename
LD (FCB1NAM+1),A ;
LD A,'S' ; Filename
LD (FCB1NAM+2),A ;
LD A,'T' ; Filename
LD (FCB1NAM+3),A ;
LD A,' ' ; Filename
LD (FCB1NAM+4),A ;
LD A,' ' ; Filename
LD (FCB1NAM+5),A ;
LD A,' ' ; Filename
LD (FCB1NAM+6),A ;
LD A,' ' ; Filename
LD (FCB1NAM+7),A ;
LD A,'T' ; Extension
LD (FCB1NAM+8),A ;
LD A,'X' ; Extension
LD (FCB1NAM+9),A ;
LD A,'T' ; Extension
LD (FCB1NAM+10),A ;
; Create FCB2 'A:????????.???'
LD A,1 ; A: drive
LD (FCB2DRV),A ;
LD A,'?' ; Filename
LD (FCB2NAM),A ;
LD (FCB2NAM+1),A ;
LD (FCB2NAM+2),A ;
LD (FCB2NAM+3),A ;
LD (FCB2NAM+4),A ;
LD (FCB2NAM+5),A ;
LD (FCB2NAM+6),A ;
LD (FCB2NAM+7),A ;
LD (FCB2NAM+8),A ;
LD (FCB2NAM+9),A ;
LD (FCB2NAM+10),A ;
; Create and open a file using ProDOS MLI
; Creates 'A/TEST.TXT'
; Directory 'A' needs to exist already
LD DE,CMSG ; Address of string
LD C,B_C_WRTSTR ;
CALL BDOS ;
LD DE,FCB1 ; Default FCB address
LD C,B_F_MAKE ;
CALL BDOS ;
CALL CHECKOK
; Set the DMA buffer to point to our text
LD DE,TEXTBUF ;
LD C,B_F_DMAOFF ;
CALL BDOS ;
; Write to the file
LD DE,WMSG ; Address of string
LD C,B_C_WRTSTR ;
CALL BDOS ;
LD DE,FCB1 ; Default FCB address
LD C,B_F_WRITE ;
CALL BDOS ;
CALL CHECKOK
LD DE,FCB1 ; Default FCB address
LD C,B_F_WRITE ;
CALL BDOS ;
CALL CHECKOK
LD DE,FCB1 ; Default FCB address
LD C,B_F_WRITE ;
CALL BDOS ;
CALL CHECKOK
LD DE,FCB1 ; Default FCB address
LD C,B_F_WRITE ;
CALL BDOS ;
CALL CHECKOK
LD DE,FCB1 ; Default FCB address
LD C,B_F_WRITE ;
CALL BDOS ;
CALL CHECKOK
LD DE,FCB1 ; Default FCB address
LD C,B_F_WRITE ;
CALL BDOS ;
CALL CHECKOK
; Close the file
LD DE,CLMSG ; Address of string
LD C,B_C_WRTSTR ;
CALL BDOS ;
LD DE,FCB1 ; Default FCB address
LD C,B_F_CLOSE ;
CALL BDOS ;
CALL CHECKOK
; Set the DMA buffer to point to FILEBUF (0080H)
LD DE,FILEBUF ;
LD C,B_F_DMAOFF ;
CALL BDOS ;
; Search for the file in the directory
LD DE,SFMSG ; Address of string
LD C,B_C_WRTSTR ;
CALL BDOS ;
LD DE,FCB1 ; Default FCB address
LD C,B_F_SFIRST ;
CALL BDOS ;
CP 0 ;
JP Z,FOUND ;
LD DE,SFMSGNF ;
JP PRFNF ;
FOUND LD DE,SFMSGF ;
PRFNF LD C,B_C_WRTSTR ;
CALL BDOS ;
;CALL CHECKOK
; Search for all files in the directory using wildcards
LD DE,SFMSG2 ; Address of string
LD C,B_C_WRTSTR ;
CALL BDOS ;
LD DE,FCB2 ; Default FCB address 2
LD C,B_F_SFIRST ;
CALL BDOS ;
CP 0 ;
JP Z,FOUND2 ;
LD DE,SFMSGNF ;
JP PRFNF2 ;
FOUND2 LD A,13 ; HACK to terminate string
LD (FILEBUF+12),A ;
LD A,'$' ;
LD (FILEBUF+13),A ;
LD DE,FILEBUF+1 ;
LD C,B_C_WRTSTR ;
CALL BDOS ;
JP DIRLOOP ; Jump forwards to DIR loop
PRFNF2 LD C,B_C_WRTSTR ;
CALL BDOS ;
DIRLOOP LD DE,FCB2 ; Default FCB address 2
LD C,B_F_SNEXT ;
CALL BDOS ;
CP 0 ;
JP Z,FOUND3 ;
LD DE,SFMSGNF ;
JP PRFNF3 ;
FOUND3 LD A,13 ; HACK to terminate string
LD (FILEBUF+12),A ;
LD A,'$' ;
LD (FILEBUF+13),A ;
LD DE,FILEBUF+1 ;
LD C,B_C_WRTSTR ;
CALL BDOS ;
JP DIRLOOP ; Loop for all files in dir
PRFNF3 LD C,B_C_WRTSTR ;
CALL BDOS ;
;CALL CHECKOK
; Overwrite DMA buffer just to be sure it is read
LD A,'X' ;
LD HL,(DMAADDR) ;
LD (HL),A ;
INC HL
LD (HL),A ;
INC HL
LD (HL),A ;
; Open the file
LD DE,OMSG ; Address of string
LD C,B_C_WRTSTR ;
CALL BDOS ;
LD DE,FCB1 ; Default FCB address
LD C,B_F_OPEN ;
CALL BDOS ;
CALL CHECKOK
; Read from the file
LD DE,RMSG ; Address of string
LD C,B_C_WRTSTR ;
CALL BDOS ;
LD DE,FCB1 ; Default FCB address
LD C,B_F_READ ;
CALL BDOS ;
CALL CHECKOK
; Print out what we just read
LD DE,(DMAADDR) ;
LD C,B_C_WRTSTR ;
CALL BDOS ;
END
; Close the file
LD DE,CLMSG ; Address of string
LD C,B_C_WRTSTR ;
CALL BDOS ;
LD DE,FCB1 ; Default FCB address
LD C,B_F_CLOSE ;
CALL BDOS ;
CALL CHECKOK
; Delete the file
LD DE,DMSG ; Address of string
LD C,B_C_WRTSTR ;
CALL BDOS ;
LD DE,FCB1 ; Default FCB address
LD C,B_F_DELETE ;
CALL BDOS ;
CALL CHECKOK
; Read keyboard and echo to screen C_READ, C_WRITE
L2 LD C,B_C_READ ;
CALL BDOS ;
LD E,A ; Prepare to echo keystroke
LD C,B_C_WRITE ;
CALL BDOS ;
JP L2 ; Forever and ever
; Check an MLI call was successful and print out message accordingly
CHECKOK LD A,(AREG) ; Look at the return code
CP 0 ; Success?
JP Z,COKS1 ;
PUSH AF ; Preserve A
LD DE,FAILMSG1 ; Fail message
CALL C_WRITESTR ;
POP AF ; Restore A
LD L,A ; Copy to HL for NUM2HEX
LD H,0 ; ...
LD DE,HEXBUF ; Generate hex string to HEXBUF
CALL NUM2HEX ; ...
LD A,0FFH ; 0FFH for error
LD L,A ; Return code in L also
LD DE,HEXBUF+2 ; Write hex value to console
CALL C_WRITESTR ;
LD DE,FAILMSG2 ; Fail message
CALL C_WRITESTR ;
JP COKS2 ;
COKS1 LD DE,SUCCMSG ; Success message
CALL C_WRITESTR ;
COKS2 LD (TEMPWORD),SP ; Print out stack pointer
LD HL,(TEMPWORD) ;
LD DE,HEXBUF ; Generate hex string to HEXBUF
CALL NUM2HEX ;
LD DE,HEXBUF ;
CALL C_WRITESTR ;
LD DE,CRMSG ; Carriage return
CALL C_WRITESTR ;
RET
WELCOME DEFB 13
DEFM 'Zapple-II Test Stub...'
DEFB 13, '$'
CMSG DEFB 13
DEFM 'Creating & opening A/TEST.TXT'
DEFB 13, '$'
WMSG DEFB 13
DEFM 'Writing record to A/TEST.TXT'
DEFB 13, '$'
CLMSG DEFB 13
DEFM 'Closing A/TEST.TXT'
DEFB 13, '$'
SFMSG DEFB 13
DEFM 'Searching directory for TEST.TXT'
DEFB 13, '$'
SFMSGF DEFB 'Found'
DEFB 13, '$'
SFMSGNF DEFB 'NOT found'
DEFB 13, '$'
SFMSG2 DEFB 13
DEFM 'Searching directory for ????????.???'
DEFB 13, '$'
OMSG DEFB 13
DEFM 'Opening A/TEST.TXT'
DEFB 13, '$'
RMSG DEFB 13
DEFM 'Reading record from A/TEST.TXT'
DEFB 13, '$'
DMSG DEFB 13
DEFM 'Deleting A/TEST.TXT'
DEFB 13, '$'
SUCCMSG DEFM 'Success! SP=$'
FAILMSG1 DEFM 'FAIL (0x$'
FAILMSG2 DEFM ') SP=$'
CRMSG DEFB 13, '$'
TEXTBUF DEFM 'Mary had a little lamb. Its fleece was white as snow. '
DEFM 'And everywhere that Mary went, that lamb was sure to go.$'
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; Implementation of CP/M STYLE BDOS
; Function to invoke is passed in C, as follows:
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
B_C_TERMCPM EQU 00H ; System reset
B_C_READ EQU 01H ; Console read
B_C_WRITE EQU 02H ; Console write
B_C_RAWIO EQU 06H ; Direct console I/O
B_GET_IOB EQU 07H ; Get IOBYTE
B_SET_IOB EQU 08H ; Set IOBYTE
B_C_WRTSTR EQU 09H ; Console write string
B_C_RDSTR EQU 0AH ; Read console string
B_C_STAT EQU 0BH ; Console status
B_S_BDOSVER EQU 0CH ; Return version number
B_DRV_ALLRST EQU 0DH ; Reset disks
B_DRV_SET EQU 0EH ; Select disk
B_F_OPEN EQU 0FH ; Open file
B_F_CLOSE EQU 10H ; Close file
B_F_SFIRST EQU 11H ; Search for first match in directory
B_F_SNEXT EQU 12H ; Search for next match in directory
B_F_DELETE EQU 13H ; Delete file
B_F_READ EQU 14H ; Read file sequentially
B_F_WRITE EQU 15H ; Write file sequentially
B_F_MAKE EQU 16H ; Create and open file
B_F_RENAME EQU 17H ; Rename file
B_DRV_LOGVEC EQU 18H ; Return bitmap of logged-in drives
B_DRV_GET EQU 19H ; Return current drive
B_F_DMAOFF EQU 1AH ; Set DMA address
B_DRV_AVEC EQU 1BH ; Return address of allocation map
B_DRV_SRO EQU 1CH ; Software write-protect current drive
B_DRV_ROVEC EQU 1DH ; Return bitmap of read-only drives
B_DRV_DPB EQU 1FH ; Get Drive Parameter Block address
B_F_USERNUM EQU 20H ; Get/set user number
B_F_RDRAND EQU 21H ; Random access read record
B_F_WRTRAND EQU 22H ; Random access write record
B_F_SIZE EQU 23H ; Compute file size
B_F_RANDREC EQU 24H ; Update random access pointer
B_DRV_RESET EQU 25H ; Selectively reset disk drives
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
ORG BDOSADDR
BDOSINIT DI ; Make sure interrupts are off
LD SP,STCKTOP ; Initialize SP
XOR A ; A=0
LD (IOBYTE),A ; Initialize IOBYTE
LD (CURDRV),A ; Drive A:, User 0
LD (FILEBUF),A ; Zero chars in command tail
XOR A ; A=0 means close all files
LD (FCMLIN),A ; Store in parameter list
LD HL,FCMLI ; Pass address of 6502 JSR instruction
CALL PRODOS ; Invoke ProDOS MLI to close all files
LD (FRN1),A ; Initialize FRNs to zero
LD (FRN2),A ; ...
LD (FRN3),A ; ...
LD (FRN4),A ; ...
LD HL,FILEBUF ; Initialize DMAADDR to 0080H
LD (DMAADDR),HL ; ...
LD HL,0000H ; Initialize LOGVEC & ROVEC to 0000H
LD (LOGVEC),HL ; ...
LD (ROVEC),HL ; ...
JP CCP ; Start the CCP
BDOSIMP
;; CALL PRHEX ; Print sys call number
LD A,C ; Prepare to check C is in range
CP 41 ; Max syscall# for CP/M 2.2 is 40
JP NC,UNIMP ; If >41 then call UNIMP
LD HL,BDOSVEC ; Start of vector table
SLA C ; Multiply C by 2
LD B,0 ; MSB of BC is zero
ADD HL,BC ; Address of vector in HL
LD C,(HL) ; Read LSB of address to jump to
INC HL ; Read MSB of address to jump to
LD H,(HL) ; ...
LD L,C ; Address needs to be in HL
JP (HL) ; Jump to it!
; Vector table
BDOSVEC DEFW C_TERMCPM ; C=00H
DEFW C_READ ; C=01H
DEFW C_WRITE ; C=02H
DEFW UNIMP ; C=03H (A_READ) AUX
DEFW UNIMP ; C=04H (A_WRITE) AUX
DEFW UNIMP ; C=05H (L_WRITE) PRN
DEFW C_RAWIO ; C=06H
DEFW GET_IOB ; C=07H
DEFW SET_IOB ; C=08H
DEFW C_WRITESTR ; C=09H
DEFW C_READSTR ; C=0AH
DEFW C_STAT ; C=0BH
DEFW S_BDOSVER ; C=0CH
DEFW DRV_ALLRST ; C=0DH
DEFW DRV_SET ; C=0EH
DEFW F_OPEN ; C=0FH
DEFW F_CLOSE ; C=10H
DEFW F_SFIRST ; C=11H
DEFW F_SNEXT ; C=12H
DEFW F_DELETE ; C=13H
DEFW F_READ ; C=14H
DEFW F_WRITE ; C=15H
DEFW F_MAKE ; C=16H
DEFW F_RENAME ; C=17H
DEFW DRV_LOGVEC ; C=18H
DEFW DRV_GET ; C=19H
DEFW F_DMAOFF ; C=1AH
DEFW DRV_AVEC ; C=1BH
DEFW DRV_SETRO ; C=1CH
DEFW DRV_ROVEC ; C=1DH
DEFW UNIMP ; C=1EH (F_ATTRIB)
DEFW DRV_DPB ; C=1FH
DEFW F_USERNUM ; C=20H
DEFW F_READRAND ; C=21H
DEFW F_WRITERAND ; C=22H
DEFW F_SIZE ; C=23H
DEFW F_RANDREC ; C=24H
DEFW DRV_RESET ; C=25H
DEFW UNIMP ; C=26H (*nothing* in CP/M 2.2)
DEFW UNIMP ; C=27H (*nothing* in CP/M 2.2)
DEFW F_WRITERAND ; C=28H (F_WRITEZF)
; Unimplemented BDOS call, just ring the bell
UNIMP LD HL,BELL ; We are going to call BELL
LD (ADDR),HL ; ...
LD A,1 ; CMD=1 means call 6502 sub
LD (CMD),A ; ...
LD (SOFTCARD),A ; Do it!
RET ; Return to calling program
; System reset. Jump to $0000 - doesn't return
C_TERMCPM RST 0 ; Quick jump to zero
; Wait for a character from the console, return it in A and L
; Also echoes the char to the console
C_READ LD HL,RDKEY ; We are going to call RDKEY
LD (ADDR),HL ; ...
LD A,1 ; CMD=1 means call 6502 sub
LD (CMD),A ; ...
LD (SOFTCARD),A ; Do it!
LD A,(AREG) ; Grab the return value
CP 83H ; See if it is Ctrl-C
JP Z,BDOSINIT ; If Ctrl-C quit user program, go to CCP
PUSH AF ; Preserve A (and F)
LD HL,COUT ; Echo the character using COUT
LD (ADDR),HL ; ...
LD A,1 ; CMD=1 means call 6502 sub
LD (CMD),A ; ...
LD (SOFTCARD),A ; Do it!
POP AF ; Restore A (and F)
AND 7FH ; Mask high bit
LD L,A ; Copy A to L
RET ; Return to calling program
; Write character in E to the console
; TODO: Handle tabs
C_WRITE LD A,80H ; Set high bit
OR E ; ...
CP 8AH ; Check for linefeed
RET Z ; If LF, don't print it
LD (AREG),A ; Pass char to COUT in 6502 A
LD HL,COUT ; We are going to call COUT
LD (ADDR),HL ; ...
LD A,1 ; CMD=1 means call 6502 sub
LD (CMD),A ; ...
LD (SOFTCARD),A ; Do it!
RET ; Return to calling program
; If E if 0FFH then input a character from console and return it in A and L
; without echoing the input character. Otherwise output char in E to the
; console (no tabs, ^S or ^Q supported)
C_RAWIO LD A,E ; See if E if 0FFH
CP 0FFH ; ...
JP Z,RIS1 ; If so, then read
; Write to console
LD A,80H ; Set high bit
OR E ; ...
CP 8AH ; Check for linefeed
RET Z ; If LF, don't print it
LD (AREG),A ; Pass char to COUT in 6502 A
LD HL,COUT ; We are going to call COUT
LD (ADDR),HL ; ...
LD A,1 ; CMD=1 means call 6502 sub
LD (CMD),A ; ...
LD (SOFTCARD),A ; Do it!
RET
; If character is waiting, read from console & return in A
; Otherwise, return 00H in A
RIS1 LD A,3 ; CMD=3 means peek at keyboard
LD (CMD),A ; ...
LD (SOFTCARD),A ; Do it
LD A,(AREG) ; Grab the return value
CP 0 ; If zero, no chars are waiting
JP Z,RIS2 ; ...
LD HL,RDKEY ; We are going to call RDKEY
LD (ADDR),HL ; ...
LD A,1 ; CMD=1 means call 6502 sub
LD (CMD),A ; ...
LD (SOFTCARD),A ; Do it!
LD A,(AREG) ; Grab the return value
AND 7FH ; Mask high bit
LD L,A ; Copy A to L
RET ;
RIS2 XOR A ; No chars waiting, A=0
LD L,A ; Return in L also
RET
; Get the IOBYTE in A and L
GET_IOB LD A,(IOBYTE) ;
LD L,A ; Copy to L
RET
; Set the IOBYTE
; E contains the IOBYTE value to set
SET_IOB LD A,E ;
LD (IOBYTE),A ;
RET
; Write ASCII string to console. '$' is the terminator
; DE contains the address of the string
C_WRITESTR LD A,(DE) ; Fetch character from string
CP '$' ; Is it '$'?
RET Z ; If so, we are done
PUSH DE ; We are gonna need E
LD E,A ; For C_WRITE
CALL C_WRITE ; Sent char to console
POP DE ; Recover the pointer
INC DE ; Advance pointer
JP C_WRITESTR ; Handle the next char
; Read console string
; DE points to the string buffer. First byte of the buffer is the capacity
; of the buffer. This function writes the number of bytes written in second
; byte. Entry finishes on CR or when the buffer is filled up.
; Supports ^H, DEL for deleting a character
; Supports ^Z for deleting entire line (since ^X doesn't work with RDKEY
C_READSTR LD H,D ; HL will be the working pointer
LD L,E ; ...
INC HL ; Advance to first character ...
INC HL ; ... 3rd byte of the buffer
PUSH DE ; Put DE into IX
POP IX ; ...
XOR A ; Set number of chars read to zero
LD (IX+1),A ; ...
CRSL1 PUSH HL ; Preserve HL
CALL C_READ ; Read a character into A
POP HL ; Restore HL
CP 13 ; Carriage return ^M pressed?
RET Z ; If so, we are done
CP 10 ; Line feed ^J pressed?
RET Z ; If so, we are done
CP 8 ; Backspace ^H pressed?
JP Z,CRSS2 ; Handle backspace
CP 7FH ; Delete key pressed?
JP Z,CRSS1 ; Handle delete
CP 26 ; ^X pressed?
JP Z,CRSS3 ; Handle ^X
LD B,A ; Stash character in B
LD A,(IX+0) ; Buffer capacity -> A
SUB (IX+1) ; Subtract characters read
CP 0 ; If no space left ...
RET Z ; ... we are done
LD (HL),B ; Write character to buffer
INC HL ; Advance to next character
INC (IX+1) ; Increment character count
JP CRSL1 ; Loop
RET ;
CRSS1 PUSH HL
LD E,8 ; Print two backspaces (^H)
CALL C_WRITE ; ...
CALL C_WRITE ; ...
LD E,' ' ; Print two spaces to erase chars
CALL C_WRITE ; ...
CALL C_WRITE ; ...
LD E,8 ; Print two backspaces (^H)
CALL C_WRITE ; ...
CALL C_WRITE ; ...
POP HL
CRSS2 PUSH HL
LD E,' ' ; Print space to erase character
CALL C_WRITE ; ...
LD E,8 ; Print backspace (^H)
CALL C_WRITE ; ...
LD A,(IX+1) ; Get character count
CP 0 ; See if it is zero
POP HL
JP Z,CRSL1 ; If so, back to top of loop
DEC HL ; Delete previously-entered character
DEC (IX+1) ; Decrement character count
JP CRSL1 ; Back to top of loop
CRSS3 LD A,0 ; Set character count to zero to ...
LD (IX+1),A ; ... cancel the entire entry
LD E,13 ; Print a carriage return
CALL C_WRITE ; ...
RET ; Done
; Returns 0 in A and L if no chars waiting, non zero otherwise
C_STAT LD A,3 ; CMD=3 means peek at keyboard
LD (CMD),A ; ...
LD (SOFTCARD),A ; Do it
LD A,(AREG) ; Grab the return value
LD L,A ; Copy A to L
RET
; Returns system type in B and H, BDOS version in A and L
S_BDOSVER LD B,0 ; System is 8080 CP/M
LD H,B ; Also in H
LD A,22H ; Pretend to v2.2
LD L,A ; Also in A
RET
; Reset disks
; Makes A: drive the default
DRV_ALLRST LD A,(CURDRV) ; Contains both user & current drive
AND 0F0H ; Set drive to 0, meaning A:
LD (CURDRV),A ; Store in CURDRV
LD BC,FILEBUF ; FILEBUF is at 0080H
LD (DMAADDR),BC ; Reset DMA address
LD HL,FLMLI ; Pass address of 6502 JSR instruction
CALL PRODOS ; Invoke ProDOS MLI to flush all files
RET
; Select disk
; Disk to select is passed in E (A: is 0, B: is 1 etc.)
; Return 00 for success, 0FFH for error in A and L
DRV_SET LD A,E ; Prepare to compare disk number
CP 16 ; Support 16 'drives' A: - P:
JP NC,DSERR ; If A>15 ... error
LD B,A ; Stash in B for now
LD A,(CURDRV) ; Has both user number & current drive
AND 0F0H ; Mask out old drive number
OR B ; Replace with new drive number
LD (CURDRV),A ; Store the requested drive number
XOR A ; A=0: Return code meaning success
JP DSRET ;
DSERR LD A,0FFH ; Return code for error
DSRET LD L,A ; Return code in L too
RET
; Open file
; DE is the address of the FCB describing the file to open
; Returns error codes in A and L:
; Returns 0 for success. The FCB for the file opened is left at DMAADDR (slot 0)
; Returns 0FFH if file not found
F_OPEN PUSH DE ; Preserve pointer to FCB
CALL F_SFIRST ; Find first matching directory entry
POP DE ; Restore pointer to FCB
; Alternative entrypoint used for opening ProDOS directory files
; and used by the CCP to load .COM files. No directory lookup.
_F_OPEN LD IX,PATHBUF ; Destination buffer
CALL FCB2PATH ; Populate PATHLEN and PATH
PUSH DE ; Copy pointer to FCB ...
POP IY ; ... into IY
; Work out which IOBUF to allocate for this file
XOR A ; Looking for FRN slot with value 0
CALL GETIOADDR ; Returns FRN slot in A, IOBUF in HL
CP 0FFH ; Check for error
JP Z,FOERR ; If no slots available, error out
LD (TEMPBYTE),A ; Record the buffer index in local var
LD BC,OFFSET ; Add offset to convert to 6502 address
ADD HL,BC ; ...
LD (FOMLII),HL ; Store in parameter list
LD HL,FOMLI ; Pass address of 6502 JSR instruction
CALL PRODOS ; Invoke ProDOS MLI
CP 0 ; See if there was an error
JP NZ,FOERR ; Handle error
; Store ProDOS FRN in S2 field of FCB
LD A,(FOMLIN) ; Get ProDOS file reference number
LD (IY+0EH),A ; Store file reference number in S2 field
; ProDOS GET_EOF call
; Assumes no files > 64K on ProDOS filesystem
LD (GEMLIN),A ; Store file ref num in param list
LD HL,GEMLI ; Pass address of 6502 JSR instruction
CALL PRODOS ; Invoke ProDOS MLI (GET_EOF)
; Convert length in bytes to length in records
LD HL,(GEMLIE2) ; Load 16 bit length
CALL LEN2RECS ; Leaves number of records in A
; TODO If >16K bytes should set num records to 128 I think
; Store records used
LD (IY+0FH),A ; Set records used field
; Set sequential record number to zero
XOR A ; Zero the sequential record number
LD (IY+20H),A ; ...
; Store ProDOS FRN in slot FRN1 - FRN4
LD A,(TEMPBYTE) ; Obtain IOBUF idx (1,2,3,4)
LD HL,FRN1-1 ; Compute address of FRN slot to use
LD B,0 ; ...
LD C,A ; ...
ADD HL,BC ; ...
LD A,(FOMLIN) ; Get ProDOS file reference number
LD (HL),A ; Store in FRN slot
XOR A ; Success
LD L,A ; Copy to L
RET ; Done
FOERR LD A,0FFH ; Error return status
LD L,A ; Copy to L
RET ; Done (error)
; Close file
; DE is the address of the FCB describing the file to close
; Returns error codes in A and L:
F_CLOSE LD H,D ; Pointer to FCB ...
LD L,E ; ... into HL
LD BC,0EH ; Offset to S2 field (reserved field)
ADD HL,BC ; Compute address
LD A,(HL) ; Obtain file reference num from FCB S2
CP 0 ; If file reference number is zero ...
JP Z,FCSUCC ; ... Nothing to do, just return
LD (FCMLIN),A ; Store in parameter list
LD HL,FCMLI ; Pass address of 6502 JSR instruction
CALL PRODOS ; Invoke ProDOS MLI
CP 0 ; See if there was an error
JP NZ,FCERR ; Handle error
LD A,(FCMLIN) ; Obtain file reference number again
CALL GETIOADDR ; Returns FRN slot in A, IOBUF in HL
CP 0FFH ; Check for error
JP Z,FCERR ; If FRN not found, error out
LD HL,FRN1-1 ; Compute addr of FRN slot to set to zero
LD B,0 ; ...
LD C,A ; ...
ADD HL,BC ; ...
XOR A ; And zero it
LD (HL),A ; ...
PUSH DE ; Copy pointer to FCB ...
POP IX ; ... into IX
XOR A ; Zero out the S2 field
LD (IX+0EH),A ; ...
FCSUCC XOR A ; Return zero for error
LD L,A ; Return in L also
RET
FCERR LD A,0FFH ; 0FFH for error
LD L,A ; Return code in L also
RET
DIRHDSZ EQU 2BH ; Size of ProDOS directory header
FILEENTSZ EQU 27H ; Size of ProDOS file entry
ENTPERBLK EQU 0DH ; Number of file entries per block
; Search for first match of filename in directory
; DE is the address of the FCB describing the file to look for
; Returns error codes in A and L: 0 for success, 0FFH for not found
; The matching FCB is always in slot 0, so success return code always 0
F_SFIRST LD (TEMPWORD),DE ; Store pointer to search FCB
LD A,(DE) ; Obtain drive number
LD (DFCBDRV),A ; Copy to directory FCB
LD DE,DFCB ; Use this FCB to open the directory
CALL F_CLOSE ; Close the directory, if open
LD DE,DFCB ; Use this FCB to open the directory
CALL _F_OPEN ; Open the directory (avoiding recursion!)
FSFL1 LD A,0FFH ; Init CDBEXT to 0FFH (see CHKDIRBLK)
LD (CDBEXT),A ; ...
CALL RDDIRBLK ; Read first 512 byte block
CP 0 ; See if it was an error
JP NZ,FSFS2 ; If error, assume EOF & just return
LD HL,DIRBUF ; Skip over directory header
LD BC,DIRHDSZ ; ...
ADD HL,BC ; ...
LD (CDBPTR),HL ; Start out at first file entry
XOR A ; Set file count to zero
LD (CDBCOUNT),A ; ...
LD DE,(TEMPWORD) ; Get ptr to search FCB back
CALL CHKDIRBLK ; Search directory block
CP 0 ; See if it was a match
JP Z,FSFS1 ; If so, return
JP FSFL1 ; Loop
FSFS1 LD DE,(TEMPWORD) ; Get ptr to search FCB back
LD A,(DE) ; Obtain drive number
LD HL,(DMAADDR) ; Pointer to FCB we are returning
LD (HL),A ; Copy drive number to FCB
XOR A ; Match
RET ;
FSFS2 LD A,0FFH ; No match
RET
; Search for next match of filename in directory
; The address of the FCB describing the file to look for is in TEMPWORD
; Returns error codes in A and L: 0 for success, 0FFH for not found
; The matching FCB is always in slot 0, so success return code always 0
F_SNEXT LD HL,(CDBPTR) ; Pointer into current block
LD A,(CDBCOUNT) ; File count for current block
FSNL1 LD DE,(TEMPWORD) ; Get ptr to search FCB back
CALL CHKDIRBLK ; Search directory block
CP 0 ; See if it was a match
JP Z,FSNS1 ; If so, return
LD A,0FFH ; Init CDBEXT to 0FFH (see CHKDIRBLK)
LD (CDBEXT),A ; ...
CALL RDDIRBLK ; Read next 512 byte block
CP 0 ; See if it was an error
JP NZ,FSNS2 ; If error, assume EOF & just return
LD HL,DIRBUF ; Skip over directory header
LD BC,DIRHDSZ ; ...
ADD HL,BC ; ...
LD (CDBPTR),HL ; Start out at first file entry
XOR A ; Set file count to zero
LD (CDBCOUNT),A ; ...
JP FSNL1 ; Loop
FSNS1 LD DE,(TEMPWORD) ; Get ptr to search FCB back
LD A,(DE) ; Obtain drive number
LD HL,(DMAADDR) ; Pointer to FCB we are returning
LD (HL),A ; Copy drive number to FCB
XOR A ; Match
RET ;
FSNS2 LD A,0FFH ; No match
RET
; Delete file
; DE is the address of the FCB describing the file to delete
; Returns error codes in A and L:
F_DELETE PUSH DE ; Preserve FCB
CALL F_SFIRST ; Search for file, create FCB
CP 0FFH ; If not found ...
JP Z,FDS2 ; ... Return with error
LD DE,FILEBUF ; FCB created by F_SFIRST is in FILEBUF
LD IX,PATHBUF ; Destination buffer
CALL FCB2PATH ; Populate PATHLEN and PATH
LD HL,FDMLI ; Pass address of 6502 JSR instruction
CALL PRODOS ; Invoke ProDOS MLI
CP 0 ; See if there was an error
JP NZ,FDS2 ; Handle error
FDL1 POP DE ; Get the FCB back
PUSH DE ; And stash it for next time
CALL F_SNEXT ; Search for file, create FCB
CP 0FFH ; If not found ...
JP Z,FDS1 ; ... We are done
LD IX,PATHBUF ; Destination buffer
LD DE,FILEBUF ; FCB created by F_NEXT is in FILEBUF
CALL FCB2PATH ; Populate PATHLEN and PATH
LD HL,FDMLI ; Pass address of 6502 JSR instruction
CALL PRODOS ; Invoke ProDOS MLI
CP 0 ; See if there was an error
JP NZ,FDS2 ; Handle error
JP FDL1 ; Loop for all matching files
FDS1 POP DE ; Restore stack
XOR A ; Return success
LD L,A ; Return code in L also
RET
FDS2 POP DE ; Restore stack
LD A,0FFH ; 0FFH for error
LD L,A ; Return code in L also
RET
; Read next record
; DE is the address of the FCB describing the file from which to read
; Returns error codes in A and L:
; 0 OK, 1 EOF, 9 invalid FCB, 10 media changed, 0FFH h/w error
F_READ PUSH DE ; Copy pointer to FCB ...
POP IX ; ... into IX
LD A,(IX+0EH) ; Obtain file reference num from FCB S2
LD (SMMLIN),A ; Store in parameter list for SET_MARK
LD (FRMLIN),A ; Store in parameter list for READ
LD A,(IX+20H) ; Obtain sequential record number
LD B,(IX+0CH) ; Obtain extent from FCB
CALL EXRC2LEN ; Leaves the length in bytes in HL
LD (SMMLIP1),HL ; Write 16 bit length in FRMLIP1,FRMLIP2
XOR A ; Set FRMLIP3 to zero
LD (SMMLIP3),A ; ...
LD HL,SMMLI ; Pass address of 6502 JSR instruction
CALL PRODOS ; Invoke ProDOS MLI - SET_MARK
CP 4DH ; See if position was out of range
JP Z,FREOF ; If so, return EOF (1)
CP 43H ; See if it was a bad file ref number
JP Z,FRBFCB ; If so, return invalid FCB code (9)
CP 0 ; See if there was some other error
JP NZ,FRERR ; If so, return code 0FFH (h/w error)
LD HL,(DMAADDR) ; Write data starting at DMA buffer addr
LD BC,OFFSET ; Convert to 6502 address
ADD HL,BC ; ...
LD (FRMLIDB),HL ; Store I/O buffer address in parm list
LD HL,FRMLI ; Pass address of 6502 JSR instruction
CALL PRODOS ; Invoke ProDOS MLI - READ
CP 4CH ; See if it was EOF
JP Z,FREOF ; If so, return EOF code (1)
CP 43H ; See if it was a bad file ref number
JP Z,FRBFCB ; If so, return invalid FCB code (9)
CP 0 ; See if there was some other error
JP NZ,FRERR ; If so, return code 0FFH (h/w error)
LD A,(IX+20H) ; Get sequential rec num from FCB
INC (IX+20H) ; Increment sequential record number
CP 129 ; Is it 129? 128 is the max
JP NZ,FRS1 ; If not, then nothing else to do
XOR A ; Set sequential rec num to zero
LD (IX+20H),A ; ...
INC (IX+0CH) ; Increment the extent
FRS1 XOR A ; Zero for success
LD L,A ; Return code in L also
RET ; Done
FREOF LD A,1 ; EOF return code
LD L,A ; Return code in L also
RET ; Done (EOF)
FRBFCB LD A,9 ; Invalid FCB return code
LD L,A ; Return code in L also
RET ; Done (Bad FCB)
FRERR LD A,0FFH ; All other errors are 0FFH
LD L,A ; Return code in L aslo
RET ; Done (error)
; Write next record
; DE is the address of the FCB describing the file to which to write
; Returns error codes in A and L:
; 0 OK, 1 dir full, 2 disk full, 9 invalid FCB, 10 media changed, 0FFH h/w error
F_WRITE PUSH DE ; Copy pointer to FCB ...
POP IX ; ... into IX
LD A,(IX+0EH) ; Obtain file reference num from FCB S2
LD (SMMLIN),A ; Store in parameter list for SET_MARK
LD (FWMLIN),A ; Store in parameter list for WRITE
LD A,(IX+20H) ; Obtain sequential record number
LD B,(IX+0CH) ; Obtain extent from FCB
CALL EXRC2LEN ; Leaves the length in bytes in HL
;; ; DEBUG
;; PUSH HL
;; LD DE,HEXBUF ; Generate hex string to HEXBUF
;; CALL NUM2HEX ; ...
;; LD DE,HEXBUF ; Write hex value to console
;; CALL C_WRITESTR ;
;; POP HL
;; ; END DEBUG
LD (SMMLIP1),HL ; Write 16 bit length in SMMLIP1,SMMLIP2
XOR A ; Set SMMLIP3 to zero
LD (SMMLIP3),A ; ...
LD HL,SMMLI ; Pass address of 6502 JSR instruction
CALL PRODOS ; Invoke ProDOS MLI - SET_MARK
CP 4DH ; See if position was out of range
JP Z,FWBFCB ; If so, return invalid FCB code (9)
CP 43H ; See if it was a bad file ref number
JP Z,FWBFCB ; If so, return invalid FCB code (9)
CP 0 ; See if there was some other error
JP NZ,FWERR ; If so, return code 0FFH (h/w error)
LD HL,(DMAADDR) ; Write data at DMA address
LD BC,OFFSET ; Convert to 6502 address
ADD HL,BC ; ...
LD (FWMLIDB),HL ; Store I/O buffer address in parm list
LD HL,FWMLI ; Pass address of 6502 JSR instruction
CALL PRODOS ; Invoke ProDOS MLI - WRITE
CP 43H ; See if it is a bad reference number
JP Z,FWBFCB ; If so, return invalid FCB code (9)
CP 48H ; See if it was an overrun error
JP Z,FWDF ; If so, return disk full code (2)
CP 0 ; See if there was some other error
JP NZ,FWERR ; If so, return code 0FFH (h/w error)
LD A,(IX+20H) ; Get sequential rec num from FCB
INC (IX+20H) ; Increment sequential record number
CP 129 ; Is it 129? 128 is the max
JP NZ,FWS1 ; If not, then nothing else to do
XOR A ; Set sequential rec num to zero
LD (IX+20H),A ; ...
INC (IX+0CH) ; Increment the extent
FWS1 XOR A ; Zero for success
LD L,A ; Return code in L also
RET ; Done
FWBFCB LD A,9 ; Invalid FCB return code
LD L,A ; Return code in L also
RET ; Done (EOF)
FWDF LD A,2 ; Disk full return code
LD L,A ; Return code in L also
RET ; Done (Disk Full)
FWERR LD A,0FFH ; All other errors are 0FFH
LD L,A ; Return code in L aslo
RET ; Done (error)
; Create (and open) file
; DE is the address of the FCB describing the file to create
; Returns error codes in A and L:
; 0 for success, 0FFH if file could not be created
F_MAKE LD IX,PATHBUF ; Destination buffer
CALL FCB2PATH ; Populate PATHLEN and PATH
LD HL,FMMLI ; Pass address of 6502 JSR instruction
CALL PRODOS ; Invoke ProDOS MLI
CP 0 ; See if there was an error
JP NZ,FMERR ; Handle error
CALL F_OPEN ; Open the file using same FCB (DE ptr)
RET ; Return with status from F_OPEN
FMERR LD A,0FFH ; 0FFH for error
LD L,A ; Return code in L also
RET
; Rename file
; DE is the address of the FCB describing the file to be renamed. The new name
; is stuffed into FCB+16 (where the allocation map usually goes)
; Returns error codes in A and L - 0 for success, 0FFH for file not found
F_RENAME CALL F_SFIRST ; Search for file, create FCB
CP 0FFH ; If not found ...
JP Z,FRNERR ; ... Return with error
LD IX,PATHBUF ; Destination buffer 1
CALL FCB2PATH ; Populate PATHLEN and PATH for first file
LD IX,PATHBUF2 ; Destination buffer 2
LD H,D ; DE -> HL for addition
LD L,E ; ...
LD BC,16 ; Increment by 16 bytes to 2nd part of FCB
ADD HL,BC ; ...
LD D,H ; HL back to DE
LD E,L ; ...
CALL FCB2PATH ; Populate PATHLEN and PATH for second file
LD HL,FRNMLI ; Pass address of 6502 JSR instruction
CALL PRODOS ; Invoke ProDOS MLI
CP 0 ; See if there was an error
JP NZ,FRNERR ; Handle error
XOR A ; Success
LD L,A ; Return code in L also
RET
FRNERR LD A,0FFH ; 0FFH for error
LD L,A ; Return code in L also
RET
; Return bitmap of logged-in drives in HL
DRV_LOGVEC LD HL,(LOGVEC) ;
RET
; Return current drive in A
DRV_GET LD A,(CURDRV) ; Contains user number & current drive
AND 0FH ; Mask out user number
RET
; Set DMA address
; DMA address is passed in DE
F_DMAOFF LD (DMAADDR),DE ;
RET
; Return address of allocation map in HL
DRV_AVEC LD HL,ALLOCVEC ;
RET
; Software write-protect current disk
DRV_SETRO LD B,80H ; Set MS-bit in B, will rotate this below
LD A,(CURDRV) ; Current drive (0 A:, 1 B: ...)
AND 0FH ; Mask out user number
INC A ; It is easier if A: is 1, B is 2 etc.
CP 9 ; See if it in LS-byte or MS-byte of ROVEC
JP NC,DSRMSB ; If A>8 then drive bit in in MS-byte
DSRL1 RL B ;
DEC A ;
JP NZ,DSRL1 ;
LD A,(ROVEC) ; Fetch the LS-byte of ROVEC
OR B ; Set the bit using OR
LD (ROVEC),A ; Store LS-byte back to ROVEC
RET ; We're done
DSRMSB LD C,8 ; Subtract 8 from the drive number
SUB C ; A = A-8
DSRL2 RL B ;
DEC A ;
JP NZ,DSRL2 ;
LD A,(ROVEC+1) ; Fetch the MS-byte of ROVEC
OR B ; Set the bit using OR
LD (ROVEC+1),A ; Store MS-byte back to ROVEC
RET
; Return bitmap of read-only drives in HL
DRV_ROVEC LD HL,(ROVEC) ; Bit 0 of L is A:, bit 7 of H is P:
RET
; Return pointer to Drive Parameter Block in HL
DRV_DPB LD HL,DPB ; Pointer to drive parameter block
RET
; Get/set user number
; E contains the user number to set, or 0FFH to get current user number
; If E is 0FFH then user number is returned in A
F_USERNUM LD A,E ; See if it is get or set
CP 0FFH ; 0FFH means 'get'
JP Z,FUNS1 ; It is 'get'
LD B,A ; Stash the user number to set in B
SLA B ; Left shift four times
SLA B ; ...
SLA B ; ...
SLA B ; ...
LD A,(CURDRV) ; Contains user number & current drive
AND 0FH ; Mask out current user number
OR B ; OR in the new user number
LD (CURDRV),A ; Store updated user number & curr drv
RET ;
FUNS1 LD A,(CURDRV) ; Contains user number & current drive
AND 0F0H ; Mask out current drive
SRA A ; Right shift the user number 4 times
SRA A ; ...
SRA A ; ...
SRA A ; ...
RET
; Random access read record
; DE contains address of FCB describing the file to read
; Return code in A and L:
; 0 success, 1 reading unwritten data, 4 reading unwritten extent,
; 6 rec number out of range, 9 invalid FCB, 10 media changed, 0FFH h/w err
F_READRAND PUSH DE ; Copy pointer to FCB ...
POP IX ; ...
LD A,(IX+0EH) ; Obtain file reference num from FCB S2
LD (SMMLIN),A ; Store in parameter list for SET_MARK
LD (FRMLIN),A ; Store in parameter list for READ
LD L,(IX+21H) ; Load random record number (LSB)
LD H,(IX+22H) ; Load random record number (MSB)
CALL RRN2LEN ; Leaves the length in bytes in HL
LD (SMMLIP1),HL ; Write 16 bit length in FRMLIP1,FRMLIP2
XOR A ; Set FRMLIP3 to zero
LD (SMMLIP3),A ; ...
LD HL,SMMLI ; Pass address of 6502 JSR instruction
CALL PRODOS ; Invoke ProDOS MLI - SET_MARK
CP 4DH ; See if position was out of range
JP Z,FRRRUD ; If so, return reading unwritten data (1)
CP 43H ; See if it was a bad file ref number
JP Z,FRRBFCB ; If so, return invalid FCB code (9)
CP 0 ; See if there was some other error
JP NZ,FRRERR ; If so, return code 0FFH (h/w error)
LD HL,(DMAADDR) ; Write data starting at DMA buffer addr
LD BC,OFFSET ; Convert to 6502 address
ADD HL,BC ; ...
LD (FRMLIDB),HL ; Store I/O buffer address in parm list
LD HL,FRMLI ; Pass address of 6502 JSR instruction
CALL PRODOS ; Invoke ProDOS MLI - READ
CP 4CH ; See if it was EOF
JP Z,FRRRUD ; If so, return EOF code (1)
CP 43H ; See if it was a bad file ref number
JP Z,FRBFCB ; If so, return invalid FCB code (9)
CP 0 ; See if there was some other error
JP NZ,FRRERR ; If so, return code 0FFH (h/w error)
LD L,(IX+21H) ; Load random record number (LSB)
LD H,(IX+22H) ; Load random record number (MSB)
CALL RECS2EXRC ; Puts extent in B, recs in A
LD A,(IX+20H),A ; Update sequential record number
LD B,(IX+0CH),B ; Update sequential extent number
XOR A ; Zero for success
LD L,A ; Return code in L also
RET ; Done
FRRRUD LD A,1 ; Reading unwritten data return code
LD L,A ; Return code in L also
RET ; Done (EOF)
FRRBFCB LD A,9 ; Invalid FCB return code
LD L,A ; Return code in L also
RET ; Done (Bad FCB)
FRRERR LD A,0FFH ; All other errors are 0FFH
LD L,A ; Return code in L aslo
RET ; Done (error)
; Random access write record
; DE contains address of FCB describing the file to write
; Return code in A and L:
; 0 success, 1 reading unwritten data, 4 reading unwritten extent,
; 6 rec number out of range, 9 invalid FCB, 10 media changed, 0FFH h/w err
F_WRITERAND PUSH DE ; Copy pointer to FCB ...
POP IX ; ...
LD A,(IX+0EH) ; Obtain file reference num from FCB S2
LD (GEMLIN),A ; Store in parameter list for GET_EOF
LD (SEMLIN),A ; Store in parameter list for SET_EOF
LD (SMMLIN),A ; Store in parameter list for SET_MARK
LD (FWMLIN),A ; Store in parameter list for WRITE
LD HL,GEMLI ; Pass address of 6502 JSR instruction
CALL PRODOS ; Invoke ProDOS MLI - GET_EOF
LD L,(IX+21H) ; Load random record number (LSB)
LD H,(IX+22H) ; Load random record number (MSB)
CALL RRN2LEN ; Leaves the length in bytes in HL
LD (SMMLIP1),HL ; 16 bit len in SMMLIP1,2 for SET_MARK
XOR A ; Set SMMLIP3 to zero
LD (SMMLIP3),A ; ...
LD HL,(GEMLIE1) ; Load current EOF into HL
LD BC,(SMMLIP1) ; Load requested offset into BC
AND A ; Clear carry
SBC HL,BC ; Subtract requested byte offset from EOF
JP NC,FWRS1 ; If >=0 no need to SET_EOF
LD (SEMLIE1),BC ; Requested offset for SET_EOF
LD HL,SEMLI ; Pass address of 6502 JSR instruction
CALL PRODOS ; Invoke ProDOS MLI - SET_EOF
FWRS1 LD HL,SMMLI ; Pass address of 6502 JSR instruction
CALL PRODOS ; Invoke ProDOS MLI - SET_MARK
CP 4DH ; See if position was out of range
JP Z,FWRBFCB ; If so, return invalid FCB code (9)
CP 43H ; See if it was a bad file ref number
JP Z,FWRBFCB ; If so, return invalid FCB code (9)
CP 0 ; See if there was some other error
JP NZ,FWRERR ; If so, return code 0FFH (h/w error)
LD HL,(DMAADDR) ; Get DMA buffer address
LD BC,OFFSET ; Convert to 6502 address
ADD HL,BC ; ...
LD (FWMLIDB),HL ; Store I/O buffer address in parm list
LD HL,FWMLI ; Pass address of 6502 JSR instruction
CALL PRODOS ; Invoke ProDOS MLI - WRITE
CP 43H ; See if it was a bad file ref number
JP Z,FWRBFCB ; If so, return invalid FCB code (9)
CP 48H ; See if it was a bad file ref number
JP Z,FWRDF ; If so, return invalid FCB code (9)
CP 0 ; See if there was some other error
JP NZ,FWRERR ; If so, return code 0FFH (h/w error)
LD L,(IX+21H) ; Load random record number (LSB)
LD H,(IX+22H) ; Load random record number (MSB)
CALL RECS2EXRC ; Puts extent in B, recs in A
LD A,(IX+20H),A ; Update sequential record number
LD B,(IX+0CH),B ; Update sequential extent number
XOR A ; Zero for success
LD L,A ; Return code in L also
RET ; Done
FWRBFCB LD A,9 ; Invalid FCB return code
LD L,A ; Return code in L also
RET ; Done (Disk Full)
FWRDF LD A,2 ; Disk fill return code
LD L,A ; Return code in L also
RET ; Done (Bad FCB)
FWRERR LD A,0FFH ; All other errors are 0FFH
LD L,A ; Return code in L aslo
RET ; Done (error)
; Compute file size
; DE contains address of FCB describing the file
; Error codes are returned in A and L (0 for success, 0FFH if file not found)
; Returns the number of 128 byte records in random record count field of FCB
F_SIZE CALL FCB2PATH ; Populate PATHLEN and PATH
LD HL,GFIMLI ; Pass address of 6502 JSR instruction
CALL PRODOS ; Invoke ProDOS MLI
CP 0 ; See if there was an error
JP NZ,FSERR ; Handle error
LD HL,(GFIMLIBU) ; Obtain the blocks used field
ADD HL,HL ; Mult x 4 to get 128 byte records
ADD HL,HL ; ...
; Store records used in R0,R1 fields of FCB
PUSH DE ; Copy DE ...
POP IX ; ... into IX
LD (IX+21H),L ; Store LSB of recs used in R0
LD (IX+22H),H ; Store LSB of recs used in R1
XOR A ; Store zero in R2
LD (IX+23H),A ; ...
XOR A ; Success
LD L,A ; Return in L also
RET
FSERR LD A,0FFH ; File not found
LD L,A ; Return in L also
RET
; Update random access pointer
; DE contains the pointer to the FCB to update
; Sets the random access record of the FCB to the value of the last record
; read or written sequentially
F_RANDREC PUSH DE ; Copy pointer to FCB ...
POP IX ; ... into IX
LD A,(IX+20H) ; Obtain sequential record number
LD B,(IX+0CH) ; Obtain extent from FCB
CALL EXRC2RECS ; Leaves the length in records in HL
DEC HL ; Because F_READ/F_WRITE advance this
LD (IX+21H),L ; Store in random access pointer ...
LD (IX+22H),H ; ... In little endian format
RET
; Selectively reset disk drives
; DE contains bitmap of drives to reset (bit 0 of E if A:, bit 7 of D is P:)
; Returns A=00H if okay, A=FFH if error
; Resetting means removing the read-only status
DRV_RESET LD A,(ROVEC) ; Do the LSB ...
XOR E ; ... with E
LD (ROVEC),A ;
LD A,(ROVEC+1) ; Then the MSB ...
XOR D ; ... with D
LD (ROVEC+1),A ;
RET
; Make a ProDOS MLI call
; Address of 6502 JSR instruction in front of ProDOS parameter list is
; passed in in register pair HL, in Z80 address space
; Return code is passed back in A
PRODOS LD BC,OFFSET ; Add offset to convert Z80->6502 address
ADD HL,BC ; ...
LD (ADDR),HL ; Store it for 6502
LD A,2 ; CMD=2 means ProDOS MLI call
LD (CMD),A ; ...
LD (SOFTCARD),A ; Do it!
LD A,(AREG) ; Get return code from the MLI call
RET
; Populate the PATH buffer (and PATHLEN) by copying from FCB
; If the FCB contains A:TEST____.TXT then the PATH buffer will be A/TEST.TXT
; Any '$' character in extension is converted to '8' so ProDOS can handle it
; DE contains a pointer to the FCB
; IX contains pointer to the path buffer into which to write
FCB2PATH PUSH IX ; Copy IX->IY
POP IY ; So IY keeps track of size byte at start
INC IX ; Advance past the size byte before writing
LD A,(DE) ; Get drive number from FCB
CP 0 ; See if it is zero (default drive)
JP NZ,F2PS1 ; If drive explicit
LD A,(CURDRV) ; If default drive use CURDRV
AND 0FH ; Mask out user number
INC A ; CURDRV is zero based
F2PS1 ADD A,'A'-1 ; Convert to drive letter
LD (IX+0),A ; Store as first char of path
INC IX ; Advance IX to next char of path to write
LD A,'/' ; Second char of path is '/'
LD (IX+0),A ; Store as second char of path
INC IX ; Advance IX to next char of path to write
LD C,2 ; Use C to count chars in filename
LD H,D ; Copy address of FCB from DE ...
LD L,E ; ... to HL
INC HL ; HL points to filename in FCB
LD A,(HL) ; First character of filename
CP ' ' ; Is it space? ie: no file specified
JP Z,F2PS7 ; Don't handle filename or extension
F2PL1 ; Handle the filename - up to 8 characters
LD A,(HL) ; Obtain filename character
CP ' ' ; See if it is a space
JP Z,F2PS3 ; If so we are done with filename
EX AF,AF' ; We need to re-use A here
LD A,C ; Get character count
CP 10 ; Drive letter, slash and 8 char filename
JP Z,F2PS2 ; If so we are done with filename
EX AF,AF' ; Swap back to original A reg
LD (IX+0),A ; Copy to PATH buffer
INC C ; Increment filename char count
INC HL ; Next byte of filename in FCB
INC IX ; Next byte of PATH buffer
JP F2PL1 ; Loop till done
F2PS2 EX AF,AF' ; Swap back to original A reg
F2PS3 ; Eat any space characters at end of filename
F2PL2 LD A,(HL) ; Read next character from FCB
CP ' ' ; Is it space?
JP NZ,F2PS4 ; If not, we are done eating!
INC HL ; Otherwise advance to next char
JP F2PL2 ; And loop
F2PS4 LD A,'.' ; Separator is a period
LD (IX+0),A ; Write to buffer
INC C ; Count the character!
INC IX ; Advance to next character in buffer
LD B,0 ; Use B to track num chars in extension
; Handle the extension - up to 3 characters
F2PL3 LD A,(HL) ; Obtain extension character
CP ' ' ; See if it is a space (? or NULL maybe?)
JP Z,F2PS7 ; If so we are done with extension
CP '$' ; See if it is a dollar ($$$ extension)
JP NZ,F2PS5 ; If not skip the substitution
LD A,'8' ; Replace '$' with '8'
F2PS5 EX AF,AF' ; We need to re-use A here
LD A,B ; Get character count
CP 3 ; Extension can be up to 3 chars
JP Z,F2PS6 ; If so we are done with filename
EX AF,AF' ; Swap back to original A reg
LD (IX+0),A ; Copy to PATH buffer
INC C ; Count the chars (overall)
INC B ; Count the chars (in extension)
INC HL ; Next byte of filename in FCB
INC IX ; Next byte of PATH buffer
JP F2PL3 ; Loop till done
F2PS6 EX AF,AF' ; Swap back to original A reg
F2PS7 LD A,C ; Store length of string
LD (IY+0),A ; We kept size byte in IY at start
RET ;
; Clear FCB pointed to by DMAADDR
CLRFCB LD HL,(DMAADDR) ; Set all 16 bytes to FCB to zero
LD C,0 ; ...
XOR A ; ...
CFL1 LD (HL),C ; ...
INC HL ; ...
INC A ; ...
CP 16 ; ...
JP NZ,CFL1 ; ...
LD HL,(DMAADDR) ; Set all filename chars in FCB to space
INC HL ; ...
LD C,' ' ; ...
XOR A ; ...
CFL2 LD (HL),C ; ...
INC HL ; ...
INC A ; ...
CP 8+3 ; ...
JP NZ,CFL2 ; ...
RET
; This operation is almost the inverse of FCB2PATH. It takes a pointer to the
; beginning of the ProDOS dirent and converts it to FCB format (8.3 with
; spaces for any unused characters.)
; Any '8' character in extension is converted back to '$' (see FCB2PATH)
; Handles '*' wildcard character
; HL points to the file entry in the ProDOS directory
; B contains the drive number (1 for A:, 2 for B: etc)
; C controls whether file size is populated in FCB (C=0 disables file size)
; The FCB is written to the buffer pointed to by DMAADDR
; Trashes pretty much all registers (except IX, IY)
NAME2FCB PUSH BC ; We will need C later
EX DE,HL ; Stash HL in DE for call to CLRFCB
CALL CLRFCB ; Clear FCB at DMAADDR
EX DE,HL ; Get file entry pointer back in HL
LD A,(HL) ; Obtain first char of ProDOS dirent
AND 0FH ; Mask to obtain length of the name
LD C,A ; Stash source character count in C
LD DE,(DMAADDR) ; Initialize DE as write pointer
LD A,B ; Move drive number to A
LD (DE),A ; Write drive number
LD B,0 ; Use B to count chars written
N2FL1 INC HL ; Advance source pointer
INC DE ; Advance destination pointer
LD A,C ; Get count of chars remaining
CP 0 ; If none left ...
JP Z,N2FS6 ; We are done
LD A,B ; Get count of chars written
CP 8+3 ; If 8+3 chars have been written ...
JP Z,N2FS6 ; We are done
LD A,(HL) ; Read character
CP '.' ; See if it is a period
JP Z,N2FS2 ; Prepare to copy the extension
CP '*' ; See if it is an asterix
JP Z,N2FS3 ; Handle asterix wildcard
LD (DE),A ; Write character
CP '8' ; See if character in name is '8'
JP NZ,N2FS0 ; If not, then no substitution
LD A,B ; See how many chars have been written
CP 8 ; >=8? If so we are in the extension
JP C,N2FS0 ; If not, then no substitution
LD A,'$' ; Otherwise substitute '8'->'$'
LD (DE),A ; Re-write the substituted character
N2FS0 INC B ; Increment count of chars written
N2FS1 DEC C ; Decrement count of chars remaining
JP N2FL1 ; Loop
; Initialize DE, B to start processing the extension
N2FS2 LD DE,(DMAADDR) ; Destination is start of extension
INC DE ;
INC DE ;
INC DE ;
INC DE ;
INC DE ;
INC DE ;
INC DE ;
INC DE ;
LD B,8 ; 8 chars have been written
JP N2FS1 ; Jump back into the read-write loop
; Handle asterix wildcard character
N2FS3 LD A,B ; See how many chars have been written
CP 8 ; >=8? If so we are in the extension
JP C,N2FS4 ; If in main filename (not extension)
; Asterix in extension
N2FL2 LD A,'?' ; We will write '?' to rest of extension
LD (DE),A ; Write character
INC DE ; Increment write pointer
INC B ; Increment character count
LD A,B ; See if we are done
CP 8+3 ; Filename+extension
JP Z,N2FS6 ; We are done
JP N2FL2 ; Loop
N2FS4 ; Asterix in main filename
N2FL3 LD A,'?' ; We will write '?' to rest of filename
LD (DE),A ; Write character
INC DE ; Increment write pointer
INC B ; Increment character count
LD A,B ; See if we are done
CP 8 ; Filename+extension
JP Z,N2FS5 ; We are done - eat chars up until '.'
JP N2FL3 ; Loop
N2FS5 ; Eat rest of chars up until '.'
N2FL4 LD A,(HL) ; Get character
CP '.' ; See if it is a period
JP Z,N2FS2 ; If so, go process the extension
INC HL ; Increment source pointer
DEC C ; Decrement count of chars remaining
LD A,C ; Get count of chars remaining
CP 0 ; If none left ...
JP Z,N2FS6 ; We are done
JP N2FL4 ; Loop
; Handle file size info
N2FS6 POP BC ; Get C back
LD A,C ; Check value of C
CP 0 ; See if zero (meaning do not check size)
RET Z ; Nothing else to do
LD DE,(DMAADDR) ; Pointer to start of FCB
LD IX,PATHBUF ; Destination buffer
CALL FCB2PATH ; Populate PATHLEN and PATH from new FCB
LD HL,GFIMLI ; Pass address of 6502 JSR instruction
CALL PRODOS ; Invoke ProDOS MLI
CP 0 ; See if there was an error
JP NZ,N2FERR ; Handle error
LD HL,(GFIMLIBU) ; Obtain the blocks used field
ADD HL,HL ; Mult x 4 to get 128 byte records
ADD HL,HL ; ...
CALL RECS2EXRC ; Puts extent in B, recs in A
LD DE,(DMAADDR) ; Pointer to start of FCB
PUSH DE ; Copy into IX
POP IX ; ...
LD (IX+0CH),B ; Store num extents in EX field of FCB
LD (IX+0FH),A ; Store num recs in RC field of FCB
RET
N2FERR LD DE,(DMAADDR) ; Pointer to start of FCB
PUSH DE ; Copy into IX
POP IX ; ...
LD A,0 ; If error, set num extents to zero
LD (IX+0CH),A ; Store in EX field of FCB
LD A,0 ; If error, set record count to zero
LD (IX+0FH),A ; Store in RC field of FCB
RET
; Read 512 byte block of directory
; Used by F_SFIRST and F_SNEXT
; Trashes HL + registers trashed by F_READ (best to assume all of them!)
; Returns A=0 for success, A=0FFH on error
RDDIRBLK LD HL,(DMAADDR) ; Save existing DMA address
PUSH HL ; ...
; Read first 512 byte block of directory
LD DE,DFCB ; Use this FCB to open the directory
LD HL,DIRBUF1 ; Set DMAADDR to point to DIRBUF1
LD (DMAADDR),HL ; ...
CALL F_READ ; Read first record of directory
CP 0 ; See if there was an error
JP NZ,RDBS1 ; If so, quit with error code
LD DE,DFCB ; Use this FCB to open the directory
LD HL,DIRBUF2 ; Set DMAADDR to point to DIRBUF2
LD (DMAADDR),HL ; ...
CALL F_READ ; Read second record of directory
CP 0 ; See if there was an error
JP NZ,RDBS1 ; If so, quit with error code
LD DE,DFCB ; Use this FCB to open the directory
LD HL,DIRBUF3 ; Set DMAADDR to point to DIRBUF3
LD (DMAADDR),HL ; ...
CALL F_READ ; Read third record of directory
CP 0 ; See if there was an error
JP NZ,RDBS1 ; If so, quit with error code
LD DE,DFCB ; Use this FCB to open the directory
LD HL,DIRBUF4 ; Set DMAADDR to point to DIRBUF4
LD (DMAADDR),HL ; ...
CALL F_READ ; Read fourth record of directory
CP 0 ; See if there was an error
JP NZ,RDBS1 ; If so, quit with error code
XOR A ; Return code for success
JP RDBS2 ; Successful return
RDBS1 LD A,0FFH ; Error return code
RDBS2 POP HL ; Reset DMA address as we found it
LD (DMAADDR),HL ; ...
RET
; Match FCBs
; Used by CHKDIRENT
; DE is the address of the FCB describing the file to look for
; Compare with FCB at DMAADDR already created by NAME2FCB
; Returns A=0 if entry matches, A=FFH if no match
; Trashes A,BC,DE,HL
MATCHFCB INC DE ; Skip over drive byte in FCB
LD HL,(DMAADDR) ; Will read FCB at DMAADDR pointer
INC HL ; Skip over drive byte in FCB
LD C,0 ; Initialize character counter
MFL1 LD A,(DE) ; Load byte of search pattern
CP '?' ; Is it '?' wildcard?
JP Z,MFS1 ; If so, automatic char match
LD B,(HL) ; Load byte of match candidate
CP B ; See if the characters match
JP NZ,MFS2 ; If not, then no match
MFS1 INC DE ; Advance source pointer
INC HL ; Advance match candidate pointer
INC C ; Increment counter
LD A,8+3 ; Compare character counter
CP C ; ...
JP NZ,MFL1 ; Loop if characters left
XOR A ; We have a match
RET ;
MFS2 LD A,0FFH ; No match
RET
; Compare a ProDOS directory file entry to see if it matches
; Used by F_SFIRST and F_SNEXT
; DE is the address of the FCB describing the file to look for
; HL points to the first file entry in the ProDOS directory
; Returns A=0 if entry matches, A=FFH if no match
CHKDIRENT LD B,0 ; Hardcode drive A: MATCHFCB ignores it
LD A,(HL) ; Get first byte of file entry
AND 0FH ; Mask to get the filename length
CP 0 ; If zero ...
JP Z,CDES1 ; File is deleted - no match
PUSH HL ; Preserve HL
PUSH DE ; Preserve DE
LD C,1 ; Do check file sizes and put in FCB
CALL NAME2FCB ; Create FCB in DMA buffer
POP DE ; Recover DE
POP HL ; Recover HL
PUSH HL ; Preserve HL
PUSH DE ; Preserve DE
CALL MATCHFCB ; Compare search FCB w/ FCB in DMA buffer
POP DE ; Recover DE
POP HL ; Recover HL
CP 0 ; Does it match?
JP Z,CDES2 ; If so, return
CDES1 LD A,0FFH ; No match
RET ;
CDES2 XOR A ; Match
RET
; Search a 512 byte block of a ProDOS directory for a matching file entry
; Used by F_SFIRST and F_SNEXT
; DE is the address of the FCB describing the file to look for
; If the extent field of this FCB is '?' then all extents are returned
; CDBPTR points to the next ProDOS file entry to parse
; CDBCOUNT is set to the number of file entries already parsed in this block
; Returns A=0 if entry matches, A=FFH if no match
CHKDIRBLK LD A,(CDBCOUNT) ; File entry counter
LD HL,(CDBPTR) ; Pointer to next file entry
CDBL1 CALL CHKDIRENT ; Match the file entry at HL, build FCB
PUSH AF ; Stash return code for now
PUSH DE ; Copy pointer to search FCB ...
POP IX ; ... into IX
LD A,(IX+0CH) ; Get extent field of search FCB
CP '?' ; Is it '?'
JP NZ,CDBS1 ; If not, we can advance to next entry
LD A,(CDBEXT) ; Load CDBEXT
CP 0FFH ; If it is 0FFH, means this is first ...
JP Z,CDBS0 ; ... time we have looked at this dirent
JP CDBS01 ; Not the first extent for this dirent
; First extent for directory entry (highest numbered extent)
; This extent may contain 1-128 records
CDBS0 LD HL,(DMAADDR) ; Address of FCB created by CHKDIRENT
PUSH HL ; Copy address of FCB ...
POP IY ; ... into IY
LD A,(IY+0CH) ; Get num extents for this entry minus one
LD (CDBEXT),A ; Store it for next time
LD (IY+0CH),A ; Store it in the FCB returned to caller
CP 0 ; If CDBEXT will be zero next time
JP Z,CDBS1 ; ... we can advance to next entry
JP CDBS2 ; Return FCB for this extent (it match)
; Not the first extent for directory entry
; Always has 128 records
CDBS01 LD B,080H ; 128 records
LD (IY+0FH),B ; Store in num recs field
DEC A ; Decrement extent counter
LD (CDBEXT),A ; Store it for next time
LD (IY+0CH),A ; Store it in the FCB returned to caller
CP 0 ; If CDBEXT will be zero next time
JP Z,CDBS1 ; ... we can advance to next entry
JP CDBS2 ; Return FCB for this extent (it match)
CDBS1 LD HL,(CDBPTR) ; Pointer to file entry
LD BC,FILEENTSZ ; Advance to next file entry
ADD HL,BC ; ...
LD (CDBPTR),HL ; Store the pointer
LD A,(CDBCOUNT) ; File entry counter
INC A ; Increment count of file entries
LD (CDBCOUNT),A ; Store the counter
LD A,0FFH ; Initialize CDBEXT to 0FFH ...
LD (CDBEXT),A ; ... to start processing next dirent
CDBS2 POP AF ; Get return code back in A
CP 0 ; Was it a match?
JP Z,CDBS3 ; If so, we are done
LD A,(CDBCOUNT) ; Get the counter back
CP ENTPERBLK ; Done all of them in this block?
JP NZ,CDBL1 ; If not, loop
LD A,0FFH ; No match
RET ;
CDBS3 XOR A ; Match
RET
; The following variables are used to preserve state between calls
CDBPTR DEFW 0000H ; Stores pointer to next file entry
CDBCOUNT DEFB 0 ; Stores file entry counter
CDBEXT DEFB 0 ; Stores extent number
; Find IOBUF address for a file reference number
; Scan through FRN1, FRN2, FRN3, FRN4 to find the file reference number in A
; If found, return FRN slot# 1,2,3,4 in A, I/O buffer address in HL
; If not found, return A=0FFH
; Trashes B
GETIOADDR LD B,A ; Stash file ref number into B
LD A,(FRN1) ; Does it match FRN1?
CP B ; ...
JP Z,GIOAS1 ; ...
LD A,(FRN2) ; Does it match FRN2?
CP B ; ...
JP Z,GIOAS2 ; ...
LD A,(FRN3) ; Does it match FRN3?
CP B ; ...
JP Z,GIOAS3 ; ...
LD A,(FRN4) ; Does it match FRN4?
CP B ; ...
JP Z,GIOAS4 ; ...
LD A,0FFH ; No match, return A=0FFH
RET ; ...
GIOAS1 LD HL,IOBUF1 ; Address of I/O buf 1 -> HL
LD A,1 ; FRN slot 1
RET
GIOAS2 LD HL,IOBUF2 ; Address of I/O buf 2 -> HL
LD A,2 ; FRN slot 2
RET
GIOAS3 LD HL,IOBUF3 ; Address of I/O buf 3 -> HL
LD A,3 ; FRN slot 3
RET
GIOAS4 LD HL,IOBUF4 ; Address of I/O buf 4 -> HL
LD A,4 ; FRN slot 4
RET
; Convert length in bytes to the number of 128 byte records
; Length in bytes is passed in HL
; Length in records is returned in A
; Only works for files whose size in multiple of 128 bytes
LEN2RECS LD A,H ; Most significant byte of length
SLA A ; Shift left to make space
LD B,A ; Stash in B for now
LD A,L ; Least significant byte of length
AND 80H ; Keep most significant bit, mask off others
SRA A ; Move to LSB (shift seven times)
SRA A ; ...
SRA A ; ...
SRA A ; ...
SRA A ; ...
SRA A ; ...
SRA A ; ...
OR B ; Leaves file length in records in A
RET
; Convert pos in extent/recs format to a linear position in 128 byte records
; Extent number is passed in B
; Records within the extent is passed in A
; Returns the length in records in HL - HL = (B*128)+A
EXRC2RECS LD L,B ; Extent number in LSB of HL
LD H,0 ; ...
ADD HL,HL ; Shift left seven times
ADD HL,HL ; ...
ADD HL,HL ; ...
ADD HL,HL ; ...
ADD HL,HL ; ...
ADD HL,HL ; ...
ADD HL,HL ; ...
LD B,0 ; Put recs in this extent in BC
LD C,A ; ...
ADD HL,BC ; Now we have total offset in records
RET
; Convert random record number of 128 byte records to length in bytes
; Length in records is passed in HL
; Returns the length in bytes in HL
RRN2LEN ADD HL,HL ; Shift left seven times
ADD HL,HL ; ...
ADD HL,HL ; ...
ADD HL,HL ; ...
ADD HL,HL ; ...
ADD HL,HL ; ...
ADD HL,HL ; ...
RET
; Convert position in terms of random record number to extent/recs
; Length in records in passed in HL
; Returns extent number in B and records within extent in A
RECS2EXRC SLA H ; 7 MS-bits of extent number
BIT 7,L ; See if MS-bit of L is set
JP Z,R2ERS1 ; If set ...
INC H ; ... set LSB of extent number
R2ERS1 LD B,H ; Return extent in B
LD A,L ; Now for the recs in the extent
AND 7FH ; Just need to mask out MS-bit
RET
; Convert pos in extent/recs format to a linear position in bytes
; Extent number is passed in B
; Records within the extent is passed in A
; Returns the length in bytes in HL - HL = ((B*128)+A)*128
EXRC2LEN CALL EXRC2RECS ; Does most of the work
CALL RRN2LEN ; Does the rest!
RET
; Convert value in HL into an HEX ASCII string, pointed to by DE
; Courtesy of http://map.grauw.nl/sources/external/z80bits.html#5.2
; Trashes A
NUM2HEX LD A,H ;
CALL N2H1 ;
LD A,H ;
CALL N2H2 ;
LD A,L ;
CALL N2H1 ;
LD A,L ;
JR N2H2 ;
N2H1 RRA ;
RRA ;
RRA ;
RRA ;
N2H2 OR 0F0H ;
DAA ;
ADD A,0A0H ;
ADC A,40H ;
LD (DE),A ;
INC DE ;
RET
; Print value of C in HEX
; Used for DEBUG only
PRHEX PUSH AF
PUSH BC
PUSH DE
PUSH HL
LD E,'['
CALL C_WRITE
LD L,C ; Copy to HL for NUM2HEX
LD H,0 ; ...
LD DE,HEXBUF ; Generate hex string to HEXBUF
CALL NUM2HEX ; ...
LD DE,HEXBUF+2 ; Write hex value to console
CALL C_WRITESTR ;
LD E,']'
CALL C_WRITE
POP HL
POP DE
POP BC
POP AF
RET
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; Very simple CCP
;
; Commands:
; - d: - Change default drive (A:, B: etc.)
; - DIR - Show directory (DIR, DIR A:, DIR FOO.TXT, DIR *.COM)
; - ERA - Erase file(s) (ERA FOO.TXT, ERA F*.TXT)
; - REN - Rename file (REN BAR.TXT=FOO.TXT)
; - SAVE - Save memory to disk (SAVE 7 FOO.COM)
; - TYPE - Show text file on console (TYPE TEST.TXT)
; - FILENAME.COM - Load and run FILENAME.COM at 0100H
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; TODO: Implement SAVE command
; TODO: Sanity check / validate number of command args for builtins ???
; Get a line of text from the console & handle it
CCP
CCPL1 LD A,(CURDRV) ; Get current drive & user number
AND 0FH ; Mask out user number
ADD A,'A' ; Convert to letter
LD E,A ; Print it
CALL C_WRITE ; ...
LD E,'>' ; Print '>'
CALL C_WRITE ; ...
LD DE,FILEBUF ; Use FILEBUF as line buffer
LD A,120 ; Max line length
LD (DE),A ; Stored in first char of buffer
CALL C_READSTR ; Get a line from the user
LD H,D ; DE->HL
LD L,E ; ...
INC HL ; Skip over capacity byte
CALL UCASE ; Convert buffer to uppercase
LD A,(FILEBUF+1) ; Number of chars entered
CP 0 ; If none ...
JP Z,CCPL1 ; Loop until user types something!
; Two char built-in commands A:, B:, C: etc
CP 2 ; Check if two chars ...
JP NZ,CCPS1 ; If not, skip
LD A,(FILEBUF+3) ; See if second char is ':'
CP ':' ; ...
JP NZ,CCPS1 ; If not, skip
LD A,(FILEBUF+2) ; Get the character before the ':'
SUB 'A' ; A is 0, B is 1 etc.
LD E,A ; Set the default drive
CALL DRV_SET ; ...
JP CCPL1 ; Go again
CCPS1 LD HL,FCB1 ; Clear FCB1
LD (DMAADDR),HL ;
CALL CLRFCB ;
LD HL,FCB2 ; Clear FCB2
LD (DMAADDR),HL ;
CALL CLRFCB ;
CALL PARSE ; Parse the command line
CALL BUILTIN ; Check for built-in commands
CP 0 ; If zero, was a built-in command
JP Z,CCP ; Go again
CALL RUNCOM ; Try to run .COM file
JP CCP ; Go again
; Convert input buffer to uppercase
; HL points to Pascal-style string (length byte first)
UCASE LD C,(HL) ; Length of string
UCL1 LD A,C ; See if any chars are left
CP 0 ; ...
RET Z ; If 0, we are done
INC HL ; Advance to next char
DEC C ; Decrement count remaining
LD A,(HL) ; Get char from buffer
CP 'z' ; Compare with ASCII 'z'
JP NC,UCS1 ; If >'z' nothing to do
CP 'a'-1 ; Compare with char before ASCII 'a'
JP C,UCS1 ; If <='a'-1 then nothing to do
SUB 'a'-'A' ; Convert to upper case
UCS1 LD (HL),A ; Put converted char back
JP UCL1 ; Loop
; Parse the command line
; Replaces the contents of FILEBUF with command tail as Pascal-style string
; Creates FCB for loading .COM program
; B is used to keep track of the parser state machine:
; 0 in whitespace before command
; 1 in command
; 2 in whitespace segment following command
; 3 in first argument
; 4 in whitespace segment following first argument
; 5 in second argument
; 6 in whitespace segment following second arg (or later)
PARSE CALL RSTPATHBUF ; Set IY and E for writing PATHBUF
LD HL,FILEBUF+1 ; Skip first byte - buffer capacity
PUSH HL ; HL->IX. Use IX as dest pointer into FILEBUF
POP IX ; ...
LD D,0 ; D is the count of chars for FILEBUF
LD B,0 ; B is the state of the parser
LD C,(HL) ; Get the length of source string
CTL1 LD A,C ; See if any chars are left in input
CP 0 ; ...
JP Z,CTS2 ; If 0, go update the length byte
INC HL ; Advance to next char of source string
DEC C ; Decrement count remaining
LD A,(HL) ; Get char from buffer
CP ' ' ; See if it a space
JP Z,CTS1 ; If space ...
CALL CMDNSPC ; Handle non-space
JP CTL1 ; Loop
CTS1 CALL CMDSPC ; Handle space
JP CTL1 ; Loop
CTS2 LD A,' ' ; Fake an additional space on the end ...
CALL CMDSPC ; ... so parser can get itself in correct state
LD A,D ; Get string length of command tail
CP 1 ; If length is 1, is just a space
JP NZ,CTS3 ; If not length 1, store length, otherwise ...
LD D,0 ; ... Set length to zero
CTS3 LD HL,FILEBUF ; First byte is length of string
LD (HL),D ; Write string length
RET ;
; Helper function to reset the PATHBUF buffer pointer & character count
; PATHBUF is used to store the filename for creating an FCB
; Sets IY to point to first character to be written to PATHBUF
; Resets count of chars written E
RSTPATHBUF PUSH HL ; Preserve HL
LD HL,PATHBUF+1 ; Use HL to initialize IY
PUSH HL ; HL->IY. Use IY as dest ptr into PATHBUF
POP IY ; ...
POP HL ; Restore original HL
LD E,0 ; Reset count of chars in PATHBUF
RET
; Examines Pascal string pointed to by HL to see if it begins with a drive
; letter and a colon. If so, reduces string length by 2 and advances HL two
; positions, sets B to the drive number. If no drive is specified, sets B
; to the default drive.
DRVLETTER INC HL ; Advance to second char
INC HL ; ...
LD A,(HL) ; See if second char typed is a colon
CP ':' ;
JP NZ,DLS1 ; If not then skip to default drive
DEC HL ; Back to first char
LD A,(HL) ; Get the drive letter
SUB 'A'-1 ; Convert to 1-based drive number
LD B,A ; In B for NAME2FCB
DEC HL ; Back one to the size byte
LD A,(HL) ; Get the string length
SUB 2 ; Eat drive letter and colon
INC HL ; ...
INC HL ; ...
LD (HL),A ; Write the reduced length
RET
DLS1 DEC HL ; Put HL back to the beginning
DEC HL ; ...
LD A,(CURDRV) ; Get drive and user number
AND 0FH ; Mask out user number
INC A ; FCB drive numbers are 1-based
LD B,A ; Always use default drive
RET
; Handle a space character in the command line
; Character is passed in A
; State 0: Do not emit space to FILEBUF
; State 1: Do space to FILEBUF
; Set length byte for PATHBUF & create FCB in PATHBUF2
; -> State 2
; State 2: Do not emit space to FILEBUF
; State 3: Emit space to FILEBUF
; Set length byte for PATHBUF & create FCB in FCB1
; -> State 4
; State 4: Do not emit space to FILEBUF
; State 5: Emit space to FILEBUF
; Set length byte for PATHBUF & create FCB in FCB2
; -> State 6
; State 6: Emit space to FILEBUF
CMDSPC EX AF,AF' ; Save character for later
LD A,B ; Get parser state
CP 0 ; State 0 - eat the space
JP NZ,CSS1 ;
RET ;
CSS1 CP 1 ; State 1 - eat the space
JP NZ,CSS2 ;
EX AF,AF' ; Get character back
CALL EMTFILBUF ; Emit char to FILEBUF
LD A,E ; Write length byte to PATHBUF
LD (PATHBUF),A ; ...
CALL RSTPATHBUF ; Reset IY & E for next use
PUSH HL ; Preserve HL, BC, DE
PUSH BC ; ...
PUSH DE ; ...
LD HL,PATHBUF2 ; DMAADDR to PATHBUF2 - will put FCB there
LD (DMAADDR),HL ; ...
LD HL,PATHBUF ; NAME2FCB will use filename at PATHBUF
CALL DRVLETTER ; Handle any x: drive letter prefix
LD C,0 ; Do not check file sizes
CALL NAME2FCB ; Create FCB at PATHBUF2
LD A,'C' ; Set extension to .COM, in case not typed
LD (PATHBUF2+9),A ; ...
LD A,'O' ; ...
LD (PATHBUF2+10),A ; ...
LD A,'M' ; ...
LD (PATHBUF2+11),A ; ...
POP DE ; Restore DE, BC, HL
POP BC ; ...
POP HL ; ...
INC B ; Transition to state 2
RET ;
CSS2 CP 2 ; State 2 - eat the space
JP NZ,CSS3 ;
RET ;
CSS3 CP 3 ; State 3
JP NZ,CSS4 ;
EX AF,AF' ; Get character back
CALL EMTFILBUF ; Emit char to FILEBUF
LD A,E ; Write length byte to PATHBUF
LD (PATHBUF),A ; ...
CALL RSTPATHBUF ; Reset IY & E for next use
PUSH HL ; Preserve HL, BC, DE
PUSH BC ; ...
PUSH DE ; ...
LD HL,FCB1 ; DMAADDR to FCB1 - will put FCB there
LD (DMAADDR),HL ; ...
LD HL,PATHBUF ; NAME2FCB will use filename at PATHBUF
CALL DRVLETTER ; Handle any x: drive letter prefix
LD C,0 ; Do not check file sizes
CALL NAME2FCB ; Create FCB at FCB1
POP DE ; Restore DE, BC, HL
POP BC ; ...
POP HL ; ...
INC B ; Transition to state 4
RET ;
CSS4 CP 4 ; State 4 - eat the space
JP NZ,CSS5 ;
RET ;
CSS5 CP 5 ; State 5
JP NZ,CSS6 ;
EX AF,AF' ; Get character back
CALL EMTFILBUF ; Emit char to FILEBUF
LD A,E ; Write length byte to PATHBUF
LD (PATHBUF),A ; ...
CALL RSTPATHBUF ; Reset IY & E for next use
PUSH HL ; Preserve HL, BC, DE
PUSH BC ; ...
PUSH DE ; ...
LD HL,FCB2 ; DMAADDR to FCB2 - will put FCB there
LD (DMAADDR),HL ; ...
LD HL,PATHBUF ; NAME2FCB will use filename at PATHBUF
CALL DRVLETTER ; Handle any x: drive letter prefix
LD C,0 ; Do not check file sizes
CALL NAME2FCB ; Create FCB at FCB2
POP DE ; Restore DE, BC, HL
POP BC ; ...
POP HL ; ...
INC B ; Transition to state 6
RET ;
CSS6 CP 6 ; State 6
JP NZ,CSS7 ;
EX AF,AF' ; Get character back
CALL EMTFILBUF ; Emit char to FILEBUF
CSS7 RET
; Handle a non-space character in the command line
; Character is passed in A
; State 0: Do not emit character to FILEBUF
; Emit character to PATHBUF
; -> State 1
; State 1: Do not emit character to FILEBUF
; Emit character to PATHBUF
; State 2: Emit character to FILEBUF -> State 3
; Emit character to PATHBUF
; State 3: Emit character to FILEBUF
; Emit character to PATHBUF
; State 4: Emit character to FILEBUF -> State 5
; Emit character to PATHBUF
; State 5: Emit character to FILEBUF
; Emit character to PATHBUF
; State 6: Emit character to FILEBUF
; Do not emit character to PATHBUF
CMDNSPC EX AF,AF' ; Save character for later
LD A,B ; Get parser state
CP 0 ; State 0 - eat the character
JP NZ,CNS1 ;
EX AF,AF' ; Get character back
CALL EMTPATHBUF ; Emit char to PATHBUF
INC B ; Transition to state 1
RET ;
CNS1 CP 1 ; State 1 - eat the character
JP NZ,CNS2 ;
EX AF,AF' ; Get character back
CALL EMTPATHBUF ; Emit char to PATHBUF
RET ;
CNS2 CP 2 ; State 2
JP NZ,CNS3 ;
EX AF,AF' ; Get character back
CALL EMTPATHBUF ; Emit char to PATHBUF
CALL EMTFILBUF ; Emit char to FILEBUF
INC B ; Transition to state 3
RET ;
CNS3 CP 3 ; State 3
JP NZ,CNS4 ;
EX AF,AF' ; Get character back
CALL EMTPATHBUF ; Emit char to PATHBUF
CALL EMTFILBUF ; Emit char to FILEBUF
CNS4 CP 4 ; State 3
JP NZ,CNS5 ;
EX AF,AF' ; Get character back
CALL EMTPATHBUF ; Emit char to PATHBUF
CALL EMTFILBUF ; Emit char to FILEBUF
INC B ; Transition to state 5
RET ;
CNS5 CP 5 ; State 5
JP NZ,CNS6 ;
EX AF,AF' ; Get character back
CALL EMTPATHBUF ; Emit char to PATHBUF
CALL EMTFILBUF ; Emit char to FILEBUF
RET ;
CNS6 CP 6 ; State 6
JP NZ,CNS7 ;
EX AF,AF' ; Get character back
CALL EMTFILBUF ; Emit char to FILEBUF
CNS7 RET
; Write character at A to IX (which points into FILEBUF)
; Helper for CMDSPC/CMDNSPC
EMTFILBUF LD (IX+0H),A ; Emit char to FILEBUF
INC IX ; ...
INC D ; Character count for FILEBUF
RET
; Write character at A to IX (which points into PATHBUF)
; Helper for CMDNSPC
EMTPATHBUF LD (IY+0H),A ; Emit char to PATHBUF
INC IY ; ...
INC E ; Character count for FILEBUF
RET
; Table of commands. Each command has length byte prefix.
DIRCMD DEFB 4
DEFM 'DIR '
ERACMD DEFB 4
DEFM 'ERA '
RENCMD DEFB 4
DEFM 'REN '
TYPCMD DEFB 5
DEFM 'TYPE '
SAVCMD DEFB 5
DEFM 'SAVE '
; Compares string at HL with string at DE
; HL points to length byte preceeding string to search for
; Returns 0 in A for match, 0FFH for no match
COMPSTR LD C,(HL) ; Get length byte
INC HL ; Advance to first character of pattern
CSTL1 LD A,C ; See if count is zero
CP 0 ; ...
JP Z,CSTS1 ; If so, we have a match
LD B,(HL) ; Get character of search pattern
LD A,(DE) ; Get byte from input string
CP B ; Compare them
JP NZ,CSTS2 ; If different, no match
INC HL ; Advance pointers
INC DE ; ...
DEC C ; Decrement count
JP CSTL1 ; Loop
CSTS1 LD A,0 ; Return 0 for match
RET ;
CSTS2 LD A,0FFH ; Return 0FFH for no match
RET
; See if command entered is a built-in command (DIR, ERA, REN, TYPE, SAVE)
; Returns 0 in A if it was a built-in, non zero otherwise
; The FCB describing the command to run is in PATHBUF2
BUILTIN LD DE,PATHBUF2+1 ; Skip over drive byte in FCB
LD HL,DIRCMD ; See if 'DIR'
CALL COMPSTR ; ...
CP 0 ; ...
JP NZ,BIS1 ; If not, skip
CALL DIRECT ; Perform DIR
JP BIS6 ; Done
BIS1 LD DE,PATHBUF2+1 ; Skip over drive byte in FCB
LD HL,ERACMD ; See if 'ERA'
CALL COMPSTR ; ...
CP 0 ; ...
JP NZ,BIS2 ; If not, skip
CALL ERASE ; Perform ERA
JP BIS6 ; Done
BIS2 LD DE,PATHBUF2+1 ; Skip over drive byte in FCB
LD HL,RENCMD ; See if 'REN'
CALL COMPSTR ; ...
CP 0 ; ...
JP NZ,BIS3 ; If not, skip
CALL Z,RENAME ; If so, call function for REN
JP BIS6 ; Done
BIS3 LD DE,PATHBUF2+1 ; Skip over drive byte in FCB
LD HL,TYPCMD ; See if 'TYPE'
CALL COMPSTR ; ...
CP 0 ; ...
JP NZ,BIS4 ; If not, skip
CALL Z,TYPEFILE ; If so, call function for TYPE
JP BIS6 ; Done
BIS4 LD DE,PATHBUF2+1 ; Skip over drive byte in FCB
LD HL,SAVCMD ; See if 'SAVE'
CALL COMPSTR ; ...
CP 0 ; ...
JP NZ,BIS5 ; If not, skip
CALL Z,SAVEFILE ; If so, call function for SAVE
JP BIS6 ; Done
BIS5 LD A,0FFH ; Not a builtin
RET
BIS6 XOR A ; Is a builtin
RET
; Load and run a .COM file to 0100H
; The FCB describing the file to run is in PATHBUF2
RUNCOM LD DE,PATHBUF2 ; Point to the FCB in PATHBUF2
LD HL,PATHBUF2 ; Set DMAADDR to PATHBUF2 (not FILEBUF!)
LD (DMAADDR),HL ; ...
CALL _F_OPEN ;
CP 0 ;
JP NZ,RCOERR ; Open error
LD HL,0100H ; Set DMAADDR to 0100H
LD (DMAADDR),HL ; ...
RCL1 CALL F_READ ; Read records until done
LD HL,(DMAADDR) ; Advance DMAADDR for each record
LD BC,80H ; ...
ADD HL,BC ; ...
LD (DMAADDR),HL ; ...
CP 0 ; Check return code from F_READ
JP Z,RCL1 ; If zero, keep looping
CP 1 ; Check return code from F_READ
JP NZ,RCLERR ; If not EOF (1), then error
CALL F_CLOSE ; Close the file
LD HL,FILEBUF ; Reset DMAADDR to 0080H
LD (DMAADDR),HL ; ...
CALL PROGSTRT ; Run user program at 0100H
LD E,13 ; Print carriage return
CALL C_WRITE ; ...
LD HL,FILEBUF ; Reset DMAADDR to 0080H
LD (DMAADDR),HL ; ...
RET ;
RCOERR LD DE,NFMSG ; 'Not found' message
CALL C_WRITESTR ; ...
RET ;
RCLERR CALL F_CLOSE ; Close the file
LD HL,FILEBUF ; Reset DMAADDR to 0080H
LD (DMAADDR),HL ; ...
LD DE,REMSG ; 'Read error' message
CALL C_WRITESTR ; ...
RET
LMSG DEFM 'Loaded'
DEFB 13,'$'
NFMSG DEFM 'Not found'
DEFB 13,'$'
REMSG DEFM 'Read error'
DEFB 13,'$'
BFMSG DEFM 'Bad format'
DEFB 13,'$'
RNEMSG DEFM 'Rename error'
DEFB 13,'$'
; Show disk directory
; Use FCB1 for directory search
DIRECT LD A,(FCB1NAM) ; Get first char of filename
CP ' ' ; See if it is space
CALL Z,DIRFCB ; Set up FCB to search all files
LD HL,FILEBUF ; Reset DMAADDR to 0080H
LD (DMAADDR),HL ; ...
LD E,13 ; Carriage return
CALL C_WRITE ;
LD DE,FCB1 ; Default FCB address 1
CALL F_SFIRST ; Find first dir entry
CP 0 ;
RET NZ ; If not found, we're done
CALL PRDIRENT ; Print entry
DIRL1 LD DE,FCB1 ; Default FCB address 1
CALL F_SNEXT ; Find next dir entry
CP 0 ;
RET NZ ; If not found, we're done
CALL PRDIRENT ; Print entry
JP DIRL1 ; Loop for all files in dir
; Set up FCB1 to search all files
; If length of command tail is zero, or length of command tail is 3
; and 3rd char is ':' then set up FCB for searching
DIRFCB LD HL,FILEBUF ; Take a look at command tail
PUSH HL ; HL->IX
POP IX ; ...
LD A,(HL) ; Get length of command tail
CP 0 ; If zero ...
JP Z,DFS1 ; ... Then default drive
CP 4 ; If 4 chars (w/ leading/trailing spc)
JP NZ,DFS1 ; If not, then default drive
LD A,(IX+3) ; Should be ':'
CP ':' ; See if it is
JP NZ,DFS1 ; If not, then default drive
LD A,(IX+2) ; Load drive letter from command tail
SUB 'A'-1 ; Make into 1-based drive number
JP DFS2 ; Go make the FCB
DFS1 LD A,(CURDRV) ; Get current drive
AND 0FH ; Mask out user number
INC A ; 1-based for FCB
DFS2 LD (FCB1DRV),A ; Store drive letter
LD A,'?' ; Filename
LD (FCB1NAM),A ;
LD (FCB1NAM+1),A ;
LD (FCB1NAM+2),A ;
LD (FCB1NAM+3),A ;
LD (FCB1NAM+4),A ;
LD (FCB1NAM+5),A ;
LD (FCB1NAM+6),A ;
LD (FCB1NAM+7),A ;
LD (FCB1NAM+8),A ;
LD (FCB1NAM+9),A ;
LD (FCB1NAM+10),A ;
RET
; Print a directory entry in FILEBUF
PRDIRENT PUSH HL ;
LD HL,FILEBUF ;
LD C,0 ;
PRDL1 LD E,(HL) ;
PUSH HL ;
CALL C_WRITE ;
POP HL ;
INC HL ;
INC C ;
LD A,9 ; 8+1 because we already incremented
CP C ;
JP Z,PRDS2 ; Jump to routine to print the '.'
PRDS1 LD A,12 ; 8+3+1 because we already incremented
CP C ;
JP NZ,PRDL1 ;
LD E,13 ;
CALL C_WRITE ;
POP HL ;
RET ;
PRDS2 LD E,'.' ;
PUSH HL ;
CALL C_WRITE ;
POP HL ;
JP PRDS1 ;
; Erase file(s)
; Pattern for erase is in FCB1
; TODO: Should prompt Y/N if wildcards used
ERASE LD HL,FILEBUF ; Reset DMAADDR to 0080H
LD (DMAADDR),HL ; ...
LD DE,FCB1 ; Pass address of FCB1
CALL F_DELETE ; Do the delete operation
CP 0 ; Check return code
JP NZ,ERER ; If non zero print error
RET
ERER LD DE,NFMSG ; 'Not found' message
CALL C_WRITESTR ; ...
RET ;
; Rename a file
; Here we need to build our own 'special' FCB using the x:dest=src command line
; Maniplate the command tail to replace the leading space with char count of
; first string and the = with char count of second string. Then use NAME2FCB
; twice to build the 'special' FCB used by F_RENAME.
RENAME LD HL,FILEBUF ; Command tail is in filebuf
LD C,(HL) ; Read length of string
INC C ; Makes length compare easier
LD B,0 ; Use B to count chars
RENL1 INC HL ; Advance to next char
INC B ; Increase char count
LD A,B ; Copy to A for compare
CP C ; Compare with string length
JP Z,RENS4 ; If at end then bad format
LD A,(HL) ; Fetch char
CP '=' ; See if it is equals sign
JP Z,RENS1 ; Handle '=' sign
JP RENL1 ; Loop
RENS1 LD A,B ; Get char count
SUB 2 ; For initial space and '='
LD DE,FILEBUF ; Will write through ptr DE
INC DE ; Advance to initial space
LD (DE),A ; Store length of first string
LD (TEMPWORD),HL ; Store address of '=' sign
RENL2 INC HL ; Advance to next char
INC B ; Increase char count
LD A,B ; Copy to A for compare
CP C ; Compare with string length
JP Z,RENS3 ; If at end then jump out
JP RENL2 ; Loop
RENS3 LD A,B ; Get overall char count into A
SUB 4 ; Compensate for extra chars
LD HL,FILEBUF ; Get char count of first string
INC HL ; ...
SUB (HL) ; Subtract from char count
LD HL,(TEMPWORD) ; Address of '=' sign
LD (HL),A ; Store length of second string
LD HL,FCB2 ; Set DMAADDR for destination
LD (DMAADDR),HL ; ...
LD HL,FILEBUF ; Set HL to dest file string
INC HL ; ...
CALL DRVLETTER ; Handle any x: drive letter prefix
LD C,0 ; NAME2FCB should not do size lookup
CALL NAME2FCB ; Make FCB for source file
LD HL,FCB1 ; Set DMAADDR for source
LD (DMAADDR),HL ; ...
LD HL,(TEMPWORD) ; Set HL to source file string
LD C,0 ; NAME2FCB should not do size lookup
CALL NAME2FCB ; Make FCB for source file
LD A,(FCB2) ; Get drive number of destination
LD (FCB1),A ; Store it for source too
LD DE,FCB1 ; Pass address of FCB to F_RENAME
LD HL,FILEBUF ; Set DMAADDR to point to FILEBUF
LD (DMAADDR),HL ; ...
CALL F_RENAME ; Perform the rename
CP 0 ; See if it was successful
JP NZ,RENS5 ; If not, display message
RET ;
RENS4 LD DE,BFMSG ; 'Bad format' message
CALL C_WRITESTR ; ...
RET
RENS5 LD DE,RNEMSG ; 'Rename error' message
CALL C_WRITESTR ; ...
RET
; Show a test file on the console
; File to open is in FCB1
TYPEFILE LD DE,FCB1 ; Point to FCB1
LD HL,FILEBUF ; Set DMAADDR to FILEBUF
LD (DMAADDR),HL ; ...
CALL F_OPEN ; Open the file with wildcard search
CP 0 ;
JP NZ,TFOERR ; Open error
TFL1 LD HL,FILEBUF ; Write ^Z to buffer in case 0 bytes read
LD A,26 ; ...
LD (HL),A ; ...
LD DE,FCB1 ; Point to FCB1
CALL F_READ ; Read records until done
CP 0 ; Check return code from F_READ
JP NZ,TFS1 ; If non-zero jump out of loop
CALL TYPEBLK ; Display block on screen
JP TFL1 ; Loop
TFS1 CP 1 ; Check return code from F_READ
JP NZ,TFLERR ; If not EOF (1), then error
CALL TYPEBLK ; Display last block on screen
CALL F_CLOSE ; Close the file
LD E,13 ; Carriage return
CALL C_WRITE ;
RET ;
TFOERR LD DE,NFMSG ; 'Not found' message
CALL C_WRITESTR ; ...
RET ;
TFLERR CALL F_CLOSE ; Close the file
LD DE,REMSG ; 'Read error' message
CALL C_WRITESTR ; ...
RET
; Helper function for TYPEFILE
TYPEBLK PUSH DE ; Preserve DE, DL
PUSH HL ; ...
LD HL,FILEBUF ; Print block in FILEBUF
LD C,0 ; Initialize character count
TBL1 LD A,(HL) ; Get character
CP 26 ; See if it is ^Z character
JP Z,TBS1 ; If so, we are done
LD E,A ; Write to console
PUSH HL ;
PUSH DE ;
CALL C_WRITE ; ...
POP DE ;
POP HL ;
INC HL ; Advance pointer
INC C ; Increment character count
LD A,C ; See if we have printed whole block
CP 80H ; ...
JP NZ,TBL1 ; If not, loop
TBS1 POP HL ; Restore HL, DE
POP DE ; ...
RET
; Save memory to disk file
; Use the FCB already constructed in FCB2
; TODO Write this
SAVEFILE
RET
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; Additional private scratch space for BDOS
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; ProDOS Parameter Lists
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; Parameter list for ProDOS CREATE call
FMMLI DEFB 20H,00H,0BFH ; JSR $BF00 in 6502 code
DEFB 0C0H ; ProDOS CREATE call
DEFW FMMLIPL+OFFSET ; Pointer to parm list in 6502 addr space
DEFB 60H ; RTS in 6502 code
FMMLIPL DEFB 7 ; ProDOS PL: Seven parameters
FMMLIP DEFW PATHBUF+OFFSET ; ProDOS PL: Pointer to path in 6502 addr
FMMLIA DEFB 0C3H ; ProDOS PL: Access (0C3H full access)
FMMLIT DEFB 0 ; ProDOS PL: File type (0 means typeless)
FMMLIAT DEFW 0000H ; ProDOS PL: Aux file type (always 0000H)
FMMLIS DEFB 1 ; ProDOS PL: Storage type (1 for file)
FMMLICD DEFW 0000H ; ProDOS PL: Create date (always 0000H)
FMMLICT DEFW 0000H ; ProDOS PL: Create time (always 0000H)
; Parameter list for ProDOS DESTROY call
FDMLI DEFB 20H,00H,0BFH ; JSR $BF00 in 6502 code
DEFB 0C1H ; ProDOS DESTROY call
DEFW FDMLIPL+OFFSET ; Pointer to parm list in 6502 addr space
DEFB 60H ; RTS in 6502 code
FDMLIPL DEFB 1 ; ProDOS PL: Seven parameters
FDMLIP DEFW PATHBUF+OFFSET ; ProDOS PL: Pointer to path in 6502 addr
; Parameter list for ProDOS RENAME call
FRNMLI DEFB 20H,00H,0BFH ; JSR $BF00 in 6502 code
DEFB 0C2H ; ProDOS RENAME call
DEFW FRNMLIPL+OFFSET ; Pointer to parm list in 6502 addr space
DEFB 60H ; RTS in 6502 code
FRNMLIPL DEFB 2 ; ProDOS PL: Two parameters
FRNMLIP1 DEFW PATHBUF+OFFSET ; ProDOS PL: Pointer to path in 6502 addr
FRNMLIP2 DEFW PATHBUF2+OFFSET ; ProDOS PL: Pointer to path in 6502 addr
; Parameter list for ProDOS GET_FILE_INFO call
GFIMLI DEFB 20H,00H,0BFH ; JSR $BF00 in 6502 code
DEFB 0C4H ; ProDOS GET_FILE_INFO call
DEFW GFIMLIPL+OFFSET ; Pointer to parm list in 6502 addr space
DEFB 60H ; RTS in 6502 code
GFIMLIPL DEFB 0AH ; ProDOS PL: Ten parameters
GFIMLIP DEFW PATHBUF+OFFSET ; ProDOS PL: Pointer to path in 6502 addr
GFIMLIAC DEFB 0 ; ProDOS PL: Access
GFIMLIT DEFB 0 ; ProDOS PL: File type
GFIMLIAT DEFW 0000H ; ProDOS PL: Aux type
GFIMLIS DEFB 1 ; ProDOS PL: Storage type
GFIMLIBU DEFW 0000H ; ProDOS PL: Blocks used
GFIMLIMD DEFW 0000H ; ProDOS PL: Modification date
GFIMLIMT DEFW 0000H ; ProDOS PL: Modification time
GFIMLICD DEFW 0000H ; ProDOS PL: Create date
GFIMLICT DEFW 0000H ; ProDOS PL: Create time
; Parameter list for ProDOS OPEN call
FOMLI DEFB 20H,00H,0BFH ; JSR $BF00 in 6502 code
DEFB 0C8H ; ProDOS OPEN call
DEFW FOMLIPL+OFFSET ; Pointer to parm list in 6502 addr space
DEFB 60H ; RTS in 6502 code
FOMLIPL DEFB 3 ; ProDOS PL: Three parameters
FOMLIP DEFW PATHBUF+OFFSET ; ProDOS PL: pointer to path in 6502 addr
FOMLII DEFW 0000H ; ProDOS PL: pointer to IO buffer
FOMLIN DEFB 0 ; ProDOS PL: File reference number
; Parameter list for ProDOS READ call
FRMLI DEFB 20H,00H,0BFH ; JSR $BF00 in 6502 code
DEFB 0CAH ; ProDOS READ call
DEFW FRMLIPL+OFFSET ; Pointer to parm list in 6502 addr space
DEFB 60H ; RTS in 6502 code
FRMLIPL DEFB 4 ; ProDOS PL: Four parameters
FRMLIN DEFB 0 ; ProDOS PL: File reference number
FRMLIDB DEFW 0000H ; ProDOS PL: Data buffer
FRMLIRC DEFW 128 ; ProDOS PL: Request count (bytes to read)
FRMLITC DEFW 0000H ; ProDOS PL: Number of bytes transferred
; Parameter list for ProDOS WRITE call
FWMLI DEFB 20H,00H,0BFH ; JSR $BF00 in 6502 code
DEFB 0CBH ; ProDOS WRITE call
DEFW FWMLIPL+OFFSET ; Pointer to parm list in 6502 addr space
DEFB 60H ; RTS in 6502 code
FWMLIPL DEFB 4 ; ProDOS PL: Four parameters
FWMLIN DEFB 0 ; ProDOS PL: File reference number
FWMLIDB DEFW 0000H ; ProDOS PL: Data buffer
FWMLIRC DEFW 128 ; ProDOS PL: Request count (bytes to read)
FWMLITC DEFW 0000H ; ProDOS PL: Number of bytes transferred
; Parameter list for ProDOS CLOSE call
FCMLI DEFB 20H,00H,0BFH ; JSR $BF00 in 6502 code
DEFB 0CCH ; ProDOS CLOSE call
DEFW FCMLIPL+OFFSET ; Pointer to parm list in 6502 addr space
DEFB 60H ; RTS in 6502 code
FCMLIPL DEFB 1 ; ProDOS PB: One parameter
FCMLIN DEFB 0 ; ProDOS PB: File reference number
; Parameter list for ProDOS FLUSH call
FLMLI DEFB 20H,00H,0BFH ; JSR $BF00 in 6502 code
DEFB 0CDH ; ProDOS FLUSH call
DEFW FLMLIPL+OFFSET ; Pointer to parm list in 6502 addr space
DEFB 60H ; RTS in 6502 code
FLMLIPL DEFB 1 ; ProDOS PL: One parameter
FLMLIN DEFB 0 ; ProDOS PL: File ref num (0 = all files)
; Parameter list for ProDOS SET_MARK call
SMMLI DEFB 20H,00H,0BFH ; JSR $BF00 in 6502 code
DEFB 0CEH ; ProDOS SET_MARK call
DEFW SMMLIPL+OFFSET ; Pointer to parm list in 6502 addr space
DEFB 60H ; RTS in 6502 code
SMMLIPL DEFB 2 ; ProDOS PL: Two parameters
SMMLIN DEFB 0 ; ProDOS PL: File reference number
SMMLIP1 DEFB 0 ; ProDOS PL: Position (LSB)
SMMLIP2 DEFB 0 ; ProDOS PL: Position
SMMLIP3 DEFB 0 ; ProDOS PL: Position (MSB)
; Parameter list for ProDOS SET_EOF call
SEMLI DEFB 20H,00H,0BFH ; JSR $BF00 in 6502 code
DEFB 0D0H ; ProDOS SET_EOF call
DEFW SEMLIPL+OFFSET ; Pointer to parm list in 6502 addr space
DEFB 60H ; RTS in 6502 code
SEMLIPL DEFB 2 ; ProDOS PL: Two parameters
SEMLIN DEFB 0 ; ProDOS PL: File reference number
SEMLIE1 DEFB 0 ; ProDOS PL: EOF position (LS-byte)
SEMLIE2 DEFB 0 ; ProDOS PL: EOF position
SEMLIE3 DEFB 0 ; ProDOS PL: EOF position (MS-byte)
; Parameter list for ProDOS GET_EOF call
GEMLI DEFB 20H,00H,0BFH ; JSR $BF00 in 6502 code
DEFB 0D1H ; ProDOS GET_EOF call
DEFW GEMLIPL+OFFSET ; Pointer to parm list in 6502 addr space
DEFB 60H ; RTS in 6502 code
GEMLIPL DEFB 2 ; ProDOS PL: Two parameters
GEMLIN DEFB 0 ; ProDOS PL: File reference number
GEMLIE1 DEFB 0 ; ProDOS PL: EOF position (LS-byte)
GEMLIE2 DEFB 0 ; ProDOS PL: EOF position
GEMLIE3 DEFB 0 ; ProDOS PL: EOF position (MS-byte)
; FCB used for opening ProDOS directory corresponding to drive
DFCB ; File control block for directory
DFCBDRV DEFB 00H ; FCB Drive (0 current, 1 A:, 2 B: etc)
DFCBNAM DEFM ' ' ; FCB filename and extension (all spaces)
DFCBEX DEFB 00H ; FCB extent field
DFCBS1 DEFB 00H ; FCB S1 field
DFCBS2 DEFB 00H ; FCB S2 field
DFCBRC DEFB 00H ; FCB RC field
DFCBMAP DEFS 16 ; Map of blocks in file
DFCBSR DEFB 0 ; FCB seq record number
DFCBRR DEFB 0,0,0 ; FCB rand record number
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; End of ProDOS Parameter Lists
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; Drive parameter block (CP/M 2.2 format)
; See Johnson-Laird Pg 33-34
DPB DEFW 32 ; # 128 byte records per track (for Disk II)
DEFB 3 ; Block shift (1024 byte allocation blocks)
DEFB 7 ; Block mask (1024 byte allocation blocks)
DEFB 0 ; Extent mask (1024 byte alocation blocks)
DEFW 1023 ; # allocation blocks on disk (1MB)
DEFW 255 ; # directory entries
DEFB 0 ; Directory allocation bitmap byte 1
DEFB 0 ; Directory allocation bitmap byte 2
DEFW 0 ; Checksum vector size
DEFW 0 ; # of reserved tracks
; Fake allocation vector (2048 allocation blocks / 8 = 256 bytes)
ALLOCVEC DEFS 256 ; TODO: This is a waste of memory
; Get rid of it?
; 64 byte buffer for storing a file path as a Pascal style string
PATHBUF
PATHLEN DEFB 0
PATH DEFS 64
; 64 byte buffer for storing a second file path as a Pascal style string
PATHBUF2
PATHLEN2 DEFB 0
PATH2 DEFS 64
; Record file reference numbers for each I/O buffer
; Or 0 if no file is using the buffer
FRN1 DEFB 0
FRN2 DEFB 0
FRN3 DEFB 0
FRN4 DEFB 0
; Buffer for printing hex numbers to console
HEXBUF DEFB 0,0,0,0,'$'
; 512 byte buffer for reading directories (used by F_SFIRST and F_SNEXT)
DIRBUF
DIRBUF1 DEFS 128
DIRBUF2 DEFS 128
DIRBUF3 DEFS 128
DIRBUF4 DEFS 128
; Four 1024 byte ProDOS I/O buffers
; These must start on a page boundary
; ProDOS occupies the space from $BF00 up (in 6502 addresses)
; 6502 code is loaded at $B800 for now
; So in theory the I/O buffers could go from $A800-$B800 (09800H base)
;ORG 9000H ; Try this - WORKS
ORG 9800H ; Try this - WORKS
IOBUF1 DEFS 1024
IOBUF2 DEFS 1024
IOBUF3 DEFS 1024
IOBUF4 DEFS 1024
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;