;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;                                                              ;;
;; Copyright (C) KolibriOS team 2004-2016. All rights reserved. ;;
;;  Distributed under terms of the GNU General Public License.  ;;
;;                                                              ;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

$Revision$

; FAT external functions
;   in:
; ebx -> parameter structure of sysfunc 70
; ebp -> FAT structure
; esi -> path string in UTF-8
;   out:
; eax, ebx = return values for sysfunc 70
iglobal
align 4
fat_user_functions:
        dd      fat_free
        dd      (fat_user_functions_end - fat_user_functions - 4) / 4
        dd      fat_Read
        dd      fat_ReadFolder
        dd      fat_CreateFile
        dd      fat_Write
        dd      fat_SetFileEnd
        dd      fat_GetFileInfo
        dd      fat_SetFileInfo
        dd      0
        dd      fat_Delete
        dd      fat_CreateFolder
fat_user_functions_end:
endg

cache_max equ 1919      ; max. is 1919*512+0x610000=0x6ffe00

PUSHAD_EAX equ [esp+28]
PUSHAD_ECX equ [esp+24]
PUSHAD_EDX equ [esp+20]
PUSHAD_EBX equ [esp+16]
PUSHAD_EBP equ [esp+8]
PUSHAD_ESI equ [esp+4]
PUSHAD_EDI equ [esp+0]

; Internal data for every FAT partition.
struct FAT PARTITION
fs_type             db  ?
fat_change          db  ?   ; 1=fat has changed
                    rb  2
Lock                MUTEX   ; currently operations with one partition
; can not be executed in parallel since the legacy code is not ready
SECTORS_PER_FAT     dd  ?
NUMBER_OF_FATS      dd  ?
SECTORS_PER_CLUSTER dd  ?
BYTES_PER_SECTOR    dd  ?   ; Note: if BPS <> 512 need lots of changes
ROOT_CLUSTER        dd  ?   ; first rootdir cluster
FAT_START           dd  ?   ; start of fat table
ROOT_START          dd  ?   ; start of rootdir (only fat16)
ROOT_SECTORS        dd  ?   ; count of rootdir sectors (only fat16)
DATA_START          dd  ?   ; start of data area (=first cluster 2)
LAST_CLUSTER        dd  ?   ; last availabe cluster
ADR_FSINFO          dd  ?   ; used only by fat32
fatRESERVED         dd  ?
fatBAD              dd  ?
fatEND              dd  ?
fatMASK             dd  ?
fatStartScan        dd  ?
cluster_tmp         dd  ?   ; used by analyze_directory and analyze_directory_to_write
longname_sec1       dd  ?   ; used by analyze_directory to save 2 previous
longname_sec2       dd  ?   ; directory sectors for delete long filename
fat_in_cache        dd  ?
; For FAT16/FAT32, this points to 512-byte buffer for the current sector of FAT.
; For FAT12, the entire FAT structure is read
; and unpacked from 12bit per cluster to word per cluster.
; Note: work with unpacked copy of FAT12 means
; additional memory and additional code for packing/unpacking.
; I'm not sure that the economy justifies the cost, but anyway,
; there is how work was done before my edits, and I'm just keeping the principle.
fat_cache_ptr       dd  ?
fat12_unpacked_ptr  dd  ?
buffer              rb  512
fsinfo_buffer       rb  512
ends

uglobal
align 4
partition_count     dd  ?   ; partitions found by set_FAT32_variables
hd_error            dd  ?
hd_setup            dd  ?
hd_wait_timeout     dd  ?
cache_search_start  dd  ?   ; used by find_empty_slot
Sector512:      ; label for dev_hdcd.inc
buffer:
rb 512
endg

; these labels are located before the main function to make
; most of jumps to these be short
fat_create_partition.free_return0:
        mov     eax, ebp
        call    free
        pop     ebp
fat_create_partition.return0:
        xor     eax, eax
        ret
fat_create_partition:
; sector size must be 512
        cmp     dword [esi+DISK.MediaInfo.SectorSize], 512
        jnz     .return0
; bootsector must have been successfully read
        cmp     dword [esp+4], 0
        jnz     .return0
; bootsector signature must be correct
        cmp     word [ebx+0x1fe], 0xaa55
        jnz     .return0
; sectors per cluster must be nonzero
        cmp     byte [ebx+0xd], 0
        jz      .return0
; bytes per sector must be 0x200
        cmp     word [ebx+0xb], 0x200
        jnz     .return0
; number of fats must be nonzero
        cmp     byte [ebx+0x10], 0
        jz      .return0
; The only reason to be invalid partition now is FAT12. Since the test for
; FAT size requires knowledge of some calculated values, which are also used
; in the normal operation, let's hope for the best and allocate data now; if
; it will prove wrong, just deallocate it.
        movi    eax, sizeof.FAT
        call    malloc
        test    eax, eax
        jz      .return0
        mov     ecx, dword [ebp+PARTITION.FirstSector]
        mov     dword [eax+FAT.FirstSector], ecx
        mov     ecx, dword [ebp+PARTITION.FirstSector+4]
        mov     dword [eax+FAT.FirstSector+4], ecx
        mov     ecx, dword [ebp+PARTITION.Length]
        mov     dword [eax+FAT.Length], ecx
        mov     ecx, dword [ebp+PARTITION.Length+4]
        mov     dword [eax+FAT.Length+4], ecx
        mov     ecx, [ebp+PARTITION.Disk]
        mov     [eax+FAT.Disk], ecx
        mov     [eax+FAT.FSUserFunctions], fat_user_functions
        or      [eax+FAT.fat_in_cache], -1
        mov     [eax+FAT.fat_change], 0
        push    ebp
        mov     ebp, eax

        lea     ecx, [ebp+FAT.Lock]
        call    mutex_init

        movzx   eax, word [ebx+0xe]     ; sectors reserved
        mov     [ebp+FAT.FAT_START], eax

        movzx   eax, byte [ebx+0xd]     ; sectors per cluster
        mov     [ebp+FAT.SECTORS_PER_CLUSTER], eax

        movzx   ecx, word [ebx+0xb]     ; bytes per sector
        mov     [ebp+FAT.BYTES_PER_SECTOR], ecx

        movzx   eax, word [ebx+0x11]    ; count of rootdir entries (=0 fat32)
        shl     eax, 5                  ; mul 32
        dec     ecx
        add     eax, ecx                ; round up if not equal count
        inc     ecx                     ; bytes per sector
        xor     edx, edx
        div     ecx
        mov     [ebp+FAT.ROOT_SECTORS], eax     ; count of rootdir sectors

        movzx   eax, word [ebx+0x16]    ; sectors per fat <65536
        test    eax, eax
        jnz     @f
        mov     eax, [ebx+0x24]         ; sectors per fat
@@:
        mov     [ebp+FAT.SECTORS_PER_FAT], eax

        movzx   eax, byte [ebx+0x10]    ; number of fats
        mov     [ebp+FAT.NUMBER_OF_FATS], eax
        mul     [ebp+FAT.SECTORS_PER_FAT]
        test    edx, edx
        jnz     .free_return0
        add     eax, [ebp+FAT.FAT_START]
        jc      .free_return0
        mov     [ebp+FAT.ROOT_START], eax       ; rootdir = fat_start + fat_size * fat_count
        add     eax, [ebp+FAT.ROOT_SECTORS]     ; rootdir sectors should be 0 on fat32
        jc      .free_return0
        mov     [ebp+FAT.DATA_START], eax       ; data area = rootdir + rootdir_size

        movzx   eax, word [ebx+0x13]    ; total sector count <65536
        test    eax, eax
        jnz     @f
        mov     eax, [ebx+0x20]         ; total sector count
@@:
; total sector count must not exceed partition size
        cmp     dword [ebp+FAT.Length+4], 0
        jnz     @f
        cmp     eax, dword [ebp+FAT.Length]
        ja      .free_return0
@@:
        mov     dword [ebp+FAT.Length], eax
        and     dword [ebp+FAT.Length+4], 0
        sub     eax, [ebp+FAT.DATA_START]       ; eax = count of data sectors
        jc      .free_return0
        xor     edx, edx
        div     [ebp+FAT.SECTORS_PER_CLUSTER]
        inc     eax
        mov     [ebp+FAT.LAST_CLUSTER], eax
        dec     eax                     ; cluster count
        jz      .free_return0
        mov     [ebp+FAT.fatStartScan], 2

        ; limits by Microsoft Hardware White Paper v1.03
        cmp     eax, 4085               ; 0xff5
        jb      .fat12
        cmp     eax, 65525              ; 0xfff5
        jb      .fat16
.fat32:
        mov     eax, [ebx+0x2c]         ; rootdir cluster
        mov     [ebp+FAT.ROOT_CLUSTER], eax
        movzx   eax, word [ebx+0x30]
        mov     [ebp+FAT.ADR_FSINFO], eax
        push    ebx
        add     ebx, 512
        call    fs_read32_sys
        test    eax, eax
        jnz     @f
        mov     eax, [ebx+0x1ec]
        cmp     eax, -1
        jz      @f
        mov     [ebp+FAT.fatStartScan], eax
@@:
        pop     ebx
        mov     [ebp+FAT.fatRESERVED], 0x0FFFFFF6
        mov     [ebp+FAT.fatBAD], 0x0FFFFFF7
        mov     [ebp+FAT.fatEND], 0x0FFFFFF8
        mov     [ebp+FAT.fatMASK], 0x0FFFFFFF
        mov     al, 32
.fat_not_12_finalize:
        mov     [ebp+FAT.fs_type], al
; For FAT16 and FAT32, allocate 512 bytes for FAT cache.
        mov     eax, 512
        call    malloc
        test    eax, eax
        jz      .free_return0
        mov     [ebp+FAT.fat_cache_ptr], eax
        mov     eax, ebp
        pop     ebp
        ret
.fat16:
        and     [ebp+FAT.ROOT_CLUSTER], 0
        mov     [ebp+FAT.fatRESERVED], 0x0000FFF6
        mov     [ebp+FAT.fatBAD], 0x0000FFF7
        mov     [ebp+FAT.fatEND], 0x0000FFF8
        mov     [ebp+FAT.fatMASK], 0x0000FFFF
        mov     al, 16
        jmp     .fat_not_12_finalize
.fat12:
        and     [ebp+FAT.ROOT_CLUSTER], 0
        mov     [ebp+FAT.fatRESERVED], 0xFF6
        mov     [ebp+FAT.fatBAD], 0xFF7
        mov     [ebp+FAT.fatEND], 0xFFF
        mov     [ebp+FAT.fatMASK], 0xFFF
        mov     al, 12
        mov     [ebp+FAT.fs_type], al
; For FAT12, allocate&read data for entire table:
; calculate A = ALIGN_UP(NUM_CLUSTERS, 8),
; calculatefatchain/restorefatchain will process A items,
; allocate ALIGN_UP(A*3/2, 512) bytes for FAT table plus A*2 bytes for unpacked data.
        mov     eax, [ebp+FAT.LAST_CLUSTER]
        and     eax, not 7
        add     eax, 8
        mov     edx, eax
        lea     eax, [eax*3]
        add     eax, 512*2-1
        shr     eax, 10
        shl     eax, 9
        lea     eax, [eax+edx*2]
        call    malloc
        test    eax, eax
        jz      .free_return0
; Read ALIGN_UP(NUM_CLUSTERS*3/2, 512) bytes.
; Note that this can be less than allocated, this is ok,
; overallocation simplifies calculatefatchain/restorefatchain.
        push    ebx
        mov     [ebp+FAT.fat_cache_ptr], eax
        mov     edx, [ebp+FAT.LAST_CLUSTER]
        lea     edx, [(edx+1)*3 + 512*2-1]
        shr     edx, 10
        xchg    eax, ebx
        xor     eax, eax
.read_fat:
        push    eax
        add     eax, [ebp+FAT.FAT_START]
        call    fs_read32_sys
        test    eax, eax
        pop     eax
        jz      @f
        dbgstr 'Failed to read FAT table'
        mov     eax, [ebp+FAT.fat_cache_ptr]
        call    free
        pop     ebx
        jmp     .free_return0
