/////////////////////////////////////////////////////////////////
// Busy & Loki // DecLzx02 6502 Universal depacker // 2.9.2021 //
// info lzx@loksa.sk                                           //
/////////////////////////////////////////////////////////////////

.filenamespace declzx02
:BasicUpstart2(mainStartup)

.var hardcore_optim=0
.var start_with_safe_X=true

//----------------------------------------------------------
//   Variables
//----------------------------------------------------------

.label  reg_DE = $04
.label  reg_D = $05
.label  reg_E = $04
.label  reg_HL = $06
.label  reg_H = $07
.label  reg_L = $06
.var    reg_A = $08
.if (hardcore_optim>0) {
	.eval reg_A = $8b
}
.label  reg_BC = $0a
.label  reg_B = $0b
.label  reg_C = $0a
.label  reg_Len_H = $0d
.label  reg_Len_L = $0c
.label  reg_LOOP = $0e
.label  reg_reused_offset_L = $10
.label  reg_reused_offset_H = $11

//----------------------------------------------------------

//----------------------------------------------------------
//   Main Startup Code
//----------------------------------------------------------
mainStartup:
* = mainStartup "Main Startup"
//#if !declzx_user_params // Skip next assigns if all user data are already defined elsewhere

// User settings of data addresses
// (used for data overlay check too)

.label srcadd = packed_data // Begin of source packed data
.label dstadd = $1000       // Begin of destination area for depacked data and entry point

// User setting optimization of depacker

.var lzxspd = 0
// Possible values of lzxspd:
//  0 ... optimized for length - short but slow
//  1+ .. optimized for speed  - but longer code /*not implemented*/

.var size_option1 = true
// set true if sequence length is lower than 0x8100 (good for for 99.99% of uses)
// available only for compression type COMBLX, COMBLC, COMZX9, COMZX8

.var return_after_depack = false
// if true then RTS after depack, otherwise jump to dstadd

.var visual_effect = 0
// 1 ... INC DEC $D020 in copy routine

//#endif
////////////////////////////////////////////////////////////////////////////////////////////////////////

// Setting pack parameters = compression identification and data overlay check
//
// Next setting are generated by LzxPack and depends on used compression.
// There are two possibilities how the setting can be provided:
//  1. Read from *.inc file directly generated by LzxPack with option -i
//  2. Manual setting according to compression identification and parameters

// 1st possibility - read setting from *.inc file:
// It is enough to uncomment following two lines
// with include and define directives and fill
// corresponding file name in this directive:

//;	INCLUDE filename.inc
//;	DEFINE  declzx_pack_params

// 2nd possiblity - manual setting:
// It is needed to set proper values for following 10 labels:
//
//   Setting compression identification -tnXYcNoAoB (mandatory)
//   (If some parameter is not present in identification, it does not matter)

//   IFNDEF declzx_pack_params // If parameters are already defined (i.e. by previous include), this IF skips following definitions

.var revers	=	1	// n/r = Direction: 0 = normal -tn, 1 = reversed -tr
.var typcom	=	3	// X = Compress type: 1=COMBLX 2=COMBLC 3=COMZX9 4=COMZX8 5=COMBS2 6=COMBX1 7=COMBX2 8=COMBX3 9=COMSX1
.var typpos	=	1	// Y = Offset coding: 1=POSOF1 2=POSOF2 3=POSOF3 4=POSOF4 5=(none) 6=POSOV1 7=POSOV2 8=POSOV3 9=POSOV4
.var bytcop	=	3	// N = Number of bits of copied byte - needed for BLX ZX9 BX1 BX2 BX3 SX1
.var ofset1	=	8	// A = Number of bits for 1st offset - needed for OF1 OF2 OF3 OF4 OV2 OV3 OV4
.var ofset2	=	9	// B = Number of bits for 1st offset - needed for OF2 OV3

//   Setting for data overlay check (optional)

.var mindst	=	10	// Minimal distance between block ends of source packed and destination unpacked data
.var maxdct	=	65535	// Maximal length of dictionary alias maximal used offsets (not used in this depacker)
.var pcklen	=	end_packed_data-packed_data	// Length of source packed data
.var totlen	=	1644	// Length of dest unpacked data

.if (cmdLineVars.containsKey("typcom")) {
	.eval typcom= cmdLineVars.get("typcom").asNumber()
}
.if (cmdLineVars.containsKey("typpos")) {
	.eval typpos = cmdLineVars.get("typpos").asNumber()
}
.if (cmdLineVars.containsKey("revers")) {
	.eval revers = cmdLineVars.get("revers").asNumber()
}
.var filename_to_decompress = "testfile"
.if (cmdLineVars.containsKey("addfile")) {
	.eval filename_to_decompress = cmdLineVars.get("addfile")
	.if (filename_to_decompress=="none") {
		.eval filename_to_decompress=""
	}
}

//  ENDIF

////////////////////////////////////////////////////////////////////////////////////////////////////////

// Internal definitions

// Systems for coding unpacked data and sequence lengths (values of label typcom)

