Add ZX02 and LZSA (1,2) decompressors

This commit is contained in:
Colin Leroy-Mira
2025-05-13 21:26:47 +02:00
parent 84094ae2b3
commit cfbfaa559c
9 changed files with 2174 additions and 0 deletions

207
libsrc/common/lzsa1.s Normal file
View File

@@ -0,0 +1,207 @@
; void __fastcall__ decompress_lzsa1(const void *src, void *dest)
;
; NMOS 6502 decompressor for data stored in Emmanuel Marty's LZSA1 format.
;
; Compress with:
; lzsa -r -f 1 input.bin output.lzsa1
;
; Copyright John Brandwood 2021.
;
; Distributed under the Boost Software License, Version 1.0.
; Boost Software License - Version 1.0 - August 17th, 2003
;
; Permission is hereby granted, free of charge, to any person or organization
; obtaining a copy of the software and accompanying documentation covered by
; this license (the "Software") to use, reproduce, display, distribute,
; execute, and transmit the Software, and to prepare derivative works of the
; Software, and to permit third-parties to whom the Software is furnished to
; do so, all subject to the following:
;
; The copyright notices in the Software and this entire statement, including
; the above license grant, this restriction and the following disclaimer,
; must be included in all copies of the Software, in whole or in part, and
; all derivative works of the Software, unless such copies or derivative
; works are solely in the form of machine-executable object code generated by
; a source language processor.
;
; THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
; IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
; FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
; SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
; FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
; ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
; DEALINGS IN THE SOFTWARE.
.export _decompress_lzsa1
.import popax
.importzp ptr1, ptr2, ptr3, ptr4, tmp1, tmp2, tmp3
lzsa_cmdbuf = tmp1 ; 1 byte.
lzsa_winptr = ptr1 ; 1 word.
lzsa_srcptr = ptr2 ; 1 word.
lzsa_dstptr = ptr3 ; 1 word.
lzsa_offset = lzsa_winptr
.proc _decompress_lzsa1
sta lzsa_dstptr
stx lzsa_dstptr+1
jsr popax
sta lzsa_srcptr
stx lzsa_srcptr+1
lzsa1_unpack: ldy #0 ; Initialize source index.
ldx #0 ; Initialize hi-byte of length.
;
; Copy bytes from compressed source data.
;
; N.B. X=0 is expected and guaranteed when we get here.
;
cp_length: lda (lzsa_srcptr),y
inc lzsa_srcptr
bne cp_skip0
inc lzsa_srcptr+1
cp_skip0: sta lzsa_cmdbuf ; Preserve this for later.
and #$70 ; Extract literal length.
lsr ; Set CC before ...
beq lz_offset ; Skip directly to match?
lsr ; Get 3-bit literal length.
lsr
lsr
cmp #$07 ; Extended length?
bcc cp_got_len
jsr get_length ; X=0, CS from CMP, returns CC.
stx cp_npages+1 ; Hi-byte of length.
cp_got_len: tax ; Lo-byte of length.
cp_byte: lda (lzsa_srcptr),y ; CC throughout the execution of
sta (lzsa_dstptr),y ; of this .cp_page loop.
inc lzsa_srcptr
bne cp_skip1
inc lzsa_srcptr+1
cp_skip1: inc lzsa_dstptr
bne cp_skip2
inc lzsa_dstptr+1
cp_skip2: dex
bne cp_byte
cp_npages: lda #0 ; Any full pages left to copy?
beq lz_offset
dec cp_npages+1 ; Unlikely, so can be slow.
bcc cp_byte ; Always true!
;
; Copy bytes from decompressed window.
;
; Longer but faster.
;
; N.B. X=0 is expected and guaranteed when we get here.
;
lz_offset: lda (lzsa_srcptr),y ; Get offset-lo.
inc lzsa_srcptr
bne offset_lo
inc lzsa_srcptr+1
offset_lo: sta lzsa_offset
lda #$FF ; Get offset-hi.
bit lzsa_cmdbuf
bpl offset_hi
lda (lzsa_srcptr),y
inc lzsa_srcptr
bne offset_hi
inc lzsa_srcptr+1
offset_hi: sta lzsa_offset+1
lz_length: lda lzsa_cmdbuf ; X=0 from previous loop.
and #$0F
adc #$03 ; Always CC from .cp_page loop.
cmp #$12 ; Extended length?
bcc got_lz_len
jsr get_length ; X=0, CS from CMP, returns CC.
got_lz_len: inx ; Hi-byte of length+256.
eor #$FF ; Negate the lo-byte of length
tay
eor #$FF
get_lz_dst: adc lzsa_dstptr ; Calc address of partial page.
sta lzsa_dstptr ; Always CC from previous CMP.
iny
bcs get_lz_win
beq get_lz_win ; Is lo-byte of length zero?
dec lzsa_dstptr+1
get_lz_win: clc ; Calc address of match.
adc lzsa_offset ; N.B. Offset is negative!
sta lzsa_winptr
lda lzsa_dstptr+1
adc lzsa_offset+1
sta lzsa_winptr+1
lz_byte: lda (lzsa_winptr),y
sta (lzsa_dstptr),y
iny
bne lz_byte
inc lzsa_dstptr+1
dex ; Any full pages left to copy?
bne lz_more
jmp cp_length ; Loop around to the beginning.
lz_more: inc lzsa_winptr+1 ; Unlikely, so can be slow.
bne lz_byte ; Always true!
;
; Get 16-bit length in X:A register pair, return with CC.
;
; N.B. X=0 is expected and guaranteed when we get here.
;
get_length: clc ; Add on the next byte to get
adc (lzsa_srcptr),y ; the length.
inc lzsa_srcptr
bne skip_inc
inc lzsa_srcptr+1
skip_inc: bcc got_length ; No overflow means done.
clc ; MUST return CC!
tax ; Preserve overflow value.
extra_byte: jsr get_byte ; So rare, this can be slow!
pha
txa ; Overflow to 256 or 257?
beq extra_word
check_length: pla ; Length-lo.
bne got_length ; Check for zero.
dex ; Do one less page loop if so.
got_length: rts
extra_word: jsr get_byte ; So rare, this can be slow!
tax
bne check_length ; Length-hi == 0 at EOF.
finished: pla ; Length-lo.
pla ; Decompression completed, pop
pla ; return address.
rts
get_byte: lda (lzsa_srcptr),y ; Subroutine version for when
inc lzsa_srcptr ; inlining isn't advantageous.
bne got_byte
inc lzsa_srcptr+1 ; Inc & test for bank overflow.
got_byte: rts
.endproc

308
libsrc/common/lzsa2.s Normal file
View File

@@ -0,0 +1,308 @@
; void __fastcall__ decompress_lzsa2(const void *src, void *dest)
;
; NMOS 6502 decompressor for data stored in Emmanuel Marty's LZSA2 format.
;
; Compress with:
; lzsa -r -f 2 input.bin output.lzsa2
;
; Copyright John Brandwood 2021.
;
; Distributed under the Boost Software License, Version 1.0.
; Boost Software License - Version 1.0 - August 17th, 2003
;
; Permission is hereby granted, free of charge, to any person or organization
; obtaining a copy of the software and accompanying documentation covered by
; this license (the "Software") to use, reproduce, display, distribute,
; execute, and transmit the Software, and to prepare derivative works of the
; Software, and to permit third-parties to whom the Software is furnished to
; do so, all subject to the following:
;
; The copyright notices in the Software and this entire statement, including
; the above license grant, this restriction and the following disclaimer,
; must be included in all copies of the Software, in whole or in part, and
; all derivative works of the Software, unless such copies or derivative
; works are solely in the form of machine-executable object code generated by
; a source language processor.
;
; THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
; IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
; FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
; SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
; FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
; ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
; DEALINGS IN THE SOFTWARE.
.export _decompress_lzsa2
.import popax
.importzp ptr1, ptr2, ptr3, ptr4, tmp1, tmp2, tmp3
lzsa_length = lzsa_winptr ; 1 word.
lzsa_cmdbuf = tmp1 ; 1 byte.
lzsa_nibflg = tmp2 ; 1 byte.
lzsa_nibble = tmp3 ; 1 byte.
lzsa_offset = ptr1 ; 1 word.
lzsa_winptr = ptr2 ; 1 word.
lzsa_srcptr = ptr3 ; 1 word.
lzsa_dstptr = ptr4 ; 1 word.
.proc _decompress_lzsa2
sta lzsa_dstptr
stx lzsa_dstptr+1
jsr popax
sta lzsa_srcptr
stx lzsa_srcptr+1
lzsa2_unpack:
ldx #$00 ; Hi-byte of length or offset.
ldy #$00 ; Initialize source index.
sty lzsa_nibflg ; Initialize nibble buffer.
;
; Copy bytes from compressed source data.
;
; N.B. X=0 is expected and guaranteed when we get here.
;
cp_length:
lda (lzsa_srcptr),y
inc lzsa_srcptr
bne cp_skip0
inc lzsa_srcptr+1
cp_skip0:
sta lzsa_cmdbuf ; Preserve this for later.
and #$18 ; Extract literal length.
beq lz_offset ; Skip directly to match?
lsr ; Get 2-bit literal length.
lsr
lsr
cmp #$03 ; Extended length?
bcc cp_got_len
jsr get_length ; X=0 for literals, returns CC.
stx cp_npages+1 ; Hi-byte of length.
cp_got_len:
tax ; Lo-byte of length.
cp_byte:
lda (lzsa_srcptr),y ; CC throughout the execution of
sta (lzsa_dstptr),y ; of this .cp_page loop.
inc lzsa_srcptr
bne cp_skip1
inc lzsa_srcptr+1
cp_skip1:
inc lzsa_dstptr
bne cp_skip2
inc lzsa_dstptr+1
cp_skip2:
dex
bne cp_byte
cp_npages:
lda #0 ; Any full pages left to copy?
beq lz_offset
dec cp_npages+1 ; Unlikely, so can be slow
bcc cp_byte ; Always true!
;
; Copy bytes from decompressed window.
;
; N.B. X=0 is expected and guaranteed when we get here.
;
; xyz
; ===========================
; 00z 5-bit offset
; 01z 9-bit offset
; 10z 13-bit offset
; 110 16-bit offset
; 111 repeat offset
;
lz_offset:
lda lzsa_cmdbuf
asl
bcs get_13_16_rep
get_5_9_bits:
dex ; X=$FF for a 5-bit offset.
asl
bcs get_9_bits ; Fall through if 5-bit.
get_13_bits:
asl ; Both 5-bit and 13-bit read
php ; a nibble.
jsr get_nibble
plp
rol ; Shift into position, clr C.
eor #$E1
cpx #$00 ; X=$FF for a 5-bit offset.
bne set_offset
sbc #2 ; 13-bit offset from $FE00.
bne set_hi_8 ; Always NZ from previous SBC.
get_9_bits:
asl ; X=$FF if CC, X=$FE if CS.
bcc get_lo_8
dex
bcs get_lo_8 ; Always CS from previous BCC.
get_13_16_rep:
asl
bcc get_13_bits ; Shares code with 5-bit path.
get_16_rep:
bmi lz_length ; Repeat previous offset.
get_16_bits:
jsr get_byte ; Get hi-byte of offset.
set_hi_8:
tax
get_lo_8:
lda (lzsa_srcptr),y ; Get lo-byte of offset.
inc lzsa_srcptr
bne set_offset
inc lzsa_srcptr+1
set_offset:
sta lzsa_offset ; Save new offset.
stx lzsa_offset+1
lz_length:
ldx #1 ; Hi-byte of length+256.
lda lzsa_cmdbuf
and #$07
clc
adc #$02
cmp #$09 ; Extended length?
bcc got_lz_len
jsr get_length ; X=1 for match, returns CC.
inx ; Hi-byte of length+256.
got_lz_len:
eor #$FF ; Negate the lo-byte of length.
tay
eor #$FF
get_lz_dst:
adc lzsa_dstptr ; Calc address of partial page.
sta lzsa_dstptr ; Always CC from previous CMP.
iny
bcs get_lz_win
beq get_lz_win ; Is lo-byte of length zero?
dec lzsa_dstptr+1
get_lz_win:
clc ; Calc address of match.
adc lzsa_offset ; N.B. Offset is negative!
sta lzsa_winptr
lda lzsa_dstptr+1
adc lzsa_offset+1
sta lzsa_winptr+1
lz_byte:
lda (lzsa_winptr),y
sta (lzsa_dstptr),y
iny
bne lz_byte
inc lzsa_dstptr+1
dex ; Any full pages left to copy?
bne lz_more
jmp cp_length ; Loop around to the beginning.
lz_more:
inc lzsa_winptr+1 ; Unlikely, so can be slow.
bne lz_byte ; Always true!
;
; Lookup tables to differentiate literal and match lengths.
;
nibl_len_tbl:
.byte 3 ; 0+3 (for literal).
.byte 9 ; 2+7 (for match).
byte_len_tbl:
.byte 18 - 1 ; 0+3+15 - CS (for literal).
.byte 24 - 1 ; 2+7+15 - CS (for match).
;
; Get 16-bit length in X:A register pair, return with CC.
;
get_length:
jsr get_nibble
cmp #$0F ; Extended length?
bcs byte_length
adc nibl_len_tbl,x ; Always CC from previous CMP.
got_length:
ldx #$00 ; Set hi-byte of 4 & 8 bit
rts ; lengths.
byte_length:
jsr get_byte ; So rare, this can be slow!
adc byte_len_tbl,x ; Always CS from previous CMP
bcc got_length
beq finished
word_length:
clc ; MUST return CC!
jsr get_byte ; So rare, this can be slow!
pha
jsr get_byte ; So rare, this can be slow!
tax
pla
bne got_word ; Check for zero lo-byte.
dex ; Do one less page loop if so.
got_word:
rts
get_byte:
lda (lzsa_srcptr),y ; Subroutine version for when
inc lzsa_srcptr ; inlining isn't advantageous.
bne got_byte
inc lzsa_srcptr+1
got_byte:
rts
finished:
pla ; Decompression completed, pop
pla ; return address.
rts
;
; Get a nibble value from compressed data in A.
;
get_nibble:
lsr lzsa_nibflg ; Is there a nibble waiting?
lda lzsa_nibble ; Extract the lo-nibble.
bcs got_nibble
inc lzsa_nibflg ; Reset the flag.
lda (lzsa_srcptr),y
inc lzsa_srcptr
bne set_nibble
inc lzsa_srcptr+1
set_nibble:
sta lzsa_nibble ; Preserve for next time.
lsr ; Extract the hi-nibble.
lsr
lsr
lsr
got_nibble:
and #$0F
rts
.endproc

148
libsrc/common/zx02.s Normal file
View File

@@ -0,0 +1,148 @@
; void __fastcall__ decompress_zx02(const void *src, void *dest)
;
; De-compressor for ZX02 files
;
; Compress with:
; zx02 input.bin output.zx0
;
; (c) 2022 DMSC
; Code under MIT license, see LICENSE file.
.export _decompress_zx02
.import popax
.importzp ptr1, ptr2, ptr3, tmp1, tmp2
offset_hi = tmp1
ZX0_src = ptr1
ZX0_dst = ptr2
bitr = tmp2
pntr = ptr3
.proc _decompress_zx02
sta ZX0_dst
stx ZX0_dst+1
jsr popax
sta ZX0_src
stx ZX0_src+1
; Init values
lda #$80
sta bitr
ldy #$FF
sty pntr
iny
sty offset_hi ; Y = 0 at end of init
; Decode literal: Copy next N bytes from compressed file
; Elias(length) byte[1] byte[2] ... byte[N]
decode_literal:
ldx #$01
jsr get_elias
cop0:
lda (ZX0_src), y
inc ZX0_src
bne :+
inc ZX0_src+1
: sta (ZX0_dst),y
inc ZX0_dst
bne :+
inc ZX0_dst+1
: dex
bne cop0
asl bitr
bcs dzx0s_new_offset
; Copy from last offset (repeat N bytes from last offset)
; Elias(length)
inx
jsr get_elias
dzx0s_copy:
lda ZX0_dst+1
sbc offset_hi ; C=0 from get_elias
sta pntr+1
cop1:
ldy ZX0_dst
lda (pntr), y
ldy #0
sta (ZX0_dst),y
inc ZX0_dst
bne :+
inc ZX0_dst+1
inc pntr+1
: dex
bne cop1
asl bitr
bcc decode_literal
; Copy from new offset (repeat N bytes from new offset)
; Elias(MSB(offset)) LSB(offset) Elias(length-1)
dzx0s_new_offset:
; Read elias code for high part of offset
inx
jsr get_elias
beq exit ; Read a 0, signals the end
; Decrease and divide by 2
dex
txa
lsr
sta offset_hi
; Get low part of offset, a literal 7 bits
lda (ZX0_src), y
inc ZX0_src
bne :+
inc ZX0_src+1
: ; Divide by 2
ror
eor #$ff
sta pntr
; And get the copy length.
; Start elias reading with the bit already in carry:
ldx #1
jsr elias_skip1
inx
bcc dzx0s_copy
; Read an elias-gamma interlaced code.
elias_get:
; Read next data bit to result
asl bitr
rol
tax
get_elias:
; Get one bit
asl bitr
bne elias_skip1
; Read new bit from stream
lda (ZX0_src), y
inc ZX0_src
bne :+
inc ZX0_src+1
: ; sec ; not needed, C=1 guaranteed from last bit
rol
sta bitr
elias_skip1:
txa
bcs elias_get
; Got ending bit, stop reading
exit:
rts
.endproc