	title scavenge Copyright (c) T. Jennings 1983
;
;****************************************
;*					*
;*		SCAVENGE		*
;*					*
;*	Mark bad blocks on MSDOS 	*
;*	as allocated in the FAT.	*
;*					*
;*	T. Jennings 5 June 82		*
;*	  created 15 Sept. 82		*
;*	Modified 14 December 1984	*
;*	    by David Gwillim		*
;*					*
;*					*
;****************************************
;
;See SCAV31.DOC for details on functioning, updates, etc.	v3.01
;
;Reads all sectors in logical MSDOS blocks
;and marks the file allocation tables such
;that the blocks are permanently allocated
;where CHKDSK will not deallocate them.
;
;
;This version works on any 2.xx MSDOS or
;PCDOS, on any media type, fixed or removable.
;One (major) limitation: it will not map out
;blocks that are already allocated to a file;
;it will say "block used", but won't tell you
;which file it is in.
;
;If SCAVENGE finds any bad blocks, it will ask
;you whether or not you want the disk updated.
;You can safely run it just to see if the disk 
;is OK.
;
;Enter 'Q' at any time to abort the run.			v3.01

;MASM, LINK, then EXE2BIN this to make a COM
;file. It will not run as an EXE file. NOTE:
;LINK will give you a 'Warning: no STACK 
;segment' error: ignore it.
;

;v3.01
; - Slight tweaking, reformatting.
; - v3.00 forced DS, SS to CS; unnecessary.
; - v3.00 had its own stack; unnecessary.
; - Removed some system functions, placing them inline.
; - Separated extensive documentation into separate file,
;   SCAVENGE.DOC.
; - No functional changes.
; David Kirschbaum
; Toad Hall
; kirsch@braggvax.ARPA

CR	equ	0dh
LF	equ	0ah
PutStr	macro			;				v3.01
	mov	ah,9
	int	21h
	endm

;Beep procedure count values
;---------------------------
;To generate a given freqency note out of the speaker with the Beep procedure
;on the PC using Channel 2 of the 8253 timer, the channel 2 count register
;must be loaded with a value such that the 8253 input clock frequency
;(1.19318 MHz) divided by the count figure equals the audio frequency.

F176HZ	equ	6779	; 176 Hz count value
F352HZ	equ	3390	; 352 Hz count value
F704HZ	equ	1695	; 704 Hz count value
F1408HZ	equ	847	; 1408 Hz count value

page
Cseg	segment para public 'code'
	assume CS:Cseg, DS:Cseg, ES:Cseg, SS:Cseg
;
;MSDOS PSP stuff.
;
	org	5ch
tfcb	label byte

	org	80h
tbuff	label byte

	org	100h

Scavenge	proc	near
	jmp	Start
;
;Disk parameters:
;
blkcnt	dw	0	;blocks this disk
blksize	dw	0	;sectors per block
secsize	dw	0	;phys. sector size
badcnt	dw	0	;# bad blocks found
newbad	dw	0	;new bad ones
block	dw	0	;current block
sector	dw	0	;sector to read
disk	db	0	;selected disk
curdsk	db	0	;current disk.
fatsec	dw	0	;1st FAT sector,
fatcnt	dw	0	;FAT sec count.

;	dw 128 dup 0
;stack	dw	0	;what else
;	db	16 dup('STACK    ')
;stack	equ	$
Scavenge	endp
page
;
;Say who we are, describe the disk we are
;about to fix, ask to continue or abort.
;
Start	proc	near
;	cli			;Disable interrupts while we move stack
;	mov	sp,offset stack
;	sti			;Re-enable interrupts
	mov	dx,offset signon
	PutStr

	mov	bx,F176HZ	;Alert user audibly
	call	Beep
	mov	bx,F704HZ
	call	Beep
	mov	bx,F352HZ
	call	Beep
	mov	bx,F1408HZ
	call	Beep

	mov	ax,0C08H	;flush, char in with no echo	v3.01
	int	21h
	or	al,20h		;Make character lower case
	cmp	al,'q'		;Quit?
	jne	St0
	 mov	dx,offset abort
	 jmp	short Msg_Exit	;display, terminate		v3.01

St0:	call	SetUp		;get disk stuff
	jnc	St1		;went ok
Msg_Exit:			;				v3.01
	 PutStr
	 int	20h		;error, terminate.

