title scavenge Copyright (c) T. Jennings 1983
;downloaded from Simtel20's MSDOS.DISK-UTIL, 10 Sep 86
;
;****************************************
;*					*
;*		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		*
;*					*
;*					*
;****************************************
;
;***********************************************************************
;**                                                                   **
;**  SCAV2XX was modified by David Gwillim to dynamically allocate    **
;**  memory to the variables BLKBUF and FATBUF when the program       **
;**  is started - this drastically reduced the size of the .COM file  **
;**  from 17K on disk to just over 1K, while still allowing the       **
;**  checking of disks with block sizes up to 16K Bytes. Also         **
;**  changed was the access to disk to determine its statistics       **
;**  (Number of Blocks, Blocksize etc) this is now done AFTER the     **
;**  pause command when the program is first started, permitting      **
;**  the program to be run with a single floppy drive. Code was       **
;**  also added to display the block size of the disk in bytes        **
;**  as part of the statistics, plus some alert tones for various     **
;**  parts of the program.                                            **
;**                                                                   **
;**  I also added a more graceful exit than via ^C or Cntrl-Break     **
;**  as this would often leave you on the wrong drive if you aborted  **
;**  mid way through the block check and also would not give you the  **
;**  chance to update the FAT with any bad blocks found so far.       **
;**                                                                   **
;**  A logical improvement to the program would be to add to the      **
;**  code so the program could report the names of the files which    **
;**  contain any bad blocks found, so the user could handle those     **
;**  files, or at least be aware that the data in them was corrupted. **
;**                                                                   **
;**  This is a very useful program and can give you great peace of    **
;**  mind if you own a hard disk and want to be sure that all is OK.  **
;**  If anyone takes a crack at doing the improvements I mentioned    **
;**  or finds and fixes any bugs, I would be delighted to hear of     **
;**  them.                                                            **
;**                                                                   **
;**                     David Gwillim                                 **
;**                     1414 N. Catalina Street                       **
;**                     Los Angeles CA 90027                          **
;**                                                                   **
;************************************************************************
;
;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.
;
;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.
;
cr	equ	0dh
lf	equ	0ah

;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
cgroup group code
code segment byte public 'code'
assume cs:cgroup,ds:cgroup,ss:cgroup
;
;MSDOS page 0 stuff.
;
	org	5ch
tfcb label byte

	org	80h
tbuff label byte

	org	100h
scavenge:
	jmp	start
;
;Disk parameters:
;
blkcnt	dw	(?)	;blocks this disk
blksize	dw	(?)	;sectors per block
secsize	dw	(?)	;phys. sector size
badcnt	dw	(?)	;# bad blocks found
newbad	dw	(?)	;new bad ones
block	dw	(?)	;current block
sector	dw	(?)	;sector to read
disk	db	(?)	;selected disk
curdsk	db	(?)	;current disk.
fatsec	dw	(?)	;1st FAT sector,
fatcnt	dw	(?)	;FAT sec count.

	dw 128 dup (?)
stack	dw	(?)	;what else
page
;
;Say who we are, describe the disk we are
;about to fix, ask to continue or abort.
;
start:	mov	ax,cs
	mov	ds,ax
	cli			;Disable interrupts while we move stack
	mov	ss,ax
	mov	sp,offset stack
	sti			;Re-enable interrupts
	mov	dx,offset signon
	call	putstr

	mov	bx,f176Hz	;Alert user audibly
	call	beep
	mov	bx,f704Hz
	call	beep
	mov	bx,f352Hz
	call	beep
	mov	bx,f1408Hz
	call	beep

	call	ina
	or	al,20h		;Make character lower case
	cmp	al,'q'		;Quit?
	jne	st0
	mov	dx,offset abort
	call	putstr
	int	20h

st0:	call	setup		;get disk stuff
	jnc	st1

	call	putstr
	int	20h	;error.

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
	call	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
	call	putstr		;should update
	call	ina
	and	al,5fh
	cmp	al,'Y'
	jne	st1A
	mov	al,disk		;write out the
	mov	dx,fatsec	;FAT,
	mov	cx,fatcnt
	mov	bx,offset fatbuf
	int	26h
	pop	ax		;pop flags
	mov	dx,offset update
	call	putstr
	jmp	st2
st1A:	mov	dx,offset noupdte
	call	putstr
st2:	mov	al,curdsk	;reselect the
	call	seldsk		;original disk
	int	20h

signon	db	cr,lf,9,'*********************************************'
	db	cr,lf,9,'* DOS 2.xx Bad Sector Mapper - Version 3.00 *'
	db	cr,lf,9,'* Updated by David Gwillim 18 December 1984 *'
	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',cr,lf,'$'
crlf	db	cr,lf,'$'
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:	call	initdsk		;reset dsk sys,
	call	getdsk		;get current,
	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
	jz	gd1
	dec	al		;make 0-N,
	mov	disk,al
	call	seldsk		;select,

	push	ds		;save local DS,
	mov	ah,1bh
	int	21h
	pop	ds
	mov	blkcnt,dx	;save # blocks,
	mov	secsize,cx	;sector size,
;th	mov	ah,0
	xor	ah,ah	;th
	mov	blksize,ax	;secs/block.

	push	ds		;now get the
	mov	dl,disk
	inc	dl		;drive 1=A, b=2
	mov	ah,32h		;FAT,
	int	21h		;get the DPB,

	mov	cx,[bx+15]	;CX= sec count,
