        TITLE   'fat - walk through FAT Tables for rxdos'
        PAGE 59, 132
        .LALL

        ;''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''';
        ;  FAT Walk Through FAT Tables for rxdos                        ;
        ;...............................................................;

        ;''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''';
        ;  Real Time Dos                                                ;
        ;- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -;
        ;                                                               ;
        ;  This material  was created as a published version  of a DOS  ;
        ;  equivalent product.   This program  logically  functions in  ;
        ;  the same way as  MSDOS functions and it  is  internal  data  ;
        ;  structure compliant with MSDOS 6.0                           ;
        ;                                                               ;
        ;  This product is distributed  AS IS and contains no warranty  ;
        ;  whatsoever,   including  warranty  of   merchantability  or  ;
        ;  fitness for a particular purpose.                            ;
        ;                                                               ;
        ;                                                               ;
        ;  (c) Copyright 1990, 1997. Api Software and Mike Podanoffsky  ;
        ;      All Rights Reserved Worldwide.                           ;
        ;                                                               ;
        ;  This product is protected under copyright laws and  may not  ;
        ;  be reproduced  in whole  or in part, in any form  or media,  ;
        ;  included but not limited to source listing, facsimile, data  ;
        ;  transmission, cd-rom, or  floppy disk without the expressed  ;
        ;  written consent of the author.                               ;
        ;                                                               ;
        ;  License  for  distribution  for commercial  use  or  resale  ;
        ;  required from:                                               ;
        ;                                                               ;
        ;  Api Software                                                 ;
        ;  12 South Walker Street                                       ;
        ;  Lowell,  MA   01851                                          ;
        ;                                                               ;
        ;  internet: mikep@world.std.com                                ;
        ;                                                               ;
        ;- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -;
        ;  Compile with MASM 5.1                                        ;
        ;...............................................................;

        include rxdosmac.asm
        include rxdosdef.asm

RxDOS   SEGMENT PUBLIC 'CODE'
        assume cs:RxDOS, ds:RxDOS, es:RxDOS, ss:RxDOS

        ;''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''';
        ;  Walk Through FAT Tables                                      ;
        ;...............................................................;

        public AllocateCluster
        public AllocateInitCluster
        public AmountFreeSpace
        public AppendCluster
        public getNextCluster
        public ReleaseClusterChain
        public scanClusterMap
        public updateClusterValue
        public _FATReadRandom

        extrn CCBChanged                        : near
        extrn computeLogSectorNumber            : near
        extrn getDPB                            : near
        extrn getAddrDPB                        : near
        extrn readBuffer                        : near
        extrn readSelBuffer                     : near
        extrn SelBuffer                         : near
        extrn CCBChanged                        : near
        extrn updateChangedCCB                  : near
        extrn updateAllChangedCCBBuffers        : near
        extrn _DebugInterruptTrap               : near
        extrn DevRead                           : near

        ;''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''';
        ;  Definitions                                                  ;
        ;...............................................................;

MINCLUSTER      EQU 2                                   ; min cluster value


        ;''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''';
        ;  Get Next Cluster                                             ;
        ;- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -;
        ;                                                               ;
        ;  Input:                                                       ;
        ;   ax     drive                                                ;
        ;   dx     current cluster                                      ;
        ;                                                               ;
        ;  Output:                                                      ;
        ;   ax     drive                                                ;
        ;   dx     next cluster                                         ;
        ;   zr     if end of cluster chain.                             ;
        ;                                                               ;
        ;  Preserves:                                                   ;
        ;   es, di, si, cx, bx, ax                                      ;
        ;...............................................................;

getNextCluster:

        Entry
        def _drive, ax
        def _cluster, dx

        saveRegisters es, di, si, cx, bx, ax
        call getAddrDPB                                 ; (es:bx) Device Parameter Block
        mov dx, -1                                      ; presume end if error
        jc getNextCluster_Return                        ; exit if error -->

;- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
;  determine whether its 12 or 16 bit FAT entries
;- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
        mov ax, word ptr [ _cluster ][ bp ]             ; get cluster #
        cmp ax, MINCLUSTER                              ; less than min cluster ?
        jc getNextCluster_04                            ; exit with -1 -->
        cmp ax, word ptr es:[ _dpbMaxClusterNumber ][ bx ]
        jc getNextCluster_08                            ; if valid cluster # -->
        
getNextCluster_04:
        jmp short getNextCluster_Return

getNextCluster_08:
        xor dx, dx
        test word ptr es:[ _dpbMaxClusterNumber ][ bx ], 0F000h
        jnz getNextCluster_16Bits                       ; if 16 -->

;- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
;  12 bit FAT entries
;- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

getNextCluster_12Bits:
        mov ax, word ptr [ _drive ][ bp ]               ; get drive
        mov dx, word ptr [ _cluster ][ bp ]             ; and cluster
        call _get_12Bit_ClusterValue
        jmp short getNextCluster_Return

;- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
;  16 bit FAT entries
;- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

getNextCluster_16Bits:
        mov cx, word ptr es:[ _dpbBytesPerSector ][ bx ]
        shr cx, 1
        div cx                                          ; FAT sector/ Offset

      ; ax will contain FAT sector
      ; dx will contain byte offset into FAT sector

        add dx, dx                                      ; make word offset
        push dx

        xor cx, cx
        mov dx, word ptr es:[ _dpbFirstFAT ][ bx ]      ; where is first FAT table ?
        add dx, ax                                      ; add offset required
        mov ax, word ptr [ _drive ][ bp ]               ; get drive
        call readBuffer                                 ; read FAT Table
        or byte ptr es:[ ccbStatus ][ di ], ( ccb_isFAT )

        pop bx                                          ; word offset into FAT table
        mov dx, word ptr es:[ ccbData ][ bx + di ]      ; get FAT word

        mov ax, dx
        and ax, 0FFF8h                                  ; FAT value, 12 bit entries.
        cmp ax, 0FFF8h                                  ; end of chain ?
        jnz getNextCluster_Return                       ; no -->
        mov dx, -1                                      ; if end, set end value

;- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
;  return
;- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

getNextCluster_Return:
        restoreRegisters ax, bx, cx, si, di, es
        cmp dx, -1                                      ; set if end of chain
        Return

        ;''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''';
        ;  Scan Ahead Cluster Map                                       ;
        ;- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -;
        ;                                                               ;
        ;  Input:                                                       ;
        ;   ax     drive                                                ;
        ;   cx     max clusters to scan                                 ;
        ;   dx     current cluster                                      ;
        ;                                                               ;
        ;  Output:                                                      ;
        ;   cx     # clusters that can be sequentially read             ;
        ;   dx     last cluster value                                   ;
        ;...............................................................;

