; COLORDUMP.ASM - Win32 CUI
; Copyright (C) 2023 Boo Khan Ming
;
; MIT license apply
;
format PE console
entry start

include 'win32a.inc'

section '.data' readable writable

_message0 db 'Usage:  COLORDUMP <filename>',13,10
_msglen0  = $ - _message0
_message1 db 'Error opening file '
_msglen1  = $ - _message1
_message2 db 'Error reading file '
_msglen2  = $ - _message2
_dummy    dd ?
_short    db ?
          db 0
_double   dw ?
_hexnum   rb 8
_hexval   rb 2
_digits   db '0123456789ABCDEF'
_space1   db 32,32
_len1     = $ - _space1
_space2   db 32
_len2     = $ - _space2
_filename rb MAX_PATH
_fnlen    dd ?
_buffer   rb 16
_len      dd ?
_ptr      dd ?
_handle   dd ?
_stdout   dd ?
_count    dd ?
_offset   dd ?
_cls      db 27,'[H',27,'[J'
_lencls   = $ - _cls
_mode     dd ?
colorblue db   27,'[48;5;33m'   ; ANSI escape code
lenblue   = $ - colorblue
colorgreen  db   27,'[48;5;46m'   ; ANSI escape code
lengreen    = $ - colorgreen
colororange db   27,'[48;5;196m'   ; ANSI escape code
lenorange   = $ - colororange
coloryellow db   27,'[48;5;172m'   ; ANSI escape code
lenyellow   = $ - coloryellow
colorreset  db   27,'[0m'   ; ANSI escape code (reset color)
lenreset    = $ - colorreset


section '.code' code readable executable

start:
        push    -11
        call    [GetStdHandle]
        mov     dword [_stdout], eax
        push   _mode
        push   [_stdout]
        call   [GetConsoleMode]
        or     [_mode], 4     ; ENABLE_VIRTUAL_TERMINAL_PROCESSING
        push   [_mode]
        push   [_stdout]
        call   [SetConsoleMode]

        call    [GetCommandLine]
        push    eax
        cld
        mov     edi, eax
        or      ecx, -1
        xor     eax, eax
        repnz   scasb           ; Calculate total length of command line arguments
        not     ecx
        pop     eax
        push    eax
        mov     dword [_fnlen], ecx
        mov     edi, eax
        or      ecx, -1
        mov     eax, 32
        repnz   scasb           ; Find the first white space after first argument
        repz    scasb           ; Scan for extra white spacing between first argument and second argument
        not     ecx
        mov     dword [_dummy], ecx
        sub     dword [_fnlen], ecx
        cmp     dword [_fnlen], 0    ; Check if no argument is supplied
        js      .err0
        mov     ecx, dword [_fnlen]
        pop     eax
        add     eax, dword [_dummy]
        dec     eax
        mov     esi, eax
        lea     edi, [_filename]
        rep     movsb

        push    0
        push    FILE_ATTRIBUTE_NORMAL
        push    OPEN_EXISTING
        push    0
        push    FILE_SHARE_READ
        push    GENERIC_READ
        push    _filename
        call    [CreateFile]
        mov     dword [_handle], eax
        cmp     eax, INVALID_HANDLE_VALUE
        je      .err1

.redo:
        push    0
        push    _len
        push    16
        push    _buffer
        push    dword [_handle]
        call    [ReadFile]
        test    eax, eax
        jz      .err2
        mov     ecx, dword [_len]
        test    ecx, ecx
        jz      .close
        mov     [_count], ecx

        mov     edx, [_offset]
        mov     ecx, 8
        call    ConvertLongHex
        call    PrintOffset
        call    PrintLongSpace
        xor     ecx, ecx
.repeat1:
        xor     edx, edx
        mov     dl, byte [_buffer + ecx]
        push    ecx
        push    edx
        mov     ecx, 2
        call    ConvertShortHex
        pop     edx
        call    PrintHex
        call    PrintShortSpace
        pop     ecx
        inc     ecx
        cmp     ecx, [_count]
        jb      .repeat1
        cmp     ecx, 16
        jb      .pad1
        jmp     .skip1
.pad1:
        mov     ebx, 16
        sub     ebx,ecx
.pad3:
        call    PrintShortSpace
        call    PrintShortSpace
        call    PrintShortSpace
        dec     ebx
        jnz     .pad3
.skip1:
        call    PrintLongSpace
        xor     ecx, ecx
