;======================================================================
; TRACE 1.00 * Copyright (c) 1992, Robert L. Hummel
; PC Magazine Assembly Language Lab Notes
;
; TRACE reads a disk's FAT, tracing either a file or a subdirectory.
; It displays a list of cluster numbers used by the file and shows the
; equivalent DOS sector numbers.
;----------------------------------------------------------------------
CSEG		SEGMENT	PARA	PUBLIC	'CODE'
	ASSUME	CS:CSEG,DS:CSEG,ES:CSEG,SS:CSEG

		ORG	100H			;COM file format
ENTPT:		JMP	MAIN

;======================================================================
; Program data area.
;----------------------------------------------------------------------
CR		EQU	13
LF		EQU	10
BLANK		EQU	32

;----------------------------------------------------------------------
; Messages.
;----------------------------------------------------------------------
COPYRIGHT$	DB	CR,LF,"TRACE 1.00 ",254," Copyright (c) 1992,"
		DB	" Robert L. Hummel",CR,LF
		DB	"PC Magazine Assembly Language Lab Notes",LF
CRLF$		DB	CR,LF,"$"

USAGE$		DB	"Usage: TRACE pathname",CR,LF
		DB	" where pathname = [d:][path][filename.ext]"
		DB	CR,LF,"$"

TITLE$		DB	"F CLUSTER  DOS SECTORS",CR,LF,"$"

MEMERR$		DB	"Not Enough Memory To Execute$"

INVDRIVE$	DB	"Drive Letter Is Invalid$"
DRIVEERR$	DB	"Error Accessing Target Drive$"
NOTFOUND$	DB	"Pathname Doesn't Exist$"
NOROOT$		DB	"Can't TRACE Root Or JOINed Drive$"

BADCLUS$	DB	"Cluster In Allocation Chain Was Marked Bad$"
INVCLUS$	DB	"Invalid Cluster Number In Allocation Chain$"
FATERR$		DB	"Error Accessing Target FAT$"

CHAINENDS$	DB	"  ---- Chain Ends",CR,LF,LF
		DB	"Total Clusters  : $"

NUMFRAGS$	DB	"h",CR,LF,"Number Fragments: $"

SPC5$		DB	5 DUP(BLANK),"$"
;----------------------------------------------------------------------
; Program variables.
;----------------------------------------------------------------------
FILESPEC	DW	0			;Pointer to filespec

OLDDRIVE	DB	0			;Current disk drive
OLDDIR		DB	"\",64 DUP(0)		;Holds current dir

NEWDRIVE	DB	0			;Trace drive

FES_12		EQU	00FH
FES_16		EQU	0FFH
FES 		DB	FES_16			;Assume 16-bit FAT

HUGEDISK	DW	0			;Non-zero if huge disk

NCLUS		DD	0
NFRAGS		DD	0

;----------------------------------------------------------------------
; Drive data structure. Holds info about the disk retrieved from the
; Drive Parameter Block.
;----------------------------------------------------------------------
DRIVE_DATA	LABEL	BYTE
BPS		DW	0			;Bytes per sector
SPC		DW	0			;Sectors per cluster
FFS		DW	0			;First FAT sector
FDS		DW	0			;First Data sector
MCN		DW	0			;Maximum cluster number

;----------------------------------------------------------------------
; This file control block is used to open the directory entry.
;----------------------------------------------------------------------
XFCB		LABEL	BYTE			;Extended FCB structure
		DB	0FFH			;Extended FCB signature
		DB	5 DUP (0)		;(Reserved)
		DB	16H			;Search attribute

		DB	0			;Default drive
		DB	".",7 DUP(BLANK)	;Current dir's name
		DB	3 DUP(BLANK)		; and extension
		DW	0			;Current block #
		DW	0			;Record size
		DD	0			;Size of file (bytes)
		DW	0			;Date
		DW	0			;Time
		DB	8 DUP(0)		;(Reserved)
		DB	0			;Current record #
		DD	0			;Random record #
