;
; project name:         KFar_Arc - plugin for KFar, which supports various archives
; target platform:      KolibriOS
; compiler:             FASM 1.67.14
; version:              0.17
; last update:          2009-09-03 (Sep 03, 2009)
; minimal KFar version: 0.43
; minimal kernel:       no limit
;
; author:               Diamond
; email:                diamondz@land.ru
; web:                  http://diamond.kolibrios.org
;

; standard start of Kolibri dynamic library
format MS COFF
public EXPORTS

section '.flat' code readable align 16

; include auxiliary procedures
include 'kglobals.inc'          ; iglobal/uglobal
include 'lang.inc'              ; define language for localized strings
include 'crc.inc'               ; CRC32 calculation
include 'sha256.inc'            ; SHA-256 hash algorithm
include 'aes.inc'               ; AES crypto algorithm
; include main code for archives loading
include '7z.inc'                ; *.7z
include 'lzma.inc'              ; LZMA-decoder for *.7z
include 'ppmd.inc'              ; PPMD-decoder for *.7z
include '7zbranch.inc'          ; branch filters for *.7z
include '7zaes.inc'             ; AES cryptor for *.7z
include 'zip.inc'               ; *.zip
include 'deflate.inc'           ; Deflate[64] decoder for *.7z and *.zip

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;; Interface for KFar ;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
virtual at 0
kfar_info_struc:
.lStructSize    dd      ?
.kfar_ver       dd      ?
.open           dd      ?
.open2          dd      ?
.read           dd      ?
.write          dd      ?
.seek           dd      ?
.tell           dd      ?
.flush          dd      ?
.filesize       dd      ?
.close          dd      ?
.pgalloc        dd      ?
.pgrealloc      dd      ?
.pgfree         dd      ?
.getfreemem     dd      ?
.pgalloc2       dd      ?
.pgrealloc2     dd      ?
.pgfree2        dd      ?
.menu           dd      ?
.menu_centered_in dd    ?
.DialogBox      dd      ?
.SayErr         dd      ?
.Message        dd      ?
.cur_console_size dd    ?
end virtual

; int __stdcall plugin_load(kfar_info* info);
; Initialization of plugin + Save used KFar functions.
plugin_load:
        mov     eax, [esp+4]
        mov     [kfar_info], eax
        push    [eax+kfar_info_struc.open2]
        pop     [open2]
        push    [eax+kfar_info_struc.filesize]
        pop     [filesize]
        push    [eax+kfar_info_struc.read]
        pop     [read]
        push    [eax+kfar_info_struc.seek]
        pop     [seek]
        push    [eax+kfar_info_struc.close]
        pop     [close]
        lea     esi, [eax+kfar_info_struc.DialogBox]
        mov     edi, DialogBox
        movsd
        movsd
        movsd
        movsd
        lea     esi, [eax+kfar_info_struc.pgalloc]
        mov     edi, pgalloc
        movsd
        movsd
        movsd
        movsd
        call    init_crc_table
        call    init_aes
        call    init_ppmd
        xor     eax, eax        ; success
        ret     4

; HANDLE __stdcall OpenFilePlugin(HANDLE basefile,
;       const void* attr, const void* data, int datasize,
;       int baseplugin_id, HANDLE baseplugin_instance, const char* name);
; This function is called when user presses Enter (or Ctrl+PgDn) on file.
; Plugin tests whether given file is of supported type
;   and if so, loads information and returns
;   handle to be used in subsequent calls to ReadFolder, SetFolder and so on.
OpenFilePlugin:
        mov     [bPasswordDefined], 0
        mov     esi, [esp+12]
        mov     ebp, [esp+4]
; test for 7z archive
        cmp     dword [esp+16], 20h     ; minimal size of 7z archive is 20h bytes
        jb      .no_7z
        cmp     word [esi], '7z'                ; signature, part 1
        jnz     .no_7z
        cmp     dword [esi+2], 0x1C27AFBC       ; signature, part 2
        jnz     .no_7z
        call    open_7z
        ret     28
.no_7z:
; test for zip archive
        cmp     dword [esp+16], 22      ; minimal size of zip archive is 22 bytes
        jb      .no_zip
        cmp     word [esi], 0x4B50
        jnz     .no_zip
        cmp     word [esi+2], 0x0403
        jz      .zip
        cmp     word [esi+2], 0x0201
        jz      .zip
        cmp     word [esi+2], 0x0606
        jz      .zip
        cmp     word [esi+2], 0x0706
        jz      .zip
        cmp     word [esi+2], 0x0605
        jnz     .no_zip
.zip:
        call    open_zip
        ret     28
.no_zip:
        xor     eax, eax
        ret     28

; Handle of plugin in kfar_arc is as follow:
virtual at 0
handle_common:
.type           dd      ?
.root.subfolders        dd      ?
.root.subfolders.end    dd      ?
.root.subfiles          dd      ?
.root.subfiles.end      dd      ?
.root.NumSubItems       dd      ?
.curdir                 dd      ?
.NumFiles               dd      ?
; ... some plugin-specific data follows ...
end virtual

; and for each archive item there is one file info structure, which begins as follow:
virtual at 0
file_common:
.fullname       dd      ?       ; pointer to cp866 string
.name           dd      ?       ; name without path (end of .fullname)
.namelen        dd      ?       ; strlen(.name)
.bIsDirectory   db      ?
.bPseudoFolder  db      ?
                rb      2
.parent         dd      ?       ; pointer to parent directory record
.subfolders     dd      ?       ; head of L2-list of subfolders [for folders]
.subfolders.end dd      ?
.subfiles       dd      ?       ; head of L2-list of files [for folders]
.subfiles.end   dd      ?
.NumSubItems    dd      ?
.next           dd      ?       ; next item in list of subfolders/files
.prev           dd      ?       ; previous item in list of subfolders/files
.stamp          dd      ?       ; stamp for GetFiles
end virtual