.repeat2:
        xor     edx, edx
        mov     dl, byte [_buffer + ecx]
        push    ecx
        cmp     dl, 32
        jb      .dot
        cmp     dl, 127
        jae     .dot
        mov     [_short], dl
        call    PrintChar
        jmp     .skip2
.dot:
        mov     [_short], '.'
        call    PrintChar
.skip2:
        pop     ecx
        inc     ecx
        cmp     ecx, [_count]
        jb      .repeat2
        cmp     ecx, 16
        jb      .pad2
        jmp     .skip3
.pad2:
        mov     ebx, 16
        sub     ebx, ecx
.pad4:
        call    PrintShortSpace
        call    PrintShortSpace
        dec     ebx
        jnz     .pad4
.skip3:
        call    PrintLine
        add     [_offset], 16
        jmp     .redo
.close:
        push    [_handle]
        call    [CloseHandle]
        jmp     .done
.err0:
        lea     esi, [_message0]
        mov     edx, _msglen0
        call    Print
        jmp     .done
.err1:
        lea     esi, [_message1]
        mov     edx, _msglen1
        call    Print
        call    PrintFileName
        call    PrintLine
        jmp     .done
.err2:
        lea     esi, [_message2]
        mov     edx, _msglen2
        call    Print
        call    PrintFileName
        call    PrintLine
.done:
        push    0
        call    [ExitProcess]

PrintLongSpace:
        mov     edx, _len1
        lea     esi, [_space1]
        call    Print
        ret
PrintShortSpace:
        mov     edx, _len2
        lea     esi, [_space2]
        call    Print
        ret
PrintLine:
        mov     edx, 2
        mov     [_double], 0x0A0D
        lea     esi, [_double]
        call    Print
        ret
PrintOffset:
        mov     edx, 8
        lea     esi, [_hexnum]
        call    Print
        ret
PrintHex:
        cmp     dl, 32
        jae     .text1
        cmp     dl, 13
        je      .text2
        cmp     dl, 09
        je      .text2
        cmp     dl, 10
        je      .text2
;.bin1:
        mov     edx, lenblue
        lea     esi, [colorblue]
        call    Print
        jmp     .okay
.text1:
        cmp     dl, 127
        jae     .bin2
        mov     edx, lengreen
        lea     esi, [colorgreen]
        call    Print
        jmp     .okay

.text2:
        mov     edx, lenyellow
        lea     esi, [coloryellow]
        call    Print
        jmp     .okay

.bin2:
        mov     edx, lenorange
        lea     esi, [colororange]
        call    Print

.okay:
        mov     edx, 2
        lea     esi, [_hexval]
        call    Print
        mov     edx, lenreset
        lea     esi, [colorreset]
        call    Print
        ret
PrintChar:      
        mov     edx, 1
        lea     esi, [_short]
        call    Print
        ret
Print:
        push    0
        push    _dummy
        push    edx
        push    esi
        push    dword [_stdout]
        call    [WriteFile]
        ret
PrintFileName:
        pusha
        mov     [_short], "'"
        call    PrintChar
        push    0
        push    _dummy
        push    dword [_fnlen]
        push    _filename
        push    dword [_stdout]
        call    [WriteFile]
        mov     [_short], "'"
        call    PrintChar
        popa
        ret

ConvertLongHex:    ;-) Nice code snippet by Tomasz Grysztar (flat assembler)
        xor      ebx, ebx
@@:
        rol      edx, 4
        mov      eax, edx
        and      eax, 1111b
        mov      al, [_digits + eax]
        mov      [ebx + _hexnum], al
        inc      ebx
        dec      ecx
        jnz      @B
        ret
      
ConvertShortHex:   ;-) Nice code snippet by Tomasz Grysztar (flat assembler)
        xor      ebx,ebx
@@:
        rol      dl, 4
        mov      al, dl
        and      eax, 1111b
        mov      al, [_digits+eax]
        mov      [ebx + _hexval], al
        inc      ebx
        dec      ecx
        jnz      @B
        ret
 
section '.idata' import readable writable

 library kernel32, 'KERNEL32.DLL'

 import kernel32,\
        GetStdHandle, 'GetStdHandle', \
        WriteConsole, 'WriteConsoleA', \
        CreateFile, 'CreateFileA', \
        ReadFile, 'ReadFile', \
        WriteFile, 'WriteFile', \
        CloseHandle, 'CloseHandle', \
        GetCommandLine, 'GetCommandLineA', \
        ExitProcess,'ExitProcess', \
        GetConsoleMode, 'GetConsoleMode',\
        SetConsoleMode, 'SetConsoleMode'