; U3 - CP/M PLUS UTILITY FOR RECOVERING ERASED FILES AND FOR
; CHANGING THE USER AREA OF A FILE. This program is based on
; UNERASE.COM in the public domain. It has been modified to
; work with CP/M 3.0 and later, to change the user area of a
; file, and to accept ambiguous files names. It currently
; works with sector sizes of 128, 256 512 and 1024 bytes. It
; is placed in the public domain by Advanced Logic Systems for
; public use. Advanced Logic Systems makes no warrantee on the
; operation or use of this program or its applicability for any
; given application.

; U3 DOES NOT DOES NOT WORK WITH VERSIONS OF CP/M PRIOR TO CP/M 3.0

; The U3 command line takes the form:
;
;		U3 [d:]afn.aft [user area]
;
; If no user area is specified U3 recoverers all erased files on
; drive d: (or the default if d: is not specified) matching
; afn.aft and places them is the current user area. If a user area
; is specified U3 moves all files in the current user area on
; drive d: (or the default if d: is not specified) matching
; afn.aft to the specified user area.

; Versions 1.01 and 1.02 of U3 were prepared by R. Saeks from
; Gale Wolfenbarger's code for version 1.00. Comments regarding
; these revisions may be passed to the author by leaving a
; message on either the
;
;		Mesilla Valley RCP/M | (505)-524-6920
; or the
;	  Lost Dutchman's Gold Mine RCP/M | (602)-848-6708

;	HISTORY

;12/24/84	REVISED CODE TO PERMIT U3 TO BE USED TO EITHER
;Ver. 1.02	RECOVER AN ERASED FILE OR CHANGE THE USER AREA
;		OF A FILE. ADDED AMBIGUOUS FILE SPECIFICATIONS
;		REVISED CODE TO WORK ON DISKS WITH
;		MORE THAN ONE DIRECTORY TRACK. MOVED CPM 3.0
;		VERSION CHECK CODE TO BEGINNING OF CODE AND
;		CORRECTED ERROR TEST IN NODEF. - R. SAEKS

;06/16/84       REVISED TO WORK WITH 1024 BYTE SECTORS AND TO PLACE
;Ver. 1.01      RECOVERED FILES IN CURRENTLY LOGGED USER AREA.
;               - R. SAEKS

;12/29/83	FIRST PASS AT DISASSEMBLY OF UNERASE.COM AND MODIFICATIONS
;Ver. 1.00	TO MAKE IT COMPATIBLE WITH CP/M PLUS
;		I ALSO DID A SELECT DISK IN DSKPRM SO THAT IF YOU TRY TO 
;		RECOVER A FILE FROM A DISK THAT IS A DIFFERENT FORMAT FROM
;		THE ONE THAT YOU ARE CURRENTLY LOGGED ON TO, ITS GETS THE 
;		RIGHT DPH.  AT THE SAME TIME I CHANGED THE SIZE OF THE AREA
;		WHERE THE NUMBER OF RESERVED SYSTEM TRACKS IS KEPT.  THIS 
;		ALLOWS THE NUMBER OF RESERVED TRACKS TO BE GREATER THAN 256.
;		- GALE WOLFENBARGER

;	ADDRESSES

BDOS:	EQU	05H		;BDOS VECTOR ADDRESS
WRMBOOT:EQU	00H		;WARM BOOT VECTOR ADDRESS
DEFFCB: EQU	5CH		;ADDRESS OF THE DEFAULT FCB
DEFFCB2:EQU	6CH		;ADDRESS OF THE SECOND DEFAULT FCB

;	BDOS FUNCTIONS

DIRCALL:EQU	32H		;CPM 3.0 DIRECT BIOS CALL
DRESET: EQU	0DH		;RESET DISK SYSTEM
GETCUR: EQU	19H		;GET CURRENTLY LOGGED IN DISK
GETUSR: EQU     20H             ;GET CURRENT USER
CONIN:	EQU	01H		;GET A CHARACTER FROM THE CONSOLE
CONOUT: EQU	02H		;PRINT A CHARACTER TO THE CONSOLE
SETDMA: EQU	1AH		;SET DMA FUNCTION
GETVER: EQU	0CH		;GET CPM VERSION #
GETPARM:EQU	1FH		;GET ADDRESS OF DISK PARAMETERS
PRINT:	EQU	09H		;PRINT STRING TO CONSOLE

;	ASCII CHARACTERS

CR:	EQU	0DH		;CARRIAGE RETURN
LF:	EQU	0AH		;LINE FEED