; void __stdcall ClosePlugin(HANDLE hPlugin);
; This function frees all resources allocated in OpenFilePlugin.
ClosePlugin:
        mov     eax, [esp+4]    ; get hPlugin
        mov     eax, [eax]      ; hPlugin is pointer to internal data structure
                                ; first dword is archive type (type_xxx constants)
        dec     eax             ; types start from 1
        jmp     dword [ClosePluginTable+eax*4]

; int __stdcall ReadFolder(HANDLE hPlugin,
;       unsigned dirinfo_start, unsigned dirinfo_size, void* dirdata);
ReadFolder:
; init header
        mov     edi, [esp+16]
        mov     ecx, 32/4
        xor     eax, eax
        rep     stosd
        mov     byte [edi-32], 1        ; version
        mov     ebp, [esp+4]
; get current directory
        lea     ebx, [ebp+handle_common.root.subfolders]
        cmp     [ebp+handle_common.curdir], 0
        jz      @f
        mov     ebx, [ebp+handle_common.curdir]
        add     ebx, file_common.subfolders
@@:
        mov     ecx, [ebx+16]
        mov     [edi-24], ecx           ; number of files
; edi points to BDFE
        push    6               ; assume EOF
        pop     eax
        sub     ecx, [esp+8]
        ja      @f
        and     dword [edi-28], 0       ; number of files read
        ret     10h
@@:
        cmp     ecx, [esp+12]
        jb      @f
        mov     ecx, [esp+12]
        xor     eax, eax        ; OK
@@:
        mov     [edi-28], ecx
        push    eax
; copy files data
        jecxz   .done
; seek to required item
        mov     eax, [esp+8+4]
        mov     esi, [ebx]
.0:
        test    esi, esi
        jnz     .1
        mov     esi, [ebx+8]
.1:
        add     esi, ebp
        dec     eax
        js      .2
        mov     esi, [esi+file_common.next]
        jmp     .0
.2:
.copy:
        pushad
        mov     eax, esi
        mov     ecx, [ebp]
        call    dword [getattrTable+(ecx-1)*4]
        pop     edi esi
        push    esi edi
        add     edi, 40
        mov     ecx, [esi+file_common.namelen]
        mov     esi, [esi+file_common.name]
        rep     movsb
        mov     byte [edi], 0
        popad
        add     edi, 304
        mov     esi, [esi+file_common.next]
        test    esi, esi
        jnz     @f
        mov     esi, [ebx+8]
@@:
        add     esi, ebp
        loop    .copy
.done:
        pop     eax
        ret     10h

; bool __stdcall SetFolder(HANDLE hPlugin,
;       const char* relative_path, const char* absolute_path);
SetFolder:
        mov     ebp, [esp+4]
        mov     edx, [ebp+handle_common.curdir]
        mov     esi, [esp+8]
        cmp     dword [esi], '..'
        jz      .toparent
        xor     ecx, ecx
@@:
        inc     ecx
        cmp     byte [esi+ecx], 0
        jnz     @b
        mov     ebx, [ebp+handle_common.root.subfolders]
        test    edx, edx
        jz      .scan
        mov     ebx, [edx+file_common.subfolders]
.scan:
        test    ebx, ebx
        jz      .err
        add     ebx, ebp
        cmp     [ebx+file_common.namelen], ecx
        jnz     .cont
        push    ecx esi
        mov     edi, [ebx+file_common.name]
        repz    cmpsb
        pop     esi ecx
        jz      .set
.cont:
        mov     ebx, [ebx+file_common.next]
        jmp     .scan
.toparent:
        test    edx, edx
        jz      .err
        mov     ebx, [edx+file_common.parent]
        test    ebx, ebx
        jz      @f
        add     ebx, ebp
@@:
.set:
        mov     [ebp+handle_common.curdir], ebx
        mov     al, 1
        ret     12
.err:
        xor     eax, eax
        ret     12

iglobal
cur_stamp dd 0
endg

uglobal
tmp_bdfe        rb      304
endg

; void __stdcall GetFiles(HANDLE hPlugin, int NumItems, void* items[],
;       void* addfile, void* adddir);
;       bool __stdcall addfile(const char* name, void* bdfe_info, HANDLE hFile);
;       bool __stdcall adddir(const char* name, void* bdfe_info);
GetFiles:
        mov     ebp, [esp+4]
        mov     ecx, [ebp+handle_common.NumFiles]
        test    ecx, ecx
        jz      .ret
        mov     ebx, ebp
        mov     eax, [ebx]
        add     ebx, [basesizes+(eax-1)*8]
        inc     [cur_stamp]
.loop:
        push    ecx
        mov     esi, [ebx+file_common.fullname]
        mov     edx, [ebp+handle_common.curdir]
        test    edx, edx
        jz      .incur
        mov     eax, [cur_stamp]
        mov     [edx+file_common.stamp], eax
        mov     edi, [edx+file_common.fullname]
        mov     ecx, [edx+file_common.namelen]
        add     ecx, [edx+file_common.name]
        sub     ecx, edi
        repz    cmpsb
        jnz     .cont
.incur:
        cmp     byte [esi], '/'
        jnz     @f
        inc     esi
@@:
        mov     ecx, [esp+12]   ; NumItems
        mov     edx, [esp+16]   ; items
        cmp     ecx, -1
        jz      .ok
.check:
        sub     ecx, 1
        js      .cont
        push    esi
        mov     edi, [edx]
        add     edi, 40
@@:
        lodsb
        scasb
        jnz     @f
        test    al, al
        jz      .ok2
        jmp     @b
@@:
        pop     esi
        cmp     al, '/'
        jnz     @f
        cmp     byte [edi-1], 0
        jz      .ok
@@:
        add     edx, 4
        jmp     .check
.ok2:
        pop     esi
