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		*
;*					*
;****************************************
;
;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
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
	mov	ss,ax
	mov	sp,offset stack

	mov	dx,offset signon
	call	pstr
	call	setup		;get disk stuff
	jnc	st1
		call	pstr
		int	32	;error.

st1:	call	liststat	;describe dsk
	mov	dx,offset contstr
	call	pstr		;type any key..
	call	ina
;
;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	pstr
	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	pstr		;should update
	call	ina
	and	al,5fh
	cmp	al,'Y'
	jne	st2
	mov	al,disk		;write out the
	mov	dx,fatsec	;FAT,
	mov	cx,fatcnt
	mov	bx,offset fatbuf
	int	26h
	pop	ax		;pop flags

st2:	mov	al,curdsk	;reselect the
	call	seldsk		;original disk
	int	32

signon	db	cr,lf,'DOS version 2 Bad Sector Mapper'
	db	cr,lf,'  T. Jennings 5 June 83'
	db	'$'
contstr	db	cr,lf,'Type ^C to abort, any '
	db	'other key to continue: $'
updstr	db	cr,lf,lf,' Want the disk updated? [y,n] :$'
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	33
	pop	ds
	mov	blkcnt,dx	;save # blocks,
	mov	secsize,cx	;sector size,
	mov	ah,0
	mov	blksize,ax	;secs/block.

	push	ds		;now get the
	mov	dl,disk
	inc	dl		;drive 1=A, b=2
	mov	ah,50		;FAT,
	int	33		;get the DPB,
	mov	cx,[bx+15]	;CX= sec count,
	mov	ch,0
	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:	mov	dx,offset blkstr ;type 'block '
	call	pstr
	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	pstr		;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 markd
	mov	dx,offset mrkstr
fb2:	call	pstr
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'
	db	' which file.',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		;times 2,
	add	bx,block	;times 3,
	shr	bx,1		;divide by 2,
	mov	ax,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 its 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	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
;sector sizes, etc.
;
liststat:
	mov	dx,offset st1str
	call	pstr
	mov	al,disk
	add	al,'A'
	call	outa
	mov	dx,offset st2str
	call	pstr
	mov	bx,blkcnt
	call	outdec
	mov	dx,offset st3str
	call	pstr
	ret

;Disk A:, total of 12345 data blocks.

st1str	db cr,lf,'Disk $'
st2str	db ':, total of $'
st3str	db ' data blocks.$'
;
;List out the bad things about this disk.
;
listbad:
	mov	dx,offset bd1str
	call	pstr
	mov	bx,badcnt
	call	outdec
	mov	dx,offset bd2str
	call	pstr
	mov	bx,newbad
	call	outdec
	mov	dx,offset bd3str
	call	pstr
	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.$'
page
;
;Generally useful system calls.
;
pstr:	mov	ah,9
	int	33
	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,
	cmp	ch,0	;if non-zero,
	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	33
	pop	dx
	mov	cl,1	;no suppress,
	ret
page
;
;More system calls.
;
seldsk:	mov	dl,al		;select disk,
	mov	ah,0eh
	int	33
	ret

getdsk:	mov	ah,19h
	int	33
	ret

initdsk:mov	ah,0dh
	int	33
	ret

ina:	mov	ah,0ch		;char in with
	mov	al,1		;flush.
	int	33
	ret

outa:	mov	dl,al
	mov	ah,2
	int	33
	ret

blkbuf	db 16384 dup (?)	;cluster buffer

fatbuf label word		;FAT buffer.

code ends

	end	scavenge