scanClusterMap:

        Entry
        def  _drive, ax
        def  _cluster, dx
        def  _maxClustersToScan, cx
        def  _nextCluster
        def  _clusterCount, 0001

        or cx, cx                                       ; zero max clusters ?
        jz scanClusterMap_22                            ; yes, exit -->
        mov word ptr [ _clusterCount ][ bp ], 0000      ; cluster count

scanClusterMap_12:
        inc word ptr [ _clusterCount ][ bp ]            ; cluster count

        mov cx, dx                                      ; save original request
        call getNextCluster                             ; get next cluster
        cmp dx, -1                                      ; end ?
        jz scanClusterMap_22                            ; yes -->

        sub cx, dx                                      ; sequential clusters are ...
        cmp cx, -1                                      ;  ... identified by a -1 offset
        jnz scanClusterMap_22                           ; if still sequential -->

        dec word ptr [ _maxClustersToScan ][ bp ]       ; scan more ?
        jnz scanClusterMap_12                           ; yes -->

scanClusterMap_22:
        mov cx, word ptr [ _clusterCount ][ bp ]        ; # sequential clusters
        Return

        ;''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''';
        ;  Update Cluster Value                                         ;
        ;- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -;
        ;                                                               ;
        ;  Updates the value given for any cluster.                     ;
        ;                                                               ;
        ;  Input:                                                       ;
        ;   ax     drive                                                ;
        ;   dx     cluster                                              ;
        ;   cx     update value                                         ;
        ;                                                               ;
        ;  Returns:                                                     ;
        ;   cx     original contents in cluster cell                    ;
        ;                                                               ;
        ;...............................................................;

updateClusterValue:

        Entry
        def _drive, ax
        def _cluster, dx
        def _value, cx
        def _sectorsize
        ddef _sector

        saveRegisters es, di, si, dx, bx, ax

;- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
;  determine whether its 12 or 16 bit FAT entries
;- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
        call getAddrDPB                                 ; (es:bx) Device Parameter Block
        jc updateClusterValue_04                        ; if error -->

        mov ax, word ptr [ _cluster ][ bp ]             ; get cluster #
        cmp ax, MINCLUSTER                              ; invalid number 
        jc updateClusterValue_04                        ; exit -->
        cmp ax, word ptr es:[ _dpbMaxClusterNumber ][ bx ]
        jc updateClusterValue_08                        ; if valid cluster # -->

updateClusterValue_04:
        mov dx, -1
        cmp dx, -1                                      ; sets zero (end of list)
        stc                                             ; and set carry (error )
        jmp updateClusterValue_Return

updateClusterValue_08:
        xor dx, dx
        
        test word ptr es:[ _dpbMaxClusterNumber ][ bx ], 0F000h
        jz updateClusterValue_12Bits                    ; if 12 -->
        jmp updateClusterValue_16Bits                   ; if 16 -->

;- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
;  12 bit FAT entries
;- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

updateClusterValue_12Bits:
        mov cx, ax
        add ax, ax
        add ax, cx                                      ; nibble address

        mov cx, word ptr es:[ _dpbBytesPerSector ][ bx ]
        mov word ptr [ _sectorsize ][ bp ], cx
        dec word ptr [ _sectorsize ][ bp ]
        shl cx, 1                                       ; nibbles / sector
        div cx                                          ; sector to read

      ; ax will contain sector
      ; dx will contain nibble offset

        shr dx, 1                                       ; word offset
        push dx                                         ; word offset

        xor cx, cx                                      ; 32 bit address
        mov dx, ax
        add dx, word ptr es:[ _dpbFirstFAT ][ bx ]      ; where is first FAT table ?
        stordarg _sector, cx, dx                        ; read next cluster sector
        mov ax, word ptr [ _drive ][ bp ]               ; get drive
        call readBuffer                                 ; read FAT Table
        or byte ptr es:[ ccbStatus ][ di ], ( ccb_isFAT )

        getarg cx, _cluster                             ; get cluster value
        getarg dx, _value                               ; get value to update
        and dx, 0FFFh

        pop bx                                          ; word offset into FAT table
        cmp bx, word ptr [ _sectorsize ][ bp ]          ; at sector size -1 boundry ?
        jnz updateClusterValue_16                       ; no, ok to return as is -->

        call _updateCrossSectorEntry
      ; call updateAllChangedCCBBuffers                 ; optimized when commented out.

        clc                                             ; if no carry
        jmp short updateClusterValue_Return

      ; see if odd or even cluster

updateClusterValue_16:
        shr cx, 1                                       ; even or odd cluster
        jnc updateClusterValue_20                       ; even, take value -->

        shl dx, 1
        shl dx, 1
        shl dx, 1
        shl dx, 1                                       ; shift value

        mov cx, word ptr es:[ ccbData ][ bx + di ]      ; get current value
        and word ptr es:[ ccbData ][ bx + di ], 000Fh   ; clear area.
        
        shr cx, 1
        shr cx, 1
        shr cx, 1
        shr cx, 1                                       ; old value shifted correctly
        jmp short updateClusterValue_22

updateClusterValue_20:
        mov cx, word ptr es:[ ccbData ][ bx + di ]      ; get current value
        and word ptr es:[ ccbData ][ bx + di ], 0F000h  ; clear area.

updateClusterValue_22:
        or word ptr es:[ ccbData ][ bx + di ], dx       ; update FAT word

updateClusterValue_26:
        and cx, 0FFFh                                   ; mask off unwanted bits
        mov ax, cx
        and ax, 0FF8h                                   ; FAT value, 12 bit entries.
        cmp ax, 0FF8h                                   ; end of chain ?
        jnz updateClusterValue_UpdatedRet               ; no -->

        mov cx, -1                                      ; if end, set end value
        jmp short updateClusterValue_UpdatedRet

;- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
;  16 bit FAT entries
;- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

updateClusterValue_16Bits:
        mov cx, word ptr es:[ _dpbBytesPerSector ][ bx ]
        shr cx, 1
        div cx                                          ; FAT sector/ Offset

      ; ax will contain FAT sector
      ; dx will contain byte offset into FAT sector

        add dx, dx                                      ; make word offset
        push dx

        xor cx, cx
        mov dx, word ptr es:[ _dpbFirstFAT ][ bx ]      ; where is first FAT table ?
        add dx, ax                                      ; add offset required
        mov ax, word ptr [ _drive ][ bp ]               ; get drive
        call readBuffer                                 ; read FAT Table
        or byte ptr es:[ ccbStatus ][ di ], ( ccb_isFAT )

        pop bx                                          ; word offset into FAT table
        getarg cx, _value                               ; update value
        xchg cx, word ptr es:[ ccbData ][ bx + di ]     ; update value

;- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
;  update
;- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

updateClusterValue_UpdatedRet:
        call CCBChanged
        or cx, cx

;- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
;  return
;- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

updateClusterValue_Return:
        restoreRegisters ax, bx, dx, si, di, es
        Return

        ;''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''';
        ;  Update Cross Sector Entry                                    ;
        ;- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -;
        ;                                                               ;
        ;  Input:                                                       ;
        ;   es:di  points to current ccb                                ;
        ;   bx     offset into buffer                                   ;
        ;   dx     value to update                                      ;
        ;   cx     cluster number                                       ;
        ;                                                               ;
        ;  Output:                                                      ;
        ;   cx     value at cluster (value before update)               ;
        ;...............................................................;