.var COMBLX	=	1	// Simple block compression - Elias-gamma length for both sequence and unpacked block (real sequence 1+ length supports compress simple bytes)
.var COMBLC	=	2	// Simple block compression - Elias-gamma length for both sequence and unpacked block (real sequence 2+ length has width more 1 bit: EG+1)
.var COMZX9	=	3	// Similar to ZX7 compression - 1 bit for each unpacked byte and 1 bit + Elias-gamma length for sequence 1+ bytes (supports packing of simple bytes)
.var COMZX8	=	4	// Similar to ZX7 compression - 1 bit for each unpacked byte and 1 bit + Elias-gamma length for sequence 2+ bytes (length EG+1 bit)
.var COMBS2	=	5	// Bitstream 1:unpack byte, 01:seq 2 bytes, 001:seq 3 bytes, 0001:seq 4+ bytes, 00000:1 unpack + rep offset, 00000:unpacked block 10+ bytes
.var COMBX1	=	6	// Bitstream 1:unpack, 01:seq2, 001:seq3, 0001:copied, 00001:seq4+, 000001:unpack + rep.offset, 0000001:copied + rep.offs, 0000000:block 12+
.var COMBX2	=	7	// Bitstream 1:unpack, 01:seq2, 001:copied, 0001:seq3, 00001:seq4+, 000001:unpack + rep.offset, 0000001:copied + rep.offs, 0000000:block 12+
.var COMBX3	=	8	// Bitstream 1:unpack, 01:copied, 001:seq2, 0001:seq3, 00001:seq4+, 000001:unpack + rep.offset, 0000001:copied + rep.offs, 0000000:block 12+
.var COMSX1	=	9	// Bitstream 1:seq 2+, 01:unpack, 001:copied, 0001:unpack + rep offset, 00001:copied + rep offset, 00000:unpack block 5+ bytes

// Systems for coding offsets in sequences (values of label typpos)

.var POSOF1	=	1	// One fixed offset used for all sequences
.var POSOF2	=	2	// Two fixed offsets (similar to ZX7)
.var POSOF3	=	3	// Three fixed step offsets (1,2,3 or 2,4,6 or 3,6,9 or 4,8,12 or 5,10,15)
.var POSOF4	=	4	// Four fixed step offsets (1,2,3,4 or 2,4,6,8 or 3,6,9,12 or 4,8,12,16 bits)
//////	=	5	// (not used)
.var POSOV1	=	6	// Simple variable Elias-gamma coding offset
.var POSOV2	=	7	// One fixed offset and one variable offset
.var POSOV3	=	8	// Two fixed offsets A,B and one variable Elias-gamma offset EG+B bits and +A addition
.var POSOV4	=	9	// Three fixed step offsets (1,2,3 or 2,4,6 or 3,6,9 or 4,8,12) and one variable offset

// You can use these constants to define labels "typcom" and "typpos" in definition of compress type -tnXYcNoAoB

////////////////////////////////////////////////////////////////////////////////////////////////////////

// Modificators
.var incofs_used = typpos==POSOF1 || typpos==POSOF2 || typpos==POSOV2 || typpos==POSOV3
.if (typcom>=COMBS2) {
	.eval size_option1 = false // if true, declzx_LDXR left reg_B=$FF, it is not good in this compressions
}

////////////////////////////////////////////////////////////////////////////////////////////////////////
// Macro definitions

// increment offset in reused ofset
.macro INCOFS() {
	.if (incofs_used) {
		sec
	}
}

// jump relative accoring to Carry value
.macro JRC(addr) {
	.if (revers>0) {
		bcc addr
	} else {
		bcs addr
	}
}


////////////////////////////////////////////////////////////////////////////////////////////////////////

// Validation of parameters 'typcom' and 'typpos'
// to make sure they contain only supported values

.assert	"compression set", typcom==COMBLX || typcom==COMBLC || typcom==COMZX9 || typcom==COMZX8 || typcom==COMBS2 || typcom==COMBX1 || typcom==COMBX2 || typcom==COMBX3 || typcom==COMSX1, true
.assert	"offset type set", typpos==POSOF1 || typpos==POSOF2 || typpos==POSOF3 || typpos==POSOF4 ||                   typpos==POSOV1 || typpos==POSOV2 || typpos==POSOV3 || typpos==POSOV4, true

// Data overlay check
// to make sure destination depacked data does not overwrite source packed data not used yet

.if (revers>0) {
	.assert  "revers range test", srcadd + mindst <= dstadd  ||  dstadd + totlen <= srcadd, true
} else {
	.assert  "normal range test", dstadd + totlen + mindst <= srcadd + pcklen  ||  srcadd + pcklen <= dstadd, true
}

////////////////////////////////////////////////////////////////////////////////////////////////////////

// Start of depacker

// Set input (HL) and output (DE) addresses.
// It can be commented out and fill in calling program.

declzx_start:

// Input:
//  HL = address of source packed data
//  DE = address of destination to depack data
//  A = $80 Initial value what means there are no bits from bitstream in A

// this fill D,E,H,L,A "registers"
.if (hardcore_optim>0) {
	ldy #4  // fill H, L, D, E
	// reg_A is already $80, skip its setting
} else {
	ldy #5  // fill A, H, L, D, E
}	
!loop:
	lda declzx_initdata-1,y
	sta reg_DE-1,y
	dey
	bne !loop-
// Y left in 0, it is left at zero all the time because it is used for indirect addressing LDA/STA (zeropage),y

//////////////////////////////////////////////
// Processing compression types and lengths //
//////////////////////////////////////////////


// *** BLX compression ***
BLOCK_COMBLX:
.if (typcom==COMBLX) {

		beq declzx_main     // jump to main loop (used short relative version)

declzx_ldx:
		jsr declzx_LDXR

declzx_main:
		jsr declzx_getegv0  // Get length (BC) of next block, result in B:X
	.if (return_after_depack) {
		bcc !next+
		rts                 // Return if end mark occured
	!next:
	}
		jsr declzx_GETBIT
		// 1 = unpacked block, 0 = sequence
		bcs declzx_ldx
		lda reg_B           // set Z flag from reg_B
		sta reg_Len_H       // store B:X to length registers
		stx reg_Len_L
		bne declzx_sek      // test for zero at high byte (A|reg_B), jump if length > 1
		dex                 // test for one at low byte (X|reg_C)
		bne declzx_sek      // test for zero at low byte, jump if length > 1
		// Sequence length = 1 means copying one standalone byte
	.if (bytcop==1) {       // 1B
		inx                 // X = 1
	} else {                // 2B
		ldx #bytcop         // X = bytcop
	}
		jsr declzx_getnumx  // Read offset for copying byte from bitstream
		inx                 // Increment offset
		bne declzx_copy     // Jump copying sequence (in this case only one byte)

declzx_sek:
		// Continue to read sequence offset (CF=0)
}