;	I USED MACROS FROM Z80.LIB TO IMPLEMENT SOME Z80 COMMANDS
;	BECAUSE MAC.COM AND Z80.LIB COME WITH CP/M PLUS.
;	IF YOU ARE USING ASM.COM OR NOT USING A Z80 PROCESSOR
;	YOU WILL HAVE TO USE EQUATES OR INSERT INLINE CODE FOR THE
;	Z80 MACROS - gw

	MACLIB	Z80

	ORG	100H

	JMP	START	
	DB	'U3 ver 1.02'
	DB	'FOR CPM 3.0'
	DB	'12/29/83 - gw'
	DB      'Rev 06/16/84, 12/24/84 - rs'

	ORG	200H
START:		 
	LXI	SP,START	
	CALL	SETUP
	CALL	RDDIR
	CALL	FINISH	
	JMP	WRMBOOT 
SETUP:
	CALL	CKVERS		;CHECK FOR CPM 3.0 OR LATER
	CALL	CKNAME		;CHECK FOR VALID FILE NAME 
	CALL	SETSRCDST	;SET SOURCE AND DESTINATION USERS
	CALL	SETBIOS 	;COPY BIOS JUMP VECTOR
	RET
RDDIR:		 
	CALL	GETDIR		;READ A DIRECTORY SECTOR
	RZ			;END OR READ ERROR
	CALL	CKENT		;CHECK ENTRIES FOR FILE
	JMP	RDDIR	
FINISH:
	MVI	C,DRESET	;RESET DISK SYSTEM
	CALL	BDOS	
	LDA	NOREC		;GET NUMBER of DIR FOUND
	ORA	A
	JZ	NOTFND		;NON FOUND
	LDA	SRCUSR
	CPI	0E5H
	JZ	RECOVMSG
	LXI	D,CHGMSG
	JMP	PRINTMSG
RECOVMSG:
	LXI	D,RECMSG
PRINTMSG:
	CALL	PRTMSG		;PRINT SUCCESS MESSAGE
	RET
NOTFND:
	LXI	D,FILERR
	CALL	PRTMSG	
	RET
CKVERS:
	MVI	C,GETVER	;GET CPM VERSION #
	CALL	BDOS	
	CPI	30H		;IS IT 3.0 OR GREATER
	JC	ONLY30		;THIS VERSION FOR CPM 3.0 AND LATER
	RET
CKNAME:  
	LDA	DEFFCB		;GET DRIVE FOR FILE
	ORA	A		;USE DEFAULT?
	JNZ	NODEF		;NO CONVERT TO FCB FORMAT
	MVI	C,GETCUR	;GET CURRENT DISK
	CALL	BDOS	
	INR	A		;SET UP FOR DECREMENT
NODEF:		 
	DCR	A		;CONVERT TO FCB FORMAT
	STA	DEFFCB		;SAVE IT
	LDA	DEFFCB+1	;CHECK FOR NO FILE
	CPI	' '+1
	RNC			;RETURN IF OK
	LXI	D,NAMERR	;PRINT FILE ERROR
	CALL	PRTMSG	
	JMP	WRMBOOT 	;GIVE UP

SETSRCDST:
        MVI     C,GETUSR        ;GET USER #
        MVI     E,0FFH          ;GET USER FLAG
        CALL    BDOS
        STA     CURUSR          ;SAVE USER #
	LXI	H,DEFFCB2+1	;POINT TO 1ST DESTINATION CHAR.
	MVI	A,' '
	CMP	M
	JZ	UNERASE		;UNERASE IF NO DEST. USER #
	MVI	B,'0'
	MOV	C,M
	INX	H
	CMP	M
	JZ	CHGUSR		;SINGLE DIGIT ASCII USER # IN BC
	MOV	B,C
	MOV	C,M
	INX	H
	CMP	M
	JNZ	USRERROR	;TWO DIGIT ASCII USER # IN BC IF ZERO
CHGUSR:
	MVI	D,0		;INITIAL VALUE FOR HIGH DIGIT
	MVI	A,'0'
	CMP	B
	JZ	CHGUSR1		;OK IF HIGH DIGIT IS '0'
	INR	A		;A = '1'
	CMP	B
	JNZ	USRERROR	;ERROR IF HIGH DIGIT NOT '0' OR '1'
	MVI	D,10		;VALUE FOR HIGH DIGIT IF '1'
