%if 0

lDebug heatshrink depacker

Copyright (C) 2008-2023 E. C. Masloch

Usage of the works is permitted provided that this
instrument is retained with the works, so that any entity
that uses the works is notified of this instrument.

DISCLAIMER: THE WORKS ARE WITHOUT WARRANTY.

%endif

%include "lmacros3.mac"


	numdef STANDALONE,	1

%ifn _STANDALONE
	overridedef DEBUG0,	0
	overridedef COUNTER,	0
%else
	numdef DEBUG0,		0
	numdef COUNTER,		0, 32
%if (_COUNTER - 1) & _COUNTER
 %error COUNTER must be a power of two
%endif
	numdef OUTBUFFER,	1


	cpu 8086
	org 256
	addsection CODE, start=256
testprogram:
	mov si, 81h

	cmp sp, stack.top
	jae @F
error:
	mov dx, msg.error
	mov ah, 09h
	int 21h
	mov ax, 4CFFh
	int 21h
	int 20h

@@:
	lodsb
	cmp al, 9
	je @B
	cmp al, 32
	je @B
	cmp al, 13
	je error
	mov dx, si
	dec dx

@@:
	lodsb
	cmp al, 9
	je @F
	cmp al, 32
	je @F
	cmp al, 13
	jne @B
@@:
	mov byte [si - 1], 0

	mov ax, 3D00h | 0_010_0_000b	; RO, DENY WRITE
	int 21h
	jc error
	mov word [handle], ax
	mov word [filebuffer.next], filebuffer
	mov word [filebuffer.tail], filebuffer
%if _OUTBUFFER
	mov word [outbuffer.next], outbuffer
%endif

	xchg bx, ax
	mov ax, 4202h
	xor cx, cx
	xor dx, dx
	int 21h
	jc error

	push dx
	push ax
	mov ax, 4200h
	xor cx, cx
	xor dx, dx
	int 21h
	jc error

	mov ax, 3C00h
	xor cx, cx
	mov dx, msg.outname
	int 21h
	jc error
	mov word [handle2], ax

	pop dx
	pop cx				; = file size

	mov di, resultbuffer
	push di
	push cx
	mov ax, 0CCCCh
	mov cx, words(resultbuffer.end - resultbuffer)
	rep stosw
	pop cx
	pop di
	mov si, resultbuffer.end

	call depack
	jc error

%if _OUTBUFFER
	mov di, [outbuffer.next]
	call dump_outbuffer
%endif

	push ss
	pop ds
	push ss
	pop es

	mov ah, 3Eh
	mov bx, [handle]
	int 21h

	mov ah, 3Eh
	mov bx, [handle2]
	int 21h

	mov ax, 4C00h
	int 21h
	int 20h


%if _COUNTER
disp_al:
	push dx
	push ax
	xchg ax, dx
	mov ah, 02h
	int 21h
	pop ax
	pop dx
	retn
%endif


		; INP:	-
		; OUT:	Does not return if error (in this example)
		;	CY if error
		;	NC if success,
		;	 al = byte read from file
		; CHG:	si
		; REM:	In this example program we assume ds = es = ss.
get_file_byte:
	mov si, [filebuffer.next]
	cmp si, [filebuffer.tail]
	jb .buffered

	push ax
	push bx
	push cx
	push dx
	mov dx, filebuffer
	mov [filebuffer.next], dx
	mov [filebuffer.tail], dx
	mov ah, 3Fh
	mov bx, [handle]
        mov cx, filebuffer.end - filebuffer

	int 21h
	jc error
	test ax, ax
	jz error
	add ax, dx
	mov word [filebuffer.tail], ax
        mov si, dx
	pop dx
	pop cx
	pop bx
	pop ax

.buffered:
	lodsb
	mov [filebuffer.next], si
	clc
	retn


