; LZSS decoder for SNES ; /Mic, 2009-2010 ; ; SNESC version ; ; Note: The compressed data must not cross a (64kB) bank boundary. .include "hdr.asm" .bank 3 slot 0 .section ".text_lzss" .DEFINE LZSS_DICTIONARY_SIZE 4096 .DEFINE LZSS_THRESHOLD 2 .DEFINE LZSS_LEN_BITS 4 .DEFINE LZSS_LEN_MASK $0f .DEFINE LZSS_MAX_LEN 18 .DEFINE LZSS_PLANES_USED 4 .DEFINE LZSS_FORMAT_PLANES 4 ; The base address should be an even multiple of 4KB .DEFINE LZSS_RAM_BASE $7f8000 .DEFINE LZSS_TEXTBUF LZSS_RAM_BASE .DEFINE LZSS_ZP_BASE $A0 .DEFINE LZSS_DATA_PTR LZSS_ZP_BASE .DEFINE LZSS_FLAGS LZSS_DATA_PTR+3 .DEFINE LZSS_STRLEN LZSS_FLAGS+1 .DEFINE LZSS_TEMP LZSS_STRLEN+2 .DEFINE LZSS_STRPTR LZSS_TEMP+2 .DEFINE LZSS_PTR LZSS_STRPTR+2 .DEFINE LZSS_TEXTBUF_OFFS LZSS_PTR+3 .DEFINE LZSS_LOBYTE LZSS_TEXTBUF_OFFS+2 .DEFINE LZSS_FLIPFLOP LZSS_LOBYTE+1 .DEFINE LZSS_CNT2 LZSS_FLIPFLOP+1 .DEFINE LZSS_DEST LZSS_CNT2+1 .DEFINE LZSS_CNT LZSS_RAM_BASE+LZSS_DICTIONARY_SIZE .DEFINE LZSS_BITPLANES LZSS_CNT2+1 .DEFINE LZSS_PLANECNT LZSS_CNT2+2 .DEFINE LZSS_TEXTBUF_P (LZSS_DICTIONARY_SIZE-LZSS_MAX_LEN) ; Function lzss_decode_vram ; lzss_decode_vram: php ; (3) sei rep #$30 ; (3) phx phy phb lda 10,s ; source address (low 16 bits) tax lda 14,s ; VRAM offset sta LZSS_ZP_BASE lda 16,s ; compressed size tay lda 12,s ; source bank sep #$20 sta LZSS_DATA_PTR+2 pha plb lda #$7f sta LZSS_DEST+2 rep #$20 lda LZSS_ZP_BASE stz LZSS_FLIPFLOP ; (3) stz LZSS_STRLEN stx LZSS_DATA_PTR ; (5) ldx #LZSS_TEXTBUF_P ; (3) stx LZSS_TEXTBUF_OFFS ; (5) ; Set the VRAM address sta.l $002116 ; (5) ; Increment the VRAM address by one word after each write to $2119 sep #$20 lda #$80 sta.l $002115 ; Set all entries in textbuf to 0 ldx #0 rep #$20 lda #0 _ldv_init_textbuf: sta.l LZSS_TEXTBUF,x inx inx cpx #LZSS_TEXTBUF_P bne _ldv_init_textbuf sep #$20 tyx ; X = size of compressed data ldy #0 ; Y = offset into compressed data _ldv_loop: cpx #0 bne + jmp _ldv_done +: lda (LZSS_DATA_PTR),y ; Read the flags byte iny dex sta LZSS_FLAGS lda #8 sta LZSS_CNT2 ; There are 8 flags in the flags byte _ldv_check_flags: lsr LZSS_FLAGS bcc _ldv_flag_clear ; Branch if the lowest bit was a zero cpx #0 bne + jmp _ldv_done +: lda LZSS_FLIPFLOP eor #1 sta LZSS_FLIPFLOP beq + lda (LZSS_DATA_PTR),y iny dex phx ldx LZSS_TEXTBUF_OFFS sta.l LZSS_TEXTBUF,x sta.l $002118 ; DBR might not be zero here, which is why long addressing is used inx stx LZSS_TEXTBUF_OFFS plx jmp _ldv_next_flag +: lda (LZSS_DATA_PTR),y iny dex phx ldx LZSS_TEXTBUF_OFFS sta.l LZSS_TEXTBUF,x sta.l $002119 rep #$20 inx txa and #(LZSS_DICTIONARY_SIZE-1) sta LZSS_TEXTBUF_OFFS sep #$20 plx bra _ldv_next_flag _ldv_flag_clear: cpx #0 bne + jmp _ldv_done +: lda (LZSS_DATA_PTR),y ; A = idx iny sta LZSS_STRPTR lda (LZSS_DATA_PTR),y ; A = j iny dex dex sta LZSS_TEMP+1 and #LZSS_LEN_MASK clc adc #LZSS_THRESHOLD+1 sta LZSS_STRLEN ; STRLEN = (j & 0x0F) + THRESHOLD + 1 lda LZSS_TEMP+1 .REPT LZSS_LEN_BITS lsr a .ENDR sta LZSS_STRPTR+1 ; STRPTR = ((j & 0xF0) << 4) | idx phx phy phb lda #$7f pha plb ldx LZSS_STRPTR ldy LZSS_TEXTBUF_OFFS lda LZSS_FLIPFLOP and #1 bne _ldv_copy_string_odd _ldv_copy_string_even: lda.w $8000,x sta.w $8000,y sta.l $002118 ; (5) inx ; (2) rep #$20 ; (3) txa ; (2) and #(LZSS_DICTIONARY_SIZE-1) ; (3) tax ; (2) iny ; (2) tya ; (2) and #(LZSS_DICTIONARY_SIZE-1) ; (3) tay ; (2) sep #$20 ; (3) dec LZSS_STRLEN ; (5) bne _ldv_copy_string_odd ; (2.n) (tot 47.n) lda #1 sta LZSS_FLIPFLOP ; If the last write was an even one then the flip-flop should be odd bra _ldv_copy_string_done _ldv_copy_string_odd: lda.w $8000,x sta.w $8000,y sta.l $002119 inx rep #$20 txa and #(LZSS_DICTIONARY_SIZE-1) tax iny tya and #(LZSS_DICTIONARY_SIZE-1) tay sep #$20 dec LZSS_STRLEN ; (5) bne _ldv_copy_string_even ; (2.n) stz LZSS_FLIPFLOP _ldv_copy_string_done: sty LZSS_TEXTBUF_OFFS plb ply plx _ldv_next_flag: dec LZSS_CNT2 bne + jmp _ldv_loop +: jmp _ldv_check_flags _ldv_done: plb ply plx plp rtl .ends