@@:
        add     ebx, 512
        inc     eax
        cmp     eax, edx
        jb      .read_fat
        mov     [ebp+FAT.fat12_unpacked_ptr], ebx
        call    calculatefatchain
        pop     ebx
        mov     eax, ebp
        pop     ebp
        ret

fat_free:
        push    eax
        mov     eax, [eax+FAT.fat_cache_ptr]
        call    free
        pop     eax
        jmp     free

calculatefatchain:

        pushad

        mov     esi, [ebp+FAT.fat_cache_ptr]
        mov     edi, [ebp+FAT.fat12_unpacked_ptr]

        mov     edx, [ebp+FAT.LAST_CLUSTER]
        and     edx, not 7
        lea     edx, [edi+(edx+8)*2]
        push    edx

 fcnew:
        mov     eax, dword [esi]
        mov     ebx, dword [esi+4]
        mov     ecx, dword [esi+8]
        mov     edx, ecx
        shr     edx, 4;8 ok
        shr     dx, 4;7 ok
        xor     ch, ch
        shld    ecx, ebx, 20;6 ok
        shr     cx, 4;5 ok
        shld    ebx, eax, 12
        and     ebx, 0x0fffffff;4 ok
        shr     bx, 4;3 ok
        shl     eax, 4
        and     eax, 0x0fffffff;2 ok
        shr     ax, 4;1 ok
        mov     dword [edi], eax
        mov     dword [edi+4], ebx
        mov     dword [edi+8], ecx
        mov     dword [edi+12], edx
        add     edi, 16
        add     esi, 12

        cmp     edi, [esp]
        jnz     fcnew
        pop     eax

        popad
        ret


restorefatchain:   ; restore fat chain

        pushad

        mov     esi, [ebp+FAT.fat12_unpacked_ptr]
        mov     edi, [ebp+FAT.fat_cache_ptr]

        mov     edx, [ebp+FAT.LAST_CLUSTER]
        and     edx, not 7
        lea     edx, [esi+(edx+8)*2]

  fcnew2:
        mov     eax, dword [esi]
        mov     ebx, dword [esi+4]
        shl     ax, 4
        shl     eax, 4
        shl     bx, 4
        shr     ebx, 4
        shrd    eax, ebx, 8
        shr     ebx, 8
        mov     dword [edi], eax
        mov     word [edi+4], bx
        add     edi, 6
        add     esi, 8

        cmp     esi, edx
        jb      fcnew2

        mov     esi, [ebp+FAT.NUMBER_OF_FATS]
        mov     edx, [ebp+FAT.LAST_CLUSTER]
        lea     edx, [(edx+1)*3 + 512*2-1]
        shr     edx, 10
        push    [ebp+FAT.FAT_START]

.write_fats:
        xor     eax, eax
        mov     ebx, [ebp+FAT.fat_cache_ptr]
.loop1:
        push    eax
        add     eax, [esp+4]
        call    fs_write32_sys
        test    eax, eax
        pop     eax
        jnz     .fail
        add     ebx, 512
        inc     eax
        cmp     eax, edx
        jb      .loop1
        pop     eax
        add     eax, [ebp+FAT.SECTORS_PER_FAT]
        push    eax
        dec     esi
        jnz     .write_fats
        pop     eax

        popad
        ret
.fail:
        dbgstr 'Failed to save FAT'
        popad
        ret

iglobal
label fat_legal_chars byte
; 0 = not allowed
; 1 = allowed only in long names
; 3 = allowed
        times 32 db 0
;                 ! " # $ % & ' ( ) * + , - . /
        db      1,3,0,3,3,3,3,3,3,3,0,1,1,3,3,0
;               0 1 2 3 4 5 6 7 8 9 : ; < = > ?
        db      3,3,3,3,3,3,3,3,3,3,0,1,0,1,0,0
;               @ A B C D E F G H I J K L M N O
        db      3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3
;               P Q R S T U V W X Y Z [ \ ] ^ _
        db      3,3,3,3,3,3,3,3,3,3,3,1,0,1,3,3
