; UNERASE.Z80 ; ; Recovers erased files for ZCPR3 ; vers equ 14 ; version number ; ; Version 1.4 -- April 4, 1991 -- Howard Goldstein ; Modified to skip the initial FRESET call if the target drive can be ; determined to be a fixed disk. This eliminates the long delay when ; the program starts up. User number is no longer displayed in List ; mode. The code has been cleaned up and reorganized in several places. ; ; Version 1.3 -- March 26, 1991 -- Gene Pizzetta ; Did not properly relog hard drives or RAM disks under ZRDOS or ZSDOS. ; Incorporated Bridger Mitchell's FRESET routine, to correctly relog ; drives on all systems. Removed internal environment. Converted to ; Zilog mnemonics. Option "Z" (recover files to user 0) eliminated; now ; recovers files to default user, or to user given in DU spec. Screen ; now pages if more than screenful of matching erased files are found. ; "Pause for disk change" option changed to "C"; "P" now controls paging. ; Options "C" and "P" can be configured as defaults. Wildcard filenames ; now sets "L" option for listing names of erase files; actual unerasing ; requires unambiguous filenames in the file list. This method can ; be changed to allow unerasing wildcard filenames (kind of dangerous) ; by a configuration option. Configuration by ZCNFG. (Paging is always ; off when files are actually being recovered, for safety reasons.) ; Now aborts if not ZCPR3 or if CP/M-Plus. ; ; Following bugs fixed: (1) Disk change routine didn't work right ; (the disk parameters were being read before the routine was called), ; (2) giving drive on command line didn't always work, (3) illegal ; drive spec detection never worked (now works for ZCPR33 or higher), ; (3) printed filename more than once if file extended for more than ; one data module (S2), (4) read wrong disk parameter block if drive ; allowed disks with more than one format and disk wasn't last one ; read (now resets drive unconditionally before reading DPB, so we ; always get the correct DPB). ; ; Version 1.2 -- September 25, 1985 -- Roger Warren ; Found and removed two more subtle nasties related to BIOS calls: ; (1) call to WRITE did not specify type, which could cause data to be ; lost; (2) call to SELDSK did not set-up E register (new mount flag). ; ; Version 1.1 -- September 20, 1985 -- Roger Warren ; Removed 2 MAJOR bugs: (1) wrong disk may be selected; (2) incorrect ; use of BIOS "SECTRAN". ; ; Version 1.0 -- May 18, 1984 -- Rick Conn ; Modified to be consistent in the ZCPR3 tool set. ; ; The following are ZCPR2 predecessors: ; ; Version 2.2 -- July 25, 1983 -- Rick Conn ; UNERA for ZCPR2. ; ; Version 2.0 -- July 23, 1983 -- Rick Conn ; UNERA for ZCPR2. Modified to be consistent in the ZCPR2 tool set. ; ; The following are non-ZCPR predecessors: ; ; Contributors: ; Version 1.6 - Dave Rand ; Version 1.5 - Irv Hoff ; Version 1.4 - Paul Traina ; Version 1.3 - Irv Hoff ; Version 1.2 - Charlie Strom ; Version 1.1 - Bruce Blakeslee ; Version 1.0 - Henry Rothberg: Retyped from Interface Age (Dec 81). ; ; Derivation: UNERA, "Program to Recover Erased Files" by Gene Cotton, ; Interface Age, December 1981, page 146. ; ; SYSLIB and Z3LIB routines ; .request z3lib,syslib ; extrn z3init,getcrt,prtname extrn codend,eprint,cout,cin,crlf,pafdc,bios extrn freset ; ; System equates: ; wboot equ 00h ; warm boot bdos equ 05h ; BDOS vector tbuff equ 80h ; default DMA buffer cpmfcb equ 5Ch ; default file control block ; ; BDOS functions ; cpmver equ 12 ; get CP/M version seldsk equ 14 ; select disk curdsk equ 25 ; get current disk getdpb equ 31 ; get DPB address fdvec equ 39 ; return fixed-disk vector ; ; BIOS offsets ; sdisk equ 9 ; select disk sttrk equ 10 ; set track stsec equ 11 ; set sector read equ 13 ; read sector write equ 14 ; write sector sectrn equ 16 ; translate sector ; ; ASCII ; bel equ 07h ; bell cr equ 0Dh ; carriage return lf equ 0Ah ; line feed ctrlc equ 03h ; ^C ; ; Environment Definition ; jp start db 'Z3ENV' ; this is a ZCPR3 utility db 1 ; external environment descriptor z3eadr: dw 0 ; ; Configuration area ; dw 0 ; filler db 'UNERAS' ; for ZCNFG db vers/10+'0',vers mod 10+'0' wildfl: db 0FFh ; 0FFh=wildcard sets L option pagefl: db 0FFh ; 0FFh=screen paging pausfl: db 0 ; FFh=always pause for disk change ; ; Start of program -- Initialize ZCPR3 environment ; start: ld hl,(z3eadr) ; point to ZCPR3 environment ld a,h ; quick and dirty check for ZCPR3 or l jr nz,start1 ; (we're okay) ld de,notz3 ; we've got the wrong guy ld c,9 jp bdos ; print message and go home notz3: db bel,'ZCPR3 required.$' ; start1: call z3init ; initialize the ZCPR3 env and the VLIB env ld (stack),sp ; store old stack pointer ld sp,stack ; set up new stack ld c,cpmver ; check for Z3PLUS call bdos cp 30h jr c,start2 ; (we're still okay) call eprint ; we already know it's a Z-system db bel,'Not for Z3Plus.',0 jr exit ; start2: call codend ; determine free space ld (fntab),hl ; set pointer to filename table call helpck ; check for and print help message call pcheck ; check parameters ld a,(fncnt) ; number of files specified or a ; 0=none call nz,tryfix ; do the recovery ; ; Sign off and reset system, if needed. ; ld a,(listfl) ; list only? or a ; 0=no jr nz,prnf ld a,(fixcnt) ; check for activity or a jr z,prnf ; say none found call eprint db ' Double check before using.',0 reset: ld a,(disk) ; get drive into A call freset ; reset drive (or system) jr exit ; prnf: call eprint db ' No files recovered.',0 ; exit: ld sp,(stack) ; quiet return ret ; ; ** Main routines ** ; ; Check for valid parameters and say which CP/M version ; pcheck: call optchk ; check for options and set flags call fcbchk ; get current user and target drive call puschk ; check if user wants to change disk call cpmchk ; establish CP/M parameters ret ; ; Look through directory ; tryfix: call nxtsec ; get a directory sector ret z ; returns zero flag if no more call chkent ; check it out and maybe fix jr tryfix ; keep it up till done ; ; Checks for P and L options in command line ; optchk: ld a,(pausfl) ; set default for disk pause ld (pause),a ld a,(pagefl) ; set default for screen paging ld (page),a xor a ; turn off other flags ld (fixcnt),a ld (listfl),a ld (fncnt),a ; no filenames call getcrt ; get console data inc hl ; point to number of lines ld a,(hl) ld (maxlin),a ; store maximum lines dec a dec a ld (lincnt),a ; initialize line counter ld hl,1 ; set sector 1 ld (sector),hl ld hl,tbuff+1 ; scan thru tbuff, building a filename table ld de,(fntab) ; point to table call sblank ; skip blanks fnloop: call getfn ; extract filename ld a,(fncnt) ; increment count inc a ld (fncnt),a ld a,(hl) ; get terminating character inc hl ; point to next cp ',' ; another follows? jr z,fnloop dec hl ; point back to delimiter call sblank ; skip to non-blank cp '/' ; check for slash jr nz,optck1 ; (nope) optck0: inc hl ; skip to next character optck1: ld a,(hl) ; get option call delchk ; done if delimiter ret z cp 'C' ; pause? jr z,optckc cp 'L' ; list only? jr z,optckl cp 'P' ; page? jr z,optckp call eprint db bel,' Invalid option: ',0 ld a,(hl) call cout call crlf jp usage ; optckl: ld a,0FFh ; set flag ld (listfl),a jr optck0 ; optckc: ld a,(pause) ; toggle disk pause flag cpl ld (pause),a jr optck0 ; optckp: ld a,(page) ; toggle disk pause flag cpl ld (page),a jr optck0 ; getfn: call scncol ; scan for colon ld b,8 ; 8 characters maximum call getfn1 ; get and fill entry ld b,3 ; prepare for type (3 characters) ld a,(hl) ; get character cp '.' ; delimiter? jr nz,getfn1 inc hl ; point to after period getfn1: ld a,(hl) ; get character call delchk ; check delimiter jr z,getfn3 cp '?' ; wild? call z,setlfl ; (yes, set list only flag) cp '*' ; wild? jr z,getfnq ; (yes, fill with "?") ld (de),a ; store character inc hl ; point to next inc de djnz getfn1 getfn2: ld a,(hl) ; flush characters to delimiter call delchk ; check for delimiter ret z inc hl ; point to next jr getfn2 ; getfn3: ld a,' ' ; get a space (fill character getfn4: ld (de),a ; put space inc de djnz getfn4 ret ; getfnq: ld a,'?' ; fill with question marks ld (de),a inc de djnz getfnq call setlfl ; set list only flag jr getfn2 ; skip to delimiter ; setlfl: push af ; set list flag ld a,(wildfl) or a jr z,setlf1 ld (listfl),a setlf1: pop af ret ; delchk: or a ; end of line? ret z cp '.' ; end of field? ret z cp ',' ; end of entry? ret z cp ' ' ret ; sblank: ld a,(hl) ; skip to non-blank cp ' ' ret nz inc hl jr sblank ; scncol: push hl ; save pointer scol1: ld a,(hl) ; get character inc hl ; point to next cp ':' ; colon? jr z,scolx call delchk ; check for delimiter jr nz,scol1 scol2: pop hl ; restore ret ; scolx: ex (sp),hl ; pointer to after colon on stack pop hl ; pointer to HL and clean stack ret ; ; Checks the current 4 directory entries against argument. ; If match, rewrites sector with reactivated 1st bytes. ; chkent: xor a ; assume no rewrite ld (rewrt),a ld b,4 ; number of entries per sector ld hl,tbuff ; beginning of buffer cklup: push bc ld a,(hl) cp 0E5h ; check for unused jr nz,ckinc inc hl ld a,(hl) ; get 1st character of filename dec hl cp 0E5h ; non-allocated entry? jr z,ckinc ld de,(fntab) ; point to potential files ld a,(fncnt) ; number of entries to count ld b,a ; ... in B cklup0: push hl ; save beginning address push de ; save pointer push bc ; set name count call compar ; compare with argument pop bc ; get name count pop de ; get pointer pop hl jr z,cklup1 ; match push hl ; save pointer ld hl,11 ; point to next entry add hl,de ex de,hl pop hl djnz cklup0 jr ckinc ; cklup1: ld a,(fixcnt) ; check flag or a ; 0=first time call z,prfix push hl call prtfcb pop hl ld a,(listfl) ; list only? or a ; 0=no jr nz,ckinc ld a,(user) ; get user ld (hl),a ; poke in user area ld a,0ffh ; say need rewrite ld (rewrt),a ld (fixcnt),a ckinc: pop bc ld de,32 ; length of entry add hl,de djnz cklup ld a,(rewrt) ; see if need rewrite or a jr z,ckdone ; no - done ; ; Write the directory sector back to the disk ; ld bc,(track) ; set track ld a,sttrk call bios ld bc,(sector) ; set sector call trnslt ld a,stsec call bios ; ;==================================================================== ; Fix # 1 for v1.2. A write type must be provided to BIOS in the C ; register to assure that the disk blocking works properly. A type ; of 1 indicates directory write, which causes the new sector to be ; output immediately (i.e., do not wait for a logical block change. ; Failure to provide this parameter may cause loss of data on blocked ; systems ; ld c,1 ; indicate directory write ;==================================================================== ld a,write ; write the sector back call bios or a jp nz,errwrt ; abort if error ckdone: ld hl,(dirmax) dec hl ; reduce sectors left ld (dirmax),hl ld hl,(sector) ; point to next sector inc hl ld (sector),hl ld de,(maxsec) ; reached limit? inc de ; one more or a ; clear carry sbc hl,de ; compare ret nz ld hl,(track) ; next track inc hl ld (track),hl ld hl,1 ; first sector of next track ld (sector),hl ret ; ; Compare 11 bytes of directory entry against argument ; compar: inc hl ex de,hl ld b,11 cmpr1: ld a,(hl) ; get character from filename list cp '?' jr z,cmpr2 ; '?' is always a match ld a,(de) ; get directory entry character and 7fh ; strip any flags cp (hl) ret nz cmpr2: inc de inc hl ; bump to next character djnz cmpr1 ; loop for 11 characters ret ; returns zero flag set for match ; ; Print screen header ; prfix: ld a,(listfl) ; list only? or a ; 0=no jr nz,prfix1 call eprint db ' Files recovered on ',0 call prdu ret ; prfix1: ld a,0FFh ; don't print this again ld (fixcnt),a call eprint db ' Erased files on ',0 call prdu ret ; prdu: ld a,(disk) ; get drive add a,'A' ; make it printable call cout ld a,(listfl) or a ld a,(user) ; print user call z,pafdc ; display user only if recovering files ld a,':' call cout ; print colon jp crlf ; and return to caller ; ; Check for CP/M version and set things ; ;==================================================================== ; The "wrong disk select" bug is fixed here. From the comment above, ; it is obvious that there was (once upon a time) a check for CP/M ; version 2.x. Well, since this is ZCPR3, the original check was ; obviously removed, however, the instruction I've commented out: ; CALL CPM22 caused the DPB address to be fetched BEFORE the drive ; was selected. This will cause the WRONG disk parameter information ; to be recorded if your system has different types of disks and the ; LAST disk selected (i.e. the one from which this program was loaded) ; did not have the same attributes (also if using a partitioned HARD ; DISK) ;==================================================================== ; cpmchk: ; ;==================================================================== ; Remove this call and fall through to get the BIOS stuff, select the ; proper disk, and fetch the disk information. ; ; call cpm22 ; if 2.2 go set things ;==================================================================== ; ; Select disk and setup disk parameter header ; ld c,fdvec ; get fixed-disk vector call bdos ld a,(disk) ; get the disk ld e,a ; put disk in E for possble seldsk call ld d,0 push de ; save for later ld b,a ; use disk no. as counter for shifting inc b call shfhl ; shift right; CY will have desired bit jr c,cpmck1 ; skip freset if fixed disk call freset ; reset and select disk jr cpmck2 cpmck1: ld c,seldsk ; select disk (drive no. in E already) call bdos cpmck2: pop bc ; drive no. to BC for BIOS call ; ;==================================================================== ; Fix # 2 for v1.2. It is a little-known fact that the BDOS provides ; the BIOS with a flag in the least significant bit of the E register ; when the SELECT DISK call to the BIOS is made. When this bit is ; set, the BIOS can assume that this disk has been selected before. ; If it is reset, the BIOS can assume that this is a new mount. In ; systems where there are multiple drive types (96 TPI, 48 TPI, etc.) ; and single/double sided disks permitted, this bit, when reset, ; causes the BIOS to determine the characteristics of the disk. This ; determination must be made prior to the BIOS determination of which ; disk parameters are to be used. As coded, the BIOS select disk, ; above, should have done the trick. This BIOS call is used to get ; the disk parameter block address. As such, setting the LSB of the ; E register merely is good form. ; ld e,1 ; pretend that disk is active already. ;==================================================================== ; ld a,sdisk ; make sure drive is call bios ld a,h ; selected or l jp z,ildisk ; should never happen ld e,(hl) ; get the address inc hl ; of the XLTO ld d,(hl) ex de,hl ld (dph),hl ; save the address ; ;==================================================================== ; Remove this instruction for v1.1 bug fix ; ; ret ;==================================================================== ; ; determine number of directory entries ; cpm22: ld c,getdpb ; get disk parameters address call bdos ; DPB address in HL on return ld e,(hl) ; number of sectors/track inc hl ; as 2-byte quantity in DE ld d,(hl) inc hl ld (maxsec),de ; set maximum sectors/track inc hl inc hl ld a,(hl) ; get EXM ld (extent),a inc hl ; point to DRM inc hl inc hl ld e,(hl) ; get number of inc hl ; directory entries ld d,(hl) ex de,hl ld b,2 inc hl ; account for - 1 call shfhl ; shift HL right 2 ld (dirmax),hl ; save number directory sectors ld hl,5 ; now point to system add hl,de ; track offset ld a,(hl) ; pick up number of inc hl ld h,(hl) ld l,a ld (track),hl ret ; ; Error occured during disk write - abort ; errwrt: call eprint db bel,' Error during disk write: Aborted.',0 jp reset ; abort ; ; Make sure a legal disk is specified and log it in. ; fcbchk: ld a,(cpmfcb+15) ; check for illegal directory or a jp nz,ildisk ; (it is) ld c,curdsk ; get current disk call bdos ld (disk),a ; ..store it ld a,(cpmfcb+13) ; get user from FCB ld (user),a ; ..store it ld a,(cpmfcb) ; get drive specification from FCB or a ; see if default ret z ; (yep, we're through here) dec a ; make A=0 ld (disk),a ; ..and store it ret ; ; Check for help request ; helpck: ld a,(cpmfcb+1) ; get 1st byte of filename cp ' ' ; make sure it's non-blank jr z,usage ; (no filename) cp '/' ; slash? ret nz ; (no, keep going) ld a,(cpmfcb+2) ; are there 2 slashes? cp '/' ret nz ; (no, must be filename) ; ; If no filename is specified, abort with notice ; usage: call eprint db 'UNERASE Version ' db (vers/10)+'0','.',(vers mod 10)+'0',cr,lf db 'Syntax:',cr,lf db ' ',0 call prtname call eprint db ' {du:}afn{,afn{,afn{,...}}} {{/}options}',cr,lf db 'Files are recovered on given drive, to given user;',cr,lf db ' otherwise, defaults are used.',cr,lf db 'DU specs after first are ignored.',cr,lf,0 ld a,(wildfl) or a jr z,usage2 call eprint db 'Ambiguous filenames set option L.',cr,lf,0 usage2: call eprint db 'Options:',cr,lf db ' L List matching erased files only',cr,lf db ' P ',0 ld a,(pagefl) call prdont call eprint db 'Page screen output',cr,lf db ' C ',0 ld a,(pausfl) call prdont call eprint db 'Pause first for disk change',0 jp exit ; prdont: or a ret z call eprint db 'Don''t ',0 ret ; ; Specified an illegal disk drive - abort ; ildisk: call eprint db bel,' Illegal drive requested.',0 jp exit ; abort ; ; Reads next sector (group of four directory entries). ; Returns with zero flag set if no more. ; nxtsec: ld hl,(dirmax) ; see if more sectors ld a,h or l ret z ; returns zero flag if no more ld bc,(track) ; set track ld a,sttrk call bios ld bc,(sector) ; set sector call trnslt ld a,stsec call bios ld a,read ; read a sector call bios and 1 ; reverse sense of error flag xor 1 ; returns with zero flag set ret ; if bad read ; ; FCB printing routine. Uses all registers. ; prtfcb: ex de,hl ld hl,1+8+3 add hl,de ld a,(extent) ; get extent mask cp (hl) ; compare to target ret c ; print only first extent inc hl ; check data module (s2) inc hl ld a,(hl) or a ret nz ; print only first data module ex de,hl ; HL points to FCB again call eprint ; new line with leading spaces db ' ',0 inc hl ld b,8 ; print filename call pr1 ld a,'.' call cout ld b,3 call pr1 ; print filetype call crlf ld a,(page) ; paging? or a ret z ; (no paging) ld a,(listfl) or a ; recovery mode? ret z ; (yes, no paging) ld a,(lincnt) ; check screen lines dec a ld (lincnt),a ret nz ; (more to go) call eprint db '[more] ',0 call cin ; wait for key cp ctrlc jp z,exit ; (^C, abort) ld a,cr call cout ld a,(maxlin) ; reset line count dec a ld (lincnt),a ret ; pr1: ld a,(hl) and 7Fh cp ' ' ; check for blanks call nz,cout inc hl djnz pr1 ret ; ; Waits for disk change, if user requested it. ; puschk: ld a,(pause) ; does user want to change disks? or a ret z ; (nope, just return) call eprint ; print pause message db bel,' Change Disk -- Press any key (^C to Abort): ',0 call cin cp ctrlc ; abort? jp z,exit jp crlf ; print a crlf and return to caller ; ; Shift regs HL right B bits logical ; shfhl: srl h ; shifted bit in carry rr l djnz shfhl ret ; ; Translate BC from logical to physical sector number ; ;==================================================================== ; Second Bug fixed here. The BIOS routine expects a number in the ; range 0 thru MAXSEC-1. This program was sending a value of 1 thru ; MAXSEC. This had 2 results: (1) the FIRST disk directory sector ; was ALWAYS skipped, and (2) when the value MAXSEC was used the BIOS ; returns an error on READING (an illegal sector) and the program ; terminates without completing the directory scan. ;==================================================================== ; trnslt: ld de,(dph) ; get address of XLTO ; ;==================================================================== ; Decrement BC prior to BIOS call ; dec bc ; bug fix for proper BIOS usage ;==================================================================== ; ld a,sectrn ; use BIOS routine call bios ld c,l ; return value in bc ld b,h ret ; ; Uninitialized data . . . ; DSEG ; ; data areas ; ds 80 ; stack stack: ds 2 ; old stack pointer user: ds 1 ; target user disk: ds 1 ; target disk maxlin: ds 1 ; maximum screen lines lincnt: ds 1 ; current line count fncnt: ds 1 ; number of file names in command line page: ds 1 ; 0 if no paging pause: ds 1 ; 0 if no pause for disk change listfl: ds 1 ; 0 if not list only dirmax: ds 2 ; number of sectors in directory = ; ; maximum number of directory entries ; ; divided by 4 (entries per sector) temp: ds 2 ; temp storage for fcb print extent: ds 1 ; extent mask maxsec: ds 2 ; maximum number of sectors/track fixcnt: ds 1 ; change flag rewrt: ds 1 ; rewrite flag 0=no, f=yes sector: ds 2 ; current sector number track: ds 2 ; track number of directory ; ; address of the translate table ; dph: ds 2 fntab: ds 2 ; filename buffer ; end