; =============================================================================
; CBS Cruncher v5.6 / CBM1 - compact Screen 5 four-direction decoder v0.2
; =============================================================================
; Decodes rows top-down/bottom-up or columns left-to-right/right-to-left, then
; reconstructs exact and X/Y-mirrored rectangles in the destination itself.
; It uses no spare VRAM page and only the 32-byte horizontal-mirror row buffer.

	ORG	E000h

; Fixed decoder routing thresholds. They are not calculated from the image list.
; Offset-1 runs of 64+ bytes use the VDP fill path; matches of 1-8 bytes use
; the small CPU scratch path, while larger matches use VDP-to-VDP copying.
CBS_VD_FAST_FILL_MIN:		EQU	40h
CBS_VD_TINY_MATCH_MAX:		EQU	08h

; Public entry table:
;   E000h / CBS_INIT_MSX    call exactly once after copying the decoder to RAM
;   E003h / CBS_INIT_SCREEN A = 5, 6, 7 or 8; call when Screen mode changes
;   E006h / CBS_DECRUNCH    v5.6 CBM1 Screen 5 image decrunch entry
;   E009h / CBS_WAIT_VDP    wait until the current VDP command finishes
;
; CBS_INIT_MSX reads VDP.DR/VDP.DW from BIOS addresses 0006h/0007h and patches
; all immediate I/O operands. CBS_INIT_SCREEN patches the pixel, line and page
; shifts. Neither initializer is executed inside the token or match loops.
CBS_INIT_MSX:
	JP		CBS_VD_INIT_MSX
CBS_INIT_SCREEN:
	SUB	05h
	RET
CBS_DECRUNCH:
	JP		CBM_MIRROR_DECRUNCH
CBS_WAIT_VDP:
	JP		CBS_VD_WAIT_COMMAND

CBS_V5354_DIRECT_VRAM:
	LD	IY,0000h			; zero means an ordinary one-strip command
	LD	(CBS_VD_CURRENT_Y),IY
	LD	(CBS_VD_COLUMN),IY

	INC	HL			; skip C5h route byte
	LD	A,(HL)
	INC	HL
	PUSH	HL			; preserve height-byte address
	LD	L,A
	LD	H,00h
	OR	A
	JR		NZ,CBS_VD_WIDTH_READY
	INC	H			; zero logical width means 256 bytes
CBS_VD_WIDTH_READY:
	LD	(CBS_VD_WIDTH),HL	; bytes in one vertical packed-byte column
	POP	HL
	LD	A,(HL)			; logical column count (packed image width)
	INC	HL			; HL = payload
	PUSH	HL			; preserve packed payload pointer
	LD	L,A
	LD	H,00h
	OR	A
	JR		NZ,CBS_VD_ROWS_READY
	INC	H
CBS_VD_ROWS_READY:
	LD	(CBS_VD_LOGICAL_ROWS),HL
	POP	HL
	XOR	A
	LD	(CBS_VD_COMMAND_ACTIVE),A
	LD	BC,FFFFh
	CALL	CBS_VD_STORE_OFFSET
	LD	A,80h
	EI
	JP		CBS_VD_ROW_START

CBS_VD_ROW_START:
	CALL	CBS_VD_ELIAS
	JR		CBS_VD_ROW_COPY_LITERALS

CBS_VD_ROW_AFTER_LITERAL:
	CALL	CBS_VD_GET_BIT
	JR		C,CBS_VD_ROW_NEW_OFFSET
	CALL	CBS_VD_ELIAS
	CALL	CBS_VD_IS_LENGTH_ONE
	JR		NC,CBS_VD_ROW_COPY_MATCH
	CALL	CBS_VD_GET_BIT
	JP		C,CBS_VD_FINISHED
	LD	BC,0001h
	JR		CBS_VD_ROW_COPY_MATCH

CBS_VD_ROW_AFTER_MATCH:
	CALL	CBS_VD_GET_BIT
	JR		C,CBS_VD_ROW_NEW_OFFSET
	CALL	CBS_VD_ELIAS
	CALL	CBS_VD_IS_LENGTH_ONE
	JR		NC,CBS_VD_ROW_COPY_LITERALS
	CALL	CBS_VD_GET_BIT
	JP		C,CBS_VD_FINISHED
	LD	BC,0001h

CBS_VD_ROW_COPY_LITERALS:
	LD	(CBS_VD_REMAIN),BC
	CALL	CBS_VD_WRITE_LITERALS
	JR		CBS_VD_ROW_AFTER_LITERAL

CBS_VD_ROW_COPY_MATCH:
	LD	(CBS_VD_REMAIN),BC
	PUSH	AF
	PUSH	HL
CBS_VD_ROW_MATCH_CALL:
	CALL	CBS_VD_COPY_MATCH_LOOP
	POP	HL
	POP	AF
	JR		CBS_VD_ROW_AFTER_MATCH

CBS_VD_ROW_NEW_OFFSET:
	CALL	CBS_VD_GET_BIT
	JR		C,CBS_VD_ROW_GENERAL_OFFSET

	LD	BC,FFFEh
	CALL	CBS_VD_ELIAS_LOOP
	INC	BC			; negative whole-line distance
	PUSH	AF
	PUSH	HL
	LD	HL,0000h
	OR	A
	SBC	HL,BC
	LD	(CBS_VD_OFFSET_ROWS),HL
	LD	HL,0000h
	LD	(CBS_VD_OFFSET_COLUMNS),HL
	LD	DE,(CBS_VD_WIDTH)
	LD	HL,0000h
	LD	A,10h
CBS_VD_ROW_MULTIPLY:
	ADD	HL,HL
	SLA	C
	RL	B
	JR		NC,CBS_VD_ROW_MULTIPLY_SKIP
	ADD	HL,DE
CBS_VD_ROW_MULTIPLY_SKIP:
	DEC	A
	JR		NZ,CBS_VD_ROW_MULTIPLY
	LD	B,H
	LD	C,L			; negative byte distance
	POP	HL
	POP	AF
	PUSH	HL
	PUSH	AF
	CALL	CBS_VD_SELECT_MATCH_ROUTE
	POP	AF
	POP	HL
	CALL	CBS_VD_ELIAS
	INC	BC
	JR		CBS_VD_ROW_COPY_MATCH