//*** BLC compression ***
BLOCK_COMBLC:
.if (typcom==COMBLC) {

declzx_main:
		jsr declzx_GETBIT    // Get ID of next block
		// 1 = unpacked block, 0 = sequence
		bcc declzx_blc
		jsr declzx_getegv0   // Get length of unpacked block, result in B:X
		jsr declzx_LDXR      // Copy unpacked block
		bcc declzx_main      // relative jump CF is always clear (cleared in declzx_getegv0)

declzx_blc:
		// first step can't be sequence (so in second loop X will be 0)
		inx                  // X = 1, Get Elias-Gamma with one additional bit
		jsr declzx_getegvx   // Read length of sequence from bitstream, result in A:X, Z flag from A
	.if (return_after_depack) {
		bcc !next+
		rts                  // Return if end mark occured
	!next:
	}
		sta reg_Len_H        // store A:X to length registers
		stx reg_Len_L
		// Continue to read sequence offset (CF=0)
}


// *** ZX9 compression ***
BLOCK_COMZX9:
.if (typcom==COMZX9) {

declzx_ldx:
.if (start_with_safe_X) {
		ldx #1              // Copy one unpacked byte
} else {
		inx                 // X = 1
}
		sty reg_B
		jsr declzx_LDXR

declzx_main:
		jsr declzx_GETBIT
		bcs declzx_ldx      // 1 ... one unpacked byte, 0 ... sequence
		jsr declzx_getegv0  // Get length of sequence, result in A:X, Z flag from A
		.if (return_after_depack) {
			bcc !next+
			rts             // Return if end mark occured
		!next:
		}
		sta reg_Len_H       // store A:X to length registers
		stx reg_Len_L
		bne declzx_sek      // test for zero at high byte, jump if length > 1
		dex                 // test for one at low byte
		bne declzx_sek      // test for zero at high byte
		// Sequence length = 1 means copying one standalone byte
	.if (bytcop==1) {       // 1B
		inx                 // X = 1
	} else {                // 2B
		ldx #bytcop         // X = bytcop
	}
		jsr declzx_getnumx  // Read offset for copying byte from bitstream
		inx                 // Increment offset
		bne declzx_copy     // Jump copying sequence (in this case only one byte)

declzx_sek:
		// Continue to read sequence offset (CF=0)
}


// *** ZX8 compression ***
BLOCK_COMZX8:
.if (typcom==COMZX8) {

declzx_ldx:
	.if (start_with_safe_X) {
		ldx #1              // Copy one unpacked byte
	} else {
		inx                 // X = 1
	}
		sty reg_B
		jsr declzx_LDXR

declzx_main:
		jsr declzx_GETBIT
		bcs declzx_ldx      // 1 ... one unpacked byte
		inx                 // 0 ... sequence 2+ bytes
		jsr declzx_getegvx  // Read sequence length (EG+1) from bitstream, result in A:X, Z flag from A
	.if (return_after_depack) {
		bcc !next+
		rts                 // Return if end mark occured
	!next:
	}
		sta reg_Len_H       // Store A:X to length registers
		stx reg_Len_L
		// Continue to read sequence offset (CF=0)
}


// *** BS2 compression ***
BLOCK_COMBS2:
.if (typcom==COMBS2) {

declzx_ldx:
	.if (start_with_safe_X) {
		ldx #1              // Copy one unpacked byte
	} else {
		inx                 // X = 1
	}

declzx_ldx_preset:
		sty reg_B
		jsr declzx_LDXR

declzx_main:
		jsr declzx_GETBIT
		bcs declzx_ldx      // 1 ... next unpacked byte follows
//		sty reg_B           // skip, B is always 0 after LDXR in this compression
		ldx #2              // X = 2, Set length to 2
		jsr declzx_GETBIT
		bcs declzx_push     // 01 ... sequence (length=2) follows
		inx                 // X = 3, Set length to 3
		jsr declzx_GETBIT
		bcs declzx_push     // 001 ... sequence (length=3) follows
		jsr declzx_GETBIT
		bcs declzx_sek      // 0001 ... sequence (length=4+) follows
		jsr declzx_GETBIT
		bcs declzx_reuse    // 00001 ... one unpack byte + reused offset
		jsr declzx_getegvx  // 00000 ... get length of unpacked block (length = EG+3), result in B:X
	.if (return_after_depack) {
		bcc !next+
		rts                 // Return if end mark occured
	!next:
	}
		jsr declzx_LDXR     // Copy unpacked data (Length>=8)
		ldx #2              // Copy two additional unpacked byte for length >= 10
		bne declzx_ldx_preset // jump always (relative, shorter)

// Copy one unpacked byte plus sequence (reused offset)
declzx_reuse:
		ldx #1              // set length to 1 (B is already 0)
		jsr declzx_LDXR     // Copy one byte
		inx                 // X = 1 
		jsr declzx_getegvx  // Sequence length EG+1, result in A:X, Z flag from A
		sta reg_Len_H       // store A:X to length registers
		stx reg_Len_L

declzx_last:
		ldx reg_reused_offset_L // Previous used offset
		lda reg_reused_offset_H
		sta reg_B
		INCOFS()            // if stored without increment, then increment now
		.if (incofs_used) {
			bcs declzx_copy // relative jump
		} else {
			bcc declzx_copy // relative jump
		}

// Copy sequence with read of length
declzx_sek:
		dex                 // X = 2
		jsr declzx_getegvx  // Get length of packed sequence EG+2 (length >= 4), result in A:X
		// CF is 0

// Copy sequence with read of length
declzx_push: // if jumped here CF=1, A=0 (optimized getbit reset A), X = 2..3
		sta reg_Len_H       // store A:X to length registers
		stx reg_Len_L
		// Continue to read sequence offset (CF?)
}


// *** BX1 compression ***
BLOCK_COMBX1:
.if (typcom==COMBX1) {

declzx_ldx:
	.if (start_with_safe_X) {
		ldx #1                // Copy one unpacked byte
	} else {
		inx                   // X = 1
	}

declzx_ldx_preset:
		sty reg_B
		jsr declzx_LDXR

declzx_main:
		jsr declzx_GETBIT
		bcs declzx_ldx        // 1 ... one unpacked byte
//		sty reg_B             // skip, B is always 0 after LDXR in this compression
		ldx #2
		jsr declzx_GETBIT
		bcs BLOCK_COMMON_BLOCK_A.declzx_push    // 01 ... sequence 2 bytes
		inx                   // X = 3
		jsr declzx_GETBIT
		bcs BLOCK_COMMON_BLOCK_A.declzx_push    // 001 ... sequence 3 bytes
		dex                   // X = 2
		jsr declzx_GETBIT
		bcc !next+  
		dex                   // X = 1
		jmp BLOCK_COMMON_BLOCK_B.declzx_onemain // 0001 ... one copied byte

!next:
		jsr declzx_GETBIT
		bcs BLOCK_COMMON_BLOCK_A.declzx_sek     // 00001 ... sequence 4+ bytes
		jsr declzx_GETBIT
		bcs BLOCK_COMMON_BLOCK_A.declzx_repunp  // 000001 ... one unpack byte + reused offset
		jsr declzx_GETBIT
		bcs BLOCK_COMMON_BLOCK_A.declzx_repcpy  // 0000001 ... one copied byte + reused offset
		inx                   // X = 3
		jsr declzx_getegvx    // 0000000 ... unpacked block (length = EG+3), result in B:X
		jsr declzx_LDXR       // Copy unpacked data (Length>=8)
		ldx #4                // Copy 4 additional unpacked byte for length >= 12
		bne declzx_ldx_preset // jump always (relative, shorter)
}


// *** BX2 compression ***
BLOCK_COMBX2:
.if (typcom==COMBX2) {

declzx_ldx:
	.if (start_with_safe_X) {
		ldx #1                // Copy one unpacked byte
	} else {
		inx                   // X = 1
	}

declzx_ldx_preset:
		sty reg_B
		jsr declzx_LDXR

declzx_main:
		jsr declzx_GETBIT
		bcs declzx_ldx        // 1 ... one unpacked byte
//		sty reg_B             // skip, B is always 0 after LDXR in this compression
		ldx #2
		jsr declzx_GETBIT
		bcs BLOCK_COMMON_BLOCK_A.declzx_push    // 01 ... sequence 2 bytes
		jsr declzx_GETBIT
		bcc !next+
		dex                   // X = 1
		jmp BLOCK_COMMON_BLOCK_B.declzx_onemain // 001 ... one copied byte

!next:
		inx                   // X = 3
		jsr declzx_GETBIT
		bcs BLOCK_COMMON_BLOCK_A.declzx_push    // 0001 ... sequence 3 bytes
		dex                   // X = 2
		jsr declzx_GETBIT
		bcs BLOCK_COMMON_BLOCK_A.declzx_sek     // 00001 ... sequence 4+ bytes
		jsr declzx_GETBIT
		bcs BLOCK_COMMON_BLOCK_A.declzx_repunp  // 000001 ... one unpack byte + reused offset
		jsr declzx_GETBIT
		bcs BLOCK_COMMON_BLOCK_A.declzx_repcpy  // 0000001 ... one copied byte + reused offset
		inx                   // X = 3
		jsr declzx_getegvx    // 0000000 ... unpacked block (length = EG+3), result in B:X
		jsr declzx_LDXR       // Copy unpacked data (Length>=8)
		ldx #4                // Copy 4 additional unpacked byte for length >= 12
		bne declzx_ldx_preset // jump always (relative, shorter)
}


// *** BX3 compression ***
BLOCK_COMBX3:
.if (typcom==COMBX3) {

declzx_ldx:
	.if (start_with_safe_X) {
		ldx #1                // Copy one unpacked byte
	} else {
		inx                   // X = 1
	}

declzx_ldx_preset:
		sty reg_B
		jsr declzx_LDXR

declzx_main:
		jsr declzx_GETBIT
		bcs declzx_ldx        // 1 ... one unpacked byte
		inx                   // X = 1
		jsr declzx_GETBIT
		bcc !next+
		jmp BLOCK_COMMON_BLOCK_B.declzx_onemain // 01 ... one copied byte

!next:
//		sty reg_B             // skip, B is always 0 after LDXR in this compression
		inx                   // X = 2
		jsr declzx_GETBIT
		bcs BLOCK_COMMON_BLOCK_A.declzx_push    // 001 ... sequence 2 bytes
		inx                   // X = 3
		jsr declzx_GETBIT
		bcs BLOCK_COMMON_BLOCK_A.declzx_push    // 0001 ... sequence 3 bytes
		dex                   // X = 2
		jsr declzx_GETBIT
		bcs BLOCK_COMMON_BLOCK_A.declzx_sek     // 00001 ... sequence 4+ bytes
		jsr declzx_GETBIT
		bcs BLOCK_COMMON_BLOCK_A.declzx_repunp  // 000001 ... one unpack byte + reused offset
		jsr declzx_GETBIT
		bcs BLOCK_COMMON_BLOCK_A.declzx_repcpy  // 0000001 ... one copied byte + reused offset
		inx                   // X = 3
		jsr declzx_getegvx    // 0000000 ... unpacked block (length = EG+3), result in B:X
		jsr declzx_LDXR       // Copy unpacked data (Length>=8)
		ldx #4                // Copy 4 additional unpacked byte for length >= 12
		bne declzx_ldx_preset // jump always (relative, shorter)
}


// *** SX1 compression ***
BLOCK_COMSX1:
.if (typcom==COMSX1) {

declzx_ldx:
	.if (start_with_safe_X) {
		ldx #1              // Copy one unpacked byte
	} else {
		inx                 // X = 1
	}
		sty reg_B
		jsr declzx_LDXR

declzx_main:
//		sty reg_B           // skip, B is always 0 after LDXR in this compression
		inx                 // X = 1, Set length to 1
		jsr declzx_GETBIT
		bcs BLOCK_COMMON_BLOCK_A.declzx_sek     // 1 ... regular 2+ sequence
		jsr declzx_GETBIT
		bcs declzx_ldx                          // 01 ... one unpacked byte
		jsr declzx_GETBIT
		bcc !next+
		jmp BLOCK_COMMON_BLOCK_B.declzx_onemain // 001 ... one copied byte
!next:

		jsr declzx_GETBIT
		bcs BLOCK_COMMON_BLOCK_A.declzx_repunp  // 0001 ... one unpack byte + reused offset
		jsr declzx_GETBIT
		bcs BLOCK_COMMON_BLOCK_A.declzx_repcpy  // 00001 ... one copied byte + reused offset
		inx                 // X = 2
		jsr declzx_getegvx  // 00000 ... unpacked block (length = EG+2), result in B:X
		jsr declzx_LDXR     // Copy unpacked data
		// C is cleared from getegvx > jump always
		bcc declzx_ldx      // Copy one additional unpacked byte for length >= 5
}

BLOCK_COMMON_BLOCK_A:
.if (typcom==COMBX1 || typcom==COMBX2 || typcom==COMBX3 || typcom==COMSX1) {

///////////
// Copy one packed byte plus sequence (reused offset)
declzx_repcpy:

.if (typcom!=COMSX1) {
		dex                 // X: 2 => 1
}
		jsr BLOCK_COMMON_BLOCK_B.declzx_onebyte
		// Jump to declzx_reuse
		JRC(declzx_reuse)

///////////
// Copy one unpacked byte plus sequence (reused offset)
declzx_repunp:              // reg_B = 0
.if (typcom!=COMSX1) {
		dex                 // X: 2 => 1
}
		jsr declzx_LDXR

///////////
// Copy sequence with reused offset
declzx_reuse:
		inx                 // X = 1
		jsr declzx_getegvx  // sequence length EG+1, result in A:X, Z flag from A
		sta reg_Len_H       // store A:X to length registers
		stx reg_Len_L

declzx_last:                // label for storing last used offset
		ldx reg_reused_offset_L  // Previous used offset load to B:X
		lda reg_reused_offset_H
		sta reg_B
		INCOFS()            // if stored without increment, then increment now
	.if (incofs_used) {
		bcs declzx_copy     // relative jump
	} else {
		bcc declzx_copy     // relative jump
	}

///////////
// Copy sequence with read of length
declzx_sek: // if jumped here X = 1..2
		jsr declzx_getegvx  // Get length of packed sequence (length >= 2|4), result in A:X
	.if (return_after_depack) {
		bcc !next+
		rts                 // Return if end mark occured
	!next:
	}
		// CF is 0

///////////
// Copy sequence with known length
declzx_push: // if jumped here CF=1, A=0, X = 2..3
		sta reg_Len_H       // store A:X to length registers
		stx reg_Len_L
		// Continue to read sequence offset (CF?)
}


// Clear carry if needed (some offset readings need CF=0 at begin)
.if ((typcom >= COMBS2) && (typpos==POSOF1 || typpos==POSOF4 || typpos==POSOV4)) {
		clc
}


////////////////////////////////////////////////////////////////////////////////
// Processing offsets
.var A_is_same_as_reg_B=false

// *** OF1 offset ***
.if (typpos==POSOF1) {      // needed CLC, needed INCOFS
		ldx #ofset1         // Number of bits of one fixed offset
		jsr declzx_getnumx  // Read value (X = number of value bits)
		sec                 // Adjust offset range value to 1+

.eval A_is_same_as_reg_B=true
.assert "offset range", ofset1 >= 1 && ofset1 <= 16, true
}

// *** OF2 offset ***
.if (typpos==POSOF2) { // CF any, needed INCOFS
		sty reg_B
		sty reg_C
		jsr declzx_GETBIT
		bcs !next+
		ldx #ofset2-ofset1  // We will read N bits, N = difference between bitwidth of offsets
		jsr declzx_getnumx  // If longer offset then reading N bits - it is first part of offset
		inc reg_C           // Increment offset or part of longer offset + 1)
	.if ((ofset2-ofset1)>7) { // size optim, for reading more than 8+ bits is needed take care of higher byte
		bne !next+
		inc reg_B
	}
!next:	
		ldx #ofset1         // Number of bits of shorter offset
		jsr declzx_getloop  // Reading bits of offset (next part of longer or entire shorter offset)
		sec                 // Adjust offset range value to 1+

.eval A_is_same_as_reg_B=true
.assert "offset range", ofset1 >= 1 && ofset1 < ofset2 && ofset2 <=16, true
}

// *** OF3 offset ***
.if (typpos==POSOF3) { // CF any, NOT needed INCOFS
		ldx #1              // Init counter to 1
		stx reg_LOOP
		sty reg_B
		jsr declzx_GETBIT
		bcc declzx_of3set   // Bit 0 => counter 1, CF is 0

		jsr declzx_GETBIT
		rol reg_LOOP        // Bits 1x => counter 2+x, CF is 0

declzx_of3set:
		sty reg_C           // Clearing BC for next reading offset value

declzx_of3lop:
		ldx #ofset1         // Number of bits of one part of offset
		jsr declzx_getloop  // Reading part of offset, result in B:(X|C)
		inx                 // Increment temporary offset value
		stx reg_C           // store for use in next loop
		bne !next+
		inc reg_B           // Change in B means A is not actual

!next:
		dec reg_LOOP        // Decrement loop counter
		bne declzx_of3lop   // Repeat for given width of offset

.assert "offset range", ofset1 >= 1 && ofset1 <=5, true
}


// *** OF4 offset ***
.if (typpos==POSOF4) { // needed CLC, NOT needed INCOFS
		ldx #02             // Next two bits means offset width
		jsr declzx_getnumx  // Reading these two bits from bitstream
		inx                 // Adjust two bits to value in range 1..4
		stx reg_LOOP        // Store to loop counter
		sty reg_C           // Clearing BC for next reading offset value (B already cleared from getnum)

declzx_of4lop:
		ldx #ofset1         // Number of bits of one part of offset
		jsr declzx_getloop  // Reading part of offset, result in B:(X|C)
		inx                 // Increment temporary offset value
		inc reg_C
		bne !next+
		inc reg_B           // Change in B means A is not actual
!next:

		dec reg_LOOP        // Decrement loop counter
		bne declzx_of4lop   // Repeat for given width of offset

.assert "offset range", ofset1 >= 1 && ofset1 <=4, true
}

// *** OV1 offset ***
.if (typpos==POSOV1) { // CF any, NOT needed INCOFS
		jsr declzx_getegv0  // Read Elias-Gamma value (it is enough), result in (A|rB):(rC:X)

.eval A_is_same_as_reg_B=true
}

// *** OV2 offset ***
.if (typpos==POSOV2) { // CF any, needed INCOFS
		sty reg_B           // Init offset to 0
		sty reg_C
		jsr declzx_GETBIT
		bcc !next+
		jsr declzx_getegv0  // Case 1 ... Read Elias-Gamma - only additional variable part, result in B:(X|C)
!next:
		ldx #ofset1         // Number of bits of shorter offset
		jsr declzx_getloop  // Read shorter offset or fixed part of variable offset
		sec                 // Adjust offset range value to 1+

.eval A_is_same_as_reg_B=true
.assert "offset range", ofset1 >= 1 && ofset1 <=14, true
}


// *** OV3 offset ***
.if (typpos==POSOV3) { // CF any, needed INCOFS
		sty reg_B           // Init offset to 0
		sty reg_C
		jsr declzx_GETBIT
		bcc declzx_ov3set   // Case 0 ... Jump for 0 to direct read shorter offset
		jsr declzx_GETBIT
		bcc !next+          // Case 10
		jsr declzx_getegv0  // Case 11 ... Read Elias-Gamma - only additional variable part, result in B:C
!next:
		ldx #ofset2-ofset1  // We will read N bits, N = difference between bitwidth of offsets
		jsr declzx_getloop  // If longer offset then reading N bits - it is first part of offset, result in B:X
		inc reg_C           // Increment offset BC here means add value 1 << ofset1 in result
		bne !next+
		inc reg_B
!next:

declzx_ov3set:
		ldx #ofset1         // Number of bits of shorter offset
		jsr declzx_getloop  // Reading bits of offset (next part of longer or entire shorter offset)
		sec                 // Adjust offset range value to 1+

.eval A_is_same_as_reg_B=true
.assert "offset range", ofset1 >= 1 && ofset2 <=14 && ofset1 < ofset2, true
}


// *** OV4 offset ***
.if (typpos==POSOV4) { // needed CLC, NOT needed INCOFS
		ldx #2              // Next two bits means offset width
		jsr declzx_getnumx  // Reading these two bits from bitstream
		stx reg_LOOP        // store to offset counter <0-3> means <1-4>
		cpx #%00000011      // Test for case 11 = variable offset
		sty reg_C           // Clearing BC for next reading offset1 value
		bne declzx_ov4lop   // jump in bits case 00, 01, 10
		jsr declzx_getegv0  // in bits case 11 it will read E-G then 3x added ofset1 bits, result in B:(X|C)
		dec reg_LOOP

declzx_ov4lop:
		ldx #ofset1         // Number of bits of one part of offset
		jsr declzx_getloop  // Reading part of offset, result in B:(X|C)
		inx                 // Increment temporary offset value
		stx reg_C           // store for use in next loop
		bne !next+          // Skip upper byte
		inc reg_B           // Change in B means A is not actual

!next:
		dec reg_LOOP        // Decrement loop counter
		bpl declzx_ov4lop   // Repeat for given width of offset

.assert "offset range", ofset1 >= 1 && ofset1 <=5, true
}


// Store actual offset for future "reused offset"
.if (typcom>=COMBS2) {
		stx reg_reused_offset_L
	.if (A_is_same_as_reg_B==false) {
		lda reg_B
	}
		sta reg_reused_offset_H
}


