1816 lines
31 KiB
Plaintext
1816 lines
31 KiB
Plaintext
; samflate :: inflatelib.z80s by Andrew Collier (C) 2009
|
|
; an implementation of the Inflate decompression algorithm
|
|
; corresponding to deflate compression as used by gzip
|
|
; and compatible with RFC 1951
|
|
|
|
; Although I have not directly translated the code, this implementation
|
|
; is based on Paul Sladen's pyflate.py which I used as a reference
|
|
; for the compressed file format. Therefore I'm releasing this code
|
|
; under the same license terms:
|
|
; # You may use and distribute this code under any DFSG-compatible
|
|
; # license (eg. BSD, GNU GPLv2).
|
|
|
|
; The latest version of this file will be kept available at the sourceforge project,
|
|
; "samflate"
|
|
|
|
; My intent is that it should not in general be necessary to modify this file
|
|
; in order to use gz decompression in general programs
|
|
; (not branching the file will make for easier bug-fixing and maintenance)
|
|
|
|
; This code must be called in section D, with stack near the top of memory
|
|
; * The first 257 bytes of section D might be overwritten! *
|
|
; section C will be used as temporary storage
|
|
|
|
; Interrupts may be enabled, provided that they IM2 is selected and a
|
|
; handler table is included inside section D.
|
|
; (If you're wondering why the code isn't in section A, it is necessary (for the dictionary lookups to work)
|
|
; that the temporary work page - which would have to be B in that case - immediately follows the rotating memory window)
|
|
|
|
; The code runs in section D and uses section C as temporary storage
|
|
; (i.e you should allow 32k of storage for operation,
|
|
; which cannot overlap with the compressed or uncompressed file.
|
|
; If you're careful, the beginning of the compressed file can overlap with the end
|
|
; of the uncompressed.)
|
|
|
|
; in fact we write the decoded file into C before copying it to the destination page
|
|
; we may write up to 257 bytes of section D as well, to do page checks less often
|
|
; gz data is generally resident in sections A+B
|
|
; this means we do not need to change the paging between every byte
|
|
|
|
; moreover we only need to check the encoded page window after overflowing 16k of the decoded file
|
|
; (in a 32k window we are guaranteed to be able to see 16k of the compressed file
|
|
; wherever the block starts; we know the compressed block is smaller than the decoded block.
|
|
; If it were not, it would be stored in the file as block type 0.)
|
|
|
|
; we will often want to copy whole pages from C to low memory
|
|
; (partial pages at the start or end of files should be done more simply and perhaps more slowly)
|
|
; We can optimise copies by using the stack - but need to take care not to corrupt anything if interrupts occur
|
|
; we use the stack to write into section A
|
|
; - interrupts either write memory in A which will then be written by correct copied data
|
|
; or else write into D, which under our own control and can be safe if we don't overwrite
|
|
; values from the stack which might otherwise have been in use
|
|
|
|
; LIMITATIONS
|
|
|
|
; This code does not keep track of a checksum
|
|
; Errors in the gz file are not likely to be detected and may cause a crash.
|
|
|
|
|
|
inflate:
|
|
; decode a block of gzip data starting at offset HL [of page C *]
|
|
; writing the result starting at offset DE [of page B *]
|
|
|
|
; * unless assembled with INFLATELIB_CONFIG__NO_MEMORY_PAGING defined
|
|
; use this when the decoded data is KNOWN to fit inside free memory
|
|
; e.g. this library can be assembled within the top 8k of section D,
|
|
; and decompressing a MODE 4 screen at DE==32768 can be made to work
|
|
|
|
; returns A=0, carry clear, if decompression appeared to be successful
|
|
; returns A!=0, carry set, if error was detected.
|
|
; (NB. We do not calculate checksum. Most error conditions are not detected and will probably cause a crash.)
|
|
|
|
|
|
IF 0 == defined(INFLATELIB_CONFIG__NO_MEMORY_PAGING)
|
|
ld a,d
|
|
and 0x3f ; section A
|
|
ld d,a
|
|
ld (dst), de
|
|
|
|
or 0x80 ; section C
|
|
ld d,a
|
|
|
|
|
|
in a,(lmpr)
|
|
ld (inflate_lmprs),a
|
|
|
|
ld a,b
|
|
and 31
|
|
or 32
|
|
ld (dst.page),a
|
|
dec a
|
|
dec a
|
|
ld (dst.page.minustwo),a
|
|
|
|
ld a,h
|
|
and 0x3f ; section A
|
|
ld h,a
|
|
|
|
ld a,c
|
|
and 31
|
|
or 32
|
|
ld (src.page),a
|
|
|
|
out (lmpr),a
|
|
ENDIF
|
|
ld (interim_dst),de
|
|
|
|
|
|
; by default, the source file will be in sections A/B
|
|
; we write decoded information into section C and copy completed pages into destination memory
|
|
|
|
; for quick and easy access we use alternate registers to store src file pointers
|
|
|
|
ld c,(hl)
|
|
inc hl
|
|
ld b,8
|
|
|
|
; hl = pointer to next byte
|
|
; c = a partially used byte (shifted, lowest bits are valid - others RAZ)
|
|
; b = number of valid bits in c
|
|
|
|
exx
|
|
|
|
|
|
|
|
call read8bits
|
|
cp 0x1f ; check gzip magic numbers
|
|
jp nz, fail
|
|
call read8bits
|
|
cp 0x8b ; check gzip magic numbers
|
|
jp nz, fail
|
|
|
|
call read8bits
|
|
cp 0x8 ; method - only type 8 (DEFLATE) is implemented
|
|
jp nz, fail
|
|
|
|
call read8bits
|
|
ld d,a ; flags
|
|
|
|
ld a,6 ; mtime, extra_flags, os_type
|
|
call dropbytes
|
|
|
|
ld a,d
|
|
and 0x04
|
|
call nz, drop_gz_fextra
|
|
|
|
ld a,d ; read a again because any of these functions might have changed it
|
|
and 0x08
|
|
call nz, drop_gz_fname
|
|
|
|
ld a,d
|
|
and 0x10
|
|
call nz, drop_gz_fcomment
|
|
|
|
ld a,d
|
|
and 0x02
|
|
call nz, drop_gz_fhcrc
|
|
|
|
|
|
NEW_BLOCK:
|
|
|
|
call read1bit ; last
|
|
ld (this_was_last_block),a
|
|
|
|
call read2bits ; blocktype
|
|
cp 3
|
|
jp z, fail
|
|
cp 1
|
|
jp c, blocktype0
|
|
jp z, blocktype1
|
|
jp blocktype2
|
|
|
|
|
|
blocktype0: ; raw block data type
|
|
call readalign
|
|
call read8bits
|
|
ld c,a
|
|
call read8bits
|
|
ld b,a
|
|
|
|
|
|
; apparently for some reason, the block length is stored twice (and should match)
|
|
call read8bits
|
|
ld l,a
|
|
call read8bits
|
|
ld h,a
|
|
|
|
or a
|
|
sbc hl,bc
|
|
ld a,h
|
|
or l
|
|
jp nz, fail
|
|
|
|
|
|
ld de,(interim_dst)
|
|
|
|
IF 0 == defined(INFLATELIB_CONFIG__NO_MEMORY_PAGING)
|
|
|
|
try_another_page_worth:
|
|
ld l,e
|
|
ld h,d
|
|
add hl,bc
|
|
ld a,h
|
|
sub 0x80
|
|
cp 0x40
|
|
jr c, fits_in_section
|
|
|
|
; the area to be copied will fill our section C working area
|
|
|
|
|
|
ld hl, 49152
|
|
or a
|
|
sbc hl,de
|
|
|
|
ld a,l
|
|
ld l,c
|
|
ld c,a
|
|
|
|
ld a,h
|
|
ld h,b
|
|
ld b,a
|
|
|
|
or a
|
|
sbc hl,bc
|
|
ld (remaining_copy_length),hl
|
|
|
|
exx
|
|
push hl
|
|
exx
|
|
pop hl ; address of the data to copy from (guaranteeed that 16k is available)
|
|
|
|
ldir
|
|
|
|
push hl
|
|
exx
|
|
pop hl ; store the source pointer back in HL' - revisit this in a moment to sort out 16k wrapping
|
|
exx
|
|
|
|
; now, copy the filled page to the destination address
|
|
|
|
ld a,(dst.page)
|
|
out (lmpr),a
|
|
|
|
ld de,(dst)
|
|
|
|
ld hl,16384 ; number of bytes until the end of this page
|
|
or a
|
|
sbc hl,de
|
|
|
|
ld c,l
|
|
ld b,h
|
|
|
|
ld l,e
|
|
ld a,d
|
|
or 0x80
|
|
ld h,a
|
|
|
|
ldir
|
|
|
|
ld hl,0
|
|
ld (dst),hl
|
|
ld a,(dst.page)
|
|
inc a
|
|
ld (dst.page),a
|
|
dec a
|
|
dec a
|
|
ld (dst.page.minustwo),a
|
|
|
|
|
|
; now, get the gz source back into low memory
|
|
exx
|
|
bit 6,h
|
|
jr z, no_16k_rollover
|
|
|
|
ld a,h
|
|
and 0x3f
|
|
ld h,a
|
|
|
|
ld a,(src.page)
|
|
inc a
|
|
ld (src.page),a
|
|
|
|
no_16k_rollover:
|
|
exx
|
|
|
|
ld a,(src.page)
|
|
out (lmpr),a
|
|
|
|
ld de,32768
|
|
|
|
remaining_copy_length: equ $+1
|
|
ld bc,0000
|
|
|
|
jp try_another_page_worth
|
|
|
|
|
|
fits_in_section:
|
|
ENDIF
|
|
|
|
exx
|
|
push hl
|
|
exx
|
|
pop hl
|
|
ldir
|
|
push hl
|
|
exx
|
|
pop hl ; store the source pointer back in HL' - revisit this in a moment to sort out 16k wrapping
|
|
exx
|
|
ld (interim_dst),de
|
|
|
|
jp END_BLOCK
|
|
|
|
|
|
blocktype1: ; static huffman
|
|
|
|
ld hl,288
|
|
ld (literals),hl
|
|
ld a,32
|
|
ld (distances),a
|
|
|
|
ld hl,codelengths
|
|
|
|
ld b,144
|
|
ld a,8
|
|
|
|
@: ld (hl),a
|
|
inc hl
|
|
djnz @-
|
|
|
|
ld b,112
|
|
ld a,9
|
|
|
|
@: ld (hl),a
|
|
inc hl
|
|
djnz @-
|
|
|
|
ld b,24
|
|
ld a,7
|
|
|
|
@: ld (hl),a
|
|
inc hl
|
|
djnz @-
|
|
|
|
ld b,8
|
|
ld a,8
|
|
|
|
@: ld (hl),a
|
|
inc hl
|
|
djnz @-
|
|
|
|
jp huffman_common
|
|
|
|
|
|
blocktype2: ; dynamic huffman
|
|
|
|
call read5bits ; HLIT
|
|
inc a
|
|
ld l,a
|
|
ld h,1
|
|
ld (literals),hl
|
|
|
|
call read5bits ; HDIST
|
|
inc a
|
|
ld (distances),a
|
|
|
|
; the shape of the huffman tree used to compress this block is presented in a huffman-encoded form
|
|
; thus first, we are given the shape of the tree used to describe it.
|
|
|
|
call read4bits
|
|
add 4
|
|
ld (code_lengths_length),a
|
|
|
|
|
|
ld hl,codelengths
|
|
ld b,20
|
|
xor a
|
|
|
|
clear_codelengths_loop:
|
|
ld (hl),a
|
|
inc hl
|
|
djnz clear_codelengths_loop
|
|
|
|
ld de, code_length_orders
|
|
ld h,codelengths/256
|
|
|
|
code_lengths_length: equ $+1
|
|
ld b,00
|
|
|
|
code_lengths_loop:
|
|
ld a,(de)
|
|
add codelengths\256
|
|
ld l,a
|
|
call read3bits
|
|
ld (hl),a
|
|
inc de
|
|
djnz code_lengths_loop
|
|
|
|
|
|
; create a huffman tree we can use from these code lengths
|
|
|
|
ld bc,19 ; (not code_lengths_length... total number of symbols, including those which may not have been described)
|
|
ld hl,codelengths
|
|
ld de,Huffman_Tables_Start
|
|
|
|
call makeHuffmanTable
|
|
|
|
|
|
; now, decode this to find the shape of the "real" huffman table
|
|
|
|
; first tree contains literals/lengths entries, second contains distances entries
|
|
|
|
|
|
|
|
ld hl,Huffman_Tables_Start
|
|
ld de,codelengths
|
|
literals: equ $+1
|
|
ld bc,0000
|
|
|
|
ld a,(distances)
|
|
add c
|
|
ld c,a
|
|
|
|
ld a,b
|
|
adc 0
|
|
ld b,a
|
|
|
|
|
|
call decodeProtoHuffman
|
|
|
|
|
|
|
|
huffman_common:
|
|
;; now we can create huffman tables from these code lengths
|
|
|
|
ld bc,(literals)
|
|
ld hl, codelengths
|
|
ld de,Huffman_Tables_Start
|
|
|
|
call makeHuffmanTable
|
|
|
|
ld de,(next_huffman_table_store)
|
|
ld (Distance_Huffman_Tables_Start),de
|
|
|
|
ld hl, codelengths
|
|
ld bc,(literals)
|
|
add hl,bc
|
|
distances: equ $+1
|
|
ld c,00
|
|
ld b,0
|
|
|
|
call makeHuffmanTable
|
|
|
|
|
|
|
|
; now we can decode the actual data from the file!
|
|
|
|
interim_dst: equ $+1
|
|
ld de,32768 ; not true!
|
|
|
|
|
|
Distance_Huffman_Tables_Start: equ $+1
|
|
ld hl,0000
|
|
|
|
ld a,l
|
|
rra
|
|
rra
|
|
ld l,a
|
|
|
|
ld (Distance_HuffmanTableBegin),hl
|
|
|
|
|
|
ld hl, Huffman_Tables_Start
|
|
|
|
ld a,l
|
|
rra
|
|
rra
|
|
ld l,a
|
|
|
|
ld (HuffmanTableBegin),hl
|
|
|
|
|
|
decode_tableloop:
|
|
|
|
exx
|
|
ld a,b
|
|
exx
|
|
add a
|
|
add a
|
|
ld (decode_tableloop_jr_offset),a
|
|
|
|
|
|
|
|
HuffmanTableBegin: equ $+1
|
|
ld hl,0000
|
|
ld a,l
|
|
|
|
decode_tableloop_jr_offset: equ $+1
|
|
jr $+2
|
|
jp fail
|
|
nop
|
|
jp decode_tableloop_unrolled_0
|
|
nop
|
|
jp decode_tableloop_unrolled_7
|
|
nop
|
|
jp decode_tableloop_unrolled_6
|
|
nop
|
|
jp decode_tableloop_unrolled_5
|
|
nop
|
|
jp decode_tableloop_unrolled_4
|
|
nop
|
|
jp decode_tableloop_unrolled_3
|
|
nop
|
|
jp decode_tableloop_unrolled_2
|
|
nop
|
|
jp decode_tableloop_unrolled_1
|
|
|
|
|
|
UNROLL: equ FOR 8
|
|
|
|
decode_tableloop_unrolled_{UNROLL}:
|
|
; as read1bit, in-lined (can also unroll loop and Duff on b') - returns bit in CARRY FLAG ONLY
|
|
|
|
exx
|
|
if (UNROLL > 0)
|
|
dec b
|
|
or a
|
|
rr c
|
|
ELSE
|
|
or a
|
|
rr c
|
|
|
|
ld b,8
|
|
ld c,(hl)
|
|
inc hl
|
|
ENDIF
|
|
exx
|
|
|
|
|
|
adc a
|
|
add a
|
|
ld l,a
|
|
|
|
ld a,(hl)
|
|
bit 7,a
|
|
jp z, decoded_symbol
|
|
|
|
inc hl
|
|
ld h,(hl)
|
|
|
|
NEXT UNROLL
|
|
jp decode_tableloop_unrolled_0
|
|
|
|
decoded_symbol:
|
|
or a
|
|
jr nz, decode_special
|
|
|
|
|
|
inc hl
|
|
ld a,(hl) ; this is ONLY so we can see value in the debugger
|
|
ldi ; copy a byte from hl to de, inc de [we don't care about hl or bc, but ldi is still faster]
|
|
|
|
IF 0 == defined(INFLATELIB_CONFIG__NO_MEMORY_PAGING)
|
|
bit 6,D
|
|
jp nz, copy_decoded_page_to_destination
|
|
|
|
ENDIF
|
|
|
|
jp decode_tableloop
|
|
|
|
|
|
end_of_huffman_block:
|
|
|
|
|
|
ld (interim_dst),de
|
|
jp END_BLOCK
|
|
|
|
|
|
decode_special:
|
|
inc hl
|
|
ld a,(hl)
|
|
or a
|
|
jr z, end_of_huffman_block
|
|
|
|
|
|
; thus we have decoded a length code
|
|
; a few more bits *may* be used to work out exactly how many bytes are repeated
|
|
|
|
; then a decode from the distance tree will tell us where the sequence is copied from
|
|
|
|
|
|
|
|
ld hl, lengthcodetable - 4 ; because code offsets start at 1 not 0
|
|
add a
|
|
add a
|
|
add l
|
|
ld l,a
|
|
ld a,h
|
|
adc 0
|
|
ld h,a
|
|
|
|
ld c,(hl)
|
|
inc hl
|
|
ld b,(hl)
|
|
inc hl
|
|
ld a,(hl)
|
|
inc hl
|
|
ld h,(hl)
|
|
ld l,a
|
|
ld (@calladdress),hl
|
|
|
|
@calladdress: equ $+1
|
|
call 0000
|
|
|
|
add c
|
|
ld c,a
|
|
ld a,b
|
|
adc 0
|
|
ld b,a
|
|
|
|
; dictionary lookup length is max 258 bytes
|
|
; we can copy the whole thing without checking for page flip if we specify that the
|
|
; first 257 bytes of section D may be overwritten
|
|
; this means that after copying a page we always jump to a new symbol
|
|
; which simplifies things (i.e. never into the middle of a copy)
|
|
|
|
|
|
|
|
|
|
|
|
distance_decode_tableloop:
|
|
|
|
exx
|
|
ld a,b
|
|
exx
|
|
add a
|
|
add a
|
|
ld (distance_decode_tableloop_jr_offset),a
|
|
|
|
|
|
|
|
Distance_HuffmanTableBegin: equ $+1
|
|
ld hl,0000
|
|
ld a,l
|
|
|
|
distance_decode_tableloop_jr_offset: equ $+1
|
|
jr $+2
|
|
jp fail
|
|
nop
|
|
jp distance_decode_tableloop_unrolled_0
|
|
nop
|
|
jp distance_decode_tableloop_unrolled_7
|
|
nop
|
|
jp distance_decode_tableloop_unrolled_6
|
|
nop
|
|
jp distance_decode_tableloop_unrolled_5
|
|
nop
|
|
jp distance_decode_tableloop_unrolled_4
|
|
nop
|
|
jp distance_decode_tableloop_unrolled_3
|
|
nop
|
|
jp distance_decode_tableloop_unrolled_2
|
|
nop
|
|
jp distance_decode_tableloop_unrolled_1
|
|
|
|
|
|
UNROLL: equ FOR 8
|
|
|
|
distance_decode_tableloop_unrolled_{UNROLL}:
|
|
; as read1bit, in-lined (can also unroll loop and Duff on b') - returns bit in CARRY FLAG ONLY
|
|
|
|
exx
|
|
if (UNROLL > 0)
|
|
dec b
|
|
or a
|
|
rr c
|
|
ELSE
|
|
or a
|
|
rr c
|
|
|
|
ld b,8
|
|
ld c,(hl)
|
|
inc hl
|
|
ENDIF
|
|
exx
|
|
|
|
|
|
adc a
|
|
add a
|
|
ld l,a
|
|
|
|
ld a,(hl)
|
|
bit 7,a
|
|
jp z, distance_decoded_symbol
|
|
|
|
inc hl
|
|
ld h,(hl)
|
|
|
|
NEXT UNROLL
|
|
jp distance_decode_tableloop_unrolled_0
|
|
|
|
|
|
distance_decoded_symbol:
|
|
|
|
inc hl
|
|
ld a,(hl)
|
|
|
|
add a
|
|
ld l,a ; l = 2x original a
|
|
add a ; a = 4x orginal a
|
|
add l ; +
|
|
|
|
|
|
ld hl, distancecodetable
|
|
add l
|
|
ld l,a
|
|
ld a,h
|
|
adc 0
|
|
ld h,a
|
|
|
|
|
|
push de
|
|
|
|
|
|
ld e,(hl)
|
|
inc hl
|
|
ld d,(hl)
|
|
inc hl
|
|
|
|
push de
|
|
|
|
ld e,(hl)
|
|
inc hl
|
|
ld d,(hl)
|
|
inc hl
|
|
ld (@calladdress1),de
|
|
|
|
ld e,(hl)
|
|
inc hl
|
|
ld d,(hl)
|
|
inc hl
|
|
ld (@calladdress2),de
|
|
|
|
|
|
@calladdress1: equ $+1
|
|
call 0000
|
|
ld e,a
|
|
|
|
@calladdress2: equ $+1
|
|
call 0000
|
|
ld d,a
|
|
|
|
pop hl
|
|
|
|
add hl,de
|
|
|
|
ex de,hl
|
|
ld hl,0
|
|
or a
|
|
sbc hl, de
|
|
|
|
pop de
|
|
|
|
; bc is length, hl is 0-distance, de is current write point
|
|
|
|
add hl,de
|
|
|
|
|
|
IF 0 == defined(INFLATELIB_CONFIG__NO_MEMORY_PAGING)
|
|
; page in the real destination page
|
|
dst.page.minustwo: equ $+1
|
|
ld a,00
|
|
out (lmpr),a
|
|
ENDIF
|
|
|
|
ldir
|
|
|
|
IF 0 == defined(INFLATELIB_CONFIG__NO_MEMORY_PAGING)
|
|
src.page: equ $+1
|
|
ld a,00
|
|
out (lmpr),a
|
|
ENDIF
|
|
|
|
|
|
IF defined(INFLATELIB_CONFIG__NO_MEMORY_PAGING)
|
|
jp decode_tableloop
|
|
ELSE
|
|
bit 6,D
|
|
jp z, decode_tableloop
|
|
|
|
copy_decoded_page_to_destination:
|
|
|
|
; at this point, section C is full
|
|
; we may have written a few bytes into section D - these need to go back into C when the pointers are reset
|
|
|
|
; at the start of a file, we are writing only partial pages
|
|
; (end of file also requires a copy out of C, but that's not here)
|
|
|
|
ld (store_number_of_extra_bytes_written),de ; save this for later use
|
|
|
|
|
|
dst.page: equ $+1
|
|
ld a,00
|
|
out (lmpr),a
|
|
|
|
dst: equ $+1
|
|
ld de,0
|
|
|
|
ld a,e
|
|
or d
|
|
jr z, optimized_whole_page_copy
|
|
|
|
ld hl,16384
|
|
or a
|
|
sbc hl,de
|
|
|
|
ld c,l
|
|
ld b,h
|
|
|
|
ld l,e
|
|
ld a,d
|
|
or 0x80
|
|
ld h,a
|
|
|
|
ldir
|
|
|
|
jp finished_page_copy
|
|
|
|
optimized_whole_page_copy:
|
|
ld hl,32768
|
|
ld de,0
|
|
xor a
|
|
|
|
page_copy_loop:
|
|
for 64, ldi
|
|
dec a
|
|
jp nz, page_copy_loop
|
|
|
|
|
|
finished_page_copy:
|
|
ld hl,0
|
|
ld (dst),hl
|
|
ld a,(dst.page)
|
|
inc a
|
|
ld (dst.page),a
|
|
dec a
|
|
dec a
|
|
ld (dst.page.minustwo),a
|
|
|
|
|
|
ld de,32768
|
|
|
|
store_number_of_extra_bytes_written: equ $+1
|
|
ld bc,0000
|
|
ld a,b
|
|
and 0x3f
|
|
ld b,a
|
|
or c
|
|
jr z,no_extra_bytes_written
|
|
|
|
ld hl,49152
|
|
ldir
|
|
|
|
no_extra_bytes_written:
|
|
|
|
|
|
; put the gzdata back into low memory
|
|
|
|
; at this point we also need to check if the gzdata pointer needs to be updated
|
|
; (i.e. roll page back to section A if it has reached B)
|
|
|
|
exx
|
|
bit 6,h
|
|
jr z, no_src_rollover
|
|
|
|
ld a,h
|
|
and 0x3f
|
|
ld h,a
|
|
|
|
ld a,(src.page)
|
|
inc a
|
|
ld (src.page),a
|
|
|
|
no_src_rollover:
|
|
exx
|
|
|
|
ld a,(src.page)
|
|
out (lmpr),a
|
|
|
|
|
|
jp decode_tableloop
|
|
|
|
ENDIF
|
|
|
|
|
|
|
|
|
|
|
|
; for each length code starting at symbol-257
|
|
; a word describing how many bits to read (function call or NULL)
|
|
; a word describing how many are added to the value read (if any)
|
|
lengthcodetable:
|
|
dw 3, read0bits
|
|
dw 4, read0bits
|
|
dw 5, read0bits
|
|
dw 6, read0bits
|
|
dw 7, read0bits
|
|
dw 8, read0bits
|
|
dw 9, read0bits
|
|
dw 10, read0bits
|
|
dw 11, read1bits
|
|
dw 13, read1bits
|
|
dw 15, read1bits
|
|
dw 17, read1bits
|
|
dw 19, read2bits
|
|
dw 23, read2bits
|
|
dw 27, read2bits
|
|
dw 31, read2bits
|
|
dw 35, read3bits
|
|
dw 43, read3bits
|
|
dw 51, read3bits
|
|
dw 59, read3bits
|
|
dw 67, read4bits
|
|
dw 83, read4bits
|
|
dw 99, read4bits
|
|
dw 115, read4bits
|
|
dw 131, read5bits
|
|
dw 163, read5bits
|
|
dw 195, read5bits
|
|
dw 227, read5bits
|
|
dw 258, read0bits
|
|
|
|
distancecodetable:
|
|
dw 1, read0bits, read0bits
|
|
dw 2, read0bits, read0bits
|
|
dw 3, read0bits, read0bits
|
|
dw 4, read0bits, read0bits
|
|
dw 5, read1bits, read0bits
|
|
dw 7, read1bits, read0bits
|
|
dw 9, read2bits, read0bits
|
|
dw 13, read2bits, read0bits
|
|
dw 17, read3bits, read0bits
|
|
dw 25, read3bits, read0bits
|
|
dw 33, read4bits, read0bits
|
|
dw 49, read4bits, read0bits
|
|
dw 65, read5bits, read0bits
|
|
dw 97, read5bits, read0bits
|
|
dw 129, read6bits, read0bits
|
|
dw 193, read6bits, read0bits
|
|
dw 257, read7bits, read0bits
|
|
dw 385, read7bits, read0bits
|
|
dw 513, read8bits, read0bits
|
|
dw 769, read8bits, read0bits
|
|
dw 1025, read8bits, read1bits
|
|
dw 1537, read8bits, read1bits
|
|
dw 2049, read8bits, read2bits
|
|
dw 3073, read8bits, read2bits
|
|
dw 4097, read8bits, read3bits
|
|
dw 6145, read8bits, read3bits
|
|
dw 8193, read8bits, read4bits
|
|
dw 12289, read8bits, read4bits
|
|
dw 16385, read8bits, read5bits
|
|
dw 24577, read8bits, read5bits
|
|
|
|
|
|
|
|
|
|
END_BLOCK:
|
|
|
|
this_was_last_block: equ $+1
|
|
ld a,00
|
|
or a
|
|
jp z, NEW_BLOCK
|
|
|
|
; copy any remaining bytes from C into the destination page
|
|
|
|
IF 0 == defined(INFLATELIB_CONFIG__NO_MEMORY_PAGING)
|
|
|
|
|
|
ld a,(dst.page)
|
|
out (lmpr),a
|
|
|
|
ld de,(dst) ; start of written data (A)
|
|
|
|
ld hl,(interim_dst) ; one past the end of written data (C)
|
|
|
|
or a
|
|
sbc hl,de
|
|
|
|
ld c,l
|
|
ld a,h
|
|
and 0x3f
|
|
ld b,a
|
|
or c
|
|
jr z, no_bytes_to_be_written_in_next_page
|
|
|
|
ld l,e
|
|
ld a,d
|
|
and 0x3f
|
|
ld d,a
|
|
or 0x80
|
|
ld h,a
|
|
|
|
ldir
|
|
no_bytes_to_be_written_in_next_page:
|
|
ENDIF
|
|
|
|
exit:
|
|
|
|
IF 0 == defined(INFLATELIB_CONFIG__NO_MEMORY_PAGING)
|
|
inflate_lmprs: equ $+1
|
|
ld a,00
|
|
out (lmpr),a
|
|
ENDIF
|
|
|
|
xor a
|
|
ret
|
|
|
|
fail:
|
|
IF 0 == defined(INFLATELIB_CONFIG__NO_MEMORY_PAGING)
|
|
ld a,(inflate_lmprs)
|
|
out (lmpr),a
|
|
ENDIF
|
|
|
|
ld a,1
|
|
scf
|
|
ret
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
drop_gz_fextra:
|
|
push hl
|
|
push bc
|
|
|
|
call read8bits
|
|
ld l,a
|
|
call read8bits
|
|
ld h,a
|
|
or c
|
|
jp z,gz_fextra_ret
|
|
|
|
|
|
ld bc,255
|
|
|
|
gz_fextra_loop:
|
|
ld a,h
|
|
or a
|
|
jr z, gz_fextra_lastbytes
|
|
|
|
ld a,255
|
|
call dropbytes
|
|
|
|
or a
|
|
sbc hl,bc
|
|
jr gz_fextra_loop
|
|
|
|
gz_fextra_lastbytes:
|
|
ld a,l
|
|
call dropbytes
|
|
|
|
gz_fextra_ret:
|
|
pop bc
|
|
pop hl
|
|
ret
|
|
|
|
|
|
|
|
drop_gz_fname:
|
|
drop_gz_fcomment:
|
|
|
|
drop_gz_fname_loop:
|
|
call read8bits
|
|
or a
|
|
jr nz, drop_gz_fname_loop
|
|
|
|
ret
|
|
|
|
|
|
drop_gz_fhcrc:
|
|
ld a,2
|
|
call dropbytes
|
|
ret
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
dropbytes:
|
|
or a
|
|
ret z
|
|
|
|
push bc
|
|
ld b,a
|
|
dropbytesloop:
|
|
call read8bits ; we can optimize this, by fiddling the file pointer
|
|
; but we MUST guarantee that if there are valid bits in c then they are updated to the new file position
|
|
; since this is not called too often, it's easier to be valid and slow for the time being
|
|
djnz dropbytesloop
|
|
pop bc
|
|
ret
|
|
|
|
makeHuffmanTable:
|
|
; hl points to a table of code lengths
|
|
; number of symbols in that table is bc
|
|
; de is where the caller want the initial lookup table to be store
|
|
; (further tables will be stored at incrementing addresses)
|
|
|
|
; a lookup table will consist of 16 entries 2 bytes each
|
|
; 0[7] type (0=four bits have been decoded, do more lookup / 1=symbol has been decoded
|
|
; 0[6] 1 (to confirm initialized)
|
|
; if type==0 if type==1
|
|
; 0[4-5] number of undecoded bits
|
|
; 0[0-2] address of next table/32 MSB symbol MSB
|
|
; 1[0-7] address of next table/32 LSB symbol LSB
|
|
|
|
|
|
ld (huffman_number_symbols),bc
|
|
ld (huffman_code_lengths),hl
|
|
ld (huffman_table_store),de
|
|
|
|
|
|
; 1) Count the number of codes for each code length. Let bl_count[N] be the number of codes of length N, N >= 1.
|
|
|
|
; zero out initial codelength_count
|
|
|
|
ld hl,codelength_count
|
|
|
|
ld b,18
|
|
clear_codelength_count_loop:
|
|
ld (hl),0
|
|
inc hl
|
|
ld (hl),0
|
|
inc hl
|
|
djnz clear_codelength_count_loop
|
|
|
|
; now increment in codelength_count for each length described
|
|
|
|
huffman_number_symbols: equ $+1
|
|
ld bc,0000
|
|
|
|
huffman_code_lengths: equ $+1
|
|
ld hl,0000
|
|
|
|
ld de,codelength_count
|
|
|
|
codelength_count_loop:
|
|
ld a,(hl)
|
|
inc hl
|
|
|
|
add a,a ; rla with guaranteed 0 bit clear
|
|
ld e,a
|
|
|
|
ld a,(de)
|
|
inc a
|
|
ld (de),a
|
|
or a
|
|
jr nz, @+
|
|
inc de
|
|
ld a,(de)
|
|
inc a
|
|
ld (de),a
|
|
@: dec bc
|
|
ld a,b
|
|
or c
|
|
jr nz, codelength_count_loop
|
|
|
|
; 2) Find the numerical value of the smallest code for each code length:
|
|
|
|
; bl_count[0] = 0; // because this is the number of unused codes. We need to ignore this
|
|
xor a
|
|
ld e,a
|
|
ld (de),a
|
|
inc de
|
|
ld (de),a
|
|
|
|
; code = 0;
|
|
ld e,a
|
|
ld d,a
|
|
ld c,a
|
|
|
|
; for (bits = 1; bits <= MAX_BITS; bits++) {
|
|
ld b,1
|
|
|
|
smallest_codes_loop:
|
|
|
|
; code = (code + bl_count[bits-1]) << 1;
|
|
ld h, codelength_count/256
|
|
ld a,b
|
|
dec a
|
|
add a,a
|
|
ld l,a
|
|
|
|
ld a,(hl)
|
|
add e
|
|
ld e,a
|
|
|
|
inc hl ; doesn't change carry bit, so following ADC carries through from previous add
|
|
ld a,(hl)
|
|
adc d
|
|
ld d,a
|
|
|
|
ld a,c
|
|
adc 0
|
|
ld c,a
|
|
|
|
rl e
|
|
rl d
|
|
rl c
|
|
|
|
; next_code[bits] = code;
|
|
ld h, next_code/256
|
|
ld a,b
|
|
rla
|
|
rla
|
|
ld l,a
|
|
|
|
ld (hl),e
|
|
inc hl
|
|
ld (hl),d
|
|
inc hl
|
|
ld (hl),c
|
|
inc hl
|
|
ld (hl),0
|
|
|
|
; }
|
|
inc b
|
|
ld a,b
|
|
cp 19
|
|
|
|
jr c, smallest_codes_loop
|
|
|
|
|
|
|
|
; 3) Assign numerical values to all codes, using consecutive values for all codes
|
|
; of the same length with the base values determined at step 2. Codes that are never
|
|
; used (which have a bit length of zero) must not be assigned a value.
|
|
|
|
; for (n = 0; n <= max_code; n++) {
|
|
|
|
ld bc,0
|
|
assign_huffman_codes_loop:
|
|
|
|
; len = tree[n].Len;
|
|
|
|
ld hl, (huffman_code_lengths)
|
|
add hl,bc
|
|
|
|
ld a,(hl)
|
|
|
|
; if (len != 0) {
|
|
or a
|
|
jr z, no_zerolength_code
|
|
|
|
; tree[n].Code = next_code[len];
|
|
rla
|
|
rla
|
|
ld l,a
|
|
ld h,next_code/256
|
|
|
|
push hl
|
|
|
|
ld e,(hl)
|
|
inc hl
|
|
ld d,(hl)
|
|
inc hl
|
|
ld a,(hl)
|
|
|
|
|
|
ld hl, codes
|
|
add hl,bc
|
|
add hl,bc
|
|
add hl,bc
|
|
add hl,bc
|
|
|
|
ld (hl),e
|
|
inc hl
|
|
ld (hl),d
|
|
inc hl
|
|
ld (hl),a
|
|
|
|
; next_code[len]++;
|
|
|
|
pop hl
|
|
|
|
inc e
|
|
ld (hl),e
|
|
inc hl
|
|
|
|
ld e,a
|
|
|
|
ld a,d
|
|
adc 0
|
|
ld (hl),a
|
|
inc hl
|
|
|
|
ld a,e
|
|
adc 0
|
|
ld (hl),a
|
|
|
|
; }
|
|
no_zerolength_code:
|
|
|
|
; }
|
|
inc bc
|
|
|
|
ld hl,(huffman_number_symbols)
|
|
|
|
or a
|
|
sbc hl,bc
|
|
jr nz, assign_huffman_codes_loop
|
|
|
|
|
|
|
|
|
|
; now rearrange those integers into a format we will find easier to use
|
|
|
|
ld hl,(huffman_table_store)
|
|
|
|
xor a
|
|
|
|
ld (hl),a
|
|
inc hl
|
|
ld (hl),a
|
|
inc hl
|
|
ld (hl),a
|
|
inc hl
|
|
ld (hl),a
|
|
inc hl
|
|
|
|
ld (next_huffman_table_store),hl
|
|
|
|
ld hl,0
|
|
ld (huffman_codes_tabulated),hl
|
|
|
|
|
|
ld de,(huffman_codes_tabulated)
|
|
predecode_next_symbol:
|
|
|
|
ld hl,(huffman_code_lengths)
|
|
|
|
add hl,de
|
|
|
|
ld a,(hl)
|
|
or a
|
|
jp z, symbol_not_used
|
|
ld b,a
|
|
|
|
|
|
ld hl, codes
|
|
|
|
add hl,de
|
|
add hl,de
|
|
add hl,de
|
|
add hl,de
|
|
|
|
|
|
ld d,(hl)
|
|
inc hl
|
|
ld c,(hl)
|
|
inc hl
|
|
ld a,(hl)
|
|
|
|
rra
|
|
rr c
|
|
rr d
|
|
rra
|
|
rr c
|
|
rr d
|
|
rra
|
|
|
|
ld e,a
|
|
; this means that c[7:0], d[7:0], e[7:6] contain exactly 18 bits with c containing most significant bits
|
|
|
|
; we can consider all the other bits in a consistent manner by shifting left e,d,c
|
|
; b is the number of bits actually used by this code
|
|
|
|
ld a,18
|
|
sub b
|
|
jr z, no_unused_code_bits
|
|
|
|
or a
|
|
discard_unused_code_bits:
|
|
rl e
|
|
rl d
|
|
rl c
|
|
dec a
|
|
jr nz, discard_unused_code_bits
|
|
|
|
no_unused_code_bits:
|
|
|
|
|
|
huffman_table_store: equ $+1 ; all codes start parseing at this table
|
|
ld hl,0000
|
|
|
|
next_huffman_predecode_bit:
|
|
; we now have b bits of a code store in c,d,e
|
|
; our current pointer in the table is hl
|
|
|
|
rl e
|
|
rl d
|
|
rl c
|
|
|
|
jr nc,@
|
|
|
|
inc hl
|
|
inc hl
|
|
|
|
@:
|
|
; so we have chosen a bit path, either write a symbol here or follow/write a new table entry
|
|
|
|
dec b
|
|
jr z, write_a_symbol_definition
|
|
|
|
; table redirect
|
|
|
|
ld a,(hl)
|
|
or a
|
|
jr z, write_a_table_definition
|
|
|
|
; follow the table already written
|
|
|
|
inc hl
|
|
ld h,(hl)
|
|
|
|
add a,a ; we always take the 0 branch here, and add later if necessary
|
|
add a,a
|
|
ld l,a
|
|
|
|
jr next_huffman_predecode_bit
|
|
|
|
write_a_table_definition:
|
|
|
|
push de ; store while we use more registers
|
|
|
|
next_huffman_table_store: equ $+1
|
|
ld de,0000
|
|
|
|
ld a,e
|
|
rra
|
|
scf
|
|
rra
|
|
ld (hl),a
|
|
inc hl
|
|
ld (hl),d
|
|
|
|
ld l,e
|
|
ld h,d
|
|
|
|
xor a
|
|
ld (de),a
|
|
inc de
|
|
ld (de),a
|
|
inc de
|
|
ld (de),a
|
|
inc de
|
|
ld (de),a
|
|
inc de
|
|
|
|
; the address of the bottom four pixels of attribute space in a mode 2 screen at section D
|
|
; is 63360 == 0xf780
|
|
; it can be useful to avoid these
|
|
|
|
ld a,d
|
|
cp 0xf7
|
|
jr nz, @
|
|
ld a,e
|
|
cp 0x80
|
|
jr nz, @
|
|
ld de,0xf800
|
|
|
|
@: ld (next_huffman_table_store),de
|
|
|
|
pop de
|
|
|
|
jr next_huffman_predecode_bit
|
|
|
|
|
|
write_a_symbol_definition:
|
|
|
|
huffman_codes_tabulated: equ $+1
|
|
ld de,0000
|
|
|
|
ld (hl),d
|
|
inc hl
|
|
ld (hl),e
|
|
|
|
|
|
symbol_not_used:
|
|
inc de
|
|
ld (huffman_codes_tabulated),de
|
|
|
|
ld hl,(huffman_number_symbols)
|
|
or a
|
|
sbc hl,de
|
|
|
|
ld a,h
|
|
or l
|
|
jp nz, predecode_next_symbol
|
|
|
|
ret
|
|
|
|
|
|
|
|
|
|
|
|
decodeProtoHuffman:
|
|
; decode bc huffman length codes
|
|
; to an area of memory starting at de
|
|
; using the huffman table at hl
|
|
|
|
ld a,l
|
|
rra
|
|
rra
|
|
ld l,a
|
|
ld (ProtoHuffmanTableBegin),hl
|
|
|
|
; this is called infrequently for small tables
|
|
; write it small and simple - the main one can be optimized more for speed
|
|
|
|
jr protodecode_tablejump
|
|
|
|
protodecode_tableloop:
|
|
inc hl
|
|
ld h,(hl)
|
|
ld l,a
|
|
|
|
protodecode_tablejump:
|
|
call read1bit
|
|
rra
|
|
|
|
ld a,l
|
|
adc a
|
|
add a
|
|
ld l,a
|
|
|
|
ld a,(hl)
|
|
bit 7,a
|
|
jr nz, protodecode_tableloop
|
|
|
|
|
|
inc hl
|
|
ld a,(hl)
|
|
cp 16
|
|
jr nc, protodecode_special
|
|
|
|
ld (de),a
|
|
inc de
|
|
dec bc
|
|
|
|
protodecode_loop:
|
|
|
|
ProtoHuffmanTableBegin: equ $+1
|
|
ld hl,0000
|
|
|
|
ld a,b
|
|
or c
|
|
jr nz, protodecode_tablejump
|
|
|
|
ret
|
|
|
|
protodecode_special:
|
|
cp 17
|
|
jr z, protodecode_17
|
|
jr nc, protodecode_18
|
|
|
|
protodecode_16:
|
|
|
|
call read2bits
|
|
add 3
|
|
ld h,a
|
|
|
|
dec de
|
|
ld a,(de)
|
|
inc de
|
|
|
|
@: ld (de),a
|
|
inc de
|
|
dec bc
|
|
dec h
|
|
jr nz,@
|
|
|
|
jr protodecode_loop
|
|
|
|
protodecode_17:
|
|
|
|
call read3bits
|
|
add 3
|
|
ld h,a
|
|
xor a
|
|
|
|
@: ld (de),a
|
|
inc de
|
|
dec bc
|
|
dec h
|
|
jr nz,@
|
|
|
|
jr protodecode_loop
|
|
|
|
protodecode_18:
|
|
|
|
call read7bits
|
|
add 11
|
|
ld h,a
|
|
xor a
|
|
|
|
@: ld (de),a
|
|
inc de
|
|
dec bc
|
|
dec h
|
|
jr nz,@
|
|
|
|
jr protodecode_loop
|
|
|
|
|
|
|
|
|
|
; file read routines
|
|
; huffman compression requires that we read partial bytes
|
|
|
|
; these functions use the alternate registers
|
|
; they will not affect main registers, except to return the result in A
|
|
|
|
; c' contains unused bits of a byte read from memory
|
|
; b' contains number of bits valid
|
|
; hl' contains pointer to next byte to read
|
|
|
|
; we read bits LSB first
|
|
; calls for more than one bit expect the partial bytes returned as LSB = LSB
|
|
|
|
|
|
read1bit:
|
|
read1bits:
|
|
exx
|
|
dec b
|
|
jr z,@
|
|
xor a
|
|
rr c
|
|
rla
|
|
exx
|
|
ret
|
|
@: xor a
|
|
rr c
|
|
rla
|
|
|
|
ld b,8
|
|
ld c,(hl)
|
|
inc hl
|
|
|
|
exx
|
|
ret
|
|
|
|
|
|
N: equ FOR 9
|
|
if (N >= 2)
|
|
|
|
|
|
read{N}bits:
|
|
exx
|
|
ld a,b
|
|
sub N
|
|
jr c, @l1
|
|
jr z, @l2
|
|
|
|
ld b,a
|
|
|
|
ld e,c
|
|
ld a,c
|
|
|
|
IF N <= 4
|
|
FOR N, rra
|
|
ELSE
|
|
FOR 8-N, rlca
|
|
ENDIF
|
|
|
|
and 0xff >> N
|
|
|
|
ld c,a
|
|
|
|
ld a,e
|
|
and (1<<N)-1
|
|
|
|
exx
|
|
ret
|
|
|
|
@l2:
|
|
ld a,c
|
|
|
|
ld b,8
|
|
ld c,(hl)
|
|
inc hl
|
|
|
|
exx
|
|
ret
|
|
|
|
@l1:
|
|
ld e,c
|
|
ld d,b
|
|
|
|
ld c,(hl)
|
|
inc hl
|
|
|
|
ld a,c
|
|
|
|
@: add a,a
|
|
djnz @
|
|
or e
|
|
and (1<<N)-1
|
|
ld e,a
|
|
|
|
ld a,N
|
|
sub d
|
|
ld b,a
|
|
|
|
ld a,c
|
|
@: or a
|
|
rra
|
|
djnz @
|
|
ld c,a
|
|
|
|
ld a,d
|
|
sub N
|
|
add 8
|
|
ld b,a
|
|
|
|
ld a,e
|
|
|
|
exx
|
|
ret
|
|
|
|
endif
|
|
NEXT N
|
|
|
|
read0bits:
|
|
xor a
|
|
ret
|
|
|
|
; at the start of type 0 blocks, we throw away spare bits and align reads to byte boundaries
|
|
readalign:
|
|
exx
|
|
ld c,(hl)
|
|
inc hl
|
|
ld b,8
|
|
exx
|
|
ret
|
|
|
|
|
|
|
|
code_length_orders: db 16,17,18,0,8,7,9,6,10,5,11,4,12,3,13,2,14,1,15
|
|
|
|
|
|
defm "samflate1.0.0"
|
|
db 0
|
|
|
|
|
|
print "Code Length", $-START_OF_PROGRAM
|
|
|
|
|
|
|
|
; How many of these *really* need to be aligned?
|
|
; need to count them in section D, as there may be many blocks before we finish a page in section C
|
|
|
|
ds align 256
|
|
codelength_count: ds 36 ; lengths up to 18, 2 bytes per length as (theoretically) there can be >256 symbols per length
|
|
|
|
ds align 32
|
|
codelengths: ds 288 + 33 ; single byte per entry, max size of literals tree = 287, of lengths tree = 33
|
|
|
|
ds align 256
|
|
next_code: ds 128 ; 4 bytes per (32) code length
|
|
|
|
|
|
ds align 256
|
|
codes: ds 288 * 4 ; worst case of 18bits*288 symbols (using 4 bytes per entry for easier code lookup)
|
|
; I'm aware that a maximum of 286 symbols are used - but the static huffman encoding
|
|
; contains space for 288, the last are never used but we need reserve space for them
|
|
|
|
DS ALIGN 4
|
|
Huffman_Tables_Start:
|
|
|
|
print 65534 - Huffman_Tables_Start + 2, "bytes available for Huffman predecode tables"
|
|
|