;----------------------------------------------------------------------
; The extended directory entry structure written to the DTA by Find
; First.
;----------------------------------------------------------------------
XDIR		LABEL	BYTE			;Extended directory

		DB	0FFH			;Extended FCB signature
		DB	5 DUP (0)		;(Reserved)
		DB	0			;Search attribute used
		DB	0			;Drive
		DB	8 DUP (0)		;Name
		DB	3 DUP (0)		;Extension
		DB	0			;File's attribute
		DB	10 DUP (0)		;Reserved
		DW	0			;Time
		DW	0			;Date
FIRSTCLUSTER	DW	0			;Starting cluster
		DD	0			;File's size

;----------------------------------------------------------------------
; MAIN procedure.
;----------------------------------------------------------------------
MAIN		PROC	NEAR
	ASSUME	CS:CSEG,DS:CSEG,ES:CSEG,SS:CSEG

;----------------------------------------------------------------------
; Initialize and display program title.
;----------------------------------------------------------------------
		CLD				;String moves forward

		MOV	CX,AX			;Save drive status

		MOV	AH,9			;Display string fn
		MOV	DX,OFFSET COPYRIGHT$	; located here
		INT	21H			; Thru DOS
;----------------------------------------------------------------------
; If the first command line argument contained a invalid drive spec, AL
; will = FFh. If so, report an error to the user.
;----------------------------------------------------------------------
		CMP	CL,0FFH			;Check for invalid drv
		JNE	M_1

		MOV	AH,9			;Display string
		MOV	DX,OFFSET INVDRIVE$	; in DX
		INT	21H			; Thru DOS

;----------------------------------------------------------------------
; Skip a line and terminate the program.
;----------------------------------------------------------------------
M_EXIT:
		MOV	AH,9			;Display string
		MOV	DX,OFFSET CRLF$		; located here
		INT	21H			; Thru DOS

		MOV	AX,4C00H		;Terminate program
		INT	21H			; Thru DOS
;----------------------------------------------------------------------
; We need the current drive either to use during disk reads or if we
; have to change the default drive and later restore it.
;----------------------------------------------------------------------
M_1:
		MOV	AH,19H			;Get current drive #
		INT	21H			; Thru DOS

		MOV	[OLDDRIVE],AL		;Save current drive
		MOV	[NEWDRIVE],AL		;Initialize cur drive
;----------------------------------------------------------------------
; Find the first non-blank character on the command line. If no
; arguments were specified, display the usage message.
;----------------------------------------------------------------------
M_2A:
		MOV	SI,81H			;Cmdline adr in PSP

		SUB	CX,CX			;Clear to 0
		ADD	CL,[SI-1]		;Chars on command line
		JNZ	M_2D
M_2B:
		MOV	AH,9			;Display string
		MOV	DX,OFFSET USAGE$	; located here
		INT	21H			; Thru DOS
		JMP	M_EXIT			;Don't change path/drv
M_2D:
		LODSB				;Get a character

		CMP	AL,BLANK		;Skip leading blanks
		JNE	M_2E

		LOOP	M_2D			;Continue scan
		JCXZ	M_2B			;Jump if all blanks
M_2E:
;----------------------------------------------------------------------
; Scan to find the end of the command line.
;----------------------------------------------------------------------
		DEC	SI			;Point to first char
		MOV	DI,SI			; and save it
M_2F:
		LODSB				;Get char

		CMP	AL,BLANK		;Scan non-blanks
		JE	M_2G

		LOOP	M_2F
		INC	SI			;Compensate for DEC
M_2G:
		DEC	SI			;Back up to blank
		MOV	BYTE PTR [SI],0		;Make ASCIIZ
		XCHG	SI,DI			;SI=start DI=end