%if _OUTBUFFER
		; INP:	ds:si -> data to write
		;	cx = length of data (may be zero)
		; OUT:	Does not return if error
		; CHG:	-
		; REM:	In this example program we assume ds = es = ss.
put_file_data:
	push ax
	push bx
	push dx
	push di
	push si
	push cx
	mov di, [outbuffer.next]
	jcxz .end
@@:
	cmp di, outbuffer.end
	jne @F

	call dump_outbuffer

@@:
	movsb
	loop @BB
	mov [outbuffer.next], di
.end:
	pop cx
	pop si
	pop di
	pop dx
	pop bx
	pop ax
	retn

dump_outbuffer:
	push cx
	mov cx, di
	mov dx, outbuffer
	sub cx, dx
	jz @F
	mov bx, word [handle2]
	mov ah, 40h
	int 21h
	jc error
	cmp ax, cx
	jne error
@@:
	mov di, dx
	pop cx
	retn
%else
		; INP:	ds:si -> data to write
		;	cx = length of data (may be zero)
		; OUT:	Does not return if error
		; CHG:	-
		; REM:	In this example program we assume ds = es = ss.
put_file_data:
	push ax
	push bx
	push dx
	mov dx, si
	mov ah, 40h
	mov bx, [handle2]
	jcxz @F
	int 21h
	jc error
	cmp ax, cx
	jne error
@@:
	pop dx
	pop bx
	pop ax
	retn
%endif


	addsection DATA, align=1 follows=CODE
msg:
.error:		ascic "Error!",13,10
.outname:	asciz "output.bin"


	addsection BSSDATA, align=16 nobits follows=DATA
	alignb 16
resultbuffer:
.size equ 4096
		resb .size
.end:
	alignb 2

filebuffer:	resb 256
.end:
	alignb 2
.next:		resw 1
.tail:		resw 1
handle:		resw 1
handle2:	resw 1

%if _OUTBUFFER
outbuffer:	resb 256
.end:
.next:		resw 1
%endif

stack:		resb 512
.top:


	usesection CODE
%endif	; _STANDALONE


		; INP:	cx:dx = length of source
		;	es:di -> destination
		;	si -> behind end of destination
		; OUT:	NC if success
		;	CY if error,
		;	 bx = ?errordata (if _DEBUG0)
		; CHG:	ax, (bx), cx, dx, es, ds, si, di
		; STT:	UP
depack:
	lframe near
	lenter
 %if _DEBUG0 || _COUNTER
	xor bx, bx
 %endif
 %if _DEBUG0
	lvar word,	errordata
	 push bx
 %endif
%if _COUNTER
	lvar word,	unused_and_counter
	lequ ?unused_and_counter + 1, counter
	 push bx		; initialise counter (high byte) to zero
%endif

	lvar dword,	original_dst
	 push es
	 push di
	lvar dword,	dst
	 push es
	 push di
	lvar dword,	src_remaining
	 push cx
	 push dx

	mov cx, si
	sub cx, di

	lvar word,	dst_length
	 push cx		; push into [bp + ?dst_length]

	mov al, 0
	rep stosb		; prepare for matches before first data byte

@@:
	call read_byte
d0	mov byte [bp + ?errordata], 70h
	jc .error

	mov ah, 0
	test ax, ax
d0	mov byte [bp + ?errordata], 71h
	jz .error
	cmp al, 15
	ja .error

	lvar word,	window_size_bits
	 push ax

	xchg cx, ax
	mov ax, 1
	shl ax, cl
	cmp ax, resultbuffer.size
	ja .error

	call read_byte
d0	mov byte [bp + ?errordata], 72h
	jc .error

	mov ah, 0
	test ax, ax
d0	mov byte [bp + ?errordata], 73h
	jz .error
	cmp ax, word [bp + ?window_size_bits]
	jae .error

	lvar word,	lookahead_size_bits
	 push ax

	xor cx, cx
	lvar word,	low_bit_index_and_high_current_byte
	 push cx