St1:	call	ListStat	;describe dsk
;
;Find all the bad blocks, if any, display them,
;ask if we should update the FAT. If so, write
;it out.
;
	mov	dx,offset crlf	;new line
	PutStr
	call	FindBad		;map bad,
	call	ListBad		;list them,
	cmp	newbad,0	;if new bad
	je	St2		;blocks...

	mov	dx,offset updstr	;ask if we
	PutStr				;should update
	mov	ax,0C08H		;flush, char in with no echo	v3.01
	int	21h
	and	al,5fh			;uppercase
	mov	dx,offset noupdte	;assume no update	v3.01
	cmp	al,'Y'
	jne	St1B			;nope, no update
	 mov	al,disk			;write out the
	 mov	dx,fatsec		;FAT,
	 mov	cx,fatcnt
	 mov	bx,offset fatbuf
	 int	26h
	 pop	ax			;pop flags after Int 26H
	 mov	dx,offset update	;fall thru to		v3.01

St1B:	PutStr				;display msg		v3.01
St2:
	mov	dl,curdsk	;reselect the original disk	v3.01
	mov	ah,0eh		;				v3.01
	int	21h		;				v3.01
	int	20h		;terminate

signon	db	CR,LF,9,'*********************************************'
	db	CR,LF,9,'* DOS 2.xx Bad Sector Mapper - Version 3.01 *'
	db	CR,LF,9,'* Updated by David Gwillim 18 December 1984 *'
	db	CR,LF,9,'*        Again by Toad Hall, 6 Oct 88       *'
	db	CR,LF,9,'* Original program by T. Jennings 5 June 83 *'
	db	CR,LF,9,'*********************************************'
	db	CR,LF,LF
	db	'Type Q to abort at any time, any other key to continue ...'
	db	CR,LF,'$'
updstr	db	CR,LF,LF,'Want the disk updated? [y,n] $'
noupdte	db	CR,LF,LF,'FAT not updated',CR,LF,'$'
update	db	CR,LF,LF,'FAT updated'
crlf	db	CR,LF,'$'		;share this		v3.01
Start	endp

page
;
;Get the data on the specified disk. Return 
;carry if no drive specified. Returns ES:DI
;pointing to the FAT for the selected drive.
;
SetUp	proc	near
	mov	ah,0dh			;reset disk system	v3.01
	int	21h
	mov	ah,19h			;get current disk	v3.01
	int	21h
	mov	curdsk,al		;save it,
	mov	al,tfcb			;make sure a
	cmp	al,0			;new one spec'd
	stc				;quit if none,
	mov	dx,offset strstr	;'Must specify disk drive' msg
	jz	gd1			;exit

	dec	al			;make 0-N,
	mov	disk,al
	mov	dl,al			;select disk,		v3.01
	mov	ah,0eh
	int	21h

	mov	di,DS			;save our DS a sec	v3.01
	mov	ah,1bh
	int	21h
	mov	DS,di			;restore DS		v3.01
	mov	blkcnt,dx		;save # blocks,
	mov	secsize,cx		;sector size,
	xor	ah,ah			;clear msb		v3.01
	mov	blksize,ax		;secs/block.

	mov	di,DS			;save DS a sec		v3.01
	mov	dl,disk			;now get the
	inc	dl			;drive 1=A, b=2
	mov	ah,32h			;FAT,
	int	21h			;get the DPB,

	mov	cx,[bx+15]		;CX= sec count,
	xor	ch,ch			;clear msb		v3.01
	mov	dx,[bx+6]		;DX= 1st sec,
	mov	DS,di			;restore DS		v3.01
	mov	fatcnt,cx		;save both,
	mov	fatsec,dx

	mov	al,disk			;AL= drive #,
	mov	bx,offset fatbuf	;DS:BX= buffer
	int	25h			;read the FAT,
	pop	ax			;pop flags after Int 25H
	mov	dx,offset bscstr	;return with msg
gd1:	ret

strstr	db	CR,LF,'Must specify a disk drive.$'
bscstr	db	CR,LF,'Bad FAT sector: disk not useable.$'

SetUp	endp

page
;
;Read the entire disk looking for bad blocks.
;When one is found, go mark it as an allocated
;bad block.
;
FindBad	proc	near
	xor	ax,ax			;get a handy 0		v3.01
	mov	block,ax		;1st block,
	mov	badcnt,ax		;none yet,

BlockLup:
	call	KbdChk			;QUIT char input from the keyboard?
	jnc	FB1A			;nope
	 mov	dx,offset abort		;'Aborting program'
	 PutStr
	 ret

