;======================================================================
; 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
