;	DISKSCAN.ASM -- Checks out disk by reading sectors
;	--------------------------------------------------

CSEG		Segment
		Assume	CS:CSEG, DS:CSEG, ES:CSEG, SS:CSEG
		Org	100h
Entry:		Jmp	Begin

;	All Data
;	--------
                db	' Copyright 1986 Ziff-Davis Publishing Co.'
		db	' Programmed by Charles Petzold '
DriveError	db	'Invalid Drive$'
DosVersErr	db	'Needs DOS 2.0+$'
MemoryError	db	'Needs 64K$'
ReadSegment	dw	?
DriveNum	db	?
DiskBlock	db	18 dup (?)
TotalSectors	dw	?
SectorsIn64K	dw	?
StartSector	dw	0
SectorLabel2	db	9,'Sector $' 
SectorLabel	db	13,'Sectors $'
DashLabel	db	' - $'
ErrorLabel	db	': Error!'
CRLF		db	13,10,'$'
ErrorAddr	dw	Err0,Err1,Err2,Err3,Err4,Err5,Err6,Err7
		dw	Err8,Err9,ErrA,ErrB,ErrC,ErrD,ErrD,ErrD  
Err0		db	'Write Protect$'
Err1		db	'Unknown Unit$'
Err2		db	'Drive Not Ready$'
Err3		db	'Unknown Command$'
Err4		db	'CRC Error$'
Err5		db	'Request Length$'
Err6		db	'Seek Error$'
Err7		db	'Unknown Media$'
Err8		db	'Sector Not Found$'
Err9		db	'No Paper$'
ErrA		db	'Write Fault$'
ErrB		db	'Read Fault$'
ErrC		db	'General Failure$'
ErrD		db	'Undocumented Error$'
BootSectMsg	db	'Boot Sector$'
RootDirMsg	db	'Root Directory$'
BadFatMsg	db	'File Alloc. Table$'
InUseMsg	db	'Used by file$'
NotInUseMsg	db	'Unallocated$'
BadFlagMsg	db	'Flagged as bad$'	
FatReadMsg	db	"Can't Read FAT$"
Divisors	dw	10000, 1000, 100, 10, 1	; For decimal conversion

;	Check Drive Parameter, DOS Version, and Enough Memory
;	-----------------------------------------------------

ErrorExit:	Mov	AH,9		; Write error message
		Int	21h		;      through DOS
		Int	20h		; And terminate

Begin:		Mov	DX, Offset DriveError	; Possible message
		Or	AL, AL		; Check Drive Validity Byte
		Jnz	ErrorExit	; If not zero, invalid drive
		Mov	DX, Offset DosVersErr	; Possible message
		Mov	AH, 30h
		Int	21h		; Get DOS Version Number
		Cmp	AL, 2		; Check for 2.0 or later
		Jb	ErrorExit	; If not, terminate with message 
		Mov	DX, Offset MemoryError	; Possible error message
		Mov	BX, 256+Offset EndProg	; Set beyond program 
		Mov	SP, BX		; Move stack closer to code
		Add	BX, 15		; Add 15 to round up
		Mov	CL, 4		; Divide BX by 16
		Shr	BX, CL
		Mov	AH, 4Ah		; Free allocated memory
		Int	21h		;   by calling DOS Set Block
		Jc	ErrorExit	; Terminate on error
		Mov	BX, 1000h	; Ask for 64K bytes
		Mov	AH, 48h		;   by using DOS
		Int	21h		;   Allocate Memory call
		Jc	ErrorExit	; Terminate on error
		Mov	[ReadSegment], AX	; Save segment of memory block

;	Get Disk Information From DOS
;	-----------------------------

		Mov	DL, DS:[005Ch]	; Get Drive Parameter
		Push	DS		; Save DS
		Mov	AH, 32h		; Call DOS to
		Int	21h		;   get DOS Disk Block (DS:BX)
		Mov	SI, BX		; Now DS:SI points to Disk Block
		Mov	DI, Offset DiskBlock	; DI points to destination
		Mov	CX, 18		; 18 bytes to copy'
		Cld			; Forward direction
		Rep	Movsb		; Move 'em in
		Pop	DS		; Get back DS
		Mov	BX, Offset DiskBlock	; BX to address Disk Block 
		Mov	DX, 1		; Set DX:AX to 65,536
		Sub	AX, AX
		Div	Word Ptr [BX + 2]	; Divide by Bytes Per Sector
		Mov	[SectorsIn64K], AX	; Save that values
		Mov	AX, [BX + 13]		; Last Cluster Number
		Dec	AX			; AX = Number of Clusters
		Mov	CL, [BX + 5]		; Cluster to Sector Shift
		Shl	AX, CL			; AX = Number Data Sectors
		Add	AX, [BX + 11]		; Add First Data Sector
		Mov	[TotalSectors], AX	; AX = Number Total Sectors
		Mov	AL, DS:[005Ch]	; Drive Number (0=def, 1=A)
		Dec	AL		; Make it 0=A, 1=B
		Jns	GotDriveNumber	; If no sign, not default drive
		Mov	AH, 19h		; Get current disk 
		Int	21h		;   by calling DOS