.ok:
; add all parents directories if needed
.parloope:
        mov     ecx, [ebx+file_common.parent]
        jecxz   .pardone
        add     ecx, ebp
        mov     eax, [cur_stamp]
        cmp     [ecx+file_common.stamp], eax
        jz      .pardone
.parloopi:
        mov     edx, ecx
        mov     ecx, [ecx+file_common.parent]
        jecxz   @f
        add     ecx, ebp
        cmp     [ecx+file_common.stamp], eax
        jnz     .parloopi
@@:
        mov     [edx+file_common.stamp], eax
        push    esi
        mov     ecx, [edx+file_common.name]
        add     ecx, [edx+file_common.namelen]
        xor     eax, eax
        xchg    al, [ecx]
        push    eax ecx
        mov     eax, edx
        mov     edi, tmp_bdfe
        push    edi
        sub     esi, [ebx+file_common.fullname]
        add     esi, [edx+file_common.fullname]
        push    esi
        mov     ecx, [ebp]
        call    dword [getattrTable+(ecx-1)*4]
        mov     eax, [esp+24+20]
        call    eax
        pop     ecx edx
        mov     [ecx], dl
        pop     esi
        test    al, al
        jz      .forced_exit
        jmp     .parloope
.pardone:
        cmp     [ebx+file_common.bIsDirectory], 0
        jz      .addfile
        mov     eax, [cur_stamp]
        cmp     [ebx+file_common.stamp], eax
        jz      .cont
        mov     [ebx+file_common.stamp], eax
        push    esi
        mov     ecx, [ebx+file_common.name]
        add     ecx, [ebx+file_common.namelen]
        xor     eax, eax
        xchg    al, [ecx]
        push    eax ecx
        mov     eax, ebx
        mov     edi, tmp_bdfe
        push    edi
        push    esi
        mov     ecx, [ebp]
        call    dword [getattrTable+(ecx-1)*4]
        mov     eax, [esp+24+20]
        call    eax
        pop     ecx edx
        mov     [ecx], dl
        pop     esi
        test    al, al
        jz      .forced_exit
        jmp     .cont
.addfile:
        push    ebx esi ebp
        push    11h
        pop     edi
        mov     eax, ebx
        mov     ecx, [ebp]
        call    dword [openTable+(ecx-1)*4]
        pop     ebp esi ebx
        test    eax, eax
        jz      .cont
        push    eax
        push    eax
        mov     edi, tmp_bdfe
        push    edi
        push    esi
        mov     eax, ebx
        mov     ecx, [ebp]
        call    dword [getattrTable+(ecx-1)*4]
        mov     eax, [esp+20+16]
        call    eax
        pop     ecx
        push    eax ebp
        push    ebx
        push    ecx
        call    myclose
        pop     ebx
        pop     ebp eax
        test    al, al
        jz      .forced_exit
.cont:
        mov     eax, [ebp]
        add     ebx, [basesizes+(eax-1)*8+4]
        pop     ecx
        dec     ecx
        jnz     .loop
.ret:
        ret     20
.forced_exit:
        pop     ecx
        jmp     .ret

; void __stdcall GetOpenPluginInfo(HANDLE hPlugin, OpenPluginInfo* info);
GetOpenPluginInfo:
        mov     eax, [esp+8]    ; get info ptr
        mov     byte [eax], 3   ; flags: add non-existing '..' entry automatically
                                ;        use GetFiles for copying
        ret     8

; int __stdcall getattr(HANDLE hPlugin, const char* filename, void* info);
mygetattr:
        call    lookup_file_name
        test    eax, eax
        jz      @f
        mov     edx, [ebp]
        dec     edx
        mov     edi, [esp+12]   ; info ptr
        call    dword [getattrTable+edx*4]
        xor     eax, eax
        ret     12
@@:
        mov     al, 5   ; ERROR_FILE_NOT_FOUND
        ret     12

; HANDLE __stdcall open(HANDLE hPlugin, const char* filename, int mode);
myopen:
        call    lookup_file_name
        test    eax, eax
        jz      @f
        mov     edx, [ebp]
        dec     edx
        mov     edi, [esp+12]   ; mode
        call    dword [openTable+edx*4]
@@:
        ret     12

; unsigned __stdcall read(HANDLE hFile, void* buf, unsigned size);
myread:
        mov     ebx, [esp+4]
        mov     eax, [ebx]
        jmp     dword [readTable+eax*4]

; void __stdcall setpos(HANDLE hFile, __int64 pos);
mysetpos:
        mov     ebx, [esp+4]
        mov     eax, [ebx]
        jmp     dword [setposTable+eax*4]

; void __stdcall close(HANDLE hFile);
myclose:
        mov     ebx, [esp+4]
        mov     eax, [ebx]
        jmp     dword [closeTable+eax*4]

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;; Auxiliary procedures ;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

; return.err and return.clear are labels to jmp if something is invalid
; the caller must previously define [_esp], [_ebp] and [error_proc], [clear_proc]
return.err:
        mov     esp, [_esp]
        mov     ebp, [_ebp]
        jmp     [error_proc]
return.clear:
        mov     esp, [_esp]
        mov     ebp, [_ebp]
        jmp     [clear_proc]

; data for following routine
iglobal
align 4
_24             dd      24
_60             dd      60
_10000000       dd      10000000
days400year     dd      365*400+100-4+1
days100year     dd      365*100+25-1
days4year       dd      365*4+1
days1year       dd      365
months          dd      31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31
months2         dd      31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31
_400            dd      400
_100            dd      100
endg

; Convert QWORD FILETIME to BDFE format.
ntfs_datetime_to_bdfe:
; edx:eax = number of 100-nanosecond intervals since January 1, 1601, in UTC
        push    eax
        mov     eax, edx
        xor     edx, edx
        div     [_10000000]
        xchg    eax, [esp]
        div     [_10000000]
        pop     edx