.loop:
%if _COUNTER
	inc byte [bp + ?counter]
	test byte [bp + ?counter], _COUNTER - 1
	jnz @F
	mov al, '.'
	call disp_al
@@:
%endif
	mov dx, word [bp + ?original_dst]
	add dx, word [bp + ?dst_length]	; dx -> end of buffer
	jc .error

	mov cx, 1
	call get_bits
	jnc .notend			; (cx = 0 if jumping)

d0	mov byte [bp + ?errordata], 7Dh
.end_check:
	xor ax, ax
	cmp word [bp + ?src_remaining], ax
	jne .error
	cmp word [bp + ?src_remaining + 2], ax
	jne .error

	db __TEST_IMM8			; (NC)
.error:
	stc

%if _COUNTER
	lahf
	mov al, 13
	call disp_al
	mov al, 10
	call disp_al
	sahf
%endif
d0	mov bx, word [bp + ?errordata]
	lleave code
	lret

.notend:				; (cx = 0)
	test al, al
	jz .notliteral

	mov cl, 8
	call get_bits			; cx = 0
d0	mov byte [bp + ?errordata], 74h
	jc .end_check
	push ax				; on stack: the literal
	 push ss
	 pop ds
	mov si, sp			; ds:si -> data on stack
	xchg ax, cx			; ax = 0
	inc ax				; length = 1
	mov bx, ax			; bx NZ
	call copy_data.bx
	pop ax				; (discard)
d0	mov byte [bp + ?errordata], 75h
	jc .error
	jmp .loop

.notliteral:				; (cx = 0)

	mov cl, byte [bp + ?window_size_bits]
					; cx = -w parameter
	call get_bits
d0	mov byte [bp + ?errordata], 76h
	jc .end_check
		; ax = output index
		; cx = 0
	xchg bx, ax
	mov cl, byte [bp + ?lookahead_size_bits]
					; cx = -l parameter
	call get_bits
d0	mov byte [bp + ?errordata], 77h
	jc .end_check
		; bx = output index less 1
		; ax = output count less 1
	inc bx				; = output index
d0	mov byte [bp + ?errordata], 78h
	jz .error

					; ax = length of match less one
	inc ax				; = length of match
	jz .error

	mov cx, bx			; 1 .. 65535

	lds si, [bp + ?dst]
	mov bx, word [bp + ?original_dst]
	neg bx
	add bx, si			; dst - original = length at start
	sub bx, cx			; length start - match distance
	jae @F				; length start >= match distance -->
		; bx = negative length of matchable at end of buffer

	mov si, dx
	add si, bx			; si -> match data
	mov bx, si
	add bx, ax			; -> behind match data (if fits)
	jc .full			; if doesn't fit, copy full -->
	cmp bx, dx			; -> behind is above-or-equal end ?
	; jae .full			; yes -->
	jb .copy_data			; ax has full length from tail of buffer -->

		; if we branch to here,
		;  si + ax >= dx
		;  therefore  ax >= dx - si
.full:
	push ax				; save original length
		; calculate dx - si
	mov ax, dx
	sub ax, si			; -> end minus -> match data
	push ax				; save length
	call copy_data
d0	mov byte [bp + ?errordata], 1
	jc .error
	pop bx				; = done length
	pop ax				; = entire length
	sub ax, bx			; entire length minus done length
	mov si, word [bp + ?original_dst]	; -> start of buffer
	db __TEST_IMM16			; skip sub
@@:
	sub si, cx			; -> into buffer

.copy_data:
	call copy_data			; give ?dst -> dest, ds:si -> source
		; returns: ?dst incremented, ds:si -> after match source