CBS_VD_ROW_GENERAL_OFFSET:
	LD	C,FEh
	CALL	CBS_VD_ELIAS_LOOP
	INC	C
	LD	B,C
	LD	C,(HL)
	INC	HL
	RR	B
	RR	C
	CALL	CBS_VD_STORE_OFFSET
	LD	BC,0001h
	CALL	NC,CBS_VD_ELIAS_BACKTRACK
	INC	BC
	JR		CBS_VD_ROW_COPY_MATCH

CBS_VD_IS_LENGTH_ONE:
	PUSH	AF
	LD	A,B
	OR	A
	JR		NZ,CBS_VD_LENGTH_NOT_ONE
	LD	A,C
	DEC	A
	JR		NZ,CBS_VD_LENGTH_NOT_ONE
	POP	AF
	SCF
	RET
CBS_VD_LENGTH_NOT_ONE:
	POP	AF
	OR	A
	RET

CBS_VD_FINISHED:
; -----------------------------------------------------------------------------
; Only another VDP command or decoder exit must wait for CE to clear. Literal
; output through port 98h may continue while an earlier HMMM is active.
; -----------------------------------------------------------------------------
CBS_VD_WAIT_IF_COMMAND:
	LD	A,00h
CBS_VD_COMMAND_ACTIVE:		EQU	$ - 1
	OR	A
	RET	Z

CBS_VD_WAIT_COMMAND:
	LD	A,(0007h)		; BIOS VDP write base port
	INC	A
	LD	C,A			; C = VDP register/status port
CBS_VD_WAIT_PUBLIC_LOOP:
	DI
	LD	A,02h
	OUT	(C),A
	LD	A,8Fh
	OUT	(C),A			; select status register 2
	IN	A,(C)
	RRCA				; Carry = CE
	LD	A,00h			; preserve Carry while restoring R15
	OUT	(C),A
	LD	A,8Fh
	EI				; takes effect after the following OUT
	OUT	(C),A			; restore status register 0 atomically
	JR		C,CBS_VD_WAIT_PUBLIC_LOOP
	XOR	A
	LD	(CBS_VD_COMMAND_ACTIVE),A
	RET

; -----------------------------------------------------------------------------
; Store a negative byte offset in BC. General offsets are divided once into a
; positive row distance and column distance. AF, including the length backtrack
; Carry, is preserved.
; -----------------------------------------------------------------------------
CBS_VD_STORE_OFFSET:
	PUSH	HL			; preserve packed-stream pointer
	PUSH	AF
	CALL	CBS_VD_SELECT_MATCH_ROUTE
	LD	HL,0000h
	OR	A
	SBC	HL,BC
	LD	DE,(CBS_VD_WIDTH)
	LD	BC,0000h
	LD	A,10h

; Divide the positive distance in HL by the positive packed width in DE.
; Quotient rows remain in HL and remainder columns in BC.
CBS_VD_DIVIDE_LOOP:
	ADD	HL,HL
	RL	C
	RL	B
	PUSH	HL
	LD	H,B
	LD	L,C
	OR	A
	SBC	HL,DE
	JR		C,CBS_VD_DIVIDE_RESTORE
	LD	B,H
	LD	C,L
	POP	HL
	INC	L
	JR		CBS_VD_DIVIDE_NEXT

CBS_VD_DIVIDE_RESTORE:
	POP	HL

CBS_VD_DIVIDE_NEXT:
	DEC	A
	JR		NZ,CBS_VD_DIVIDE_LOOP
	LD	(CBS_VD_OFFSET_ROWS),HL
	LD	H,B
	LD	L,C
	LD	(CBS_VD_OFFSET_COLUMNS),HL
	POP	AF
	POP	HL
	RET

; Select the one-byte repeat shortcut directly from signed BC. This removes
; the old positive-distance word from permanent decoder RAM.
CBS_VD_SELECT_MATCH_ROUTE:
	PUSH	BC
	INC	BC			; FFFFh (-1) becomes zero
	LD	A,B
	OR	C
	LD	DE,CBS_VD_COPY_OFFSET1
	JR		Z,CBS_VD_STORE_MATCH_ROUTE
	LD	DE,CBS_VD_COPY_MATCH_LOOP
CBS_VD_STORE_MATCH_ROUTE:
	LD	(CBS_VD_ROW_MATCH_CALL+1),DE
	POP	BC
	RET

; -----------------------------------------------------------------------------
; Emit a literal run through port 98h. Each transfer ends at a destination row
; boundary.
; -----------------------------------------------------------------------------

CBS_VD_WRITE_LITERALS:
	PUSH	AF
CBS_VD_LITERAL_LOOP:
	PUSH	HL			; packed payload pointer
	CALL	CBS_VD_CHOOSE_DEST_CHUNK
	CALL	CBS_VD_WAIT_IF_COMMAND
	DI
	LD	A,24h
	OUT	(99h),A
CBM_CONTROL_PORT_0:	EQU	$ - 1
	LD	A,91h
	OUT	(99h),A
CBM_CONTROL_PORT_1:	EQU	$ - 1
	LD	A,9Bh
CBM_PORT_COMMAND:	EQU	$ - 1
	LD	C,A
	CALL	CBS_VD_SET_DEST_LOGICAL
	LD	HL,0000h
CBM_DEST_X:		EQU	$ - 2
	OUT	(C),L			; R36 DX low
	OUT	(C),H			; R37 DX high
	LD	HL,0000h
CBM_DEST_Y:		EQU	$ - 2
	OUT	(C),L			; R38 DY low
	OUT	(C),H			; R39 DY high
	CALL	CBS_VD_COMMAND_SIZE
	OUT	(C),L			; R40 NX low
	OUT	(C),H			; R41 NX high
	OUT	(C),E			; R42 NY low
	OUT	(C),D			; R43 NY high
	POP	HL
	LD	A,(HL)
	INC	HL
	OUT	(C),A			; R44 first packed byte
	XOR	A
	OUT	(C),A			; R45 forward
	LD	A,F0h
	OUT	(C),A			; R46 HMMC
	LD	A,01h
	LD	(CBS_VD_COMMAND_ACTIVE),A

	PUSH	IX
	POP	DE
	DEC	DE
	LD	A,D
	OR	E
	JR		NZ,CBS_VD_LITERAL_MORE
	EI
	JR		CBS_VD_LITERAL_ADVANCE
