;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; 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 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;