;               ` a b c d e f g h i j k l m n o
        db      3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3
;               p q r s t u v w x y z { | } ~
        db      3,3,3,3,3,3,3,3,3,3,3,3,0,3,3,0
endg

fat_name_is_legal:
; in: esi -> UTF-8 name
; out: CF=1 -> legal
        push    esi
        xor     eax, eax
@@:
        lodsb
        test    al, al
        js      @b
        test    [fat_legal_chars+eax], 1
        jnz     @b
        test    al, al
        jnz     @f
        stc
@@:
        pop     esi
        ret

fat_next_short_name:
; in: edi->8+3 name
; out: name corrected
;      CF=1 <=> error
        pushad
        mov     ecx, 8
        mov     al, '~'
        std
        push    edi
        add     edi, 7
        repnz scasb
        pop     edi
        cld
        jz      .tilde
; tilde is not found, insert "~1" at end
        add     edi, 6
        cmp     word [edi], '  '
        jnz     .insert_tilde
@@:
        dec     edi
        cmp     byte [edi], ' '
        jz      @b
        inc     edi
.insert_tilde:
        mov     word [edi], '~1'
        popad
        clc
        ret
.tilde:
        push    edi
        add     edi, 7
        xor     ecx, ecx
@@:
; after tilde may be only digits and trailing spaces
        cmp     byte [edi], '~'
        jz      .break
        cmp     byte [edi], ' '
        jz      .space
        cmp     byte [edi], '9'
        jnz     .found
        dec     edi
        jmp     @b
.space:
        dec     edi
        inc     ecx
        jmp     @b
.found:
        inc     byte [edi]
        add     dword [esp], 8
        jmp     .zerorest
.break:
        jecxz   .noplace
        inc     edi
        mov     al, '1'
@@:
        xchg    al, [edi]
        inc     edi
        cmp     al, ' '
        mov     al, '0'
        jnz     @b
.succ:
        pop     edi
        popad
        clc
        ret
.noplace:
        dec     edi
        cmp     edi, [esp]
        jz      .err
        add     dword [esp], 8
        mov     word [edi], '~1'
        inc     edi
        inc     edi
@@:
        mov     byte [edi], '0'
.zerorest:
        inc     edi
        cmp     edi, [esp]
        jb      @b
        pop     edi
        popad
        ;clc    ; automatically
        ret
.err:
        pop     edi
        popad
        stc
        ret

fat_gen_short_name:
;   in:
; esi -> UTF-8 name
; edi -> buffer (8+3=11 chars)
        pushad
        mov     eax, '    '
        push    edi
        stosd
        stosd
        stosd
        pop     edi
        xor     eax, eax
        movi    ebx, 8
        lea     ecx, [edi+8]
.loop:
        lodsb
        test    al, al
        js      .space
        jz      .done
        test    [fat_legal_chars+eax], 2
        jz      .space
        cmp     al, '.'
        jz      .dot
        dec     bl
        jns     .store
        inc     bl
.space:
        or      bh, 1
        jmp     .loop

.store:
        call    cp866toUpper
        stosb
        jmp     .loop

.dot:
        test    bh, 2
        jz      .firstdot
        pop     ebx
        add     ebx, edi
        sub     ebx, ecx
        push    ebx
        cmp     ebx, ecx
        jb      @f
        pop     ebx
        push    ecx
@@:
        cmp     edi, ecx
        jbe     .skip
@@:
        dec     edi
        mov     al, [edi]
        dec     ebx
        mov     [ebx], al
        mov     byte [edi], ' '
        cmp     edi, ecx
        ja      @b
.skip:
        mov     bh, 3
        jmp     @f
.firstdot:
        cmp     bl, 8
        jz      .space
        push    edi
        or      bh, 2
@@:
        mov     edi, ecx
        mov     bl, 3
        jmp     .loop
.done:
        test    bh, 2
        jz      @f
        pop     edi
@@:
        lea     edi, [ecx-8]
        test    bh, 1
        jz      @f
        call    fat_next_short_name
@@:
        popad
        ret

fat12_free_space:
;---------------------------------------------
;
; returns free space in edi
; rewr.by Mihasik
;---------------------------------------------

        push    eax ebx ecx

        mov     edi, [ebp+FAT.fat12_unpacked_ptr];start of FAT
        xor     ax, ax;Free cluster=0x0000 in FAT
        xor     ebx, ebx;counter
        mov     ecx, [ebp+FAT.LAST_CLUSTER]
        inc     ecx
        cld
    rdfs1:
        repne scasw
        jnz     rdfs2 ;if last cluster not 0
        inc     ebx
        test    ecx, ecx
        jnz     rdfs1
    rdfs2:
        shl     ebx, 9;free clusters*512
        mov     edi, ebx

        pop     ecx ebx eax
        ret



set_FAT:
;--------------------------------
; input  : EAX = cluster
;          EDX = value to save
;          EBP = pointer to FAT structure
; output : EDX = old value
;--------------------------------
; out: CF set <=> error
        push    eax ebx esi

        cmp     eax, 2
        jb      sfc_error
        cmp     eax, [ebp+FAT.LAST_CLUSTER]
        ja      sfc_error
        cmp     [ebp+FAT.fs_type], 12
        je      set_FAT12
        cmp     [ebp+FAT.fs_type], 16
        je      sfc_1
        add     eax, eax
  sfc_1:
        add     eax, eax
        mov     esi, 511
        and     esi, eax        ; esi = position in fat sector
        shr     eax, 9          ; eax = fat sector
        add     eax, [ebp+FAT.FAT_START]
        mov     ebx, [ebp+FAT.fat_cache_ptr]

        cmp     eax, [ebp+FAT.fat_in_cache]; is fat sector already in memory?
        je      sfc_in_cache    ; yes

        cmp     [ebp+FAT.fat_change], 0; is fat changed?
        je      sfc_no_change   ; no
        call    write_fat_sector; yes. write it into disk
        jc      sfc_error

  sfc_no_change:
        mov     [ebp+FAT.fat_in_cache], eax; save fat sector
        call    fs_read32_sys
        test    eax, eax
        jne     sfc_error


  sfc_in_cache:
        cmp     [ebp+FAT.fs_type], 16
        jne     sfc_test32

  sfc_set16:
        xchg    [ebx+esi], dx   ; save new value and get old value
        jmp     sfc_write

  sfc_test32:
        mov     eax, [ebp+FAT.fatMASK]

  sfc_set32:
        and     edx, eax
        xor     eax, -1         ; mask for high bits
        and     eax, [ebx+esi]  ; get high 4 bits
        or      eax, edx
        mov     edx, [ebx+esi]  ; get old value
        mov     [ebx+esi], eax  ; save new value

  sfc_write:
        mov     [ebp+FAT.fat_change], 1; fat has changed

  sfc_nonzero:
        and     edx, [ebp+FAT.fatMASK]

  sfc_return:
        pop     esi ebx eax
        ret
  sfc_error:
        stc
        jmp     sfc_return

  set_FAT12:
        test    edx, 0xF000
        jnz     sfc_error
        mov     ebx, [ebp+FAT.fat12_unpacked_ptr]
        xchg    [ebx+eax*2], dx
        mov     [ebp+FAT.fat_change], 1
        pop     esi ebx eax
        clc
        ret

get_FAT:
;--------------------------------
; input  : EAX = cluster
;          EBP = pointer to FAT structure
; output : EAX = next cluster
;--------------------------------
; out: CF set <=> error
        push    ebx esi

        cmp     [ebp+FAT.fs_type], 12
        je      get_FAT12

        cmp     [ebp+FAT.fs_type], 16
        je      gfc_1
        add     eax, eax
  gfc_1:
        add     eax, eax
        mov     esi, 511
        and     esi, eax        ; esi = position in fat sector
        shr     eax, 9          ; eax = fat sector
        add     eax, [ebp+FAT.FAT_START]
        mov     ebx, [ebp+FAT.fat_cache_ptr]

        cmp     eax, [ebp+FAT.fat_in_cache]; is fat sector already in memory?
        je      gfc_in_cache

        cmp     [ebp+FAT.fat_change], 0; is fat changed?
        je      gfc_no_change   ; no
        call    write_fat_sector; yes. write it into disk
        jc      hd_error_01

  gfc_no_change:
        mov     [ebp+FAT.fat_in_cache], eax
        call    fs_read32_sys
        test    eax, eax
        jne     hd_error_01

  gfc_in_cache:
        mov     eax, [ebx+esi]
        and     eax, [ebp+FAT.fatMASK]
  gfc_return:
        pop     esi ebx
        ret
 hd_error_01:
        stc
        jmp     gfc_return

get_FAT12:
        mov     ebx, [ebp+FAT.fat12_unpacked_ptr]
        movzx   eax, word [ebx+eax*2]
        pop     esi ebx
        clc
        ret


get_free_FAT:
;-----------------------------------------------------------
; output : if CARRY=0 EAX = # first cluster found free
;          if CARRY=1 disk full
; Note   : for more speed need to use fat_cache directly
;-----------------------------------------------------------
        push    ecx
        mov     ecx, [ebp+FAT.LAST_CLUSTER]; counter for full disk
        mov     eax, [ebp+FAT.fatStartScan]
        cmp     [ebp+FAT.fs_type], 12
        jz      get_free_FAT12
        dec     ecx
        cmp     eax, 2
        jb      gff_reset

  gff_test:
        cmp     eax, [ebp+FAT.LAST_CLUSTER]; if above last cluster start at cluster 2
        jbe     gff_in_range
  gff_reset:
        mov     eax, 2

  gff_in_range:
        push    eax
        call    get_FAT         ; get cluster state
        jc      gff_not_found_1

        test    eax, eax        ; is it free?
        pop     eax
        je      gff_found       ; yes
        inc     eax             ; next cluster
        dec     ecx             ; is all checked?
        jnz     gff_test        ; no

  gff_not_found:
        pop     ecx             ; yes. disk is full
        stc
        ret

  gff_not_found_1:
        pop     eax
        jmp     gff_not_found

  gff_found:
        lea     ecx, [eax+1]
        mov     [ebp+FAT.fatStartScan], ecx
        pop     ecx
        clc
        ret

get_free_FAT12:
        push    edx edi
        mov     edi, [ebp+FAT.fat12_unpacked_ptr]
        cmp     eax, 2
        jb      .reset
        cmp     eax, ecx
        jbe     @f
.reset:
        mov     eax, 2
@@:
        mov     edx, eax
        lea     edi, [edi+eax*2]
        sub     ecx, eax
        inc     ecx
        xor     eax, eax
        repnz scasw
        jz      .found
        cmp     edx, 2
        jz      .notfound
        mov     edi, [ebp+FAT.fat12_unpacked_ptr]
        lea     ecx, [edx-2]
        repnz scasw
        jnz     .notfound
.found:
        sub     edi, [ebp+FAT.fat12_unpacked_ptr]
        shr     edi, 1
        mov     [ebp+FAT.fatStartScan], edi
        lea     eax, [edi-1]
        pop     edi edx ecx
        ret
.notfound:
        pop     edi edx ecx
        stc
        ret


write_fat_sector:
;-----------------------------------------------------------
; write changed fat to disk
;-----------------------------------------------------------
        push    eax ebx ecx

        mov     [ebp+FAT.fat_change], 0
        mov     eax, [ebp+FAT.fat_in_cache]
        cmp     eax, -1
        jz      write_fat_not_used
        mov     ebx, [ebp+FAT.fat_cache_ptr]
        mov     ecx, [ebp+FAT.NUMBER_OF_FATS]

  write_next_fat:
        push    eax
        call    fs_write32_sys
        test    eax, eax
        pop     eax
        jnz     write_fat_not_used

        add     eax, [ebp+FAT.SECTORS_PER_FAT]
        dec     ecx
        jnz     write_next_fat

  write_fat_not_used:
        pop     ecx ebx eax
        ret





bcd2bin:
;----------------------------------
; input  : AL=BCD number (eg. 0x11)
; output : AH=0
;          AL=decimal number (eg. 11)
;----------------------------------
        xor     ah, ah
        shl     ax, 4
        shr     al, 4
        aad
        ret


get_date_for_file:
;-----------------------------------------------------
; Get date from CMOS and pack day,month,year in AX
; DATE   bits  0..4   : day of month 0..31
;              5..8   : month of year 1..12
;              9..15  : count of years from 1980
;-----------------------------------------------------
        mov     al, 0x7 ;day
        out     0x70, al
        in      al, 0x71
        call    bcd2bin
        ror     eax, 5

        mov     al, 0x8 ;month
        out     0x70, al
        in      al, 0x71
        call    bcd2bin
        ror     eax, 4

        mov     al, 0x9 ;year
        out     0x70, al
        in      al, 0x71
        call    bcd2bin
        add     ax, 20  ;because CMOS return only the two last
                        ;digit (eg. 2000 -> 00 , 2001 -> 01) and we
        rol     eax, 9  ;need the difference with 1980 (eg. 2001-1980)
        ret


get_time_for_file:
;-----------------------------------------------------
; Get time from CMOS and pack hour,minute,second in AX
; TIME   bits  0..4   : second (the low bit is lost)
;              5..10  : minute 0..59
;              11..15 : hour 0..23
;-----------------------------------------------------
        mov     al, 0x0 ;second
        out     0x70, al
        in      al, 0x71
        call    bcd2bin
        ror     eax, 6

        mov     al, 0x2 ;minute
        out     0x70, al
        in      al, 0x71
        call    bcd2bin
        ror     eax, 6

        mov     al, 0x4 ;hour
        out     0x70, al
        in      al, 0x71
        call    bcd2bin
        rol     eax, 11
        ret


set_current_time_for_entry:
;-----------------------------------------------------
; Set current time/date for file entry
; input  : ebx = file entry pointer
;-----------------------------------------------------
        push    eax
        call    get_time_for_file; update files date/time
        mov     [ebx+22], ax
        call    get_date_for_file
        mov     [ebx+24], ax
        pop     eax
        ret



add_disk_free_space:
;-----------------------------------------------------
; input  : ecx = cluster count
; Note   : negative = remove clusters from free space
;          positive = add clusters to free space
;-----------------------------------------------------
        test    ecx, ecx        ; no change
        je      add_dfs_no
        cmp     [ebp+FAT.fs_type], 32  ; free disk space only used by fat32
        jne     add_dfs_no

        push    eax ebx
        mov     eax, [ebp+FAT.ADR_FSINFO]
        lea     ebx, [ebp+FAT.fsinfo_buffer]
        call    fs_read32_sys
        test    eax, eax
        jnz     add_not_fs

        cmp     dword [ebx+0x1fc], 0xaa550000; check sector id
        jne     add_not_fs

        add     [ebx+0x1e8], ecx
        push    [ebp+FAT.fatStartScan]
        pop     dword [ebx+0x1ec]
        mov     eax, [ebp+FAT.ADR_FSINFO]
        call    fs_write32_sys
;    jc    add_not_fs

  add_not_fs:
        pop     ebx eax

  add_dfs_no:
        ret



clear_cluster_chain:
;-----------------------------------------------------
; input  : eax = first cluster
;-----------------------------------------------------
        push    eax ecx edx
        xor     ecx, ecx        ; cluster count

  clean_new_chain:
        cmp     eax, [ebp+FAT.LAST_CLUSTER]; end of file
        ja      delete_OK
        cmp     eax, 2          ; unfinished fat chain or zero length file
        jb      delete_OK
        cmp     eax, [ebp+FAT.ROOT_CLUSTER]; don't remove root cluster
        jz      delete_OK

        xor     edx, edx
        call    set_FAT         ; clear fat entry
        jc      access_denied_01

        inc     ecx             ; update cluster count
        mov     eax, edx        ; old cluster
        jmp     clean_new_chain

  delete_OK:
        call    add_disk_free_space; add clusters to free disk space
        clc
  access_denied_01:
        pop     edx ecx eax
        ret


if 0
get_hd_info:
;-----------------------------------------------------------
; output : eax = 0 - ok
;                3 - unknown FS
;               10 - access denied
;          edx = cluster size in bytes
;          ebx = total clusters on disk
;          ecx = free clusters on disk
;-----------------------------------------------------------
        cmp     [ebp+FAT.fs_type], 16
        jz      info_fat_ok
        cmp     [ebp+FAT.fs_type], 32
        jz      info_fat_ok
        xor     edx, edx
        xor     ebx, ebx
        xor     ecx, ecx
        mov     eax, ERROR_UNKNOWN_FS
        ret

  info_fat_ok:
;    call  reserve_hd1

        xor     ecx, ecx        ; count of free clusters
        mov     eax, 2
        mov     ebx, [ebp+FAT.LAST_CLUSTER]

  info_cluster:
        push    eax
        call    get_FAT         ; get cluster info
        jc      info_access_denied

        test    eax, eax        ; is it free?
        jnz     info_used       ; no
        inc     ecx

  info_used:
        pop     eax
        inc     eax
        cmp     eax, ebx        ; is above last cluster?
        jbe     info_cluster    ; no. test next cluster

        dec     ebx             ; cluster count
        imul    edx, [ebp+FAT.SECTORS_PER_CLUSTER], 512; cluster size in bytes
        xor     eax, eax
        ret

  info_access_denied:
        add     esp, 4
        xor     edx, edx
        xor     ebx, ebx
        xor     ecx, ecx
        mov     eax, ERROR_ACCESS_DENIED
        ret
end if

update_disk:
        cmp     [ebp+FAT.fat_change], 0 ; is fat changed?
        je      upd_no_change
        cmp     [ebp+FAT.fs_type], 12
        jz      .fat12
;-----------------------------------------------------------
; write changed fat and cache to disk
;-----------------------------------------------------------

        call    write_fat_sector
        jc      update_disk_acces_denied
        jmp     upd_no_change
.fat12:
        call    restorefatchain
        mov     [ebp+FAT.fat_change], 0

  upd_no_change:

        push    esi
        mov     esi, [ebp+PARTITION.Disk]
        call    disk_sync
        pop     esi
  update_disk_acces_denied:
        ret

fat_lock:
        lea     ecx, [ebp+FAT.Lock]
        jmp     mutex_lock
fat_unlock:
        lea     ecx, [ebp+FAT.Lock]
        jmp     mutex_unlock

fat_get_name:
; in: edi -> FAT entry
; out: ebp -> UTF-16 name, CF=1 -> no valid entry
        cmp     byte [edi], 0
        jz      .no
        cmp     byte [edi], 0xE5
        jz      .no
        cmp     byte [edi+11], 0xF
        jz      .longname
        test    byte [edi+11], 8
        jnz     .no
        push    ecx esi edi
        mov     esi, edi
        mov     edi, ebp
        mov     ecx, 8
@@:
        lodsb
        call    ansi2uni_char
        stosw
        loop    @b
        mov     cl, 8
@@:
        cmp     word [edi-2], ' '
        jnz     @f
        sub     edi, 2
        loop    @b
@@:
        mov     word [edi], '.'
        add     edi, 2
        mov     cl, 3
@@:
        lodsb
        call    ansi2uni_char
        stosw
        loop    @b
        mov     cl, 3
@@:
        cmp     word [edi-2], ' '
        jnz     @f
        sub     edi, 2
        loop    @b
        sub     edi, 2
@@:
        and     word [edi], 0   ; CF=0
        pop     edi esi ecx
        ret

.no:
        stc
        ret

.longname:
        mov     al, byte [edi]
        and     eax, 0x3F
        dec     eax
        cmp     al, 20
        jae     .no     ; ignore invalid entries
        mov     word [ebp+260*2], 0     ; force null-terminating for orphans
        imul    eax, 13*2
        test    byte [edi], 0x40
        jz      @f
        mov     word [ebp+eax+13*2], 0
@@: ; copy name (13 chars in UTF-16)
        push    esi edi
        lea     esi, [edi+1]
        lea     edi, [ebp+eax]
        movsd
        movsd
        movsd
        inc     esi
        sub     edi, 2
        movsd
        movsd
        movsd
        add     esi, 2
        movsd
        pop     edi esi
        test    eax, eax
        jnz     .no ; if this is not first entry, more processing required
        ret

fat_compare_name:
; in: esi -> name in UTF-8, ebp -> name in UTF-16
;   out:
; ZF=1 -> names match, esi -> next component of name
; ZF=0 -> esi is not changed
        push    ebp esi
@@:
        call    utf8to16
        call    utf16toUpper
        mov     edx, eax
        mov     ax, [ebp]
        call    utf16toUpper
        cmp     ax, dx
        jnz     .done
        add     ebp, 2
        test    ax, ax
        jnz     @b
        dec     esi
        pop     eax ebp
        xor     eax, eax    ; set ZF
        ret

.done:
        cmp     dx, '/'
        jnz     @f
        test    ax, ax
        jnz     @f
        mov     [esp], esi
@@:
        pop     esi ebp
        ret

fat_find_lfn:
; in: esi -> name in UTF-8
;     [esp+4] = next
;     [esp+8] = first
;     [esp+C]... - possibly parameters for first and next
; out: CF=1 - file not found, eax=error code
;      else CF=0, esi->next name component, edi->direntry
        pusha
        lea     eax, [esp+0Ch+20h]
        call    dword [eax-4]
        jc      .reterr
        sub     esp, 262*2      ; reserve place for LFN
.l1:
        lea     ebp, [esp]
        call    fat_get_name
        jc      .l2
        call    fat_compare_name
        jz      .found
.l2:
        mov     ebp, [esp+8+262*2]
        lea     eax, [esp+0Ch+20h+262*2]
        call    dword [eax-8]
        jnc     .l1
        add     esp, 262*2
.reterr:
        mov     [esp+28], eax
        stc
        popa
        ret

.found:
        add     esp, 262*2
        mov     ebp, [esp+8]
; if this is LFN entry, advance to true entry
        cmp     byte [edi+11], 0xF
        jnz     @f
        lea     eax, [esp+0Ch+20h]
        call    dword [eax-8]
        jc      .reterr
@@:
        add     esp, 8  ; CF=0
        push    esi
        push    edi
        popa
        ret

fat_time_to_bdfe:
; in: eax=FAT time
; out: eax=BDFE time
        push    ecx edx
        mov     ecx, eax
        mov     edx, eax
        shr     eax, 11
        shl     eax, 16 ; hours
        and     edx, 0x1F
        add     edx, edx
        mov     al, dl  ; seconds
        shr     ecx, 5
        and     ecx, 0x3F
        mov     ah, cl  ; minutes
        pop     edx ecx
        ret

fat_date_to_bdfe:
        push    ecx edx
        mov     ecx, eax
        mov     edx, eax
        shr     eax, 9
        add     ax, 1980
        shl     eax, 16 ; year
        and     edx, 0x1F
        mov     al, dl  ; day
        shr     ecx, 5
        and     ecx, 0xF
        mov     ah, cl  ; month
        pop     edx ecx
        ret

bdfe_to_fat_time:
        push    edx
        mov     edx, eax
        shr     eax, 16
        and     dh, 0x3F
        shl     eax, 6
        or      al, dh
        shr     dl, 1
        and     dl, 0x1F
        shl     eax, 5
        or      al, dl
        pop     edx
        ret

bdfe_to_fat_date:
        push    edx
        mov     edx, eax
        shr     eax, 16
        sub     ax, 1980
        and     dh, 0xF
        shl     eax, 4
        or      al, dh
        and     dl, 0x1F
        shl     eax, 5
        or      al, dl
        pop     edx
        ret

fat_entry_to_bdfe:
; convert FAT entry at edi to BDFE (block of data of folder entry) at esi, advance esi
        mov     eax, [ebp-4]
        mov     [esi+4], eax    ; cp866/UNICODE name
fat_entry_to_bdfe2:
        movzx   eax, byte [edi+11]
        mov     [esi], eax      ; attributes
        movzx   eax, word [edi+14]
        call    fat_time_to_bdfe
        mov     [esi+8], eax    ; creation time
        movzx   eax, word [edi+16]
        call    fat_date_to_bdfe
        mov     [esi+12], eax   ; creation date
        and     dword [esi+16], 0       ; last access time is not supported on FAT
        movzx   eax, word [edi+18]
        call    fat_date_to_bdfe
        mov     [esi+20], eax   ; last access date
        movzx   eax, word [edi+22]
        call    fat_time_to_bdfe
        mov     [esi+24], eax   ; last write time
        movzx   eax, word [edi+24]
        call    fat_date_to_bdfe
        mov     [esi+28], eax   ; last write date
        mov     eax, [edi+28]
        mov     [esi+32], eax   ; file size (low dword)
        xor     eax, eax
        mov     [esi+36], eax   ; file size (high dword)
        test    ebp, ebp
        jz      .ret
        add     esi, 40
        push    edi esi
        mov     edi, esi
        mov     esi, ebp
        test    byte [ebp-4], 1
        jz      .ansi
.uni:
        lodsw
        stosw
        test    eax, eax
        jnz     .uni
        pop     esi edi
        add     esi, 520
.ret:
        ret

.ansi:
        lodsw
        call    uni2ansi_char
        stosb
        test    al, al
        jnz     .ansi
        pop     esi edi
        add     esi, 264
        ret

bdfe_to_fat_entry:
; convert BDFE at edx to FAT entry at edi
; destroys eax
; attributes byte
        test    byte [edi+11], 8        ; volume label?
        jnz     @f
        mov     al, [edx]
        and     al, 0x27
        and     byte [edi+11], 0x10
        or      byte [edi+11], al
@@:
        mov     eax, [edx+8]
        call    bdfe_to_fat_time
        mov     [edi+14], ax            ; creation time
        mov     eax, [edx+12]
        call    bdfe_to_fat_date
        mov     [edi+16], ax            ; creation date
        mov     eax, [edx+20]
        call    bdfe_to_fat_date
        mov     [edi+18], ax            ; last access date
        mov     eax, [edx+24]
        call    bdfe_to_fat_time
        mov     [edi+22], ax            ; last write time
        mov     eax, [edx+28]
        call    bdfe_to_fat_date
        mov     [edi+24], ax            ; last write date
        ret

hd_find_lfn:
; in: esi -> path string in UTF-8
; out: CF=1 - file not found, eax=error code
;      else CF=0 and edi->direntry, eax=sector
        push    esi edi
        push    0
        push    0
        push    fat1x_root_first
        push    fat1x_root_next
        mov     eax, [ebp+FAT.ROOT_CLUSTER]
        cmp     [ebp+FAT.fs_type], 32
        jz      .fat32
.loop:
        and     [ebp+FAT.longname_sec1], 0
        and     [ebp+FAT.longname_sec2], 0
        call    fat_find_lfn
        jc      .notfound
        cmp     byte [esi], 0
        jz      .found
        test    byte [edi+11], 10h
        jz      .notfound
        and     dword [esp+12], 0
        mov     eax, [edi+20-2]
        mov     ax, [edi+26]    ; cluster
.fat32:
        mov     [esp+8], eax
        mov     dword [esp+4], fat_notroot_first
        mov     dword [esp], fat_notroot_next
        jmp     .loop

.notfound:
        add     esp, 16
        pop     edi esi
        stc
        ret

.found:
        lea     eax, [esp+8]
        cmp     dword [eax], 0
        jz      .root
        call    fat_get_sector
        jmp     .cmn

.root:
        mov     eax, [eax+4]
        add     eax, [ebp+FAT.ROOT_START]
.cmn:
        add     esp, 20         ; CF=0
        pop     esi
        ret

;----------------------------------------------------------------
fat_Read:
        call    fat_lock
        push    edi
        cmp     byte [esi], 0
        jnz     @f
.noaccess:
        pop     edi
        call    fat_unlock
        or      ebx, -1
        mov     eax, ERROR_ACCESS_DENIED
        ret
@@:
        call    hd_find_lfn
        jnc     .found
        pop     edi
        push    eax
        call    fat_unlock
        pop     eax
        or      ebx, -1
        ret
.found:
        test    byte [edi+11], 0x10     ; do not allow read directories
        jnz     .noaccess
        cmp     dword [ebx+8], 0
        jz      @f
        xor     ebx, ebx
        call    fat_unlock
        mov     eax, ERROR_END_OF_FILE
        pop     edi
        ret
@@:
        mov     edx, [ebx+4]    ; file offset
        mov     ecx, [ebx+12]   ; size
        mov     ebx, [ebx+16]   ; buffer
        push    ebx
        push    0
        test    ecx, ecx
        jz      .done
        mov     eax, [edi+28]
        sub     eax, edx
        jb      .fileEnd
        cmp     eax, ecx
        jae     @f
        mov     ecx, eax
        mov     byte [esp], 6
@@:
        mov     eax, [edi+20-2]
        mov     ax, [edi+26]
; now eax=cluster, ebx=buffer for data, ecx=count, edx=position
        mov     edi, [ebp+FAT.SECTORS_PER_CLUSTER]
        shl     edi, 9
@@:
        cmp     eax, 2
        jb      .fileEnd
        cmp     eax, [ebp+FAT.fatRESERVED]
        jae     .fileEnd
        sub     edx, edi
        jc      @f
        call    get_FAT
        jc      .noaccess2
        jmp     @b
@@:
        mov     esi, eax
        dec     eax
        dec     eax
        imul    eax, [ebp+FAT.SECTORS_PER_CLUSTER]
        add     eax, [ebp+FAT.DATA_START]
        add     edx, edi
        jz      .alignedCluster
        mov     edi, edx
        shr     edi, 9
        add     eax, edi
        and     edx, 511
        cmp     ecx, 512
        jc      .sectorPiece
        test    edx, edx
        jz      .alignedSector
.sectorPiece:
        push    eax ebx
        lea     ebx, [ebp+FAT.buffer]
        call    fs_read32_app
        test    eax, eax
        mov     eax, ebx
        pop     ebx
        jne     .noaccess3
        add     eax, edx
        push    ecx
        add     ecx, edx
        cmp     ecx, 512
        jbe     @f
        mov     ecx, 512
@@:
        sub     ecx, edx
        call    memmove
        sub     [esp], ecx
        add     ebx, ecx
        pop     ecx eax
        xor     edx, edx
        inc     edi
        inc     eax
        test    ecx, ecx
        jz      .done
.alignedSector:
        shl     edi, 9
        add     ecx, edi
        mov     edi, [ebp+FAT.SECTORS_PER_CLUSTER]
        shl     edi, 9
.alignedCluster:
        cmp     ecx, 512
        jc      .sectorPiece
        mov     edx, eax
        mov     eax, esi
@@:
        sub     ecx, edi
        jbe     .readEnd
        call    get_FAT
        jc      .noaccess4
        cmp     eax, 2
        jb      .fileEnd2
        cmp     eax, [ebp+FAT.fatRESERVED]
        jae     .fileEnd2
        inc     esi
        cmp     eax, esi
        jz      @b
.fragmentEnd:
        xchg    eax, esi
        dec     eax
        dec     eax
        imul    eax, [ebp+FAT.SECTORS_PER_CLUSTER]
        add     eax, [ebp+FAT.DATA_START]
        push    ecx
        mov     ecx, eax
        mov     eax, esi
        dec     eax
        dec     eax
        imul    eax, [ebp+FAT.SECTORS_PER_CLUSTER]
        add     eax, [ebp+FAT.DATA_START]
        push    eax
.readFragment:
        sub     ecx, edx
        mov     eax, edx
        xor     edx, edx
        call    fs_read64_app
        shl     ecx, 9
        add     ebx, ecx
        test    eax, eax
        pop     eax
        jnz     .noaccess3
        pop     ecx
        xor     edx, edx
        jecxz   .done
        jmp     .alignedCluster
.readEnd:
        add     ecx, edi
        mov     edi, ecx
        and     ecx, 511
        shr     edi, 9
        dec     eax
        dec     eax
        imul    eax, [ebp+FAT.SECTORS_PER_CLUSTER]
        add     eax, [ebp+FAT.DATA_START]
        add     eax, edi
        push    ecx
        push    eax
        mov     ecx, eax
        jmp     .readFragment
.noaccess3:
        pop     eax
.noaccess2:
        mov     byte [esp], ERROR_DEVICE
.done:
        call    fat_unlock
        pop     eax edx edi
        sub     ebx, edx
        ret
.fileEnd:
        mov     byte [esp], ERROR_END_OF_FILE
        jmp     .done
.noaccess4:
        mov     byte [esp], ERROR_DEVICE
        jmp     @f
.fileEnd2:
        mov     byte [esp], ERROR_END_OF_FILE
@@:
        inc     esi
        xor     ecx, ecx
        jmp     .fragmentEnd

;----------------------------------------------------------------
fat_ReadFolder:
        call    fat_lock
        mov     eax, [ebp+FAT.ROOT_CLUSTER]
        push    edi
        cmp     byte [esi], 0
        jz      .doit
        call    hd_find_lfn
        jnc     .found
        pop     edi
        push    eax
        call    fat_unlock
        pop     eax
        or      ebx, -1
        ret
.found:
        test    byte [edi+11], 0x10     ; do not allow read files
        jnz     .found_dir
        pop     edi
        call    fat_unlock
        or      ebx, -1
        mov     eax, ERROR_ACCESS_DENIED
        ret
.found_dir:
        mov     eax, [edi+20-2]
        mov     ax, [edi+26]    ; eax=cluster
.doit:
        push    esi
        sub     esp, 262*2      ; reserve space for LFN
        push    dword [ebx+8]   ; cp866/UNICODE name
        mov     edx, [ebx+16]   ; pointer to buffer
; init header
        push    eax
        mov     edi, edx
        mov     ecx, 32/4
        xor     eax, eax
        rep stosd
        pop     eax
        mov     byte [edx], 1   ; version
        mov     esi, edi        ; esi points to BDFE
        mov     ecx, [ebx+12]   ; number of blocks to read
        mov     ebx, [ebx+4]    ; index of the first block
.new_cluster:
        mov     [ebp+FAT.cluster_tmp], eax
        test    eax, eax
        jnz     @f
        cmp     [ebp+FAT.fs_type], 32
        jz      .notfound
        mov     eax, [ebp+FAT.ROOT_START]
        push    [ebp+FAT.ROOT_SECTORS]
        push    ebx
        jmp     .new_sector
@@:
        dec     eax
        dec     eax
        imul    eax, [ebp+FAT.SECTORS_PER_CLUSTER]
        push    [ebp+FAT.SECTORS_PER_CLUSTER]
        add     eax, [ebp+FAT.DATA_START]
        push    ebx
.new_sector:
        lea     ebx, [ebp+FAT.buffer]
        mov     edi, ebx
        push    eax
        call    fs_read32_sys
        test    eax, eax
        pop     eax
        jnz     .notfound2
        add     ebx, 512
        push    eax
.l1:
        push    ebp
        lea     ebp, [esp+20]
        call    fat_get_name
        pop     ebp
        jc      .l2
        cmp     byte [edi+11], 0xF
        jnz     .do_bdfe
        add     edi, 0x20
        cmp     edi, ebx
        jb      .do_bdfe
        pop     eax
        inc     eax
        dec     dword [esp+4]
        jnz     @f
        mov     eax, [ebp+FAT.cluster_tmp]
        test    eax, eax
        jz      .done
        call    get_FAT
        jc      .notfound2
        cmp     eax, 2
        jb      .done
        cmp     eax, [ebp+FAT.fatRESERVED]
        jae     .done
        push    eax
        mov     eax, [ebp+FAT.SECTORS_PER_CLUSTER]
        mov     [esp+8], eax
        pop     eax
        mov     [ebp+FAT.cluster_tmp], eax
        dec     eax
        dec     eax
        imul    eax, [ebp+FAT.SECTORS_PER_CLUSTER]
        add     eax, [ebp+FAT.DATA_START]
@@:
        lea     ebx, [ebp+FAT.buffer]
        mov     edi, ebx
        push    eax
        call    fs_read32_sys
        test    eax, eax
        pop     eax
        jnz     .notfound2
        add     ebx, 512
        push    eax
.do_bdfe:
        inc     dword [edx+8]   ; new file found
        dec     dword [esp+4]
        jns     .l2
        dec     ecx
        js      .l2
        inc     dword [edx+4]   ; new file block copied
        push    ebp
        lea     ebp, [esp+20]
        call    fat_entry_to_bdfe
        pop     ebp
.l2:
        add     edi, 0x20
        cmp     edi, ebx
        jb      .l1
        pop     eax
        inc     eax
        dec     dword [esp+4]
        jnz     .new_sector
        mov     eax, [ebp+FAT.cluster_tmp]
        test    eax, eax
        jz      .done
        call    get_FAT
        jc      .notfound2
        cmp     eax, 2
        jb      .done
        cmp     eax, [ebp+FAT.fatRESERVED]
        jae     .done
        push    eax
        mov     eax, [ebp+FAT.SECTORS_PER_CLUSTER]
        mov     [esp+8], eax
        pop     eax
        pop     ebx
        add     esp, 4
        jmp     .new_cluster
.notfound2:
        add     esp, 8
.notfound:
        add     esp, 262*2+4
        pop     esi edi
        mov     ebx, [edx+4]
        call    fat_unlock
        mov     eax, ERROR_DEVICE
        ret
.done:
        add     esp, 262*2+4+8
        mov     ebx, [edx+4]
        xor     eax, eax
        dec     ecx
        js      @f
        mov     al, ERROR_END_OF_FILE
@@:
        push    eax
        call    fat_unlock
        pop     eax
        pop     esi edi
        ret

fat1x_root_next:
        push    ecx
        lea     ecx, [ebp+FAT.buffer+0x200-0x20]
        cmp     edi, ecx
        jae     fat1x_root_next_sector
        pop     ecx
        add     edi, 0x20
        ret     ; CF=0
fat1x_root_next_sector:
; read next sector
        push    [ebp+FAT.longname_sec2]
        pop     [ebp+FAT.longname_sec1]
        mov     ecx, [eax+4]
        push    ecx
        add     ecx, [ebp+FAT.ROOT_START]
        mov     [ebp+FAT.longname_sec2], ecx
        pop     ecx
        inc     ecx
        mov     [eax+4], ecx
        cmp     ecx, [ebp+FAT.ROOT_SECTORS]
        pop     ecx
        jb      fat1x_root_first
        mov     eax, ERROR_FILE_NOT_FOUND
        stc
        ret
fat1x_root_first:
        mov     eax, [eax+4]
        add     eax, [ebp+FAT.ROOT_START]
        push    ebx
        lea     edi, [ebp+FAT.buffer]
        mov     ebx, edi
        call    fs_read32_sys
        pop     ebx
        test    eax, eax
        jnz     .readerr
        ret     ; CF=0
.readerr:
        mov     eax, ERROR_DEVICE
        stc
        ret
.notfound:
        mov     eax, ERROR_FILE_NOT_FOUND
        stc
        ret
fat1x_root_begin_write:
        push    edi eax
        call    fat1x_root_first
        pop     eax edi
        ret
fat1x_root_end_write:
        pusha
        mov     eax, [eax+4]
        add     eax, [ebp+FAT.ROOT_START]
        lea     ebx, [ebp+FAT.buffer]
        call    fs_write32_sys
        popa
        ret
fat1x_root_next_write:
        push    ecx
        lea     ecx, [ebp+FAT.buffer+0x200]
        cmp     edi, ecx
        jae     @f
        pop     ecx
        ret
@@:
        call    fat1x_root_end_write
        jmp     fat1x_root_next_sector
fat1x_root_extend_dir:
        stc
        ret

fat_notroot_next:
        push    ecx
        lea     ecx, [ebp+FAT.buffer+0x200-0x20]
        cmp     edi, ecx
        jae     fat_notroot_next_sector
        pop     ecx
        add     edi, 0x20
        ret     ; CF=0
fat_notroot_next_sector:
        push    [ebp+FAT.longname_sec2]
        pop     [ebp+FAT.longname_sec1]
        push    eax
        call    fat_get_sector
        mov     [ebp+FAT.longname_sec2], eax
        pop     eax
        mov     ecx, [eax+4]
        inc     ecx
        cmp     ecx, [ebp+FAT.SECTORS_PER_CLUSTER]
        jae     fat_notroot_next_cluster
        mov     [eax+4], ecx
        jmp     @f
fat_notroot_next_cluster:
        push    eax
        mov     eax, [eax]
        call    get_FAT
        mov     ecx, eax
        pop     eax
        jc      fat_notroot_first.deverr
        cmp     ecx, 2
        jb      fat_notroot_next_err
        cmp     ecx, [ebp+FAT.fatRESERVED]
        jae     fat_notroot_next_err
        mov     [eax], ecx
        and     dword [eax+4], 0
@@:
        pop     ecx
fat_notroot_first:
        call    fat_get_sector
        push    ebx
        lea     edi, [ebp+FAT.buffer]
        mov     ebx, edi
        call    fs_read32_sys
        pop     ebx
        test    eax, eax
        jz      .ret ; CF=0
        push    ecx
.deverr:
        pop     ecx
        mov     eax, ERROR_DEVICE
        stc
.ret:
        ret
fat_notroot_next_err:
        pop     ecx
        mov     eax, ERROR_FILE_NOT_FOUND
        stc
        ret
fat_notroot_begin_write:
        push    eax edi
        call    fat_notroot_first
        pop     edi eax
        ret
fat_notroot_end_write:
        call    fat_get_sector
        push    ebx
        lea     ebx, [ebp+FAT.buffer]
        call    fs_write32_sys
        pop     ebx
        ret
fat_notroot_next_write:
        push    ecx
        lea     ecx, [ebp+FAT.buffer+0x200]
        cmp     edi, ecx
        jae     @f
        pop     ecx
        ret
@@:
        push    eax
        call    fat_notroot_end_write
        pop     eax
        jmp     fat_notroot_next_sector
fat_notroot_extend_dir:
        push    eax
        call    get_free_FAT
        jnc     .found
        pop     eax
        ret     ; CF=1
.found:
        push    edx
        mov     edx, [ebp+FAT.fatEND]
        call    set_FAT
        jc      .writeerr
        mov     edx, eax
        mov     eax, [esp+4]
        mov     eax, [eax]
        push    edx
        call    set_FAT
        pop     edx
        jnc     @f
.writeerr:
        pop     edx
        pop     eax
        stc
        ret
@@:
        push    ecx
        or      ecx, -1
        call    add_disk_free_space
; zero new cluster
        mov     ecx, 512/4
        lea     edi, [ebp+FAT.buffer]
        push    edi
        xor     eax, eax
        rep stosd
        pop     edi
        pop     ecx
        mov     eax, [esp+4]
        mov     [eax], edx
        and     dword [eax+4], 0
        pop     edx
        mov     eax, [eax]
        dec     eax
        dec     eax
        push    ebx ecx
        mov     ecx, [ebp+FAT.SECTORS_PER_CLUSTER]
        imul    eax, ecx
        add     eax, [ebp+FAT.DATA_START]
        mov     ebx, edi
@@:
        push    eax
        call    fs_write32_sys
        pop     eax
        inc     eax
        loop    @b
        pop     ecx ebx eax
        clc
        ret

fat_get_sector:
        push    ecx
        mov     ecx, [eax]
        dec     ecx
        dec     ecx
        imul    ecx, [ebp+FAT.SECTORS_PER_CLUSTER]
        add     ecx, [ebp+FAT.DATA_START]
        add     ecx, [eax+4]
        mov     eax, ecx
        pop     ecx
        ret

fshrad:
        call    fat_unlock
        mov     eax, ERROR_ACCESS_DENIED
        xor     ebx, ebx
        ret

;----------------------------------------------------------------
fat_CreateFolder:
        push    1
        jmp     @f

fat_CreateFile:
        push    0
@@:
        call    fat_lock
        pop     eax
        cmp     byte [esi], 0
        jz      fshrad
        mov     ecx, [ebx+12]
        mov     edx, [ebx+16]
        pushad
        xor     edi, edi
        push    esi
@@:
        lodsb
        test    al, al
        jz      @f
        cmp     al, '/'
        jnz     @b
        lea     edi, [esi-1]
        jmp     @b

@@:
        pop     esi
        test    edi, edi
        jnz     .noroot
        mov     edx, [ebp+FAT.ROOT_CLUSTER]
        cmp     [ebp+FAT.fs_type], 32
        jz      .pushnotroot
        xor     edx, edx
        push    edx
        push    fat1x_root_extend_dir
        push    fat1x_root_end_write
        push    fat1x_root_next_write
        push    fat1x_root_begin_write
        push    edx
        push    edx
        push    fat1x_root_first
        push    fat1x_root_next
        jmp     .common1

.noroot:
        mov     eax, ERROR_ACCESS_DENIED
        cmp     byte [edi+1], 0
        jz      .ret1
; check existence
        mov     byte [edi], 0
        push    edi
        call    hd_find_lfn
        pop     esi
        mov     byte [esi], '/'
        jnc     @f
.notfound0:
        mov     eax, ERROR_FILE_NOT_FOUND
.ret1:
        mov     [esp+28], eax
        call    fat_unlock
        popad
        xor     ebx, ebx
        ret

@@:
        inc     esi
        test    byte [edi+11], 0x10     ; must be directory
        mov     eax, ERROR_ACCESS_DENIED
        jz      .ret1
        mov     edx, [edi+20-2]
        mov     dx, [edi+26]            ; ebp=cluster
        mov     eax, ERROR_FS_FAIL
        cmp     edx, 2
        jb      .ret1
.pushnotroot:
        push    edx
        push    fat_notroot_extend_dir
        push    fat_notroot_end_write
        push    fat_notroot_next_write
        push    fat_notroot_begin_write
        push    0
        push    edx
        push    fat_notroot_first
        push    fat_notroot_next
.common1:
        call    fat_find_lfn
        jc      .notfound
        test    byte [edi+11], 10h
        jz      .exists_file
; found directory
        add     esp, 36
        call    fat_unlock
        popad
        test    al, al
        mov     eax, ERROR_ACCESS_DENIED
        jz      @f
        mov     al, 0
@@:
        xor     ebx, ebx
        ret

.exists_file:
        cmp     byte [esp+36+28], 0
        jz      @f
        add     esp, 36
        call    fat_unlock
        popad
        mov     eax, ERROR_ACCESS_DENIED
        xor     ebx, ebx
        ret

@@: ; delete FAT chain
        push    edi
        xor     eax, eax
        mov     dword [edi+28], eax     ; zero size
        xor     ecx, ecx
        mov     eax, [edi+20-2]
        mov     ax, [edi+26]
        mov     word [edi+20], cx
        mov     word [edi+26], cx
        test    eax, eax
        jz      .done1
@@:
        cmp     eax, [ebp+FAT.fatRESERVED]
        jae     .done1
        push    edx
        xor     edx, edx
        call    set_FAT
        mov     eax, edx
        pop     edx
        jc      .done1
        inc     ecx
        jmp     @b

.short_name_found:
        pop     ecx edi esi
        call    fat_next_short_name
        jnc     .test_short_name_loop
.disk_full:
        add     esp, 12+36
        call    fat_unlock
        popa
        mov     eax, ERROR_DISK_FULL
        xor     ebx, ebx
        ret

.notfound:  ; generate short name
        call    fat_name_is_legal
        jc      @f
        add     esp, 36
        call    fat_unlock
        popad
        mov     eax, ERROR_FILE_NOT_FOUND
        xor     ebx, ebx
        ret

@@:
        sub     esp, 12
        mov     edi, esp
        call    fat_gen_short_name
.test_short_name_loop:
        push    esi edi ecx
        mov     esi, edi
        lea     eax, [esp+12+12+8]
        mov     edx, [eax+24]
        mov     [eax], edx
        and     dword [eax+4], 0
        call    dword [eax-4]
        jc      .found
.test_short_name_entry:
        cmp     byte [edi+11], 0xF
        jz      .test_short_name_cont
        mov     ecx, 11
        push    esi edi
        repz cmpsb
        pop     edi esi
        jz      .short_name_found
.test_short_name_cont:
        lea     eax, [esp+12+12+8]
        call    dword [eax-8]
        jnc     .test_short_name_entry
.found:
        pop     ecx edi esi
; now find space in directory
; we need to save LFN <=> LFN is not equal to short name <=> generated name contains '~'
        mov     al, '~'
        push    ecx edi
        mov     ecx, 8
        repnz scasb
        movi    eax, 1     ; 1 entry
        jnz     .notilde
; we need ceil(strlen(esi)/13) additional entries = floor((strlen(esi)+12+13)/13) total
        xor     ecx, ecx
        push    esi
@@:
        call    utf8to16
        inc     ecx
        test    ax, ax
        jnz     @b
        pop     esi
        mov     eax, ecx
        add     eax, 12+13-1
        mov     ecx, 13
        cdq
        div     ecx
.notilde:
        push    -1
        push    -1
        push    -1
; find <eax> successive entries in directory
        xor     ecx, ecx
        push    eax
        lea     eax, [esp+16+8+12+8]
        mov     edx, [eax+24]
        mov     [eax], edx
        and     dword [eax+4], 0
        call    dword [eax-4]
        pop     eax
        jnc     .scan_dir
.fsfrfe3:
        add     esp, 12+8+12+36
        call    fat_unlock
        popad
        mov     eax, ERROR_DEVICE
        xor     ebx, ebx
        ret

.scan_dir:
        cmp     byte [edi], 0
        jz      .free
        cmp     byte [edi], 0xE5
        jz      .free
        xor     ecx, ecx
.scan_cont:
        push    eax
        lea     eax, [esp+16+8+12+8]
        call    dword [eax-8]
        mov     edx, eax
        pop     eax
        jnc     .scan_dir
        cmp     edx, ERROR_DEVICE
        jz      .fsfrfe3
        push    eax
        lea     eax, [esp+16+8+12+8]
        call    dword [eax+20]          ; extend directory
        pop     eax
        jnc     .scan_dir
        add     esp, 12+8+12+36
        call    fat_unlock
        popad
        mov     eax, ERROR_DISK_FULL
        xor     ebx, ebx
        ret

.free:
        test    ecx, ecx
        jnz     @f
        mov     [esp], edi
        mov     ecx, [esp+12+8+12+8]
        mov     [esp+4], ecx
        mov     ecx, [esp+12+8+12+12]
        mov     [esp+8], ecx
        xor     ecx, ecx
@@:
        inc     ecx
        cmp     ecx, eax
        jb      .scan_cont
; found!
        push    esi ecx
; If creating a directory, allocate one data cluster now and fail immediately
; if this is impossible. This prevents from creating an invalid directory entry
; on a full disk.
; yup, the argument is quite non-intuitive... but what should I do if
; the entire function uses such arguments? BTW, it refers to al from pushad,
; which in turn is filled with 0 in fat_CreateFile and 1 in fat_CreateFolder.
        cmp     byte [esp+8+12+8+12+36+28], 0
        jz      .no.preallocate.folder.data
        call    get_free_FAT
        jnc     @f
        add     esp, 8+12+8
        jmp     .disk_full

@@:
        mov     [esp+8+12+8+12+36+20], eax  ; store the cluster somewhere
.no.preallocate.folder.data:    ; calculate name checksum
        mov     esi, [esp+8+12]
        mov     ecx, 11
        xor     eax, eax
@@:
        ror     al, 1
        add     al, [esi]
        inc     esi
        loop    @b
        pop     ecx esi edi
        pop     dword [esp+8+12+12]
        pop     dword [esp+8+12+12]
; edi points to first entry in free chunk
        dec     ecx
        jz      .nolfn
        push    esi eax
        lea     eax, [esp+8+8+12+8]
        call    dword [eax+8]   ; begin write
        mov     al, 40h
.writelfn:
        or      al, cl
        stosb
        mov     esi, [esp+4]
        push    ecx
        dec     ecx
        jz      @f
        imul    ecx, 13
.scroll:
        call    utf8to16
        loop    .scroll
@@:
        mov     cl, 5
        call    fat_read_symbols
        mov     ax, 0xF
        stosw
        mov     al, [esp+4]
        stosb
        mov     cl, 6
        call    fat_read_symbols
        xor     eax, eax
        stosw
        mov     cl, 2
        call    fat_read_symbols
        pop     ecx
        lea     eax, [esp+8+8+12+8]
        call    dword [eax+12]  ; next write
        xor     eax, eax
        loop    .writelfn
        pop     eax esi
.nolfn:
        xchg    esi, [esp]
        mov     ecx, 11
        rep movsb
        mov     word [edi], 20h         ; attributes
        sub     edi, 11
        pop     esi ecx
        add     esp, 12
        mov     byte [edi+13], 0        ; tenths of a second at file creation time
        call    get_time_for_file
        mov     [edi+14], ax            ; creation time
        mov     [edi+22], ax            ; last write time
        call    get_date_for_file
        mov     [edi+16], ax            ; creation date
        mov     [edi+24], ax            ; last write date
        mov     [edi+18], ax            ; last access date
        xor     ecx, ecx
        mov     word [edi+20], cx       ; high word of cluster
        mov     word [edi+26], cx       ; low word of cluster - to be filled
        mov     dword [edi+28], ecx     ; file size - to be filled
        cmp     byte [esp+36+28], cl
        jz      .doit
; create directory
        mov     byte [edi+11], 10h      ; attributes: folder
        mov     esi, edi
        lea     eax, [esp+8]
        call    dword [eax+16]  ; flush directory
        mov     eax, [esp+36+20] ; extract saved cluster
        mov     [esp+36+20], edi ; this is needed for calculating arg of add_disk_free_space!
        push    ecx
        mov     ecx, [ebp+FAT.SECTORS_PER_CLUSTER]
        shl     ecx, 9
        push    ecx
        push    edi
        jmp     .doit2

.done1:
        pop     edi
        call    get_time_for_file
        mov     [edi+22], ax
        call    get_date_for_file
        mov     [edi+24], ax
        mov     [edi+18], ax
        or      byte [edi+11], 20h      ; set 'archive' attribute
.doit:
        mov     esi, [esp+36+20]
        lea     eax, [esp+8]
        call    dword [eax+16]  ; flush directory
        push    ecx
        mov     ecx, [esp+4+36+24]
        push    ecx
        push    edi
        test    ecx, ecx
        jz      .done
        call    get_free_FAT
        jc      .diskfull
.doit2:
        push    eax
        mov     [edi+26], ax
        shr     eax, 16
        mov     [edi+20], ax
        lea     eax, [esp+16+8]
        call    dword [eax+16]  ; flush directory
        pop     eax
        push    edx
        mov     edx, [ebp+FAT.fatEND]
        call    set_FAT
        pop     edx
.write_cluster:
        push    eax
        dec     eax
        dec     eax
        imul    eax, [ebp+FAT.SECTORS_PER_CLUSTER]
        add     eax, [ebp+FAT.DATA_START]
        push    [ebp+FAT.SECTORS_PER_CLUSTER]
.write_sector:
        cmp     byte [esp+20+36+28], 0
        jnz     .writedir
        mov     ecx, 512
        cmp     dword [esp+12], ecx
        jb      .writeshort
; we can write directly from given buffer
        mov     ebx, esi
        add     esi, ecx
        jmp     .writecommon

.writeshort:
        mov     ecx, [esp+12]
        push    ecx
        lea     edi, [ebp+FAT.buffer]
        mov     ebx, edi
        rep movsb
.writedircont:
        lea     ecx, [ebp+FAT.buffer+0x200]
        sub     ecx, edi
        push    eax
        xor     eax, eax
        rep stosb
        pop     eax
        pop     ecx
.writecommon:
        push    eax
        call    fs_write32_app
        test    eax, eax
        pop     eax
        jnz     .writeerr
        inc     eax
        sub     dword [esp+12], ecx
        jz      .writedone
        dec     dword [esp]
        jnz     .write_sector
        pop     eax
; allocate new cluster
        pop     eax
        mov     ecx, eax
        call    get_free_FAT
        jc      .diskfull
        push    edx
        mov     edx, [ebp+FAT.fatEND]
        call    set_FAT
        xchg    eax, ecx
        mov     edx, ecx
        call    set_FAT
        pop     edx
        xchg    eax, ecx
        jmp     .write_cluster

.diskfull:
        mov     eax, ERROR_DISK_FULL
        jmp     .ret

.writeerr:
        pop     eax eax
        sub     esi, ecx
        mov     eax, ERROR_DEVICE
        jmp     .ret

.writedone:
        pop     eax eax
.done:
        xor     eax, eax
.ret:
        pop     edi ecx
        sub     esi, [esp+4+36+20]
        mov     [esp+4+36+28], eax
        mov     [esp+4+36+16], esi
        lea     eax, [esp+12]
        call    dword [eax+8]
        mov     [edi+28], esi
        call    dword [eax+16]
        mov     [esp+36+16], ebx
        lea     eax, [esi+511]
        shr     eax, 9
        mov     ecx, [ebp+FAT.SECTORS_PER_CLUSTER]
        lea     eax, [eax+ecx-1]
        xor     edx, edx
        div     ecx
        pop     ecx
        sub     ecx, eax
        call    add_disk_free_space
        add     esp, 36
        call    update_disk
        call    fat_unlock
        popad
        ret

.writedir:
        push    512
        lea     edi, [ebp+FAT.buffer]
        mov     ebx, edi
        mov     ecx, [ebp+FAT.SECTORS_PER_CLUSTER]
        shl     ecx, 9
        cmp     ecx, [esp+16]
        jnz     .writedircont
        dec     dword [esp+20]
        push    esi
        mov     ecx, 32/4
        rep movsd
        pop     esi
        mov     dword [edi-32], '.   '
        mov     dword [edi-32+4], '    '
        mov     dword [edi-32+8], '    '
        mov     byte [edi-32+11], 10h
        push    esi
        mov     ecx, 32/4
        rep movsd
        pop     esi
        mov     dword [edi-32], '..  '
        mov     dword [edi-32+4], '    '
        mov     dword [edi-32+8], '    '
        mov     byte [edi-32+11], 10h
        mov     ecx, [esp+20+36]
        cmp     ecx, [ebp+FAT.ROOT_CLUSTER]
        jnz     @f
        xor     ecx, ecx
@@:
        mov     word [edi-32+26], cx
        shr     ecx, 16
        mov     [edi-32+20], cx
        jmp     .writedircont

@@:
        or      eax, -1
        rep stosw
        ret

fat_read_symbols:
        test    esi, esi
        jz      @b
        call    utf8to16
        stosw
        test    ax, ax
        jnz     @f
        xor     esi, esi
@@:
        loop    fat_read_symbols
        ret

;----------------------------------------------------------------
fat_Write:
        cmp     byte [esi], 0
        jz      .access_denied
        call    fat_lock
        push    edi
        call    hd_find_lfn
        jnc     .found
        pop     edi
        push    eax
        call    fat_unlock
.ret0:
        pop     eax
        xor     ebx, ebx
        ret

.access_denied:
        push    ERROR_ACCESS_DENIED
        jmp     .ret0

.found:     ; FAT does not support files larger than 4GB
        cmp     dword [ebx+8], 0
        jz      @f
.eof:
        pop     edi
        push    ERROR_END_OF_FILE
        call    fat_unlock
        jmp     .ret0
@@:
        mov     ecx, [ebx+12]
        mov     edx, [ebx+16]
        mov     ebx, [ebx+4]
; now edi points to direntry, ebx=start byte to write,
; ecx=number of bytes to write, edx=data pointer
; extend file if needed
        add     ecx, ebx
        jc      .eof    ; FAT does not support files larger than 4GB
        push    edx
        push    eax     ; save directory sector
        push    0       ; return value=0
        call    get_time_for_file
        mov     [edi+22], ax            ; last write time
        call    get_date_for_file
        mov     [edi+24], ax            ; last write date
        mov     [edi+18], ax            ; last access date
        push    dword [edi+28]          ; save current file size
        cmp     ecx, [edi+28]
        jbe     .length_ok
        cmp     ecx, ebx
        jz      .length_ok
        call    hd_extend_file
        jnc     .length_ok
        mov     [esp+4], eax
; hd_extend_file can return three error codes: FAT table error, device error or disk full.
; First two cases are fatal errors, in third case we may write some data
        cmp     al, ERROR_DISK_FULL
        jz      .disk_full
        call    fat_unlock
        pop     eax
        pop     eax
        pop     ecx
        pop     edx
        pop     edi
        xor     ebx, ebx
        ret
.disk_full:
; correct number of bytes to write
        mov     ecx, [edi+28]
        cmp     ecx, ebx
        ja      .length_ok
        push    0
.ret:
        pop     eax
        sub     edx, [esp+12]
        mov     ebx, edx        ; ebx=number of written bytes
        call    update_disk
        test    eax, eax
        jz      @f
        mov     byte [esp+4], ERROR_DEVICE
@@:
        call    fat_unlock
        pop     eax
        pop     eax
        pop     ecx
        pop     edx
        pop     edi
        ret
.length_ok:
        mov     esi, [edi+28]
        mov     eax, [edi+20-2]
        mov     ax, [edi+26]
        mov     edi, eax        ; edi=current cluster
        push    0               ; current sector in cluster
; save directory
        mov     eax, [esp+12]
        push    ebx
        lea     ebx, [ebp+FAT.buffer]
        call    fs_write32_sys
        pop     ebx
        test    eax, eax
        jz      @f
.device_err:
        mov     byte [esp+8], ERROR_DEVICE
        jmp     .ret
.fat_err:
        mov     byte [esp+8], ERROR_FS_FAIL
        jmp     .ret
@@:

; now ebx=start pos, ecx=end pos, both lie inside file
        sub     ecx, ebx
        jz      .ret
.write_loop:
; skip unmodified sectors
        cmp     dword [esp+4], 0x200
        jb      .modify
        sub     ebx, 0x200
        jae     .skip
        add     ebx, 0x200
.modify:
; get length of data in current sector
        push    ecx
        sub     ebx, 0x200
        jb      .hasdata
        neg     ebx
        xor     ecx, ecx
        jmp     @f
.hasdata:
        neg     ebx
        cmp     ecx, ebx
        jbe     @f
        mov     ecx, ebx
@@:
; get current sector number
        mov     eax, edi
        dec     eax
        dec     eax
        imul    eax, [ebp+FAT.SECTORS_PER_CLUSTER]
        add     eax, [ebp+FAT.DATA_START]
        add     eax, [esp+4]
; load sector if needed
        cmp     dword [esp+8], 0        ; we don't need to read uninitialized data
        jz      .noread
        cmp     ecx, 0x200      ; we don't need to read sector if it is fully rewritten
        jz      .noread
        cmp     ecx, esi        ; (same for the last sector)
        jz      .noread
        push    eax ebx
        lea     ebx, [ebp+FAT.buffer]
        call    fs_read32_app
        test    eax, eax
        pop     ebx eax
        jz      @f
.device_err2:
        pop     ecx
        jmp     .device_err
@@:
.noread:
; zero uninitialized data if file was extended (because hd_extend_file does not this)
        push    eax ecx edi
        xor     eax, eax
        mov     ecx, 0x200
        sub     ecx, [esp+8+12]
        jbe     @f
        lea     edi, [ebp+FAT.buffer]
        add     edi, [esp+8+12]
        rep stosb
@@:
; zero uninitialized data in the last sector
        mov     ecx, 0x200
        sub     ecx, esi
        jbe     @f
        lea     edi, [ebp+FAT.buffer+esi]
        rep stosb
@@:
        pop     edi ecx
; copy new data
        mov     eax, edx
        neg     ebx
        jecxz   @f
        lea     ebx, [ebp+FAT.buffer+0x200+ebx]
        call    memmove
        xor     ebx, ebx
@@:
        pop     eax
; save sector
        push    ebx
        lea     ebx, [ebp+FAT.buffer]
        call    fs_write32_app
        pop     ebx
        test    eax, eax
        jnz     .device_err2
        add     edx, ecx
        sub     [esp], ecx
        pop     ecx
        jz      .ret
.skip:
; next sector
        pop     eax
        inc     eax
        push    eax
        cmp     eax, [ebp+FAT.SECTORS_PER_CLUSTER]
        jb      @f
        and     dword [esp], 0
        mov     eax, edi
        call    get_FAT
        mov     edi, eax
        jc      .device_err
        cmp     edi, 2
        jb      .fat_err
        cmp     edi, [ebp+FAT.fatRESERVED]
        jae     .fat_err
@@:
        sub     esi, 0x200
        jae     @f
        xor     esi, esi
@@:
        sub     dword [esp+4], 0x200
        jae     @f
        and     dword [esp+4], 0
@@:
        jmp     .write_loop

hd_extend_file.zero_size:
        xor     eax, eax
        jmp     hd_extend_file.start_extend

; extends file on hd to given size (new data area is undefined)
; in: edi->direntry, ecx=new size
; out: CF=0 => OK, eax=0
;      CF=1 => error, eax=code (ERROR_FS_FAIL or ERROR_DISK_FULL or ERROR_DEVICE)
hd_extend_file:
        push    esi
        mov     esi, [ebp+FAT.SECTORS_PER_CLUSTER]
        imul    esi, [ebp+FAT.BYTES_PER_SECTOR]
        push    ecx
; find the last cluster of file
        mov     eax, [edi+20-2]
        mov     ax, [edi+26]
        mov     ecx, [edi+28]
        jecxz   .zero_size
.last_loop:
        sub     ecx, esi
        jbe     .last_found
        call    get_FAT
        jnc     @f
.device_err:
        pop     ecx
.device_err2:
        pop     esi
        push    ERROR_DEVICE
.ret_err:
        pop     eax
        stc
        ret
@@:
        cmp     eax, 2
        jb      .fat_err
        cmp     eax, [ebp+FAT.fatRESERVED]
        jb      .last_loop
.fat_err:
        pop     ecx esi
        push    ERROR_FS_FAIL
        jmp     .ret_err
.last_found:
        push    eax
        call    get_FAT
        jnc     @f
        pop     eax
        jmp     .device_err
@@:
        cmp     eax, [ebp+FAT.fatRESERVED]
        pop     eax
        jb      .fat_err
; set length to full number of clusters
        sub     [edi+28], ecx
.start_extend:
        pop     ecx
; now do extend
        push    edx
        mov     edx, 2          ; start scan from cluster 2
.extend_loop:
        cmp     [edi+28], ecx
        jae     .extend_done
; add new cluster
        push    eax
        call    get_free_FAT
        jc      .disk_full
        mov     edx, [ebp+FAT.fatEND]
        call    set_FAT
        mov     edx, eax
        pop     eax
        test    eax, eax
        jz      .first_cluster
        push    edx
        call    set_FAT
        pop     edx
        jmp     @f
.first_cluster:
        ror     edx, 16
        mov     [edi+20], dx
        ror     edx, 16
        mov     [edi+26], dx
@@:
        push    ecx
        mov     ecx, -1
        call    add_disk_free_space
        pop     ecx
        mov     eax, edx
        add     [edi+28], esi
        jmp     .extend_loop
.extend_done:
        mov     [edi+28], ecx
        pop     edx esi
        xor     eax, eax        ; CF=0
        ret
.device_err3:
        pop     edx
        jmp     .device_err2
.disk_full:
        pop     eax edx esi
        movi    eax, ERROR_DISK_FULL
        stc
        ret

fat_update_datetime:
        call    get_time_for_file
        mov     [edi+22], ax            ; last write time
        call    get_date_for_file
        mov     [edi+24], ax            ; last write date
        mov     [edi+18], ax            ; last access date
        ret

;----------------------------------------------------------------
fat_SetFileEnd:
        call    fat_lock
        push    edi
        cmp     byte [esi], 0
        jnz     @f
.access_denied:
        push    ERROR_ACCESS_DENIED
.ret:
        call    fat_unlock
        pop     eax
        pop     edi
        ret
@@:
        call    hd_find_lfn
        jnc     @f
.reteax:
        push    eax
        jmp     .ret
@@:
; must not be directory
        test    byte [edi+11], 10h
        jnz     .access_denied
; file size must not exceed 4 Gb
        cmp     dword [ebx+8], 0
        jz      @f
        push    ERROR_END_OF_FILE
        jmp     .ret
@@:
        push    eax     ; save directory sector
; set file modification date/time to current
        call    fat_update_datetime
        mov     eax, [ebx+4]
        cmp     eax, [edi+28]
        jb      .truncate
        ja      .expand
        pop     eax
        lea     ebx, [ebp+FAT.buffer]
        call    fs_write32_sys
        test    eax, eax
        jz      @f
        push    ERROR_DEVICE
        jmp     .ret
@@:
        push    0
        jmp     .ret
.expand:
        push    ebx ebp ecx
        push    dword [edi+28]  ; save old size
        mov     ecx, eax
        call    hd_extend_file
        push    eax             ; return code
        jnc     .expand_ok
        cmp     al, ERROR_DISK_FULL
        jz      .disk_full
.pop_ret:
        call    update_disk
        pop     eax ecx ecx ebp ebx ecx
        jmp     .reteax
.expand_ok:
.disk_full:
; save directory
        mov     eax, [edi+28]
        xchg    eax, [esp+20]
        lea     ebx, [ebp+FAT.buffer]
        call    fs_write32_sys
        test    eax, eax
        mov     eax, [edi+20-2]
        mov     ax, [edi+26]
        mov     edi, eax
        jz      @f
.pop_ret11:
        mov     byte [esp], ERROR_DEVICE
        jmp     .pop_ret
@@:
        test    edi, edi
        jz      .pop_ret
; now zero new data
        push    0
; edi=current cluster, [esp]=sector in cluster
; [esp+24]=new size, [esp+8]=old size, [esp+4]=return code
.zero_loop:
        cmp     edi, 2
        jb      .error_fat
        cmp     edi, [ebp+FAT.fatRESERVED]
        jae     .error_fat
        sub     dword [esp+8], 0x200
        jae     .next_cluster
        lea     eax, [edi-2]
        imul    eax, [ebp+FAT.SECTORS_PER_CLUSTER]
        add     eax, [ebp+FAT.DATA_START]
        add     eax, [esp]
        cmp     dword [esp+8], -0x200
        jz      .noread
        push    eax
        lea     ebx, [ebp+FAT.buffer]
        call    fs_read32_app
        test    eax, eax
        pop     eax
        jnz     .err_next
.noread:
        mov     ecx, [esp+8]
        neg     ecx
        push    edi
        lea     edi, [ebp+FAT.buffer+0x200]
        add     edi, [esp+12]
        push    eax
        xor     eax, eax
        mov     [esp+16], eax
        rep stosb
        pop     eax
        pop     edi
        call    fs_write32_app
        test    eax, eax
        jz      .next_cluster
.err_next:
        mov     byte [esp+4], ERROR_DEVICE
.next_cluster:
        pop     eax
        sub     dword [esp+20], 0x200
        jbe     .pop_ret
        inc     eax
        push    eax
        cmp     eax, [ebp+FAT.SECTORS_PER_CLUSTER]
        jb      .zero_loop
        and     dword [esp], 0
        mov     eax, edi
        call    get_FAT
        mov     edi, eax
        jnc     .zero_loop
        pop     eax
        jmp     .pop_ret11
.truncate:
        mov     [edi+28], eax
        push    ecx
        mov     ecx, [edi+20-2]
        mov     cx, [edi+26]
        push    eax
        test    eax, eax
        jz      .zero_size
; find new last cluster
@@:
        cmp     ecx, 2
        jb      .error_fat2
        cmp     ecx, [ebp+FAT.fatRESERVED]
        jae     .error_fat2
        mov     eax, [ebp+FAT.SECTORS_PER_CLUSTER]
        shl     eax, 9
        sub     [esp], eax
        jbe     @f
        mov     eax, ecx
        call    get_FAT
        mov     ecx, eax
        jnc     @b
.device_err3:
        pop     eax ecx eax edi
        call    update_disk
        call    fat_unlock
        movi    eax, ERROR_DEVICE
        ret
@@:
; we will zero data at the end of last sector - remember it
        push    ecx
; terminate FAT chain
        push    edx
        mov     eax, ecx
        mov     edx, [ebp+FAT.fatEND]
        call    set_FAT
        mov     eax, edx
        pop     edx
        jnc     @f
.device_err4:
        pop     ecx
        jmp     .device_err3
.zero_size:
        and     word [edi+20], 0
        and     word [edi+26], 0
        push    0
        mov     eax, ecx
@@:
; delete FAT chain
        call    clear_cluster_chain
        jc      .device_err4
; save directory
        mov     eax, [esp+12]
        push    ebx
        lea     ebx, [ebp+FAT.buffer]
        call    fs_write32_sys
        pop     ebx
        test    eax, eax
        jnz     .device_err4
; zero last sector, ignore errors
        pop     ecx
        pop     eax
        dec     ecx
        imul    ecx, [ebp+FAT.SECTORS_PER_CLUSTER]
        add     ecx, [ebp+FAT.DATA_START]
        push    eax
        sar     eax, 9
        add     ecx, eax
        pop     eax
        and     eax, 0x1FF
        jz      .truncate_done
        push    ebx eax
        mov     eax, ecx
        lea     ebx, [ebp+FAT.buffer]
        call    fs_read32_app
        pop     eax
        lea     edi, [ebp+FAT.buffer+eax]
        push    ecx
        mov     ecx, 0x200
        sub     ecx, eax
        xor     eax, eax
        rep stosb
        pop     eax
        call    fs_write32_app
        pop     ebx
.truncate_done:
        pop     ecx eax edi
        call    update_disk
        call    fat_unlock
        xor     eax, eax
        ret
.error_fat:
        pop     eax
        mov     byte [esp], ERROR_FS_FAIL
        jmp     .pop_ret
.error_fat2:
        pop     eax ecx eax edi
        call    update_disk
        call    fat_unlock
        movi    eax, ERROR_FS_FAIL
        ret

;----------------------------------------------------------------
fat_GetFileInfo:
        cmp     byte [esi], 0
        jnz     @f
        mov     eax, 2
        ret
@@:
        push    edi
        call    fat_lock
        call    hd_find_lfn
        jc      .error
        push    ebp
        xor     ebp, ebp
        mov     esi, [ebx+16]
        mov     dword [esi+4], ebp
        call    fat_entry_to_bdfe2
        pop     ebp
        call    fat_unlock
        xor     eax, eax
        pop     edi
        ret
.error:
        push    eax
        call    fat_unlock
        pop     eax
        pop     edi
        ret

;----------------------------------------------------------------
fat_SetFileInfo:
        cmp     byte [esi], 0
        jnz     @f
        mov     eax, 2
        ret
@@:
        push    edi
        call    fat_lock
        call    hd_find_lfn
        jc      .error
        push    eax
        mov     edx, [ebx+16]
        call    bdfe_to_fat_entry
        pop     eax
        lea     ebx, [ebp+FAT.buffer]
        call    fs_write32_sys
        call    update_disk
        call    fat_unlock
        pop     edi
        xor     eax, eax
        ret
.error:
        push    eax
        call    fat_unlock
        pop     eax
        pop     edi
        ret

;----------------------------------------------------------------
fat_Delete:
        call    fat_lock
        cmp     byte [esi], 0
        jnz     @f
; cannot delete root!
.access_denied:
        push    ERROR_ACCESS_DENIED
.pop_ret:
        call    fat_unlock
        pop     eax
        xor     ebx, ebx
        ret
@@:
        and     [ebp+FAT.longname_sec1], 0
        and     [ebp+FAT.longname_sec2], 0
        push    edi
        call    hd_find_lfn
        jnc     .found
        pop     edi
        push    ERROR_FILE_NOT_FOUND
        jmp     .pop_ret
.found:
        cmp     dword [edi], '.   '
        jz      .access_denied2
        cmp     dword [edi], '..  '
        jz      .access_denied2
        test    byte [edi+11], 10h
        jz      .dodel
; we can delete only empty folders!
        pushad
        mov     esi, [edi+20-2]
        mov     si, [edi+26]
        xor     ecx, ecx
        lea     eax, [esi-2]
        imul    eax, [ebp+FAT.SECTORS_PER_CLUSTER]
        add     eax, [ebp+FAT.DATA_START]
        lea     ebx, [ebp+FAT.buffer]
        call    fs_read32_sys
        test    eax, eax
        jnz     .err1
        lea     eax, [ebx+0x200]
        add     ebx, 2*0x20
.checkempty:
        cmp     byte [ebx], 0
        jz      .empty
        cmp     byte [ebx], 0xE5
        jnz     .notempty
        add     ebx, 0x20
        cmp     ebx, eax
        jb      .checkempty
        inc     ecx
        cmp     ecx, [ebp+FAT.SECTORS_PER_CLUSTER]
        jb      @f
        mov     eax, esi
        call    get_FAT
        jc      .err1
        cmp     eax, 2
        jb      .error_fat
        cmp     eax, [ebp+FAT.fatRESERVED]
        jae     .empty
        mov     esi, eax
        xor     ecx, ecx
@@:
        lea     eax, [esi-2]
        imul    eax, [ebp+FAT.SECTORS_PER_CLUSTER]
        add     eax, [ebp+FAT.DATA_START]
        add     eax, ecx
        lea     ebx, [ebp+FAT.buffer]
        call    fs_read32_sys
        test    eax, eax
        lea     eax, [ebx+0x200]
        jz      .checkempty
.err1:
        popad
.err2:
        pop     edi
        call    fat_unlock
        movi    eax, ERROR_DEVICE
        ret
.error_fat:
        popad
        pop     edi
        call    fat_unlock
        movi    eax, ERROR_FS_FAIL
        ret
.notempty:
        popad
.access_denied2:
        pop     edi
        call    fat_unlock
        movi    eax, ERROR_ACCESS_DENIED
        ret
.empty:
        popad
        push    eax ebx
        lea     ebx, [ebp+FAT.buffer]
        call    fs_read32_sys
        test    eax, eax
        pop     ebx eax
        jnz     .err2
.dodel:
        push    eax
        mov     eax, [edi+20-2]
        mov     ax, [edi+26]
        xchg    eax, [esp]
; delete folder entry
        mov     byte [edi], 0xE5
; delete LFN (if present)
.lfndel:
        lea     edx, [ebp+FAT.buffer]
        cmp     edi, edx
        ja      @f
        cmp     [ebp+FAT.longname_sec2], 0
        jz      .lfndone
        push    [ebp+FAT.longname_sec2]
        push    [ebp+FAT.longname_sec1]
        pop     [ebp+FAT.longname_sec2]
        and     [ebp+FAT.longname_sec1], 0
        push    ebx
        mov     ebx, edx
        call    fs_write32_sys
        mov     eax, [esp+4]
        call    fs_read32_sys
        pop     ebx
        pop     eax
        lea     edi, [ebp+FAT.buffer+0x200]
@@:
        sub     edi, 0x20
        cmp     byte [edi], 0xE5
        jz      .lfndone
        cmp     byte [edi+11], 0xF
        jnz     .lfndone
        mov     byte [edi], 0xE5
        jmp     .lfndel
.lfndone:
        push    ebx
        lea     ebx, [ebp+FAT.buffer]
        call    fs_write32_sys
        pop     ebx
; delete FAT chain
        pop     eax
        call    clear_cluster_chain
        call    update_disk
        call    fat_unlock
        pop     edi
        xor     eax, eax
        ret