#asm
;
;  *** START OF ROUTINES INVOLVING FILES ***
;    These routines rely on the presence of CRUN and CONIO libraries
;
;
;  Functions in FILE.LIB :
;
;  putc(char,iochan)    Output char to file
;  getc(iochan)         Get char from file
;  fopen(filename,mode) Open file for "r" or "w"
;  fclose(iochan)       Close file
;  eof()                Check for attempt to read past end of file
;  exit()               Close write file and exit to BDOS
;
ccFCBO EQU 5CH
ccOUTBF EQU 80H
;
;  Put a byte into write buffer and empty to disk when full
;  Function format: putc(char,iochannel)
;
putc: POP BC ;Return address
 POP DE ;I/O channel
 POP HL ;O/P byte in L
 PUSH HL
 PUSH DE
 PUSH BC
 LD A,(ccOFLG) ;File open?
 CP 1
 JP NZ,ccFCMSG ;File closed message
 LD A,(ccCONFG) ;Is O/P to go to CON:?
 CP 1 ;CON: marked as write file?
 LD A,L ;Get a byte to A
 JP NZ,ccWNB ;Send it to file
 JP ccATOV ;Or to CON: and hence return
;
;  Get a byte from a read file.
;  Function format:  getc(iochannel)
;
getc: LD A,(ccINFLG) ;File open?
 CP 1
 JP NZ,ccFCMSG ;File closed message
 CALL ccGNB ;Get byte to A
 JP ccsxt ;
;
;  Open a file for reading or writing
;  Function format: fopen(filename,mode)  e.g. fopen("FRED.DAT","r")
;  Returns channel no. 0 if not opened. O/P can be to CON:
;
fopen: POP BC ;Return address
 POP DE ;Point to mode "r" or "w"
 POP HL ;Point to file name
 PUSH HL
 PUSH DE
 PUSH BC
 LD A,(DE)
 AND 5FH ;L/C to U/C
 CP 'W'
 JP Z,ccWOPN1 ;Go and open write file or CON:
 CP 'R' 
 JP Z,ccROPN1 ;Or open a read file
 LD DE,ccFMERR ;Otherwise it's an error
 CALL ccPMESS ;Print message
 LD HL,0  ;Error indicated by channel no. 0
 RET
;
; Open a write file which may be directed to CON:
;
ccWOPN1: LD A,(ccOFLG) ;Check if write channel is already in use
 CP 0 ;0 if OK
 JP NZ,ccFLINU ;Go and print error and return
 LD A,20H ;Skip blanks
ccWOPN2: CP (HL)
 JP NZ,ccWOPN3
 INC HL
 JP ccWOPN2
ccWOPN3: PUSH HL ;Save pointer to file name
 LD DE,ccCONST ;Point to 'CON:' string
 CALL ccCMPST ;Return Z if matched
 JP Z,ccWOPN4
 POP HL ;Try LST:
 PUSH HL
 LD DE,ccLSTST
 CALL ccCMPST
 JP NZ,ccWOPN5 ;It must be a file
 LD A,1 ;To put in ccLSTFG
ccWOPN4: POP DE ;Recover pointer
 LD (ccLSTFG),A ;0 if CON: 1 if LST:
 LD A,1 ;Mark file open
 LD (ccOFLG),A
 LD (ccCONFG),A ;Mark as CON:
 JP ccsxt ;back with channel 1 in HL
;  Set up genuine file
ccWOPN5: POP HL ;Get pointer back
 LD DE,ccFCBO ;Point to FCB for write file
 CALL ccSFCB ;Set up FCB for opening  DE->FCB,HL=Filename
 PUSH DE
 LD DE,ccOUTBF
 CALL ccSDMA ;Set DMA for O/P
 POP DE
 CALL ccDELFL ;Delete if needed
 CALL ccCREFL ;Create file
 JP Z,ccDIRFL ;Directory full error
 XOR A ;Set O/P buffer pointer 
 LD (ccOBP),A
 INC A
 LD (ccOFLG),A ;Mark o/p file open
 JP ccsxt ; And return with Channel 1 in HL
;
; Compare two 4 byte strings pointed to by HL and DE
;  return Z and A=0 if matched
;
ccCMPST: LD B,4
ccCMPS1: LD A,(HL)
 CALL ccCAPST ;Convert to U/C
 EX DE,HL
 CP (HL)
 EX DE,HL
 RET NZ ;Match failed
 INC HL
 INC DE
 DEC B
 JP NZ,ccCMPS1
 XOR A ;Set Z and zero A
 RET