CHGUSR1:
	MVI	A,'0'-1
	CMP	C
	JNC	USRERROR	;ERROR IF LOW DIGIT < '0'
	MVI	A,'9'
	CMP	C
	JC	USRERROR	;ERROR IF LOW DIGIT > 9
	MOV	A,C
	SBI	'0'		;MAKE LOW DIGIT HEX
	ADD	D
	CPI	16
	JNC	USRERROR	;ERROR IF USER # > 15
	STA	DSTUSR		;DEST. USER # IN HEX
	LDA	CURUSR
	STA	SRCUSR		;SOURCE USER # IS CURRENT USER #
	RET
UNERASE:
	LDA	CURUSR		;PLACE FILE IN CURRENT USER
	STA	DSTUSR
	MVI	A,0E5H		;E5H = ERASED FILE "USER #"
	STA	SRCUSR
	RET	
USRERROR:
	LXI	D,USRERR
	CALL	PRTMSG
	JMP	WRMBOOT

SETBIOS:			;SET UP LOCAL BIOS JUMP VECTOR
	LXI	B,LOCDMA	;GET DEFAULT DMA ADDRESS
	SBCD	BCREG
	MVI	A,12		;SET DMA TO DEFAULT
	STA	FUNC
	CALL	CALLBIOS
	CALL	DSKPRM		;GET DISK PARAMETERS FROM BDOS 
	LDA	DEFFCB		;SELECT DRIVE
	MOV	C,A
	MVI	B,0
	MOV	E,B
	SBCD	BCREG		;BIOS SELDSK FUNCTION
	SDED	DEREG
	MVI	A,9		;SELDSK
	STA	FUNC
	CALL	CALLBIOS
	MOV	A,H
	ORA	L		;CHECK FOR DRIVE ERROR
	JZ	NODRV		;GO REPORT IT
	MOV	E,M		;GET DPH ADDRESS
	INX	H
	MOV	D,M
	XCHG
	SHLD	DPHADD		;SAVE ADDRESS TO DPH
	RET
