FreeDOS/test/ldosboot/testwrit.asm

1024 lines
21 KiB
NASM

%if 0
Write success indicator after file system booting
by C. Masloch, 2020
Usage of the works is permitted provided that this
instrument is retained with the works, so that any entity
that uses the works is notified of this instrument.
DISCLAIMER: THE WORKS ARE WITHOUT WARRANTY.
%endif
%assign __lMACROS1_MAC__DEBUG_DEFAULTS 1
%include "lmacros3.mac"
struc BS
bsJump: resb 3
bsOEM: resb 8
bsBPB:
endstruc
struc EBPB ; BPB sec
bpbBytesPerSector: resw 1 ; offset 00h 0Bh
bpbSectorsPerCluster: resb 1 ; offset 02h 0Dh
bpbReservedSectors: resw 1 ; offset 03h 0Eh
bpbNumFATs: resb 1 ; offset 05h 10h
bpbNumRootDirEnts: resw 1 ; offset 06h 11h -- 0 for FAT32
bpbTotalSectors: resw 1 ; offset 08h 13h
bpbMediaID: resb 1 ; offset 0Ah 15h
bpbSectorsPerFAT: resw 1 ; offset 0Bh 16h -- 0 for FAT32
bpbCHSSectors: resw 1 ; offset 0Dh 18h
bpbCHSHeads: resw 1 ; offset 0Fh 1Ah
bpbHiddenSectors: resd 1 ; offset 11h 1Ch
bpbTotalSectorsLarge: resd 1 ; offset 15h 20h
bpbNew: ; offset 19h 24h
ebpbSectorsPerFATLarge: resd 1 ; offset 19h 24h
ebpbFSFlags: resw 1 ; offset 1Dh 28h
ebpbFSVersion: resw 1 ; offset 1Fh 2Ah
ebpbRootCluster: resd 1 ; offset 21h 2Ch
ebpbFSINFOSector: resw 1 ; offset 25h 30h
ebpbBackupSector: resw 1 ; offset 27h 32h
ebpbReserved: resb 12 ; offset 29h 34h
ebpbNew: ; offset 35h 40h
endstruc
struc BPBN ; ofs B16 S16 B32 S32
bpbnBootUnit: resb 1 ; 00h 19h 24h 35h 40h
resb 1 ; 01h 1Ah 25h 36h 41h
bpbnExtBPBSignature: resb 1 ; 02h 1Bh 26h 37h 42h -- 29h for valid BPBN
bpbnSerialNumber: resd 1 ; 03h 1Ch 27h 38h 43h
bpbnVolumeLabel: resb 11 ; 07h 20h 2Bh 3Ch 47h
bpbnFilesystemID: resb 8 ; 12h 2Bh 36h 47h 52h
endstruc ; 1Ah 33h 3Eh 4Fh 5Ah
struc LOADSTACKVARS, -10h
lsvFirstCluster: resd 1
lsvFATSector: resd 1
lsvFATSeg: resw 1
lsvLoadSeg: resw 1
lsvDataStart: resd 1
endstruc
lsvclSignature equ "CL"
lsvclBufferLength equ 256
struc LOADDATA, LOADSTACKVARS - 10h
ldMemoryTop: resw 1
ldLoadTop: resw 1
ldSectorSeg: resw 1
ldFATType: resb 1
ldHasLBA: resb 1
ldClusterSize: resw 1
ldParaPerSector:resw 1
ldLoadingSeg: resw 1
ldLoadUntilSeg: resw 1
endstruc
struc LOADCMDLINE, LOADDATA - lsvclBufferLength
ldCommandLine:
.start: resb lsvclBufferLength
endstruc
struc LBAPACKET
lpSize: resw 1
lpCount: resw 1
lpBuffer: resd 1
lpSector: resq 1
endstruc
struc DIRENTRY
deName: resb 8
deExt: resb 3
deAttrib: resb 1
resb 8
deClusterHigh: resw 1
deTime: resw 1
deDate: resw 1
deClusterLow: resw 1
deSize: resd 1
endstruc
ATTR_READONLY equ 1
ATTR_HIDDEN equ 2
ATTR_SYSTEM equ 4
ATTR_VOLLABEL equ 8
ATTR_DIRECTORY equ 10h
ATTR_ARCHIVE equ 20h
%ifndef _MAP
%elifempty _MAP
%else ; defined non-empty, str or non-str
[map all _MAP]
%endif
defaulting
numdef QUERY_GEOMETRY, 1 ; query geometry via 13.08 (for CHS access)
numdef CHS, 1 ; support CHS (if it fits)
numdef LBA, 1 ; support LBA (if available)
numdef LBA_33_BIT, 1 ; support 33-bit LBA
numdef LBA_CHECK_NO_33, 1 ; else: check that LBA doesn't carry
numdef LBA_SKIP_CHECK, 0 ; don't use proper LBA extensions check
numdef LBA_RETRY, 1 ; retry LBA reads
numdef CHS_RETRY, 1 ; retry CHS reads
strdef FILE_NAME, "RESULT"
strdef FILE_EXT, "TXT" ; name of file to write
strdef FILE_SUCCESS_MSG,"success"
cpu 8086
org 0
start:
push ss
pop ds
mov word [cs:dirsp], sp
mov ax, cs
cmp ax, 200h + 200h ; loaded high ?
mov dx, 200h ; yes, use low buffer
jae @F
add ax, paras(end - $$) ; => behind used load image
mov dx, ax
neg ax ; - behind used
add ax, word [bp + ldLoadTop] ; top - used = length of free
cmp ax, 200h ; enough ?
jb error_outofmemory ; no -->
; (For simplicity we do not attempt to relocate our load image.)
@@:
mov word [cs:dirbuf], dx
%if _QUERY_GEOMETRY || !_LBA_SKIP_CHECK
call query_geometry
; The ebpbNew BPBN needs to be initialised
; to use this function. It must be called
; before using read_sector.
%endif
cmp byte [bp + ldFATType], 16
ja search32
cmp byte [bp + ldFATType], 0
ja search1216
call error
asciz "Unknown file system (ldFATType = 0)."
search1216:
xor cx, cx
mov bx, word [bp + ldParaPerSector]
shr bx, 1 ; = entries per sector
; number of sectors used for root directory (store in CX)
mov si, [bp + bsBPB + bpbNumRootDirEnts]
mov ax, bx
; The ax value here is the last value of bx, which is set
; by the shr instruction. Therefore, it cannot be higher
; than 7FFFh, so this cwd instruction always zeros dx.
cwd
dec ax ; rounding up
add ax, si ; from BPB
adc dx, dx ; account for overflow (dx was zero)
div bx ; get number of root sectors
xchg ax, cx ; cx = number of root secs, ! ah = 0
; first sector of root directory
mov al, [bp + bsBPB + bpbNumFATs]; ! ah = 0, hence ax = number of FATs
mul word [bp + bsBPB + bpbSectorsPerFAT]
add ax, [bp + bsBPB + bpbReservedSectors]
adc dl, dh ; account for overflow (dh was and is 0)
; Scan root directory for file. We don't bother to check for deleted
; entries (E5h) or entries that mark the end of the directory (00h).
; number of root entries in si here
.next_sect:
mov cx, bx ; entries per sector as loop counter
call read_sector_to_dirbuf
mov bx, cx ; restore bx for next iteration later
xor di, di ; es:di-> first entry in this sector
.next_ent:
call check_entry
dec si ; count down entire root's entries
loopnz .next_ent ; count down sector's entries (jumps iff si >0 && cx >0)
jnz .next_sect ; (jumps iff si >0 && cx ==0)
; ends up here iff si ==0
; ie all root entries checked unsuccessfully
jmp error_filenotfound
search32:
mov ax, [bp + bsBPB + ebpbRootCluster]
mov dx, [bp + bsBPB + ebpbRootCluster + 2]
mov si, word [bp + lsvFATSector + 2]
mov di, word [bp + lsvFATSector]
call check_clust
jc error_filenotfound
.next_root_clust:
call clust_to_first_sector
push cx
push bx
mov cx, [bp + ldClusterSize]
.next_root_sect:
push cx
mov cx, [bp + ldParaPerSector]
shr cx, 1
; Scan root directory for file. We don't bother to check for deleted
; entries (E5h) or entries that mark the end of the directory (00h).
call read_sector_to_dirbuf
push di
xor di, di ; es:di-> first entry in this sector
.next_ent:
call check_entry
loop .next_ent ; count down sector's entries (jumps iff cx >0)
pop di
pop cx
loop .next_root_sect
pop bx
pop cx
call clust_next
jnc .next_root_clust
jmp error_filenotfound
read_sector_to_dirbuf:
mov bx, word [cs:dirbuf]
mov word [cs:dirsec], ax
mov word [cs:dirsec + 2], dx
jmp read_sector
check_entry:
test byte [es:di + deAttrib], ATTR_DIRECTORY | ATTR_VOLLABEL
jnz @F ; directory, label, or LFN entry --> (NZ)
push si
push di
push cx
push ds
push cs
pop ds
mov si, msg.filename ; ds:si-> name to match
mov cx, 11 ; length of padded 8.3 FAT filename
repe cmpsb ; check entry
pop ds
pop cx
pop di
pop si
@@: ; ZR = match, NZ = mismatch
je found_it ; found entry -->
lea di, [di + DIRENTRY_size]
retn
found_it:
mov sp, word [cs:dirsp]
mov word [cs:dirofs], di
cmp word [es:di + deSize], 0
jne @F
cmp word [es:di + deSize + 2], 0
jne @F
error_emptyfile: equ $
call error
asciz "File is empty."
@@:
; get starting cluster of file
mov ax, [es:di + deClusterLow]
mov dx, [es:di + deClusterHigh] ; dx:ax = first cluster
cmp byte [bp + ldFATType], 16
ja @F
xor dx, dx
@@:
mov di, [bp + lsvFATSector]
mov si, [bp + lsvFATSector + 2]
call check_clust
jc error_emptyfile
call clust_to_first_sector
mov bx, cs
add bx, paras(filbuf - $$)
call write_sector
mov es, word [cs:dirbuf]
mov bx, word [cs:dirofs]
mov word [es:bx + deSize], filbuf.size
and word [es:bx + deSize + 2], 0
mov bx, es
mov ax, word [cs:dirsec]
mov dx, word [cs:dirsec + 2]
call write_sector
push cs
pop ds
mov si, msg.success
call disp_error
quit:
int3
mov ax, 0F000h
mov es, ax
push cs
pop ds ; avoid "repe cs cmpsw" (8086 bug)
mov di, 0FFF5h
mov si, msg.dosemudate
mov cx, 4
repe cmpsw ; running in DosEmu?
jne .quit_not_dosemu
xor bx, bx
mov ax, -1
int 0E6h ; dosemu quit
.quit_not_dosemu:
; from https://stackoverflow.com/a/5240330/738287
mov ax, 5301h
xor bx, bx
int 15h ; connect to APM API
mov ax, 530Eh
xor bx, bx
mov cx, 0102h
int 15h ; set APM version to 1.02
mov ax, 5307h
mov bx, 1
mov cx, 3
int 15h ; shut down system
; setopt [cs:quitrecurse], 1
call error
asciz "Quit failed."
error:
push cs
pop ds
mov si, msg.error
call disp_error
pop si
call disp_error
mov si, msg.error.trailer
call disp_error
%if 0
testopt [cs:quitrecurse], 1
jz quit
%endif
int3
xor ax, ax
int 16h
int 19h
disp_error:
.:
lodsb
test al, al
jz .ret
mov ah, 0Eh
mov bx, 7
push bp
; (call may change bp)
int 10h
pop bp
jmp short .
align 4
dirsec: dd 0
dirofs: dw 0
dirbuf: dw 0
dirsp: dw 0
; quitrecurse: db 0
msg:
.error: asciz "Load error: "
.success: db "Test writer loaded successfully. Quitting the machine."
.error.trailer: asciz 13,10
.filename: fill 8, 32, db _FILE_NAME
fill 3, 32, db _FILE_EXT
align 4
.dosemudate: db "02/25/93"
align 16, db 38
filbuf:
.:
db _FILE_SUCCESS_MSG,13,10
.size: equ $ - .
%if .size > 32
%error File success message is too long
%endif
_fill 8192, 38, .
query_geometry:
%if _QUERY_GEOMETRY ; +30 bytes
mov dl, [bp + bsBPB + ebpbNew + bpbnBootUnit]
%if !_LBA_SKIP_CHECK
push dx
%endif
; test dl, dl ; floppy?
; jns @F ; don't attempt query, might fail -->
; Note that while the original PC BIOS doesn't support this function
; (for its diskettes), it does properly return the error code 01h.
; https://sites.google.com/site/pcdosretro/ibmpcbios (IBM PC version 1)
mov ah, 08h
xor cx, cx ; initialise cl to 0
stc ; initialise to CY
int 13h ; query drive geometry
jc @F ; apparently failed -->
and cx, 3Fh ; get sectors
jz @F ; invalid (S is 1-based), don't use -->
mov [bp + bsBPB + bpbCHSSectors], cx
mov cl, dh ; cx = maximum head number
inc cx ; cx = number of heads (H is 0-based)
mov [bp + bsBPB + bpbCHSHeads], cx
@@:
%endif
%if !_LBA_SKIP_CHECK
mov ah, 41h
%if _QUERY_GEOMETRY
pop dx
%else
mov dl, [bp + bsBPB + ebpbNew + bpbnBootUnit]
%endif
mov bx, 55AAh
stc
int 13h ; 13.41.bx=55AA extensions installation check
mov al, 0 ; zero in case of no LBA support
jc .no_lba
cmp bx, 0AA55h
jne .no_lba
test cl, 1 ; support bitmap bit 0
jz .no_lba
inc ax ; al = 1 to indicate LBA support
.no_lba:
mov byte [bp + ldHasLBA], al
%endif
%if 1 || _QUERY_GEOMETRY || !_LBA_SKIP_CHECK
disp_error.ret:
retn
%endif
; Read a sector using Int13.02 or Int13.42
;
; INP: dx:ax = sector number within partition
; bx:0-> buffer
; (_LBA) ds = ss
; OUT: If unable to read,
; ! jumps to error instead of returning
; If sector has been read,
; dx:ax = next sector number (has been incremented)
; bx:0-> next buffer (bx = es+word[para_per_sector])
; es = input bx
; CHG: -
; STT: ds = ss
;
; Note: If error 09h (data boundary error) is returned,
; the read is done into the ldSectorSeg buffer,
; then copied into the user buffer.
read_sector:
clropt [bp + ldHasLBA], 2
jmp @F
write_sector:
setopt [bp + ldHasLBA], 2
@@:
push dx
push cx
push ax
push si
push bx
; DX:AX==LBA sector number
; add partition start (= number of hidden sectors)
add ax,[bp + bsBPB + bpbHiddenSectors + 0]
adc dx,[bp + bsBPB + bpbHiddenSectors + 2]
%if (!_LBA || !_LBA_33_BIT) && _LBA_CHECK_NO_33
jc .err_CY
%endif
%if _LBA ; +70 bytes (with CHS, +63 bytes without CHS)
%if _LBA_33_BIT
sbb si, si ; -1 if was CY, 0 else
neg si ; 1 if was CY, 0 else
%endif
xor cx, cx ; cx = 0 (needed if jumping to .no_lba_checked)
%if !_LBA_SKIP_CHECK
test byte [bp + ldHasLBA], 1
jz .no_lba_checked
%endif
push cx
%if _LBA_33_BIT
push si ; bit 32 = 1 if operating in 33-bit space
%else
push cx ; second highest word = 0
%endif
push dx
push ax ; qword sector number (lpSector)
push bx
push cx ; bx:0 -> buffer (lpBuffer)
inc cx
push cx ; word number of sectors to read (lpCount)
mov cl, 10h
push cx ; word size of disk address packet (lpSize)
mov si, sp ; ds:si -> disk address packet (on stack)
mov dl, [bp + bsBPB + ebpbNew + bpbnBootUnit]
call .get_ah_3_write_2_read
or ah, 40h ; 42h extensions read or 43h extensions write
%if _LBA_RETRY
call .int13_retry
%else
call .int13_preserve_lpcount
%endif
jnc .lba_done
%if _LBA_SKIP_CHECK
cmp ah, 1 ; invalid function?
je .no_lba_skip ; try CHS instead -->
%endif
cmp ah, 9 ; data boundary error?
jne .lba_error
testopt [bp + ldHasLBA], 2
jz @F
mov es, word [si + 4 + 2] ; user buffer
call .sectorseg_helper_write
mov word [si + 4 + 2], es ; => sector buffer
mov ah, 43h
%if _LBA_RETRY
call .int13_retry
%else
int 13h
; (don't need .int13_preserve_lpcount as no further call)
%endif
jc .lba_error
jmp .lba_done
@@:
; push word [si + 4 + 0]
push word [si + 4 + 2] ; user buffer
push word [bp + ldSectorSeg]
pop word [si + 4 + 2]
; and word [si + 4 + 0], byte 0
mov ah, 42h
%if _LBA_RETRY
call .int13_retry
%else
int 13h
; (don't need .int13_preserve_lpcount as no further call)
%endif
jc .lba_error
pop es
; pop cx
call .sectorseg_helper_read
.lba_done:
add sp, 10h
pop bx
jmp short .chs_done
.lba_error: equ .err
%if !_CHS
.no_lba_skip: equ .err
.no_lba_checked: equ .err
%elif _LBA_SKIP_CHECK
.no_lba_skip:
; si == sp
add sp, 8
pop ax
pop dx
%if _LBA_33_BIT
pop si
pop cx ; cx = 0 (needed as input for next cwd instruction)
test si, si
mov si, sp ; si == sp
%else
pop cx
pop cx
; si == sp - 16
%endif
%else
.no_lba_checked:
%if _LBA_33_BIT
test si, si
%endif
mov si, sp ; si == sp
%endif
%endif
%if _CHS ; +70 bytes
%if _LBA && _LBA_33_BIT
jnz .err
%endif
; dx:ax = LBA sector number, (if _LBA) cx = 0
; divide by number of sectors per track to get sector number
; Use 32:16 DIV instead of 64:32 DIV for 8088 compatability
; Use two-step 32:16 divide to avoid overflow
%if !_LBA
xchg cx, ax ; cx = low word of sector, clobbers ax
xchg ax, dx ; ax = high word of sector, clobbers dx
xor dx, dx ; dx:ax = high word of sector
%else
xchg cx, ax ; cx = low word of sector, ax = 0
push dx ; stack = high word of sector
cwd ; dx = 0 (because ax was 0)
pop ax ; ax = high word of sector
; dx:ax = high word of sector
%endif
div word [bp + bsBPB + bpbCHSSectors]
xchg cx,ax
div word [bp + bsBPB + bpbCHSSectors]
xchg cx,dx
; DX:AX=quotient, CX=remainder=sector (S) - 1
; divide quotient by number of heads
xchg bx, ax ; bx = low word of quotient, clobbers ax
xchg ax, dx ; ax = high word of quotient, clobbers dx
xor dx, dx ; dx = 0
div word [bp + bsBPB + bpbCHSHeads]
; ax = high / heads, dx = high % heads
xchg bx, ax ; bx = high / heads, ax = low quotient
div word [bp + bsBPB + bpbCHSHeads]
; bx:ax=quotient=cylinder (C), dx=remainder=head (H)
; move variables into registers for INT 13h AH=02h
mov dh, dl ; dh = head
inc cx ; cl5:0 = sector
xchg ch, al ; ch = cylinder 7:0, al = 0
shr ax, 1
shr ax, 1 ; al7:6 = cylinder 9:8
; bx has bits set iff it's > 0, indicating a cylinder >= 65536.
or bl, bh ; collect set bits from bh
or cl, al ; cl7:6 = cylinder 9:8
; ah has bits set iff it was >= 4, indicating a cylinder >= 1024.
or bl, ah ; collect set bits from ah
mov dl, [bp + bsBPB + ebpbNew + bpbnBootUnit]
; dl = drive
jnz .err ; error if cylinder >= 1024 -->
; ! bx = 0 (for 13.02 call)
; we call INT 13h AH=02h once for each sector. Multi-sector reads
; may fail if we cross a track or 64K boundary
pop es
call .get_ah_3_write_2_read
mov al, 01h ; access one sector
%if _CHS_RETRY
call .int13_retry
%else
int 13h
%endif
jnc .done
cmp ah, 9 ; data boundary error?
jne .err
testopt [bp + ldHasLBA], 2
jz @F
push es ; user buffer
call .sectorseg_helper_write
mov ax, 0301h
%if _LBA_RETRY
call .int13_retry
%else
int 13h
%endif
jc .err
pop es
jmp .done
@@:
push es ; user buffer
mov es, word [bp + ldSectorSeg]
mov ax, 0201h
%if _CHS_RETRY
call .int13_retry
%else
int 13h
%endif
jc .err
pop es
call .sectorseg_helper_read
.done:
; increment segment
mov bx, es
%endif
.chs_done:
mov es, bx
add bx, word [bp + ldParaPerSector]
pop si
pop ax
pop cx
pop dx
; increment LBA sector number
inc ax
jne @F
inc dx
@@:
retn
%if (_LBA && _LBA_RETRY) || (_CHS && _CHS_RETRY)
.int13_retry:
push ax
%if _LBA
call .int13_preserve_lpcount
%else
int 13h ; first try
%endif
jnc @F ; NC, success on first attempt -->
; reset drive
xor ax, ax
int 13h
jc @F ; CY, reset failed, error in ah -->
; try read again
pop ax ; restore function number
%if _LBA
call .int13_preserve_lpcount
%else
int 13h ; retry, CF error status, ah error number
%endif
retn
@@: ; NC or CY, stack has function number
inc sp
inc sp ; discard word on stack, preserve CF
retn
%endif
%if _LBA
; have to reset the LBAPACKET's lpCount, as the handler may
; set it to "the number of blocks successfully transferred".
; hack: si points into unclaimed stack space
; when this is called from the CHS handler.
; this should not cause any issues however.
; actually, if !_LBA_SKIP_CHECK, then si is set
; to point to claimed stack space. also legal.
.int13_preserve_lpcount:
push word [si + lpCount]
int 13h
pop word [si + lpCount]
retn
%endif
; INP: word [bp + ldSectorSeg] => source buffer with read data
; es => destination buffer
; CHG: si, cx
; OUT: ss = ds
; data copied to destination buffer
; STT: UP
.sectorseg_helper_read:
xor si, si
mov ds, word [bp + ldSectorSeg]
push di
; mov di, cx
xor di, di
mov cx, word [bp + bsBPB + bpbBytesPerSector]
rep movsb
pop di
push ss
pop ds
retn
; INP: es => source buffer with data to read
; word [bp + ldSectorSeg] => destination buffer
; CHG: -
; OUT: ss = ds
; es => destination buffer
; data copied to destination buffer
; STT: UP
.sectorseg_helper_write:
push es
pop ds
push si
push di
push cx
xor di, di
mov es, word [bp + ldSectorSeg]
xor si, si
mov cx, word [bp + bsBPB + bpbBytesPerSector]
rep movsb
pop cx
pop di
pop si
push ss
pop ds
retn
.get_ah_3_write_2_read:
mov ah, 02h ; read
testopt [bp + ldHasLBA], 2
jz @F
mov ah, 03h ; write
@@:
retn
.err:
error_diskaccess:
testopt [bp + ldHasLBA], 2
jz @F
call error
db "Disk write error.", 0
@@:
call error
db "Disk read error.", 0
error_badchain:
call error
db "Bad cluster chain.", 0
error_badclusters:
call error
db "Bad amount of clusters.", 0
error_outofmemory:
call error
db "Out of memory.", 0
error_filenotfound:
call error
db "File not found.", 0
; INP: dx:ax = cluster - 2 (0-based cluster)
; OUT: cx:bx = input dx:ax
; dx:ax = first sector of that cluster
; CHG: -
clust_to_first_sector:
push dx
push ax
push dx
mul word [bp + ldClusterSize]
xchg bx, ax
xchg cx, dx
pop ax
mul word [bp + ldClusterSize]
test dx, dx
jnz short error_badchain
xchg dx, ax
add dx, cx
.cy_error_badchain:
jc short error_badchain
xchg ax, bx
add ax, [bp + lsvDataStart]
adc dx, [bp + lsvDataStart + 2]
jc short .cy_error_badchain
; dx:ax = first sector in cluster
pop bx
pop cx ; cx:bx = cluster
retn
; INP: cx:bx = cluster (0-based)
; si:di = loaded FAT sector, -1 if none
; OUT: CY if no next cluster
; NC if next cluster found,
; dx:ax = next cluster value (0-based)
; si:di = loaded FAT sector
; CHG: cx, bx
clust_next:
mov ax, bx
mov dx, cx
add ax, 2
adc dx, 0
push es
cmp byte [bp + ldFATType], 16
je .fat16
ja .fat32
.fat12:
; FAT12 entries are 12 bits, bytes are 8 bits. Ratio is 3 / 2,
; so multiply cluster number by 3 first, then divide by 2.
; ax = cluster number (up to 12 bits set)
mov dx, ax
shl ax, 1 ; = 2n (up to 13 bits set)
add ax, dx ; = 2n+n = 3n (up to 14 bits set)
shr ax, 1 ; ax = byte offset into FAT (0..6129)
; CF = whether to use high 12 bits
sbb cx, cx ; = -1 iff CY, else 0
; Use the calculated byte offset as an offset into the FAT
; buffer, which holds all of the FAT's relevant data.
mov es, [bp + lsvFATSeg]
xchg bx, ax ; bx -> 16-bit word in FAT to load
; get 16 bits from FAT
mov ax, [es:bx]
and cl, 4 ; = 4 iff CY after shift, else 0
shr ax, cl ; shift down iff odd entry, else unchanged
and ax, 0FFFh ; insure it's only 12 bits
jmp short .gotvalue_zero_dx
.fat32:
; * 4 = byte offset into FAT (0--4000_0000h)
add ax, ax
adc dx, dx
.fat16:
; * 2 = byte offset into FAT (0--2_0000h)
add ax, ax
adc dx, dx
push ax
xchg ax, dx
xor dx, dx ; dx:ax = high word
div word [bp + bsBPB + bpbBytesPerSector]
xchg bx, ax ; bx = high word / divisor
pop ax ; dx = remainder, ax = low word
div word [bp + bsBPB + bpbBytesPerSector]
xchg dx, bx ; dx:ax = result, bx = remainder
; dx:ax = sector offset into FAT (0--200_0000h)
; bx = byte offset into FAT sector (0--8190)
cmp dx, si
jne @F ; read sector
cmp ax, di
je @FF ; sector is already buffered
@@:
mov si, dx
mov di, ax
mov word [bp + lsvFATSector + 2], dx
mov word [bp + lsvFATSector + 0], ax
push bx
add ax, [bp + bsBPB + bpbReservedSectors]
adc dx, 0
mov bx, [bp + lsvFATSeg]
call read_sector
pop bx
@@:
mov es, [bp + lsvFATSeg]
mov dx, [es:bx + 2]
mov ax, [es:bx] ; dx:ax = FAT32 entry
cmp byte [bp + ldFATType], 16 ; is it FAT32 ?
jne @F ; yes -->
.gotvalue_zero_dx:
xor dx, dx ; no, clear high word
@@:
pop es
; INP: dx:ax = cluster value, 2-based
; OUT: dx:ax -= 2 (makes it 0-based)
; CY iff invalid cluster
check_clust:
and dh, 0Fh
sub ax, 2
sbb dx, 0
cmp byte [bp + ldFATType], 16
ja .fat32
je .fat16
.fat12:
cmp ax, 0FF7h - 2
jmp short .common
.fat32:
cmp dx, 0FFFh
jb @F ; CY here means valid ...-
.fat16:
cmp ax, 0FFF7h - 2
@@: ; -... or if NC first, CY here also
.common:
cmc ; NC if valid
retn
align 16, db 38
end: