; 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

	