CBS_VD_LITERAL_MORE:
	LD	A,ACh			; R44, no indirect increment
	OUT	(99h),A
CBM_CONTROL_PORT_2:	EQU	$ - 1
	LD	A,91h
	OUT	(99h),A
CBM_CONTROL_PORT_3:	EQU	$ - 1
	LD	A,(CBM_PORT_COMMAND)
	LD	C,A
	EI
CBS_VD_LITERAL_TRANSFER:
	LD	A,(HL)
	INC	HL
	OUT	(C),A
	DEC	DE
	LD	A,D
	OR	E
	JR		NZ,CBS_VD_LITERAL_TRANSFER
CBS_VD_LITERAL_ADVANCE:
	PUSH	HL			; preserve packed payload pointer
	CALL	CBS_VD_ADVANCE
	CALL	CBS_VD_SUBTRACT_CHUNK
	POP	HL			; POP preserves the subtract result flags
	JR		NZ,CBS_VD_LITERAL_LOOP
	POP	AF
	RET

; HL = logical stream row, DE = byte within that row. Return physical X in
; HL and physical Y in DE for all four CBM1 traversal orders.
CBS_VD_LOGICAL_TO_XY:
	LD	A,00h
CBS_VD_SCAN_ORDER:	EQU	$ - 1
	CP	02h
	JR		NC,CBS_VD_LOGICAL_VERTICAL

	PUSH	HL			; keep logical row while X uses the column
	LD	H,D
	LD	L,E
	ADD	HL,HL
	LD	DE,0000h
CBM_BASE_X:		EQU	$ - 2
	ADD	HL,DE
	EX	(SP),HL			; stack = physical X, HL = logical row
	LD	A,(CBS_VD_SCAN_ORDER)
	OR	A
	JR		Z,CBS_VD_LOGICAL_HORIZONTAL_Y
	EX	DE,HL
	LD	HL,0000h
CBS_VD_LOGICAL_ROWS:	EQU	$ - 2
	DEC	HL
	OR	A
	SBC	HL,DE
CBS_VD_LOGICAL_HORIZONTAL_Y:
	LD	DE,0000h
CBM_BASE_Y:		EQU	$ - 2
	ADD	HL,DE
	POP	DE			; DE = physical X
	EX	DE,HL			; HL = X, DE = Y
	RET

CBS_VD_LOGICAL_VERTICAL:
	PUSH	DE			; keep logical column while X uses the row
	CP	03h
	JR		NZ,CBS_VD_LOGICAL_VERTICAL_X
	EX	DE,HL
	LD	HL,(CBS_VD_LOGICAL_ROWS)
	DEC	HL
	OR	A
	SBC	HL,DE
CBS_VD_LOGICAL_VERTICAL_X:
	ADD	HL,HL
	LD	DE,(CBM_BASE_X)
	ADD	HL,DE
	EX	(SP),HL			; stack = physical X, HL = logical column
	LD	DE,(CBM_BASE_Y)
	ADD	HL,DE
	POP	DE
	EX	DE,HL
	RET

CBS_VD_SET_SOURCE_LOGICAL:
	CALL	CBS_VD_LOGICAL_TO_XY
	LD	(CBM_SOURCE_X),HL
	EX	DE,HL
	LD	(CBM_SOURCE_Y),HL
	RET

CBS_VD_SET_DEST_LOGICAL:
	LD	HL,(CBS_VD_CURRENT_Y)
	LD	DE,(CBS_VD_COLUMN)
	CALL	CBS_VD_LOGICAL_TO_XY
	LD	(CBM_DEST_X),HL
	EX	DE,HL
	LD	(CBM_DEST_Y),HL
	RET

; Return command NX in HL and NY in DE. Horizontal streams advance packed
; bytes across X; vertical streams advance them down Y.
CBS_VD_COMMAND_SIZE:
	PUSH	IY
	POP	HL
	LD	A,H
	OR	L
	JR		Z,CBS_VD_COMMAND_SIZE_STRIP
	LD	A,(CBS_VD_SCAN_ORDER)
	CP	02h
	JR		NC,CBS_VD_COMMAND_RECT_VERTICAL
	EX	DE,HL
	LD	HL,(CBS_VD_WIDTH)
	ADD	HL,HL
	RET
CBS_VD_COMMAND_RECT_VERTICAL:
	ADD	HL,HL
	LD	DE,(CBS_VD_WIDTH)
	RET
CBS_VD_COMMAND_SIZE_STRIP:
	PUSH	IX
	POP	HL
	LD	A,(CBS_VD_SCAN_ORDER)
	CP	02h
	JR		NC,CBS_VD_COMMAND_SIZE_VERTICAL
	ADD	HL,HL
	LD	DE,0001h
	RET
CBS_VD_COMMAND_SIZE_VERTICAL:
	EX	DE,HL
	LD	HL,0002h
	RET

CBS_VD_COMMAND_ARG:
	PUSH	IY
	POP	HL
	LD	A,H
	OR	L
	RET	Z
	LD	A,(CBS_VD_SCAN_ORDER)
	DEC	A
	JR		NZ,CBS_VD_COMMAND_ARG_X
	LD	A,08h
	RET
CBS_VD_COMMAND_ARG_X:
	SUB	02h
	JR		Z,CBS_VD_COMMAND_ARG_NEGATIVE_X
	XOR	A
	RET
CBS_VD_COMMAND_ARG_NEGATIVE_X:
	LD	A,04h
	RET

CBS_VD_COPY_OFFSET1:
	CALL	CBS_VD_WAIT_IF_COMMAND
	LD	DE,(CBS_VD_COLUMN)
	LD	A,D
	OR	E
	JR		NZ,CBS_VD_OFFSET1_SAME_ROW
	LD	HL,(CBS_VD_CURRENT_Y)
	DEC	HL
	LD	DE,(CBS_VD_WIDTH)
	DEC	DE
	JR		CBS_VD_OFFSET1_SOURCE_READY