;th	mov	ch,0
	xor	ch,ch	;th
	mov	dx,[bx+6]	;DX= 1st sec,
	pop	ds
	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
	mov	dx,offset bscstr
gd1:	ret
strstr	db	cr,lf,'Must specify a disk drive.$'
bscstr	db	cr,lf,'Bad FAT sector: disk not useable.$'

page
;
;Read the entire disk looking for bad blocks.
;When one is found, go mark it as an allocated
;bad block.
;
findbad:
	mov	block,0		;1st block,
	mov	badcnt,0	;none yet,

fb1:	call	kbdchk		;QUIT character input from the keyboard?
	jnc	fb1A
	mov	dx,offset abort
	call	putstr
	ret

fb1A:	mov	dx,offset blkstr ;type 'block '
	call	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
	call	putstr		;type 'bad'
	call	mapout		;mark bad,
	mov	dx,offset cntstr ;error if cant
	jc	fb2
	add	newbad,cx	;count it,
	mov	dx,offset alrstr
	jcxz	fb2		;already marked
	mov	dx,offset mrkstr

	mov	bx,f704Hz	;Warn user audibly
	call	beep
	mov	bx,f176Hz
	call	beep

fb2:	call	putstr

	mov	bx,f1408Hz	;Warn user audibly
	call	beep
	mov	bx,f176Hz
	call	beep

fb3:	inc	block		;next block,
	dec	blkcnt		;another...
	jnz	fb1		;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 dont know which file.',lf,'$'
abort	db	' ... Aborting program',cr,lf,lf,'$'

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:
	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	dx,0fff0h
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
	cmp	ax,0		;if allocated,
	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
page
;
;Read one block, return carry set if read
;error. Leave the useless data in the buffer
;following the end of this program.
;
readblk:
	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,
	ret
page
;
;List the general info on the disk, like
;number of blocks, block size, FAT size & location, etc.
;
liststat:
	mov	dx,offset st1str
	call	putstr
	mov	al,disk
	add	al,'A'
	call	outa
	mov	dx,offset st2str
	call	putstr
	mov	bx,blkcnt
	call	outdec
	mov	dx,offset st3str
	call	putstr
	mov	ax,512
	mul	blksize
	mov	bx,ax
	call	outdec
	mov	dx,offset st4str
	call	putstr
	mov	bx,fatcnt
	call	outdec
	mov	dx,offset st5str
	call	putstr
	mov	bx,fatsec
	call	outdec
	mov	dx,offset st6str
	call	putstr
	ret

;Disk A:  Total of 12345 data blocks 1024 bytes each.

st1str	db cr,lf,'Disk $'
st2str	db ':  Total of $'
st3str	db ' data blocks $'
st4str	db ' bytes each.',cr,lf,'$'
st5str	db ' FAT sectors, first FAT sector is logical sector $'
st6str  db cr,lf,'$'
;
;List out the bad things about this disk.
;
listbad:
	mov	dx,offset bd1str
	call	putstr
	mov	bx,badcnt
	call	outdec
	mov	dx,offset bd2str
	call	putstr
	mov	bx,newbad
	call	outdec
	mov	dx,offset bd3str
	call	putstr
	ret

;Total of 12345 bad blocks, found 12234 more this pass.
bd1str	db cr,'Total of $'		;note no linefeed.
bd2str	db ' bad blocks, found $'
bd3str	db ' more this pass.',cr,lf,'$'
page
;
;Generally useful system calls.
;
putstr:	mov	ah,9
	int	21h
	ret
;
;Type BX in decimal, suppressing leading
;zeros.
;
outdec:	mov	cl,0	;0 suppress flag,
	mov	dx,10000
	call	rsdiv
	mov	dx,1000
	call	rsdiv
	mov	dx,100
	call	rsdiv
	mov	dx,10
	call	rsdiv
	mov	ch,bl
	jmp	odf

rsdiv:	mov	ch,-1	;iteration -1,
rs1:	inc	ch	;count,
	sub	bx,dx	;subtract,
	jnc	rs1	;til underflow,
	add	bx,dx	;adjust back,
;th	cmp	ch,0	;if non-zero,
	or	ch,ch	;th
	jne	odf	;type it,
	test	cl,1	;dont type 0's
	jnz	odf	;if leading,
	ret

odf:	push	dx
	mov	dl,ch
	add	dl,'0'	;ASCII,
	mov	ah,2
	int	21h
	pop	dx
	mov	cl,1	;no suppress,
	ret
page
;
;More system calls.
;
seldsk:	mov	dl,al		;select disk,
	mov	ah,0eh
	int	21h
	ret

getdsk:	mov	ah,19h
	int	21h
	ret

initdsk:mov	ah,0dh
	int	21h
	ret

ina:	mov	ah,0ch		;char in with no echo
	mov	al,8		;and flush.
	int	21h
	ret

kbdchk:	mov	ah,0Bh		;Check keyboard for character waiting
	int	21h
;th	cmp	al,0
	or	al,al	;th
	jz	chk1
	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

outa:	mov	dl,al
	mov	ah,2
	int	21h
	ret

;
; BEEP - entry conditions BX register contains count figure for frequency
;        to be generated.
;
beep:	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

blkbuf	equ	$		;cluster buffer

fatbuf	equ	$+16384		;FAT buffer.

code ends

end	scavenge
