;=============================================================================== ; SysCopy - Copy System Image to/from reserved tracks of disk for RomWBW ; adaptation of CP/M 2.2 ;=============================================================================== ; ; Author: Wayne Warthen (wwarthen@gmail.com) ;_______________________________________________________________________________ ; ; Usage: ; SYSCOPY [=] ; ; and may be a drive or a file reference ; If 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 ;_______________________________________________________________________________ ; ; ToDo: ; 1) Add option to wait/prompt for disk change ; 2) Allow and to be memory ;_______________________________________________________________________________ ; ;=============================================================================== ; Definitions ;=============================================================================== ; 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? ; ; 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 ; 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 read 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 hl,(cbftbl) ; get address of CBIOS function table ld a,$27 ; $27 is CBIOS READ entry offset call addhl ; set HL to resultant entry point ld (actfnc),hl ; 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 ; set function to read ld hl,(cbftbl) ; get address of CBIOS function table ld a,$27 ; $27 is CBIOS READ entry offset call addhl ; set HL to resultant entry point ld (actfnc),hl ; 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 hl,(cbftbl) ; get address of CBIOS function table ld a,$2A ; $2A is CBIOS WRITE entry offset call addhl ; set HL to resultant entry point ld (actfnc),hl ; 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 ; 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: ; setup to read/write a sector ld bc,(acttrk) ; get active track call cbios ; invoke cbios with... .db $1E ; SETTRK entry offset ld bc,(actsec) ; get active sector call cbios ; invoke cbios with... .db $21 ; SETSEC entry offset ld bc,(bufptr) ; get active buffer pointer call cbios ; invoke cbios with... .db $24 ; 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 hl,(actfnc) ; load the CBIOS function vector call jphl ; indirect call (read or write) 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,128 ; record length is 128 bytes 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 rwdsk ; 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 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 ; call prtchr ; print it ld a,10 ; 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 offset inc hl ; point past value following call instruction ex (sp),hl ; put address back at top of stack and recover HL ld hl,(cbftbl) ; address of CBIOS function table to HL call addhl ; determine specific function address jp (hl) ; invoke CBIOS ; ; 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 .dw 0 ; active function (read or write) ; ; Messages ; msguse .db "Usage: SYSCOPY [=]$" 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