;----------------------------------------------------------------------
; If the spec contained a drive, make it the current drive.
; DOS will have placed the drive number in the first FCB.
; If A: was specified, the FCB will contain 1, not 0. Adjust the value.
; If the FCB contains 0, it means no drive was specified.
; If present, remove the d: drive spec from the command line argument.
;----------------------------------------------------------------------
		MOV	DL,DS:[5CH]		;Get drive from FCB
		DEC	DL			;If 0,turns sign bit on
		JS	M_3

		MOV	[NEWDRIVE],DL		;Save working drive

		MOV	AH,0EH			;Select current drive
		INT	21H			; Thru DOS

		INC	SI			;Skip "d:"
		INC	SI			; in spec

		CMP	BYTE PTR [SI],0		;Spec empty now?
		JE	M_2B
M_3:
		MOV	[FILESPEC],SI		;Save pointer to spec
;----------------------------------------------------------------------
; Save the current directory on this drive so we can restore it later.
;----------------------------------------------------------------------
		MOV	AH,47H			;Get current directory
		SUB	DL,DL			; on current drive
		MOV	SI,OFFSET OLDDIR+1	;Store here
		INT	21H			; Thru DOS
		JNC	M_4

		MOV	AH,9
		MOV	DX,OFFSET DRIVEERR$	;Did we have a problem?
		INT	21H
		JMP	SHORT M_7B		;Restore drive only
M_4:
;----------------------------------------------------------------------
; The Find First function writes a directory structure for the found
; file to the area of memory pointed to by the DTA.
;----------------------------------------------------------------------
		MOV	AH,1AH			;Set DTA
		MOV	DX,OFFSET XDIR		; to this area
		INT	21H			; Thru DOS
;----------------------------------------------------------------------
; Assume that the command line argument is a directory spec.
; Attempt to change to the new directory.
; If the call works, the filespec is for a directory.
;----------------------------------------------------------------------
		MOV	AH,3BH			;Set directory
		MOV	DX,[FILESPEC]		; to this string
		INT	21H			; Thru DOS
		JC	M_5A
;----------------------------------------------------------------------
; If the [.] entry is found, the Find First function fills in the DTA
; with returns directory structure in XDIR.
;----------------------------------------------------------------------
		MOV	AH,11H			;Find First Match
		MOV	DX,OFFSET XFCB		; for this FCB
		INT	21H			; Thru DOS

		OR	AL,AL			;AL=0 if successful
		JZ	M_9