GotDriveNumber:	Mov	[DriveNum], AL	; Save Drive Number (0=A, 1=B)

;	Start Reading
;	-------------	

MainLoop:	Mov	DX, Offset SectorLabel	; String to display on screen
		Call	StringWrite		; Display it
		Mov	AX, [StartSector]	; Starting sector number
		Call	WordWrite		; Display number on screen
		Mov	DX, Offset DashLabel	; String containing a dash
		Call	StringWrite		; Display it on the screen
		Mov	CX, [SectorsIn64K]	; Number of sectors to read
		Add	AX, CX			; Add it to starting sector
		Jc	NumRecalc
		Cmp	AX, [TotalSectors]	; See if bigger than total
		Jbe	NumSectorsOK		; If so, proceed

NumRecalc:	Mov	AX, [TotalSectors]	; Otherwise get total sectors
		Mov	CX, AX			; Move it to CX also
		Sub	CX, [StartSector]	; Now CX = sectors to read

NumSectorsOK:	Dec	AX			; AX = last sector to read
		Call	WordWrite		; Display it on screen
		Call	ReadSectors		; Read the sectors
		Jnc	NextSectors		; If no error, skip detail
		Call	ReadSectors		; Repeat read
		Jnc	NextSectors		; If still no error, skip

DiskError:	Mov	DX, Offset ErrorLabel	; String saying "Error!"
		Call	StringWrite		; Display it on screen

ErrorLoop:	Push	CX			; Now save previous number
		Mov	CX, 1			; So we can read one at a time
		Call	ReadSectors		; Read one sector
		Jnc	NoError			; If no error, proceed
		Mov	BL, AL			; Save error code
		Mov	DX, Offset SectorLabel2	; String with "Sector "
		Call	StringWrite		; Display it on screen
		Mov	AX, [StartSector]	; The sector we just read
		Call	WordWrite		; Display it on screen
		Mov	DX, Offset DashLabel	; String with a dash
		Call	StringWrite		; Display it on screen
		And	BL, 0Fh			; Blank out error top bits
		Sub	BH, BH			; Now BX is error code
		Add	BX, BX			; Double it for word access
		Mov	DX, [ErrorAddr + BX]	; Get address of message
		Call	StringWrite		; Display message on screen
		Call	FindSector		; See where sector is 
		Mov	DX, Offset CRLF		; String for new line 
		Call	StringWrite		; Do carriage ret & line feed 

NoError:	Inc	[StartSector]		; Kick up the start sector
		Pop	CX			; Get back counter
		Loop	ErrorLoop		; And read next sector
		Mov	AX, [StartSector]	; Sector of next group
		Jmp	Short CheckFinish	; Check if at end yet

NextSectors:	Mov	AX, [StartSector]	; For no error, increment 
		Add	AX, [SectorsIn64K]	;   StartSector for next group
		Jc	Terminate		; (If overflow, terminate)
		Mov	[StartSector], AX	; And save it

CheckFinish:	Cmp	AX, [TotalSectors]	; See if at then end
		Jae	Terminate		; If so, just terminate
		Jmp	MainLoop		; If not, do it again

Terminate:	Int	20h			; Terminate

;	Find Sector in FAT to see if used by file, etc.
;	-----------------------------------------------

