;  decompress_small_v2.asm - space-efficient decompressor implementation for x86
;
;  Copyright (C) 2019 Emmanuel Marty
;
;  This software is provided 'as-is', without any express or implied
;  warranty.  In no event will the authors be held liable for any damages
;  arising from the use of this software.
;
;  Permission is granted to anyone to use this software for any purpose,
;  including commercial applications, and to alter it and redistribute it
;  freely, subject to the following restrictions:
;
;  1. The origin of this software must not be misrepresented; you must not
;     claim that you wrote the original software. If you use this software
;     in a product, an acknowledgment in the product documentation would be
;     appreciated but is not required.
;  2. Altered source versions must be plainly marked as such, and must not be
;     misrepresented as being the original software.
;  3. This notice may not be removed or altered from any source distribution.

    segment .text
    bits 32

;  ---------------------------------------------------------------------------
;  Decompress raw LZSA2 block
;  inputs:
;  * esi: raw LZSA2 block
;  * edi: output buffer
;  output:
;  * eax:    decompressed size
;  ---------------------------------------------------------------------------
    
    %ifndef BIN
      global lzsa2_decompress
      global _lzsa2_decompress
    %endif
    
lzsa2_decompress:
_lzsa2_decompress:
    pushad
    
    ;mov    edi, [esp+32+4]      ; edi = outbuf
    ;mov    esi, [esp+32+8]      ; esi = inbuf
    
    xor    ecx, ecx
    xor    ebx, ebx             ; ebx = 0100H
    inc    bh
    xor    ebp, ebp

.decode_token:
    mul    ecx
    lodsb                       ; read token byte: XYZ|LL|MMMM
    mov    dl, al               ; keep token in dl
   
    and    al, 018H             ; isolate literals length in token (LL)
    shr    al, 3                ; shift literals length into place

    cmp    al, 03H              ; LITERALS_RUN_LEN_V2?
    jne    .got_literals        ; no, we have the full literals count from the token, go copy

    call   .get_nibble          ; get extra literals length nibble
    add    al, cl               ; add len from token to nibble 
    cmp    al, 012H             ; LITERALS_RUN_LEN_V2 + 15 ?
    jne    .got_literals        ; if not, we have the full literals count, go copy

    lodsb                       ; grab extra length byte
    add    al,012H              ; overflow?
    jnc    .got_literals        ; if not, we have the full literals count, go copy

    lodsw                       ; grab 16-bit extra length

.got_literals:
    xchg   ecx, eax
    rep    movsb                ; copy ecx literals from esi to edi

    test   dl, 0C0h             ; check match offset mode in token (X bit)
    js     .rep_match_or_large_offset

    ;;cmp dl,040H               ; check if this is a 5 or 9-bit offset (Y bit)
                                ; discovered via the test with bit 6 set
    xchg   ecx, eax             ; clear ah - cx is zero from the rep movsb above
    jne    .offset_9_bit

                                ; 5 bit offset
    cmp    dl, 020H             ; test bit 5
    call   .get_nibble_x
    jmp    .dec_offset_top

.offset_9_bit:                  ; 9 bit offset
    lodsb                       ; get 8 bit offset from stream in A
    dec    ah                   ; set offset bits 15-8 to 1
    test   dl, 020H             ; test bit Z (offset bit 8)
    je     .get_match_length
.dec_offset_top:
    dec    ah                   ; clear bit 8 if Z bit is clear
                                ; or set offset bits 15-8 to 1
    jmp    .get_match_length

.rep_match_or_large_offset:
    ;;cmp dl,0c0H               ; check if this is a 13-bit offset or a 16-bit offset/rep match (Y bit)
    jpe    .rep_match_or_16_bit

                                ; 13 bit offset

    cmp    dl, 0A0H             ; test bit 5 (knowing that bit 7 is also set)
    xchg   ah, al
    call   .get_nibble_x
    sub    al, 2                ; substract 512
    jmp    .get_match_length_1

.rep_match_or_16_bit:
    test   dl, 020H             ; test bit Z (offset bit 8)
    jne    .repeat_match        ; rep-match

                                ; 16 bit offset
    lodsb                       ; Get 2-byte match offset

.get_match_length_1:
    xchg   ah, al
    lodsb                       ; load match offset bits 0-7

.get_match_length:
    xchg   ebp, eax             ; ebp: offset
.repeat_match:
    xchg   eax, edx             ; ax: original token
    and    al, 07H              ; isolate match length in token (MMM)
    add    al, 2                ; add MIN_MATCH_SIZE_V2

    cmp    al, 09H              ; MIN_MATCH_SIZE_V2 + MATCH_RUN_LEN_V2?
    jne    .got_matchlen        ; no, we have the full match length from the token, go copy

    call   .get_nibble          ; get extra literals length nibble
    add    al, cl               ; add len from token to nibble 
    cmp    al, 018H             ; MIN_MATCH_SIZE_V2 + MATCH_RUN_LEN_V2 + 15?
    jne    .got_matchlen        ; no, we have the full match length from the token, go copy

    lodsb                       ; grab extra length byte
    add    al,018H              ; overflow?
    jnc    .got_matchlen        ; if not, we have the entire length
    je     .done_decompressing  ; detect EOD code

    lodsw                       ; grab 16-bit length

.got_matchlen:
    xchg   ecx, eax             ; copy match length into ecx
    xchg   esi, eax          
    movsx  ebp, bp              ; sign-extend bp to 32-bits
    lea    esi,[ebp+edi]        ; esi now points at back reference in output data
    rep    movsb                ; copy match
    xchg   esi, eax             ; restore esi
    jmp    .decode_token        ; go decode another token

.done_decompressing:
    sub    edi, [esp+32+4]
    mov    [esp+28], edi
    popad
    ret                         ; done

.get_nibble_x:
    cmc                         ; carry set if bit 4 was set
    rcr    al, 1
    call   .get_nibble          ; get nibble for offset bits 0-3
    or     al, cl               ; merge nibble
    rol    al, 1
    xor    al, 0E1H             ; set offset bits 7-5 to 1
    ret

.get_nibble:
    neg    bh                   ; nibble ready?
    jns    .has_nibble
   
    xchg   ebx, eax
    lodsb                       ; load two nibbles
    xchg   ebx, eax

.has_nibble:
    mov    cl, 4                ; swap 4 high and low bits of nibble
    ror    bl, cl
    mov    cl, 0FH
    and    cl, bl
    ret