CBS_VD_OFFSET1_SAME_ROW:
	DEC	DE
	LD	HL,(CBS_VD_CURRENT_Y)
CBS_VD_OFFSET1_SOURCE_READY:
	CALL	CBS_VD_SET_SOURCE_LOGICAL
	CALL	CBM_SET_SOURCE_READ
	IN	A,(98h)
CBM_PORT_READ:		EQU	$ - 1
	LD	(CBS_VD_FILL_COLOR_LOAD+1),A
CBS_VD_OFFSET1_FILL_LOOP:
	CALL	CBS_VD_TRY_FULL_FILL_ROWS
	JR		C,CBS_VD_OFFSET1_FILL_RECTANGLE
	CALL	CBS_VD_CHOOSE_DEST_CHUNK
	CALL	CBS_VD_START_HMMV
	CALL	CBS_VD_ADVANCE
	CALL	CBS_VD_SUBTRACT_CHUNK
	JR		NZ,CBS_VD_OFFSET1_FILL_LOOP
	RET
CBS_VD_OFFSET1_FILL_RECTANGLE:
	CALL	CBS_VD_START_HMMV
	LD	HL,(CBS_VD_CURRENT_Y)
	PUSH	IY
	POP	DE
	ADD	HL,DE
	LD	(CBS_VD_CURRENT_Y),HL
	CALL	CBS_VD_SUBTRACT_CHUNK
	LD	IY,0000h
	JR		NZ,CBS_VD_OFFSET1_FILL_LOOP
	RET

CBS_VD_TRY_FULL_FILL_ROWS:
	LD	HL,(CBS_VD_COLUMN)
	LD	A,H
	OR	L
	RET	NZ
	LD	HL,(CBS_VD_REMAIN)
	LD	DE,(CBS_VD_WIDTH)
	LD	BC,0000h
CBS_VD_FILL_RECT_COUNT_LOOP:
	OR	A
	SBC	HL,DE
	JR		C,CBS_VD_FILL_RECT_COUNT_RESTORE
	INC	BC
	JR		CBS_VD_FILL_RECT_COUNT_LOOP
CBS_VD_FILL_RECT_COUNT_RESTORE:
	ADD	HL,DE
	LD	A,B
	OR	C
	RET	Z
	PUSH	BC
	POP	IY
	EX	DE,HL
	LD	HL,(CBS_VD_REMAIN)
	OR	A
	SBC	HL,DE
	PUSH	HL
	POP	IX
	SCF
	RET

CBS_VD_COPY_MATCH_LOOP:
	CALL	CBS_VD_TRY_FULL_MATCH_ROWS
	JR		C,CBS_VD_MATCH_RECTANGLE
	LD	HL,(CBS_VD_COLUMN)
	LD	DE,0000h
CBS_VD_OFFSET_COLUMNS:		EQU	$ - 2
	LD	BC,0000h
	OR	A
	SBC	HL,DE
	JR		NC,CBS_VD_SOURCE_COLUMN_READY
	LD	DE,0000h
CBS_VD_WIDTH:			EQU	$ - 2
	ADD	HL,DE
	INC	BC
CBS_VD_SOURCE_COLUMN_READY:
	PUSH	HL			; retain source column without a RAM spill
	LD	HL,(CBS_VD_CURRENT_Y)
	LD	DE,0000h
CBS_VD_OFFSET_ROWS:		EQU	$ - 2
	OR	A
	SBC	HL,DE
	OR	A
	SBC	HL,BC
	POP	DE
	PUSH	DE
	CALL	CBS_VD_SET_SOURCE_LOGICAL

	CALL	CBS_VD_CHOOSE_DEST_CHUNK
	LD	HL,(CBS_VD_WIDTH)
	POP	DE
	OR	A
	SBC	HL,DE
	PUSH	IX
	POP	DE
	CALL	CBS_VD_MIN_HL_DE
	PUSH	HL
	POP	IX
	CALL	CBS_VD_START_HMMM
CBS_VD_MATCH_ADVANCE:
	CALL	CBS_VD_ADVANCE
CBS_VD_MATCH_SUBTRACT:
	CALL	CBS_VD_SUBTRACT_CHUNK
	JP		NZ,CBS_VD_COPY_MATCH_LOOP
	RET

CBS_VD_MATCH_RECTANGLE:
	CALL	CBS_VD_START_HMMM
	LD	HL,(CBS_VD_CURRENT_Y)
	PUSH	IY
	POP	DE
	ADD	HL,DE
	LD	(CBS_VD_CURRENT_Y),HL
	CALL	CBS_VD_SUBTRACT_CHUNK
	LD	IY,0000h
	JP		NZ,CBS_VD_COPY_MATCH_LOOP
	RET

; Combine complete aligned logical rows while staying within the already
; expanded source-row distance. Carry returns one safe HMMM rectangle.
CBS_VD_TRY_FULL_MATCH_ROWS:
	LD	HL,(CBS_VD_COLUMN)
	LD	A,H
	OR	L
	RET	NZ
	LD	HL,(CBS_VD_OFFSET_COLUMNS)
	LD	A,H
	OR	L
	RET	NZ
	LD	HL,(CBS_VD_OFFSET_ROWS)
	LD	A,H
	OR	L
	RET	Z
	LD	HL,(CBS_VD_REMAIN)
	LD	DE,(CBS_VD_WIDTH)
	LD	BC,0000h
CBS_VD_RECT_COUNT_LOOP:
	OR	A
	SBC	HL,DE
	JR		C,CBS_VD_RECT_COUNT_RESTORE
	INC	BC
	PUSH	HL
	LD	HL,(CBS_VD_OFFSET_ROWS)
	OR	A
	SBC	HL,BC
	POP	HL
	JR		Z,CBS_VD_RECT_COUNT_READY
	JR		CBS_VD_RECT_COUNT_LOOP
CBS_VD_RECT_COUNT_RESTORE:
	ADD	HL,DE
CBS_VD_RECT_COUNT_READY:
	LD	A,B
	OR	C
	RET	Z
	PUSH	BC
	POP	IY
	EX	DE,HL
	LD	HL,(CBS_VD_REMAIN)
	OR	A
	SBC	HL,DE
	PUSH	HL
	POP	IX
	LD	HL,(CBS_VD_CURRENT_Y)
	LD	DE,(CBS_VD_OFFSET_ROWS)
	OR	A
	SBC	HL,DE
	LD	DE,0000h
	CALL	CBS_VD_SET_SOURCE_LOGICAL
	SCF
	RET