; edx:eax = number of seconds since January 1, 1601
        push    eax
        mov     eax, edx
        xor     edx, edx
        div     [_60]
        xchg    eax, [esp]
        div     [_60]
        mov     [edi], dl
        pop     edx
; edx:eax = number of minutes
        div     [_60]
        mov     [edi+1], dl
; eax = number of hours (note that 2^64/(10^7*60*60) < 2^32)
        xor     edx, edx
        div     [_24]
        mov     [edi+2], dl
        mov     [edi+3], byte 0
; eax = number of days since January 1, 1601
        xor     edx, edx
        div     [days400year]
        imul    eax, 400
        add     eax, 1601
        mov     [edi+6], ax
        mov     eax, edx
        xor     edx, edx
        div     [days100year]
        cmp     al, 4
        jnz     @f
        dec     eax
        add     edx, [days100year]
@@:
        imul    eax, 100
        add     [edi+6], ax
        mov     eax, edx
        xor     edx, edx
        div     [days4year]
        shl     eax, 2
        add     [edi+6], ax
        mov     eax, edx
        xor     edx, edx
        div     [days1year]
        cmp     al, 4
        jnz     @f
        dec     eax
        add     edx, [days1year]
@@:
        add     [edi+6], ax
        push    esi edx
        mov     esi, months
        movzx   eax, word [edi+6]
        test    al, 3
        jnz     .noleap
        xor     edx, edx
        push    eax
        div     [_400]
        pop     eax
        test    edx, edx
        jz      .leap
        xor     edx, edx
        div     [_100]
        test    edx, edx
        jz      .noleap
.leap:
        mov     esi, months2
.noleap:
        pop     edx
        xor     eax, eax
        inc     eax
@@:
        sub     edx, [esi]
        jb      @f
        add     esi, 4
        inc     eax
        jmp     @b
@@:
        add     edx, [esi]
        pop     esi
        inc     edx
        mov     [edi+4], dl
        mov     [edi+5], al
        add     edi, 8
        ret

; By given array of files information, initialize links between them
; ("[folder] contains [item]" relations).
; Information structure must be compatible with 'file_common'.
; Size of information structure is in [esp+4].
init_file_links:
; in: edx->file infos, ebx = number of files, [esp+4] = size,
;     edi->{dd root.subfolders, dd root.subfolders.end,
;           dd root.subfiles, dd root.subfiles.end, dd root.NumItems}
        xor     eax, eax
        mov     [.free], eax
        push    edi
        stosd
        stosd
        stosd
        stosd
        stosd
        pop     edi
; Loop through all files
.mainloop:
        dec     ebx
        js      .mainloopdone
; Parse file name
; esi->current character in name
; dword [esp] = start of current component in file name
; ecx->{dd curdir.subfolders, dd curdir.subfolders.end,
;       dd curdir.subfiles, dd curdir.subfiles.end}
        mov     esi, [edx+file_common.fullname]
        mov     ecx, edi
.parseloop:
        push    esi
.parsename:
        lodsb
        test    al, al
        jz      @f
        cmp     al, '/'
        jnz     .parsename
@@:
; we have found next component of current name; look for it in current directory
        sub     esi, [esp]
        dec     esi     ; esi = strlen(component)
        cmp     esi, 259
        jbe     @f
        push    ContinueBtn
        push    1
        push    aNameTooLong_ptr
        push    1
        call    [SayErr]
        jmp     return.clear
@@:
        push    ecx
        mov     eax, [ecx]      ; eax->subfolders list
        mov     ecx, esi
.scansubfolders:
        test    eax, eax
        jz      .nofolder
        add     eax, [hOut]
        cmp     [eax+file_common.namelen], ecx
        jnz     .scancont
        mov     esi, [esp+4]
        push    ecx edi
        mov     edi, [eax+file_common.name]
        repz    cmpsb
        pop     edi ecx
        jz      .subfound
.scancont:
        mov     eax, [eax+file_common.next]
        jmp     .scansubfolders
.subfound:
; found subfolder, set it as current and continue parsing name
        add     [esp+4], ecx
        pop     ecx
        lea     ecx, [eax+file_common.subfolders]
        pop     esi
        lodsb
        test    al, al
        jnz     .parseloop
; that was the last component of the name, and we have found subfolder
; so found subfolder is a virtual subfolder and must be replaced with current
; name
        mov     eax, [ecx-file_common.subfolders+file_common.namelen]
        mov     [edx+file_common.namelen], eax
        sub     esi, eax
        dec     esi
        mov     [edx+file_common.name], esi
        sub     edx, [hOut]     ; convert pointer to relative
; replace item in L2-list
        mov     eax, [ecx-file_common.subfolders+file_common.prev]
        test    eax, eax
        jnz     .1
        mov     eax, [ecx-file_common.subfolders+file_common.parent]
        sub     eax, file_common.next - file_common.subfolders
        jnc     .1
        lea     eax, [edi-file_common.next]
        jmp     .2
.1:
        add     eax, [hOut]
.2:
        mov     [eax+file_common.next], edx
        mov     eax, [ecx-file_common.subfolders+file_common.next]
        test    eax, eax
        jnz     .3
        mov     eax, [ecx-file_common.subfolders+file_common.parent]
        sub     eax, file_common.prev - file_common.subfolders.end
        jnc     .3
        lea     eax, [edi-file_common.prev+4]
        jmp     .4
.3:
        add     eax, [hOut]
.4:
        mov     [eax+file_common.prev], edx
; correct parent links in childrens
        mov     eax, [ecx]
@@:
        test    eax, eax
        jz      @f
        add     eax, [hOut]
        mov     [eax+file_common.parent], edx
        mov     eax, [eax+file_common.next]
        jmp     @b
@@:
        mov     eax, [ecx+8]
@@:
        test    eax, eax
        jz      @f
        add     eax, [hOut]
        mov     [eax+file_common.parent], edx
        mov     eax, [eax+file_common.next]
        jmp     @b