_updateCrossSectorEntry:

        Entry
        def  _sectorflag, cx
        def  _updatevalue, dx
        def  _returnvalue

        mov ax, word ptr es:[ ccbData ][ bx + di ]      ; get value at current sector
        storarg _returnvalue, ax

        shr cx, 1                                       ; even or odd cluster
        jnc _updateCrossSector_20                       ; even, take value -->

        shl dx, 1
        shl dx, 1
        shl dx, 1
        shl dx, 1
        and dx, 0FFF0h
        storarg _updatevalue, dx
        and byte ptr es:[ ccbData ][ bx + di ], 0Fh     ; init area
        or byte ptr es:[ ccbData ][ bx + di ], dl       ; set high value (just the single nibble)
        jmp short _updateCrossSector_22                 ; 

_updateCrossSector_20:
        mov byte ptr es:[ ccbData ][ bx + di ], dl      ; update low order FAT word

;- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
;  update next sector
;- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

_updateCrossSector_22:
        call CCBChanged

        xor ah, ah
        mov al, byte ptr es:[ ccbDrive       ][ di ]
        mov cx, word ptr es:[ ccbLBN. _high  ][ di ]
        mov dx, word ptr es:[ ccbLBN. _low   ][ di ]
        inc dx
        adc cx, 0                                       ; 32 bit add
        call ReadBuffer                                 ; read next sector
        or byte ptr es:[ ccbStatus ][ di ], ( ccb_isFAT )

        getarg dx, _updatevalue
        mov ax, word ptr es:[ ccbData ][ di ]           ; get value at current sector
        test word ptr [ _sectorflag ][ bp ], 1          ; even or odd cluster
        jz _updateCrossSector_30                        ; even, take value -->
        mov byte ptr es:[ ccbData ][ di ], dh           ; set low order value

        xchg ah, al
        mov al, byte ptr [ _returnvalue ][ bp ]
        shr ax, 1
        shr ax, 1
        shr ax, 1
        shr ax, 1                                       ; return value in ax
        jmp short _updateCrossSector_32                 ; 