d0	mov byte [bp + ?errordata], 7Bh
	jc .error

	jmp .loop


		; INP:	?dst -> destination,
		;	 destination may overlap the circular buffer boundary.
		;	ds:si -> source
		;	ax = how long the data is (0 is valid),
		;	 always 1 for literals.
		;	 source must fit in circular buffer, except if
		;	 source and destination overlap. in this case the
		;	 source starts in the buffer but may extend past
		;	 the circular buffer boundary.
		;	dx -> end of circular buffer
		;	word [?original_dst] -> start of circular buffer
		; OUT:	?dst incremented
		;	CY if error (buffer too small)
		;	NC if success
		; CHG:	cx, bx, dx, ax, es, di, ds, si
copy_data:
	xor bx, bx
.bx:
		; bx = nonzero if literal
%if 0
	push ax
	push bx
	push cx
	push dx
	mov ax, 4201h
	mov cx, 0
	mov dx, 0
	mov bx, word [handle2]
	int 21h
	nop
	pop dx
	pop cx
	pop bx
	pop ax
%endif

d0	inc byte [bp + ?errordata + 1]

	les di, [bp + ?dst]

	xchg cx, ax			; cx = remaining length
	jcxz .end

	cmp di, dx
	ja .ret_CY			; error
		; literals may branch to .start_maybe_part
	je .start_maybe_part		; ?dst -> at end (put whole data at start)
	add di, cx			; -> behind data chunk (if fits)
	jc .fill			; doesn't fit -->
	cmp di, dx			; fits ?
		; literals never branch to .fill
	ja .fill			; doesn't fit -->
	mov di, word [bp + ?dst]	; restore -> destination
		; ds:si l cx fits in circular buffer
	jmp .mov			; store full up to below-or-equal -> end

.fill:
	mov di, word [bp + ?dst]	; restore -> destination
	mov ax, dx			; -> behind buffer
	sub ax, di			; -> behind buffer minus -> destination
					; = length fitting at end
	sub cx, ax			; cx -= length fitting at end
					; = remaining length to store at start
	xchg cx, ax			; cx = length fitting at end
	call .mov_and_put		; store at end
	xchg cx, ax			; cx = remaining length to store at start

.start_maybe_part:
	mov di, word [bp + ?original_dst]
	test bx, bx			; literal ?
	jnz .mov			; yes -->
		; literals never go here
		; the following handles source overlapping destination
		;  s.t. source crosses the circular buffer boundary.
	mov ax, si
	add ax, cx			; -> behind source
	jc .part			; CY, must copy part -->
	cmp ax, dx			; below-or-equal end of buffer ?
	jbe .mov			; yes -->
.part:
	mov ax, dx
	sub ax, si			; ax = how much source data remains
	sub cx, ax			; cx = how much to copy after
	xchg cx, ax			; cx = part length (source data remains)
	call .mov_and_put		; put from end
	xchg cx, ax			; cx = how much to copy from buffer start
	mov si, word [bp + ?original_dst]	; -> start of buffer
					; continue with di -> near start of buffer
.mov:
	call .mov_and_put

.end:
	mov word [bp + ?dst], di
	clc
.ret:
	retn

.ret_CY:
	stc
	retn

.mov_and_put:
	 push ds
	 push si
	push es
	push di
	push cx
	rep movsb
	pop cx
	pop si
	pop ds
	call put_file_data
	 pop si
	 pop ds
	add si, cx
	retn


		; INP:	cx = 0..15
		; OUT:	NC if successful,
		;	 ax = value read
		;	 cx = 0
		;	CY if error
		; CHG:	ds, si
get_bits:
	push bx
	mov bx, word [bp + ?low_bit_index_and_high_current_byte]
	cmp cx, 15
	ja .error
	test cx, cx
	jz .error
	xor ax, ax
.loop:
	test bl, bl
	jnz .havebit
	push ax
	call read_byte
	mov bh, al
	mov bl, 80h
	pop ax
	jc .error
.havebit:
	shl ax, 1
	test bh, bl
	jz @F
	inc ax
@@:
	shr bl, 1
	loop .loop
.end:
	db __TEST_IMM8
