; =============================================================================
; CBS Cruncher v5.3 + v5.4 - combined RAM-to-RAM decruncher v0.1
; =============================================================================
; Input:
;   HL = three-byte CBS header
;        40h = v5.3 state flow / linear RAM
;        48h = v5.4 adaptive-near state flow / linear RAM
;   DE = destination in RAM
;
; RAM-tail entry:
;   HL = complete 41h/49h packed file at its current address
;   DE = start of the destination block
;   BC = complete expanded length / destination-block length
;   CBS_V5354_RAM_TAIL copies the packed file backward to DE+BC-packedLength,
;   then enters the normal forward decruncher. Use only a stream reported as
;   tail-safe by the compressor; invalid placement can overwrite unread input.
;
; The decoder examines header bit 3 once:
;   clear = v5.3; a new-offset token immediately enters the general offset
;   set   = v5.4; a new-offset token first reads near/general selector
;
; CBS_V5354_VARIANT_JUMP is a one-byte self-modifying specialization. The
; decoder must execute from RAM. For v5.3 its JR displacement becomes 8 and
; skips the complete selector reader. For v5.4 it becomes 0 and falls through.
; There is therefore no format check in the hot token loop.
;
; Both formats share the literal state, last-offset state, general-offset
; reader, match copier, interlaced Elias reader and high-value-256 EOS.
; No index-register, alternate-register, ROM, mapper, row or VRAM paths exist.
;
; The state-flow and interlaced Elias structure derive from the ZX0 standard
; decoder by Einar Saukas and Urusergi (BSD-3-Clause).
; =============================================================================

; -----------------------------------------------------------------------------
; Copy a measured-safe packed file to the end of its destination block.
;
; Both register banks temporarily hold the entry parameters. This keeps the
; relocation independent of RAM variables and leaves the normal hot decoder
; unchanged. LDDR supports the common case where packed data begins at the
; start of the destination block and must move upward before expansion.
; -----------------------------------------------------------------------------
CBS_V5354_RAM_TAIL:
	PUSH	HL			; duplicate entry parameters in shadow registers
	PUSH	DE
	PUSH	BC
	EXX
	POP	BC			; BC = expanded length
	POP	DE			; DE = destination start
	POP	HL			; HL = packed-file start

	INC	HL
	LD	C,(HL)			; packed length low
	INC	HL
	LD	B,(HL)			; packed length high
	DEC	HL
	DEC	HL			; HL = packed-file start
	ADD	HL,BC
	DEC	HL			; HL = last packed byte
	PUSH	DE			; preserve destination start

	EXX				; original BC still holds expanded length
	EX	DE,HL			; HL = destination start
	ADD	HL,BC
	DEC	HL			; HL = destination-block last byte
	EX	DE,HL			; DE = destination-block last byte
	PUSH	DE

	EXX
	POP	DE			; DE = destination-block last byte
	LDDR				; relocate complete header + payload backward
	INC	DE			; DE = relocated packed-file start
	EX	DE,HL			; HL = relocated packed-file start
	POP	DE			; DE = destination start

	PUSH	HL			; return the active register bank to the caller's
	PUSH	DE			; normal set before entering the shared decoder
	EXX
	POP	DE
	POP	HL
	JP	CBS_V5354_RAM_TO_RAM

CBS_V5354_RAM_TO_RAM:
	LD	A,(HL)			; format / variant / destination
	AND	08h			; 00h=v5.3, 08h=v5.4
	XOR	08h			; 08h=v5.3 skip, 00h=v5.4 fall through
	LD	(CBS_V5354_VARIANT_JUMP+1),A

	INC	HL			; skip header byte
	INC	HL			; skip packed length low
	INC	HL			; skip packed length high

	LD	BC,FFFFh			; default offset = -1
	PUSH	BC
	INC	BC			; BC = 0; gamma entry increments C
	LD	A,80h			; control-bit sentinel

CBS_V5354_LITERALS:
	CALL	CBS_V5354_ELIAS
	LDIR
	CALL	CBS_V5354_GET_BIT			; previous offset or new offset?
	JR	C,CBS_V5354_NEW_OFFSET
	CALL	CBS_V5354_ELIAS

CBS_V5354_COPY:
	EX	(SP),HL			; preserve packed HL, restore offset
	PUSH	HL			; preserve offset across LDIR
	ADD	HL,DE			; match source = destination + offset
	LDIR
	POP	HL
	EX	(SP),HL			; preserve offset, restore packed HL
	CALL	CBS_V5354_GET_BIT			; literals or new offset?
	JR	NC,CBS_V5354_LITERALS

CBS_V5354_NEW_OFFSET:
	POP	BC			; discard previous offset

; The selector reader is exactly eight bytes. The patched JR displacement is
; 8 for v5.3 and 0 for v5.4.
CBS_V5354_VARIANT_JUMP:
	JR	CBS_V5354_SELECTOR			; assembled displacement = 0

CBS_V5354_SELECTOR:
	CALL	CBS_V5354_GET_BIT
	JR	C,CBS_V5354_GENERAL_OFFSET
	JP	CBS_V5354_NEAR_OFFSET

CBS_V5354_GENERAL_OFFSET:
	LD	C,FEh			; seed complemented offset gamma
	CALL	CBS_V5354_ELIAS_LOOP
	INC	C
	RET	Z			; impossible high value 256 = EOS
	LD	B,C
	LD	C,(HL)			; packed low seven bits + length bit
	INC	HL
	RR	B
	RR	C			; Carry = first length control bit
	PUSH	BC			; preserve new negative offset
	LD	BC,0001h
	CALL	NC,CBS_V5354_ELIAS_BACKTRACK
	INC	BC			; new-offset matches have length 2+
	JR	CBS_V5354_COPY

CBS_V5354_NEAR_OFFSET:
	LD	C,FEh
	CALL	CBS_V5354_ELIAS_LOOP
	INC	C			; C = negative distance 1-15
	LD	B,FFh
	PUSH	BC
	LD	BC,0000h
	CALL	CBS_V5354_ELIAS
	INC	BC			; match length = gamma + 1
	JR	CBS_V5354_COPY

CBS_V5354_ELIAS:
	INC	C
	JR	CBS_V5354_ELIAS_LOOP

CBS_V5354_GET_BIT:
	ADD	A,A
	RET	NZ
	LD	A,(HL)
	INC	HL
	RLA
	RET

CBS_V5354_ELIAS_LOOP:
	ADD	A,A
	JR	NZ,CBS_V5354_ELIAS_SKIP
	LD	A,(HL)
	INC	HL
	RLA

CBS_V5354_ELIAS_SKIP:
	RET	C

CBS_V5354_ELIAS_BACKTRACK:
	ADD	A,A
	JR	NZ,CBS_V5354_ELIAS_BACKTRACK_READY
	LD	A,(HL)
	INC	HL
	RLA
CBS_V5354_ELIAS_BACKTRACK_READY:
	RL	C
	RL	B
	JR	CBS_V5354_ELIAS_LOOP