@@:
        add     edx, [hOut]
; set children links
        mov     eax, [ecx]
        mov     [edx+file_common.subfolders], eax
        mov     eax, [ecx+4]
        mov     [edx+file_common.subfolders.end], eax
        mov     eax, [ecx+8]
        mov     [edx+file_common.subfiles], eax
        mov     eax, [ecx+12]
        mov     [edx+file_common.subfiles.end], eax
        mov     eax, [ecx+16]
        mov     [edx+file_common.NumSubItems], eax
; set prev/next links
        mov     eax, [ecx-file_common.subfolders+file_common.next]
        mov     [edx+file_common.next], eax
        mov     eax, [ecx-file_common.subfolders+file_common.prev]
        mov     [edx+file_common.prev], eax
; add old item to list of free items
uglobal
align 4
init_file_links.free    dd      ?
endg
        sub     ecx, file_common.subfolders
        mov     eax, [.free]
        mov     [ecx], eax
        sub     ecx, [hOut]
        mov     [.free], ecx
        jmp     .mainloopcont
.nofolder:
        mov     eax, edx
        mov     esi, [esp+4]
        cmp     byte [esi+ecx], 0
        jz      .newitem
; the current item is as 'dir1/item1' and 'dir1' has not been found
; allocate virtual subfolder 'dir1'
        mov     eax, [init_file_links.free]
        test    eax, eax
        jz      .realloc
        add     eax, [hOut]
        push    dword [eax]
        pop     [init_file_links.free]
        jmp     .allocated
.realloc:
; there is no free space, so reallocate [hOut] block
        mov     eax, [hOut]
        sub     [esp], eax      ; make pointers relative
        sub     edx, eax
        sub     edi, eax
        push    ecx
        mov     ecx, [hOut.allocated]
        add     ecx, [esp+12+4]
        mov     [hOut.allocated], ecx
        push    ecx
        and     ecx, 0xFFF
        cmp     ecx, [esp+16+4]
        pop     ecx
        ja      @f
        push    edx
        mov     edx, eax
        call    [pgrealloc]
        pop     edx
        test    eax, eax
        jnz     @f
        mov     ecx, [hOut]
        call    [pgfree]
        mov     esp, [_esp]
        or      eax, -1
        ret
@@:
        pop     ecx
        mov     [hOut], eax
        add     [esp], eax      ; make pointers absolute
        add     edx, eax
        add     edi, eax
        add     eax, [hOut.allocated]
        sub     eax, [esp+8+4]
.allocated:
; eax -> new item
        mov     [eax+file_common.bIsDirectory], 1
        mov     [eax+file_common.bPseudoFolder], 1
.newitem:
        mov     [eax+file_common.namelen], ecx
; !!! in this case .fullname is not null-terminated !!!
        mov     ecx, [edx+file_common.fullname]
        mov     [eax+file_common.fullname], ecx
        push    edi eax
        lea     edi, [eax+file_common.parent]
        xor     eax, eax
        push    7
        pop     ecx
        rep     stosd
        pop     eax edi
        pop     ecx
        pop     esi
; ecx = parent item, eax = current item
        mov     [eax+file_common.name], esi
        inc     dword [ecx+16]  ; new item in parent folder
        push    ecx
; add new item to end of L2-list
        cmp     [eax+file_common.bIsDirectory], 0
        jnz     @f
        add     ecx, 8
@@:
        push    eax
        sub     eax, [hOut]
        cmp     dword [ecx], 0
        jnz     @f
        mov     [ecx], eax
@@:
        xchg    eax, [ecx+4]
        xchg    eax, ecx
        pop     eax
        mov     [eax+file_common.prev], ecx
        jecxz   @f
        add     ecx, [hOut]
        sub     eax, [hOut]
        mov     [ecx+file_common.next], eax
        add     eax, [hOut]
@@:
        pop     ecx
; set parent link
        cmp     ecx, edi
        jz      @f
        sub     ecx, file_common.subfolders
        sub     ecx, [hOut]
        mov     [eax+file_common.parent], ecx
@@:
; set current directory to current item
        lea     ecx, [eax+file_common.subfolders]
; if that was not last component, continue parse name
        add     esi, [eax+file_common.namelen]
        lodsb
        test    al, al
        jnz     .parseloop
.mainloopcont:
; continue main loop
        add     edx, [esp+4]
        jmp     .mainloop
.mainloopdone:
; Loop done.
        ret     4

; This subroutine is called by getattr and open.
; This subroutine looks for file name and returns NULL or pointer to file info record.
lookup_file_name:
        mov     ebp, [esp+8]    ; hPlugin
        mov     esi, [esp+12]   ; filename
        lea     edi, [ebp+handle_common.root.subfolders]
        xor     eax, eax
; KFar operates with absolute names, skip first '/'
        cmp     byte [esi], '/'
        jnz     .notfound
        inc     esi
.mainloop:
; get next component of name
        push    -1
        pop     ecx
@@:
        inc     ecx
        cmp     byte [esi+ecx], '/'
        jz      @f
        cmp     byte [esi+ecx], 0
        jnz     @b
@@:
; esi->component, ecx=length
; scan for required item in subfolders list
        push    -1
        mov     eax, [edi]      ; .subfolders
.scan1:
        test    eax, eax
        jz      .notfound1
        add     eax, ebp
        cmp     [eax+file_common.namelen], ecx
        jnz     .cont1
        push    ecx esi edi
        mov     edi, [eax+file_common.name]
        repz    cmpsb
        pop     edi esi ecx
        jz      .found1
.cont1:
        mov     eax, [eax+file_common.next]
        jmp     .scan1
.notfound1:
        pop     edx
; if this is last component in file name, scan in subfiles list
        cmp     byte [esi+ecx], al
        jnz     .notfound
        inc     edx
        jnz     .notfound
        mov     eax, [edi+8]    ; .subfiles
        push    edx
        jmp     .scan1