FB1A:	mov	dx,offset blkstr	;type 'block '
	PutStr
	mov	bx,block		;block number,
	call	OutDec
	call	ReadBlk			;read a block,
	jnc	Fb3			;if bad, 
	inc	badcnt			;count it,
	mov	dx,offset badstr
	PutStr				;type 'bad'
	call	MapOut			;mark bad,
	mov	dx,offset cntstr	;'Already used'
	jc	Fb2			;error if can't
	add	newbad,cx		;count it,
	mov	dx,offset alrstr	;'Already marked'
	jcxz	Fb2			;error if already marked
	 mov	dx,offset mrkstr	;'mapped out'
	 mov	bx,F704HZ		;Warn user audibly
	 call	Beep
	 mov	bx,F176HZ
	 call	Beep
Fb2:	PutStr
	mov	bx,F1408HZ		;Warn user audibly
	call	Beep
	mov	bx,F176HZ
	call	Beep

Fb3:	inc	block			;next block,
	dec	blkcnt			;another...
	jnz	BlockLup		;keep looking.
	ret


blkstr	db	CR,'Block $'
badstr	db	' bad,$'
alrstr	db	' already marked.',CR,LF,'$'
mrkstr	db	' mapped out.',LF,'$'
cntstr	db	" already used! I don't know which file.",LF,'$'
abort	db	' ... Aborting program',CR,LF,LF,'$'

FindBad	endp
page
;
;Mark the current block as bad in the FAT.
;Multiply the block number by 1.5 to find the
;block number, (actually *3, /2) and if not
;used, mark it bad. If used, report which file
;it's in. If it's already mapped as bad, 
;return CX=0, else return CX=1.
;
MapOut	proc	near
	mov	bx,block		;block,
	shl	bx,1			;first times 2,
	add	bx,block		;then add one to make times 3,
	shr	bx,1			;now divide by 2,
	mov	ax,word ptr fatbuf[bx]	;get word,
;
;If carry is set, we want the high 12 bits in
;the word in AX, else the low 12 bits. Set CH
;as the shift count, DX as the mask.
;
	mov	ch,0			;assume low,
	mov	dx,0fffh
	jnc	Mo1
	 mov	ch,4			;else high 12,
	 mov	dl,0F0H			;			v3.01
Mo1:	and	ax,dx			;mask it,
	mov	cl,ch
	shr	ax,cl			;shift it,
;
;AX is the block number; if it's anything
;but 000 or ff7, return with carry set,
;indicating that it's already used.
;
	cmp	ax,0ff7h		;if ff7,
	je	Mo2			;already marked
	 or	ax,ax			;if allocated		v3.01
	 je	Mo3
	stc				;error!
Mo2:	mov	cx,0			;none mapped,
	ret
;
;Bad unused block. Mark as bad in the FAT.
;
Mo3:	mov	ax,0ff7h		;marker,
	mov	cl,ch
	shl	ax,cl			;shift it,
	or	word ptr fatbuf[bx],ax	;mark it.
	mov	cx,1			;1 mapped,
	ret
MapOut	endp
page
;
;Read one block, return carry set if read
;error. Leave the useless data in the buffer
;following the end of this program.
;
ReadBlk	proc	near
	mov	ax,block		;find start
	mov	cx,blksize		;sector,
	mul	cx			;CX= count,
	mov	dx,ax			;DX= sector,
	mov	al,disk
	mov	bx,offset blkbuf	;our buffer,
	int	25h			;read sectors,
	pop	dx			;pop flags after Int 25H
	ret
ReadBlk	endp
page
;
;List the general info on the disk, like
;number of blocks, block size, FAT size & location, etc.
;
ListStat	proc	near
	mov	al,disk			;disk nr		v3.01
	add	al,'A'			;asciify		v3.01
	mov	st2str,al		;stuff in string	v3.01
	mov	dx,offset st1str	;'Disk %: Total of '	v3.01
	PutStr				;display str		v3.01
	mov	bx,blkcnt
	call	OutDec1			;we know >9		v3.01
	mov	dx,offset st3str	;'Data blocks'
	PutStr
	mov	ax,512
	mul	blksize
	mov	bx,ax
	call	OutDec1			;we know >9		v3.01
	mov	dx,offset st4str	;' bytes each'
	PutStr
	mov	bx,fatcnt
	call	OutDec			;probably <10		v3.01
	mov	dx,offset st5str	;' FAT sectors, first ...'
	PutStr
	mov	bx,fatsec
	call	OutDec			;display first FAT sector
	mov	dx,offset crlf		;CR,LF			v3.01
	PutStr
	ret