////////////////////////////////////////////////////////////////////////////////
// Copy sequence
//
// B:X=offset, reg_Len_HL=length, DE=destination
// CF=possible increment of offset
// return CF = !revers
declzx_copy:
	.if (typcom==COMBX1) {
		lda #>(BLOCK_COMBX1.declzx_main-1)
		pha
		lda #<(BLOCK_COMBX1.declzx_main-1)
		pha
	}
	.if (typcom==COMBX2) {
		lda #>(BLOCK_COMBX2.declzx_main-1)
		pha
		lda #<(BLOCK_COMBX2.declzx_main-1)
		pha
	}
	.if (typcom==COMBX3) {
		lda #>(BLOCK_COMBX3.declzx_main-1)
		pha
		lda #<(BLOCK_COMBX3.declzx_main-1)
		pha
	}
	.if (typcom==COMSX1) {
		lda #>(BLOCK_COMSX1.declzx_main-1)
		pha
		lda #<(BLOCK_COMSX1.declzx_main-1)
		pha
	}

declzx_copy_common:

	.if (visual_effect==1) {
		inc $d020
	}

		// PUSH address to source data, it will be overwritten temporarily
		lda reg_H
		pha
		lda reg_L
		pha

	.if (revers>0) {
		// Add offset to destination address
		// HL = DE+B:X
		txa
		adc reg_E
		sta reg_L
		lda reg_D
		adc reg_B  // CF should be 0
		sta reg_H
	} else {
		// Subtract offset from destination address
		// HL = DE-B:X
		.if (incofs_used) {
			rol    // neg CF for SBC if was used offset with possible increment flagged by CF
			eor #$01
			ror
		} else {
			sec    // SBC needs CF=1
		}
		stx reg_C  // store X from B:X to temp memory (SMC would be one byte longer STX $xxxx.... SBC #xx)
		lda reg_E
		sbc reg_C
		sta reg_L
		lda reg_D
		sbc reg_B  // CF should be 1
		sta reg_H
	}
		// and now HL points to source data of copied sequence
		// load to B:X length of copied sequence
		lda reg_Len_H
		sta reg_B
		ldx reg_Len_L
		jsr declzx_LDXR // Copy sequence
		// Restore address to packed source data "HL"
		pla
		sta reg_L
		pla
		sta reg_H
	.if (visual_effect==1) {
		dec $d020
	}
// Copy finished and we can continue in next block of data
	.if (typcom==COMBLX) {
		JRC(BLOCK_COMBLX.declzx_main)
	}
	.if (typcom==COMBLC) {
		JRC(BLOCK_COMBLC.declzx_main)
	}
	.if (typcom==COMZX9) {
		JRC(BLOCK_COMZX9.declzx_main)
	}
	.if (typcom==COMZX8) {
		JRC(BLOCK_COMZX8.declzx_main)
	}
	.if (typcom==COMBS2) {
		.print * - BLOCK_COMBS2.declzx_main
		jmp BLOCK_COMBS2.declzx_main
	}
	.if (typcom==COMBX1 || typcom==COMBX2 || typcom==COMBX3 || typcom==COMSX1) {
		rts
	}


////////////////////////////////////////////////////////////////////////////////
// Subroutine for copying one byte
// input: X=1
// Used in:
//  - direct command for copying one byte
//  - reused offset with preceded copying one byte

BLOCK_COMMON_BLOCK_B:
.if (typcom==COMBX1 || typcom==COMBX2 || typcom==COMBX3 || typcom==COMSX1) {

// jump here to copy one byte and jump back to main loop using RTS
declzx_onemain:

// push label of main loop to stack
	.if (typcom==COMBX1) {
		lda #>(BLOCK_COMBX1.declzx_main-1)
		pha
		lda #<(BLOCK_COMBX1.declzx_main-1)
		pha
	}
	.if (typcom==COMBX2) {
		lda #>(BLOCK_COMBX2.declzx_main-1)
		pha
		lda #<(BLOCK_COMBX2.declzx_main-1)
		pha
	}
	.if (typcom==COMBX3) {
		lda #>(BLOCK_COMBX3.declzx_main-1)
		pha
		lda #<(BLOCK_COMBX3.declzx_main-1)
		pha
	}
	.if (typcom==COMSX1) {
		lda #>(BLOCK_COMSX1.declzx_main-1)
		pha
		lda #<(BLOCK_COMSX1.declzx_main-1)
		pha
	}

declzx_onebyte:             // if entered here with JSR then RTS back
		sty reg_Len_H       // set length to 1 (upper byte)

// Size optimized LDX #bytcop and store 1 to reg_Len_L
	.if (bytcop==1) {       // 2B
//		ldx #bytcop         // Bit width 1 of offset for copying standalone bytes
		stx reg_Len_L       // X = 1
	}
	.if (bytcop==2) {       // 3B
		stx reg_Len_L       // X = 1
		inx                 // Bit width 2 of offset for copying standalone bytes
	}
	.if (bytcop>=3) {       // 4B
		stx reg_Len_L       // X = 1
		ldx #bytcop         // Bit width 3+ of offset for copying standalone bytes
	}
		clc                 // get num with no preprocessed bits
		jsr declzx_getnumx  // Read offset from bitstream
		inx                 // increment to move range to 1+
		bne declzx_copy_common  // relative "unconditional" jump
}

////////////////////////////////////////////////////////////////////////////////
// Get numeric value from bitstream
///////////
// read whole Elias-gamma value
declzx_getegv0:
.if (typcom==COMBLX || typcom==COMBLC || typcom==COMZX9 || typpos==POSOV1 || typpos==POSOV2 || typpos==POSOV3 || typpos==POSOV4) {
		ldx #0                // Read of Elias-gamma value with no additional bits
}

