You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

4116 lines
121 KiB

TITLE "ZSDOS 2"
;************************************************************************
;* Z S D O S v2 *
;* A banked CP/M 2.2 compatible replacement BDOS *
;* by Harold F. Bower and Cameron W. Cotrill *
;*----------------------------------------------------------------------*
;* Disassembly: jxl May 2025 *
;* public release 1.0 May 2025 *
;* see remarks at the end *
;*----------------------------------------------------------------------*
;* Assemble with SLR Z80ASM *
;* *
;* A>Z80ASM ZSDOS2/6 *
;************************************************************************
NAME ('DOS')
VER EQU 20H
REV MACRO
DEFB '27g'
ENDM
DATE MACRO
DEFB '1993'
ENDM
;::::: DEFINITIONS
RAMLOW EQU 0000H ; Start address memory
; Bios entry points
COMMON /_BIOS_/
BIOS EQU $
CSEG
; BOOT EQU BIOS+0000H ; Cold Boot
; WBOOT EQU BIOS+0003H ; Warm Boot
CONST EQU BIOS+0006H ; Console Status
CONIN EQU BIOS+0009H ; Console Input
CONOUT EQU BIOS+000CH ; Console Output
LIST EQU BIOS+000FH ; List Output
PUNCH EQU BIOS+0012H ; Punch Output
READER EQU BIOS+0015H ; Reader Input
HOME EQU BIOS+0018H ; Home Disk
SELDSK EQU BIOS+001BH ; Select Disk
SETTRK EQU BIOS+001EH ; Select Track
SETSEC EQU BIOS+0021H ; Select Sector
SETDMA EQU BIOS+0024H ; Set DMA Address
READ EQU BIOS+0027H ; Read 128 Bytes
WRITE EQU BIOS+002AH ; Write 128 Bytes
; LISTST EQU BIOS+002DH ; List Status
SECTRN EQU BIOS+0030H ; Sector Translation
MOVE EQU BIOS+004BH ; (Interbank) move
SELMEM EQU BIOS+0051H ; Select memory bank
XMOVE EQU BIOS+0057H ; Set memory banks for MOVE
RETMEM EQU BIOS+0075H ; Return current memory bank
OTPABK EQU BIOS+082H ; offset TPA bank (from B/P Bios base)
OSYSBK EQU BIOS+083H ; offset SYS bank
OZ3ENV EQU BIOS+098H ; offset Z3ENV
; Control chars
CONTC EQU 03H ; Key to generate warm boot
CONTH EQU 08H ; Backspace
TAB EQU 09H ; Tab
LF EQU 0AH ; Line feed
CR EQU 0DH ; Carriage return
CONTP EQU 10H ; Set/reset print flag
CONTR EQU 12H ; Retype line
CONTS EQU 13H ; Stop console output
CONTX EQU 18H ; Delete line (backspaces)
CONTU EQU 15H ; Same as Control-X
RUBOUT EQU 7FH ; Delete last char
; Disk related
MAXEXT EQU 1FH ; Maximum extent number
MAXMOD EQU 3FH ; Maximum data module number
TDCKSM EQU 91H ; Checksum of !!!TIME&.DAT
; Attribute bits
PUBATT EQU 2 ; Public attribute offset
PSFATT EQU 7 ; Public/system file (internal only)
WHLATT EQU 8 ; Wheel protect attribute offset
ROATT EQU 9 ; Read only attribute offset
SYSATT EQU 10 ; System attribute offset
ARCATT EQU 11 ; Archive attribute offset
; FCB positions
FCBEXT EQU 12 ; Extent number
FCBUSR EQU 13 ; User valid at offset 13 if set (internal)
FCBMOD EQU 14 ; Data module number - D7 used as unmod flag
FCBREC EQU 15 ; Record number
NXTREC EQU 32 ; Next record number
; Internal
; ZSDOS 1 = 6D / ZSDOS 2 = ED
FLGBITS EQU 11101101B ; PUBlic On, P/P Write Off, R/O On,
; Fast Relog On, Disk Chg Warning Off,
; Path On, No System Path On
; (Reserved) On
;_______________________// Non-Banked //
DEFB 'ZSDOS',VER/16+'0'
; ZSDOS entry point
JP ENTRY ; jump to start of program code
;::::: DATA / CONFIGURATION AREA
; CP/M 2.2 compatible error vector table
STBDSC: DEFW ERROR ; Bad sector message
STSEL: DEFW ERROR ; Select error
STRO: DEFW ERROR ; Drive read only
SFILRO: DEFW ERROR ; File read only
; External Path
PATH: DEFW PATHAD ; Path address for file open, 0 if no Path
; Wheel Byte pointer
WHEEL: DEFW 0 ; Address of Wheel byte, 0 if none
; User configuration byte(s)
FLAGS: DEFB FLGBITS ; Flag byte (see above)
STFLAG: DEFB 0110B ; Stamp enable/disable
; bit2= Modify, bit1= Create, bit0= Access
; Dispatch table for time/date stamp routines
GSTIME: DEFW DOTDER ; Address of get/set time/date routine
DEFW DOTDER
UNLOAD: DEFW 0 ; Pointer to remove time stamp routine
IPATH: DEFW PATHAD ; copy to restore Internal Path (see ZSCFG2)
;-----
TABCNT: DEFB 0 ; tab counter
TABCX1: DEFB 0 ; temporary tab counter (used by RDBUF)
FCONTP: DEFB 0 ; list enable flag (Control-P) - used by BGii
LASTCH: DEFB 0 ; last character - used by BGii
USER: DEFB 0 ; user number - used by BGii
DEFDRV: DEFB 0 ; default drive number - used by BGii and DS
DRIVE: DEFB 0 ; drive number
;-----
FCB0: DEFB 0 ; FCB byte 0
DMA: DEFW 0080H ; DMA address
TRANS: DEFW 0 ; translation vector
TEMP0: DEFW 0 ; number of files on drive
DIRBUF: DEFW 0 ; directory buffer pointer - used by BGii
IXP: DEFW 0 ; disk parameter block
CSV: DEFW 0 ; check sum pointer
ALV: DEFW 0 ; allocation vector pointer
;-----
MAXSEC: DEFW 0 ; number of sectors/track
NBLOCK: DEFB 0 ; block shift
NMASK: DEFB 0 ; mask number of blocks
NEXTND: DEFB 0 ; extent mask
MAXLEN: DEFW 0 ; maximum block number-1
NFILES: DEFW 0 ; maximum number of files-1
NDIR0: DEFB 0 ; first two entries ALV buffer
DEFB 0 ; ..(NDIR1)
NCHECK: DEFW 0 ; number of checksum entries
NFTRK: DEFW 0 ; first track number
;-----
FUNCT: DEFB 0 ; function code
PEXIT: DEFW 0 ; exit code
;-----
FLDRV: DEFB 0 ; drive select used flag
RDWR: DEFB 0 ; read/write flag
SEARQU: DEFB 0 ; search question mark used
SEARPU: DEFB 0 ; search public file
;-----
RECDIR: DEFW 0 ; record directory (checksum)
FILCNT: DEFW 0 ; file counter
SECPNT: DEFB 0 ; sector pointer
SUBFLG: DEFB 0 ; submit flag (reset disk command)
DCOPY: DEFW 0 ; copy address FCB
SEAREX: DEFB 0 ; exit code search
SEARNB: DEFB 0 ; search number of bytes
ERMODE: DEFB 0 ; BDOS error mode
ARWORD: DEFW 0 ; DE argument on entry - used for BGii
DEVAL: DEFW 0 ; return value for DE reg
SPSAVE: DEFW 0 ; Stack pointer location
;-----
FINITD: DEFB 0 ; INITDR flag preventing code re-execution
TPABNK: DEFB 0 ; TPA bank number
DEFB 'ZSDOS ',VER/16+'0','.',VER MOD 16 + '0'
DEFB ' Copyright (c) '
DATE
DEFB ' by C.W.Cotrill & H.F.Bow'
IXSAVE: DEFB 'er' ; User's IX register
ZSDOSS: DEFW 0 ; ZSDOS Stack
DEFB 0,0,0,0 ; ..also used as buffer to read/set clock
;-----
; Time/Date
RWCODE: DEFB 0 ; 0= read stamp, 1= write stamp
STOFF: DEFB 0 ; offset in stamp
STATUS: DEFB 0FFH ; status of first routine for exit checs
; Other
FCBADR: DEFW 0 ; address of (target) FCB
FCBBUP: DEFB 0 ; number of FCB bytes to copy/update
ZSFCB: DEFS 36 ; buffer for internal FCB (36 bytes)
ZSSTMP: DEFS 15 ; buffer for Time Stamp (15 bytes)
;::::::::::::::::::::::::::::::::::::::::
;::::: ZSDOS Entry Point
ENTRY: CALL RETMEM ; B/P Bios fn #39 (A= current memory bank)
LD (TPABNK),A ; save TPA bank number
XOR A ; clear A
LD B,A ; for later 16-bit addr's
LD L,A
LD H,A ; set HL to zero
LD (PEXIT),HL ; clear exit code
LD (FLDRV),HL ; reset drive select and R/W flags
LD (SPSAVE),SP ; save stack pointer
LD SP,ZSDOSS ; get internal stack pointer
PUSH IX ; save index register on our stack
PUSH DE ; save parameter register
POP IX ; get it back in IX
LD (ARWORD),DE ; save in memory for BGii
LD HL,DOSEXIT ; get exit address ZSDOS
PUSH HL ; save it on stack to return from ZSDOS
LD A,C ; get function code (reg B= 0)
LD (FUNCT),A ; save it for later use
CP 12 ; is it a non-disk function ?
JR C,ENTRY0 ; ..if so, jump
CP MAXCMD ; fcn < maximum command number (49) ?
JR C,ENTRY1 ; ..if so, jump
; extended function scanner for added functions
CP 98 ; is it less than cmnd 98 ?
RET C ; ..if so, return (fcn not known/valid)
CP 152 ; is it cmnd 152 ?
JP Z,CMD152 ; ..if so, execute it
CP 103+1 ; is it greater than cmnd 103 ?
RET NC ; ..if so, return (fcn not known/valid)
SUB 98-MAXCMD ; rework 98..103 --> 50..55
LD C,A ; save reworked function number
; ..and fall through to ENTRY0
; for Non-disk functions (ie. fcn # less than 12), push address of
; SAVEA routine on Stack (save A reg as return code). Saves code in
; Console Routines, as a simple RET can be used in most cases.
ENTRY0: LD HL,SAVEA
PUSH HL ; vector return through A reg save
ENTRY1: LD HL,CTABLE ; load table
ADD HL,BC ; add
ADD HL,BC ; add twice to get word value
LD A,(HL) ; get LSB
INC HL ; pointer to MSB
LD H,(HL) ; get MSB
LD L,A ; save LSB in L
; copy byte argument into A and C to simplify Function calls
; allows direct Bios jumps for several fcn's with code savings
LD C,E ; place arg in C for BIOS
LD A,E ; and in A for others
JP (HL) ; jump to routine
;::::::::::::::::::::::::::::::::::::::::
;::::: Command Table
CTABLE: DEFW ERROR5 ; Warm boot (Bios) with ERMODE clear
DEFW CMND01 ; Console input
DEFW WRCON ; Console output
DEFW READER ; Reader input (BIOS)
DEFW PUNCH ; Punch output (BIOS)
DEFW LIST ; List output (BIOS)
DEFW CMND06 ; Direct console I/O
DEFW CMND07 ; Get I/O byte
DEFW CMND08 ; Set I/O byte
DEFW CMND09 ; Print string
DEFW CMND10 ; Read console buffer
DEFW CMND11 ; Get console status
DEFW CMND12 ; Return version number
DEFW CMND13 ; Reset disk system
DEFW CMND14 ; Select disk
DEFW CMND15 ; Open file
DEFW CMND16 ; Close file
DEFW CMND17 ; Search for first
DEFW CMND18 ; Search for next
DEFW CMND19 ; Delete file
DEFW CMND20 ; Read sequential
DEFW CMND21 ; Write sequential
DEFW CMND22 ; Make file
DEFW CMND23 ; Rename file
DEFW CMND24 ; Return login vector
DEFW CMND25 ; Return current disk
DEFW CMND26 ; Set DMA address
DEFW CMND27 ; Get address allocation vector
DEFW CMND28 ; Write protect disk
DEFW CMND29 ; Get R/O vector
DEFW CMND30 ; Set file attributes
DEFW CMND31 ; Get address disk parameter header (DPH)
DEFW CMND32 ; Get/set user code
DEFW CMND33 ; Read random
DEFW CMND34 ; Write random
DEFW CMND35 ; Compute file size
DEFW CMND36 ; Set random record
DEFW CMND37 ; Reset multiple drive
DEFW DUMMY ; Function 38 (unused)
DEFW CMND39 ; Return fixed disk login vector
DEFW CMND40 ; Write random with zero fill
DEFW DUMMY ; Function 41 (unused)
DEFW DUMMY ; Function 42 (unused)
DEFW DUMMY ; Function 43 (unused)
DEFW DUMMY ; Function 44 (unused)
DEFW CMND45 ; Set Error Mode
DEFW CMND46 ; Return Disk Free Space
DEFW CMND47 ; Return DMA
DEFW CMND48 ; Return DOS version
DEFW CMND49 ; Return Environment Descriptor Address
MAXCMD EQU ($-CTABLE)/2
DEFW CMD98 ; Get Time
DEFW CMD99 ; Set Time
DEFW CMD100 ; Get Flags
DEFW CMD101 ; Set Flags
DEFW CMD102 ; Get Stamp
DEFW CMD103 ; Put Stamp
;::::::::::::::::::::::::::::::::::::::::
;::::: I/O Routines
;--------------------------------------------------
; fn #1 CONIN
; enter: none, exit: A= char
;--------------------------------------------------
; read char fron Console
; and echo if char = CR, LF, TAB, CONTH or >=Space
CMND01: CALL GETCH ; get character (and test it)
RET C ; ..if less than Space, exit
PUTCH: PUSH HL ; save regs for other calls
CALL WRCON ; echo character
POP HL
RET
;--------------------------------------------------
; fn # 6 Direct Console I/O
; enter: E= 0FFH (In), exit: A= Input Character
; 0FEH (In) Console Status
; 0FDH (In) Input Character
; 00H..0FCH (Out) 00H
;--------------------------------------------------
; enhanced to CP/M 3 spec
; checks ZSDOS typeahead for reliable Console I/O under all
; conditions as per a suggestion by Bridger Mitchell
CMND06: INC E ; test if get char is available
JR Z,DCIO1 ; ..if yes, do input
INC E ; test for 0xFE
JR Z,DCIO2 ; ..if so, get status
INC E ; test for 0xFD
JR Z,GETCH ; ..if so, wait for input char
JP CONOUT ; else, print char (Bios Console Output)
DCIO2: LD A,(LASTCH) ; check for buffered char
OR A
LD A,00000001B ; preset ready
CALL Z,CONST ; call Bios Console Status
AND A ; test it
RET ; and return to caller
DCIO1: CALL DCIO2 ; get console status
RET Z ; ..if no character present, exit
; else, fall through
; get char from Console
GETCH: LD HL,LASTCH ; check ZSDOS type ahead for char
LD A,(HL)
LD (HL),0 ; reset last character
OR A ; set flags
CALL Z,CONIN ; call Bios Console Input
; get char and fall through to test it
; test char
; out: Carry= 0 for CR, LF, TAB, CONTH, or >=Space
; Carry= 1 for all other characters
CP CR ; is it a Carriage Return ?
RET Z ; ..if so, return
CP LF ; Line Feed ?
RET Z ; ..return
CP TAB ; Tab ?
RET Z ; ..return
CP CONTH ; Backspace ?
RET Z ; ..return
CP ' ' ; test >= Space
RET ; ..and return to caller
;--------------------------------------------------
; fn # 8 Set I/O Byte
; enter: E= I/O Byte, exit: A= 00H
;--------------------------------------------------
CMND08: LD (RAMLOW+0003H),A ; save it in RAM and fall through
;--------------------------------------------------
; fn # 7 Get I/O Byte
; enter: None, exit: A= I/O Byte (0003H)
;--------------------------------------------------
CMND07: LD A,(RAMLOW+0003H) ; get I/O byte from RAM
RET
;--------------------------------------------------
; fn # 10 Read Console Buffer
; enter: DE= address Buffer, exit: A= 00H
;--------------------------------------------------
CMND10: LD A,(TABCNT)
LD (TABCX1),A ; save start Tab position
INC DE
XOR A
LD (DE),A ; set char count to zero
INC DE ; point to actual buffer start
RDBUF1: PUSH DE ; save buffer pointer
CALL GETCH ; get next byte from user
POP DE
LD HL,RDBUF1
PUSH HL ; return address to stack
LD HL,(ARWORD)
LD C,(HL) ; put buffer length in C
INC HL ; and point to current length
CP CR
JR Z,JZRBX ; ..if CR, then exit
CP LF
JZRBX: JP Z,RDBUFX ; ..or if LF, then exit
; delete char from buffer
; (Rubout, Backspace, CR, LF are _never_ in the buffer)
RDBUF2: CP RUBOUT ; delete char ?
JR Z,DOBACK ; ..if so, jump
CP CONTH ; Control-H also deletes
JR NZ,RDBUF3 ; ..if no delete, skip to next test
DOBACK: LD A,(HL)
AND A ; test if attempting to del from empty line
RET Z ; ..if so, then exit
DOBAK0: DEC DE ; back up to last character
DEC (HL) ; erase from buffer
PUSH DE ; save buffer pointer
LD B,(HL) ; get new char count
INC HL ; point to first char
EX DE,HL
LD HL,TABCNT
LD C,(HL) ; save current Tab count
INC HL
LD A,(HL) ; get starting Tab position
DEC HL
LD (HL),A ; init the counter
INC B ; insure non-zero
JR DOBAK2 ; jump to done test
DOBAK1: LD A,(DE) ; get char from buffer
CALL WRCON2 ; counts chars
INC DE
DOBAK2: DJNZ DOBAK1 ; continue count until done
LD A,C ; get prior Tab count
SUB (HL) ; get diff between new and old
LD B,A ; set up as count
LD (HL),C ; restore prior count
POP DE ; restore pointer
; delete B chars from Console
PUSH DE ; save pointer
DOBAK5: LD C,CONTH
PUSH BC ; save counter from destruction
CALL CONOUT ; call Bios Console Output
LD C,' '
CALL CONOUT ; output Backspace, Space to CON: only
LD A,CONTH
CALL WRCON ; now Backspace CON:, counter, and printer
POP BC ; restore counter
DJNZ DOBAK5 ; loop until all done
POP DE ; restore pointer
RET
; erase buffer
RDBUF3: CP CONTU ; test erase line
JR Z,ERALIN ; ..if so, do it
CP CONTX
JR NZ,RDBUF4 ; skip to next test if no erase line
ERALIN: XOR A
OR (HL) ; line empty ?
RET Z ; ..if so, exit
PUSH HL
CALL DOBAK0 ; else, delete another (skip empty check)
POP HL
JR ERALIN ; if Ctrl-R=True, do following code
; else bypass
RDBUF4: CP CONTR ; if Ctrl-R type clean buffer version on CON:
JR NZ,RDBUF5
PUSH HL ; save pointer to buffer length
CALL CROUT ; do CR/LF
LD HL,TABCNT
LD (HL),0 ; init Tab count
INC HL
LD B,(HL) ; and get Tab offset count
LD A,' '
INC B ; insure nz value
JR RETY1A ; so case of lh side of screen ok
RETYP1: CALL WRCON ; space off start of line
RETY1A: DJNZ RETYP1
POP HL ; point to buffer length
LD B,(HL) ; get how many chars to print
INC HL ; restore buffer pointer
EX DE,HL ; put buffer pointer in DE
INC B ; comp for first djnz
JR RETYP3 ; skip to done test
RETYP2: LD A,(DE) ; get char from buffer
CALL WRCTL ; output it
INC DE ; bump pointer
RETYP3: DJNZ RETYP2 ; loop until done
RET
; toggle line printer echo
RDBUF5: CP CONTP ; toggle printer ?
JR NZ,RDBUF6 ; ..if not, next test
LD HL,FCONTP
LD A,(HL) ; get printer echo flag
CPL ; toggle it
LD (HL),A ; put back
RET
; check if Ctrl-C is first char in BUFF, exit if so
RDBUF6: LD (DE),A ; put character in buffer
PUSH HL
CALL WRCTL ; echo the character
POP HL
INC (HL) ; increment the character count
LD A,(HL) ; get current length
CP C ; test against buffer size
JR Z,RDBUFX
DEC A ; set Z flag for first character
LD A,(DE) ; get the character back
INC DE ; and bump the pointer
RET NZ ; ..if not the first character, return
CP CONTC ; possible user abort ?
RET NZ ; ..if not, return
JP ERROR5 ; else, jump to error reset exit
; done with Read Console Buffer function
RDBUFX: POP HL ; clear RDBUF1 return address
LD A,CR
JR WRCON ; ..and echo a CR
; print Control char as '^X'
WRCTL: CP ' ' ; test if Control char
JR NC,WRCON ; ..if not, send it out
CP TAB ; test if Tab
JR Z,WRCON0 ; ..if it is, then expand with spaces
PUSH AF ; save char
LD A,'^' ; output a caret
CALL WRCON1 ; no need for Tab test here
POP AF
ADD A,40H ; convert to printable
; ..and fall through to WRCON
;--------------------------------------------------
; fn #2 CONOUT
; enter: E= char, exit: none (A= Bios reg A)
;--------------------------------------------------
; output char with list echo, Tab expansion
WRCON: CP TAB ; is it a Tab ?
JR NZ,WRCON1 ; ..if not, jump
WRCON0: LD A,' ' ; expand Tab with spaces
CALL WRCON1 ; write space
LD A,(TABCNT) ; get Tab count
AND 7 ; test if done
JR NZ,WRCON0 ; ..if not, then repeat
LD A,TAB ; return Tab
RET ; return to caller
WRCON1: PUSH BC ; save pointers
PUSH DE
LD C,A ; save character
PUSH BC
BGPTCH0 EQU $+1 ; <--- BGii patches this addr
CALL CMND11 ; test status and CONTS/CONTC
POP BC ; get character back
PUSH BC ; save it again
CALL CONOUT ; call Bios Console Output
POP BC ; get character back
PUSH BC ; save it again
LD A,(FCONTP) ; get printer echo flag
OR A ; test it
CALL NZ,LIST ; ..if non-zero, output char to printer
POP BC ; restore character
LD A,C ; fall through to count routine
POP DE ; restore pointers
POP BC
; count characters in line as shown by f10
LD HL,TABCNT ; get pointer to Tab counter
WRCON2: INC (HL) ; increment Tab counter
CP RUBOUT ; test if character = Rubout
JR Z,WRCON3 ; ..if so, treat like Backspace
CP ' '
RET NC ; ok if not Control char
CP TAB ; only DOBACK ever gets Tabs through here
JR Z,WRCON4 ; ..if Tab, handle differently
CP CONTH
JR Z,WRCON3 ; ..or Backspace
INC (HL) ; must have been echoed as two chars
CP LF
JR Z,WRCON3 ; unless it's LF
CP CR ; ..or CR
RET NZ
LD (HL),2 ; reset Tab counter
WRCON3: DEC (HL) ; decrement Tab counter
DEC (HL)
RET ; and exit
WRCON4: LD A,7 ; bumped by one already
ADD A,(HL) ; Tabs are every 8 spaces
AND 0F8H ; ..MOD 8
LD (HL),A ; save updated Tab count
RET ; ..and continue
;--------------------------------------------------
; fn # 11 Get Console Status
; enter: None, exit: A= 00H No character
; 01H Char. present
;--------------------------------------------------
; BGii uses this routine
BGCONST:
CMND11: CALL DCIO2 ; get character present status
RET Z ; ..if none, then exit
CALL GETCH ; get next console char
CP CONTS ; is it stop char ?
JR NZ,GCONS2 ; ..if not, jump
CALL CONIN ; call Bios Console Input to get next char
CP CONTC ; does the user want to exit (Ctrl-C) ?
JR NZ,CMND11 ; ..if not, check for another character
JP ERROR5 ; else, jump to warm boot and clear ERMODE
GCONS2: LD (LASTCH),A ; save character
LD A,1 ; character present code
RET ; return to caller
; echo CR,LF
CROUT: LD DE,MCRLF ; fall through to output routine
;--------------------------------------------------
; fn # 9 Print String
; enter: DE= address String, exit: None (A= '$')
;--------------------------------------------------
CMND09: LD A,(DE) ; get byte from buffer
CP '$' ; test, last byte ?
RET Z ; ..if yes, then return to caller
INC DE ; else, point to next byte
CALL WRCON ; ..and output character
JR CMND09 ; loop, test again
;::::::::::::::::::::::::::::::::::::::::
;::::: Error Routines
PRDEC: LD BC,100
CALL NUM
LD C,10
CALL NUM
LD BC,0101H
; display number
NUM: LD D,-1 ; load number -1
NUM1: INC D ; increment number
SUB C ; divide by C
JR NC,NUM1 ; ..if not finished, then loop
ADD A,C ; restore last value
PUSH AF ; save it
LD A,D ; test if '0'
OR B ; and if leading zero
JR Z,NUM2 ; ..if yes, then exit
LD B,A ; set no leading zero
LD A,D ; get number
ADD A,'0' ; make ascii
CALL PUTCH ; echo number preserving BC
NUM2: POP AF ; restore number
RET ; and exit
;::::: ERROR MESSAGES
MDSKCH: DEFB 'Changed$'
MBADSC: DEFB 'Bad Sector$'
MSEL: DEFB 'No Drive$'
MFILRO: DEFB 'File '
MRO: DEFB 'W/P$'
MBERR: DEFB 'ZSDOS Error on $'
MBFUNC: DEFB CR,LF,'Call'
MDRIVE: DEFB ': $'
MFILE: DEFB ' File: $'
MCRLF: DEFB CR,LF,'$'
; new ZDSOS error handler
; in: B= error code
; DE= message pointer
ERROR: LD A,(ERMODE)
LD C,A ; save error mode
RRCA ; test suppress print
JR C,ERROR3 ; ..if suppressed, then skip to display
; print ZSDOS Error on X: explanation
PUSH BC
PUSH DE ; save params
CALL CROUT ; output CR,LF
LD DE,MBERR
CALL CMND09 ; output 'ZSDOS Error on'
LD A,(DEFDRV) ; get current default drive
ADD A,'A' ; convert to ascii
CALL WRCON ; output to Console
LD DE,MDRIVE ; point to Drive tag
CALL CMND09 ; ouput also
POP DE ; restore error message pointer
CALL CMND09 ; send message
; now print CALL: XXX [FILE: XXXXXXXX.XXX]
LD DE,MBFUNC
CALL CMND09 ; display 'Call: '
LD A,(FUNCT) ; get function number
CALL PRDEC ; output it
LD A,(FLDRV)
AND A ; was FCB used ?
JR Z,ERROR2 ; ..if not, skip file name display
POP BC ; get error type
PUSH BC
PUSH IX ; save FCB pointer
LD A,(FUNCT) ; are we erasing a file ?
CP 19 ; ..if so, get name from DIRBUF
JR NZ,ERROR0 ; ..ambiguous name may have been used
CALL CALDIR ; get Dir buffer pointer
EX (SP),HL ; to show that we are really gagged on
ERROR0: LD DE,MFILE
CALL CMND09 ; output 'File: '
POP HL ; point to FCB
LD B,11 ; output this many chars
ERROR1: INC HL
LD A,3
CP B ; time to send '.' ?
LD A,46 ; get ready for it
CALL Z,PUTCH ; ..and send it if time
LD A,(HL) ; get char
AND 7FH ; mask attributes
CALL PUTCH ; output it
DJNZ ERROR1
ERROR2: CALL CROUT ; send CR,LF
POP BC ; get error mode back
ERROR3: LD A,4
SUB B ; test if select error
JR NZ,ERROR4 ; ..if not, skip
LD HL,DRIVE ; point to old default
LD A,(HL) ; get it
DEC HL ; point to bad drive
CP (HL) ; same ?
JR Z,ERROR4 ; ..if so, skip relog
PUSH BC
CALL SELSYB ; switch System bank
CALL SELDK ; get Bios back in step
POP BC
ERROR4: BIT 1,C ; test if return error mode
JR NZ,ERROR7 ; ..if return error, go
LD A,1
SUB B ; test if fatal error
JR NC,ERROR6 ; ..if not a fatal error, jump
; else, fall through
;--------------------------------------------------
; fn # 0 Boot
; enter: None, exit: None
;--------------------------------------------------
ERROR5: XOR A ; clear A
LD (ERMODE),A ; set DOS error mode to default CP/M
LD A,(OTPABK) ; store TPABNK = 0 (at B/P Bios base +0x82)
CALL SELMEM ; ..and also set it, BIOS fn #27 SELMEM (A= bank #)
RST 0 ; ..and leave
ERROR6: CALL DCIO1 ; get console char if present
AND A ; test if any
JR NZ,ERROR6 ; keep getting them until typeahead eaten
CALL GETCH ; now get operator's response
CP CONTC ; test if abort
RET NZ ; ..if operator said ignore error
JR ERROR5 ; else, boot
ERROR7: LD A,B ; get error
LD H,A ; save code in H reg for return
AND A ; test if disk changed warning
RET Z ; ..if so, continue relog
LD L,0FFH ; set extended error code
LD (PEXIT),HL ; save as return code
; ..and fall through to DOS exit
;::::::::::::::::::::::::::::::::::::::::
;::::: DOS Exit Routine
DOSEXIT: LD A,(FLDRV) ; test Drive select used flag
OR A
JR Z,DOSEXT0 ; ..if no, then exit
LD A,(FCB0) ; get FCB byte 0
LD (IX+0),A ; save it
CALL SELTPB ; select TPA bank
LD HL,ZSFCB ; buffer internal FCB
LD DE,(FCBADR)
LD A,(FCBBUP) ; get number of bytes to copy
OR A ; test if empty
JR Z,DOSEXT1 ; ..if zero, skip
LD C,A ; else, set byte counter (16-bit)
LD B,0
LDIR ; and copy
DOSEXT1: LD A,(DRIVE) ; get old Drive number
CALL SELSYB ; switch System bank
CALL SELDK ; select disk
; Stack is in an undefined condition at this point, if error handler
; was invoked. Independent of Stack position, the user's IX register
; has to be restored. (Thanks to Joe Wright for catching this one!)
DOSEXT0: CALL SELTPB ; select TPA bank
LD SP,(SPSAVE) ; restore user stack
LD IX,(IXSAVE) ; restore IX
LD HL,(PEXIT) ; get exit code
LD DE,(DEVAL) ; and DE reg for DateStamper
LD A,L ; copy function code
LD B,H
RET ; and return to caller
;::::::::::::::::::::::::::::::::::::::::
;::::: Disk Routines
;--------------------------------------------------
; fn # 13 Reset Disk System
; enter: None, exit: A= 00H No $*.* on A
; FFH $*.* on A
;--------------------------------------------------
CMND13: CALL SELSYB ; switch System bank
JP CMD13B ; and continue in Banked code
;_______________________// Banked //
COMMON /BANK2/
;--------------------------------------------------
; Reset Disk System
; (continue CMND13)
;--------------------------------------------------
CMD13B: LD HL,RAMLOW+80H ; set up default DMA address
LD (DMA),HL ; and save it
CALL STDMA ; do Bios call
XOR A ; set default drive = 'A'
LD (DEFDRV),A ; save it
LD DE,0FFFFH ; reset all drives
; ..and fall through to CMD37B
;_______________________// Non-Banked //
CSEG
;--------------------------------------------------
; fn # 37 Reset Mult Drive
; enter: DE= Bit Mask, exit: A= 00H
;--------------------------------------------------
; fixed disk login vector is also altered by this call
CMND37: CALL SELSYB ; switch System bank
JP CMD37B ; and continue in Banked code
;_______________________// Banked //
COMMON /BANK2/
;--------------------------------------------------
; Reset Multiple Login Drive
; (continue CMND37)
;--------------------------------------------------
; fixed disk login vector is also altered by this call
CMD37B: CALL UNLOG ; clear selected Drives in DE from login
LD A,(FLAGS)
BIT 2,A ; test hard R/O enabled
JR NZ,UNWPT1 ; ..if enabled, jump
LD HL,DSKWP ; get Drive W/P vector
CALL ANDDEM ; reset W/P stat only of requested Drives
UNWPT1: LD A,(FUNCT)
CP 13 ; skip hard disk login change ?
LD HL,HDLOG
CALL NZ,ANDDEM ; clear HD login vector if fcn 37
RELOG1: LD HL,(HDLOG)
CALL HLORDE ; don't clear fixed disks from T/D
EX DE,HL ; place modified logout in DE
LD HL,TDFVCT
CALL ANDDEM ; clear T/D vector as needed
LD A,(DEFDRV) ; get default drive
CALL SELDK
; check for possible existance of submit file
; ZSDOS watches for any $*.* file in any User on any Drive during
; re-log, make, and delete. In this manner, SUBFLG will always be
; valid - even under Fast Relog and NZCOM. (Thanks to Joe Wright
; for suggesting the need for this, and suggesting ways to do it.)
SUBEXT: LD A,(SUBFLG) ; get submit flag
JP SAVEA ; exit
; check first byte of Dir entry or FCB for '$'
; in: HL= pointer to Dir or FCB
CKSUB: INC HL ; point to file name
LD A,(HL) ; get first char file name
DEC HL
SUB '$' ; test if '$'
RET NZ ; ..if not, then exit
DEC A ; load with 0xFF
LD (SUBFLG),A ; save it in subflg
RET
; unlog Drive mask in DE
UNLOG: LD A,E ; get LSB
CPL ; complement it
LD E,A
LD A,D ; get MSB
CPL ; complement it
LD D,A ; DE = not reset
LD HL,LOGIN ; get address of login vector
ANDDEM: LD A,E ; clear login bits of reset drives
AND (HL) ; ..a byte at a time
LD (HL),A ; put to memory
INC HL
LD A,D
AND (HL)
LD (HL),A
RET
;_______________________// Non-Banked //
CSEG
;--------------------------------------------------
; fn # 17 Search for First Entry of File
; enter: DE= Address of FCB, exit: A= Directory Code
;--------------------------------------------------
CMND17: CALL SELDRV ; select drive from FCB
LD A,(IX+0)
SUB '?' ; test if '?'
JR Z,CMD17B ; ..if so, all entries match
LD A,(IX+FCBMOD) ; get system byte
CP '?' ; test if '?'
JR Z,CMD17A ; ..if yes, jump
LD (IX+FCBMOD),0 ; load system byte with zero
CMD17A: LD A,15 ; test first 15 items in FCB
CMD17B: CALL SEARCH ; do search
CMD17C: CALL SELTPB ; select TPA bank
LD HL,(DIRBUF) ; copy Directory buffer
LD BC,128 ; Directory = 128 bytes
LD DE,(DMA) ; to DMA address
LDIR
RET ; exit
;--------------------------------------------------
; fn # 18 Search for Next Occurrence of File
; enter: DE= Address of FCB, exit: A= Directory Code
;--------------------------------------------------
CMND18: LD IX,(DCOPY) ; get last FCB used by search
LD (ARWORD),IX ; save FCB pointer for BGii
CALL SELDRV ; select drive from FCB
CALL SEARCN ; search next file match
XOR A ; clear A (zero)
LD (FCBBUP),A ; ..save as num of bytes to update in FCB
JR CMD17C ; and copy Directory to DMA address
;--------------------------------------------------
; fn # 19 Delete File
; enter: DE= Address of FCB, exit: A= Error Code
;--------------------------------------------------
CMND19: CALL SELDRV ; select drive from FCB
; (also switches System bank)
CALL DELETE ; ..and continue in Banked code
CMND19A: LD A,(SEAREX) ; get exit byte 00= file found, 0FFH= not
JR SAVEA ; and exit
;--------------------------------------------------
; fn # 23 Rename File
; enter: DE= Address of FCB, exit: A= Error Code
;--------------------------------------------------
CMND23: CALL SELDRV ; select drive from FCB
CALL RENAM ; rename file
JR CMND19A ; and exit
;--------------------------------------------------
; fn # 25 Get Current Disk
; enter: None, exit: A= Current Disk
;--------------------------------------------------
CMND25: LD A,(DEFDRV) ; get current drive
SAVEA: LD (PEXIT),A ; return character
DUMMY: RET ; ..and exit ZSDOS
;--------------------------------------------------
; fn # 101 Set Flags
; enter: DE=Flags, exit: None
;--------------------------------------------------
CMD101: LD (FLAGS),A ; set ZSDOS flags
; ..and fall through
;--------------------------------------------------
; fn # 100 Get flags
; enter: None, exit: HL= Flags
;--------------------------------------------------
CMD100: LD A,(FLAGS) ; get ZSDOS flags
JR SAVEA ; ..and exit
;--------------------------------------------------
; fn # 30 Set File Attributes
; enter: DE= Address of FCB, exit: A= Error Code
;--------------------------------------------------
CMND30: CALL SELDRV ; select drive from FCB
CALL CSTAT ; change status bits of file
JR CMND19A ; and exit
;--------------------------------------------------
; fn # 12 Get Version Number
; enter: None, exit: A= Version Number (22H, CP/M compatible)
;--------------------------------------------------
ZDPCH1:
CMND12: LD HL,22H ; set CP/M compatible version number
LD A,E
CP 'D' ; is caller testing for DateStamper ?
JR NZ,CMD12A ; ..if not, jump exit
LD H,E ; otherwise return DS Active flag
LD DE,CMD98A ; have a clock, so get clock address
CMD12A: LD (DEVAL),DE ; in case DS gave us a clock address
JR SAVHL ; for speed
; The following code section may seem a bit obscure. When the Z80 jumps
; in at a label, it executes the LD HL instruction. The DEFB 0DDH turns
; the LD HL instructions that follow into LD IX. In effect, this turns
; the DEFB 0DDH into a one byte relative jump to SAVHL. As IX is never
; used by these calls, its loss is of no consequence.
; A similar trick is used in SEAR15.
;--------------------------------------------------
; fn # 48 Get DOS and Version
; enter: None, exit: H= DOS type: 'S'=ZSDOS, 'D'=ZDDOS
; L= BCD Version Number
;--------------------------------------------------
CMND48: LD HL,'S' SHL 8 + VER ; 'S' indicates ZSDOS, ZRDOS returns 0
DEFB 0DDH ; trash IX and fall through
;--------------------------------------------------
; fn # 29 Get R/O Vector
; enter: None, exit: HL= R/O Vector
;--------------------------------------------------
CMND29: LD HL,(DSKWP) ; get disk W/P vector
DEFB 0DDH ; trash IX and fall through
;--------------------------------------------------
; fn # 39 Get Fixed Disk Vector
; enter: None, exit: HL= Fixed Disk Vector
;--------------------------------------------------
CMND39: LD HL,(HDLOG) ; return fixed disk login vector
DEFB 0DDH ; trash IX and fall through
;--------------------------------------------------
; fn # 27 Get Alloc. Address (ALV)
; enter: None, exit: HL= Address of Allocation Vector
;--------------------------------------------------
CMND27: LD HL,(ALV) ; get allocation vector
DEFB 0DDH ; trash IX and fall through
;--------------------------------------------------
; fn # 49 Return ENV Address
; enter: None, exit: HL= Address Env. Descriptor
;--------------------------------------------------
CMND49: LD HL,(OZ3ENV) ; get Z3ENV address (Bios base +0x98)
DEFB 0DDH ; trash IX and fall through
;--------------------------------------------------
; fn # 24 Get Login Vector
; enter: None, exit: HL= Login Vector
;--------------------------------------------------
CMND24: LD HL,(LOGIN) ; get login vector
DEFB 0DDH ; trash IX and fall through
;--------------------------------------------------
; fn # 31 Get DPB Address
; enter: None, exit: HL= Address of DPB
;--------------------------------------------------
CMND31: LD HL,(IXP) ; get drive table
DEFB 0DDH ; trash IX and fall through
;--------------------------------------------------
; fn # 47 Get DMA address
; enter: None, exit : HL= Current DMA Address
;--------------------------------------------------
CMND47: LD HL,(DMA) ; get current DMA address
LD A,H ; test if valid (<> zero)
OR L
SAVHL: LD (PEXIT),HL ; save it
RET ; and exit
;--------------------------------------------------
; fn # 45 Set error mode
; enter: E= FFH (Get), exit: A=0 0H
; FEH (Get Err/Disp)
; 01H (Set ZSDOS)
; 00H (Set CP/M)
;--------------------------------------------------
; ##### implementation does not comply with documentation
CMND45: LD (ERMODE),A
RET
;--------------------------------------------------
; fn # 32 Set/Get User Code
; enter: E= FFH (Get), exit: A= User Number
; User Number (Set) 00H
;--------------------------------------------------
CMND32: LD HL,USER ; point to User byte location
INC A ; test if 0xFF
LD A,(HL) ; get old user code
JR Z,SAVEA ; ..if 0xFF, then exit
LD A,E ; get new User code
AND 1FH ; mask it
LD (HL),A ; save it
RET ; and exit
;--------------------------------------------------
; fn # 35 Compute File Size
; enter: DE= Address of FCB, exit: A= Error Code
;--------------------------------------------------
CMND35: CALL SELDR1 ; select drive from FCB
LD A,36 ; 36 bytes
LD (FCBBUP),A ; ..to update in FCB
CALL FILSZ ; compute file size
JR CMND19A ; and exit
;--------------------------------------------------
; fn # 36 Set Random Record
; enter: DE= Address of FCB, exit: A= 00H
;--------------------------------------------------
CMND36: LD HL,32 ; set pointer to next record
CALL CALRRC ; calculate random record count
LDRRC: LD (IX+33),D ; and save it
LD (IX+34),C
LD (IX+35),B
RET ; ..exit
; ##### CHECK: unreferenced code
PUSH IX
CALL SELDRV
JP NOTUSE2
; #####
;-----
; Select Disk From FCB
BGSELDRV:
SELDRV: LD A,(ERMODE) ; are we in modified User mode ?
AND A
JR NZ,SELDR1 ; ..if so, jump
LD HL,(ARWORD)
LD BC,FCBUSR ; else, point to User number
ADD HL,BC
LD (HL),A ; clear User flag
SELDR1: LD A,0FFH ; set disk select done flag
LD (FLDRV),A
LD HL,(ARWORD) ; get argument of fcn call (DE= FCB address)
LD (FCBADR),HL ; ..and save it
LD DE,ZSFCB ; set to internal FCB
PUSH DE
POP IX ; ..copy to IX
LD (ARWORD),DE ; ..and save
LD BC,36 ; byte count
LDIR ; ..and copy
LD A,16 ; 16 bytes
LD (FCBBUP),A ; save # bytes to update in FCB
LD A,(DEFDRV) ; get current drive
LD E,A ; save it in register E
LD HL,(ARWORD)
LD A,(HL) ; get drive from FCB
LD (FCB0),A ; save it
CP '?' ; test if '?'
JR Z,CMND14 ; ..if yes, then select drive from reg E
PUSH IX ; save BGii's IX register
; IX won't be altered on cmnd14
LD IX,(ARWORD) ; get FCB pointer
AND 1FH ; mask drive
PUSH HL
JR Z,SELDR0 ; select drive from register E
LD E,(HL) ; get drive from FCB
DEC E ; decrement drive number, so A= 0
SELDR0: CALL CMND14 ; do select of drive
POP HL ; restore FCB pointer
; resolve User for FCB
; in: IX= FCB ptr
; out: A= User
LD A,(IX+FCBUSR) ; ..get potential User in case
BIT 7,A ; is this a valid User ?
JR NZ,RESUS1 ; ..if there is, then skip
LD A,(USER) ; get User number
JR RESUS1 ; ..and bypass push IX
; set User in FCB to value passed in A
RESUSR: PUSH IX
RESUS1: LD IX,(ARWORD)
AND 1FH ; user number in A
LD (IX+0),A ; save in FCB 0 byte
OR 80H ; set valid DOS user flag
LD (IX+FCBUSR),A ; ..and in FCB 13 byte
POP IX ; restore caller's IX
RET
;_______________________// Banked //
COMMON /BANK2/
; select disk error exit
; Stack is off by one level here, but there is a one way trip around
SELDK3: LD HL,(STSEL) ; load error message address
LD B,4 ; select error
LD DE,MSEL ; load select error message
JP JUMPHL ; Select Disk from E register
;_______________________// Non-Banked //
CSEG
;--------------------------------------------------
; fn # 14 Select Disk
; enter: E= Disk Number, exit: A= 00H No $*.* File
; FFH $*.* File
;--------------------------------------------------
CMND14: LD A,(DEFDRV) ; get current drive
LD (DRIVE),A ; save it in memory
CALL SELSYB ; switch System bank
JP CMD14B ; ..and continue in Banked code
;_______________________// Banked //
COMMON /BANK2/
;--------------------------------------------------
; Select Disk
; (continue CMND14)
;--------------------------------------------------
CMD14B: LD A,E ; copy Drive number
; call w/ A= drive no. (0..15 = A..P)
SELDK: LD HL,(LOGIN) ; get login vector
AND 0FH ; mask Drive number
LD B,A ; save counter
CALL NZ,SHRHLB
SELDK0: EX DE,HL ; put Drive bit mask in DE
LD HL,DEFDRV ; get pointer last drive
BIT 0,E ; test if Drive logged in
JR Z,SELDK2 ; ..if not, log it in
CP (HL) ; test same Drive ?
RET Z ; ..if yes, then exit
; NOTE:
; A long standing DOS bug concerns the SELECT function. If fcn 14 is
; called and the drive doesn't exist, the default will still point
; to the bad drive unless we fix it in the error routine.
; It is for this reason that drive is saved above. We must allow
; default to assume the illegal drive value long enough for the
; error handler to print ot, then re-select the old default.
SELDK2: LD (HL),A ; save new current Drive
PUSH DE ; save Drive logged in flag
LD C,A ; copy Drive number
LD B,A
CALL DRVOK ; test if Drive is valid (Z3ENV Drive vector)
JR NC,SELDK3 ; ..if not, exit
CALL SELDSK ; do Bios call Select Disk
LD A,H ; test if error
OR L
JR Z,SELDK3 ; ..if yes, illegal Drive number
LD DE,TRANS ; point to local translation storage
LD BC,2 ; ..and move 2-byte ptr in
LDIR
LD (TEMP0),HL ; save address in temp0
LD C,6 ; advance to Dirbuf part of DPH
ADD HL,BC ; as TEMP1 and TEMP2 unused in P?DOS
LD DE,DIRBUF ; load Dirbuf pointer
LD C,8 ; copy 8 bytes
LDIR
LD HL,(IXP) ; get Drive parameter address
LD C,15 ; copy 15 bytes
LDIR
POP DE ; get Drive logged in flag
BIT 0,E ; test it
RET NZ ; Drive logged in, so return
CALL GETCDM
EX DE,HL ; Drive mask in DE
LD HL,(LOGIN) ; get login vector
CALL HLORDE ; set Drive bit in login vector
LD (LOGIN),HL ; save login vector
LD A,(FLAGS) ; get flags
BIT 3,A ; fast relog enabled ?
JR Z,INITDR ; if disabled, skip
; The following code checks the WACD size to determine if the drive
; being selected is a fixed disk. If WACD is 0, the disk is non-
; removable. However, BIOS may support remapping of logical drives.
; Therefore BDOS must catch the swap and clear the Hard Disk ALV and
; allow the allocation bitmaps to be rebuilt. Hence, every disk
; that is being selected for the first time traverses this code.
; If a disk was logged as a fixed disk and all of the sudden has a
; WACD buffer, the Fixed Disk Login Vector is cleared.
; For bug-free operation of Fast Fixed Disk Logging, if drives are
; swapped, NEVER SWAP TWO FIXED DRIVES!
LD HL,(NCHECK) ; is this a fixed Drive ?
LD A,H
OR L
LD C,A ; save fixed disk flag (Z= true)
LD HL,(HDLOG)
LD A,E ; see if logged as fixed disk
AND L
LD L,A
LD A,D
AND H ; MSB
OR L ; Z flag set if HL and DE = 0
LD A,0FFH ; don't alter flags
JR Z,SELDK4 ; ..if not logged as fixed disk, skip over
INC A ; else, flag as logged
SELDK4: LD B,A ; save logged as fixed disk flag (Z= true)
OR C ; test if still fixed disk
RET Z ; skip re-map if logged and not swapped
XOR A
LD H,A
LD L,A ; null vector
OR B ; was it logged as fixed disk ?
JR Z,SELDK5 ; invalidate HDLOG vector
; (drive no longer considered fixed disk)
LD A,C
OR A ; wasn't fixed disk before - is it now ?
JR NZ,INITDR ; ..if it isn't, skip vector update
LD HL,(HDLOG)
CALL HLORDE ; else, add this drive to fixed disk vector
SELDK5: LD (HDLOG),HL ; update fixed disk vector
; ..and fall through to INITDR
; init drive
; clear ALV bit buffer after Drive reset
INITDR: LD HL,(MAXLEN) ; get length ALV buffer-1 (bits)
CALL SHRHL3 ; divide by 8 to get bytes
LD B,H
LD C,L ; counter to BC (will be count+1 cleared)
LD HL,(ALV) ; get pointer ALV buffer
PUSH HL
LD D,H
LD E,L
INC DE ; ALV buffer +1 in DE
XOR A
LD (HL),A ; clear first 8 bits
LDIR ; and remainder of buffer
POP HL ; get ALV pointer
LD DE,(NDIR0) ; get first two bytes ALV buffer
LD (HL),E ; save LSB
INC HL ; increment pointer
LD (HL),D ; save MSB
LD HL,(TEMP0) ; clear number of files on this Drive
LD (HL),A ; clear LSB (A still has 0)
INC HL ; increment pointer
LD (HL),A ; clear MSB
LD A,0FFH ; set initial value for flag
LD (FINITD),A
ZDPCH2 EQU $ ; <--- Intercept first scan (ZDS Patch)
CALL SETFCT ; set file count
INITD2: LD A,0FFH ; update Directory checksum
CALL RDDIR ; read FCB's from Directory
CALL TSTFCT ; test last FCB
JP Z,SUBEXT ; return subflg for strict CP/M compat
CALL CALDIR ; calculate entry point FCB
LD A,(HL) ; get first byte FCB
CP 0E5H ; test empty Directory entry
JR Z,INITD2 ; ..if yes, then get next FCB
AND 7FH ; mask
CP 21H ; test time stamp
JR Z,INITD2 ; ..if yes, then get next FCB
; check if FCB belongs to !!!TIME&.DAT file
LD A,(FINITD) ; get first loop flag
OR A ; test if zero
JR Z,INITD5 ; ..if yes, then jump
; to prevent re-execution of following code
INC A ; ..else, increment
LD (FINITD),A ; and save flag again
LD BC,00C00H ; set counter B=12, and clear C
PUSH HL
INITD3: LD A,(HL) ; get byte from FCB
AND 7FH ; mask high bit
ADD A,C ; add in previous result
LD C,A ; ..and save it in C
INC HL ; move ptr forward
DJNZ INITD3 ; loop till done
CP TDCKSM ; test if matches checksum of !!!TIME&.DAT
JR NZ,INITD4 ; ..if not, skip over
LD HL,(TDFVCT) ; else, get T/D vector
CALL SDRVB ; set drive bit in HL
LD (TDFVCT),HL ; and save T/D vector again
INITD4: POP HL
ZDPCH3 EQU $ ; <--- Test for T&D if first time (ZDS Patch)
INITD5: CALL CKSUB ; test for submit file
LD C,1 ; set bit in ALV buffer
CALL FILLBB ; set bits from FCB in ALV buffer
CALL TSTLF ; test for last file
CALL NC,SETLF0 ; ..and update the last file count if so
JR INITD2 ; get next FCB
;_______________________// Non-Banked //
CSEG
;-----
; return mask for current Drive in HL
GETCDM: LD HL,0 ; no drives to Or
; set Drive bit in HL
SDRVB: EX DE,HL ; copy HL=>DE
LD HL,1 ; get mask drive 'A'
LD A,(DEFDRV) ; get current drive
OR A ; test if drive 'A'
JR Z,HLORDE ; ..if yes, then done
SDRVB0: ADD HL,HL ; get next mask
DEC A ; decrement drive number
JR NZ,SDRVB0 ; ..and test if done
HLORDE: LD A,D ; HL= HL or DE
OR H
LD H,A
LD A,E
OR L
LD L,A
RET ; exit
SHRHL3: LD B,3 ; ZSDOS v1 comment: "used in a few places"
; ##### used exactly once - make INITDR inline
;-----
; shift HL right logical B bits
SHRHLB: SRL H
RR L ; Shift HL right one bit (divide by 2)
DJNZ SHRHLB
RET
;_______________________// Banked //
COMMON /BANK2/
;-----
; calculate Sector/Track directory
STDIR: LD HL,(FILCNT) ; get FCB counter Directory
SRL H ; divide by 4
RR L
SRL H
RR L
LD (RECDIR),HL ; save value (used by checksum)
STDIR2: EX DE,HL ; move to DE
STDIR1: LD HL,0 ; clear HL
; calculate Sector/Track
; Track= HL,DE / MAXSEC, Sector= HL,DE MOD MAXSEC
; in: HL, DE= sector number (128 byte sector)
CALST: LD BC,(MAXSEC) ; get sectors/track
LD A,17 ; set up loop counter
CALST0: OR A
SBC HL,BC ; HL > BC ?
CCF
JR C,CALST1 ; ..if yes, then jump
ADD HL,BC ; else, restore HL
OR A ; clear Carry
CALST1: RL E ; shift result in DE
RL D
DEC A ; test last bit done
JR Z,CALST2 ; ..if yes, then exit
ADC HL,HL ; else, shift next bit in HL
JR CALST0 ; continue
CALST2: PUSH HL ; save sector number
LD HL,(NFTRK) ; get first track
ADD HL,DE ; add track number
LD B,H ; copy it to BC
LD C,L
CALL SETTRK ; Bios call Set Track
POP BC ; restore sector number
LD DE,(TRANS) ; get translation table address
CALL SECTRN ; Bios call Sector Translation
LD B,H ; copy result in BC
LD C,L
JP SETSEC ; Bios call Set Sector
; get Disk map block number from FCB
; out: HL= FCB address
; DE= DM
; BC= offset in DM
; Zero Flag set (Z) if DM= 0, else reset (NZ)
; (squeezed by Joe Wright)
GETDM: LD L,(IX+NXTREC) ; get record number in L
RL L ; shift it left once
LD A,(NEXTND) ; get EXM
AND (IX+FCBEXT) ; And the extent number
LD H,A ; to H
LD A,(NBLOCK) ; get BSH
LD B,A ; to B
INC B ; +1
CALL SHRHLB ; shift HL right B times
LD D,B ; zero to D
LD A,L ; result in A
GETDM4: LD HL,(ARWORD)
LD C,16 ; add offset 16 to point to DM
ADD HL,BC
LD C,A ; add entry FCB
ADD HL,BC
LD A,(MAXLEN+1) ; test 8 bits/16 bits FCB entry
OR A
LD E,(HL) ; get 8 bit value
JR Z,GETDMX ; ..if 8-bit entries, exit
ADD HL,BC ; add twice (16-bit values)
LD E,(HL) ; get LSB
INC HL ; increment pointer
LD D,(HL) ; get MSB
DEC HL ; decrement pointer
GETDMX: LD A,D ; check for zero DM value
OR E
RET ; and exit
; calculate Sector number
; in: DE= block number from FCB
CALSEC: LD HL,0 ; clear MSB sector number
LD A,(NBLOCK) ; get loop counter
LD B,A ; save it in B
EX DE,HL
CALSC0: ADD HL,HL ; shift L,D,E
RL E
DJNZ CALSC0 ; B times
EX DE,HL
LD A,(NMASK) ; get sector mask
AND (IX+NXTREC) ; And with next record
OR E ; set up LSB sector number
LD E,A
RET ; and exit
;_______________________// Non-Banked //
CSEG
;-----
; check file Read-Only status, then fall through to CALDIR
CKRODI: CALL CHKFRO ; abort if the file is R/O
; ..fall through
;-----
; calculate DIRBUF entry point
CALDIR: LD A,(SECPNT) ; get sector pointer
CALDIR1: LD HL,(DIRBUF) ; get start address dirbuf
CALDI0: ADD A,L ; add L=L+A
LD L,A
RET NC ; ..if no Carry, then exit
INC H ; increment H
RET ; and exit
;-----
; init file count
SETFCT: LD HL,-1 ; set up file count
LD (FILCNT),HL ; save it
RET ; and exit
;--------------------------------------------------
; fn # 28 Write Protect Disk
; enter: None, exit: A= 00H
;--------------------------------------------------
CMND28: LD HL,(DSKWP) ; get disk W/P vector
CALL SDRVB ; include drive bit
LD (DSKWP),HL ; save disk W/P vector
LD DE,(NFILES) ; get max number of files-1 (bumped below)
LD HL,(TEMP0) ; get pointer to disk parameter block
INC HL ; correct pointer
; setlf0 relocated inline here
SETLF0: INC DE ; increment last file
LD (HL),D ; save it in TEMP0
DEC HL
LD (HL),E
RET ; and exit
;_______________________// Banked //
COMMON /BANK2/
;-----
; search using first 15 bytes of FCB, test if found
SRCT15: CALL SEAR15 ; search on 15-bytes..
; ..fall through to test presence
;-----
; test file count
TSTFCT: LD HL,(FILCNT) ; test file count= 0xFFFF
LD A,H ; get MSB
AND L ; And LSB
INC A ; test if result= 0xFF
RET ; and exit
;-----
; test last file
TSTLF: LD HL,(TEMP0) ; get pointer to last file
LD DE,(FILCNT) ; get file counter
LD A,E ; subtract DE-(HL)
SUB (HL)
INC HL
LD A,D
SBC A,(HL)
RET ; and exit
;-----
; get next FCB from Drive
; in: A= 0 check checksum, A= 0FFH update checksum
RDDIR: LD C,A ; save checksum flag
LD HL,(FILCNT) ; get file counter
INC HL ; increment it
LD (FILCNT),HL ; and save it
LD DE,(NFILES) ; get maximum number of files
LD A,E ; is this the last file ?
SUB L
LD A,D
SBC A,H
JP C,SETFCT ; ..if so, set file count to 0xFFFF
LD A,L ; get file count LSB
RRCA ; *32
RRCA
RRCA
AND 60H ; mask it
LD (SECPNT),A ; save it for later use
RET NZ ; ..if not first FCB sector, return
PUSH BC ; save checksum flag
CALL STDIR ; calculate sector/track Directory
RDDIR2: CALL DMADIR ; set up DMA Directory
CALL READR ; read a record
CALL STDMA ; ..and set up user's DMA
POP BC ; check/update checksum Directory
; C= 0 check checksum, C= 0FFH update checksum
CHKDIR: LD HL,(NCHECK) ; get number of checked records
LD DE,(RECDIR) ; get current record
XOR A ; clear Carry
SBC HL,DE ; test current record
RET Z ; ..if zero, exit
RET C ; ..if greater than NCHECK, exit
CALL CKS127 ; checksum first 127 bytes
ADD A,(HL) ; ..then 128th byte
LD HL,(CSV) ; get pointer checksum directory
ADD HL,DE ; add current record
INC C ; test checksum flag
JR NZ,CHKDR1 ; 0xFF -> update checksum
LD (HL),A ; update checksum
RET ; and exit
CHKDR1: CP (HL) ; test checksum
RET Z ; ..if ok, exit
; checksum differs, so Disk has changed - relog and continue
LD A,(FLAGS)
BIT 4,A ; inform user ?
LD B,0 ; disk change error code
LD DE,MDSKCH ; disk changed message
CALL NZ,ERROR ; inform user
; relog current Drive after media change detected
CALL GETCDM ; get current Drive mask in HL
EX DE,HL ; transfer mask to DE
CALL UNLOG ; reset login vector for logged Drive
CALL RELOG1 ; do the meat of relogging
; Caveat emptor: this call is recursive..
CALL SETFCT ; re-initialize search file count
XOR A ; we only get here by checking
JR RDDIR ; ..and all checking is done in RDDIR
;-----
; read sector from Drive
READR: CALL READ ; Bios call Read Sector
JR WRITE0
;-----
; write sector on Drive
WRITER: CALL WRITE ; Bios call Write Sector
WRITE0: OR A ; test exit code
RET Z ; ..if ok, exit
LD B,1 ; disk I/O error code
LD DE,MBADSC ; load bad sector message
LD HL,(STBDSC) ; load bad sector vector
JP JUMPHL ; output error
;_______________________// Non-Banked //
CSEG
; ##### CHECK: unreferenced code
PUSH IX
CALL CMND16
; #####
; JP target of unreferenced code between CMND36 and SELDRV
NOTUSE2: LD HL,(FCBADR)
LD (ARWORD),HL
; JP target of unreferenced code between CMND34 and CMND21
NOTUSE3: POP IX
JP SELTPB ; select TPA bank
;--------------------------------------------------
; fn # 16 Close File
; enter: DE= Address of FCB, exit: A= Directory Code
;--------------------------------------------------
BGPTCH2 EQU $+1 ; <--- BGii patch point
CMND16: CALL SELDR1 ; select Drive from FCB
; (also switches System bank)
JP CLOSE ; ..and continue in Banked code
;_______________________// Banked //
COMMON /BANK2/
;--------------------------------------------------
; Close File
; (continue CMND16)
;--------------------------------------------------
CLOSE: BIT 7,(IX+FCBMOD) ; test FCB/file modified
RET NZ ; ..if not, no close required
CALL CHKRO ; test disk W/P
CALL SRCT15 ; search file and test present
RET Z ; ..if not, then exit with error
CALL CKRODI ; check file W/P, get Directory entry
LD BC,16 ; offset to DM block
ADD HL,BC ; add offset
EX DE,HL ; save DIR ptr in DE
LD HL,(ARWORD) ; get FCB ptr
ADD HL,BC ; add offset
EX DE,HL
LD B,C ; transfer counter
; copy FCB (DE) to DIR (HL), if and only if DIR= 0 or DIR= FCB
CLOSE0: INC (HL)
DEC (HL) ; test DIR for 0
LD A,(DE) ; get byte from FCB
JR Z,CLOSE1 ; ..if 0, ok to copy
CP (HL) ; test if same as DIR
JP NZ,RETCFF ; ..if not, abort close and return error
CLOSE1: LD (HL),A ; else save in DIR
INC DE
INC HL
DJNZ CLOSE0 ; bump pointers and loop until done
LD DE,-20 ; add -20 to get extent number from DIR
ADD HL,DE ; HL= pointer to extent number
LD A,(IX+FCBEXT) ; get extent number FCB
CP (HL) ; compare with extent number Directory
JR C,CLOSE3 ; ..if FCB < Directory, then jump
LD (HL),A ; save extent number in Directory
INC HL ; get pointer to next record
INC HL
INC HL
LD A,(IX+FCBREC) ; get next record FCB
LD (HL),A ; save next record in Directory
CLOSE3: CALL CLOSE6 ; clear Archive bit and write FCB
CALL GETDME ; get data module and extent
JR Z,CLOSE4
PUSH BC ; save prior module and extent
LD BC,0
CALL SETDME ; set FCB data module and extent to 0
CALL SRCT15 ; find proper DIR entry
POP BC
JR Z,JSETDME ; ..if extent 0 not found, exit
CLOSE4: PUSH BC
CALL CLOSE6 ; clear Archive bit and write FCB
LD B,4 ; mask for STFLAG (bit2 = Modify stamp)
LD HL,STUP ; address of stamp update routine
CALL STPMSK ; mask stamp type and update if enabled
POP BC ; get original module and extent bak
JSETDME: JP SETDME ; restore to FCB and exit
CLOSE6: CALL CALDIR ; get directory entry
LD BC,11 ; point to Archive byte
ADD HL,BC
RES 7,(HL) ; reset Archive bit
RES 7,(IX+ARCATT) ; reset bit in FCB
; write FCB to disk
WRFCB: CALL CALDIR ; point to Dir entry to write
LD A,FCBUSR ; offset to User byte in FCB
CALL CALDI0 ; ..do the add here
LD (HL),0 ; prevent writing it to disk
CALL STDIR ; calculate setor/track Directory
LD C,0FFH ; update checksum Directory
CALL CHKDIR
WRITD1: CALL DMADIR ; set up DMA Directory (label for DS)
LD C,1 ; write Directory flag
CALL WRITER ; write record
JP STDMA ; set up DMA user
;_______________________// Non-Banked //
CSEG
;--------------------------------------------------
; fn # 26 Set DMA Address
; enter: DE= DMA Address, exit: A= 00H
;--------------------------------------------------
CMND26: LD (DMA),DE ; save DMA address
; set DMA address
STDMA: LD BC,(DMA) ; get DMA address
JR DMADR0 ; ..and do Bios call
; set DMA address Directory
DMADIR: LD BC,(DIRBUF) ; get DMA address Directory
DMADR0: JP SETDMA ; Bios call Set DMA
;_______________________// Banked //
COMMON /BANK2/
;-----
; get bit from ALV buffer
; in: DE= block number
; out: A= bit in LSB
; B= bit number in A
; HL= pointer in ALV buffer
GETBIT: LD A,E ; get bit number
AND 7 ; mask it
INC A ; +1
LD C,A ; save it
SRL D ; get byte number
RR E ; divide DE by 8
SRL D
RR E
SRL D
RR E
LD HL,(ALV) ; get start address ALV buffer
LD B,A ; save bit number for next shift
ADD HL,DE ; add byte number
LD A,(HL) ; get 8 bits
GETBT0: RLCA ; get correct bit
DJNZ GETBT0
LD B,C ; restore bit number
RET ; and return to caller
;--------------------------------------------------
; Delete File
; (continue CMND19)
;--------------------------------------------------
DELETE: CALL COMCOD ; call common code w/ VDEL on stack
; delete core routine
VDEL: CALL CKRODI ; check file W/P, get Directory entry
LD (HL),0E5H ; remove file
INC HL
LD A,(HL) ; get first char
SUB '$' ; see if submit file
JR NZ,VDEL1 ; ..if not, skip over
LD (SUBFLG),A ; clear subflag if $*.* erased
VDEL1: INC HL
RES 7,(HL) ; ensure erased files are not public
LD C,0 ; remove bits ALV buffer
; ..fall through and return to caller
; fill bit buffer from FCB in DIRBUF
; in: C= 0 reset bit, C= 1 set bit
FILLBB: CALL CALDIR ; get directory entry
LD DE,16 ; get offset DM block
ADD HL,DE ; add offset
LD B,E ; get block counter
FILLB0: LD E,(HL) ; get LSB block number
INC HL ; increment pointer
LD D,0 ; reset MSB block counter
LD A,(MAXLEN+1) ; test >256 blocks present
OR A
JR Z,FILLB1 ; ..if not, jump
DEC B ; decrement block counter
LD D,(HL) ; get correct MSB
INC HL ; increment block counter
FILLB1: LD A,D ; test block number
OR E
JR Z,FILLB2 ; ..if zero, then get next block
PUSH HL ; save pointer
PUSH BC ; save counter and set/reset bit
LD HL,(MAXLEN) ; get maximum length ALV buffer
OR A ; reset Carry
SBC HL,DE ; test DE <= maxlen ALV buffer
JR C,FILLB3 ; ..if yes, skip insert bit
; set/reset bit in ALV buffer
SETBIT: PUSH BC ; save set/reset bit
CALL GETBIT ; get bit
AND 0FEH ; mask it
POP DE ; get set/reset bit
OR E ; set/reset bit
SETBT0: RRCA ; rotate bit in correct position
DJNZ SETBT0
LD (HL),A ; save 8 bits
FILLB3: POP BC ; get counter and set/reset bit
POP HL ; get pointer
FILLB2: DJNZ FILLB0 ; repeat for all DM entries
RET ; and return to caller
;_______________________// Non-Banked //
CSEG
;-----
; check file W/P bit - SEARCH called first
CHKFRO: CALL CALDIR ; get Directory entry
LD DE,WHLATT ; offset to R/O bit
ADD HL,DE ; add offset
LD DE,(WHEEL) ; get Wheel byte address
LD A,(DE) ; retrieve the actual byte
AND A ; ..and check it
JR NZ,CHKFR4 ; we have Wheel, so allow writes
BIT 7,(HL) ; else, check Wheel attribute
JR NZ,CHKFR2 ; ..if yes, then jump error
CHKFR4: INC HL ; check W/P bit
BIT 7,(HL) ; test file W/P
JR NZ,CHKFR2 ; ..if W/P, then jump
CHKFR3: BIT 7,(IX+PSFATT) ; was file accessed as Pubic or Path ?
RET Z ; ..if normal access, return
LD A,(FLAGS) ; else, test for writes allowed
AND 0010B
RET NZ ; ..if writes allowed, go ahead
CHKFR2: LD HL,(SFILRO) ; ptr file W/P message (CP/M 2.2 vector table)
LD B,3 ; file W/P error code
LD DE,MFILRO ; load file W/P message
JP JUMPHL ; display message
;-----
; check Drive Write Protect
BGCKDRO:
CHKRO: CALL CHKRO1 ; is the disk W/P ?
RET NZ ; ..if disk is R/W, then return
LD B,2 ; else, set disk W/P error code
LD DE,MRO ; load drive W/P message
LD HL,(STRO) ; ptr Drive W/P message (CP/M 2.2 vector table)
JP JUMPHL ; display message
CHKRO1: LD HL,(DSKWP) ; get W/P drive vector
CALL SDRVB ; set the bit for this drive
SBC HL,DE ; see if extra bit added (Carry is clear)
RET
;_______________________// Banked //
COMMON /BANK2/
;-----
; search using first 12 bytes of FCB
SEAR12: LD A,12
DEFB 21H ; trash HL and fall through
; search using first 15 bytes of FCB
SEAR15: LD A,15
; search for File Name
; in: A= number of bytes for which to search
SEARCH: LD (SEARNB),A ; save number of bytes
LD A,0FFH ; set exit code to 0xFF (not found)
LD (SEAREX),A
LD (DCOPY),IX ; copy FCB pointer to RAM (search next)
CALL SETFCT ; initiate file counter
; force directory read with call home (Floppy only)
LD HL,(NCHECK) ; is this a fixed media ?
LD A,H
OR L
CALL NZ,HOME ; if removable, invoke Bios call Home routine
; search next File Name
SEARCN: XOR A ; checksum Directory
LD H,A
LD L,A
LD (SEARQU),HL ; clear flags, question mark and PUBlic found
RES 7,(IX+PSFATT) ; reset PUBlic/SYStem file flag
CALL RDDIR ; get FCB from Directory
CALL TSTFCT ; test if past last entry
JR Z,JSEAR8 ; ..if yes, jump (note Carry always clear)
LD DE,(DCOPY) ; get FCB pointer
LD A,(DE) ; get first byte
CP 0E5H ; test if searching empty Directory
JR Z,SEARC1 ; ..if yes, then jump
PUSH DE ; save FCB pointer
CALL TSTLF ; test last file on this Drive
POP DE ; restore FCB pointer
JSEAR8: JR NC,SEARC8 ; ..if yes, then jump
SEARC1: CALL CALDIR ; get entry in Directory
LD A,(HL) ; get first byte Directory entry
AND 7FH ; mask high bit
CP 21H ; test time stamp
JR Z,SEARCN ; ..if yes, then get next Directory entry
LD C,0 ; clear counter
LD A,(SEARNB) ; get number of bytes to search for
LD B,A ; save it in counter
SEARC2: LD A,B ; test if character is zero
OR A
JR Z,SEARC9 ; ..if yes, then jump
LD A,(DE) ; get byte from FCB
XOR '?' ; test if question mark
AND 7FH ; mask it
JR Z,SEARC6 ; ..if yes, then jump
LD A,C ; get FCB counter
OR A ; test first byte
JR NZ,SEARC3 ; ..if not, then jump
LD A,(FLAGS) ; get flag byte
RRA ; test PUBlic file enable
JR NC,SEARC3 ; ..if not, jump
INC HL ; get pointer to PUBlic bit
INC HL
BIT 7,(HL) ; test PUBlic bit Directory
DEC HL ; restore pointer
DEC HL
JR Z,SEARC3 ; ..if no PUBlic file, then jump
LD A,(DE) ; get first byte FCB
CP 0E5H ; test if searching empty Directory
JR Z,SEARC3 ; ..if yes, then jump
; The following 3 lines of code represent a deviation from description
; of PUBlic files as given in DDJ article by Bridger Mitchell and Derek
; McKay of Plu*Perfect Systems. The specification states that PUBlic
; files will _NOT_ be found by any wildcard reference, except when a
; '?' is in the FCB+0 byte. The code here relaxes that as follows:
; If we are in the same User area as the PUBlic file, then don't report
; file as PUBlic, but find it. This has a nasty side effect - PUBlic
; files can be erased if we are in the same area. However, these files
; also show up on the directory (they wouldn't otherwise), so at least
; we should know we're blasting them.
XOR (HL) ; test FCB = Directory entry
AND 7FH ; mask it (setting Zero flag)
JR Z,SEARC5 ; ..if user is same, jump
LD A,0FFH
LD (SEARPU),A ; set PUBlic file found
SET 7,(IX+PSFATT) ; set PUBlic/SYStem file flag
JR SEARC5 ; jump found
SEARC3: LD A,C ; get FCB counter
CP 13 ; is it User code ?
JR Z,SEARC5 ; ..if so, jump (don't test)
CP 12 ; is it an extent number ?
LD A,(DE) ; get byte from FCB
JR Z,SEARC7 ; ..if extent number, then jump
XOR (HL) ; is FCB byte = Directory Entry byte ?
AND 7FH ; mask it
SEARC4: JR NZ,SEARCN ; ..if not the same, jump and get next entry
SEARC5: INC DE ; increment FCB pointer
INC HL ; increment Directory Entry pointer
INC C ; increment counter
DEC B ; decrement counter
JR SEARC2 ; test next byte
SEARC6: DEC A ; set question mark found flag
LD (SEARQU),A
JR SEARC5 ; jump found
SEARC7: XOR (HL) ; test extent
CALL SEARC7A ; mask extent
JR SEARC4 ; ..and test result
SEARC7A: PUSH BC
LD B,A ; save extent
LD A,(NEXTND) ; get extent mask
CPL ; complement it
AND MAXEXT ; mask it
AND B ; mask extent
POP BC ; restore counters
RET
SEARC8: CALL SETFCT ; error set file counter
JP RETCFF ; set return code to 0xFF and exit
SEARC9: LD HL,(SEARQU) ; get question mark and PUBlic found flags
LD A,H
AND L
JR NZ,SEARC4 ; ..if yes, then search for next entry
CALL TSTLF ; test for last file
CALL NC,SETLF0 ; and update if so
LD HL,(RECDIR) ; set DE return to Directory record
LD (DEVAL),HL ; ..for DateStamper simulation
LD A,(FILCNT) ; get file counter
AND 3 ; mask it
LD (PEXIT),A ; and set exit code
XOR A ; clear exit code search
LD (SEAREX),A
RET ; and return to caller
;-----
; common code of DELETE, RENAME, and CSTAT
; (coded in a manner that is compatible with Z280 in protected mode)
COMCOD: CALL CHKRO ; check disk W/P
CALL SEAR12 ; search file
COMCO1: CALL TSTFCT ; test if file found
POP HL ; routine addr to HL (in case not found)
RET Z ; ..if not, then exit
PUSH HL ; else, found so routine back to stack
PUSH HL ; ..twice as RET pops first push
LD HL,COMCO2
EX (SP),HL ; COMCO2 to stack, routine addr to HL
JP (HL) ; ..branch to routine
COMCO2: CALL WRFCB ; write Directory buffer on disk
CALL SEARCN ; search next entry
JR COMCO1 ; and test it
;-----
; Rename File
; note wildcard support
RENAM: CALL COMCOD ; go to common code w/ VRENAM on stack
; rename core routine
VRENAM: CALL CHKFRO ; check file W/P
LD HL,(ARWORD) ; get FCB address
LD DE,16 ; offset to new name
ADD HL,DE ; add offset
EX DE,HL ; swap regs
CALL CALDIR ; get Directory entry
INC HL
INC HL
RES 7,(HL) ; make any renamed file private
DEC HL
DEC HL
LD B,11 ; set up loop counter
RENAM1: INC HL ; increment Directory pointer
INC DE ; increment FCB pointer
LD A,(DE) ; get character from FCB
AND 7FH ; mask it
CP '?' ; test if question mark
JR NZ,RENAM2 ; ..if not, then change character on disk
LD A,(HL) ; else, no change, so get what's there
RENAM2: RLA ; clear MSB
RL (HL) ; get MSB from directory
RRA ; and move to FCB
LD (HL),A ; save in directory
DJNZ RENAM1 ; loop until done
RET
;-----
; Change Status Bits for File
CSTAT: CALL COMCOD ; got to common code w/ VCSTAT on stack
; change core routine
VCSTAT: PUSH IX
POP DE ; FCB pointer in DE
CALL CALDIR ; get Directory entry
LD B,11 ; set up loop counter
CSTAT1: INC HL ; increment Directory pointer
INC DE ; increment FCB pointer
LD A,4 ; are we pointing to Wheel Attribute ?
CP B
JR NZ,CSTAT2 ; ..if not, jump
PUSH HL
LD HL,(WHEEL) ; else, do we have Wheel privileges ?
LD A,(HL)
POP HL
AND A ; set flags to show
JR NZ,CSTAT2 ; ..if we have Wheel, jump
BIT 7,(HL) ; is file Wheel protected ?
JP NZ,CHKFR2 ; ..if so, jump
CSTAT2: LD A,(DE) ; get status bit from FCB
RL (HL) ; remove MSB of Directory
RLA ; get MSB from FCB
RR (HL) ; and move into Directory char
DJNZ CSTAT1 ; loop till done
RET
;-----
; Compute File Size
FILSZ: LD BC,0 ; reset file size length
LD D,C
CALL LDRRC ; save it in FCB+33,34,35
CALL SEAR12 ; search file
FILSZ0: CALL TSTFCT ; test if file found
RET Z ; ..if not, exit
CALL CALDIR ; get Directory entry
EX DE,HL ; copy to DE
LD HL,15 ; offset to next record
CALL CALRRC ; calculate random record count
LD A,D ; test LSB < (IX+33)
SUB (IX+33)
LD A,C ; test ISB < (IX+34)
SBC A,(IX+34)
LD A,B ; test MSB < (IX+35)
SBC A,(IX+35)
CALL NC,LDRRC ; write new maximum
CALL SEARCN ; search next file
JR FILSZ0 ; and test it
;--------------------------------------------------
; Find File
; (called by CMND15)
;--------------------------------------------------
FINDF: CALL SRCT15 ; search file
RET NZ ; ..if found, then exit
LD A,(FLAGS)
BIT 5,A ; test if Path enabled
RET Z ; ..if not, exit
LD HL,(PATH) ; get Path address
LD A,H ; test if zero (no Path)
OR L
RET Z ; ..if so, exit
FINDF0: CALL DUPATH ; test if current DU: is in Path
JP Z,SEARC8 ; ..if not, then jump
LD A,B ; Drive in A
PUSH BC ; save DU
PUSH HL
CALL SELDK
; ##### PUSH HL first, then BC
; saves POP HL + next PUSH HL (2 bytes)
POP HL
POP BC
LD A,C ; User in A
PUSH HL ; save Path pointer
CALL RESUSR ; add new User number in FCB+0 and FCB+13
CALL SRCT15 ; search file and test if present
POP HL ; restore Path pointer
JR Z,FINDF0 ; ..if not present, then test next Path entry
PUSH HL ; save Path pointer
CALL CALDIR ; get Directory entry
LD DE,10 ; add offset SYStem bit
ADD HL,DE
BIT 7,(HL) ; test SYStem file
LD A,(FLAGS) ; test for relaxed Path definition
RLA ; ..by rotating bit
RLA ; ..into Carry flag
POP HL ; restore Path pointer
JR C,FINDF3 ; ..if Carry, SYStem attribute not required
JR Z,FINDF0 ; else, SYStem file, so test next Path entry
FINDF3: LD A,(DEFDRV) ; get current Drive
INC A ; increment Drive number
LD (FCB0),A ; save it in exit FCB0
SET 7,(IX+PSFATT) ; set PUBlic/SYStem file flag
RET ; and return to caller
;_______________________// Non-Banked //
CSEG
;--------------------------------------------------
; fn # 15 Open File
; enter: DE= address of FCB, exit: A= Directory Code
;--------------------------------------------------
CMND15: CALL SELDRV ; select Drive from FCB
; (also switches System bank)
LD A,32 ; 32 bytes
LD (FCBBUP),A ; ..to update in FCB
LD (IX+FCBMOD),0 ; clear data module number
CALL FINDF ; find file using Path (..in Banked code)
CALL TSTFCT ; test file found (..in Banked code)
RET Z ; ..if not, then exit
OPENF0: LD A,(IX+PSFATT) ; get PUBlic/SYStem file bit
PUSH AF ; save it
LD A,(IX+FCBEXT) ; get extent number from FCB
PUSH AF ; save it
CALL CALDIR ; get Directory entry
LD A,(HL) ; find real User number file is in
OR 80H ; set User valid flag
PUSH IX ; save FCB entry
POP DE ; get in DE
LD BC,32 ; number of bytes to move
LDIR ; move Directory to FCB
LD (IX+FCBUSR),A ; and put User byte back
SET 7,(IX+FCBMOD) ; set FCB/File not modified
LD B,(IX+FCBEXT) ; get extent number
LD C,(IX+FCBREC) ; get next record number
POP AF ; get old extent number
LD (IX+FCBEXT),A ; save it
CP B ; compare old and new extent number
JR Z,OPENF1 ; ..if same, then jump
LD C,0 ; set next record count to 0
RR C ; record count to max (0x80) if need new extent
OPENF1: LD (IX+FCBREC),C ; save next record count
POP AF ; get PUBlic/SYStem file bit
RL (IX+PSFATT) ; remove MSB from IX+7
RLA ; set new MSB in Carry
RR (IX+PSFATT) ; save Carry in IX+7
LD B,1 ; mask for STFLAG (bit0 = Access stamp)
LD HL,STLA ; address of last accessed routine
JP STPMSK ; ..and continue in Banked code
;--------------------------------------------------
; fn # 22 Make File
; enter: DE= Address of FCB, exit: A= Directory Code
;--------------------------------------------------
CMND22: CALL SELDRV ; select Drive from FCB
LD (IX+FCBMOD),0 ; clear data module number
LD A,32 ; 32 bytes
LD (FCBBUP),A ; save # bytes to update in FCB
MAKES: CALL CHKRO ; check drive W/P
CALL SELSYB ; switch System bank
JP MAKESB ; ..and continue in Banked code
;_______________________// Banked //
COMMON /BANK2/
;--------------------------------------------------
; Make File
; (continue CMND22 / MAKES)
;--------------------------------------------------
MAKESB: LD HL,(ARWORD)
LD A,(HL) ; get first byte of FCB
PUSH AF ; save it
LD (HL),0E5H ; set first byte to empty file
LD A,1 ; search for 1 byte
CALL SEARCH ; search empty file
POP AF ; get first byte of FCB
LD (IX+0),A ; restore it
CALL TSTFCT ; test empty file found
RET Z ; ..if not, return error
LD HL,(ARWORD) ; get FCB pointer
CALL CKSUB ; check if this is a submit file
LD DE,15 ; prepare offset
ADD HL,DE ; add it
LD B,17 ; set counter
CALL CLRMEM ; clear FCB+15 up to FCB+31
RES 7,(IX+PSFATT) ; reset PUBlic/SYStem file bit
RES 7,(IX+ARCATT) ; reset Archive bit if present
CALL CALDIR ; get Directory entry
PUSH IX ; save FCB entry
POP DE ; get it in DE
EX DE,HL ; exchange FCB and Directory entry
LD BC,32 ; number of bytes to copy
LDIR
CALL WRFCB ; write FCB on disk
SET 7,(IX+FCBMOD) ; set FCB/File not modified
LD B,2 ; mask for STFLAG (bit1 = Create stamp)
LD HL,STCR ; address stamp create routine
JP STPMSK ; mask type and stamp file if enabled
;-----
; open next extent
OPENEX: BIT 7,(IX+FCBMOD) ; test if FCB/File modified (write)
JR NZ,OPENX2 ; ..if not, jump
CALL CLOSE ; close current FCB
LD A,(PEXIT) ; get exit code
INC A ; test if error
RET Z ; ..if so, exit
OPENX2: CALL CALNEX ; calculate next extent
JR C,OPENX3 ; ..if error, jump
OPENX0: CALL SRCT15 ; search for 15-char match and test presence
JR NZ,OPENX5 ; ..if yes, jump
LD A,(RDWR) ; test Read/Write flag
OR A ; test if read
JR Z,OPENX3 ; ..if yes, then error
CALL MAKES ; make new extent if write
CALL TSTFCT ; test if successful
JR NZ,OPENX6 ; ..if yes, then exit
OPENX3: SET 7,(IX+FCBMOD) ; set FCB/File not modified
RETCFF: LD A,0FFH ; set exit code
OPENX4: JP SAVEA ; and return to caller
OPENX5: CALL OPENF0 ; open file
OPENX6: XOR A ; clear exit code
JR OPENX4 ; use same code to exit routine
;-----
; calculate next extent
; out: Carry set (C) if overflow detected
CALNEX: CALL GETDME ; get extent number, data module number
BIT 6,B ; test error bit random record
SCF ; set error flag
RET NZ ; ..if non-zero, error exit
INC C ; increment extent number
LD A,C ; get extent number
AND MAXEXT ; mask it for max extent
LD C,A ; save it in C
JR NZ,CALNE1 ; ..if new data module not required, jump
INC B ; set next data module
LD A,B ; get it in A
AND MAXMOD ; mask it for max module
LD B,A ; save it in B
SCF ; set error flag
RET Z ; ..if file overflow, return
CALNE1: LD (IX+NXTREC),0 ; zero next record count
SETDME: LD (IX+FCBEXT),C ; save extent number
LD (IX+FCBMOD),B ; save data module number
AND A ; clear flag
RET
GETDME: LD C,(IX+FCBEXT) ; get extent number
LD B,(IX+FCBMOD) ; get data module number
LD A,C
CALL SEARC7A ; mask extent
RES 7,B ; clear unmodified flag
OR B ; test for module and extent = 0
RET ; ..and return to caller
;_______________________// Non-Banked //
CSEG
;--------------------------------------------------
; fn # 33 Read Random Record
; enter: DE= Address of FCB, exit: A= Read/Write Code
;--------------------------------------------------
CMND33: CALL SELDR1 ; select Drive from FCB
LD A,36
LD (FCBBUP),A ; save # bytes to update in FCB
; read random sector
XOR A ; set Read/Write flag
CALL LDFCB ; load FCB random record (..in Banked code)
JR Z,READS ; ..if no error, then read sector
RET
;--------------------------------------------------
; fn # 20 Read Sequential
; enter: DE= Address of FCB, exit: A= Read/Write Code
;--------------------------------------------------
CMND20: CALL SELDR1 ; select Drive from FCB
; (also switches System bank)
LD A,33 ; 33 bytes
LD (FCBBUP),A ; save # bytes to update in FCB
; read sector
READS: JP READSB ; ..continue in Banked code
;_______________________// Banked //
COMMON /BANK2/
;--------------------------------------------------
; Read Sector
; (continue CMND20 / READS)
;--------------------------------------------------
READSB: XOR A ; set Read/Write flag
LD (RDWR),A ; save it
LD A,(IX+NXTREC) ; get record counter
CP 80H ; test if last record in this extent
JR Z,READSB1 ; ..if yes, then open next extent
CP (IX+FCBREC) ; test if greater than current record
JR C,READSB2 ; ..if not, then get record
READSB0: LD A,1 ; set end of file flag
JP SAVEA ; and exit
READSB1: CALL OPNXCK ; open next extent
READSB2: CALL GETDM ; get block number from DM in FCB
JR Z,READSB0 ; ..if block number= 0, jump to end of file
CALL CALSEC ; calculate sector number (128 bytes)
CALL CALST ; calculate sector/track number
CALL READR ; read data
JP WRITS7 ; increment elsewhere if necessary
;-----
; consolidated routine to open extent and check status
OPNXCK: CALL OPENEX ; open next extent
LD A,(PEXIT) ; get exit code
OR A
RET Z ; return if open OK
POP HL ; else, pop return address to abort R/W
JR READSB0 ; ..and set error code to EOF
;_______________________// Non-Banked //
CSEG
;--------------------------------------------------
; fn # 34 Write Random
; fn # 40 Write random with zero fill
; enter: DE= Address of FCB, exit: A= Read/Write Code
;--------------------------------------------------
CMND34:
CMND40: CALL SELDR1 ; select Drive from FCB
LD A,36 ; 36 bytes
LD (FCBBUP),A ; save # bytes to update in FCB
; write random sector and write random with zero fill
LD A,0FFH ; set Read/Write flag
CALL LDFCB ; load FCB random record (..in Banked code)
JR Z,WRITES ; ..if no error, then write record
RET ; else, return error
; ##### CHECK: unreferenced code
CALL CHKRO ; check drive W/P status
PUSH IX
LD IX,ZSFCB ; buffer internal FCB
CALL NOTUSE1 ; write sector
JP NOTUSE3
RET
; #####
;--------------------------------------------------
; fn # 21 Write Sequential
; enter: DE= Address of FCB, exit: A= Read/Write Code
;--------------------------------------------------
CMND21: CALL SELDR1 ; select Drive from FCB
LD A,33 ; 33 bytes
LD (FCBBUP),A ; save # bytes to update in FCB
; write sector - permitted to PUBlic file and those found along Path
WRITES: LD A,0FFH ; set Read/Write flag
LD (RDWR),A ; save it
BGPTCH1 EQU $+1 ; <--- patched location for BGii
CALL CHKRO ; check disk W/P
NOTUSE1: CALL SELSYB ; switch System bank
JP WRITESB ; ..and continue in Banked code
;_______________________// Banked //
COMMON /BANK2/
;--------------------------------------------------
; Write Sector
; (continue CMND21 / WRITES)
;--------------------------------------------------
WRITESB: BIT 7,(IX+ROATT) ; test if file W/P
JR NZ,WRITSA ; ..if yes, then file W/P message
CALL CHKFR3 ; test W/P if Path or Public used
LD HL,(WHEEL) ; get address of Wheel byte
LD A,(HL) ; do we have Wheel privileges ?
AND A
JR NZ,WRITSB ; ..if yes, allow write
BIT 7,(IX+WHLATT) ; else, test if file is Wheel protected
WRITSA: JP NZ,CHKFR2 ; ..if so, then file W/P message
WRITSB: BIT 7,(IX+NXTREC) ; end of this extent ?
CALL NZ,OPNXCK ; open next extent and check status
CALL GETDM ; get block number from FCB
JP NZ,WRITS5 ; ..if not zero, then jump to write sector
PUSH HL ; save pointer to block number
LD A,C ; test first block number in extent
OR A
JR Z,WRITS1 ; ..if yes, then jump
DEC A ; decrement pointer to block number
CALL GETDM4 ; get previous block number
; get free block from ALV buffer
; in: DE= old block number
; out: DE= new block number, 0 if no free block
; (HL counts up, DE counts down)
WRITS1: LD H,D ; copy old block to HL
LD L,E
GETFR0: LD A,D ; test down counter is zero
OR E
JR Z,GETFR1 ; ..if so, jump
DEC DE ; decrement down counter
PUSH HL ; save up/down counter
PUSH DE
CALL GETBIT ; get bit from ALV buffer
RRA ; test if zero
JR NC,GETFR3 ; ..if yes, then found empty block
POP DE ; get up/down counter
POP HL
GETFR1: LD BC,(MAXLEN) ; get maximum ALV length-1 in BC
LD A,L ; is HL >= length ALV-1 ?
SUB C ; ..do while preserving HL
LD A,H
SBC A,B
JR NC,GETFR2 ; ..if end of buffer, then jump
INC HL ; increment up counter
PUSH DE ; save down/up counter
PUSH HL
EX DE,HL ; up counter in DE
CALL GETBIT ; get bit from ALV buffer
RRA ; test if zero
JR NC,GETFR3 ; ..if yes, then found empty block
POP HL ; get down/up counter
POP DE
JR GETFR0 ; and test next block
GETFR2: LD A,D ; test if last block testet
OR E
JR NZ,GETFR0 ; ..if not, then test next block
JR WRITSG ; continue with DE= 0
GETFR3: SCF ; set block number used
RLA ; save bit
; SETBT0 code now inline
GETFR4: RRCA ; rotate bit in correct position
DJNZ GETFR4
LD (HL),A ; save 8 bits
POP DE ; get correct counter
POP HL ; restore stack pointer
; ..continue with (DE= block number)
WRITSG: POP HL ; get pointer to block number
LD A,D ; test if block number = 0
; WRITS8 code now inline
OR E
LD A,2 ; set disk full error
JP Z,SAVEA ; and return to caller
RES 7,(IX+FCBMOD) ; reset FCB/File modified
LD (HL),E ; save block number
LD A,(MAXLEN+1) ; get number of blocks
OR A ; is it < 256 ?
JR Z,WRITS2 ; ..if so, jump
INC HL ; increment to MSB block number
LD (HL),D ; ..and save MSB block number
WRITS2: LD C,2 ; set write new block flag
LD A,(NMASK) ; get sector mask
AND (IX+NXTREC) ; mask with record number
JR Z,WRITSX ; ..if zero, then Ok (at start new record)
LD C,0 ; else, clear new block flag
WRITSX: LD A,(FUNCT) ; get function number
SUB 40 ; test if Write Random Record with zero fill
JR NZ,WRITS6 ; ..if not, then jump
PUSH DE ; save block number
LD HL,(DIRBUF) ; use Directory buffer for zero fill
LD B,128 ; 128 bytes to clear
CALL CLRMEM ; do clear
CALL CALSEC ; calculate sector number (128 bytes)
LD A,(NMASK) ; get sector mask
LD B,A ; copy it
INC B ; increment it to get number of writes
CPL ; complement sector mask
AND E ; mask sector number
LD E,A ; and save it
LD C,2 ; set write new block flag
WRITS4: PUSH HL ; save registers
PUSH DE
PUSH BC
CALL CALST ; calculate sectors/track
CALL DMADIR ; set DMA Directory buffer
POP BC ; get write new block flag
PUSH BC ; save it again
CALL WRITER ; write record on disk
POP BC ; restore registers
POP DE
POP HL
LD C,0 ; clear write new block flag
INC E ; increment sector number
DJNZ WRITS4 ; write all blocks
CALL STDMA ; set user DMA address
POP DE ; get block number
WRITS5: LD C,0 ; clear write new block flag
WRITS6: RES 7,(IX+FCBMOD) ; reset FCB/File modified flag
PUSH BC ; save it
CALL CALSEC ; calculate sector number (128 bytes)
CALL CALST ; calculate sectors/track
POP BC ; get write new block flag
CALL WRITER ; write record on disk
LD A,(IX+NXTREC) ; get record counter
CP (IX+FCBREC) ; compare with next record
JR C,WRITS7 ; ..if less, then jump
INC A ; increment record count
LD (IX+FCBREC),A ; save it on next record position
RES 7,(IX+FCBMOD) ; reset FCB/File modified flag
WRITS7: LD A,(FUNCT) ; get function number
CP 20
RET C ; return if < 20
CP 21+1
RET NC ; return if > 21
INC (IX+NXTREC) ; increment record count
RET ; and return to caller
;--------------------------------------------------
; Load FCB for random read/write
; (called by CMND33 / CMND34 / CMND40)
;--------------------------------------------------
; out: Zero Flag set (Z) = no error, reset (NZ) if error
LDFCB: LD (RDWR),A ; save Read/Write flag
LD A,(IX+33) ; get first byte random record
LD D,A ; save it in D
RES 7,D ; reset MSB to get next record
RLA ; shift MSB in Carry
LD A,(IX+34) ; load next byte random record
RLA ; shift Carry
PUSH AF ; save it
AND MAXEXT ; mask next extent
LD C,A ; save it in C
POP AF ; get byte
RLA ; shift 4 times
RLA
RLA
RLA
AND 00FH ; mask it
LD B,A ; save data module number
LD A,(IX+35)
LD E,6 ; set random record too large flag
CP 4 ; test random record too large
JR NC,LDFCB8 ; ..if yes, then error
RLCA ; shift 4 times
RLCA
RLCA
RLCA
ADD A,B ; add byte
LD B,A ; save data module number in B
LD (IX+NXTREC),D ; set next record count
LD D,(IX+FCBMOD) ; get data module number
BIT 6,D ; test error random record
JR NZ,LDFCB0 ; ..if yes, then jump
LD A,C ; get new extent number
CP (IX+FCBEXT) ; compare with FCB
JR NZ,LDFCB0 ; ..if not equal, then open next extent
LD A,B ; get new data module number
XOR (IX+FCBMOD) ; compare with data module number
AND MAXMOD ; mask it
JR Z,LDFCB6 ; ..if equal, then return
LDFCB0: BIT 7,D ; test FCB modified (write)
JR NZ,LDFCB1 ; ..if no, then jump
PUSH DE ; save registers
PUSH BC
CALL CLOSE ; close extent
POP BC ; restore registers
POP DE
LD E,3 ; set close error
LD A,(PEXIT) ; get exit code
INC A
JR Z,LDFCB7 ; ..if error, then exit
LDFCB1: CALL SETDME ; save data module and extent
CALL SEAR15 ; search next FCB
LD A,(PEXIT) ; get error code
INC A
JR NZ,LDFCB5 ; if no error, then exit
LD A,(RDWR) ; get Read/Write flag
LD E,4 ; set read empty record
INC A
JR NZ,LDFCB7 ; ..if read error, then jump
CALL MAKES ; make new FCB
LD E,5 ; set make error
LD A,(PEXIT) ; get error code
INC A
JR Z,LDFCB7 ; ..if error, then exit
JR LDFCB6 ; else, exit w/ zero set (no error)
LDFCB5: CALL OPENF0 ; open file
LDFCB6: JP OPENX6 ; set zero flag and clear error code
LDFCB7: LD (IX+FCBMOD),0C0H ; set random record error
LDFCB8: LD A,E ; get error code
LD (PEXIT),A ; and save it
OR A ; clear Zero flag
SETB14: SET 7,(IX+FCBMOD) ; set FCB/File not modified
RET ; and return to caller
;_______________________// Non-Banked //
CSEG
;-----
; calculate random record
; in: HL= offset in FCB
; DE= FCB pointer
; out: D= LSB random record
; C= ISB
; B= MSB
CALRRC: ADD HL,DE ; pointer to FCB+15 or FCB+32
LD A,(HL) ; get record number
LD HL,12 ; offset to extent number
ADD HL,DE ; get pointer to extent byte
LD D,A ; save record number
LD A,(HL) ; get extent byte
AND MAXEXT ; mask it (000eeeee)
RL D ; shift MSB in Carry (Cy= R, D= rrrrrrr0)
ADC A,0 ; add Carry (00xeeeeex)
RRA ; shift 1 time (16 bits, 000xeeee)
RR D ; D= xrrrrrrr
LD C,A ; save ISB
INC HL ; increment to data module number
INC HL
LD A,(HL) ; get data module number 00mmmmmmm
RRCA ; divide module by 16
RRCA
RRCA
RRCA
PUSH AF ; save it mmmm00mm
AND 03H ; mask for maximum module
LD B,A ; save it 000000mm
POP AF ; get LSB
AND 0F0H ; mask it mmmm0000
ADD A,C ; add with ISB mmmxeeee
LD C,A ; save ISB
RET NC ; no Carry then return
INC B ; increment MSB 000000mm
RET ; and return to caller
; 000000mm mmmxeeee xrrrrrrr
;-----
; clear/fill memory
CLRM4: LD B,4 ; clear memory area (used by MAKES and WRITSX)
CLRMEM: XOR A ; clear A
; fill memory with byte in A
; in: A= byte
; B= counter (# bytes)
; HL= destination
FILLM: LD (HL),A ; store byte
INC HL ; increment pointer
DJNZ FILLM ; and clear all bytes
RET
;-----
; check/update !!!TIME&.DAT checksum
; calc checksum of 127 bytes, return with HL pointing to 128th byte
; in: DIRBUF= pointer to T&D record
; out: A= checksum
; HL= points to checksum byte in record
CKS127: LD HL,(DIRBUF) ; directory buffer pointer
XOR A ; clear A
LD B,127 ; test first 127 bytes
CKSLP: ADD A,(HL) ; sum all bytes to A
INC HL
DJNZ CKSLP
RET
;-----
; get word (16-bit value) indirectly
; in: HL= base addr
; A= offset
; out: HL= 16-bit value at offset addr
; Z-Flag set if HL= 0
GWHLA: CALL CALDI0 ; add A to HL
LD A,(HL) ; get low byte at memory location
INC HL
LD H,(HL) ; get high byte
LD L,A
OR H ; test if zero (set return status)
RET
;_______________________// Banked //
COMMON /BANK2/
;-----
; test if Drive is valid
; in: B= drive number
; out: Carry flag set (C) = ok, NC= not ok
DRVOK: LD HL,(OZ3ENV) ; B/P Bios base +0x98 (addr Z3 ENV decriptor)
LD A,H ; test if valid (<> zero)
OR L
JR Z,DRVOK1 ; ..if not, jump exit (assume drive is valid)
LD A,8 ; else, offset to Env type (ENV base +8)
CALL CALDI0 ; move ptr fwd
BIT 7,(HL) ; is it extended (=> 0x80) ?
JR Z,DRVOK1 ; ..if not, jump
LD A,44 ; move ptr fwd to DRVEC
CALL GWHLA ; ..and get vector of valid drives in HL
LD A,16 ; set up loop counter
SUB B ; adjust to drive in scope
DRVOK0: DEC A ; counter -1
ADD HL,HL ; "shift" MSB into Carry
JR NZ,DRVOK0 ; loop
RET ; ..and return with status in Carry
DRVOK1: SCF ; set return status (Carry set = Ok)
RET
;_______________________// Non-Banked //
CSEG
;-----
; test if current DU: is in Path
; in: HL= path address
; out: B= current drive, C= current user
; A= 0xFF, Z-Flag clear (NZ) if match or '$$'
; Z-Flag set if no match
DUPATH: LD BC,(USER) ; get default drive (B) and user number (C)
LD A,(HL) ; get byte of path entry
OR A ; test for end
RET Z ; ..if yes, then return with Z-Flag set
CP '$' ; test if current drive
JR Z,DUPAT0 ; ..if so, then jump
DEC A ; decrement drive number of path entry
LD B,A ; ..and save in B
DUPAT0: INC HL ; advance pointer
LD A,(HL) ; get user number
CP '$' ; test if current user
JR Z,DUPAT1 ; ..if so, then jump
LD C,A ; else, save in C
DUPAT1: INC HL ; advance pointer
OR 0FFH ; set exit code to 0xFF (match)
RET ; and return to caller
;--------------------------------------------------
; fn # 46 Return Free Space
; enter: E= Disk Number, exit: A= Error Code
; Space in DMA+0..DMA+3
;--------------------------------------------------
CMND46: CALL CMND14 ; select disk from E register
CALL SELSYB ; switch System bank
CALL CMD46B ; ..and continue in Banked code
JP DOSEXT1
;_______________________// Banked //
COMMON /BANK2/
;--------------------------------------------------
; Return Free Space
; (continue CMND46)
;--------------------------------------------------
CMD46B: LD HL,DFCALC
CALL CLRM4 ; clear 4 bytes
LD DE,(MAXLEN) ; get maximum blocks-1
INC DE ; +1
LD HL,(ALV) ; get ptr to allocation vector
; process one byte of ALV - outer loop
DFREE1: LD C,(HL) ; get bit pattern of allocation byte
PUSH HL ; save pointer
LD B,8 ; process 8 bits
DFREE2: RL C ; shift MSB to Carry
CCF ; invert
JR NC,DFREE4 ; ..if MSB was 1, then jump
; count zeros - inner loop
PUSH BC
LD B,4
LD HL,DFCALC
DFREE3: LD A,(HL)
ADC A,0 ; add Carry
LD (HL),A ; ..and save back
INC HL ; move ptr
DJNZ DFREE3 ; loop till done
POP BC
DFREE4: DEC DE ; decrease number of blocks
LD A,E ; test if zero
OR D
JR Z,DFREE5 ; ..if zero, exit inner loop
DJNZ DFREE2
POP HL ; restore ALV ptr
INC HL ; move to next byte
JR DFREE1 ; ..and loop (outer)
; allocation is measured in blocks, convert to kB
DFREE5: POP HL ; clear stack
LD A,(NBLOCK) ; get block shift factor
SUB 3 ; (BSH= 3 -> 1k, 4 -> 2k, ...8 -> 32k)
JR Z,DFREEX ; if BSH-3 = 0, then we're done
; multiply 32-bit (4 bytes)
DFREE6: LD HL,DFCALC ; set ptr to data storage
LD B,4 ; bytes to work
DFREE7: RL (HL) ; shift MSB into Carry
INC HL ; move ptr
DJNZ DFREE7 ; ..and loop
DEC A
JR NZ,DFREE6 ; if not zero, loop for next block size
; ..else, fall through and transfer data
; transfer to DMA using interbank move
; (destination could be in a disabled memory bank)
DFREEX: LD A,(TPABNK) ; get TPA bank #
LD B,A ; ..in B
LD A,(OSYSBK) ; get SYS bank # (Bios base +0x83 SYSBNK)
LD C,A ; ..in C
CALL XMOVE ; call Bios fn #29 XMOVE, set banks for MOVE
LD HL,DFCALC ; source address
LD DE,(DMA) ; destination address
LD BC,4 ; bytes to move
JP MOVE ; ..and do it (Bios fn #25)
;_______________________// Banked Data Segment //
COMMON /B2RAM/
; Disk free calculation data area (4 bytes)
DFCALC: DEFS 4
; DEFB 0,0,0,0
;_______________________// Non-Banked //
CSEG
;--------------------------------------------------
; fn # 152 Parse FileSpec
; enter: DE= FCB Address, exit: A= Number of '?'s in Fn.Ft
; String in DMA Buffer DE= Address of delimiter char
; FCB+15= 0 if Parse Ok, 0FFH if Error(s)
;--------------------------------------------------
CMD152: PUSH DE ; save ptr to FCB
XOR A
LD (DE),A ; clear FCB Drive field
INC DE ; move ptr fwd
LD A,' ' ; prepare to init FCB name and type fields
LD B,8+3
CM152A: LD (DE),A
INC DE
DJNZ CM152A
; destination FCB initialized
POP DE ; restore ptr to FCB
LD HL,(DMA) ; get DMA address
LD BC,(USER) ; get User number
LD A,C ; ..in A
LD (STATUS),A ; and save as status
CALL SCANF8 ; place first token (8-bytes) in name field
CP ':' ; is the delimiter a Colon ?
LD A,0 ; prepare status flag for ok DU/DIR
JP NZ,SCAN1 ; ..if only a file name, then jump
INC HL ; point to char after Colon
PUSH HL
PUSH DE
CALL CMND49 ; get address of Z3 Environment
; ##### CHECK: should jump further down ? (repeats CALL CMND49)
JR Z,DUSCAN ; ..if none found, jump
LD A,21 ; offset to Z3NDIR (addr Named Dir Buffer)
CALL GWHLA ; get vector at offset memory location
JR Z,DUSCAN ; ..if address not valid, jump to DU scan
INC DE
; scan Named Dir entries
DIRS1: CALL DUPATH ; test if current DU: in Path
JR Z,DUSCAN ; ..if not, then jump
PUSH BC ; save DU
PUSH HL ; save ptr to file name
PUSH DE ; save ptr to NDR entry
LD B,8 ; max 8 chars
DIRS2: LD A,(DE) ; get char
CP (HL) ; compare to NDR entry
JR NZ,DIRS3 ; ..if no match, exit loop
INC HL ; else, point to next char
INC DE
DJNZ DIRS2 ; loop
DIRS3: POP DE ; restore regs
POP HL
POP BC
JR Z,CHKVVC ; ..if ok, then jump
LD BC,16 ; 8 bytes name + 8 bytes password
ADD HL,BC ; advance ptr
JR DIRS1
; scan Drive/User
DUSCAN: CALL CMND49 ; get address of Z3 Environment
JR Z,DUS0 ; ..if none found, jump
LD A,46 ; offset to DU Flag
CALL CALDI0 ; move ptr fwd
LD A,(HL) ; get byte at memory location
OR A
JR Z,DUSERR ; ..if zero, then jump
; (1= ok to accept DU:, 0= not ok)
; no Z3 Envorionment, assume DU: form and scan/extract Drive/User
DUS0: LD L,C
EX DE,HL
CALL DELIM
EX DE,HL
CP ' '+1 ; legal char ?
JR C,DUSOK ; ..if only delimiter, jump w/ current DU
SUB 41H ; convert possible drive spec to number
JR C,DUS1 ; ..if less than 'A', must be digit
; set Drive number (0= A)
LD B,A ; save drive in B
INC DE ; point to next input char
LD A,(DE) ; get char
CP ' '+1 ; end of string ?
JR C,DUCHEK ; ..if so, jump to check limits
; set User number
DUS1: LD HL,2*256 ; get up to 2 digits
DUS1A: LD A,(DE)
CP ' '+1 ; is it a delimiter ?
JR C,DUS2 ; ..if so, jump to end
SUB '0' ; digit ?
JR C,DUSERR ; ..if not, jump
CP 10 ; number in range (0..9) ?
JR NC,DUSERR ; ..if not, jump
LD C,A ; save
LD A,L ; ..and multiply old digit
ADD A,A ; *2
ADD A,A ; *4
ADD A,L ; *5
ADD A,A ; *10
ADD A,C
LD L,A
INC DE ; advance to next byte
DEC H ; count down
JR NZ,DUS1A ; ..loop if more to go
LD A,(DE)
DUS2: CP ' ' ; is it the proper delimiter ?
JR NZ,DUSERR ; ..if not, jump error exit
LD C,L ; save good User in C
; BC now has parsed DU:, check legality
DUCHEK: LD HL,(USER) ; get currently logged DU
OR A
SBC HL,BC ; same as that parsed ?
JR Z,DUSOK ; ..if so, take good exit
CHKVVC: CALL SELSYB ; switch System bank
CALL DRVOK ; check if drive is valid
CALL SELTPB ; switch back to TPA bank
JR C,DUSOK
DUSERR: LD BC,(USER) ; get current DU
DEFB 0F6H ; fall through with OR A,0AFH
DUSOK: XOR A ; set return status OK
POP DE
POP HL
PUSH AF
LD A,B ; get drive #
INC A ; change to A= 1 base
LD (DE),A ; store in FCB+0
LD A,C ; get User #
LD (STATUS),A ; store in mem
CALL SCANF8 ; scan token (8-byte)
POP AF ; restore A cleared
; ..and fall through
; skip to file type field
; in: HL= ptr to next char
; DE= ptr to DN field of FCB
SCAN1: PUSH AF ; save error flag for exit
EX DE,HL
LD BC,8 ; point to before file type field of FCB
ADD HL,BC
EX DE,HL ; Extract file type field
LD B,3
LD A,(HL) ; get the delimiter char
CP '.' ; is it '.' ?
JR NZ,SCAN2 ; ..if no type, then jump
INC HL ; else, point to char after '.'
CALL SCANF ; ..and get file type
SCAN2: LD (DEVAL),HL
INC DE ; move ptr from last char of name to T1
INC DE ; ..to T2
INC DE ; ..to T3
INC DE ; ..to EXM
XOR A ; zero
LD (DE),A ; store here at FCB+12 (EXM)
INC DE ; move to S1
LD A,(STATUS) ; get User number
LD (DE),A ; ..and store it here at FCB+13 (S1)
INC DE ; move ptr to S2
XOR A ; zero
LD (DE),A ; store here at FCB+14 (S2)
INC DE ; move ptr to FCB+15
POP AF ; restore error flag
LD (DE),A ; ..and save it in FCB+15
RET
;-----
; scan up to 8 chars
SCANF8: XOR A ; get a 0
LD (PEXIT),A ; ..and clear question mark counter
LD B,8 ; scan for up to 8 chars
; ..fall through to main scan routine
; scan token with interpreting/expanding '*' and '?' wild cards
; in: B= max number of bytes
; HL= ptr to token
; DE= destination addr (FCB file name field)
; out: HL= ptr to terminating delimiter
SCANF: PUSH DE ; preserve FCB address
SCANF0: INC DE ; ptr to next byte in FCB
CALL DELIM ; is it a delimiter ?
JR Z,SCANF2 ; ..if so, jump
INC HL ; else, point to next char
CP '*' ; is (DE) a wild card ?
JR NZ,SCANF1 ; ..if not, continue
DEC HL ; else, back up to same char
LD A,'?' ; ..and expand with '?'
SCANF1: LD (DE),A
CP '?' ; is it wild ?
JR NZ,SCNOQ ; ..if not, jump
PUSH HL ; else, save HL
LD HL,PEXIT ; point to count storage
INC (HL) ; ..and increment
POP HL ; restore HL
SCNOQ: DJNZ SCANF0 ; ..loop till done
INC DE ; then advance to next FCB char
; flush to next delimiter
SCANF3: CALL DELIM
JR Z,SCANFX ; ..if delimiter found, then jump
INC HL ; point to next char
JR SCANF3 ; and loop
; preserve present char, and fill remaining chars in field w/ spaces
SCANF2: PUSH AF ; save char
SCANFA: LD A,' ' ; store this char
LD (DE),A ; ..in FCB field
INC DE ; point to next
DJNZ SCANFA ; loop till done
POP AF ; restore char
SCANFX: POP DE ; restore FCB address
RET ; ..and exit
; capitalize char, then fall through to check for delimiter
DELIM: LD A,(HL) ; get char
CP 'a' ; less than small letter A ?
JR C,SDELM
CP 'z'+1 ; between small A and small Z ?
JR NC,SDELM
AND 5FH ; remove bit 5 to capitalize
; check for delimiter
; in: HL= ptr to char
; out: A= addressed char
; Zero Flag set (Z) if delimiter, end-of-line or cmd separator
; Zero Flag cleard (NZ) if char is not a delimiter
SDELM: CP '_' ; is it an Underscore ?
RET Z
CP '.' ; a Period ?
RET Z
CP ',' ; a Comma ?
RET Z
CP '>' ; greater than a Greater Sign ?
RET NC ; ..if so, return
CP ':' ; is it any of : ; < = > ?
JR NC,SDEL0 ; ..if so, jump to set status
CP ' ' ; is it less than a Space ?
RET NC ; ..if so, return (Space has zero set)
XOR A ; else, must be less than Space,
RET ; so return with zero in A
SDEL0: CP A ; set Z-Flag retaining char
RET
;::::::::::::::::::::::::::::::::::::::::
;::::: Time and Date Routines
;--------------------------------------------------
; fn # 98 Get time
; enter: DE= Address to Put Time, exit: A= Time/Date Code
;--------------------------------------------------
CMD98A: EX DE,HL ; entry point for cmnd12
CMD98: LD C,0 ; set parameter to get time/date
DEFB 21H ; ..and fall through to GSTD (trashing HL)
;--------------------------------------------------
; fn # 99 Set time
; enter: DE= Address of Time, exit: A= Time/Date Code
;--------------------------------------------------
CMD99: LD C,1 ; set parameter to set time/date
GSTD: LD HL,(GSTIME) ; get time/date get/set routine address
PUSH HL ; ..to stack for pseudo 'Jump'
DOTDER: OR 0FFH ; save 1 T state while setting flags
RET ; vector to service routine
;--------------------------------------------------
; fn # 103 Set file stamp
; enter: DE= Address of FCB, exit: A= Time/Date Code
; Stamp in DMA Buffer
;--------------------------------------------------
; wildcards allowed in FCB
; ##### ZSDOS-GP.Z80 (v1) refers to "10 byte stamp from DMA" ??
CMD103: LD HL,(DMA) ; address DMA buffer
LD DE,ZSSTMP ; time stamp buffer
CALL STPCP0 ; ..and copy
;--------------------------------------------------
; fn # 102 Get file stamp
; enter: DE= Address of FCB, exit: A= Time/Date Code
; Stamp in DMA Buffer
;--------------------------------------------------
; wildcards allowed in FCB
CMD102: CALL SELDRV ; select Drive in FCB
; (also switches System bank)
JP CMD103B ; copy stamp (..in Banked code)
;_______________________// Banked //
COMMON /BANK2/
; ZSDOS2 supports DateStamper and P2DOS (CP/M+) file stamping.
; Internally, the Universal T&D format is used, and converted
; as needed when reading from / writing to disk. Similarly,
; all stamps are converted to/from the Universal stamp format
; (15 bytes packed BCD), and stuffed into ZSSTMP buffer.
;-----
; Universal Stamp format
; Bytes 0..4 Create 5..9 Access 10..14 Modify
; | YY MM DD HH MM | YY MM DD HH MM | YY MM DD HH MM |
;
; YY MM DD HH MM SS - Universal T&D format
; (all BCD, prefix 19 assumed for years 79-99, else 20)
;-----
; P2DOS (CP/M+) Stamp format
; Bytes 0..3 Create 4..7 Modify
; | nn nn HH MM | nn nn HH MM |
;
; nnnn HH MM SS - P2DOS (CP/M+) format
; (nnnn = binary number of das since 1 Jan 1978)
; (HH MM SS = time in BCD)
;--------------------------------------------------
; Get/Set file stamp
; (continue from CMD102/CMD103)
;--------------------------------------------------
CMD102B:
CMD103B: CALL SRCT15 ; find the FCB
JP Z,DOTDER ; ..if not found, exit w/ error
LD HL,GETSTR ; address of get time stamp routine
LD A,(FUNCT) ; check function
CP 102 ; get stamp ?
JR Z,DOTDR3 ; ..if yes, jump
LD HL,PUTSTR ; address of put (set) time stamp routine
JR STAMPT ; mask for enabled stamp type(s) in B
STPMSK: LD A,(STFLAG) ; get time stamp flags
AND B ; mask
RET Z ; ..if not selected, then return
; enter here for stamp Create/Access/Modify
STAMPT: PUSH HL
CALL CHKRO1 ; test for disk W/P but avoid error trap
POP HL
JP Z,DOTDER ; no stamp if disk is W/P
DOTDR3: CALL GETDME ; get data module and extent number
JP NZ,DOTDER ; ..if not extent 0 of module 0, then quit
JP (HL) ; else, continue in GETSTR/PUTSTR routine
;-----
; Stamp Create
STCR: CALL P2CR ; call P2D Create routine
LD B,0 ; offset into stamp in B
JR STT ; Stamp Update
;-----
; Stamp Modify
STUP: CALL P2UP ; call P2D Update routine
LD B,10 ; offset into stamp in B
JR STT ; Stamp Access
;-----
; Stamp Access
STLA: LD B,5 ; offset into stamp in B
LD A,0FFH ; set correct value to validate status
; update stamp
STT: LD (STATUS),A ; save first routine status
LD C,2 ; show as stamp
LD A,(SECPNT) ; get sector pointer
ADD A,3 ; point to no date attribute
LD L,A
LD H,0
LD DE,(DIRBUF) ; get Directory buffer pointer
ADD HL,DE ; point to Dir entry
BIT 7,(HL)
LD A,(STATUS) ; get prev routine status for possible ret
RET NZ ; ..if no date attribute, return (don't update)
JR STPSV0 ; else, jump to service routine
;-----
; get/put Stamp routines
GETSTR: CALL GSTAMP ; get file stamp in Universal format
CP 1 ; test if OK
JR Z,GETST0 ; ##### not needed if following code is removed
; ##### CHECK unreferenced code
LD BC,0 ; flag as read
CALL STPSVC
; #####
GETST0: JP STPCPY
;_______________________// Non-Banked //
CSEG
STPCPY: CALL SELTPB ; select TPA bank
LD HL,ZSSTMP ; time stamp buffer
LD DE,(DMA) ; address DMA buffer
STPCP0: LD BC,15 ; copy 15 bytes
LDIR
RET
;_______________________// Banked //
COMMON /BANK2/
RET ; ##### unreferenced, will not be executed
; get/put Stamp routines contd.
PUTSTR: CALL PSTAMP ; put file stamp in Universal format
LD BC,1 ; flag as write
; ..and fall through
;-----
; Stamp service routine, combined get/put
; in: A= index to Dir entry (00H, 20H, 40H, 60H)
; B= offset in stamp (0, 5, 10)
; C= function (0= read, 1= write, 2= update)
STPSVC: LD (STATUS),A ; save first routine status
STPSV0: LD (RWCODE),BC ; save function code and sector offset
LD BC,(TDFVCT) ; get T/D vector
CALL GETCDM ; get mask for current Drive in HL
LD A,H
AND B
LD H,A
LD A,L
AND C
OR H ; test if Drive has T/D vector
JR Z,STPSVE ; ..if it doesn't, jump error
CALL DMADIR ; set DMA address to Directory buffer
LD BC,(RECDIR) ; get current Directory record
XOR A
SRL B ; calculate buffer offset
RR C ; divide record by two, lsb to carry
RRA ; place in msb of A
LD HL,SECPNT ; get sector pointer
ADD A,(HL) ; get offset to stamp in buffer
RRA ; divide by 2 (8 stamps/rec)
PUSH AF ; save index for later
PUSH BC ; ..and save relative record number
; now calculate record number needed
LD HL,(NDIR0) ; first entry ALV buffer
LD DE,(NMASK) ; mask number of blocks
INC E ; now is number of records per block
XOR A ; clear A (cheap zero)
LD D,A ; ..to D for 16-bit math
LD B,16 ; check all bits
STPSV1: ADD HL,HL ; shift MSB into Carry
JR NC,STPSV2 ; ..if bit was zero, then jump
EX (SP),HL ; else, get relative record
ADD HL,DE ; add records for this directory block
EX (SP),HL ; back to top-of-stack
STPSV2: DJNZ STPSV1 ; loop until done
POP DE ; has actual record number now
CALL STDIR1 ; set track/sector and read
CALL READ
POP DE ; get index (was in A before)
AND A ; test if read ok
JR NZ,STPSVE ; ..if error, jump
CALL CKS127 ; checksum first 127 bytes
CP (HL) ; test against DS's checksum
JR NZ,STPSVE ; ..if error, jump exit
LD BC,15 ; size of Universal Stamp
LD E,D ; index in E
LD D,B ; D= 0
LD HL,(DIRBUF)
ADD HL,DE ; now pointing to correct stamp
LD DE,ZSSTMP ; time stamp buffer
LD A,(RWCODE) ; get Read/Write code
AND A ; is it Get stamp ?
JR NZ,PUTSTP ; ..if not, we're supposed to write
LDIR ; copy bytes
JR STPSVX ; ..and exit
;-----
; copy Stamp from User DMA to local buffer
PUTSTP: SUB 2 ; is this a simple put
JR Z,UPSTMP ; ..if not, read the clock
EX DE,HL ; swap pointers
PUTS1: LDIR ; move new stamp into place
CALL CKS127 ; checksum first 127 bytes of local buffer
LD (HL),A ; ..and save checksum in last byte of record
INC C ; flag as non-deferred (0 from ldir -> 01)
CALL WRITE ; write sector to disk
AND A ; test if error
JR NZ,STPSVE
STPSVX: LD A,1 ; indicate all is well (OL status)
DEFB 01H ; ..fall through the LD A,FF with LD BC,FF3E
; return error status
STPSVE: LD A,0FFH ; set status
PUSH AF ; save return code
CALL STDMA ; set DMA
POP AF ; restore return code
CP 1 ; is it OK ?
RET Z ; ..if so, return
LD A,(STATUS) ; else, get first routine status
RET ; ..and return
;-----
; read Clock, place in proper Stamp field
UPSTMP: LD D,A ; reg A= 0
LD A,(STOFF) ; get offset in stamp
LD E,A ; make word length in DE
ADD HL,DE ; pointer to target address
PUSH HL ; save it
OR A ; is it Create time ?
LD B,15 ; prepare to clear 15 bytes
CALL Z,CLRMEM ; ..if Create, then zero entire stamp entry
LD DE,ZSDOSS ; point to Time and Date buffer
PUSH DE
CALL CMD98 ; call fn 98 to read clock
POP HL ; get buffer start
POP DE ; get stamp target address
DEC A ; was the clock read OK ?
JR NZ,STPSVE ; ..if not, jump error exit
LD BC,5 ; else, move 5 bytes
JR PUTS1 ; ..and finish up
;-----
; update Create/Modify field in T&D buffer
P2CR: LD E,0 ; set to Create field in stamp
DEFB 21H ; trash HL and fall through
P2UP: LD E,4 ; set to Modify field in stamp
CALL SETREC ; calculate offset in stamp
LD C,E ; move offset to C (B= 0)
ADD HL,BC ; destination now in HL
LD DE,ZSDOSS ; set address to read time
PUSH HL ; save destination address
PUSH DE ; ..and source address
CALL CMD98 ; call fn 98 to get time
POP DE ; restore addresses
POP HL
DEC A ; was the clock read OK ?
JR NZ,NOTIM0 ; ..if not, jump error exit
; ##### move label/target PSTMP1 up by one instruction
; this CALL U2PTIM could be saved (3 bytes)
CALL U2PTIM ; else, convert DE= U-time -> HL= P-time
JR PSTMP1 ; jump to write FCB
;-----
; Put File Stamp in Universal format
; convert Create and Modify time fields from Universal T&D format
; to P2DOS (CP/M+) form and insert in Directory buffer, call WRFCB
; routine to write on exit
; in: A= index to Dir entry (00H, 20H, 40H)
; out: A= time/date code
PSTAMP: CALL SETREC ; calculate the Stamp area address for file
LD DE,ZSSTMP ; time stamp buffer
CALL U2PTIM ; convert Create (DE= U-time -> HL= P-time)
JR NZ,NOTIM0 ; ..if invalid date, jump error exit
INC DE ; bypass Last Access field of input
INC DE
INC DE
INC DE
INC DE
CALL U2PTIM ; convert Modify field
PSTMP1: JR NZ,NOTIM0 ; ..if invalid date, jump error exit
CALL WRFCB ; write stamp
OKRET: LD A,1 ; set status ok and return
RET
NOSTD: POP AF ; remove return address from Stack
NOTIM0: OR 0FFH ; set error flags
RET ; back to caller
;-----
; Get File Stamp in Universal format
; read Create and Modify stamps and convert to Universal T&D format,
; null the Access time field
; in: A= index to Dir entry (00H, 20H, 40H)
; out: A= time/date code
GSTAMP: CALL SETREC ; calculate source Stamp address in HL
LD DE,ZSSTMP ; time stamp buffer
CALL P2UTIM ; convert Create (HL= P-time -> DE= U-time)
LD B,5 ; zero Last Access field for this type
XOR A ; clear A
GSLOOP: LD (DE),A ; ..and poke a zero
INC DE
DJNZ GSLOOP ; ..in each location
CALL P2UTIM ; convert Modify field
JR OKRET ; ..jump to return OK status
;-----
; convert Universal T&D to P2DOS (CP/M+) form
; in: DE= ptr to Universal T&D string
; HL= ptr to destination buffer for P2DOS (CP/M+) T&D
; out: A= 0, Zero Flag set (Z), if conversion OK
; A= 0FFH, Zero Flag clear (NZ), error (dest nulled)
; DE= ptr to Seconds byte in Universal T&D (not moved)
; HL= ptr to Seconds byte in P2DOS (CP/M+) T&D (not filled)
U2PTIM: PUSH HL ; save destination address
LD A,(DE) ; get BCD year
LD B,A ; ..to B
INC DE ; advance to month
LD A,(DE) ; get BCD month
OR B ; is it invalid (YY = MM = 0) ?
JR Z,NODATE ; ..if invalid stamp, jump to error exit
LD A,B ; get BCD year again from B
CALL BCDBIN ; convert year to binary
CP 78 ; is it 20th century ?
JR NC,YR19 ; ..if so, jump
ADD A,100 ; else, make it 21st century
YR19: LD BC,1900 ; set base century
ADD A,C ; add current year to base
LD C,A
LD A,0
ADC A,B
LD B,A
LD A,(DE) ; get BCD month
INC DE
CALL BCDBIN ; ..convert to binary
LD H,A
LD A,(DE) ; get day
INC DE ; point to U-hours
PUSH DE ; ..and save address on stack
CALL BCDBIN ; convert day to Binary
LD L,A ; day to L (Binary)
; check validity of day, month year (CHKDAT from DATE.ASM)
; L= binary day, H= binary month, BC= binary year (1978..2077)
LD A,H ; month
DEC A ; convert to 0-11 range
CP 12 ; is it a valid month ?
JR NC,BADDAT ; ..if invalid, then jump error
PUSH HL ; save day/month
LD E,A ; month-1 in E
LD D,0
LD HL,DMTABLE ; set lookup table for months
ADD HL,DE
LD D,(HL) ; get days in this month
POP HL ; restore day/month
CP 2-1 ; is this February ?
CALL Z,LEAPYR ; ..if so, check for leap year
JR NZ,CHKDT0 ; else, jump
INC D ; make 29 days
CHKDT0: LD A,L ; check for day within range
DEC A ; have day > 0, check for <= max. days
CP D
JR NC,BADDAT ; ..anything else is error
; calculate 16-bit binary date since 1978 in days, works til 2157
PUSH HL ; save month (H) and day (L)
LD H,0 ; null out month, leaving just days
EX DE,HL ; ..move to DE
LD L,C ; year in HL
LD H,B
LD BC,1978 ; start with base year in BC
DAYS0: OR A
SBC HL,BC ; is this the starting year ?
ADD HL,BC
JR Z,DAYS1 ; ..if so, jump
PUSH HL
LD HL,365 ; add days in non-leap year
ADD HL,DE ; ..to total days count in DE
EX DE,HL ; ..and put new days total in DE
POP HL
CALL LEAPYR ; is this a leap year ?
INC BC ; advance to next year
JR NZ,DAYS0 ; ..if not leap year, then loop
INC DE ; else, add a day
JR DAYS0 ; ..then loop
; error routines, destination P2DOS field nulled
NODATE: INC DE ; advance source ptr for same routine
INC DE
DEFB 3EH ; ..fall through to 2nd POP with LD A,0D1H
BADDAT: POP DE ; restore Universal T&D string (-> hours)
POP HL ; restore destination addr for P2DOS date
BADDA1: CALL CLRM4 ; fill with 4 nulls
INC DE ; ..advance ptr to exit position
INC DE
DEC A ; set error flags (A= 0FFH, Zero clear (NZ))
RET
; DE= total days (year + day), BC= binary year, (Stack)= month and day
; first day 0001H : Su 01 Jan 1978
; last day 8EADH : Fr 31 Dec 2077
; real last day FFFFH : Su 05 Jun 2157
DAYS1: POP HL ; restore month and day
EX DE,HL ; date to HL, month/day to DE
PUSH HL ; ..and save binary date
LD HL,DMTABLE ; address days-of-month table
LD E,1
DAYS2: LD A,D ; check for matching month
CP E
JR Z,DAYS4 ; ..exit when match
LD A,(HL) ; get days in this month
EX (SP),HL ; put table on stack, binary date to HL
ADD A,L ; add month's days to cum binary date
LD L,A
LD A,0
ADC A,H
LD H,A
LD A,E ; check this month
CP 2 ; is it February ?
CALL Z,LEAPYR ; ..if so, check if leap year
JR NZ,DAYS3A ; jump if not Feb and/or leap year
INC HL ; else, bump cum binary date by 29 Feb
DAYS3A: EX (SP),HL ; put cum binary date to stack
; ..and days-of-month table to HL
INC HL ; point to next month
INC E ; bump index counter
JR DAYS2 ; ..and loop
DAYS4: POP BC ; exit here, put cum binary date to BC
POP DE ; restore Universal T&D string (-> Hrs)
POP HL ; ..and destination address
LD (HL),C ; put binary date in string
INC HL
LD (HL),B
MOVHM: INC HL
EX DE,HL ; swap src/dest pointers
LDI ; move BCD hours
LDI ; ..and BCD minutes
EX DE,HL ; restore pointers into correct regs for exit
XOR A ; set OK flags
RET ; and return
;-----
; calculate leap year correction
; in: BC= year (binary, leap years = xxxxxx00B)
; out: Zero Flag set (Z) = correction necessary, reset (NZ) = no corr
LEAPYR: BIT 0,C ; get lower part of date
RET NZ ; ..if not leap year, return
BIT 1,C ; test other bit
RET ; ..and return
;-----
; convert BCD to Binary
; in: A= BCD digit
; out: A= binary number
BCDBIN: OR A ; test if zero
RET Z ; ..if so, return (it's the same)
PUSH BC ; save register
LD B,0 ; set counter
BCDBI0: INC B ; bump counter
SUB 1 ; count down BCD
DAA
JR NZ,BCDBI0 ; ..till all gone
LD A,B
POP BC
RET
;----
; convert Binary to BCD
; in: A= byte to be converted
; out: A= two packed BCD digits
; Convert byte in A register to two packed BCD digits
BINBCD: PUSH BC ; save register
LD B,0FFH ; preset counter
BINBCL: INC B ; bump counter output
SUB 10
JR NC,BINBCL ; loop bumping counter till no more 10's
ADD A,10 ; ..correct for underflow
LD C,A ; save low nybble here for a while
LD A,B ; ..and bring high nybble in A
ADD A,A ; move it into position
ADD A,A
ADD A,A
ADD A,A
ADD A,C ; add in low nybble
POP BC ; restore regs
RET
;-----
; convert P2DOS (CP/M+) form to Universal T&D string
; in: HL= ptr to P2DOS (CP/M+) T&D
; DE= ptr to destination buffer for Universal T&D
; out: A= 0, Zero Flag set (Z), if conversion OK
; A= 0FFH, Zero Flag clear (NZ), error (dest nulled)
; HL= ptr to Seconds byte in P2DOS (CP/M+) T&D (not moved)
; DE= ptr to Seconds byte in Universal T&D (not filled)
P2UTIM: PUSH DE ; save destination addr
LD E,(HL) ; get binary date to DE
INC HL
LD D,(HL)
INC HL
EX DE,HL ; put binary day/date in HL, P2D ptr in DE
LD A,H ; check for valid entry
OR L ; is date present ?
JR NZ,P2UTI0 ; ..if not null, then jump
POP HL ; get destination addr back
LD B,5
CALL BADDA1 ; ..and null the U-time field
EX DE,HL ; put pointers in correct regs
RET ; ..and return to caller
P2UTI0: PUSH DE ; save P2D pointer (-> Min)
LD BC,1978 ; beginning year
DMJ0: LD DE,365 ; set days in normal year
CALL LEAPYR ; check for leap year
JR NZ,DMJ1 ; ..if not leap year, then jump
INC DE ; else, add one day
DMJ1: OR A ; when # of days left..
SBC HL,DE ; ..is less than days in year..
JR C,DMJ2 ; ..year is in HL, so exit
JR Z,DMJ2 ; ..or if last day of year
INC BC ; bump starting year
JR DMJ0 ; ..and back for another try
; BC= binary year, HL= remaining days
DMJ2: ADD HL,DE ; compensate for underflow
LD A,1 ; start with month 1 (Jan)
LD D,0 ; prepare for 16-bit math
PUSH HL ; save days remaining
LD HL,DMTABLE ; address days-of-month table
DMJ3: LD E,(HL) ; get days in current month to E
CP 2 ; is it February ?
CALL Z,LEAPYR ; ..if so, check for leap year
JR NZ,DMJ4 ; ..if not leap year, then jump
INC E ; else, make 29 days
DMJ4: EX (SP),HL ; swap ptr (HL) with days remaining (Stack)
OR A
SBC HL,DE ; subtract days in month from remaining days
JR C,DMJ5 ; ..if we've gone too far, then exit
JR Z,DMJ5 ; ..or just far enough (last day of month)
EX (SP),HL ; swap back
INC HL ; point to next month in table
INC A ; bump month counter
JR DMJ3 ; ..and try again
; arrive here with binary year on Stack, relative month in A (Jan = 1),
; days in that month in E, and binary year in BC
DMJ5: ADD HL,DE ; compensate for underflow
EX (SP),HL ; ..and put back on Stack
POP HL ; restore day in L
CALL BINBCD ; convert month (in A) to BCD
LD H,B ; ..moving year to HL
LD B,A
LD A,L ; convert day
LD L,C
CALL BINBCD ; ..to BCD
LD C,A
LD DE,100 ; subtract centuries, one by one..
DMJ7A: OR A
SBC HL,DE
JR NC,DMJ7A ; ..until we go too far
ADD HL,DE ; then correct for underflow
LD A,L ; get year (tens and ones)
CALL BINBCD ; ..to BCD
POP DE ; restore P2D pointer (-> Min)
POP HL ; get Universal T&D string address
LD (HL),A ; store years
INC HL
LD (HL),B ; ..months
INC HL
LD (HL),C ; ..days
CALL MOVHM ; store hours and minutes, and set flags
EX DE,HL ; put U-time exit address in DE
RET ; ..and finish up elsewhere
;------
; calculate offset within T&D entry, if one exists
; out: HL= ptr to first byte of Create field
SETREC: LD HL,(DIRBUF)
LD BC,60H ; offset to Time and Date fields
ADD HL,BC
LD A,(HL) ; get byte
SUB 21H ; is TimeStamping present
JP NZ,NOSTD ; ..if not, quit here
LD A,(SECPNT) ; get sector pointer
RRCA ; shift 2 times
RRCA
LD C,A ; save temporarily
RRCA ; shift 2 more times
RRCA
ADD A,C ; ..and add in again
LD C,A ; set for offset (C= 0, 10, 20)
ADD HL,BC ; add offset
INC HL ; ..and bump to Create Time start
XOR A ; set return status OK
RET
; day-of-month table
DMTABLE: DEFB 31,28,31,30,31,30,31,31,30,31,30,31
;_______________________// Non-Banked //
CSEG
;-----
; JP (HL) with bank context switching
JUMPHL: CALL RETMEM ; get current mem bank (BIOS fn #39 RETMEM)
LD (CURBNK),A ; save it
CALL SELTPB ; select TPA bank
CALL JPHL ; do the actual JP (HL)
LD A,(CURBNK) ; get bank # back
JP SELMEM ; select mem bank again (BIOS fn #27 SELMEM)
; ..and let return from there
JPHL: JP (HL)
CURBNK: DEFS 1
;_______________________// Non-Banked //
CSEG ; technically not needed
; but necessary to assemble identical REL file
;::::::::::::::::::::::::::::::::::::::::
;::::: Memory Bank Switching Routines
; bring SYSBNK into context (Banked mode)
SELSYB: PUSH AF
LD A,(OSYSBK) ; B/P Bios base +0x83 (offset SYSBNK)
JR SELBNK ; switch bank
; bring TPABNK into context (Non-banked mode)
SELTPB: PUSH AF
LD A,(TPABNK) ; get TPA bank number
; ..and fall through
; select memory bank
SELBNK: CALL SELMEM ; BIOS fn #27 SELMEM (A= bank #)
POP AF
RET
;::::: DATA AREA (in CSEG)
PATHAD: DEFB 1,0 ; Internal path - Drive A, User 0
DEFB 0,0 ; blank entry
DEFB 0,0 ; blank entry
DEFB 0 ; ..and ending null
;-----
TDFVCT: DEFW 0 ; time and date vector
;-----
LOGIN: DEFW 0 ; Login vector
DSKWP: DEFW 0 ; Disk write protect vector
HDLOG: DEFW 0 ; Fixed disk login vector
END
;************************************************************************
; Remarks jxl:
; When assembled as M-REL, this source file produces an _exact_ copy
; of ZS227G.ZRL included in available B/P Bios package(s).
; Compared to v1 (ZSDOS-GP.Z80) many code portions are unaltered, or
; show only slight modifications. Major changes result from banking and
; addition of universal T&D stamp routines, supporting DateStamper
; as well as P2DOS (CP/M+) stamps.
; Besides basic functionality (e.g. error handling), all I/O routines
; are located in non-banked memory. Many Disk routines begin in non-banked
; memory with just a small footprint, and are continued in banked memory.
; This is also the case for T&D stamp routines. With the exception of
; 4 bytes stored in /B2RAM/, all data bytes are located in non-banked
; CSEG code segment.
;
; A comment on function 152 Parse FileSpec: ZCPR 4.1 (Z41.ZRL) relies
; on that function. Thus, it cannot be removed. Even though most probably
; no other application might use it. Currently, the entire function is
; located in non-banked memory. A considerable amount of space could be
; reclaimed by moving this function (or parts of it) to banked memory.
;
; Possible optimisations noticed during disassembly are marked with
; "#####" in the comment. Speaking of which - non-banked and banked parts
; need to be sector-aligned, ie. BPBUILD fills the remainder of the last
; 128-byte block with zeros. In its current state, the non-banked part
; leaves 71 bytes unused. Or, in other words, a block could be saved
; if 57 bytes could be eliminated and/or moved to banked code section.
;************************************************************************