_updateCrossSector_30:
        and byte ptr es:[ ccbData ][ di ], 0F0h         ; clear high order
        or byte ptr es:[ ccbData ][ di ], dh            ; update high order FAT word

        xchg ah, al        
        mov al, byte ptr [ _returnvalue ][ bp ]

;- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
;  rebuild original value
;- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

_updateCrossSector_32:
        push ax
        call CCBChanged

        pop cx
        and cx, 0FFFh
        Return

        ;''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''';
        ;  Get 12Bit FAT Table Value                                    ;
        ;- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -;
        ;                                                               ;
        ;  Input:                                                       ;
        ;   ax     drive                                                ;
        ;   dx     current cluster                                      ;
        ;                                                               ;
        ;  Output:                                                      ;
        ;   dx     value at cluster (next cluster)                      ;
        ;   zr     if end of cluster chain.                             ;
        ;...............................................................;

_get_12Bit_ClusterValue:
        Entry
        def  _drive, ax
        def  _cluster, dx
        def  _sectorsize
        ddef _sector
        ddef _dpb, es, bx

        mov ax, dx
        add ax, ax
        add ax, dx                                      ; nibble address

        xor dx, dx
        mov cx, word ptr es:[ _dpbBytesPerSector ][ bx ]
        mov word ptr [ _sectorsize ][ bp ], cx
        dec word ptr [ _sectorsize ][ bp ]
        shl cx, 1                                       ; nibbles / sector
        div cx                                          ; sector to read

      ; ax will contain sector
      ; dx will contain nibble offset

        shr dx, 1                                       ; word offset
        push dx                                         ;

        xor cx, cx                                      ; 32 bit address
        mov dx, ax
        add dx, word ptr es:[ _dpbFirstFAT ][ bx ]      ; where is first FAT table ?
        stordarg _sector, cx, dx                        ; 32 bit sector address

        mov ax, word ptr [ _drive ][ bp ]               ; get drive
        call readBuffer                                 ; read FAT Table
        or byte ptr es:[ ccbStatus ][ di ], ( ccb_isFAT )

        pop bx                                          ; word offset into FAT table
        mov dx, word ptr es:[ ccbData ][ bx + di ]      ; get FAT word

        cmp bx, word ptr [ _sectorsize ][ bp ]          ; at sector size -1 boundry ?
        jnz _get_12Bit_ClusterValue_12                  ; no, ok to return as is -->

        push dx                                         ; else save what we have
        getdarg cx, dx, _sector                         ; read next cluster sector
        add dx, 0001                                    ; incr by one
        adc cx, 0000

        mov ax, word ptr [ _drive ][ bp ]               ; get drive
        call readBuffer                                 ; read FAT Table
        or byte ptr es:[ ccbStatus ][ di ], ( ccb_isFAT )

        pop dx
        mov dh, byte ptr es:[ ccbData ][ di ]           ; get FAT word

_get_12Bit_ClusterValue_12:
        test word ptr [ _cluster ][ bp ], 1             ; is cluster Odd ?
        jz _get_12Bit_ClusterValue_14                   ; no, just take value -->

        mov cl, 4
        shr dx, cl

_get_12Bit_ClusterValue_14:
        and dx, 0FFFh                                   ; 12 bit mask
        mov ax, dx
        and ax, 00FF8h                                  ; FAT value, 12 bit entries.
        cmp ax, 00FF8h                                  ; end of chain ?
        jnz _get_12Bit_ClusterValue_16                  ; no -->
        mov dx, -1                                      ; if end, set end value

_get_12Bit_ClusterValue_16:
        Return

        ;''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''';
        ;  Delete Cluster Chain                                         ;
        ;- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -;
        ;                                                               ;
        ;  Removes cluster addressed in dx from *both* FAT tables and   ;
        ;  returns pointer to next cluster ( or FFFF).                  ;
        ;                                                               ;
        ;  Input:                                                       ;
        ;   ax     drive                                                ;
        ;   dx     cluster                                              ;
        ;                                                               ;
        ;  Output:                                                      ;
        ;   dx     next cluster                                         ;
        ;   zr     if end of cluster chain.                             ;
        ;...............................................................;

ReleaseClusterChain:

        saveAllRegisters
        call getAddrDPB                                 ; (es:bx) Device Parameter Block
        jc releaseClusterChain_16                       ; if device invalid -->

        or dx, dx                                       ; cluster valid ?
        jz releaseClusterChain_16                       ; no, exit -->
        cmp dx, word ptr es:[ _dpbMaxClusterNumber ][ bx ]
        jnc releaseClusterChain_16                      ; no, exit -->

releaseClusterChain_08:
        cmp word ptr es:[ _dpbFreeCount ][ bx ], -1     ; value initialized ?
        jz releaseClusterChain_12                       ; no -->
        inc word ptr es:[ _dpbFreeCount ][ bx ]         ; increment allocated unit

releaseClusterChain_12:
        xor cx, cx
        call updateClusterValue                         ; release cluster at ax:dx
        jz releaseClusterChain_16                       ; no more -->

        mov dx, cx                                      ; next cluster
        cmp dx, -1                                      ; end of chain ?
        jnz releaseClusterChain_08                      ; no -->