CBS_VD_CHOOSE_DEST_CHUNK:
	LD	HL,(CBS_VD_WIDTH)
	LD	DE,0000h
CBS_VD_COLUMN:			EQU	$ - 2
	OR	A
	SBC	HL,DE
	LD	DE,0000h
CBS_VD_REMAIN:			EQU	$ - 2
	CALL	CBS_VD_MIN_HL_DE
	PUSH	HL
	POP	IX
	RET

; Return min(HL,DE) in HL.
CBS_VD_MIN_HL_DE:
	PUSH	HL
	OR	A
	SBC	HL,DE
	POP	HL
	RET	C
	RET	Z
	EX	DE,HL
	RET

CBS_VD_SUBTRACT_CHUNK:
	LD	HL,(CBS_VD_REMAIN)
	PUSH	IX
	POP	DE
	OR	A
	SBC	HL,DE
	LD	(CBS_VD_REMAIN),HL
	LD	A,H
	OR	L
	RET

; Advance the packed destination coordinate.
CBS_VD_ADVANCE:
	LD	HL,(CBS_VD_COLUMN)
	PUSH	IX
	POP	DE
	ADD	HL,DE
	LD	(CBS_VD_COLUMN),HL
	LD	DE,(CBS_VD_WIDTH)
	OR	A
	SBC	HL,DE
	RET	NZ

	LD	HL,0000h
	LD	(CBS_VD_COLUMN),HL
	LD	HL,0000h
CBS_VD_CURRENT_Y:		EQU	$ - 2
	INC	HL
	LD	(CBS_VD_CURRENT_Y),HL
	RET

; -----------------------------------------------------------------------------
; Start HMMV for the destination rectangle held in two immediate operands.
; R17 starts at R36, so only DX, DY, NX and NY are emitted before R44-R46.
; -----------------------------------------------------------------------------
CBS_VD_START_HMMV:
	CALL	CBS_VD_WAIT_IF_COMMAND
	CALL	CBS_VD_SET_DEST_LOGICAL
	DI
	LD	A,24h
	OUT	(99h),A
CBM_CONTROL_PORT_4:	EQU	$ - 1
	LD	A,91h
	OUT	(99h),A
CBM_CONTROL_PORT_5:	EQU	$ - 1
	LD	A,(CBM_PORT_COMMAND)
	LD	C,A
	LD	HL,(CBM_DEST_X)
	OUT	(C),L
	OUT	(C),H
	LD	HL,(CBM_DEST_Y)
	OUT	(C),L
	OUT	(C),H
	CALL	CBS_VD_COMMAND_SIZE
	OUT	(C),L
	OUT	(C),H
	OUT	(C),E
	OUT	(C),D
CBS_VD_FILL_COLOR_LOAD:
	LD	A,00h
	OUT	(C),A
	CALL	CBS_VD_COMMAND_ARG
	OUT	(C),A
	LD	A,C0h
	JR		CBS_VD_COMMAND_ACTIVE_START

CBS_VD_START_HMMM:
	CALL	CBS_VD_WAIT_IF_COMMAND
	CALL	CBS_VD_SET_DEST_LOGICAL
	DI
	LD	A,20h
	OUT	(99h),A
CBM_CONTROL_PORT_6:	EQU	$ - 1
	LD	A,91h
	OUT	(99h),A
CBM_CONTROL_PORT_7:	EQU	$ - 1
	LD	A,(CBM_PORT_COMMAND)
	LD	C,A
	LD	HL,0000h
CBM_SOURCE_X:		EQU	$ - 2
	OUT	(C),L
	OUT	(C),H
	LD	HL,0000h
CBM_SOURCE_Y:		EQU	$ - 2
	OUT	(C),L
	OUT	(C),H
	LD	HL,(CBM_DEST_X)
	OUT	(C),L
	OUT	(C),H
	LD	HL,(CBM_DEST_Y)
	OUT	(C),L
	OUT	(C),H
	CALL	CBS_VD_COMMAND_SIZE
	OUT	(C),L
	OUT	(C),H
	OUT	(C),E
	OUT	(C),D
	XOR	A
	OUT	(C),A
	CALL	CBS_VD_COMMAND_ARG
	OUT	(C),A
	LD	A,D0h
CBS_VD_COMMAND_ACTIVE_START:
	OUT	(C),A
	LD	A,01h
	LD	(CBS_VD_COMMAND_ACTIVE),A
	EI
	RET

CBS_VD_SHIFT_HL:
	LD	A,B
	OR	A
	RET	Z
	ADD	HL,HL
	DEC	B
	JR		CBS_VD_SHIFT_HL

; -----------------------------------------------------------------------------
; CPU VRAM write address for the current destination packed byte coordinate.
; -----------------------------------------------------------------------------


CBS_VD_ELIAS:
	LD	BC,0001h			; VDP writers do not leave BC at zero
	JR		CBS_VD_ELIAS_LOOP

CBS_VD_GET_BIT:
	ADD	A,A
	RET	NZ
	LD	A,(HL)
	INC	HL
	RLA
	RET

CBS_VD_ELIAS_LOOP:
	ADD	A,A
	JR		NZ,CBS_VD_ELIAS_SKIP
	LD	A,(HL)
	INC	HL
	RLA

CBS_VD_ELIAS_SKIP:
	RET	C

CBS_VD_ELIAS_BACKTRACK:
	ADD	A,A
	JR		NZ,CBS_VD_ELIAS_BACKTRACK_READY
	LD	A,(HL)
	INC	HL
	RLA
CBS_VD_ELIAS_BACKTRACK_READY:
	RL	C
	RL	B
	JR		CBS_VD_ELIAS_LOOP

; A = Screen mode 5-8. The trusted caller supplies a valid mode. Each table row
; is pixel shift, VRAM line shift and page-bank shift respectively.

; -----------------------------------------------------------------------------
; One-time machine initializer. VDP.DW at 0007h supplies the write base port;
; VDP.DR at 0006h independently supplies the read base port. Call CBS_INIT_MSX
; once before the first decrunch. It remains intact after every image.
; -----------------------------------------------------------------------------

