; LZSS decoder for SEGA Master System / Game Gear
; /Mic, 2009

; The decoder function itself is 191 bytes in size 


; Note: The base address must be $C000 for the address masking in the decoder to work.
.DEFINE LZSS_RAM_BASE $C000
.DEFINE LZSS_TEXTBUF LZSS_RAM_BASE

.DEFINE LZSS_CNT LZSS_RAM_BASE+LZSS_DICTIONARY_SIZE
.DEFINE LZSS_PTR LZSS_CNT+2
.DEFINE LZSS_CNT2 LZSS_PTR+2
.DEFINE LZSS_BITPLANES LZSS_CNT2+1
.DEFINE LZSS_PLANECNT LZSS_CNT2+2
.DEFINE LZSS_FLAGS LZSS_CNT2+3
.DEFINE LZSS_TEXTBUF_P (LZSS_DICTIONARY_SIZE-LZSS_MAX_LEN)


; Function lzss_decode_vram
;
; Decompress LZSS packed data to VRAM
;
; In:
;  IX = Pointer to compressed data
;  IY = Size of compressed data
;  HL = VRAM base address
;
; Out:
;  -
;
; Example:
;
;  ld ix,packed_patterns
;  ld iy,packed_patterns_end-packed_patterns
;  ld hl,$2000
;  call lzss_decode_vram
;
lzss_decode_vram:
	; Set the VRAM address
	ld	a,l
	out	($BF),a
	ld	a,h
	or	$40
	out	($BF),a

	ld	hl,LZSS_TEXTBUF+LZSS_TEXTBUF_P
	ld	(LZSS_PTR),hl

	; Set all entries in textbuf to 0	
	ld	hl,LZSS_TEXTBUF
	ld	bc,LZSS_TEXTBUF_P
_ldv_init_textbuf:
	ld	a,0
	ld	(hl),a
	inc	hl
	dec	bc
	ld	a,b
	or	c
	jr	nz,_ldv_init_textbuf
	ld	a,0
	ld	(LZSS_PLANECNT),a
_ldv_loop:
	ld	a,iyl
	or	iyh			; Do we have any more bytes remaining?
	jp	z,_ldv_done
	ld	a,(ix+0)		; Read one compressed byte
	dec	iy
	inc	ix			; Increase input stream pointer
	ld	(LZSS_FLAGS),a
	ld	a,8			; 8 flags per byte
	ld	(LZSS_CNT2),a
_ldv_check_flags:
	ld	a,(LZSS_PLANECNT)
	ld	c,a
	ld	a,(LZSS_FLAGS)
	srl	a
	ld	(LZSS_FLAGS),a
	jr	nc,_ldv_flag_clear
	ld	a,iyl
	or	iyh
	jr	z,_ldv_done
	ld	a,(ix+0)		; Read one compressed byte
	dec	iy
	inc	ix
	ld	b,1			; Make sure the jr after output_byte isn't taken
	ld	hl,(LZSS_PTR)
	jr	_ldv_output_byte
_ldv_flag_clear:
	ld	a,iyl
	or	iyh
	jr	z,_ldv_done
	ld	e,(ix+0)		; e = idx
	inc	ix
	ld	a,(ix+0)		; a = j
	inc	ix
	dec	iy
	dec	iy
	ld	d,a
	.REPT LZSS_LEN_BITS
	srl	d			; de = ((j & 0xF0) << 4) | idx
	.ENDR
	and 	LZSS_LEN_MASK
	add	a,LZSS_THRESHOLD+1
	ld	b,a			; b = (j & 0x0F) + THRESHOLD
	ld	a,d
	or	>(LZSS_RAM_BASE)	; de |= $C000
	ld	d,a
	ld	hl,(LZSS_PTR)
_ldv_copy_string:
	ld	a,(de)			; Read from textbuf
	push	af
	; Increase read address and wrap
	inc	de
	ld	a,d
	and	>(LZSS_RAM_BASE+LZSS_DICTIONARY_SIZE-1)	
	ld	d,a	
	pop	af
_ldv_output_byte:
	out	($BE),a			; Write to VRAM
	ld	(hl),a			; Write to textbuf

.IF LZSS_PLANES_USED < LZSS_FORMAT_PLANES
	; Pad with empty bitplanes if needed
	ld	a,c
	inc	a
	and	LZSS_FORMAT_PLANES-1
	ld	c,a
	ld	a,LZSS_PLANES_USED-1 
	cp	c
	jr	nc,_ldv_dont_pad
_ldv_pad:
	ld	a,0
	out	($BE),a
	inc	c
	ld	a,c
	and	LZSS_FORMAT_PLANES-1
	jr	nz,_ldv_pad
_ldv_dont_pad:
.ENDIF

	; Increase write address and wrap
	inc	hl
	ld	a,h
	and	>(LZSS_RAM_BASE+LZSS_DICTIONARY_SIZE-1)
	ld	h,a
_ldv_next_string_pos:
	dec	b
	jr	nz,_ldv_copy_string
	ld	a,c
	ld	(LZSS_PLANECNT),a
	ld	(LZSS_PTR),hl
_ldv_next_flag:
	ld	a,(LZSS_CNT2)
	dec	a
	ld	(LZSS_CNT2),a
	jp	z,_ldv_loop
	jp	_ldv_check_flags
_ldv_done:
	ret


