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

334 lines
9.1 KiB
NASM

;DYNAMIC_TABLES
;FAST_LITERAL ;/!\ does not work with current implementation of krill loader, as it touches the lowbyte of the sector_pointers
;FAST_MATCH
;-------------------------------------------------------------------------------
;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_scratch = $fe ;Temporary zeropage storage
;
;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
;-------------------------------------------------------------------------------
;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 address
jsr _lz_refill_bits
sty lz_dst
jsr _lz_refill_bits
sty lz_dst+1
!ifdef DYNAMIC_TABLES {
;load 24 byte long tables
lda #<_lz_moff_length
sta _lz_cp+1
-
jsr _lz_refill_bits
_lz_cp sty _lz_moff_length
inc _lz_cp+1
lda _lz_cp+1
cmp #<(_lz_moff_length+$18)
bne -
;sec
} else {
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)
!ifdef FAST_LITERAL {
_lz_maximum jsr lz_fetch_sector ;Grab a new sector for the literal loop. Carry is set, so we fall through next check
_lz_mfinish bcc *+4
inc lz_dst+1
} else {
_lz_mfinish bcc *+4
_lz_maximum inc lz_dst+1 ;This is also used by maximum length
}
;literals needing an explicit type bit
;Literal or match to follow?
asl lz_bits
_lz_type_cont bcc _lz_do_match
beq _lz_type_refill
;******** Process literal run ********
lda #%00000000 ;Decode run length
_lz_lrun_loop rol
asl lz_bits
bcs _lz_lrun_test
_lz_lrun_back asl lz_bits
bne _lz_lrun_loop
jsr _lz_refill_bits
bne _lz_lrun_loop ;(BRA)
_lz_lrun_test bne _lz_lrun_gotten
jsr _lz_refill_bits
bcc _lz_lrun_back
_lz_lrun_gotten
sta _lz_copy_cnt+1 ;Store LSB of run-length
ldy #$00
!ifdef FAST_LITERAL {
stx lz_sector_ptr2 ;Store x as lowbyte, so we can use y for lda + sta, and we don't need to bother about overruns
_lz_lcopy
lz_sector_ptr2 = *+1 ;Copy the literal data.
lda lz_sector,y
} else {
_lz_lcopy
lz_sector_ptr2 = *+1 ;Copy the literal data.
lda lz_sector,x
inx
bne *+5
jsr lz_fetch_sector ;Grab a new sector for the literal loop
}
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
!ifdef FAST_LITERAL {
adc lz_sector_ptr2
tax ;fix x
bcc *+6
jsr lz_fetch_sector ;Grab a new sector for the literal loop
clc
tya
}
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 ********
_lz_do_match lda #%00100000 ;Determine offset length by a two-bit
_lz_moff_range asl lz_bits ;prefix combined with the first run
bne *+5 ;length bit (where a one identifies
jsr _lz_refill_bits ;a two-byte match).
rol ;The rest of the length bits will
bcc _lz_moff_range ;then follow *after* the offset data
tay
lda _lz_moff_length,y
beq _lz_moff_far
_lz_moff_loop asl lz_bits ;Load partial offset byte
bne *+9
sty lz_scratch
jsr _lz_refill_bits
ldy lz_scratch
rol
bcc _lz_moff_loop
bmi _lz_moff_near
_lz_moff_far sta lz_scratch ;Save the bits we just read as the
;high-byte
lz_sector_ptr3 = *+1
lda lz_sector,x ;For large offsets we can load the
inx ;low-byte straight from the stream
bne *+5 ;without going throught the shift
jsr lz_fetch_sector ;register
; sec
adc _lz_moff_adjust_lo,y ;y .. 2 .. 5? ?! necessary with a full lowbyte?!?!
bcs _lz_moff_pageok
dec lz_scratch
sec
_lz_moff_pageok adc lz_dst+0
sta lz_match+0
lda lz_scratch
adc _lz_moff_adjust_hi,y
sec
bcs _lz_moff_join ;(BRA)
_lz_moff_near
; sec ;Special case handling of <8 bit offsets.
adc _lz_moff_adjust_lo,y;We may can safely ignore the MSB from
; sec ;the base adjustment table as the
adc lz_dst+0 ;maximum base (for a 4/5/6/7 bit
sta lz_match+0 ;length sequence) is 113
lda #$ff
_lz_moff_join adc lz_dst+1
sta lz_match+1
cpy #$04 ;Get any remaning run length bits
lda #%00000001
bcs _lz_mrun_start ;Sentinel check can be skipped in that case
_lz_mrun_loop asl lz_bits
bne *+5
jsr _lz_refill_bits
rol
asl lz_bits
bcc _lz_mrun_loop
bne _lz_mrun_gotten
jsr _lz_refill_bits
bcc _lz_mrun_loop
;XXX TODO only needed on near matches as offset = 1 in that case
_lz_mrun_gotten tay ;A 257-byte (=>$00) run serves as a
beq _lz_end_of_file ;sentinel
_lz_mrun_start
sta _lz_mcopy_len
ldy #$ff ;The copy loop. This needs to be run
;forwards since RLE-style matches can overlap the destination
!ifdef FAST_MATCH {
lsr ;Check bit 0
bcc _lz_odd ;Odd or even number? Enter at right position of our loop that always copies two bytes in one go
}
_lz_mcopy
iny
lda (lz_match),y ;Copy one byte
!ifdef FAST_MATCH {
sta (lz_dst),y
_lz_odd
iny ;Next byte
lda (lz_match),y ;And another one
}
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
;******** Offset coding tables ********
;This length table is a bit funky. The idea here is to use the
;value as the initial value of the shift register instead of
;keeping a separate counter.
;In other words we iterate until the leading one is shifted out.
;Then afterwards the bit just below it (our new sign bit) is set
;if the offset is shorter than 8-bits, and conversely it's
;cleared if we need to fetch a separate low-byte
;as well.
;The fact that the sign bit is cleared as a flag is compensated
;for in the lz_moff_adjust_hi table
!ifndef DYNAMIC_TABLES {
_lz_moff_length
;Long (>2 byte matches)
!byte %00011111 ;4 bits
!byte %00000011 ;7 bits
!byte %01011111 ;10 bits
!byte %00001011 ;13 bits
;Short (2 byte matches)
!byte %01011111 ;10 bits
!byte %00000000 ;8 bits
!byte %00000111 ;6 bits
!byte %00111111 ;3 bits
_lz_moff_adjust_hi = *-2
;Long (>2 byte matches)
; !byte %11111111 ;1-16 (unreferenced)
; !byte %11111111 ;17-144 (unreferenced)
!byte %01111111 ;145-1168
!byte %01111011 ;1169-9360
;Short (2 byte matches)
!byte %01111110 ;329-1352
!byte %11111110 ;73-328
; !byte %11111111 ;9-72 (unreferenced)
; !byte %11111111 ;1-8 (unreferenced)
_lz_moff_adjust_lo = * - 1
;Long (>2 byte matches)
;!byte %11111110 ;1-16
!byte %11101110 ;17-144
!byte %01101110 ;145-1168
!byte %01101110 ;1169-9360
;Short (2 byte matches)
!byte %10110110 ;329-1352
!byte %10110110 ;73-328
!byte %11110110 ;9-72
!byte %11111110 ;1-8
;******** 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
beq lz_fetch_sector
_lz_end_of_file rts
lz_fetch_sector
inc lz_sector_ptr1+1
inc lz_sector_ptr2+1
inc lz_sector_ptr3+1
rts
!ifdef DYNAMIC_TABLES {
_lz_moff_length
_lz_moff_adjust_lo = * + 8
_lz_moff_adjust_hi = * + 16
}