///////////
// read Elias-gamma value with extra X bits
declzx_getegvx:
		jsr declzx_GETBIT     // Get bit from bitstream
		inx                   // Count zero bits before value bits
		bcc declzx_getegvx
		// CF is 1, it will be added to result as highest bit

///////////
// read X bit value
declzx_getnumx:               // if entered here with CF, then it is added to BC before reading next bit
		sty reg_B             // Initialize destination value to zero
		sty reg_C
		bcs declzx_getnext    // First CF=1 is needed to rotate into BC always

///////////
// read X bit value with X is count of next bits to read, BC is input/output value
declzx_getloop:
		jsr declzx_GETBIT     // read bit

declzx_getnext:
		rol reg_C             // Include bit into destination value
		rol reg_B             // propagate carry
	.if (return_after_depack) {
		bcs declzx_rts        // If value > 65535 then return immediately with CF=1
	} else {
		bcs declzx_end        // If value > 65535 then jump to dst address
	}

declzx_dec_bitcounter:
		dex                   // decrement bit counter
		bne declzx_getloop    // Repeat for all needed bits of value
		ldx reg_C             // store C from pair BC to X to have faster access to C as pair B:X
		// in COMBLX this optimalization to always fill A with reg_B is useless
	.if (typcom!=COMBLX) {
		lda reg_B             // store B from pair BC to A to set Z,N flags from B, saves a few bytes
	}
declzx_rts:
		rts                   // Return with CF=0 (not end mark)


////////////////////////////////////////////////////////////////////////////////
// copy sequence from [HL++] to [DE++] B:X times
// if size_option1 is true then code is faster and smaller but last working B:X is 0x8100
// CF is untouched
declzx_LDXR: {
	.if (!size_option1) {
		inc reg_B              // if B is zero then increment and later decrement and test for zero
	}
//??? da sa to aj niekedy skipnut...
		txa                        // test of low byte is zero, then decrement high byte
		beq !loop2+

!loop:
		jsr declzx_GETBYTE     // A=[HL++]
		sta (reg_DE),y         // [DE]=A
		// DE++
	.if (revers>0) {
		lda reg_E
		bne !next+
		dec reg_D
	!next:
		dec reg_E
	} else {
		inc reg_E
		bne !next+
		inc reg_D
	!next:
	}
		// BX--
		dex
		bne !loop-
!loop2:
		dec reg_B

	.if (!size_option1) {
		bne !loop-  // handles reg_B in whole range
	} else {
		bpl !loop-  // handles reg_B in <0;0x80> range
	}
		rts
}


////////////////////////////////////////////////////////////////////////////////
// Get one byte from bitstream
declzx_GETBYTE: {
		lda (reg_HL),y
	.if (revers>0) {
		ldy reg_L   // Load flags for low byte
		bne !next+  // If not zero, then decrement only low byte
		dec reg_H   // Decrement high byte
	!next:
		dec reg_L   // Decrement low byte
		ldy #0
	} else {
		inc reg_L   // Increment low byte
		bne !next+  // If overflow then
		inc reg_H   // Increment high byte
	!next:
	}
		rts
}


////////////////////////////////////////////////////////////////////////////////
// Get one bit from bitstream 
declzx_GETBIT: {
		asl reg_A            // Reading bit from buffer (A = temporary buffer for bits)
		bne !next+           // If there were some bits in buffer then return
		jsr declzx_GETBYTE   // Buffer is empty so we must read next byte from bitstream
		rol                  // MSB bit will be returned reading bit
		sta reg_A            // store to buffer
!next:
	.if (typcom>=COMBS2) {
		tya                  // size optimized code (adds 1B here, removes 2B somewhere else)
	}
		rts
}

//////////////////////////////////////////////////////////////////////////////////////////////////////;
declzx_end:
// if found end mark then return with flag or run depacked code
.if (return_after_depack==false) {
		.byte $4C             // jmp dstadd // start depacked code
	.if (revers>0) {
		.word dstadd          // jmp address in reverse case
	}
}
// initialization data of DE, HL, A registers (in reverse jump address is 2 bytes back)
declzx_initdata:
.if (revers>0) {
		.word dstadd+totlen-1 // it is need to set address of last byte
		.word srcadd+pcklen-1 // In case of reversed unpacking,
} else {
		.word dstadd          // jmp address and dstaddress
		.word srcadd          // srcaddress
}
.if (hardcore_optim==0) {     // if false, A is set to $80 by selecting address in zero page containing $80 value
		.byte $80             // Initial value in reg_A what means there are no bits from bitstream in A
}

//////////////////////////////////////////////////////////////////////////////////////////////////////;
packed_data:
.if (filename_to_decompress!="") {
	.var str="C:\\Temp\\lzx\\"
	.eval str+=filename_to_decompress
	.if (revers>0) {
		.eval str+="-tr"
	} else {
		.eval str+="-tn"
	}
	.eval str+=typcom
	.eval str+=typpos

	.if (typcom==COMBLX || typcom==COMZX9 || typcom==COMBX1 || typcom==COMBX2 || typcom==COMBX3 || typcom==COMSX1) {
		.eval str+="c"+bytcop
	}
	.if (typpos==POSOF1 || typpos==POSOF2 || typpos==POSOF3 || typpos==POSOF4 || typpos==POSOV2 || typpos==POSOV3 || typpos==POSOV4) {
		.eval str+="o"+ofset1
	}
	.if (typpos==POSOF2 || typpos==POSOV3) {
		.eval str+="o"+ofset2
	}
	.eval str+=".lzx"

	.print str
	.import binary str
}

end_packed_data: