ys2-intro/loader/tools/doynamite1.1/simple/decrunch.asm
2025-11-13 19:07:39 +03:00

238 lines
6.8 KiB
NASM

;those will make the depacker ~5% faster but bloat it:
;FAST_LITERAL_COPY = 1 ;will increase size by 11 bytes
;FAST_MATCH_COPY = 1 ;will increase size by 11 bytes
;-------------------------------------------------------------------------------
;Regular version of the Lempel-Ziv decompressor
;-------------------------------------------------------------------------------
;lz_match = $f9 ;Match source pointer
;lz_dst = $fb ;Decompression destination pointer.
; ;Initialize this to whatever address
; ;you want to decompress to
;
;lz_bits = $fd ;Internal shift register
;
;lz_sector = $0400 ;The one-page buffer from which the
; ;compressed data is actually read,
; ;and which gets refilled by
; ;lz_fetch_sector
;-------------------------------------------------------------------------------
;This is the user's hook to replenish the sector buffer with some new bytes.
;
;A and Y are expected to be preserved while carry must remain set on exit.
;X should point to the first byte of the new data, e.g. zero for a full 256-byte
;page of data or two to skip past the sector and track links.
;
;When fetching from a larger in-memory array rather than a single sector buffer
;the lz_sector_ptr1..3 pointers will need to be patched up
;-------------------------------------------------------------------------------
;lz_fetch_sector
; inc lz_sector_ptr1+1
; inc lz_sector_ptr2+1
; inc lz_sector_ptr3+1
; rts
;-------------------------------------------------------------------------------
;Typical usage
;-------------------------------------------------------------------------------
; ldx #>source
; ldy #<source
; jsr lz_decrunch
;-------------------------------------------------------------------------------
;This is the main lz_decrunch function which may be called to decompress an
;entire file.
;
;On entry and exit the X register points to the next available byte in the
;sector buffer, in ascending order from $00 to $ff.
;This implies that the initial sector must have already been fetched, and that a
;file ending with X wrapped to $00 will have needlessly fetched an extra sector
;(which may be taken advantage of when decoding a contiguous set of files.)
;-------------------------------------------------------------------------------
;******** Start the next match/literal run ********
lz_decrunch
sty lz_sector_ptr1+1
sty lz_sector_ptr2+1
sty lz_sector_ptr3+1
;fetch depack addr
jsr _lz_refill_bits
sty lz_dst
jsr _lz_refill_bits
sty lz_dst+1
sec ;This is the main entry point. Forcibly
_lz_type_refill jsr _lz_refill_bits ;fill up the the bit buffer on entry
bne _lz_type_cont ;(BRA)
;Wrap the high-byte of the destination pointer.
_lz_mfinish bcc *+4
_lz_maximum inc lz_dst+1 ;This is also used by maximum length
;literals needing an explicit type bit
_lz_type_check
;Literal or match to follow?
asl lz_bits
_lz_type_cont bcc _lz_do_match
beq _lz_type_refill ;no more bits left, fetch new bits and reevaluate
;******** Process literal run ********
lda #$00
-
rol
asl lz_bits
bne *+5
jsr _lz_refill_bits
bcc _lz_lrun_gotten
asl lz_bits
bne -
jsr _lz_refill_bits
bne -
_lz_lrun_gotten
sta _lz_copy_cnt+1 ;Store LSB of run-length
ldy #$00
_lz_lcopy
lz_sector_ptr2 = *+1 ;Copy the literal data.
lda lz_sector,x
inx
bne *+5
jsr lz_fetch_sector
sta (lz_dst),y
iny
_lz_copy_cnt cpy #$00
bne _lz_lcopy
;Time to advance the destination pointer.
;Maximum run length literals exit here as a type-bit needs
;to be fetched afterwards
tya
beq _lz_maximum ;maximum literal run, bump sector pointers and so on
clc
adc lz_dst+0
sta lz_dst+0
bcc _lz_do_match
inc lz_dst+1
;One literal run following another only makes sense if the
;first run is of maximum length and had to be split. As that
;case has been taken care of we can safely omit the type bit
;here
;******** Process match ********
;check bit -> 1 -> short match (len 2)
;else long match
_lz_do_match
lda #$01 ;this could be made shorter by using the last bitfetch of the upcoming loop and restoring the carry again by a cmp #$02. Saves bytes, but makes things slower, as eof check is also done with all short matches then
asl lz_bits ;first length bit (where a one identifies
bne *+5 ;a two-byte match)
jsr _lz_refill_bits
bcc lz_get_offs ;all done, length is 2, skip further bitfetches
-
asl lz_bits
bne *+5
jsr _lz_refill_bits
rol
asl lz_bits
bne *+5
jsr _lz_refill_bits
bcc -
tay ;A 257-byte (=>$00) run serves as a
beq _lz_end_of_file ;sentinel
lz_get_offs
sta _lz_mcopy_len ;store length at final destination
lda #%11000000 ;fetch 2 more prefix bits
rol ;previous bit is still in carry \o/
-
asl lz_bits ;XXX TODO in ultra slim variant this could be a subroutine
bne *+5 ;XXX TODO this code could also be called twice
jsr _lz_refill_bits
rol
bcs -
tay
lda lentab,y
beq lz_far ;XXX TODO currently 8 and 9 bit long offsets carry teh same value here, so if one wants to use both, one has to set the value for 8 bit long offsets
;to $ff and check things here with a cmp #$ff. Including teh eor #$ff in lz_far or not will then solvet he problem. This is faster though.
-
asl lz_bits
bne *+5
jsr _lz_refill_bits
rol
bcs -
bmi lz_short
lz_far
eor #$ff ;negate
tay
lz_sector_ptr3 = *+1
lda lz_sector,x ;For large offsets we can load the
inx ;low-byte straight from the stream
bne lz_join ;without going throught the shift
jsr lz_fetch_sector ;register
!byte $2c ;skip next two bytes
lz_short
ldy #$ff ;XXX TODO GNAAA y is set twice to $ff in the case of short matches
lz_join
adc lz_dst ;subtract offset from lz_dst
sta lz_match
tya ;hibyte
adc lz_dst+1
sta lz_match+1
ldy #$ff ;The copy loop. This needs to be run
;forwards since RLE-style matches can overlap the destination
_lz_mcopy
iny
lda (lz_match),y ;Copy one byte
sta (lz_dst),y
_lz_mcopy_len = *+1
cpy #$ff
bne _lz_mcopy
tya ;Advance destination pointer
; sec
adc lz_dst+0
sta lz_dst+0
jmp _lz_mfinish
;******** Fetch some more bits to work with ********
lz_sector_ptr1 = *+1
_lz_refill_bits ldy lz_sector,x
sty lz_bits
; sec
rol lz_bits
inx
bne +
lz_fetch_sector
inc lz_sector_ptr1+1
inc lz_sector_ptr2+1
inc lz_sector_ptr3+1
+
_lz_end_of_file rts
lentab
;XXX TODO combine tables so that values used twice are eliminated? -> more classes can be used?
;XXX TODO best rearrange tables by using lookup table?
;short offset init values
!byte %11011111
!byte %11111011
!byte %00000000
!byte %10000000
;long offset init values
!byte %11101111
!byte %11111101
!byte %10000000
!byte %11110000