; LZSS decoder for SNES ; /Mic, 2009-2010 ; Note: The compressed data must not cross a (64kB) bank boundary. ; The base address should be an even multiple of 4KB .DEFINE LZSS_RAM_BASE $7f0000 .DEFINE LZSS_TEXTBUF LZSS_RAM_BASE .DEFINE LZSS_ZP_BASE $80 .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 ; ; Decompress LZSS packed data to VRAM ; ; In: ; X = Pointer to compressed data (16-bit) ; Y = Size of compressed data (16-bit) ; A = VRAM base address (16-bit) ; DBR = compressed data bank ; ; Out: ; - ; ; Example: ; ; sep #$20 ; lda #:packed_patterns ; phb ; pha ; plb ; rep #$30 ; lda #$4000 ; ldx #packed_patterns ; ldy #(packed_patterns_end-packed_patterns) ; jsr lzss_decode_vram ; plb ; lzss_decode_vram: php ; (3) rep #$30 ; (3) pha ; (4) stz LZSS_FLIPFLOP ; (3) stz LZSS_STRLEN stx LZSS_DATA_PTR ; (5) ldx #0 ; (3) stx LZSS_PTR ; (5) ldx #LZSS_TEXTBUF_P ; (3) stx LZSS_TEXTBUF_OFFS ; (5) .IFNDEF LZSS_DECODE_RAM ; 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 .ELSE sta LZSS_DEST sep #$20 .ENDIF phb pla sta LZSS_DATA_PTR+2 lda #$7f sta LZSS_PTR+2 sta LZSS_DEST+2 ; 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 +: .IFNDEF LZSS_DECODE_RAM 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 ;rep #$20 inx ;txa ;and #(LZSS_DICTIONARY_SIZE-1) ;sta LZSS_TEXTBUF_OFFS ;sep #$20 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 .ELSE lda (LZSS_DATA_PTR),y iny dex phx ldx LZSS_TEXTBUF_OFFS sta.l LZSS_TEXTBUF,x sta [LZSS_DEST] inc LZSS_DEST bne + inc LZSS_DEST+1 +: .ENDIF 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 .IFNDEF LZSS_DECODE_RAM lda LZSS_FLIPFLOP and #1 bne _ldv_copy_string_odd _ldv_copy_string_even: lda.w $0000,x sta.w $0000,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 $0000,x sta.w $0000,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 .ELSE _ldv_copy_string: lda.l LZSS_TEXTBUF,x sta [LZSS_PTR],y sta [LZSS_DEST] inc LZSS_DEST bne + inc LZSS_DEST+1 +: inx rep #$20 txa and #(LZSS_DICTIONARY_SIZE-1) tax iny tya and #(LZSS_DICTIONARY_SIZE-1) tay sep #$20 dec LZSS_STRLEN bne _ldv_copy_string .ENDIF _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: rep #$20 pla plp rts