; Platform-specific procedures for Windows.

; Reallocate memory at pointer [start.buf] and size [start.allocated],
; new size is in eax.
realloc:
        push    eax
        stdcall [VirtualAlloc], 0, eax, MEM_COMMIT + MEM_RESERVE, PAGE_READWRITE
        pop     ecx
        test    eax, eax
        jz      nomemory
        add     [start.free], ecx
        xchg    ecx, [start.allocated]
        sub     [start.free], ecx
        push    esi edi
        mov     edi, eax
        xchg    eax, [start.buf]
        shr     ecx, 2
        jz      .nothing
        mov     esi, eax
        rep     movsd
        call    free_eax
.nothing:
        pop     edi esi
        ret

; Read the next portion of input data to [start.buf].
read:
        push    eax     ; reserve space for *lpNumberOfBytesRead
        mov     ecx, esp
        mov     eax, [start.buf]
        add     eax, [start.allocated]
        sub     eax, [start.free]
        stdcall [ReadFile], [start.in], eax, [start.free], ecx, 0
        test    eax, eax
        jz      @f
.nothing:
        pop     eax
        ret
@@:
; ERROR_BROKEN_PIPE and ERROR_MORE_DATA are normal codes when dealing with pipes
        call    [GetLastError]
        cmp     eax, ERROR_BROKEN_PIPE
        jz      .nothing
        cmp     eax, ERROR_MORE_DATA
        jz      .nothing
        pop     eax
        jmp     readerr

; Write output data: eax=pointer, edi=size.
write:
        push    eax
        mov     ecx, esp
        stdcall [WriteFile], [start.out], eax, edi, ecx, 0
        test    eax, eax
        pop     eax
        jz      writeerr
        cmp     eax, edi
        jnz     writeerr
        ret

; Parse command line, open input and output files.
get_params:
; 1. Get the command line split to argv[] array.
        call    [GetCommandLineW]
        push    eax     ; reserve space for *pNumArgs
        stdcall [CommandLineToArgvW], eax, esp
        pop     ebx     ; ebx = argc, eax = argv
        push    eax     ; save argument for [LocalFree]
; 2. Prepare for scanning, skipping argv[0].
        cmp     ebx, 1
        jbe     .noargs
        dec     ebx
        lea     esi, [eax+4]    ; skip argv[0]
        xor     edi, edi        ; no args parsed yet
; 3. Parse loop.
.parse:
; 3a. Get the next argument.
        lodsd
; 3b. Check whether it is a known option.
        cmp     dword [eax], '-' + 'e' * 65536
        jnz     @f
        cmp     word [eax+4], 0
        jnz     @f
; 3c. If it is, modify flags and continue the loop.
        mov     [start.flags], 1        ; '-e' is given
        jmp     .nextarg
@@:
; 3d. Otherwise, it is a name of input or output file.
; edi keeps the count of names encountered before;
; edi = 0 means this is input file, edi = 1 - output file,
; otherwise this is third arg, which is an error
        cmp     edi, 1
        ja      .toomanyargs
; 3e. Some parameters of CreateFileW differ for input and output. Setup them.
        mov     ecx, GENERIC_WRITE
        mov     edx, CREATE_ALWAYS
        jz      @f
        add     ecx, ecx        ; GENERIC_READ
        inc     edx             ; OPEN_EXISTING
@@:
; 3f. Open/create the file, save the handle.
        stdcall [CreateFileW], eax, ecx, FILE_SHARE_READ+FILE_SHARE_DELETE, 0, edx, FILE_ATTRIBUTE_NORMAL, 0
        cmp     eax, INVALID_HANDLE_VALUE
        jz      .fileerr
        mov     [start.in+edi*4], eax
        inc     edi
.nextarg:
        dec     ebx
        jnz     .parse
.noargs:
; 4. End of command line reached. If input and/or output was not given, use defaults.
        test    edi, edi
        jnz     .hasinput
        stdcall [GetStdHandle], STD_INPUT_HANDLE
        mov     [start.in], eax
.hasinput:
        cmp     edi, 1
        ja      .hasoutput
        stdcall [GetStdHandle], STD_OUTPUT_HANDLE
        mov     [start.out], eax
.hasoutput:
; 5. Free memory allocated in step 1 and return.
        call    [LocalFree]
        ret
.toomanyargs:
        call    [LocalFree]
        jmp     information
.fileerr:
        call    [LocalFree]
        test    edi, edi
        jz      in_openerr
        jmp     out_openerr

; Exit, return code is in al.
exit:
        movzx   eax, al
        push    eax     ; save return code for [ExitProcess]
        mov     eax, [start.buf]
        test    eax, eax
        jz      @f
        call    free_eax
@@:
        stdcall [CloseHandle], [start.in]
        stdcall [CloseHandle], [start.out]
        call    [ExitProcess]

; Output the message given in esi to stderr.
sayerr:
        stdcall [GetStdHandle], STD_ERROR_HANDLE
        push    eax
        mov     ecx, esp
        movzx   edx, byte [esi-1]
        stdcall [WriteFile], eax, esi, edx, ecx, 0
        pop     ecx
        ret

; Helper procedure for realloc and exit.
free_eax:
        stdcall [VirtualFree], eax, 0, MEM_RELEASE
        ret

; Get environment variable esi (ebx-relative pointer) to the buffer,
; expanding it if needed.
get_environment_variable:
        lea     eax, [edi+ebx]
        lea     ecx, [esi+ebx]
        stdcall [GetEnvironmentVariableA], ecx, eax, [start.free]
; GetEnvironmentVariable returns one of following values:
; * if all is ok: number of characters copied to the buffer,
;       not including terminating zero
; * if buffer size is too small: number of characters required in the buffer,
;       including terminating zero
; * if environment variable not found: zero
; We treat the last case the same as first one.
        cmp     eax, [start.free]
        jae     .resize
        inc     eax     ; include terminating zero
        add     edi, eax
        sub     [start.free], eax
        mov     byte [edi+ebx-1], 0     ; force zero-terminated or empty string
        ret
.resize:
; esi can be inside the buffer or outside the buffer;
; we must not correct it in the first case,
; but advance relative to new value of ebx in the second one.
        mov     ecx, esi
        cmp     esi, [start.allocated]
        jb      @f
        add     esi, ebx
@@:
        stdcall alloc_in_buf, eax
        cmp     esi, ecx
        jz      get_environment_variable
        sub     esi, ebx
        jmp     get_environment_variable

; Test whether a file with name [.testname] exists.
; Returns eax<0 if not, nonzero otherwise.
test_file_exists:
        mov     eax, [start.testname]
        add     eax, ebx
        stdcall [GetFileAttributesA], eax
        inc     eax
        ret

; Imports
align 4
data import
library kernel32,'kernel32.dll',shell32,'shell32.dll'
import kernel32, \
        GetLastError, 'GetLastError', \
        ExitProcess, 'ExitProcess', \
        VirtualAlloc, 'VirtualAlloc', \
        VirtualFree, 'VirtualFree', \
        LocalFree, 'LocalFree', \
        GetStdHandle, 'GetStdHandle', \
        CreateFileW, 'CreateFileW', \
        GetFileAttributesA, 'GetFileAttributesA', \
        ReadFile, 'ReadFile', \
        WriteFile, 'WriteFile', \
        CloseHandle, 'CloseHandle', \
        GetCommandLineW, 'GetCommandLineW', \
        GetEnvironmentVariableA, 'GetEnvironmentVariableA'
import shell32, CommandLineToArgvW, 'CommandLineToArgvW'
end data