.error:
	stc
	mov word [bp + ?low_bit_index_and_high_current_byte], bx
	pop bx
	retn


		; INP:	?src_remaining
		; OUT:	NC if success,
		;	 al = value read
		;	 ?src_remaining decremented
		;	CY if error (source buffer too small),
		;	 ?src_remaining = 0
		; CHG:	ds, si
read_byte:
	sub word [bp + ?src_remaining], 1
	sbb word [bp + ?src_remaining + 2], 0
	jb .empty

	call get_file_byte
	retn

.empty:
	and word [bp + ?src_remaining], 0
	and word [bp + ?src_remaining + 2], 0
	stc
	retn

	lleave ctx

%ifn _STANDALONE
	resetdef COUNTER
	resetdef DEBUG0


read_and_depack:
	houdini

		; common setup for depacking
reloc	mov word [handle], bx
internaldatarelocation

reloc	mov word [depacked_buffer], dx
internaldatarelocation
reloc	mov word [depacked_buffer + 2], es
internaldatarelocation
reloc	mov word [depacked_length], cx
internaldatarelocation

reloc	rol byte [partialdepack], 1
internaldatarelocation
	jnc .new
		; did we save a prior partial depack ?
reloc	cmp word [depack_saved_sp], strict byte 0FFFFh
internaldatarelocation -3
	je .new				; no -->
reloc	mov ax, word [depackseek + 2]
internaldatarelocation
reloc	cmp ax, word [depackskip + 2]
internaldatarelocation
	jne @F
reloc	mov ax, word [depackseek]
internaldatarelocation
reloc	cmp ax, word [depackskip]
internaldatarelocation
@@:
	ja .new				; seek is > skip, must rewind -->

reloc	mov ax, word [depackseek]
internaldatarelocation
reloc	sub word [depackskip], ax
internaldatarelocation
reloc	mov ax, word [depackseek + 2]
internaldatarelocation
reloc	sbb word [depackskip + 2], ax	; skip -= seek
internaldatarelocation

	push dx
	push bx
	push di
	push si
	push es
	push bp
	push cx				; stack frame must match what .read expects

reloc	mov word [depack_prior_sp], sp
internaldatarelocation			; store our sp
	jmp put_file_data.entry		; continue depacking


.new:
	push dx
	push cx
	push es
reloc	mov dx, word [libseek]
internaldatarelocation
reloc	mov cx, word [libseek + 2]
internaldatarelocation
	mov ax, 4200h
	call dos_or_boot_io
	pop es
	jc .io_error

reloc2	mov word [filebuffer.next], filebuffer
internaldatarelocation -4
internaldatarelocation
reloc2	mov word [filebuffer.tail], filebuffer
internaldatarelocation -4
internaldatarelocation

	pop cx
	pop dx

	xor ax, ax
reloc	mov word [depackseek], ax
internaldatarelocation
reloc	mov word [depackseek + 2], ax
internaldatarelocation

	push dx
	push bx
	push di
	push si
	push es
	push bp
	push cx
	 push ss
	 pop es
reloc	mov di, resultbuffer
internaldatarelocation
reloc	mov si, resultbuffer.end
internaldatarelocation

reloc	mov cx, word [libtab_compressed_length + 2]
internaldatarelocation
reloc	mov dx, word [libtab_compressed_length]
internaldatarelocation

reloc	mov word [depack_prior_sp], sp
internaldatarelocation
reloc	mov sp, depack_stack.top
internaldatarelocation
	call depack
reloc	mov word [depack_saved_sp], 0FFFFh
internaldatarelocation -4
.read:
	 push ss
	 pop ds

reloc	push word [depackseek]
internaldatarelocation
reloc	push word [depackseek + 2]
internaldatarelocation
reloc	pop word [depackskip + 2]
internaldatarelocation
reloc	pop word [depackskip]
internaldatarelocation

reloc	mov sp, word [depack_prior_sp]
internaldatarelocation
	jc @F