CBM_MIRROR_DECRUNCH:
	LD	(CBM_WRAPPER),HL
	LD	(CBM_DEST_PACKED),DE
	LD	(CBM_DEST_FLAGS),A
	AND	03h
	JP		NZ,CBM_UNSUPPORTED
	LD	A,D
	ADD	A,A
	LD	(CBM_BASE_X),A
	XOR	A
	LD	(CBM_BASE_X+1),A
	LD	A,E
	LD	(CBM_BASE_Y),A
	LD	A,00h
CBM_DEST_FLAGS:	EQU	$ - 1
	AND	Ch
	RRCA
	RRCA
	LD	(CBM_BASE_Y+1),A

	LD	HL,0000h
CBM_WRAPPER:		EQU	$ - 2
	LD	DE,0004h
	ADD	HL,DE
	LD	A,(HL)
	CP	05h
	JP		NZ,CBM_UNSUPPORTED
	INC	HL
	LD	A,(HL)
	OR	A
	JP		Z,CBM_UNSUPPORTED
	CP	21h
	JP		NC,CBM_UNSUPPORTED
	LD	(CBM_TILE_COLUMNS),A
	INC	HL
	LD	A,(HL)
	OR	A
	JP		Z,CBM_UNSUPPORTED
	CP	21h
	JP		NC,CBM_UNSUPPORTED
	LD	(CBM_TILE_ROWS),A
	INC	HL
	LD	A,(HL)
	CP	04h
	JP		NC,CBM_UNSUPPORTED
	LD	(CBS_VD_SCAN_ORDER),A

	LD	A,00h
CBM_TILE_COLUMNS:	EQU	$ - 1
	LD	E,A
	LD	D,00h
	LD	B,00h
CBM_TILE_ROWS:		EQU	$ - 1
	LD	HL,0000h
CBM_TOTAL_CELLS_LOOP:
	ADD	HL,DE
	DJNZ	CBM_TOTAL_CELLS_LOOP
	LD	(CBM_TOTAL_CELLS),HL

	LD	HL,(CBM_WRAPPER)
	LD	DE,000Ch
	ADD	HL,DE
	LD	DE,0000h
CBM_DEST_PACKED:	EQU	$ - 2
	LD	A,(CBM_DEST_FLAGS)
	CALL	CBS_V5354_DIRECT_VRAM
	CALL	CBS_VD_WAIT_COMMAND

	LD	HL,(CBM_WRAPPER)
	LD	DE,0008h
	ADD	HL,DE
	LD	E,(HL)
	INC	HL
	LD	D,(HL)
	LD	HL,(CBM_WRAPPER)
	ADD	HL,DE
	LD	DE,000Ch
	ADD	HL,DE
	LD	(CBM_COMMAND_POINTER),HL

CBM_COMMAND_LOOP:
	CALL	CBM_READ_COMMAND
	CP	FFh
	JP		Z,CBM_FINISHED
	LD	(CBM_OPCODE),A
	BIT	7,A
	JP		Z,CBM_UNSUPPORTED
	BIT	6,A
	JR		NZ,CBM_EXTENDED_SHAPE
	AND	Fh
	ADD	A,A
	LD	E,A
	LD	D,00h
	LD	HL,CBM_SHAPES
	ADD	HL,DE
	LD	A,(HL)
	LD	(CBM_WIDTH_CELLS),A
	INC	HL
	LD	A,(HL)
	LD	(CBM_HEIGHT_CELLS),A
	JR		CBM_REFERENCE_FIELDS

CBM_EXTENDED_SHAPE:
	CALL	CBM_READ_COMMAND
	LD	B,A
	RRCA
	RRCA
	RRCA
	RRCA
	AND	Fh
	INC	A
	LD	(CBM_WIDTH_CELLS),A
	LD	A,B
	AND	Fh
	INC	A
	LD	(CBM_HEIGHT_CELLS),A

CBM_REFERENCE_FIELDS:
	CALL	CBM_READ_WORD
	LD	(CBM_DESTINATION_INDEX),DE
	CALL	CBM_READ_WORD
	LD	(CBM_SOURCE_INDEX),DE
	LD	HL,0000h
CBM_DESTINATION_INDEX:	EQU	$ - 2
	CALL	CBM_VALIDATE_INDEX
	JP		NC,CBM_UNSUPPORTED
	LD	HL,0000h
CBM_SOURCE_INDEX:	EQU	$ - 2
	CALL	CBM_VALIDATE_INDEX
	JP		NC,CBM_UNSUPPORTED
	CALL	CBM_PREPARE_RECTANGLE
	LD	A,00h
CBM_OPCODE:		EQU	$ - 1
	AND	30h
	JR		Z,CBM_COPY_EXACT
	CP	20h
	JR		Z,CBM_COPY_VERTICAL
	JP		CBM_COPY_HORIZONTAL	; tail route: do not leak a return address

CBM_READ_WORD:
	CALL	CBM_READ_COMMAND
	LD	E,A
	CALL	CBM_READ_COMMAND
	LD	D,A
	RET

CBM_READ_COMMAND:
	PUSH	HL
	LD	HL,0000h
CBM_COMMAND_POINTER:	EQU	$ - 2
	LD	A,(HL)
	INC	HL
	LD	(CBM_COMMAND_POINTER),HL
	POP	HL
	RET


CBM_COPY_EXACT:
	LD	A,00h
CBM_HEIGHT_PIXELS:	EQU	$ - 1
	LD	(CBM_COPY_HEIGHT),A
	CALL	CBM_START_HMMM
	JP		CBM_COMMAND_LOOP

CBM_COPY_VERTICAL:
	LD	A,(CBM_HEIGHT_PIXELS)
	EXX
	LD	B,A
	EXX
	DEC	A
	LD	E,A
	LD	D,00h
	LD	HL,(CBM_SOURCE_Y)
	ADD	HL,DE
	LD	(CBM_SOURCE_Y),HL
	LD	A,01h
	LD	(CBM_COPY_HEIGHT),A