;Disk A:  Total of 12345 data blocks 1024 bytes each.

st1str	db CR,LF,'Disk '		;			v3.01
st2str	db '%: Total of $'		;			v3.01
st3str	db ' data blocks $'
st4str	db ' bytes each.',CR,LF,'$'
st5str	db ' FAT sectors, first FAT sector is logical sector $'
ListStat	endp

;
;List out the bad things about this disk.
;
ListBad	proc	near
	mov	dx,offset bd1str	;'Total of '
	PutStr
	mov	bx,badcnt
	call	OutDec
	mov	dx,offset bd2str	;' bad blocks'		v3.01
	PutStr
	mov	dx,offset crlf		;assume no bad blocks	v3.01
	cmp	badcnt,0		;no bad blocks yet?	v3.01
	jz	NoBad			;nope, display no more	v3.01
	 mov	dx,offset bd3str	;', found '		v3.01
	 PutStr				;			v3.01
	 mov	bx,newbad
	 call	OutDec
	 mov	dx,offset bd4str	;' more this pass.'
NoBad:	PutStr				;			v3.01
	ret

;Total of 12345 bad blocks, found 12234 more this pass.
bd1str	db CR,'Total of $'		;note no linefeed.
bd2str	db ' bad blocks$'
bd3str	db ', found $'
bd4str	db ' more this pass.',CR,LF,'$'

ListBad	endp
page
;
;Type BX in decimal, suppressing leading zeros.
;
OutDec	proc	near
	cmp	bx,9			;single digit?		v3.01
	ja	OutDec1			;nope			v3.01
	 mov	dl,bl			;get single digit	v3.01
	 add	dl,'0'			;asciify		v3.01
	 mov	ah,2			;display char
	 int	21h			;			v3.01
	 ret				;			v3.01

OutDec1:
	xor	cl,cl			;0 suppress flag	v3.01
	mov	dx,10000
	call	RsDiv
	mov	dx,1000
	call	RsDiv
	mov	dx,100
	call	RsDiv
	mov	dx,10
	call	RsDiv
	mov	ch,bl
	jmp	short Odf

RsDiv:	mov	ch,-1		;iteration -1,
Rs1:	inc	ch		;count,
	sub	bx,dx		;subtract,
	jnc	Rs1		;til underflow,
	add	bx,dx		;adjust back,
	or	ch,ch		;if non-zero			v3.01
	jne	Odf		;type it,
	 test	cl,1		;don't type 0's
	 jz	OdX		;if leading			v3.01

Odf:	push	dx
	mov	dl,ch
	add	dl,'0'		;ASCII,
	mov	ah,2		;display char
	int	21h
	pop	dx
	mov	cl,1		;no suppress,
OdX:	ret			;				v3.01
OutDec	endp
;
KbdChk	proc	near
	mov	ah,0Bh		;Check keyboard for character waiting
	int	21h
	or	al,al		;anything there?		v3.01
	jz	Chk1		;nope				v3.01
	 mov	ah,8		;Character waiting - see what it is
	 int	21h
	 or	al,20h		;Make it lower case
	 cmp	al,'q'		;Time to QUIT?
	 je	Chk2		;Yes
Chk1:	clc			;No - Make sure carry flag is cleared
	ret

Chk2:	stc			;Yep - set carry flag
	ret
KbdChk	endp

;
; BEEP - entry conditions BX register contains count figure for frequency
;        to be generated.
;
Beep	proc	near
	mov	al,0b6h		; Channel 2, LSB then MSB, Square Wave, Binary
	out	43h,al		; Program 8253 command register
	mov	ax,bx		; Get the frequency to be generated
	out	42h,al		; Load Channel 2 count register LSB
	mov	al,ah
	out	42h,al		; Load Channel 2 count register MSB
	in	al,61h		; Read settings from 8255 PPI I/O Port "PB"
	mov	ah,al		; Save original settings in AH
	or	al,3		; Enable Timer Channel 2 & Speaker data
	out	61h,al		; program the 8255 with new setting-speaker on
	sub	cx,cx		; Sneaky way to put 0FFFFH into CX when
wait2:	loop	wait2		; LOOP is first executed
	mov	al,ah		; Get original 8255 Port "PB" settings
	out	61h,al		; Reset port to original values-speaker off
	ret
Beep	endp

blkbuf	equ	$		;cluster buffer

fatbuf	equ	$+16384		;FAT buffer.

Cseg	ENDS
	END	Scavenge