.found1:
        pop     edi
; item is found, go to next component
        lea     edi, [eax+file_common.subfolders]
        lea     esi, [esi+ecx+1]
        cmp     byte [esi-1], 0
        jnz     .mainloop
; this was the last component
.notfound:
        ret

; Memory streams handling.
; Archive handlers create memory stream for small files:
; size of which is not greater than (free RAM size)/4 and
; not greater than following constant...
;LIMIT_FOR_MEM_STREAM = 2*1024*1024
; ...if it is defined. Now the definition is commented:
; if user has many physical memory, why not to use it?

virtual at 0
mem_stream:
.type   dd      ?       ; type_mem_stream
.size   dd      ?
.pos    dd      ?
.buf:
end virtual

; unsigned __stdcall read(ebx = HANDLE hFile, void* buf, unsigned size);
read_mem_stream:
        mov     eax, [esp+12]
        mov     ecx, [ebx+mem_stream.size]
        sub     ecx, [ebx+mem_stream.pos]
        jnc     @f
        xor     ecx, ecx
@@:
        cmp     eax, ecx
        jb      @f
        mov     eax, ecx
@@:
        mov     ecx, eax
        lea     esi, [ebx+mem_stream.buf]
        add     esi, [ebx+mem_stream.pos]
        add     [ebx+mem_stream.pos], eax
        mov     edi, [esp+8]
        mov     edx, ecx
        shr     ecx, 2
        rep     movsd
        mov     ecx, edx
        and     ecx, 3
        rep     movsb
        ret     12

; void __stdcall setpos(ebx = HANDLE hFile, __int64 pos);
setpos_mem_stream:
        mov     eax, [esp+8]
        mov     [ebx+mem_stream.pos], eax
        ret     12

; void __stdcall close(ebx = HANDLE hFile);
close_mem_stream:
        mov     ecx, ebx
        call    [pgfree]
        ret     4

; Allocate handle for file
; esi -> handle table, ecx = size of handle
alloc_handle:
; Handle table is L2-list of allocated pages.
; Scan for free entry
        mov     edx, esi
@@:
        mov     edx, [edx]
        cmp     edx, esi
        jz      .alloc_new
        mov     eax, [edx+8]            ; head of L1-list of free entries
        test    eax, eax                ; has free entry?
        jz      @b
; we have found allocated page with free entry; allocate entry and return
        inc     dword [edx+12]          ; number of busy entries
        push    dword [eax]
        pop     dword [edx+8]
.ret:
        ret
.alloc_new:
; no free pages; get new page and initialize
        push    ecx
        mov     ecx, 0x1000
        call    [pgalloc]
        pop     ecx
        test    eax, eax
        jz      .ret
; insert new page to start of L2-list
        mov     edx, [esi]
        mov     [eax], edx
        mov     [esi], eax
        mov     [eax+4], esi
        mov     [edx+4], eax
        mov     dword [eax+12], 1       ; 1 allocated entry
; initialize list of free entries
        lea     edx, [eax+16]
        push    edx     ; save return value
        add     edx, ecx
        mov     [eax+8], edx
        add     eax, 0x1000
@@:
        mov     esi, edx
        add     edx, ecx
        mov     [esi], edx
        cmp     edx, eax
        jb      @b
        and     dword [esi], 0
        pop     eax
        ret

; Free handle allocated in previous procedure
; esi = handle
free_handle:
        mov     ecx, esi
        and     ecx, not 0xFFF  ; get page
; add entry to head of L1-list of free entries
        mov     eax, [ecx+8]
        mov     [esi], eax
        mov     [ecx+8], esi
        dec     dword [ecx+12]  ; decrement number of allocated entries
        jnz     .ret
; delete page from common L2-list
        mov     eax, [ecx]
        mov     edx, [ecx+4]
        mov     [eax+4], edx
        mov     [edx], eax
; free page
        call    [pgfree]
.ret:
        ret

; Ask user to enter password.
; Out: ZF set <=> user pressed Esc
;      'password_ansi', 'password_unicode', 'password_size' filled
query_password:
        cmp     [bPasswordDefined], 0
        jnz     .ret
        mov     edi, password_data
        mov     eax, password_maxlen
        stosd           ; maximum length
        xor     eax, eax
        stosd           ; start of visible part
        stosd           ; position of cursor
        stosb           ; initial state: empty string
        mov     eax, [cur_console_size]
        mov     eax, [eax]      ; get current console width
        sub     eax, 12
        mov     edi, password_dlg
        mov     [edi+password_dlg.width-password_dlg], eax
        dec     eax
        dec     eax
        mov     [edi+password_dlg.width1-password_dlg], eax
        mov     [edi+password_dlg.width2-password_dlg], eax
        push    edi
        call    [DialogBox]
        inc     eax
        jz      .ret
; convert ANSI-cp866 to UNICODE string; also calculate 'password_size'
        mov     esi, password_ansi
        mov     edi, password_unicode
        or      [password_size], -1
.cvt:
        inc     [password_size]
        lodsb
        mov     ah, 0
; 0x00-0x7F - trivial map
        cmp     al, 0x80
        jb      .symb
; 0x80-0xAF -> 0x410-0x43F
        cmp     al, 0xB0
        jae     @f
        add     ax, 0x410-0x80
        jmp     .symb
@@:
; 0xE0-0xEF -> 0x440-0x44F
        cmp     al, 0xE0
        jb      .unk
        cmp     al, 0xF0
        jae     @f
        add     ax, 0x440-0xE0
        jmp     .symb
@@:
; 0xF0 -> 0x401
; 0xF1 -> 0x451
        cmp     al, 'Ё'
        jz      .yo1
        cmp     al, 'ё'
        jz      .yo2
.unk:
        mov     al, '_'
        jmp     .symb
.yo1:
        mov     ax, 0x401
        jmp     .symb