reloc	mov ax, word [depacked_length]
internaldatarelocation
	neg ax			; - remain
	pop cx
	add ax, cx		; requested - remain = how much read
	db __TEST_IMM8		; skip pop, NC
@@:
	pop cx
	pop bp
	pop es
	pop si
	pop di
	pop bx
	pop dx
	retn

.io_error:
	add sp, 4
	stc
	retn


get_file_byte:
reloc	mov si, [ss:filebuffer.next]
internaldatarelocation
reloc	cmp si, [ss:filebuffer.tail]
internaldatarelocation
	jb .buffered

	push ax
	push bx
	push cx
	push dx
	push es
	push ds

	 push ss
	 pop ds
	 push ss
	 pop es
reloc	mov dx, filebuffer
internaldatarelocation
reloc	mov [filebuffer.next], dx
internaldatarelocation
reloc	mov [filebuffer.tail], dx
internaldatarelocation
	mov ax, 3F00h
reloc	mov bx, [handle]
internaldatarelocation
	mov cx, filebuffer.end - filebuffer
	call dos_or_boot_io
	jc @F			; CY -->
	test ax, ax
	stc			; CY
	jz @F
	add ax, dx		; NC
reloc	mov word [filebuffer.tail], ax
internaldatarelocation
	mov si, dx
@@:
	pop ds
	pop es
	pop dx
	pop cx
	pop bx
	pop ax
	jc .ret
.buffered:
	ss lodsb
reloc	mov [ss:filebuffer.next], si
internaldatarelocation
	clc
.ret:
	retn


put_file_data:
	push ax
	push bx
	push cx
	push dx
	push ds
	push si
	push es
	push di
	 push ss
	 pop ds
.entry2:
%if 0
reloc	cmp word [depackseek], strict word 0AFh
internaldatarelocation -4
	jne @F
reloc	cmp word [depackseek + 2], strict byte 0
internaldatarelocation -3
	jne @F
	houdini
@@:
%endif
	push cx
reloc	cmp word [depackskip + 2], strict byte 0
internaldatarelocation -3
	jne .skip
reloc	cmp word [depackskip], cx
internaldatarelocation
	jae .skip
	xor ax, ax
reloc	xchg ax, word [depackskip]
internaldatarelocation
	add si, ax
	sub cx, ax		; data - skip = length of data
	jz .ret_cx
reloc	les di, [depacked_buffer]
internaldatarelocation
reloc	mov ax, word [depacked_length]
internaldatarelocation
	cmp ax, cx
	jae @F
	mov cx, ax
@@:
	mov ax, cx
	rep movsb

reloc	mov word [depacked_buffer], di
internaldatarelocation
reloc	sub word [depacked_length], ax
internaldatarelocation
	jnz .ret_cx
	pop cx
reloc	add word [depackseek], ax
internaldatarelocation
reloc	adc word [depackseek + 2], strict byte 0
internaldatarelocation -3
	sub cx, ax
reloc	mov word [depack_left_length], cx
internaldatarelocation
reloc	mov word [depack_left_address], si
internaldatarelocation
	push bp
reloc	mov word [depack_saved_sp], sp
internaldatarelocation
	clc
	jmp read_and_depack.read

.entry:
reloc	mov sp, word [depack_saved_sp]
internaldatarelocation
	pop bp
reloc	mov cx, word [depack_left_length]
internaldatarelocation
reloc	mov si, word [depack_left_address]
internaldatarelocation
	jmp .entry2

.skip:
reloc	sub word [depackskip], cx
internaldatarelocation
reloc	sbb word [depackskip + 2], strict byte 0
internaldatarelocation -3

.ret_cx:
	pop cx
reloc	add word [depackseek], cx
internaldatarelocation
reloc	adc word [depackseek + 2], strict byte 0
internaldatarelocation -3

.ret:
	pop di
	pop es
	pop si
	pop ds
	pop dx
	pop cx
	pop bx
	pop ax
	retn
%endif
