; 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<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"