releaseClusterChain_16:
        restoreAllRegisters
        ret

        ;''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''';
        ;  Find All Free Space                                          ;
        ;- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -;
        ;                                                               ;
        ;  Input:                                                       ;
        ;   ax     drive                                                ;
        ;                                                               ;
        ;  Output:                                                      ;
        ;   ax     drive                                                ;
        ;   cx     # free clusters                                      ;
        ;   es:bx  pointer to DPB                                       ;
        ;...............................................................;

AmountFreeSpace:

        Entry
        def  _drive, ax
        def  _freespace
        def  _currcluster
        def  _maxclusters
        def  _entriesPerSector

        ddef _dpb
        ddef _sector
        ddef _buffer
    
        call getDPB                                     ; (es:bx) Device Parameter Block
        stordarg _dpb, es, bx                           ; save address of dpb 
        jc amountFree_Return_Exit                       ; exit if invalid -->

        mov cx, word ptr es:[ _dpbFreeCount ][ bx ]     ; get free cluster count
        cmp cx, -1                                      ; is free space valid ?
        jz amountFree_checkDevice                       ; no, check device physically -->

amountFree_Return:
        getarg ax, _drive
        getdarg es, bx, _dpb
        mov word ptr es:[ _dpbFreeCount ][ bx ], cx     ; save valid number

amountFree_Return_Exit:
        Return

;- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
;  check device physically
;- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

amountFree_checkDevice:

        xor dx, dx                                      ; starting cluster
        mov word ptr [ _freespace   ][ bp ], dx
        mov word ptr [ _currcluster ][ bp ], dx

        mov ax, word ptr es:[ _dpbMaxClusterNumber ][ bx ]
        add ax, 2                                       ; adj for first two used entries
        mov word ptr [ _maxclusters ][ bp ], ax

        test word ptr es:[ _dpbMaxClusterNumber ][ bx ], 0F000h
        jnz amountFree16BitFAT                          ; if 16-bit FAT -->

;- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
;  12 Bit FAT 
;- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

amountFree12BitFAT:
        push dx
        getarg ax, _drive                               ; restore drive
        getdarg es, bx, _dpb                            ; restore dpb
        call _get_12Bit_ClusterValue                    ; optimized get value

        or dx, dx                                       ; cluster free ?
        jnz amountFree12BitFAT_18                       ; no -->
        inc word ptr [ _freespace ][ bp ]

amountFree12BitFAT_18:
        pop dx
        inc dx
        cmp dx, word ptr [ _maxclusters ][ bp ]
        jc amountFree12BitFAT

        getarg cx, _freespace
        jmp amountFree_Return

;- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
;  16 Bit FAT 
;- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

amountFree16BitFAT:
        mov cx, word ptr es:[ _dpbBytesPerSector ][ bx ]
        shr cx, 1                                       ; entries per sector
        storarg _entriesPerSector, cx

        xor cx, cx
        mov dx, word ptr es:[ _dpbFirstFAT ][ bx ]      ; where is first FAT table ?
        stordarg _sector, cx, dx

        mov ax, word ptr [ _drive ][ bp ]               ; get drive
        call readBuffer                                 ; read FAT Table
        stordarg _buffer, es, di                        ; save buffer address
        or byte ptr es:[ ccbStatus ][ di ], ( ccb_isFAT )

amountFree16BitFAT_08:
        xor ax, ax
        getarg cx, _entriesPerSector
        mov di, word ptr [ _buffer. _pointer ][ bp ]
        lea di, offset ccbData [ di ]                   ; start of buffer

amountFree16BitFAT_12:
        repnz scasw                                     ; search for null word
        jnz amountFree16BitFAT_16                       ; count is zero -->
        inc word ptr [ _freespace ][ bp ]               ; increment free space
        or cx, cx                                       ; more clusters this sector ?
        jnz amountFree16BitFAT_12                       ; yes -->

amountFree16BitFAT_16:
        mov ax, word ptr [ _entriesPerSector ][ bp ]
        add ax, word ptr [ _currcluster      ][ bp ]
        mov word ptr [ _currcluster  ][ bp ], ax

        mov cx, word ptr [ _maxclusters ][ bp ]
        sub cx, ax                                      ; clusters remaining
        jc amountFree16BitFAT_24                        ; if at end -->
        je amountFree16BitFAT_24                        ; if at end -->

        cmp cx, word ptr [ _entriesPerSector ][ bp ]
        jnc amountFree16BitFAT_20
        mov word ptr [ _entriesPerSector ][ bp ], cx    ; max to read last sector

amountFree16BitFAT_20:
        getarg ax, _drive                               ; get drive
        getdarg es, di, _buffer                         ; no longer scan through ccb
        inc word ptr [ _sector ][ bp ]
        getdarg cx, dx, _sector                         ; get next sector
        call readSelBuffer                              ; else read buffer
        jmp amountFree16BitFAT_08
        
amountFree16BitFAT_24:
        getarg cx, _freespace
        jmp amountFree_Return

        ;''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''';
        ;  Allocate Next Free Cluster                                   ;
        ;- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -;
        ;                                                               ;
        ;  Input:                                                       ;
        ;   ax     drive                                                ;
        ;   dx     recommended start search address                     ;
        ;                                                               ;
        ;  Output:                                                      ;
        ;   ax     drive                                                ;
        ;   dx     allocated cluster number                             ;
        ;   cy     if end of cluster chain.                             ;
        ;...............................................................;

AllocateCluster:

        Entry
        def _drive, ax
        def _cluster, dx
        def _maxclusters
        ddef _dpb

        call getAddrDPB                                 ; (es:bx) Device Parameter Block
        jc allocateCluster_NoneFree                     ; if device invalid -->

        stordarg _dpb, es, bx                           ; save _dpb address

        mov dx, word ptr es:[ _dpbMaxClusterNumber ][ bx ]
        mov word ptr [ _maxclusters ][ bp ], dx

;- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
;  loop through entire FAT table search for free cluster
;- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

        getarg dx, _cluster                             ; starting cluster

allocateCluster_12:
        push dx
        mov ax, word ptr [ _drive ][ bp ]               ; get drive
        call getNextCluster                             ; get value at cluster

        mov cx, dx                                      ; cluster value
        pop dx                                          ; cluster number
        or cx, cx                                       ; is cluster free ?
        jz allocateCluster_18                           ; yes -->

        inc dx
        cmp dx, word ptr [ _cluster ][ bp ]             ; back at recommended cluster ?
        jz allocateCluster_NoneFree                     ; end of search -->

        cmp dx, word ptr [ _maxclusters ][ bp ]
        jc allocateCluster_12

        xor dx, dx                                      ; loop back around to start of disk
        jmp allocateCluster_12

;- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
;  if no space available on disk
;- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

allocateCluster_NoneFree:
        stc
        jmp short allocateCluster_Return

;- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
;  update allocated buffer
;- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

allocateCluster_18:
        mov cx, 0FFFFh                                  ; updated value
        call updateClusterValue                         ; update value

        getdarg es, bx, _dpb                            ; get _dpb address
        cmp word ptr es:[ _dpbFreeCount ][ bx ], -1     ; value initialized ?
        jz allocateCluster_Return                       ; no -->
        dec word ptr es:[ _dpbFreeCount ][ bx ]         ; subtract allocated unit
        or dx, dx                                       ; no carry

;- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
;  return
;- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

allocateCluster_Return:
        getarg ax, _drive
        getdarg es, bx, _dpb
        Return

        ;''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''';
        ;  Allocate/Init A Cluster.                                     ;
        ;- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -;
        ;                                                               ;
        ;  Note:                                                        ;
        ;   This function will allocate a cluster and completely fill   ;
        ;   it with zeroes.  Ideal for directories which must have      ;
        ;   initialized entries.                                        ;
        ;                                                               ;
        ;                                                               ;
        ;- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -;
        ;                                                               ;
        ;  Input:                                                       ;
        ;   ax     drive                                                ;
        ;                                                               ;
        ;  Output:                                                      ;
        ;   ax     drive                                                ;
        ;   dx     cluster                                              ;
        ;   es:di  pointer to a ccb buffer                              ;
        ;...............................................................;

AllocateInitCluster:

        Entry
        def _drive, ax
        def _secPerCluster
        def _cluster
        ddef _sector

;- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
;  allocate a free cluster / sector
;- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
        saveRegisters si, bx

        call getAddrDPB                                 ; point to dpb

        xor dx, dx
        mov cx, 0001
        add cl, byte ptr es:[ _dpbClusterSizeMask ][ bx ]
        mov word ptr [ _secPerCluster ][ bp ], cx

        call AllocateCluster                            ; allocate a cluster
        mov word ptr [ _cluster ][ bp ], dx
        jc AllocateInitCluster_12

        call computeLogSectorNumber                     ; cluster -> sector number
        stordarg _sector, cx, dx                        ; save sector #

;- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
;  allocate a buffer/ init
;- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
        call selBuffer                                  ; allocate a buffer
        push di                                         ; pointer to header
        lea di, offset ccbData [ di ]                   ; point to data buffer
        clearMemory sizeCCBData                         ; clear to zeroes
        pop di                                          ; pointer to header

AllocateInitCluster_08:
        call updateChangedCCB                           ; MUST force write updated buffer

        add word ptr es:[ ccbLBN. _low  ][ di ], 0001
        adc word ptr es:[ ccbLBN. _high ][ di ], 0000
        dec word ptr [ _secPerCluster  ][ bp ]          ; continuous write of directory 
        jnz AllocateInitCluster_08

;- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
;  return
;- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
        getdarg cx, dx, _sector
        mov word ptr es:[ ccbLBN. _low  ][ di ], dx
        mov word ptr es:[ ccbLBN. _high ][ di ], cx
        or cx, cx                                       ; no carry.

AllocateInitCluster_12:
        getarg ax, _drive
        getarg dx, _cluster
        restoreRegisters bx, si
        Return

        ;''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''';
        ;  Append A Cluster                                             ;
        ;- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -;
        ;                                                               ;
        ;  Input:                                                       ;
        ;   ax     drive                                                ;
        ;   dx     cluster to append                                    ;
        ;                                                               ;
        ;  Output:                                                      ;
        ;   ax     drive                                                ;
        ;   dx     new cluster value                                    ;
        ;...............................................................;

AppendCluster:

        Entry
        def _drive, ax
        def _cluster, dx

        cmp dx, 341
        jc AppendCluster_06
        nop

;- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
;  allocate a free cluster / sector
;- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

AppendCluster_06:
        push bx
        call AllocateCluster                            ; allocate a cluster
        jc appendCluster_08                             ; if error -->

        push dx                                         ; allocated cluster
        mov cx, dx                                      ; use it to link to prev cluster
        getarg dx, _cluster
        call updateClusterValue                         ; update cluster value
        pop dx                                          ; restore allocated cluster
        or dx, dx

