mirror of https://github.com/wwarthen/RomWBW.git
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.
978 lines
26 KiB
978 lines
26 KiB
;===============================================================================
|
|
; SysCopy - Copy System Image to/from reserved tracks of disk for RomWBW
|
|
; adaptation of CP/M 2.2 & CP/M 3
|
|
;===============================================================================
|
|
;
|
|
; Author: Wayne Warthen (wwarthen@gmail.com)
|
|
;_______________________________________________________________________________
|
|
;
|
|
; Usage:
|
|
; SYSCOPY <dest>[=<src>]
|
|
;
|
|
; <dest> and <src> may be a drive or a file reference
|
|
; If <src> is not specified, the system image will
|
|
; be read from the current drive
|
|
;_______________________________________________________________________________
|
|
;
|
|
; Change Log:
|
|
; 2016-04-24 [WBW] Updated to preserve MBR partition table
|
|
; 2020-02-17 [WBW] Updated for CP/M 3
|
|
;_______________________________________________________________________________
|
|
;
|
|
; ToDo:
|
|
; 1) Add option to wait/prompt for disk change
|
|
; 2) Allow <src> and <dest> to be memory
|
|
;_______________________________________________________________________________
|
|
;
|
|
;===============================================================================
|
|
; Definitions
|
|
;===============================================================================
|
|
;
|
|
false .equ 0 ; define true
|
|
true .equ ~false ; define false
|
|
;
|
|
stksiz .equ $40 ; we are a stack pig
|
|
;
|
|
restart .equ $0000 ; CP/M restart vector
|
|
bdos .equ $0005 ; BDOS invocation vector
|
|
;
|
|
imgbuf .equ $900 ; load point for system image (from original SYSGEN)
|
|
mbrbuf .equ imgbuf+$4000 ; load point for MBR storage
|
|
;
|
|
;===============================================================================
|
|
; Code Section
|
|
;===============================================================================
|
|
;
|
|
.org $100
|
|
; setup stack (save old value)
|
|
ld (stksav),sp ; save stack
|
|
ld sp,stack ; set new stack
|
|
; processing...
|
|
call main ; do the real work
|
|
call crlf ; formatting
|
|
; return
|
|
jp 0 ; return to CP/M via reset
|
|
;
|
|
;ld sp,(stksav) ; restore stack
|
|
;ret ; return to CP/M w/o reset
|
|
;
|
|
; Main routine
|
|
;
|
|
main:
|
|
call init ; initialize
|
|
ret nz ; abort on failure
|
|
|
|
call parse ; parse command tail
|
|
ret nz ; abort on failure
|
|
|
|
call confirm ; confirm pending action
|
|
ret nz ; abort on failure
|
|
|
|
call crlf ; formatting
|
|
|
|
ld de,msgrd
|
|
call prtstr ; display "reading" message
|
|
call rdimg ; do the image read
|
|
ret nz ; abort on failure
|
|
|
|
ld de,msgwrt
|
|
call prtstr ; display "writing" message
|
|
call wrtimg ; do the image write
|
|
ret nz ; abort on failure
|
|
|
|
ld de,msgdon ; completion message
|
|
call prtstr ; display it
|
|
|
|
ret
|
|
;
|
|
; Initialization
|
|
;
|
|
init:
|
|
; add check for RomWBW?
|
|
;
|
|
; get OS version
|
|
ld c,12 ; BDOS get os version
|
|
call bdos ; do it, L=version
|
|
cp $30 ; Test for v3.0
|
|
jr c,init1 ; if <, pre v3.0
|
|
ld a,true ; OS v3.0 or above
|
|
ld (v3os),a ; save it
|
|
jr init2
|
|
init1:
|
|
ld a,false ; OS < v3.0
|
|
ld (v3os),a ; save it
|
|
init2:
|
|
; locate cbios function table address
|
|
ld hl,(restart+1) ; load address of CP/M restart vector
|
|
ld de,-3 ; adjustment for start of table
|
|
add hl,de ; HL now has start of table
|
|
ld (cbftbl),hl ; save it
|
|
; save current drive no
|
|
ld c,$19 ; bdos func: get current drive
|
|
call bdos ; invoke BDOS function
|
|
inc a ; 1-based index for fcb
|
|
ld (defdrv),a ; save it
|
|
; print version banner
|
|
call crlf ; formatting
|
|
ld de,msgban1 ; point to version message part 1
|
|
call prtstr ; print it
|
|
ld a,(v3os) ; get OS version flag
|
|
or a ; set flags
|
|
ld de,msgv2 ; point to V2 mode message
|
|
call z,prtstr ; if V2, say so
|
|
ld de,msgv3 ; point to V3 mode message
|
|
call nz,prtstr ; if V3, say so
|
|
call crlf ; formatting
|
|
ld de,msgban2 ; point to version message part 2
|
|
call prtstr ; print it
|
|
call crlf ; formatting
|
|
; return success
|
|
xor a
|
|
ret
|
|
;
|
|
; Parse command tail
|
|
;
|
|
parse:
|
|
ld hl,$81 ; point to start of command tail (after length byte)
|
|
call nonblank ; locate start of parms
|
|
jp z,erruse ; no parms
|
|
ld de,destfcb ; point to destination fcb
|
|
call convert ; convert destination spec
|
|
jp nz,erramb ; Error, ambiguous file specification
|
|
call nonblank ; skip blanks
|
|
or a ; end of command tail (null)?
|
|
jr z,parse2 ; setup default source fcb
|
|
cp '=' ; std delimiter
|
|
jr z,parse1 ; valid delimiter, continue
|
|
cp '_' ; alt delimiter
|
|
jr z,parse1 ; valid delimiter, continue
|
|
jp errdlm ; invalid delimiter
|
|
parse1:
|
|
inc hl ; skip delimiter
|
|
call nonblank ; skip blanks
|
|
parse2:
|
|
ld de,srcfcb ; point to source fcb
|
|
call convert ; convert spec to fcb
|
|
jp nz,erramb ; Error, ambiguous file specification
|
|
; return success
|
|
xor a ; signal success
|
|
ret ; done parsing
|
|
;
|
|
; Confirm pending action with user
|
|
;
|
|
confirm:
|
|
; prompt
|
|
call crlf
|
|
ld de,sconf1
|
|
call prtstr
|
|
ld hl,srcfcb
|
|
call prtfcb
|
|
ld de,sconf2
|
|
call prtstr
|
|
ld hl,destfcb
|
|
call prtfcb
|
|
ld de,sconf3
|
|
call prtstr
|
|
;
|
|
; get input (imgbuf is used for temp storage)
|
|
ld c,$0A ; get console buffer
|
|
ld de,imgbuf ; into buf
|
|
ld a,1 ; max of 1 character
|
|
ld (de),a ; set up buffer
|
|
call bdos ; invoke BDOS
|
|
ld a,(imgbuf+1) ; get num chars entered
|
|
dec a ; check that we got exactly one char
|
|
jr nz,confirm ; bad input, re-prompt
|
|
ld a,(imgbuf+2) ; get the character
|
|
and $DF ; force upper case
|
|
cp 'Y' ; compare to Y
|
|
ret ; return with Z set appropriately
|
|
;
|
|
; Read system image
|
|
;
|
|
rdimg:
|
|
ld hl,srcfcb ; point to source fcb
|
|
call chkfcb ; check if for drive/file spec
|
|
bit 1,a ; is there a file spec?
|
|
jp nz,rdfil ; yes, read using file i/o
|
|
jp rddsk ; no, read using raw disk i/o
|
|
;
|
|
; Write system image
|
|
;
|
|
wrtimg:
|
|
ld hl,destfcb ; point to destination fcb
|
|
call chkfcb ; check it for drive/file spec
|
|
bit 1,a ; is there a file spec?
|
|
jp nz,wrfil ; yes, write using file i/o
|
|
jp wrdsk ; no, write using raw disk i/o
|
|
|
|
;
|
|
; Read system image from file system
|
|
;
|
|
rdfil:
|
|
; open the file
|
|
ld c,$0F ; bdos open file
|
|
ld de,srcfcb ; source fcb
|
|
ld (rwfcb),de ; save it
|
|
call bdos ; invoke bdos function
|
|
cp $FF ; $FF is error
|
|
jp z,errfil ; handle error condition
|
|
; read the header
|
|
ld a,$14 ; setup for bdos read sequential
|
|
ld (rwfun),a ; save bdos function
|
|
ld a,12 ; start with 1536 byte header (12 records)
|
|
ld (reccnt),a ; init record counter
|
|
ld hl,imgbuf ; start of buffer
|
|
ld (bufptr),hl ; init buffer pointer
|
|
call rwfil ; read the header
|
|
ret nz ; abort on error (no need to close file)
|
|
; check header and get image size
|
|
call chkhdr ; verifies marker & ver, hl = image size
|
|
ret nz ; abort on error (no need to close file)
|
|
ld b,7 ; right shift 7 bits to get 128 byte record count
|
|
rdfil1: srl h ; shift right msb
|
|
rr l ; shift lsb w/ carry from msb
|
|
djnz rdfil1 ; loop till done
|
|
ld a,l ; record count to a
|
|
ld (reccnt),a ; set remaining records to read
|
|
add a,12 ; add the header back
|
|
ld (imgsiz),a ; and save the total image size (in records)
|
|
call rwfil ; do it
|
|
ret nz ; abort on error
|
|
; return via close file
|
|
jp closefile ; close file
|
|
;
|
|
; Write system image to file system
|
|
;
|
|
wrfil:
|
|
; check for pre-existing target file
|
|
ld c,$11 ; bdos find first
|
|
ld de,destfcb ; destination fcb
|
|
call bdos
|
|
cp $FF ; check for error
|
|
jr z,wrfil1 ; not there, skip delete
|
|
; delete target file if it exists
|
|
ld c,$13 ; bdos delete
|
|
ld de,destfcb ; destination fcb
|
|
call bdos
|
|
cp $FF ; check return code
|
|
jp z,errdel ; handle error
|
|
wrfil1: ; create target file
|
|
ld c,$16 ; bdos create file
|
|
ld de,destfcb ; destination fcb
|
|
ld (rwfcb),de ; save it
|
|
call bdos
|
|
cp $FF ; check return code
|
|
jp z,errfil ; handle error
|
|
; write the image
|
|
ld a,$15 ; setup for bdos write sequential
|
|
ld (rwfun),a ; save bdos function
|
|
ld a,(imgsiz) ; number of records to write
|
|
ld (reccnt),a ; init record counter
|
|
ld hl,imgbuf ; start of buffer
|
|
ld (bufptr),hl ; init buffer pointer
|
|
call rwfil ; do it
|
|
ret nz ; abort on error
|
|
; return via close file
|
|
jp closefile ; close file
|
|
;
|
|
; Common routine to handle read/write for file system
|
|
;
|
|
rwfil:
|
|
ld c,$1A ; BDOS set dma
|
|
ld de,(bufptr) ; current buffer pointer
|
|
push de ; save pointer
|
|
call bdos ; do it
|
|
pop de ; recover pointer
|
|
ld hl,128 ; record length
|
|
add hl,de ; increment buffer pointer
|
|
ld (bufptr),hl ; save it
|
|
ld a,(rwfun) ; get the active function
|
|
ld c,a ; set it
|
|
ld de,(rwfcb) ; active fcb
|
|
call bdos ; do it
|
|
or a ; check return code
|
|
jp nz,errdos ; BDOS err
|
|
ld hl,reccnt ; point to record count
|
|
dec (hl) ; decrement record count
|
|
jr nz,rwfil ; loop till done
|
|
xor a ; signal success
|
|
ret ; done
|
|
;
|
|
; Close file
|
|
;
|
|
closefile:
|
|
ld c,$10 ; BDOS close file
|
|
ld de,(rwfcb) ; active fcb
|
|
call bdos ; do it
|
|
cp $FF ; $FF is error
|
|
jp z,errclo ; if error, handle it
|
|
xor a ; signal success
|
|
ret ; done
|
|
;
|
|
; Read image directly from disk system tracks using CBIOS
|
|
;
|
|
rddsk:
|
|
; force return to go through disk reset
|
|
ld hl,resdsk ; load address of reset disk routine
|
|
push hl ; and put it on the stack
|
|
; set drive for subsequent reads
|
|
ld a,(srcfcb) ; get the drive
|
|
dec a ; adjust for zero indexing
|
|
call setdsk ; setup disk
|
|
ret nz ; abort on error
|
|
; set function to read
|
|
ld a,13 ; CBIOS func 13: Read
|
|
ld (actfnc),a ; save it
|
|
; read the header
|
|
ld a,12 ; start with 1536 byte header (12 records)
|
|
ld (reccnt),a ; initialize record counter
|
|
call rwdsk ; read the header
|
|
ret nz ; abort on error
|
|
; check header and get image size
|
|
call chkhdr ; check integrity, HL = image size on return
|
|
ret nz ; abort on error
|
|
; convert image size to count of 128-byte records
|
|
ld b,7 ; right shift 7 bits to get 128 byte record count
|
|
rddsk1: srl h ; shift right msb
|
|
rr l ; shift lsb w/ carry from msb
|
|
djnz rddsk1 ; loop till done
|
|
; set the number of records pending to read
|
|
ld a,l ; record count to a
|
|
ld (reccnt),a ; set remaining records to read
|
|
; save the total image size (including header) for later
|
|
add a,12 ; add the header records back
|
|
ld (imgsiz),a ; and save the total image size (in records)
|
|
; read the remaining system image records
|
|
call rwdsk ; finish up
|
|
ret nz ; abort on error
|
|
; perform BDOS disk reset (critical since we mucked with CBIOS)
|
|
ld c,$0D ; BDOS reset disk
|
|
call bdos ; do it
|
|
; return
|
|
xor a ; signal success
|
|
ret ; done
|
|
;
|
|
; Write image directly to disk system tracks using CBIOS
|
|
;
|
|
wrdsk:
|
|
; force return to go through disk reset
|
|
ld hl,resdsk ; load address of reset disk routine
|
|
push hl ; and put it on the stack
|
|
; setup to read existing MBR
|
|
ld a,(destfcb) ; get the drive
|
|
dec a ; adjust for zero indexing
|
|
call setdsk ; setup disk
|
|
ret nz ; abort on error
|
|
ld hl,mbrbuf ; override to read
|
|
ld (bufptr),hl ; ... into MBR buffer
|
|
ld a,4 ; 4 records = 1 512 byte sector
|
|
ld (reccnt),a ; initialize record counter
|
|
; set function to read
|
|
ld a,13 ; CBIOS func 13: Read
|
|
ld (actfnc),a ; save it
|
|
; read the existing MBR into memory
|
|
call rwdsk ; read the sector
|
|
ret nz ; abort on error
|
|
; test for valid partition table ($55, $AA at offset $1FE)
|
|
ld hl,(mbrbuf+$1FE); HL := signature
|
|
ld a,$55 ; load expected value of first byte
|
|
cp l ; check for proper value
|
|
jr nz,wrdsk1 ; mismatch, ignore old partition table
|
|
ld a,$AA ; load expected value of second byte
|
|
cp h ; check for proper value
|
|
jr nz,wrdsk1 ; mismatch, ignore old partition table
|
|
; valid MBR, copy existing partition table over to new image
|
|
ld hl,mbrbuf+$1BE ; copy from MBR offset of existing MBR
|
|
ld de,imgbuf+$1BE ; copy to MBR offset of new image
|
|
ld bc,$40 ; size of MBR
|
|
ldir ; do it
|
|
wrdsk1: ; setup to write the image from memory to disk
|
|
ld a,(destfcb) ; get the drive
|
|
dec a ; adjust for zero indexing
|
|
call setdsk ; setup disk
|
|
ret nz ; abort on error
|
|
; set function to write
|
|
ld a,14 ; CBIOS func 14: Write
|
|
ld (actfnc),a ; save it
|
|
; setup the record count to write
|
|
ld a,(imgsiz) ; get previously recorded image size
|
|
ld (reccnt),a ; save it as pending record count
|
|
; write the image
|
|
call rwdsk ; write the image
|
|
ret nz ; abort on error
|
|
; return
|
|
xor a ; signal success
|
|
ret ; done
|
|
;
|
|
; Perform BDOS disk reset
|
|
; Required after making direct CBIOS disk calls
|
|
;
|
|
resdsk:
|
|
; perform BDOS disk reset
|
|
push af ; preserve status
|
|
ld c,$0D ; BDOS reset disk
|
|
call bdos ; do it
|
|
pop af ; restore status
|
|
ret
|
|
;
|
|
; Setup for CBIOS disk access
|
|
;
|
|
setdsk:
|
|
; select disk
|
|
ld (actdsk),a ; save active disk no
|
|
ld c,a ; move to c
|
|
ld e,0 ; treat as first select
|
|
call cbios ; invoke cbios with...
|
|
;.db $1B ; SELDSK entry offset
|
|
.db 9 ; SELDSK entry offset
|
|
; check return (sets HL to DPH address)
|
|
ld a,h
|
|
or l
|
|
jp z,errsel ; HL == 0 is select error
|
|
; set HL to DPB address
|
|
ld de,10 ; DPB address is 10 bytes into DPH
|
|
add hl,de ; HL := address of DPB pointer
|
|
ld a,(hl) ; dereference...
|
|
inc hl
|
|
ld h,(hl)
|
|
ld l,a ; HL := address of DPB
|
|
; extract sectors per track from first word of DPB
|
|
ld c,(hl)
|
|
inc hl
|
|
ld b,(hl) ; BC := sectors per track
|
|
ld (actspt),bc ; save it
|
|
; ensure there are system tracks (verify that offset field in DPB is not zero)
|
|
ld de,12 ; offset field is 12 bytes into DPB
|
|
add hl,de ; point to offset field in DPB
|
|
ld a,(hl) ; load first byte in A
|
|
inc hl ; point to second byte
|
|
or (hl) ; or with first byte
|
|
jp z,errsys ; if zero, abort (no system tracks)
|
|
; initialize for I/O
|
|
ld hl,0
|
|
ld (acttrk),hl ; active track := 0
|
|
ld (actsec),hl ; active sector := 0
|
|
ld hl,imgbuf ; assume r/w to image buffer
|
|
ld (bufptr),hl ; reset buffer pointer
|
|
;
|
|
xor a ; signal success
|
|
ret ; done
|
|
;
|
|
; Read or write (reccnt) sectors to/from disk via CBIOS
|
|
;
|
|
rwdsk:
|
|
ld hl,128 ; assume rec len for < CP/M 3
|
|
ld (reclen),hl ; and save it
|
|
ld a,(v3os) ; CP/M 3 or greater?
|
|
or a ; set flags
|
|
jr z,rwdsk0 ; if not, continue
|
|
; adjust reccnt, logical (128) to physical (512)
|
|
ld a,(reccnt) ; get pending rec cnt
|
|
add a,3 ; round up
|
|
srl a ; shift to
|
|
srl a ; ... divide by 4
|
|
ld (reccnt),a ; and resave it
|
|
ld hl,512 ; use physical rec len
|
|
ld (reclen),hl ; and save it
|
|
rwdsk0:
|
|
; setup to read/write a sector
|
|
ld bc,(acttrk) ; get active track
|
|
call cbios ; invoke cbios with...
|
|
;.db $1E ; SETTRK entry offset
|
|
.db 10 ; SETTRK entry offset
|
|
ld bc,(actsec) ; get active sector
|
|
call cbios ; invoke cbios with...
|
|
;.db $21 ; SETSEC entry offset
|
|
.db 11 ; SETSEC entry offset
|
|
ld bc,(bufptr) ; get active buffer pointer
|
|
call cbios ; invoke cbios with...
|
|
;.db $24 ; SETDMA entry offset
|
|
.db 12 ; SETDMA entry offset
|
|
; read/write sector
|
|
ld a,(reccnt) ; get the pending record count
|
|
dec a ; last record?
|
|
ld c,2 ; allow cached writes by default
|
|
jr nz,rwdsk1 ; not last record, continue
|
|
ld c,1 ; last record, no caching please
|
|
rwdsk1:
|
|
ld a,(actfnc)
|
|
call cbiosfn
|
|
or a ; set flags on return code
|
|
jp nz,errio ; if not zero, error abort
|
|
; adjust buffer pointer
|
|
ld hl,(bufptr) ; get buffer pointer
|
|
ld de,(reclen) ; get rec len
|
|
add hl,de ; adjust buffer ptr for next record
|
|
ld (bufptr),hl ; save it
|
|
; next sector
|
|
ld hl,(actsec) ; current sector
|
|
inc hl ; increment sector
|
|
ld (actsec),hl ; save it
|
|
; check for end of track
|
|
ld de,(actspt) ; get current sectors per track
|
|
or a ; clear CF
|
|
sbc hl,de ; current track == sectors per track?
|
|
jr nz,rwdsk2 ; no, skip track change
|
|
; next track
|
|
ld hl,0
|
|
ld (actsec),hl ; current sector := 0
|
|
ld hl,acttrk ; point to track variable
|
|
inc (hl) ; increment track
|
|
; check pending record count and loop or return
|
|
rwdsk2: ld hl,reccnt
|
|
dec (hl) ; decrement pending record count
|
|
ret z ; if zero, done, return with Z set
|
|
jr rwdsk0 ; otherwise, loop
|
|
;
|
|
jphl: jp (hl) ; indirect jump
|
|
;
|
|
; Verify system image header in buf by checking the expected signature.
|
|
; Compute and return image size (based on header values) in HL.
|
|
; NZ set if signature error.
|
|
;
|
|
chkhdr:
|
|
; check signature
|
|
ld hl,(imgbuf+$580) ; get signature
|
|
ld de,$A55A ; signature value
|
|
or a ; clear CF
|
|
sbc hl,de ; compare
|
|
jp nz,errsig ; invalid signature
|
|
; compute the image size (does not include size of header)
|
|
ld hl,(imgbuf+$5FC) ; get CPM_END
|
|
ld de,(imgbuf+$5FA) ; get CPM_LOC
|
|
or a ; clear CF
|
|
sbc hl,de ; image size := CPM_END - CPM_LOC
|
|
xor a ; signal success
|
|
ret ; done
|
|
;
|
|
; Convert a filename at (HL) into an FCB at (DE).
|
|
; Includes wildcard expansion.
|
|
; On return, A=0 if unambiguous name specified, and
|
|
; (HL) points to character following filename spec
|
|
;
|
|
convert:
|
|
push de ; put fcb address on stack
|
|
ex de,hl
|
|
ld a,(de) ; get first character.
|
|
or a
|
|
jp z,convrt1
|
|
sbc a,'A'-1 ; might be a drive name, convert to binary.
|
|
ld b,a ; and save.
|
|
inc de ; check next character for a ':'.
|
|
ld a,(de)
|
|
cp ':'
|
|
jp z,convrt2
|
|
dec de ; nope, move pointer back to the start of the line.
|
|
convrt1:
|
|
ld a,(defdrv)
|
|
ld (hl),a
|
|
jp convrt3
|
|
convrt2:
|
|
ld a,b
|
|
ld (hl),b
|
|
inc de
|
|
; Convert the base file name.
|
|
convrt3:ld b,08h
|
|
convrt4:ld a,(de)
|
|
call delim
|
|
jp z,convrt8
|
|
inc hl
|
|
cp '*' ; note that an '*' will fill the remaining
|
|
jp nz,convrt5 ; field with '?'
|
|
ld (hl),'?'
|
|
jp convrt6
|
|
convrt5:ld (hl),a
|
|
inc de
|
|
convrt6:dec b
|
|
jp nz,convrt4
|
|
convrt7:ld a,(de)
|
|
call delim ; get next delimiter
|
|
jp z,getext
|
|
inc de
|
|
jp convrt7
|
|
convrt8:inc hl ; blank fill the file name
|
|
ld (hl),' '
|
|
dec b
|
|
jp nz,convrt8
|
|
getext: ld b,03h
|
|
cp '.'
|
|
jp nz,getext5
|
|
inc de
|
|
getext1:ld a,(de)
|
|
call delim
|
|
jp z,getext5
|
|
inc hl
|
|
cp '*'
|
|
jp nz,getext2
|
|
ld (hl),'?'
|
|
jp getext3
|
|
getext2:ld (hl),a
|
|
inc de
|
|
getext3:dec b
|
|
jp nz,getext1
|
|
getext4:ld a,(de)
|
|
call delim
|
|
jp z,getext6
|
|
inc de
|
|
jp getext4
|
|
getext5:inc hl
|
|
ld (hl),' '
|
|
dec b
|
|
jp nz,getext5
|
|
getext6:ld b,3
|
|
getext7:inc hl
|
|
ld (hl),0
|
|
dec b
|
|
jp nz,getext7
|
|
pop hl ; HL := start of FCB
|
|
push de ; save input line pointer
|
|
; Check to see if this is an ambiguous file name specification.
|
|
; Set the A register to non-zero if it is.
|
|
ld bc,11 ; set name length.
|
|
getext8:inc hl
|
|
ld a,(hl)
|
|
cp '?' ; any question marks?
|
|
jp nz,getext9
|
|
inc b ; count them.
|
|
getext9:dec c
|
|
jp nz,getext8
|
|
ld a,b
|
|
or a
|
|
pop hl ; return with updated input pointer
|
|
ret
|
|
;
|
|
; Print formatted FCB at (HL)
|
|
;
|
|
prtfcb:
|
|
push hl ; save HL
|
|
call chkfcb ; set flags indicating nature of FCB
|
|
pop hl ; restore HL
|
|
ret z ; nothing to print
|
|
push af ; save FCB flags
|
|
ld a,(hl) ; get first byte of FCB (drive)
|
|
inc hl ; point to next char
|
|
or a ; is drive specified (non-zero)?
|
|
jr z,prtfcb1 ; if zero, do not print drive letter
|
|
add a,'@' ; adjust drive number to alpha
|
|
call prtchr ; print it
|
|
ld a,':'
|
|
call prtchr ; print drive separator
|
|
prtfcb1:
|
|
pop af ; restore FCB flags
|
|
bit 1,a ; bit 1 set if filename specified
|
|
ret z ; return if no filename
|
|
ld b,8 ; base is 8 characters
|
|
call prtfcb2 ; print them
|
|
ld a,'.'
|
|
call prtchr ; print file extension separator
|
|
ld b,3 ; extension is 3 characters
|
|
prtfcb2:
|
|
ld a,(hl) ; load the next character
|
|
inc hl ; point to next character
|
|
cp ' ' ; check for blank
|
|
call nz,prtchr ; print char if it is not a blank
|
|
djnz prtfcb2 ; loop till done
|
|
ret ; return
|
|
;
|
|
; Check FCB to see if a drive and/or filename is specified.
|
|
; Set bit 0 for drive and bit 1 for filename in A
|
|
;
|
|
chkfcb:
|
|
ld c,0 ; use C for flags, start with none
|
|
ld a,(hl) ; get drive
|
|
or a ; anything there?
|
|
jr z,chkfcb1 ; skip if nothing there
|
|
set 0,c ; set bit zero to indicate a drive spec
|
|
chkfcb1:
|
|
ld b,11 ; set up to check 11 bytes (base & ext)
|
|
chkfcb2:
|
|
inc hl ; bump to next byte
|
|
ld a,(hl) ; get next
|
|
cp 'A' ; blank means empty byte
|
|
jr nc,chkfcb3 ; if not blank, we have a filename
|
|
djnz chkfcb2 ; loop
|
|
jr chkfcb4 ; nothing there
|
|
chkfcb3:
|
|
set 1,c ; set bit 1 to indicate a file spec
|
|
chkfcb4:
|
|
ld a,c ; put result in a
|
|
or a ; set flags
|
|
ret
|
|
;
|
|
; Print dot
|
|
;
|
|
prtdot:
|
|
push af
|
|
ld a,'.'
|
|
call prtchr
|
|
pop af
|
|
ret
|
|
;
|
|
; Print character in A without destroying any registers
|
|
;
|
|
prtchr:
|
|
push bc ; save registers
|
|
push de
|
|
push hl
|
|
ld e,a ; character to print in E
|
|
ld c,$02 ; BDOS function to output a character
|
|
call bdos ; do it
|
|
pop hl ; restore registers
|
|
pop de
|
|
pop bc
|
|
ret
|
|
;
|
|
; Print $ terminated string at (DE) without destroying any registers
|
|
;
|
|
prtstr:
|
|
push bc ; save registers
|
|
push de
|
|
push hl
|
|
ld c,$09 ; BDOS function to output a '$' terminated string
|
|
call bdos ; do it
|
|
pop hl ; restore registers
|
|
pop de
|
|
pop bc
|
|
ret
|
|
;
|
|
; Print the value in A in hex without destroying any registers
|
|
;
|
|
prthex:
|
|
push af ; save AF
|
|
push de ; save DE
|
|
call hexascii ; convert value in A to hex chars in DE
|
|
ld a,d ; get the high order hex char
|
|
call prtchr ; print it
|
|
ld a,e ; get the low order hex char
|
|
call prtchr ; print it
|
|
pop de ; restore DE
|
|
pop af ; restore AF
|
|
ret ; done
|
|
;
|
|
; Convert binary value in A to ascii hex characters in DE
|
|
;
|
|
hexascii:
|
|
ld d,a ; save A in D
|
|
call hexconv ; convert low nibble of A to hex
|
|
ld e,a ; save it in E
|
|
ld a,d ; get original value back
|
|
rlca ; rotate high order nibble to low bits
|
|
rlca
|
|
rlca
|
|
rlca
|
|
call hexconv ; convert nibble
|
|
ld d,a ; save it in D
|
|
ret ; done
|
|
;
|
|
; Convert low nibble of A to ascii hex
|
|
;
|
|
hexconv:
|
|
and $0F ; low nibble only
|
|
add a,$90
|
|
daa
|
|
adc a,$40
|
|
daa
|
|
ret
|
|
;
|
|
; Start a new line
|
|
;
|
|
crlf:
|
|
ld a,13 ; <CR>
|
|
call prtchr ; print it
|
|
ld a,10 ; <LF>
|
|
jr prtchr ; print it
|
|
;
|
|
; Get the next non-blank character from (HL).
|
|
;
|
|
nonblank:
|
|
ld a,(hl) ; load next character
|
|
or a ; string ends with a null
|
|
ret z ; if null, return pointing to null
|
|
cp ' ' ; check for blank
|
|
ret nz ; return if not blank
|
|
inc hl ; if blank, increment character pointer
|
|
jr nonblank ; and loop
|
|
;
|
|
; Check character at (DE) for delimiter.
|
|
;
|
|
delim: or a
|
|
ret z
|
|
cp ' ' ; blank
|
|
ret z
|
|
jr c,delim1 ; handle control characters
|
|
cp '=' ; equal
|
|
ret z
|
|
cp '_' ; underscore
|
|
ret z
|
|
cp '.' ; period
|
|
ret z
|
|
cp ':' ; colon
|
|
ret z
|
|
cp $3b ; semicolon
|
|
ret z
|
|
cp '<' ; less than
|
|
ret z
|
|
cp '>' ; greater than
|
|
ret
|
|
delim1:
|
|
; treat control chars as delimiters
|
|
xor a ; set Z
|
|
ret ; return
|
|
;
|
|
; Invoke CBIOS function
|
|
; The CBIOS function offset must be stored in the byte
|
|
; following the call instruction. ex:
|
|
; call cbios
|
|
; .db $0C ; offset of CONOUT CBIOS function
|
|
;
|
|
cbios:
|
|
ex (sp),hl
|
|
ld a,(hl) ; get the function number
|
|
inc hl ; point past value following call instruction
|
|
ex (sp),hl ; put address back at top of stack and recover HL
|
|
|
|
cbiosfn:
|
|
; enter here if function already in reg A
|
|
ld (bpb_fn),a ; save function
|
|
;
|
|
ld a,(v3os) ; CP/M 3 or greater?
|
|
or a ; set flags
|
|
jr nz,cbios2 ; if >= V3, handle it
|
|
;
|
|
; CBIOS call for CP/M < v3
|
|
ld a,(bpb_fn) ; get pending function number
|
|
ld l,a ; function number to L
|
|
add a,l ; ... and multiply by 3 for
|
|
add a,l ; ... jump table offset
|
|
ld hl,(cbftbl) ; address of CBIOS function table to HL
|
|
call addhl ; determine specific function address
|
|
jp (hl) ; invoke CBIOS
|
|
;
|
|
cbios2:
|
|
; CBIOS call for CP/M v3 or greater
|
|
ld (bpb_bc),bc
|
|
ld (bpb_de),de
|
|
ld (bpb_hl),hl
|
|
|
|
ld c,50 ; direct bios call function number
|
|
ld de,bpb ; BIOS parameter block
|
|
jp bdos ; return via BDOS call
|
|
;
|
|
; Add the value in A to HL (HL := HL + A)
|
|
;
|
|
addhl:
|
|
add a,l ; A := A + L
|
|
ld l,a ; Put result back in L
|
|
ret nc ; if no carry, we are done
|
|
inc h ; if carry, increment H
|
|
ret ; and return
|
|
;
|
|
; Errors
|
|
;
|
|
erruse: ; command usage error (syntax)
|
|
ld de,msguse
|
|
jr err
|
|
erramb: ; ambiguous file spec (wild cards) is not allowed
|
|
ld de,msgamb
|
|
jr err
|
|
errdlm: ; invalid delimiter in command tail
|
|
ld de,msgdlm
|
|
jr err
|
|
errfil: ; source file not found
|
|
ld de,msgfil
|
|
jr err
|
|
errclo: ; file close error
|
|
ld de,msgclo
|
|
jr err
|
|
errdel: ; file delete error
|
|
ld de,msgdel
|
|
jr err
|
|
errsig: ; invalid system image signature error
|
|
ld de,msgsig
|
|
jr err
|
|
errsel: ; CBIOS drive select error
|
|
ld de,msgsel
|
|
jr err
|
|
errsys: ; no system tracks on drive error
|
|
ld de,msgsys
|
|
jr err
|
|
errio: ; I/O error
|
|
ld de,msgio
|
|
jr err
|
|
err: ; print error string and return error signal
|
|
call crlf ; print newline
|
|
call prtstr ; print error string
|
|
or $FF ; signal error
|
|
ret ; done
|
|
errdos: ; handle BDOS errors
|
|
push af ; save return code
|
|
call crlf ; newline
|
|
ld de,msgdos ; load
|
|
call prtstr ; and print error string
|
|
pop af ; recover return code
|
|
call prthex ; print error code
|
|
or $FF ; signal error
|
|
ret ; done
|
|
;
|
|
;===============================================================================
|
|
; Storage Section
|
|
;===============================================================================
|
|
;
|
|
defdrv .db 0 ; default drive for FCB
|
|
cbftbl .dw 0 ; address of CBIOS function table
|
|
imgsiz .db 0 ; image size (count of 128 byte records)
|
|
;
|
|
destfcb .fill 36,0 ; destination FCB
|
|
srcfcb .fill 36,0 ; source FCB
|
|
;
|
|
stksav .dw 0 ; stack pointer saved at start
|
|
.fill stksiz,0 ; stack
|
|
stack .equ $ ; stack top
|
|
;
|
|
rwfun .db 0 ; active read/write function
|
|
rwfcb .dw 0 ; active read/write FCB
|
|
reccnt .db 0 ; active remaining records to read/write
|
|
bufptr .dw 0 ; active pointer into buffer
|
|
;
|
|
actdsk .db 0 ; active disk no
|
|
acttrk .dw 0 ; active track
|
|
actsec .dw 0 ; active sector
|
|
actspt .dw 0 ; active sectors per track
|
|
actfnc .db 0 ; active cbios i/o function (read or write)
|
|
v3os .db 0 ; true ($FF) if OS v3.0 or greater
|
|
reclen .dw 0 ; active record length
|
|
;
|
|
bpb: ; BIOS parameter block for CP/M 3 BIOS calls
|
|
bpb_fn .db 0 ; function
|
|
bpb_a .db 0 ; reg A
|
|
bpb_bc .dw 0 ; reg BC
|
|
bpb_de .dw 0 ; reg DE
|
|
bpb_hl .dw 0 ; reg HL
|
|
;
|
|
; Messages
|
|
;
|
|
msgban1 .db "SYSCOPY v2.0 for RomWBW CP/M, 17-Feb-2020$"
|
|
msgv2 .db " (CP/M 2 Mode)$"
|
|
msgv3 .db " (CP/M 3 Mode)$"
|
|
msgban2 .db "Copyright 2020, Wayne Warthen, GNU GPL v3$"
|
|
|
|
msguse .db "Usage: SYSCOPY <dest>[=<source>]$"
|
|
msgamb .db "Ambiguous file specification not allowed$"
|
|
msgdlm .db "Invalid delimiter$"
|
|
msgfil .db "File not found$"
|
|
msgclo .db "File close error$"
|
|
msgdel .db "Error deleting target file$"
|
|
msgsig .db "Invalid system image (bad signature)$"
|
|
msgdos .db "DOS error, return code=0x$"
|
|
msgsel .db "Disk select error$"
|
|
msgsys .db "Non-system disk error$"
|
|
msgio .db "Disk I/O error$"
|
|
msgrd .db "Reading image... $"
|
|
msgwrt .db "Writing image... $"
|
|
msgdon .db "Done$"
|
|
sconf1 .db "Transfer system image from $"
|
|
sconf2 .db " to $"
|
|
sconf3 .db " (Y/N)? $"
|
|
;
|
|
.end
|
|
|