.yo2:
        mov     ax, 0x451
.symb:
        stosw
        test    al, al
        jnz     .cvt
        inc     [bPasswordDefined]      ; clears ZF flag
.ret:
        ret

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;; API for other programs ;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; void* __stdcall deflate_unpack(void* packed_data, unsigned* pLength)
deflate_unpack:
	push	dword [esp+8]
        push    esp
        push    deflate_unpack_cb
        call    deflate_unpack2
        ret     8

deflate_unpack_cb:
        mov     edx, [esp+4]
        push    edx
        mov     edx, [edx+12]
        mov     edx, [edx]
        mov     eax, [esp+8+4]
        mov     [eax], edx
        pop     edx
        xor     eax, eax
        xchg    eax, [edx+8]
        ret     8

; void* __stdcall deflate_unpack2(void* get_next_chunk, void* parameter, unsigned* pUnpackedLength)
;	void* __stdcall get_next_chunk(void* parameter, unsigned* pLength)
deflate_unpack2:
        pusha
        mov     ecx, 0x11000
        call    mypgalloc
        test    eax, eax
        jz      .ret
        or      dword [eax+streamInfo.fullSize], -1
        or      dword [eax+streamInfo.fullSize+4], -1
        and     dword [eax+streamInfo.bufSize], 0
        mov     dword [eax+streamInfo.fillBuf], .fillBuf
        mov     edx, [esp+32+4]
        mov     [eax+streamInfo.size], edx
        mov     edx, [esp+32+8]
        mov     [eax+streamInfo.size+4], edx
        mov     [eax+streamInfo.size+8+deflate_decoder.inStream], eax
        add     eax, streamInfo.size+8
        lea     edi, [eax+deflate_decoder.size]
        mov     [eax+streamInfo.bufPtr], edi
        mov     ebp, eax
        call    deflate_init_decoder
        xor     edx, edx
        mov     ecx, 10000h
.loop:
        push    @f
        pushad
        mov     [_esp], esp
        mov     [_ebp], ebp
        mov     [error_proc], .error
        mov     ecx, 10000h
        jmp     [eax+streamInfo.fillBuf]
@@:
        push    68
        pop     eax
        push    20
        pop     ebx
        int     0x40
        test    eax, eax
        jz      .nomem
        mov     edx, eax
        mov     eax, ebp
        mov     esi, edi
        push    edi
        lea     edi, [edx+ecx-0x10000]
        push    ecx
        mov     ecx, 0x10000/4
        rep     movsd
        pop     ecx
        pop     edi
        add     ecx, 0x10000
        jmp     .loop
.error:
        pop     eax
        push    edi
        popad
        pop     eax
        mov     eax, ebp
        lea     esi, [eax+deflate_decoder.size]
        sub     ecx, 0x10000
        sub     edi, esi
        add     ecx, edi
        mov     eax, [esp+32+12]
        test    eax, eax
        jz      @f
        mov     [eax], ecx
@@:
        test    ecx, ecx
        jnz     @f
        inc     ecx
@@:
        push    68
        pop     eax
        push    20
        pop     ebx
        int     0x40
        test    eax, eax
        jz      .nomem
        sub     ecx, edi
        mov     edx, edi
        lea     edi, [eax+ecx]
        mov     ecx, edx
        shr     ecx, 2
        rep     movsd
        mov     ecx, edx
        and     ecx, 3
        rep     movsb
        push    eax
        push    68
        pop     eax
        push    13
        pop     ebx
        lea     ecx, [ebp-streamInfo.size-8]
        int     40h
        pop     eax
        jmp     .ret
.nomem:
        push    68
        pop     eax
        push    13
        pop     ebx
        test    edx, edx
        jz      @f
        mov     ecx, edx
        push    eax
        int     0x40
        pop     eax
@@:
        lea     ecx, [ebp-streamInfo.size-8]
        int     0x40
        xor     eax, eax
.ret:
        mov     [esp+28], eax
        popa
        ret     12
.fillBuf:
        push    eax
        push    eax
        push    esp
        push    dword [eax+streamInfo.size+4]
        call    dword [eax+streamInfo.size]
        pop     edx
        pop     ebp
        mov     [ebp+streamInfo.bufPtr], eax
        and     [ebp+streamInfo.bufDataLen], 0
        test    eax, eax
        jz      @f
        mov     [ebp+streamInfo.bufDataLen], edx
@@:
        popad
        ret

mypgalloc:
        push    68
        pop     eax
        push    12
        pop     ebx
        int     0x40
        ret

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;; Initialized data ;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

; export table
align 4
EXPORTS:
        dd      aVersion,       3
        dd      aPluginLoad,    plugin_load
        dd      aOpenFilePlugin,OpenFilePlugin
        dd      aClosePlugin,   ClosePlugin
        dd      aReadFolder,    ReadFolder
        dd      aSetFolder,     SetFolder
        dd      aGetFiles,      GetFiles
        dd      aGetOpenPluginInfo, GetOpenPluginInfo
        dd      aGetattr,       mygetattr
        dd      aOpen,          myopen
        dd      aRead,          myread
        dd      aSetpos,        mysetpos
        dd      aClose,         myclose
        dd      aDeflateUnpack, deflate_unpack
        dd      aDeflateUnpack2,deflate_unpack2
        dd      0

; exported names
aVersion        db      'version',0
aPluginLoad     db      'plugin_load',0
aOpenFilePlugin db      'OpenFilePlugin',0
aClosePlugin    db      'ClosePlugin',0
aReadFolder     db      'ReadFolder',0
aSetFolder      db      'SetFolder',0
aGetFiles       db      'GetFiles',0
aGetOpenPluginInfo db   'GetOpenPluginInfo',0
aGetattr        db      'getattr',0
aOpen           db      'open',0
aRead           db      'read',0
aSetpos         db      'setpos',0
aClose          db      'close',0
aDeflateUnpack  db      'deflate_unpack',0
aDeflateUnpack2 db      'deflate_unpack2',0