appendCluster_08:
        pop bx
        Return

        ;''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''';
        ;  Read Random Buffer FAT File                                  ;
        ;- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -;
        ;                                                               ;
        ;  Input:                                                       ;
        ;   ss:bx  fat access control block                             ;
        ;                                                               ;
        ;  Output:                                                      ;
        ;   es:bx  pointer in block buffer to data                      ;
        ;   cx     remaining bytes in block                             ;
        ;   zr     means end of file or wrong address                   ;
        ;...............................................................;

_FATReadRandom:

        Entry
        ddef _accessControl, ss, bx
        ddef _reqOffset
        def  _bytesPerCluster
        ddef _DPBPointer
        ddef _BufferAddress

        saveRegisters ds, di, si, dx, ax
        and word ptr [ diskAcOptions ][ bx ], NOT (DISKAC_RESULT_READMULTIPLESECTORS)

        mov ax, word ptr [ diskAcDrive ][ bx ]
        call getAddrDPB                                 ; (es:bx) Device Parameter Block
        stordarg _DPBPointer, es, bx
        mov cx, 0000                                    ; say none
        ifc _FATReadRandom_Return                       ; if error, exit -->

;- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
;  if request is before start of buffer
;- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
        setDS ss
        mov bx, word ptr [ _accessControl. _pointer ][ bp ]
        mov ax, word ptr [ diskAcPosition. _low  ][ bx ]
        mov dx, word ptr [ diskAcPosition. _high ][ bx ]
        stordarg _reqOffset, dx, ax

        sub ax, word ptr [ diskAcOffAtBegCluster. _low  ][ bx ]        
        sbb dx, word ptr [ diskAcOffAtBegCluster. _high ][ bx ]
        jge _FATReadRandom_16

_FATReadRandom_08:
        xor ax, ax
        mov word ptr [ diskAcOffAtBegBuffer. _low  ][ bx ], ax
        mov word ptr [ diskAcOffAtBegBuffer. _high ][ bx ], ax
        mov word ptr [ diskAcOffAtBegCluster. _low  ][ bx ], ax
        mov word ptr [ diskAcOffAtBegCluster. _high ][ bx ], ax
        mov word ptr [ diskAcCurCluster ][ bx ], ax

;- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
;  locate starting cluster and sector
;- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
_FATReadRandom_16:
        cmp word ptr [ diskAcCurCluster ][ bx ], 0000
        jnz _FATReadRandom_18
        mov ax, word ptr [ diskAcBegCluster ][ bx ]
        mov word ptr [ diskAcCurCluster ][ bx ], ax

_FATReadRandom_18:
        getdarg es, si, _DPBPointer
        mov ax, word ptr es:[ _dpbBytesPerSector ][ si ]
        mov cl, byte ptr es:[ _dpbClusterSizeShift ][ si ]
        shl ax, cl                                      ; bytes per cluster

;- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
;  compute a fake sector size if addressing cluster 0 (root dir).
;- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
        cmp word ptr [ diskAcBegCluster ][ bx ], 0000
        jnz _FATReadRandom_20
        mov ax, word ptr es:[ _dpbMaxAllocRootDir ][ si ]
        mov cx, sizeDIRENTRY
        mul cx

_FATReadRandom_20:
        mov word ptr [ _bytesPerCluster ][ bp ], ax

;- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
;  see if within current cluster
;- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
_FATReadRandom_22:
        mov dx, word ptr [ _reqOffset. _low  ][ bp ]
        mov cx, word ptr [ _reqOffset. _high ][ bp ]
        sub dx, word ptr [ diskAcOffAtBegCluster. _low  ][ bx ]
        sbb cx, word ptr [ diskAcOffAtBegCluster. _high ][ bx ]
        jnz _FATReadRandom_26                           ; must continue searching forward -->
        cmp dx, word ptr [ _bytesPerCluster ][ bp ]     ; beyond cluster size ?
        jc _FATReadRandom_32                            ; no, current cluster is ok -->

_FATReadRandom_26:
        mov ax, word ptr [ diskAcDrive ][ bx ]
        mov dx, word ptr [ diskAcCurCluster ][ bx ]
        or dx, dx                                       ; zero cluster ?
        jz _FATReadRandom_28                            ; yes -->

        call getNextCluster
        jz _FATReadRandom_28                            ; if end of cluster chain -->

        mov cx, word ptr [ _bytesPerCluster  ][ bp ]
        add word ptr [ diskAcOffAtBegCluster. _low  ][ bx ], cx
        adc word ptr [ diskAcOffAtBegCluster. _high ][ bx ], 0000
        mov word ptr [ diskAcCurCluster      ][ bx ], dx
        jmp _FATReadRandom_22

_FATReadRandom_28:
        xor cx, cx
        jmp _FATReadRandom_Return 

;- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
;  see if within current sector
;- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
_FATReadRandom_32:
        lds bx, dword ptr [ _accessControl ][ bp ]
        mov ax, word ptr [ diskAcDrive ][ bx ]
        mov dx, word ptr [ diskAcCurCluster ][ bx ]
        call computeLogSectorNumber                     ; cluster -> sector number

        push cx
        push dx                                         ; save logical sector number

        mov ax, word ptr [ _reqOffset. _low  ][ bp ]
        mov dx, word ptr [ _reqOffset. _high ][ bp ]
        sub ax, word ptr [ diskAcOffAtBegCluster. _low  ][ bx ]
        sbb dx, word ptr [ diskAcOffAtBegCluster. _high ][ bx ]
        div word ptr  es:[ _dpbBytesPerSector ][ si ]

        mov cx, word ptr [ _reqOffset. _low ][ bp ]
        sub cx, dx                                      ; subtract out remainder
        mov word ptr [ diskAcOffAtBegBuffer. _low ][ bx ], cx
        mov cx, word ptr [ _reqOffset. _high ][ bp ]
        mov word ptr [ diskAcOffAtBegBuffer. _high ][ bx ], cx

        pop dx
        pop cx                                          ; restore lsn
        add dx, ax                                      ; real sector offset
        adc cx, 0

;- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
; read sector
;- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
        mov ax, word ptr [ diskAcDrive ][ bx ]
        mov word ptr [ diskAcCurSector. _low ][ bx ], dx
        mov word ptr [ diskAcCurSector. _high ][ bx ], cx

        test word ptr [ _reqOffset. _low ][ bp ], (SIZESECTOR - 1)
        ifnz _FATReadRandom_52                          ; if not on sector boundary ->
        test word ptr [ diskAcOptions ][ bx ], DISKAC_READ_MULTIPLESECTORS
        ifz _FATReadRandom_52                           ; if not multiple sectors ->

;- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
; identify reads that will cross certain segment boundaries
;- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
        mov es, word ptr [ diskAcBufferPtr. _segment ][ bx ]
        mov di, word ptr [ diskAcBufferPtr. _pointer ][ bx ]
        NormalizeBuffer es, di                          ; normalize buffer pointer
        stordarg _BufferAddress, es, di

        mov cx, es                                      ; 
        and cx, 00FFFh                                  ; see if segment after normalize
        cmp cx, 0FE0h                                   ; is beyond xFE0.
        jnc _FATReadRandom_52                           ; if tough segment boundary case

        mov ax, 1000h
        sub ax, cx                                      ; # paragraphs that can be read
        shl ax, 1
        shl ax, 1
        shl ax, 1
        shl ax, 1                                       ; actual bytes that can be read
        cmp ax, word ptr [ diskAcBytesToRead ][ bx ]    ; bytes to read
        jc _FATReadRandom_40                            ; cx contains max bytes
        mov ax, word ptr [ diskAcBytesToRead ][ bx ]    ; else allow max requested

;- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
; identify max forward scan clusters
;- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
_FATReadRandom_40:
        xor dx, dx
        div word ptr [ _bytesPerCluster ][ bp ]         ; compute max clusters to scan

        mov cx, ax                                      ; max clusters
        mov dx, word ptr [ diskAcCurCluster ][ bx ]
        mov ax, word ptr [ diskAcDrive      ][ bx ]
        call scanClusterMap                             ; number contiguous clusters

        mov ax, word ptr [ _bytesPerCluster ][ bp ]     ; compute 
        mul cx                                          ; total bytes to read

        xor dx, dx                                      ; nothing way up here
        cmp ax, word ptr [ diskAcBytesToRead ][ bx ]    ; more than requested ?
        jc _FATReadRandom_42                            ; no -->
        mov ax, word ptr [ diskAcBytesToRead ][ bx ]    ; limit to requested size

_FATReadRandom_42:
        mov cx, SIZESECTOR                              ; into sectors
        div cx                                          ; sectors to read

        getdarg es, di, _BufferAddress
        or word ptr [ diskAcOptions ][ bx ], DISKAC_RESULT_READMULTIPLESECTORS
        mov dx, word ptr [ diskAcCurSector. _low ][ bx ]
        mov cx, word ptr [ diskAcCurSector. _high ][ bx ]
        mov bx, word ptr [ diskAcDrive ][ bx ]
        xchg ax, bx
        call DevRead

        mov ax, SIZESECTOR                              ; sector size
        mul cx                                          ; actual bytes read
        mov cx, ax                                      ; actual bytes read
        getdarg es, di, _BufferAddress
        jmp short _FATReadRandom_Return                 ; return bytes read

;- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
;  read sector (unoptimized fast read )
;- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
_FATReadRandom_52:
        mov ax, word ptr [ diskAcDrive ][ bx ]
        mov dx, word ptr [ diskAcCurSector. _low ][ bx ]
        mov cx, word ptr [ diskAcCurSector. _high ][ bx ]
        call ReadBuffer
        mov ax, word ptr [ diskAcOptions ][ bx ]
        or byte ptr es:[ ccbStatus ][ di ], al          ; type of block

        lea di, offset ccbData [ di ]                   ; point to data 
        mov word ptr [ diskAcBufferPtr. _segment ][ bx ], es
        mov word ptr [ diskAcBufferPtr. _pointer ][ bx ], di

;- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
;  return pointer
;- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
_FATReadRandom_60:
        lds bx, dword ptr [ _accessControl ][ bp ]
        les si, dword ptr [ _DPBPointer    ][ bp ]
        mov cx, word ptr es:[ _dpbBytesPerSector ][ si ]

        mov ax, word ptr [ _reqOffset. _low  ][ bp ]
        mov dx, word ptr [ _reqOffset. _high ][ bp ]
        sub ax, word ptr [ diskAcOffAtBegBuffer. _low  ][ bx ]
        sbb dx, word ptr [ diskAcOffAtBegBuffer. _high ][ bx ]

        les bx, dword ptr [ diskAcBufferPtr ][ bx ]
        add bx, ax                                      ; offset into buffer
        sub cx, ax                                      ; bytes remaining in buffer

;- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
;  return
;- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
_FATReadRandom_Return:
        restoreRegisters ax, dx, si, di, ds
        Return

RxDOS   ENDS
        END