;
; Open a read file for ccGNB routine
;  Enter with HL->filename
;
ccROPN1: LD A,(ccINFLG) ;Check if i/p channel is in use
 CP 0 ;0 if OK
 JP NZ,ccFLINU ;Error message and return
 LD DE,ccFCBI ;Point to FCB for read file
 CALL ccSFCB ;Go and set FCB 
 PUSH DE
 LD DE,ccINBUF
 CALL ccSDMA ;Set up DMA for I/P
 POP DE
 CALL ccOPNFL ;Return with Z set if not there
 JP Z,ccNOFIL
 LD A,1 ;Mark file as open
 LD (ccINFLG),A
 XOR A ;Set EOF flag to show that EOF has not yet been reached
 LD (ccFENDF),A
 LD A,80H
 LD (ccIBP),A ;Force initial read when accessed
 LD HL,2 ;Return with channel no. in HL
 RET
;
; Close a file. Switch off and ccCONFG
;   Function format: fclose(iochannel)
;   if iochannel=0, ^Z is not added to
;   end of write file if it is open.
;
fclose: LD A,L ;CHECK CHANNEL NO.
 CP 2
 JP Z,ccFCLS3 ;Read file
 LD A,(ccCONFG) ;Is there one to close?
 CP 1
 JP Z,ccFCLS2 ;No. O/P was to CON:
 XOR A
 CP L ; Check if CZ is to be omitted iochannel=0
 JP Z,ccFCLS1
 LD A,CZ ;EOF to write file
 CALL ccWNB
ccFCLS1: LD L,1 ; reset to the write channel no.
 LD DE,ccOUTBF
 CALL ccSDMA
 LD DE,ccFCBO
 CALL ccDWRT ;Flush write buffer to disk
 CALL ccCLSFL ;Close write file
ccFCLS2: XOR A
 LD (ccOFLG),A ;Mark O/P file closed
 LD (ccCONFG),A
 LD (ccLSTFG),A ;LST: off
 RET
ccFCLS3: XOR A ;Mark read file closed
 LD (ccINFLG),A
 RET
;
;  Test for EOF in a read file. 
;  Function format: eof()  Returns 0 until an attempt is made to
;   read a sector beyond the end of the file
;
eof: LD A,(ccFENDF)
 JP ccsxt
;
;  Exit after closing write files
;  Function format: exit()
;
exit: LD HL,1
 PUSH HL ;Write channel
 LD A,(ccOFLG)
 OR A ;Z if write closed
 CALL NZ,fclose
 XOR A ; Reset ccINFLG
 LD (ccINFLG),A
 JP 0
;
; Set up FCB. Enter with DE->FCB , HL->File name, AF,DE preserved
;
ccSFCB: PUSH AF
 PUSH HL
 PUSH DE
 CALL ccCFCB ;Pad out FCB with 20H and set NR and EXT to 0
 POP DE ;FCB
 POP HL ;Name
 LD (ccFNST),HL
 PUSH DE ;Still save FCB
ccSFCB1: LD A,(HL) ;Skip blanks
 CP 20H
 INC HL
 JP Z,ccSFCB1
 LD A,(HL)
 CP ':' ;Drive given?
 DEC HL
 JP NZ,ccSFCB2 ;Use default
 LD A,(HL)
 CALL ccCAPST
 SUB '@'
 LD (DE),A
 INC HL
 INC HL ;Point to name
 JP ccSFCB3
ccSFCB2: XOR A ;Use default drive
 LD (DE),A
ccSFCB3: INC DE
 LD B,8
ccSFCB4: LD A,(HL) ;Move filename to FCB
 CALL ccCAPST ; -> U/C
 CP '.'
 JP Z,ccSFCB5
 CP 0 ; End?
 JP Z,ccSFCB7
 LD (DE),A
 INC HL
 INC DE
 DEC B
 JP NZ,ccSFCB4 ;Keep looping until '.'
ccSFCB5: LD A,(HL)
 CP 0 ;End of string?
 JP Z,ccSFCB7 ;Exit
 CP 20H
 JP Z,ccSFCB7
 CP '.'
 INC HL
 JP NZ,ccSFCB5 ;Keep looping if name not done
 LD B,3
 POP DE ;Get FCB start
 PUSH DE
 PUSH HL ;Save pointer to type
 LD HL,9
 ADD HL,DE
 EX DE,HL ;Point DE to type in FCB
 POP HL