DSKPRM: 	 
	LDA	DEFFCB
	MOV	E,A
	MVI	C,14		;SELECT DISK
	CALL	BDOS
	MVI	C,GETPARM	;GET ADDRESS OF DISK PARMS
	CALL	BDOS
	MOV	E,M		;GET LOGICAL SPT
	INX	H
	MOV	D,M
	SDED	SPT
	LXI	D,6		;OFFSET TO DRM (# OF DIRECTORY ENTRIES)
	DAD	D		;ADD OFFSET
	MOV	E,M		;GET DRM
	INX	H
	MOV	D,M
	PUSH	D
	PUSH	H
	LXI	D,7		;POINT TO PSH
	DAD	D
	MOV	A,M		;GET IT
	STA	PSH		;SAVE IT
	POP	H
	POP	D
	XCHG
	INX	H		;ADD 1 FOR TOTAL DIRECTORY ENTRIES
	LDA	PSH
	CPI	3		;1024 BYTE SECTORS
	CZ	PSH1024
	LDA	PSH
	CPI	2		;512 BYTE SECTORS
	CZ	PSH512
	LDA	PSH
	CPI	1		;256 BYTE SECTORS
	CZ	PSH256
	LDA	PSH
	ORA	A		;128 BYTE SECTORS
	CZ	PSH128	
	MOV	A,L		;SAVE # OF DIRECTORY SECTORS
	STA	DIRSECTS	
	LHLD	SPT		;LOGICAL SECTORS PER TRACK	
	LDA	PSH
	CPI	3		;1024 BYTE SECTORS
	CZ	DIV8
	LDA	PSH
	CPI	2		;512 BYTE SECTORS
	CZ	DIV4
	LDA	PSH
	CPI	1		;256 BYTE SECTORS
	CZ	DIV2
	MOV	A,L		;PHSPT = SPT FOR 128 BYTE SECTORS
	STA	PHSPT		;PHYSICAL SECTORS PER TRACK
	LXI	H,5		;ADD OFFSET TO NUMBER OF RESERVED TRACKS
	DAD	D
	MOV	E,M		;GET NUMBER OF RESERVED TRACKS
	INX	H
	MOV	D,M
	SDED	RESTKS		;SAVE RESERVED TRACKS FOR LATER
	RET
ONLY30:
	LXI	D,ERR30 	;PRINT MESSAGE FOR CPM PLUS ONLY
	CALL	PRTMSG
	JMP	WRMBOOT
GETDIR: 	 
	LDA	DIRSECTS	;GET # OF DIRECTORY SECTORS
	ORA	A
	RZ			;RETURN IF NONE
	CALL	PHSECTRK	;COMPUTE CURRENT PHYSICAL SECTOR AND TRACK
	LBCD	RESTKS		;GET NUMBER OF RESERVED TRACKS IN BC
	MVI	H,0		;GET PHYSICAL TRACK NUMBER IN HL
	LDA	PHTRK
	MOV	L,A
	DAD	B		;DIRECTORY TRACK IN HL
	SHLD	BCREG		;SET TRACK TO DIRECTORY
	MVI	A,10		;SETTRK
	STA	FUNC
	CALL	CALLBIOS
	LDA	PHSECT		;GET CURRENT PHYSICAL SECTOR
	MOV	C,A
	CALL	TRNSEC		;TRANSLATE SECTOR IF NECESSARY
	MVI	B,0
	SBCD	BCREG		;POINT TO CURRENT SECTOR
	MVI	A,11		;SETSEC
	STA	FUNC
	CALL	CALLBIOS
	MVI	A,13		;READ SECTOR OF DIRECTORY
	STA	FUNC
	CALL	CALLBIOS
	ANI	1
	XRI	1
	RET
CKENT:		 
	XRA	A		;ZERO FLAG
	STA	RECFLG
	LDA	DVD		;GET NUMBER OF DIRECTORY ENTRIES PER SECTOR
	MOV	B,A
	LXI	H,LOCDMA
CKNXT:
	LDA	SRCUSR
	MOV	D,A		;SOURCE USER # IN D
	MOV	A,M		;GET BYTE
	CMP	D		;CHECK FOR SOURCE USER #
	JNZ	GETNXT		;NOPE SKIP
	PUSH	H
	CALL	CKFIL		;YES CHECK FOR RECOVERY
	POP	H
RECENT: 	 
	JNZ	GETNXT
        LDA     DSTUSR
	MOV	M,A		;RECOVER/CHANGE THE ENTRY
	MVI	A,0FH		;SET RECOVERED FLAG
	STA	RECFLG
	LDA	NOREC		;INCREMENT NUMBER OF RECORDS FOUND
	INR	A
	STA	NOREC	
GETNXT: 	 
	LXI	D,32		;ADVANCE TO NEXT ENTRY
	DAD	D
	DCR	B		;DECREMENT COUNTER
	JNZ	CKNXT
	LDA	RECFLG		;CHECK FOR RECOVERED/CHANGED ENTRY
	ORA	A
	JZ	LOOP		;NOPE CONTINUE LOOP
				;WRITE SECTOR BACK TO DISK IF FILE REC/CHG
	CALL	PHSECTRK	;COMPUTE CURRENT PHYSICAL SECTOR AND TRACK
	LBCD	RESTKS		;GET NUMBER OF RESERVED TRACKS IN BC
	MVI	H,0		;GET PHYSICAL TRACK NUMBER IN HL
	LDA	PHTRK
	MOV	L,A
	DAD	B		;DIRECTORY TRACK IN HL
	SHLD	BCREG		;SET TRACK TO DIRECTORY
	MVI	A,10		;SETTRK
	STA	FUNC
	CALL	CALLBIOS
	LDA	PHSECT		;GET CURRENT PHYSICAL SECTOR
	MOV	C,A
	CALL	TRNSEC		;TRANSLATE SECTOR
	MVI	B,0
	SBCD	BCREG		;POINT TO CURRENT SECTOR
	MVI	A,11		;SETSEC
	STA	FUNC
	CALL	CALLBIOS
	LXI	B,1		;NONE DEFERRED WRITE
	SBCD	BCREG
	MVI	A,14		;WRITE
	STA	FUNC
	CALL	CALLBIOS	;UPDATE DIRECTORY
	ORA	A		;CHECK FOR WRITE ERROR
	JNZ	WRTERROR	;REPORT ERROR
LOOP:
	LDA	DIRSECTS	;DECREMENT DIRECTORY SECTORS
	DCR	A
	STA	DIRSECTS
	LDA	SECTOR		;INCREMENT SECTOR
	INR	A
	STA	SECTOR	
	RET
CKFIL:
	INX	H		;CHECK DELETED ENTRY FOR RECOVERY
	LXI	D,DEFFCB+1	;POINT AT FILE TO BE RECOVERED
	XCHG
	MVI	C,11		;LOOK AT 11 CHARACTERS
CMPFIL:
	MVI	A,'?'		;CHECK TO SEE IF SPEC IS AMBIGUOUS
	CMP	M
	JZ	NXTCHR
	LDAX	D		;GET CHARACTER IN NAME
	ANI	7FH		;TURN OFF HIGH BIT
	CMP	M		;ARE THEY EQUAL?
	RNZ			;NOPE GO GET ANOTHER ENTRY
NXTCHR:
	INX	D		;YES, CONTINUE
	INX	H
	DCR	C
	JNZ	CMPFIL
	RET
PSH1024:
	MVI	A,32		;SET NUMBER OF DIRECTORY ENTRIES PER SECTOR
	STA	DVD
	JMP	DIV32
PSH512:
	MVI	A,16		;SET NUMBER OF DIRECTORY ENTRIES PER SECTOR
	STA	DVD
	JMP	DIV16
PSH256:
	MVI	A,8		;SET NUMBER OF DIRECTORY ENTRIES PER SECTOR
	STA	DVD
	JMP	DIV8
PSH128:
	MVI	A,4		;SET NUMBER OF DIRECTORY ENTRIES PER SECTOR
	STA	DVD
	JMP	DIV4

DIV32:				;DIVIDE HL BY 32
	CALL	DIV2
DIV16:				;DIVIDE HL BY 32
	CALL	DIV2
DIV8:				;DIVIDE HL BY 32
	CALL	DIV2
DIV4:				;DIVIDE HL BY 32
	CALL	DIV2
DIV2:				;DIVIDE HL BY 2 ROUTINE
	XRA	A		;CLEAR CARRY
	MOV	A,H		;GET HIGH ORDER BYTE
	RAR			;SHIFT RIGHT FOR A DIVIDE BY 2
	MOV	H,A
	MOV	A,L		;GET LOW ORDER BYTE
	RAR			;SHIFT RIGHT FOR A DIVIDE BY 2
	MOV	L,A
	RET

PHSECTRK:
	MVI	C,0		;SET PHTRK TO 0
	LDA	PHSPT
	MOV	B,A		;PHYS SPT IN B
	LDA	SECTOR		;LOGICAL DIRECTORY SECTOR
CKPHTRK:
	CMP	B		;COMPUTE PHSECT = SECTORS MOD PHSPT
	JC	STSECTRK	;AND PHTRK = (SECTORS - PHSECT)/PHSPT
	SUB	B
	INR	C
	JMP	CKPHTRK
STSECTRK:
	STA	PHSECT
	MOV	A,C
	STA	PHTRK
	RET

PRTMSG: 	 
	MVI	C,PRINT 	;PRINT STRING TO CONSOLE
	JMP	BDOS	
NODRV:				;PRINT DRIVE ERROR MESSAGE
	LXI	D,DRVERR
	CALL	PRTMSG	
	JMP	WRMBOOT 
WRTERROR:			;PRINT WRITE ERROR MESSAGE
	LXI	D,WRTERR
	CALL	PRTMSG	
	JMP	WRMBOOT 
TRNSEC: 	 
	LHLD	DPHADD		;GET TRANSLATE TABLE ADDRESS
	XCHG
	SBCD	BCREG
	SDED	DEREG
	MVI	A,16		;SECTRAN
	STA	FUNC
	CALL	CALLBIOS
	MOV	C,L
	RET
CALLBIOS:			;CPM 3.0 DIRECT BIOS CALL
	MVI	C,DIRCALL
	LXI	D,FUN50
	CALL	BDOS
	RET
FUN50:
FUNC:	DS	1
AREG:	DS	1
BCREG:	DS	2
DEREG:	DS	2
HLREG:	DS	2

DPHADD:
	DS	2
DIRSECTS:
	DS	1
RESTKS: DS	2
SECTOR: DB	0
NOREC:	DB	0
RECFLG: DB	0
PSH:	DS	1
DVD:	DB	0
CURUSR: DS      1
SPT:	DS	2
PHSPT:	DS	1
PHSECT:	DS	1
PHTRK:	DS	1
SRCUSR:	DS	1
DSTUSR:	DS	1
WRTERR: 	 
	DB	CR,LF,'Error occurred during disk Write - ABORT$'
DRVERR: 	 
	DB	CR,LF,'Specified an illegal disk drive - ABORT$'
RECMSG: 	 
	DB	CR,LF,'File recovered.$'
CHGMSG:
	DB	CR,LF,'File user area changed.$'
NAMERR: 	 
	DB	CR,LF,'No File Name specified - ABORT$'
FILERR: 	 
	DB	CR,LF,'File NOT found$'
USRERR:
	DB	CR,LF,'Invalid User Number - ABORT$'
ERR30:
	DB	CR,LF,'THIS VERSION OF U3 ONLY WORKS'
	DB	CR,LF,'FOR CPM 3.0 OR LATER',CR,LF,'$'
LOCDMA:
	DS	1024		;LOCAL DMA BUFFER

	END