CBM_VERTICAL_ROW:
	CALL	CBM_START_HMMM
	CALL	CBM_PREVIOUS_SOURCE_ROW
	CALL	CBM_NEXT_DEST_ROW
	EXX
	DEC	B
	EXX
	JR		NZ,CBM_VERTICAL_ROW
	JP		CBM_COMMAND_LOOP

CBM_COPY_HORIZONTAL:
	LD	A,(CBM_HEIGHT_PIXELS)
	EXX
	LD	B,A
	EXX
	LD	A,(CBM_OPCODE)
	AND	20h
	JR		Z,CBM_HORIZONTAL_ROW
	LD	A,(CBM_HEIGHT_PIXELS)
	DEC	A
	LD	E,A
	LD	D,00h
	LD	HL,(CBM_SOURCE_Y)
	ADD	HL,DE
	LD	(CBM_SOURCE_Y),HL
CBM_HORIZONTAL_ROW:
	CALL	CBM_SET_SOURCE_READ
	LD	HL,CBM_ROW_BUFFER
	LD	A,00h
CBM_WIDTH_BYTES:	EQU	$ - 1
	LD	B,A
	LD	A,(CBM_PORT_READ)
	LD	C,A
	INIR
	CALL	CBM_SET_DEST_WRITE
	LD	A,(CBM_WIDTH_BYTES)
	LD	B,A
	LD	E,A
	LD	D,00h
	LD	HL,CBM_ROW_BUFFER
	ADD	HL,DE
	DEC	HL
	LD	A,98h
CBM_PORT_WRITE:	EQU	$ - 1
	LD	C,A
CBM_HORIZONTAL_WRITE:
	LD	A,(HL)
	RRCA
	RRCA
	RRCA
	RRCA			; reverse the two Screen 5 pixels in this byte
	OUT	(C),A
	DEC	HL
	DJNZ	CBM_HORIZONTAL_WRITE
	LD	A,(CBM_OPCODE)
	AND	20h
	JR		Z,CBM_HORIZONTAL_SOURCE_FORWARD
	CALL	CBM_PREVIOUS_SOURCE_ROW
	JR		CBM_HORIZONTAL_DESTINATION
CBM_HORIZONTAL_SOURCE_FORWARD:
	CALL	CBM_NEXT_SOURCE_ROW
CBM_HORIZONTAL_DESTINATION:
	CALL	CBM_NEXT_DEST_ROW
	EXX
	DEC	B
	EXX
	JR		NZ,CBM_HORIZONTAL_ROW
	JP		CBM_COMMAND_LOOP

CBM_FINISHED:
	CALL	CBS_VD_WAIT_COMMAND
	OR	A
	RET
CBM_UNSUPPORTED:
	SCF
	RET

; HL = unsigned destination delta.
CBM_ADD_DESTINATION:
	LD	DE,(CBM_DESTINATION_INDEX)
	ADD	HL,DE
	LD	(CBM_DESTINATION_INDEX),HL
	RET

; HL = tile index. Validate the index and the current rectangle shape against
; both right and bottom edges. Carry set means safe; Carry clear rejects it.
CBM_VALIDATE_INDEX:
	LD	DE,0000h
CBM_TOTAL_CELLS:	EQU	$ - 2
	OR	A
	SBC	HL,DE
	RET	NC
	ADD	HL,DE
	LD	A,(CBM_TILE_COLUMNS)
	LD	E,A
	LD	D,00h
	LD	C,00h
CBM_VALIDATE_INDEX_ROW:
	OR	A
	SBC	HL,DE
	JR		C,CBM_VALIDATE_INDEX_REMAINDER
	INC	C
	JR		CBM_VALIDATE_INDEX_ROW
CBM_VALIDATE_INDEX_REMAINDER:
	ADD	HL,DE
	LD	A,00h
CBM_WIDTH_CELLS:	EQU	$ - 1
	ADD	A,L
	LD	B,A
	LD	A,(CBM_TILE_COLUMNS)
	CP	B
	JR		C,CBM_VALIDATE_INDEX_BAD
	LD	A,00h
CBM_HEIGHT_CELLS:	EQU	$ - 1
	ADD	A,C
	LD	B,A
	LD	A,(CBM_TILE_ROWS)
	CP	B
	JR		C,CBM_VALIDATE_INDEX_BAD
	SCF
	RET
CBM_VALIDATE_INDEX_BAD:
	OR	A
	RET

; CBM1 source and destination tile IDs are 0..1023.
CBM_PREPARE_RECTANGLE:
	LD	HL,(CBM_SOURCE_INDEX)
	CALL	CBM_INDEX_TO_COORDINATE
	PUSH	HL
	EX	DE,HL
	LD	DE,(CBM_BASE_X)
	ADD	HL,DE
	LD	(CBM_SOURCE_X),HL
	POP	HL
	LD	DE,(CBM_BASE_Y)
	ADD	HL,DE
	LD	(CBM_SOURCE_Y),HL
CBM_PREPARE_DESTINATION:
	LD	HL,(CBM_DESTINATION_INDEX)
	CALL	CBM_INDEX_TO_COORDINATE
	PUSH	HL
	EX	DE,HL
	LD	DE,(CBM_BASE_X)
	ADD	HL,DE
	LD	(CBM_DEST_X),HL
	POP	HL
	LD	DE,(CBM_BASE_Y)
	ADD	HL,DE
	LD	(CBM_DEST_Y),HL
	LD	A,(CBM_WIDTH_CELLS)
	ADD	A,A
	ADD	A,A
	LD	(CBM_WIDTH_BYTES),A
	ADD	A,A
	LD	(CBM_WIDTH_PIXELS),A
	LD	A,(CBM_HEIGHT_CELLS)
	ADD	A,A
	ADD	A,A
	ADD	A,A
	LD	(CBM_HEIGHT_PIXELS),A
	RET

; HL=raster tile index. Return DE=X pixels and HL=Y pixels. The stored tile
; column count avoids assuming or deriving a 32-tile-wide screen.
CBM_INDEX_TO_COORDINATE:
	LD	A,(CBM_TILE_COLUMNS)
	LD	E,A
	LD	D,00h
	LD	C,00h			; quotient = tile Y
CBM_INDEX_DIVIDE_ROW:
	OR	A
	SBC	HL,DE
	JR		C,CBM_INDEX_REMAINDER
	INC	C
	JR		CBM_INDEX_DIVIDE_ROW