ccSFCB6: LD A,(HL)
 CALL ccCAPST
 CP 0
 JP Z,ccSFCB7
 LD (DE),A
 INC HL
 INC DE
 DEC B
 JP NZ,ccSFCB6
ccSFCB7: POP DE ;Exit. DE->FCB
 POP AF
 RET
;
; CLEAR OUT FCB WITH BLANKS & 0's IN NR & EX
;
ccCFCB: PUSH DE
 POP HL
 LD (HL),0 ;Set default drive
 INC HL
 LD A,20H
 LD B,11
ccCFCB1: LD (HL),A
 INC HL
 DEC B
 JP NZ,ccCFCB1
 XOR A
 LD (HL),A ;EX
 LD HL,20H ;Point to NR
 ADD HL,DE
 LD (HL),A
 RET
;
; Set letters to U/C
;
ccCAPST: CP 61H ;'a'
 RET C
 CP 7BH ;'z'+1
 RET NC
 SUB 20H
 RET
;
; Print out file name
;
ccPFILE: PUSH HL
 CALL crlf
 LD HL,(ccFNST)
 PUSH HL
 CALL puts
 POP HL
 POP HL
 RET
;
ccDIRFL: CALL ccPFILE ; Print file name
 LD DE,ccDFLM ;Print 'Directory full' message
ccDFUL1: CALL ccPMESS
 LD HL,0 ;Error indicator
 RET
;
ccNOFIL: CALL ccPFILE
 LD DE,ccNOFLM ;Print 'No such file' message
 JP ccDFUL1
;
ccFCMSG: LD DE,ccFCERR ;Print 'File closed' message
 CALL ccPMESS
 LD A,0FFH ;Error code
 JP ccsxt
ccFLINU: CALL ccPFILE
 LD DE,ccFIUM ;Print 'File Channel in use'
 JP ccDFUL1
; 
ccCONST: DEFB 'CON:' ;CON: string for comparison in ccWOPN1
ccLSTST: DEFB 'LST:' ;LST: string
;
; File Messages
;
ccNOFLM: DEFB ' No such file'
 DEFW 0A0DH
 DEFB '$'
ccDFLM: DEFB ' Disk/Directory full'
 DEFW 0A0DH
 DEFB '$'
ccFMERR: DEFB ' File Mode Error'
 DEFW 0A0DH
 DEFB '$'
ccFIUM: DEFB ' Read or Write file channel in use'
 DEFW 0A0DH
 DEFB '$'
ccFCERR: DEFW 0A0DH
 DEFB 'File not open'
 DEFW 0A0DH
 DEFB '$' ;
;
;  FLAG BYTES
;
ccCONFG: DEFB 0 ;Flag to show that CON: is the O/P file
ccFENDF: DEFB 0 ;Flag to show that EOF hasn't been reached during a read
ccINFLG: DEFB 0 ;Flag to show that i/p channel is in use
ccOFLG: DEFB 0  ;O/p channel in use (1 if in use)
ccFNST: DEFS 2 ;Pointer to file name
;
;
; ASSORTED DISK ROUTINES - MANY BASED ON THOSE IN CPMUG VOL 16.
;
; 'ccENTRY' is a general register saver for use with BDOS calls.
;
ccENTRY: PUSH HL
 PUSH DE
 PUSH BC
 CALL ccBDOS
 POP BC
 POP DE
 POP HL
 RET
;
; ccDELFL - DELETE FILE. FCB POINTED BY DE
; HL, DE, BC PRESERVED
;
ccDELFL: PUSH BC
 LD C,19 ;DELETE
 CALL ccENTRY
 POP BC ;NO EXIT CONDS
 RET
;
; ccPMESS - PRINTS OUT MESSAGE ->DE  UNTIL $
; ALL REGS SAVED
;
ccPMESS: PUSH AF
 PUSH BC
 LD C,9
 CALL ccENTRY
 POP BC
 POP AF
 RET
;
; ccOPNFL  OPEN AN EXISTING FILE WHOSE FCB IS POINTED TO BY DE
; FILE NAME AND DRIVE IN FCB ALREADY. EXIT WITH Z SET IF NO SUCH FILE.
; HL, DE, BC PRESERVED
;
ccOPNFL: PUSH HL
 PUSH BC
 LD HL,12
 ADD HL,DE
 LD (HL),0 ;ZERO EXTENT
 LD HL,32
 ADD HL,DE
 LD (HL),0 ;ZERO NR
 LD C,15 ;OPEN
 CALL ccENTRY
 CP 0FFH ;Z IF NONE
 POP BC
 POP HL
 RET