; common strings
if lang eq ru
aContinue       db      'Продолжить',0
aCancel         db      'Отмена',0
aHeaderError    db      'Ошибка в заголовке архива',0
aReadError      db      'Ошибка чтения',0
aNoFreeRam      db      'Недостаточно свободной оперативной памяти',0
aEncodingProblem db     'Проблема с кодировкой',0
aEncodingProblem_str db 'Имена некоторых файлов в архиве содержат символы,',0
.2              db      'не представимые в кодировке cp866.',0
.3              db      'Эти символы будут заменены на подчёркивания.',0
aEnterPassword  db      'Введите пароль:',0
aEnterPasswordLen = $ - aEnterPassword - 1
aEnterPasswordTitle db  'Ввод пароля',0
aArchiveDataError db    'Ошибка в данных архива',0
aArchiveDataErrorPass db 'Ошибка в данных архива или неверный пароль',0
aChangePass     db      'Ввести пароль',0
aNameTooLong    db      'Слишком длинное имя',0
aCannotOpenFile db      'Не могу открыть файл',0
else
aContinue       db      'Continue',0
aCancel         db      'Cancel',0
aHeaderError    db      'Invalid archive header',0
aReadError      db      'Read error',0
aNoFreeRam      db      'There is not enough of free RAM',0
aEncodingProblem db     'Encoding problem',0
aEncodingProblem_str db 'The names of some files in the archive contain',0
.2              db      'characters which can not be represented in cp866.',0
.3              db      'Such characters will be replaced to underscores.',0
aEnterPassword  db      'Enter password:',0
aEnterPasswordLen = $ - aEnterPassword - 1
aEnterPasswordTitle db  'Get password',0
aArchiveDataError db    'Error in archive data',0
aArchiveDataErrorPass db 'Error in archive data or incorrect password',0
aChangePass     db      'Enter password',0
aNameTooLong    db      'Name is too long',0
aCannotOpenFile db      'Cannot open file',0
end if

; kfar_arc supports many archive types.
; OpenFilePlugin looks for supported archive signature and gives control
;   to concrete handler if found.
; Other functions just determine type of opened archive and jump to corresponding handler.
type_mem_stream = 0     ; memory stream - for file handles (returned from 'open')
type_7z = 1
type_zip = 2

; archive functions (types start from type_7z)
align 4
ClosePluginTable:
        dd      close_7z
        dd      close_zip
getattrTable:
        dd      getattr_7z
        dd      getattr_zip
openTable:
        dd      open_file_7z
        dd      open_file_zip
basesizes:
        dd      handle_7z.basesize, file_in_7z.size
        dd      handle_zip.basesize, file_in_zip.size

; file functions (types start from type_mem_stream)
readTable:
        dd      read_mem_stream
        dd      read_7z
        dd      read_zip
setposTable:
        dd      setpos_mem_stream
        dd      setpos_7z
        dd      setpos_zip
closeTable:
        dd      close_mem_stream
        dd      close_file_7z
        dd      close_file_zip

; pointers for SayErr and Message
ContinueBtn     dd      aContinue
HeaderError_ptr dd      aHeaderError
aReadError_ptr  dd      aReadError
aNoFreeRam_ptr  dd      aNoFreeRam
aEncodingProblem_str_ptr:
                dd      aEncodingProblem_str
                dd      aEncodingProblem_str.2
                dd      aEncodingProblem_str.3
aNameTooLong_ptr dd     aNameTooLong
aArchiveDataError_ptr dd aArchiveDataError
aArchiveDataErrorPass_ptr dd aArchiveDataErrorPass
CancelPassBtn   dd      aCancel
                dd      aChangePass

; "enter password" dialog for KFar
password_dlg:
        dd      1       ; use standard dialog colors
        dd      -1      ; center window by x
        dd      -1      ; center window by y
.width  dd      ?       ; width (will be filled according to current console width)
        dd      2       ; height
        dd      4, 2    ; border size
        dd      aEnterPasswordTitle     ; title
        dd      ?       ; colors (will be set by KFar)
        dd      0       ; used internally by dialog manager, ignored
        dd      0, 0    ; reserved for DlgProc
        dd      2       ; 2 controls
; the string "enter password"
        dd      1       ; type: static
        dd      1,0     ; upper-left position
.width1 dd      ?,0     ; bottom-right position
        dd      aEnterPassword  ; data
        dd      0       ; flags
; editbox for password
        dd      3       ; type: edit
        dd      1,1     ; upper-left position
.width2 dd      ?,0     ; bottom-right position
        dd      password_data   ; data
        dd      2Ch     ; flags

IncludeIGlobals

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;; Uninitialized data ;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

section '.udata' data readable writable align 16
kfar_info       dd      ?
crc_table       rd      256
_esp            dd      ?
_ebp            dd      ?
bufsize         dd      ?
bufptr          dd      ?
bufend          dd      ?
buffer          rb      1024
inStream        dd      ?
hOut            dd      ?
.allocated      dd      ?

error_proc      dd      ?
clear_proc      dd      ?

; import from kfar
open2           dd      ?
filesize        dd      ?
read            dd      ?
seek            dd      ?
close           dd      ?
pgalloc         dd      ?
pgrealloc       dd      ?
pgfree          dd      ?
getfreemem      dd      ?
DialogBox       dd      ?
SayErr          dd      ?
Message         dd      ?
cur_console_size dd     ?

; data for editbox in kfar dialog
password_maxlen = 512
password_data:
.maxlen         dd      ?
.pos            dd      ?
.start          dd      ?
password_ansi   rb      password_maxlen+1
bPasswordDefined db     ?

; converted password
password_unicode rw     password_maxlen+1
password_size   dd      ?

IncludeUGlobals

bWasWarning     db      ?