FindSector:	Mov	DX, Offset DashLabel		; Print dash
		Call	StringWrite
		Mov	AX, [StartSector]		; Sector with error
		Mov	DX, Offset BootSectMsg		; Set up message
		Cmp	AX, Word Ptr [DiskBlock + 6]	; See if sector boot
		Jb	PrintMsg			; If so, print as such
		Mov	DX, Offset BadFatMsg		; Set up message
		Cmp	AX, Word Ptr [DiskBlock + 16]	; See if sector in FAT
 		Jb	PrintMsg			; If so, print as such
		Mov	DX, Offset RootDirMsg		; Set up message
		Cmp	AX, Word Ptr [DiskBlock + 11]	; See if sector in dir
		Jb	PrintMsg			; If so, print as such
		Push	[StartSector]			; Save the sector
		Mov	AX, Word Ptr [DiskBlock + 6]	; Reserved sectors
		Mov	[StartSector], AX		; Start of first FAT
		Mov	CL, [DiskBlock + 15]		; Sectors for FAT
		Sub	CH, CH				; Zero out top byte		
		Call	ReadSectors			; Read in FAT
		Pop	[StartSector]			; Get back bad sector
		Mov	DX, Offset FatReadMsg		; Set up possible msg
		Jc	PrintMsg			; If read error, print
		Mov	AX, [StartSector]		; Get bad sector
		Sub	AX, Word Ptr [DiskBlock + 11]	; Subtract data start
		Mov	CL, [DiskBlock + 5]		; Sector Shift
		Shr	AX, CL				; Shift the sector
		Add	AX, 2				; AX is now cluster
		Push	ES			; Save ES for awhile				
		Mov	ES, [ReadSegment]	; ES segment of FAT
		Cmp	Word Ptr [DiskBlock + 13], 0FF0h; 12 or 16-bit FAT?
		Jge	Fat16Bit		; And jump accordingly
		Mov	BX, AX			; This is cluster number
		Mov	SI, AX			; So is this
		Shr	BX, 1			; This is one-half cluster
		Mov	AX, ES:[BX + SI]	; BX + SI = 1.5 CX
		Jnc	NoShift			; If no CY from shift, got it
		Mov	CL, 4			; If CY from shift must
		Shr	AX, CL			;   shift word 4 bits right

NoShift:	Or	AX, 0F000h		; Now put 1's in top bits
		Cmp	AX, 0F000h		; See if zero otherwise
		Jmp	Short CheckWord		; And continue checking

Fat16Bit:	Mov	BX, AX			; This is cluster number
		Shl	BX, 1			; Double it
		Mov	AX, ES:[BX]		; Pull out word from sector
		Or	AX, AX			; See if zero (unallocated)

CheckWord:	Pop	ES			; Get back ES
		Mov	DX, Offset NotInUseMsg	; Set up possible message
		Jz	PrintMsg		; If so, print message
		Mov	DX, Offset BadFlagMsg	; Set up possible message
		Cmp	AX, 0FFF7h		; See if cluster flagged bad
		Jz	PrintMsg		; If so, print message
		Mov	DX, Offset InUseMsg	; If not, cluster is in use

PrintMsg:	Call	StringWrite		; Print cluster disposition
		Ret				; And return

;	Read Sectors (CX = Number of Sectors, Return CY and AL for error)
;	-----------------------------------------------------------------

ReadSectors:	Push	BX			; Push all needed registers
		Push	CX
		Push	DX
		Push	DS
		Mov	AL, [DriveNum]		; Get the drive number code
		Sub	BX, BX			; Buffer address offset
		Mov	DX, [StartSector]	; Starting Sector
		Mov	DS, [ReadSegment]	; Buffer address segment
		Int	25h			; Absolute Disk Read
		Pop	BX			; Fix up stack
		Pop	DS			; Get back registers
		Pop	DX
		Pop	CX
		Pop	BX
		Ret				; Return to program 

;	Screen Display Routines
;	-----------------------

WordWrite:	Push	AX			; Push some registers
		Push	BX			; AX contains word to display
		Push	CX
		Push	DX
		Push	SI
		Mov	SI, Offset Divisors	; SI points to divisors
		Mov	CX, 4			; CL counter; CH zero blanker 

WordWriteLoop:	Mov	BX, [SI]		; Get divisor 
		Add	SI, 2			; Increment SI for next one
		Sub	DX, DX			; Prepare for division
		Div	BX			; Divide DX:AX by BX
		Push	DX			; Save remainder
		Or	CH, AL			; See if zero
		Jz	LeadZero		; If so, do not display it
		Add	AL, '0'			; Convert number to ASCII
		Mov	DL, AL			; Print out character
		Mov	AH, 2			;   by calling DOS
		Int	21h

LeadZero:	Pop	AX			; Get back remainder
		Dec	CL			; Decrement counter
		Jg	WordWriteLoop		; If CL still > 0, do it again
		Mov	CH, 1			; No more zero blanking
		Jz	WordWriteLoop		; Convert last digit to ASCII
		Pop	SI			; Get back pushed registers
		Pop	DX
		Pop	CX
		Pop	BX
		Pop	AX
		Ret

StringWrite:	Push	AX			; Displays string from DX
		Mov	AH, 9			;   to screen by calling DOS
		Int	21h
		Pop	AX
		Ret

EndProg		Label	Byte			; End of program
CSEG		EndS
		End	Entry