;
; ccCLSFL - CLOSE FILE WHOSE FCB IS POINTED TO BY DE
; HL, DE, AND BC PRESERVED. RETURNS Z SET IF NOT PRESENT
;
ccCLSFL: PUSH BC
 LD C,16 ;CLOSE
 CALL ccENTRY
 POP BC
 CP 0FFH ; Z IF NOT PRESENT
 RET
;
; ccCREFL - CREATE FILE WHOSE FCB IS POINTED TO BY DE
; HL, DE, BC PRESERVED. Z SET IF NO DIR SPACE
;
ccCREFL: PUSH BC
 LD C,22 ;MAKE FILE
 CALL ccENTRY
 CP 0FFH ;Z IF NO SPACE
 POP BC
 RET
;
; ccSDMA - SET DMA BUFFER TO ADDRESS IN DE
; HL, DE, BC PRESERVED.
;
ccSDMA: PUSH BC
 LD C,26 ;DMA SET
 CALL ccENTRY
 POP BC
 RET
;
; ccDREAD - READ A FILE SECTOR. DE POINTS TO FCB
; HL, DE, BC PRESERVED
;
ccDREAD: PUSH BC
 LD C,20 ;READ SECTOR
 CALL ccENTRY
 CP 0 ;Z SET IF NORMAL
 POP BC
 RET
;
; ccDWRT - WRITE A FILE SECTOR. DE POINTS TO FCB
; HL, DE, BC PRESERVED
;
ccDWRT: PUSH BC
 LD C,21 ;WRITE SECTOR
 CALL ccENTRY
 CP 0 ;Z SET IF NORMAL
 POP BC
 RET
;
;
; WRITE NEXT BYTE TO FILE VIA STANDARD BUFFER AT 80H WITH THE
; STANDARD FCB.
;
ccWNB: PUSH HL
 PUSH DE
 PUSH AF
 LD A,(ccOBP)
 CP 80H
 JP NZ,ccWNB0
 LD DE,ccOUTBF
 CALL ccSDMA ;SET FOR STANDARD O/P BUFF
 LD DE,ccFCBO
 CALL ccDWRT ;WRITE 1 SECTOR
 JP Z,ccWNB00 ;OK
 LD DE,ccWERR
 CALL ccPMESS
 JP 0H ;WARM START
ccWERR: CALL ccPFILE
 DEFB ' Write Error'
 DEFW 0A0DH
 DEFB '$'
ccWNB00: XOR A ;ZERO COUNT IF NEW BUFF
ccWNB0: LD E,A
 LD D,0
 INC A
 LD (ccOBP),A
 LD HL,ccOUTBF
 ADD HL,DE
 POP AF
 LD (HL),A
 POP DE
 POP HL
 RET
;
ccOBP: DEFS 1 ;O/P BUFFER COUNTER
;ccOUTBF: DEFS 80H ;Currently set at CP/M default buffer
;ccFCBO: DEFS 36 ;Currently set at 5CH
;
;  GET NEXT BYTE FROM I/P BUFFER ccINBUF AND REFILL WHEN EXHAUSTED
;
ccGNB: PUSH DE
 LD A,(ccFENDF) ;EOF?
 CP 0
 JP NZ,ccGNB1
 LD A,(ccIBP)
 CP 80H ;Refill needed?
 JP NZ,ccGNB0
 PUSH HL
 PUSH BC
 LD DE,ccINBUF
 CALL ccSDMA
 LD DE,ccFCBI
 CALL ccDREAD
 POP BC
 POP HL
 JP NZ,ccGNB1 ;Past end of file
 XOR A
ccGNB0: LD E,A
 LD D,0
 INC A
 LD (ccIBP),A
 PUSH HL
 LD HL,ccINBUF
 ADD HL,DE
 LD A,(HL)
 POP HL
 POP DE
 RET
ccGNB1: LD A,0FFH ;EOF keep sending -1
 LD (ccFENDF),A
 POP DE
 RET
;
ccIBP: DEFS 1
ccINBUF: DEFS 80H ;Input buffer if needed
ccFCBI: DEFS 36 ;FCB if needed
; END
#endasm
S 36 ;FCB if needed
; END
#endasm
ndasm
eded
ccFCBI: DEFS