;----------------------------------------------------------------------
; The spec was a root directory (since we CD'd successfully to it), but
; not a subdir (since it didn't contain a "." file entry). Since we
; can't trace the root directory, command line must be in error.
;----------------------------------------------------------------------
		MOV	AH,9			;Display string
		MOV	DX,OFFSET NOROOT$	; located here
		INT	21H			; Thru DOS

		JMP	SHORT M_7A
;----------------------------------------------------------------------
; The spec wasn't a directory since the CD call failed. Assume that
; it's a file and parse the spec backwards to the first backslash, then
; try to set the resultant directory.
;----------------------------------------------------------------------
M_5A:
		MOV	CX,DI			;From end of cmd line
		SUB	CX,[FILESPEC]		; subtract start => len
M_5B:
		DEC	DI			;Back up a char
		CMP	BYTE PTR [DI],"\"	; is it a backslash?
		LOOPNE	M_5B
;----------------------------------------------------------------------
; If we exited the loop because we ran out of characters (CX=0) then
;   we'll assume that only a filename was specified.
; Otherwise, a backslash was found.
;----------------------------------------------------------------------
		JE	M_6A

		DEC	DI			;Compensate for INC
		JMP	SHORT M_8A
;----------------------------------------------------------------------
; DI points to the last backslash in the pathname.
; Overwrite the backslash with a 0, and make the path the current dir.
; Separate the path from the filespec. If the path string is a single
; backslash, handle it special. Otherwise, if we still can't, then the
; argument was invalid.
;----------------------------------------------------------------------
M_6A:
		MOV	BYTE PTR [DI],0		;Change path to ASCIIZ
		MOV	DX,[FILESPEC]		;Is start of filespec
		CMP	DI,DX			; this backslash?
		JNE	M_6B

		DEC	DX			;Back up
		MOV	BYTE PTR [DI-1],"\"	;Create a spec
M_6B:
		MOV	AH,3BH			;Set directory
		INT	21H			; Thru DOS
		JNC	M_8A
M_6C:
		MOV	DX,OFFSET NOTFOUND$	;Assume an error
		MOV	AH,9			;Display string fn
		INT	21H			; Thru DOS
;----------------------------------------------------------------------
; Restore the directory on the target drive.
;----------------------------------------------------------------------
M_7A:
		MOV	AH,3BH			;Set directory
		MOV	DX,OFFSET OLDDIR	; to what we found
		INT	21H			; Thru DOS
;----------------------------------------------------------------------
; Restore the original drive.
;----------------------------------------------------------------------
M_7B:
		MOV	AH,0EH			;Set current drive
		MOV	DL,[OLDDRIVE]		; to original
		INT	21H			; Thru DOS

		JMP	M_EXIT
;----------------------------------------------------------------------
; Parse the filename into the FCB.
;----------------------------------------------------------------------
M_8A:
		MOV	CX,9			;8 chars + dot

		MOV	SI,OFFSET XFCB+8	;Name field=dest
		INC	DI			;Skip \
M_8B:
		MOV	AL,BYTE PTR [DI]	;Get char
		INC	DI			; and point to next

		CMP	AL,"."			;Was it the separator?
		JNE	M_8C

		MOV	CX,3			;3 extension chars max
		MOV	SI,OFFSET XFCB+16	;Extension dest
		JMP	M_8B
M_8C:
		OR	AL,AL			;Was it end of data?
		JZ	M_8D

		MOV	BYTE PTR [SI],AL	;Put char in XFCB
		INC	SI			; advance dest pointer

		LOOP	M_8B			;Continue parsing

		CMP	BYTE PTR [DI],0		;Shouldn't be any left
		JNE	M_6C
M_8D:
;----------------------------------------------------------------------
; Now use Find First to locate the filespec that remains.
;----------------------------------------------------------------------
		MOV	AH,11H			;Find First Match
		MOV	DX,OFFSET XFCB		; for this FCB
		INT	21H			; Thru DOS

		OR	AL,AL			;AL=0 if successful
		JNZ	M_6C
;----------------------------------------------------------------------
; Now we know the starting cluster number of the file/directory.
; We need to determine some information in order to trace the FAT.
;
; BPS - Bytes per DOS sector. So we can read the FAT using Int 25h.
;
; SPC - Cluster mask = sectors per cluster - 1. Used to determine
;	actual disk space used by the file/directory.
;
; FFS - First FAT Sector. The logical sector number that contains the
;	first sector of the FAT.
;
; FDS - First Data Sector. The logical sector number of the first data
;	sector on the disk.
;
; MCN - Maximum cluster number. So we can determine if a FAT entry is
;	invalid.
;
; FES - FAT entry size. This is 12 or 16 bits. Needed to calculate the
;	next entry in an allocation chain. Calculated indirectly.
;
;----------------------------------------------------------------------
; Use the undocumented function Get Default DPB to examine the disk.
; Copy the needed information to local storage.
;----------------------------------------------------------------------
M_9:
		MOV	AH,1FH			;Get Default DPB
		INT	21H			; Undocumented DOS fn
	ASSUME	DS:NOTHING			;Function changes DS

		MOV	DI,OFFSET DRIVE_DATA	;Dest=Drive data area

		MOV	AX,DS:[BX][2]		;Bytes per sector
		STOSW				;Write to ES:DI

		MOV	AL,DS:[BX][4]		;Sector mask
		INC	AL			; +1=SPC
		SUB	AH,AH			;Zero extend it
		STOSW

		MOV	AX,DS:[BX][6]		;# reserved sectors
		STOSW				; = first FAT sector

		MOV	AX,DS:[BX][0BH]		;First Data Sector
		STOSW

 		MOV	AX,DS:[BX][0DH]		;Max cluster number
		STOSW

		PUSH	CS			;Put CS segment
		POP	DS			; into DS
	ASSUME	DS:CSEG
;----------------------------------------------------------------------
; From the information in the DPB, determine if this disk is using a
; 12-bit or 16-bit FAT. (Initial value is 16-bit.)
; IF (# clusters < 4087) THEN FAT=12-bit ELSE FAT=16-bit
;----------------------------------------------------------------------
		CMP	AX,4086			;Check # of clusters
		JA	M_10
		MOV	BYTE PTR [FES],FES_12	;Set FAT type flag
M_10:
;----------------------------------------------------------------------
; If this drive is formatted as a huge partition, it will have more
; than 65536 total sectors. To determine this, multiply the number of
; clusters (in AX) by the number of sectors per cluster. After the
; multiplication, DX will hold the high-order word of the result. If
; the product is greater than 65535, DX will be non-zero.
;----------------------------------------------------------------------
		MOV	DX,[SPC]		;Sectors per cluster
		MUL	DX			;*# clusters
		MOV	[HUGEDISK],DX		;Use DX as flag
;----------------------------------------------------------------------
; Make sure we've got enough memory to hold one FAT sector which we
; read during the tracing procedure.
;----------------------------------------------------------------------
		MOV	AX,SP			;Get the end of segment
		SUB	AX,OFFSET FATBUF+512	; subtract prog/stack

		SHR	AX,1			;Divide by 2 to see if
		CMP	AX,[BPS]		; enough for 2 sectors?
		JAE	M_11C

		MOV	DX,OFFSET MEMERR$	;Assume memory error
M_11B:
		MOV	AH,9
		INT	21H
		JMP	M_7A			;Restore dir/drive
M_11C:
;----------------------------------------------------------------------
; Now trace the spec's allocation chain through the FAT, printing out a
; status report as we go.
;----------------------------------------------------------------------
		MOV	AH,9			;Display string
		MOV	DX,OFFSET TITLE$	; located here
		INT	21H			; Thru DOS

		SUB	DI,DI			;Previous cluster #
		MOV	AX,[FIRSTCLUSTER]	;Start here
M_12A:
		CALL	SHOW_CLUSTER		;Display cluster info
		JNC	M_12B

		OR	DX,DX			;If CY, DX!=0 is error
		JNZ	M_11B
		JMP	SHORT M_13
M_12B:
		ADD	WORD PTR [NCLUS],1	;Count the cluster
		ADC	WORD PTR [NCLUS+2],0	; in the total

		CALL	NEXT_CLUSTER		;Get next
		JC	M_11B			;CY = error
		JMP	M_12A			; else continue
;----------------------------------------------------------------------
; Summarize the spec's cluster info.
;----------------------------------------------------------------------
M_13:
		MOV	AH,9			;Display string
		MOV	DX,OFFSET CHAINENDS$	; located here
		INT	21H			; Thru DOS

		MOV	AX,WORD PTR [NCLUS+2]	;High word
		CALL	HEXWORD
		MOV	AX,WORD PTR [NCLUS]	;Low word
		CALL	HEXWORD

		MOV	AH,9			;Display string
		MOV	DX,OFFSET NUMFRAGS$	; located here
		INT	21H			; Thru DOS

		MOV	AX,WORD PTR [NFRAGS+2]	;High word
		CALL	HEXWORD
		MOV	AX,WORD PTR [NFRAGS]	;Low word
		CALL	HEXWORD

		MOV	AH,2			;Display char
		MOV	DL,"h"			; in DL
		INT	21H			; Thru DOS

		JMP	M_7A

MAIN		ENDP

;======================================================================
; SHOW_CLUSTER (Near)
;
; If the cluster number is valid, display it and translate it to the
; equivalent DOS sectors numbers.
;----------------------------------------------------------------------
; Entry:
;	AX = current cluster number
;	DI = previous cluster number
; Exit:
;   If NC, success
;	AX = current cluster number
;	DI = current cluster number
;   If CY,
;	DX  = 0, chain ended normally
;	   != 0, offset of error message
;----------------------------------------------------------------------
; Changes: BX DX DI BP
;----------------------------------------------------------------------
SHOW_CLUSTER	PROC	NEAR
	ASSUME	CS:CSEG, DS:CSEG, ES:CSEG, SS:CSEG

		PUSH	AX			;Save register
;----------------------------------------------------------------------
; Cluster numbers 0 and 1 are reserved.
;----------------------------------------------------------------------
		MOV	DX,OFFSET INVCLUS$	;Error message
		CMP	AX,1			;0 or 1?
		JBE	SC_ERR
;----------------------------------------------------------------------
; A value of (F)FF8h or greater indicates that this is the last cluster
; in the allocation chain.
;----------------------------------------------------------------------
		SUB	DX,DX			;Assume end of chain

		MOV	BX,0FFF7H		;16-bit value
		AND	BH,[FES]		;Mask if needed

		CMP	AX,BX			;Check value
		JB	SC_2
		JA	SC_ERR
;----------------------------------------------------------------------
; Drop thru here if AX=(F)FF7h, indicating a bad cluster. This is bad
; news and indicates some file corruption.
;----------------------------------------------------------------------
		MOV	DX,OFFSET BADCLUS$	;Error message
SC_ERR:
		STC				;Carry=error
SC_EXIT:
		POP	AX			;Restore register
		RET
;----------------------------------------------------------------------
; Test for a valid cluster number.
;----------------------------------------------------------------------
SC_2:
		MOV	DX,OFFSET INVCLUS$	;Error message
		CMP	AX,[MCN]		;Max cluster number
		JAE	SC_ERR
;----------------------------------------------------------------------
; See if this cluster number is sequential with the previous one. If
; not, it begins a new fragment.
;----------------------------------------------------------------------
		MOV	DL,BLANK		;Assume consecutive

		INC	DI			;Point 1 past prev clus
		CMP	AX,DI			;Is it current cluster?
		JE	SC_3

		ADD	WORD PTR [NFRAGS],1	;Increase 32-bit frag
		ADC	WORD PTR [NFRAGS+2],0	; counter by 1
		MOV	DI,AX			;Update cluster tracker

		MOV	DL,175			;Fragment character
SC_3:
		MOV	BP,AX			;Save cluster in BP

		MOV	AH,2			;Display char in DL
		INT	21H			; Thru DOS

		MOV	AH,2			;Display char
		MOV	DL,BLANK		; in DL
		INT	21H			; Thru DOS
;----------------------------------------------------------------------
; Display the cluster number and translate to equivalent DOS sector
; numbers.
;----------------------------------------------------------------------
		MOV	AX,BP			;Retrieve cluster #
		CALL	HEXWORD			;Display AX

		MOV	AH,9			;Display string
		MOV	DX,OFFSET SPC5$		; 5 spaces
		INT	21H			; Thru DOS
;----------------------------------------------------------------------
; Convert the cluster number in AX into an equivalent series of DOS
; sector number.
;----------------------------------------------------------------------
		MOV	AX,BP			;Get back cluster #
		SUB	AX,2			;Disregard ID bytes

		MOV	BX,[SPC]		;Sectors per cluster
		MUL	BX			;DX:AX=sectors

		ADD	AX,[FDS]		;Offset from FDS
		ADC	DX,0			;In case 32-bit #

		PUSH	DX			;Save high-order word
		PUSH	AX			; and low-order word

		XCHG	AX,DX			;Get high-order word
		CALL	HEXWORD			; display it
		MOV	AX,DX			;Then low-order word
		CALL	HEXWORD

		MOV	AH,2			;Display char
		MOV	DL,"-"			; in DL
		INT	21H			; Thru DOS

		POP	AX			;Retrieve originals
		POP	DX

		DEC	BX
		ADD	AX,BX			;Add cluster length
		ADC	DX,0			;In case 32-bit #

		XCHG	AX,DX			;Get high-order word
		CALL	HEXWORD			; display it
		MOV	AX,DX			;Then low-order word
		CALL	HEXWORD
;----------------------------------------------------------------------
; Start a new line and return to caller.
;----------------------------------------------------------------------
		MOV	AH,9			;Display string
		MOV	DX,OFFSET CRLF$		; at DX
		INT	21H			; Thru DOS

		CLC				;Say success
		JMP	SC_EXIT

SHOW_CLUSTER	ENDP

;======================================================================
; NEXT_CLUSTER (Near)
;
; Given a cluster number, reads the appropriate FAT sector into memory,
; looks up the entry in the FAT, and retrieves the number of the next
; cluster in the chain. Assumes the passed cluster number is valid.
;----------------------------------------------------------------------
; Entry:
;	AX = valid cluster number
; Exit :
;   If NC,
;	AX = next cluster number
;   If CY,
;	DX = error message
;----------------------------------------------------------------------
; Changes: AX BX CX DX BP
;----------------------------------------------------------------------
FATSECT		DW	-1			;# of FAT sect in mem

NEXT_CLUSTER	PROC	NEAR
	ASSUME	CS:CSEG, DS:CSEG, ES:CSEG, SS:CSEG
;----------------------------------------------------------------------
; From the cluster number, determine in which sector of the FAT this
; cluster is located. 12-bit and 16-bit FATs are decoded differently.
;----------------------------------------------------------------------
		SUB	DX,DX			;Prepare for long DIV

		CMP	[FES],FES_12		;Check FAT type
		JNE	NC_1A
;----------------------------------------------------------------------
; Decode a 12-bit FAT entry.
; 1. Translate the 12-bit cluster number into byte offset into FAT.
;    Since the maximum cluster number for a 12-bit FAT is 4086, and
;    since each FAT entry is 1.5 bytes, the maximum offset into the
;    FAT is 6129 [17F1h] bytes. This will always fit into a word.
;----------------------------------------------------------------------
		SUB	BP,BP			;Create a zero

		MOV	BX,AX			;Multiply
		ADD	AX,AX			; AX
		ADD	AX,BX			; by 3
		SHR	AX,1			;Divide by 2

		RCL	BP,1			;Save carry flag state
		JMP	SHORT NC_1B
;----------------------------------------------------------------------
; Decode a 16-bit FAT entry.
; 1. Translate cluster number into byte offset into FAT.
;    Note that while the cluster number can never exceed 16 bits, the
;    byte offset into the FAT can.
;----------------------------------------------------------------------
NC_1A:
		SHL	AX,1			;Multiply AX by 2
		RCL	DX,1			;Move CY into DX
NC_1B:
;----------------------------------------------------------------------
; 2. Divide the offset by the number of bytes in a sector to determine
;    into which sector of the FAT this offset points.
;----------------------------------------------------------------------
		MOV	CX,[BPS]		;Bytes per sector
		DIV	CX			;AX=sects, DX=bytes
;----------------------------------------------------------------------
; 3. If the required FAT sector (in AX) isn't the first one in memory,
;    load it and the one following it. (This accommodates a 12-bit FAT
;    entry that spans a sector boundary.)
;----------------------------------------------------------------------
		CMP	AX,[FATSECT]		;Is this sect in mem?
		JE	NC_2A
;----------------------------------------------------------------------
; Read in the FAT sectors starting at AX.
;----------------------------------------------------------------------
		MOV	[FATSECT],AX		;Say this one's here

		CALL	READ_FAT
		JNC	NC_2A

		MOV	DX,OFFSET FATERR$	;Report problem, CY set
NC_EXIT:
		RET
;----------------------------------------------------------------------
; 4. Read a word at the specified offset (in DX). If 16-bit FAT, we're
;    done. For a 12-bit FAT, mask off the appropriate bits.
;----------------------------------------------------------------------
NC_2A:
		MOV	BX,DX			 ;Offset is in DX
		MOV	AX,WORD PTR [FATBUF][BX] ;Get the word

		CMP	[FES],FES_12		;Check FAT type
		JNE	NC_2C
;----------------------------------------------------------------------
; For 12-bit FATs, if the original cluster was even, use the lower 12
; bits of the word. If the original cluster was odd, use the upper 12
; bits of the word.
;----------------------------------------------------------------------
		OR	BP,BP			 ;Was mult whole word?
		JZ	NC_2B

		MOV	CL,4			;Shift count
		SHR	AX,CL			;Use upper 12 bits
		JMP	SHORT NC_2C
NC_2B:
		AND	AH,0FH			;Use lower 12 bits
NC_2C:
		CLC				;Say success
		JMP	NC_EXIT

NEXT_CLUSTER	ENDP

;======================================================================
; READ_FAT (Near)
;
; This routine reads a sector from the disk into the FATBUF. It
; automatically adjusts for huge (type 6) disks.
;----------------------------------------------------------------------
; Entry:
;	AX = Number of the FAT sector to read.
; Exit: None
;----------------------------------------------------------------------
; Changes: AX BX CX
;----------------------------------------------------------------------
DISK_PACKET	LABEL	BYTE

FIRST_SECTOR	DW	0,0			;32-bit sector number
		DW	1			;Number of sectors
		DW	OFFSET FATBUF		;Buffer offset
BUF_SEG		DW	0			; and segment


READ_FAT	PROC	NEAR
	ASSUME	CS:CSEG, DS:CSEG, ES:CSEG, SS:CSEG

		PUSH	DX			;Save register
;----------------------------------------------------------------------
; Convert the relative FAT sector to an absolute logical sector number.
;----------------------------------------------------------------------
		ADD	AX,[FFS]		;Convert to disk sector

;----------------------------------------------------------------------
; If this is not a huge disk, use the normal function protocol.
;----------------------------------------------------------------------
		CMP	[HUGEDISK],0
		JNE	RFS_1A

		MOV	DX,AX			;Starting sector
		MOV	AL,[NEWDRIVE]		;Read from this drive
		MOV	BX,OFFSET FATBUF	;Put data here
		MOV	CX,2			;Read two sectors
		JMP	SHORT RFS_1B
;----------------------------------------------------------------------
; Use the type 6 disk protocol. Because it's a 16-bit FAT, we only have
; to read one sector.
;----------------------------------------------------------------------
RFS_1A:
		MOV	[FIRST_SECTOR],AX
		MOV	[BUF_SEG],DS
		MOV	AL,[NEWDRIVE]		;Read from this drive
		MOV	BX,OFFSET DISK_PACKET
		MOV	CX,-1			;Signal type 6
RFS_1B:
		INT	25H			;Direct disk read
		POP	DX			;Discard old flags

		POP	DX			;Restore register
		RET

READ_FAT	ENDP

;======================================================================
; HEXWORD - Write AX as 4 hex digits to std out
;----------------------------------------------------------------------
; Entry:
;	AX = value to display
; Exit : None
;----------------------------------------------------------------------
; CHANGES: None
;----------------------------------------------------------------------
HEXWORD		PROC	NEAR
	ASSUME	CS:CSEG, DS:CSEG, ES:NOTHING, SS:CSEG

		PUSH	CX
		PUSH	DX

		MOV	CX,4
H_1:
		PUSH	CX
		MOV	CL,4
		ROL	AX,CL
		POP	CX

		PUSH	AX

		AND	AL,0FH
		ADD	AL,90H			;Convert AL to ASCII
		DAA
		ADC	AL,40H
		DAA

		MOV	AH,2			;Display char
		MOV	DL,AL			; in DL
		INT	21H			; Thru DOS

		POP	AX

		LOOP	H_1

		POP	DX
		POP	CX
		RET

HEXWORD		ENDP

;======================================================================

FATBUF		EQU	$

CSEG		ENDS
		END	ENTPT