CBM_INDEX_REMAINDER:
	ADD	HL,DE			; remainder = tile X
	LD	A,L
	ADD	A,A
	ADD	A,A
	ADD	A,A
	LD	E,A
	LD	D,00h
	LD	A,C
	ADD	A,A
	ADD	A,A
	ADD	A,A
	LD	L,A
	LD	H,00h
	RET

CBM_NEXT_SOURCE_ROW:
	LD	HL,(CBM_SOURCE_Y)
	INC	HL
	LD	(CBM_SOURCE_Y),HL
	RET
CBM_PREVIOUS_SOURCE_ROW:
	LD	HL,(CBM_SOURCE_Y)
	DEC	HL
	LD	(CBM_SOURCE_Y),HL
	RET
CBM_NEXT_DEST_ROW:
	LD	HL,(CBM_DEST_Y)
	INC	HL
	LD	(CBM_DEST_Y),HL
	RET

CBM_SET_SOURCE_READ:
	CALL	CBS_VD_WAIT_COMMAND
	DI
	LD	HL,(CBM_SOURCE_Y)
	CALL	CBM_SCREEN5_ROW_ADDRESS
	LD	DE,(CBM_SOURCE_X)
	SRL	D
	RR	E
	ADD	HL,DE
	PUSH	HL
	LD	A,H
	AND	C0h
	RLCA
	RLCA
	OUT	(99h),A
CBM_CONTROL_PORT_8:	EQU	$ - 1
	LD	A,8Eh
	OUT	(99h),A
CBM_CONTROL_PORT_9:	EQU	$ - 1
	POP	HL
	LD	A,L
	OUT	(99h),A
CBM_CONTROL_PORT_10:	EQU	$ - 1
	LD	A,H
	AND	3Fh
	OUT	(99h),A
CBM_CONTROL_PORT_11:	EQU	$ - 1
	EI
	RET

CBM_SET_DEST_WRITE:
	CALL	CBS_VD_WAIT_COMMAND
	DI
	LD	HL,(CBM_DEST_Y)
	CALL	CBM_SCREEN5_ROW_ADDRESS
	LD	DE,(CBM_DEST_X)
	SRL	D
	RR	E
	ADD	HL,DE
	PUSH	HL
	LD	A,H
	AND	C0h
	RLCA
	RLCA
	OUT	(99h),A
CBM_CONTROL_PORT_12:	EQU	$ - 1
	LD	A,8Eh
	OUT	(99h),A
CBM_CONTROL_PORT_13:	EQU	$ - 1
	POP	HL
	LD	A,L
	OUT	(99h),A
CBM_CONTROL_PORT_14:	EQU	$ - 1
	LD	A,H
	AND	3Fh
	OR	40h
	OUT	(99h),A
CBM_CONTROL_PORT_15:	EQU	$ - 1
	EI
	RET

CBM_SCREEN5_ROW_ADDRESS:
	ADD	HL,HL
	ADD	HL,HL
	ADD	HL,HL
	ADD	HL,HL
	ADD	HL,HL
	ADD	HL,HL
	ADD	HL,HL
	RET





CBM_START_HMMM:
	CALL	CBS_VD_WAIT_COMMAND
	DI
	LD	A,20h
	OUT	(99h),A
CBM_CONTROL_PORT_16:	EQU	$ - 1
	LD	A,91h
	OUT	(99h),A
CBM_CONTROL_PORT_17:	EQU	$ - 1
	LD	A,(CBM_PORT_COMMAND)
	LD	C,A
	LD	HL,(CBM_SOURCE_X)
	OUT	(C),L
	OUT	(C),H
	LD	HL,(CBM_SOURCE_Y)
	OUT	(C),L
	OUT	(C),H
	LD	HL,(CBM_DEST_X)
	OUT	(C),L
	OUT	(C),H
	LD	HL,(CBM_DEST_Y)
	OUT	(C),L
	OUT	(C),H
	LD	A,00h
CBM_WIDTH_PIXELS:	EQU	$ - 1
	OUT	(C),A
	XOR	A
	OUT	(C),A
	LD	A,00h
CBM_COPY_HEIGHT:	EQU	$ - 1
	OUT	(C),A
	XOR	A
	OUT	(C),A
	OUT	(C),A			; R44 unused
	OUT	(C),A			; R45 forward
	LD	A,D0h
	OUT	(C),A			; R46 HMMM
	EI
	RET



CBM_SHAPES:
	DB	4,4, 4,2, 2,4, 3,2, 2,3, 2,2, 4,1
	DB	1,4, 3,1, 1,3, 2,1, 1,2, 1,1

CBM_ROW_BUFFER:		DS	20h,00h

CBM_RAM_END:

CBS_VD_INIT_MSX:
	LD	A,(0007h)
	LD	(CBM_PORT_WRITE),A
	INC	A
	LD	HL,CBM_CONTROL_PORT_PATCHES
	LD	B,18
CBM_PATCH_CONTROL_PORT_LOOP:
	LD	E,(HL)
	INC	HL
	LD	D,(HL)
	INC	HL
	LD	(DE),A
	DJNZ	CBM_PATCH_CONTROL_PORT_LOOP
	INC	A
	INC	A
	LD	(CBM_PORT_COMMAND),A
	LD	A,(0006h)
	LD	(CBM_PORT_READ),A
	RET

CBM_CONTROL_PORT_PATCHES:
	DW	CBM_CONTROL_PORT_0,CBM_CONTROL_PORT_1,CBM_CONTROL_PORT_2,CBM_CONTROL_PORT_3,CBM_CONTROL_PORT_4
	DW	CBM_CONTROL_PORT_5,CBM_CONTROL_PORT_6,CBM_CONTROL_PORT_7,CBM_CONTROL_PORT_8,CBM_CONTROL_PORT_9
	DW	CBM_CONTROL_PORT_10,CBM_CONTROL_PORT_11,CBM_CONTROL_PORT_12,CBM_CONTROL_PORT_13,CBM_CONTROL_PORT_14
	DW	CBM_CONTROL_PORT_15,CBM_CONTROL_PORT_16,CBM_CONTROL_PORT_17
