;  unzx0_68000.s - ZX0 decompressor for 68000
;
; platon42: Modified to not preserve registers and to not use long word operations with
; unlikely and unsupported block lengths > 64 KB. get_elias inlined for speed and other
; optimizations.
;
;  in:  a0 = start of compressed data
;       a1 = start of decompression buffer
;
;  out: a0 = end of compressed data
;       a1 = end of decompression buffer
;
;  trashes: d0-d2/a2
;
;  Copyright (C) 2021 Emmanuel Marty
;  Copyright (C) 2023 Emmanuel Marty, Chris Hodges
;  ZX0 compression (c) 2021 Einar Saukas, https://github.com/einar-saukas/ZX0
;
;  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.

zx0_decompress:
        moveq.l #-128,d1        ; initialize empty bit queue
                                ; plus bit to roll into carry
        moveq.l #-1,d2          ; initialize rep-offset to 1
        bra.s   .literals

.do_copy_offs2
        move.b  (a1,d2.l),(a1)+
        move.b  (a1,d2.l),(a1)+

        add.b   d1,d1           ; read 'literal or match' bit
        bcs.s   .get_offset     ; if 0: go copy literals

.literals
        ; read number of literals to copy
        moveq.l #1,d0           ; initialize value to 1
.elias_loop1
        add.b   d1,d1           ; shift bit queue, high bit into carry
        bne.s   .got_bit1       ; queue not empty, bits remain
        move.b  (a0)+,d1        ; read 8 new bits
        addx.b  d1,d1           ; shift bit queue, high bit into carry
                                ; and shift 1 from carry into bit queue

.got_bit1
        bcs.s   .got_elias1     ; done if control bit is 1
        add.b   d1,d1           ; read data bit
        addx.w  d0,d0           ; shift data bit into value in d0
        bra.s   .elias_loop1    ; keep reading

.got_elias1
        subq.w  #1,d0           ; dbf will loop until d0 is -1, not 0
.copy_lits
        move.b  (a0)+,(a1)+     ; copy literal byte
        dbra    d0,.copy_lits   ; loop for all literal bytes

        add.b   d1,d1           ; read 'match or rep-match' bit
        bcs.s   .get_offset     ; if 1: read offset, if 0: rep-match

.rep_match
        ; read match length (starts at 1)
        moveq.l #1,d0           ; initialize value to 1
.elias_loop2
        add.b   d1,d1           ; shift bit queue, high bit into carry
        bne.s   .got_bit2       ; queue not empty, bits remain
        move.b  (a0)+,d1        ; read 8 new bits
        addx.b  d1,d1           ; shift bit queue, high bit into carry
                                ; and shift 1 from carry into bit queue

.got_bit2
        bcs.s   .got_elias2     ; done if control bit is 1
        add.b   d1,d1           ; read data bit
        addx.w  d0,d0           ; shift data bit into value in d0
        bra.s   .elias_loop2    ; keep reading

.got_elias2
        subq.w  #1,d0           ; dbra will loop until d0 is -1, not 0
.do_copy_offs
        move.l  a1,a2           ; calculate backreference address
        add.l   d2,a2           ; (dest + negative match offset)
.copy_match
        move.b  (a2)+,(a1)+     ; copy matched byte
        dbra    d0,.copy_match  ; loop for all matched bytes

        add.b   d1,d1           ; read 'literal or match' bit
        bcc.s   .literals       ; if 0: go copy literals

.get_offset
        moveq.l #-2,d0          ; initialize value to $fe

        ; read high byte of match offset
.elias_loop3
        add.b   d1,d1           ; shift bit queue, high bit into carry
        bne.s   .got_bit3       ; queue not empty, bits remain
        move.b  (a0)+,d1        ; read 8 new bits
        addx.b  d1,d1           ; shift bit queue, high bit into carry
                                ; and shift 1 from carry into bit queue

.got_bit3
        bcs.s   .got_elias3     ; done if control bit is 1
        add.b   d1,d1           ; read data bit
        addx.w  d0,d0           ; shift data bit into value in d0
        bra.s   .elias_loop3    ; keep reading

.got_elias3
        addq.b  #1,d0           ; obtain negative offset high byte
        beq.s   .done           ; exit if EOD marker
        move.b  d0,-(sp)        ; transfer negative high byte to stack
        move.w  (sp)+,d2        ; shift it to make room for low byte

        move.b  (a0)+,d2        ; read low byte of offset + 1 bit of len
        asr.l   #1,d2           ; shift len bit into carry/offset in place
        bcs.s   .do_copy_offs2  ; if len bit is set, no need for more
        moveq.l #1,d0           ; initialize length value to 1
        ; read rest of elias-encoded match length
        add.b   d1,d1           ; read data bit
        addx.w  d0,d0           ; shift data bit into value in d0

.elias_loop4
        add.b   d1,d1           ; shift bit queue, high bit into carry
        bne.s   .got_bit4       ; queue not empty, bits remain
        move.b  (a0)+,d1        ; read 8 new bits
        addx.b  d1,d1           ; shift bit queue, high bit into carry
                                ; and shift 1 from carry into bit queue

.got_bit4
        bcs.s   .do_copy_offs   ; done if control bit is 1
        add.b   d1,d1           ; read data bit
        addx.w  d0,d0           ; shift data bit into value in d0
        bra.s   .elias_loop4    ; keep reading
.done
        rts
