diff options
author | WlodekM <[email protected]> | 2024-06-16 10:35:45 +0300 |
---|---|---|
committer | WlodekM <[email protected]> | 2024-06-16 10:35:45 +0300 |
commit | abef6da56913f1c55528103e60a50451a39628b1 (patch) | |
tree | b3c8092471ecbb73e568cd0d336efa0e7871ee8d /src/Deflate.c |
initial commit
Diffstat (limited to 'src/Deflate.c')
-rw-r--r-- | src/Deflate.c | 1317 |
1 files changed, 1317 insertions, 0 deletions
diff --git a/src/Deflate.c b/src/Deflate.c new file mode 100644 index 0000000..247b445 --- /dev/null +++ b/src/Deflate.c @@ -0,0 +1,1317 @@ +#include "Deflate.h" +#include "String.h" +#include "Logger.h" +#include "Funcs.h" +#include "Platform.h" +#include "Stream.h" +#include "Errors.h" +#include "Utils.h" + +#define Header_ReadU8(value) if ((res = s->ReadU8(s, &value))) return res; +/*########################################################################################################################* +*-------------------------------------------------------GZip header-------------------------------------------------------* +*#########################################################################################################################*/ +enum GzipState { + GZIP_STATE_HEADER1, GZIP_STATE_HEADER2, GZIP_STATE_COMPRESSIONMETHOD, GZIP_STATE_FLAGS, + GZIP_STATE_LASTMODIFIED, GZIP_STATE_COMPRESSIONFLAGS, GZIP_STATE_OPERATINGSYSTEM, + GZIP_STATE_HEADERCHECKSUM, GZIP_STATE_FILENAME, GZIP_STATE_COMMENT, GZIP_STATE_DONE +}; + +void GZipHeader_Init(struct GZipHeader* header) { + header->state = GZIP_STATE_HEADER1; + header->done = false; + header->flags = 0; + header->partsRead = 0; +} + +cc_result GZipHeader_Read(struct Stream* s, struct GZipHeader* header) { + cc_uint8 tmp; + cc_result res; + switch (header->state) { + + case GZIP_STATE_HEADER1: + Header_ReadU8(tmp); + if (tmp != 0x1F) return GZIP_ERR_HEADER1; + header->state++; + + /* FALLTHRU */ + case GZIP_STATE_HEADER2: + Header_ReadU8(tmp); + if (tmp != 0x8B) return GZIP_ERR_HEADER2; + header->state++; + + /* FALLTHRU */ + case GZIP_STATE_COMPRESSIONMETHOD: + Header_ReadU8(tmp); + if (tmp != 0x08) return GZIP_ERR_METHOD; + header->state++; + + /* FALLTHRU */ + case GZIP_STATE_FLAGS: + Header_ReadU8(tmp); + header->flags = tmp; + if (header->flags & 0x04) return GZIP_ERR_FLAGS; + header->state++; + + /* FALLTHRU */ + case GZIP_STATE_LASTMODIFIED: + for (; header->partsRead < 4; header->partsRead++) { + Header_ReadU8(tmp); + } + header->state++; + header->partsRead = 0; + + /* FALLTHRU */ + case GZIP_STATE_COMPRESSIONFLAGS: + Header_ReadU8(tmp); + header->state++; + + /* FALLTHRU */ + case GZIP_STATE_OPERATINGSYSTEM: + Header_ReadU8(tmp); + header->state++; + + /* FALLTHRU */ + case GZIP_STATE_FILENAME: + if (header->flags & 0x08) { + for (; ;) { + Header_ReadU8(tmp); + if (tmp == '\0') break; + } + } + header->state++; + + /* FALLTHRU */ + case GZIP_STATE_COMMENT: + if (header->flags & 0x10) { + for (; ;) { + Header_ReadU8(tmp); + if (tmp == '\0') break; + } + } + header->state++; + + /* FALLTHRU */ + case GZIP_STATE_HEADERCHECKSUM: + if (header->flags & 0x02) { + for (; header->partsRead < 2; header->partsRead++) { + Header_ReadU8(tmp); + } + } + header->state++; + header->partsRead = 0; + header->done = true; + } + return 0; +} + + +/*########################################################################################################################* +*-------------------------------------------------------ZLib header-------------------------------------------------------* +*#########################################################################################################################*/ +enum ZlibState { ZLIB_STATE_COMPRESSIONMETHOD, ZLIB_STATE_FLAGS, ZLIB_STATE_DONE }; + +void ZLibHeader_Init(struct ZLibHeader* header) { + header->state = ZLIB_STATE_COMPRESSIONMETHOD; + header->done = false; +} + +cc_result ZLibHeader_Read(struct Stream* s, struct ZLibHeader* header) { + cc_uint8 tmp; + cc_result res; + switch (header->state) { + + case ZLIB_STATE_COMPRESSIONMETHOD: + Header_ReadU8(tmp); + if ((tmp & 0x0F) != 0x08) return ZLIB_ERR_METHOD; + /* Upper 4 bits are window size (ignored) */ + header->state++; + + /* FALLTHRU */ + case ZLIB_STATE_FLAGS: + Header_ReadU8(tmp); + if (tmp & 0x20) return ZLIB_ERR_FLAGS; + header->state++; + header->done = true; + } + return 0; +} + + +/*########################################################################################################################* +*--------------------------------------------------Inflate (decompress)---------------------------------------------------* +*#########################################################################################################################*/ +enum INFLATE_STATE_ { + INFLATE_STATE_HEADER, INFLATE_STATE_UNCOMPRESSED_HEADER, + INFLATE_STATE_UNCOMPRESSED_DATA, INFLATE_STATE_DYNAMIC_HEADER, + INFLATE_STATE_DYNAMIC_CODELENS, INFLATE_STATE_DYNAMIC_LITSDISTS, + INFLATE_STATE_DYNAMIC_LITSDISTSREPEAT, INFLATE_STATE_COMPRESSED_LIT, + INFLATE_STATE_COMPRESSED_LITEXTRA, INFLATE_STATE_COMPRESSED_DIST, + INFLATE_STATE_COMPRESSED_DISTEXTRA, INFLATE_STATE_COMPRESSED_DATA, + INFLATE_STATE_FASTCOMPRESSED, INFLATE_STATE_DONE +}; + +/* Insert next byte into the bit buffer */ +#define Inflate_GetByte(state) state->AvailIn--; state->Bits |= (cc_uint32)(*state->NextIn++) << state->NumBits; state->NumBits += 8; +/* Retrieves bits from the bit buffer */ +#define Inflate_PeekBits(state, bits) (state->Bits & ((1UL << (bits)) - 1UL)) +/* Consumes/eats up bits from the bit buffer */ +#define Inflate_ConsumeBits(state, bits) state->Bits >>= (bits); state->NumBits -= (bits); +/* Aligns bit buffer to be on a byte boundary */ +#define Inflate_AlignBits(state) cc_uint32 alignSkip = state->NumBits & 7; Inflate_ConsumeBits(state, alignSkip); +/* Ensures there are 'bitsCount' bits, or returns if not */ +#define Inflate_EnsureBits(state, bitsCount) while (state->NumBits < bitsCount) { if (!state->AvailIn) return; Inflate_GetByte(state); } +/* Ensures there are 'bitsCount' bits */ +#define Inflate_UNSAFE_EnsureBits(state, bitsCount) while (state->NumBits < bitsCount) { Inflate_GetByte(state); } +/* Peeks then consumes given bits */ +#define Inflate_ReadBits(state, bitsCount) Inflate_PeekBits(state, bitsCount); Inflate_ConsumeBits(state, bitsCount); +/* Sets to given result and sets state to DONE */ +#define Inflate_Fail(state, res) state->result = res; state->State = INFLATE_STATE_DONE; + +/* Goes to the next state, after having read data of a block */ +#define Inflate_NextBlockState(state) (state->LastBlock ? INFLATE_STATE_DONE : INFLATE_STATE_HEADER) +/* Goes to the next state, after having finished reading a compressed entry */ +#define Inflate_NextCompressState(state) ((state->AvailIn >= INFLATE_FASTINF_IN && state->AvailOut >= INFLATE_FASTINF_OUT) ? INFLATE_STATE_FASTCOMPRESSED : INFLATE_STATE_COMPRESSED_LIT) +/* The maximum amount of bytes that can be output is 258 */ +#define INFLATE_FASTINF_OUT 258 +/* The most input bytes required for huffman codes and extra data is 16 + 5 + 16 + 13 bits. Add 3 extra bytes to account for putting data into the bit buffer. */ +#define INFLATE_FASTINF_IN 10 + +static cc_uint32 Huffman_ReverseBits(cc_uint32 n, cc_uint8 bits) { + n = ((n & 0xAAAA) >> 1) | ((n & 0x5555) << 1); + n = ((n & 0xCCCC) >> 2) | ((n & 0x3333) << 2); + n = ((n & 0xF0F0) >> 4) | ((n & 0x0F0F) << 4); + n = ((n & 0xFF00) >> 8) | ((n & 0x00FF) << 8); + return n >> (16 - bits); +} + +/* Builds a huffman tree, based on input lengths of each codeword */ +static cc_result Huffman_Build(struct HuffmanTable* table, const cc_uint8* bitLens, int count) { + int bl_count[INFLATE_MAX_BITS], bl_offsets[INFLATE_MAX_BITS]; + int code, offset, value; + int i, j; + + /* Initialise 'zero bit length' codewords */ + table->firstCodewords[0] = 0; + table->firstOffsets[0] = 0; + table->endCodewords[0] = 0; + + /* Count number of codewords assigned to each bit length */ + for (i = 0; i < INFLATE_MAX_BITS; i++) bl_count[i] = 0; + for (i = 0; i < count; i++) { + bl_count[bitLens[i]]++; + } + + /* Ensure huffman tree actually makes sense */ + bl_count[0] = 0; + for (i = 1; i < INFLATE_MAX_BITS; i++) { + /* Check if too many huffman codes for bit length */ + if (bl_count[i] > (1 << i)) return INF_ERR_NUM_CODES; + } + + /* Compute the codewords for the huffman tree. + * Codewords are ordered, so consider this example tree: + * 2 of length 2, 3 of length 3, 1 of length 4 + * Codewords produced would be: 00,01 100,101,110, 1110 + */ + code = 0; offset = 0; + for (i = 1; i < INFLATE_MAX_BITS; i++) { + code = (code + bl_count[i - 1]) << 1; + bl_offsets[i] = offset; + + table->firstCodewords[i] = code; + table->firstOffsets[i] = offset; + offset += bl_count[i]; + + /* Last codeword is actually: code + (bl_count[i] - 1) + * However, when decoding we peform < against this value though, so need to add 1 here. + * This way, don't need to special case bit lengths with 0 codewords when decoding. + */ + if (bl_count[i]) { + table->endCodewords[i] = code + bl_count[i]; + } else { + table->endCodewords[i] = 0; + } + } + + /* Assigns values to each codeword. + * Note that although codewords are ordered, values may not be. + * Some values may also not be assigned to any codeword. + */ + value = 0; + Mem_Set(table->fast, UInt8_MaxValue, sizeof(table->fast)); + for (i = 0; i < count; i++, value++) { + int len = bitLens[i]; + if (!len) continue; + table->values[bl_offsets[len]] = value; + + /* Compute the accelerated lookup table values for this codeword. + * For example, assume len = 4 and codeword = 0100 + * - Shift it left to be 0100_00000 + * - Then, for all the indices from 0100_00000 to 0100_11111, + * - bit reverse index, as huffman codes are read backwards + * - set fast value to specify a 'value' value, and to skip 'len' bits + */ + if (len <= INFLATE_FAST_BITS) { + cc_int16 packed = (cc_int16)((len << INFLATE_FAST_LEN_SHIFT) | value); + int codeword = table->firstCodewords[len] + (bl_offsets[len] - table->firstOffsets[len]); + codeword <<= (INFLATE_FAST_BITS - len); + + for (j = 0; j < 1 << (INFLATE_FAST_BITS - len); j++, codeword++) { + int index = Huffman_ReverseBits(codeword, INFLATE_FAST_BITS); + table->fast[index] = packed; + } + } + bl_offsets[len]++; + } + return 0; +} + +/* Attempts to read the next huffman encoded value from the bitstream, using given table */ +/* Returns -1 if there are insufficient bits to read the value */ +static int Huffman_Decode(struct InflateState* state, struct HuffmanTable* table) { + cc_uint32 i, j, codeword; + int packed, bits, offset; + + /* Buffer as many bits as possible */ + while (state->NumBits <= INFLATE_MAX_BITS) { + if (!state->AvailIn) break; + Inflate_GetByte(state); + } + + /* Try fast accelerated table lookup */ + if (state->NumBits >= INFLATE_FAST_BITS) { + packed = table->fast[Inflate_PeekBits(state, INFLATE_FAST_BITS)]; + if (packed >= 0) { + bits = packed >> INFLATE_FAST_LEN_SHIFT; + Inflate_ConsumeBits(state, bits); + return packed & INFLATE_FAST_VAL_MASK; + } + } + + /* Slow, bit by bit lookup */ + codeword = 0; + for (i = 1, j = 0; i < INFLATE_MAX_BITS; i++, j++) { + if (state->NumBits < i) return -1; + codeword = (codeword << 1) | ((state->Bits >> j) & 1); + + if (codeword < table->endCodewords[i]) { + offset = table->firstOffsets[i] + (codeword - table->firstCodewords[i]); + Inflate_ConsumeBits(state, i); + return table->values[offset]; + } + } + + Inflate_Fail(state, INF_ERR_INVALID_CODE); + return -1; +} + +/* Inline the common <= 9 bits case */ +#define Huffman_UNSAFE_Decode(state, table, result) \ +{\ + Inflate_UNSAFE_EnsureBits(state, INFLATE_MAX_BITS);\ + packed = table.fast[Inflate_PeekBits(state, INFLATE_FAST_BITS)];\ + if (packed >= 0) {\ + consumedBits = packed >> INFLATE_FAST_BITS;\ + Inflate_ConsumeBits(state, consumedBits);\ + result = packed & 0x1FF;\ + } else {\ + result = Huffman_UNSAFE_Decode_Slow(state, &table);\ + }\ +} + +static int Huffman_UNSAFE_Decode_Slow(struct InflateState* state, struct HuffmanTable* table) { + cc_uint32 i, j, codeword; + int offset; + + /* Slow, bit by bit lookup. Need to reverse order for huffman. */ + codeword = Inflate_PeekBits(state, INFLATE_FAST_BITS); + codeword = Huffman_ReverseBits(codeword, INFLATE_FAST_BITS); + + for (i = INFLATE_FAST_BITS + 1, j = INFLATE_FAST_BITS; i < INFLATE_MAX_BITS; i++, j++) { + codeword = (codeword << 1) | ((state->Bits >> j) & 1); + + if (codeword < table->endCodewords[i]) { + offset = table->firstOffsets[i] + (codeword - table->firstCodewords[i]); + Inflate_ConsumeBits(state, i); + return table->values[offset]; + } + } + + Inflate_Fail(state, INF_ERR_INVALID_CODE); + /* Need to exit the fast decode loop */ + /* TODO: This means a few garbage bytes can get written */ + /* to the output, but it probably doesn't matter */ + state->AvailIn = 0; + return 0; +} + +void Inflate_Init2(struct InflateState* state, struct Stream* source) { + state->State = INFLATE_STATE_HEADER; + state->LastBlock = false; + state->Bits = 0; + state->NumBits = 0; + state->NextIn = state->Input; + state->AvailIn = 0; + state->Output = NULL; + state->AvailOut = 0; + state->Source = source; + state->WindowIndex = 0; + state->result = 0; +} + +static const cc_uint8 fixed_lits[INFLATE_MAX_LITS] = { + 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, + 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, + 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, + 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, + 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, + 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, + 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, + 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, + 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, 7,7,7,7,7,7,7,7,8,8,8,8,8,8,8,8 +}; +static const cc_uint8 fixed_dists[INFLATE_MAX_DISTS] = { + 5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5, 5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5 +}; + +static const cc_uint16 len_base[31] = { + 3,4,5,6,7,8,9,10,11,13, + 15,17,19,23,27,31,35,43,51,59, + 67,83,99,115,131,163,195,227,258,0,0 +}; +static const cc_uint8 len_bits[31] = { + 0,0,0,0,0,0,0,0,1,1, + 1,1,2,2,2,2,3,3,3,3, + 4,4,4,4,5,5,5,5,0,0,0 +}; +static const cc_uint16 dist_base[32] = { + 1,2,3,4,5,7,9,13,17,25, + 33,49,65,97,129,193,257,385,513,769, + 1025,1537,2049,3073,4097,6145,8193,12289,16385,24577,0,0 +}; +static const cc_uint8 dist_bits[32] = { + 0,0,0,0,1,1,2,2,3,3, + 4,4,5,5,6,6,7,7,8,8, + 9,9,10,10,11,11,12,12,13,13,0,0 +}; +static const cc_uint8 codelens_order[INFLATE_MAX_CODELENS] = { + 16,17,18,0,8,7,9,6,10,5,11,4,12,3,13,2,14,1,15 +}; + +static void Inflate_InflateFast(struct InflateState* s) { + /* huffman variables */ + cc_uint32 lit, len, dist; + cc_uint32 bits, lenIdx, distIdx; + int packed, consumedBits; + + /* window variables */ + cc_uint8* window; + cc_uint32 i, curIdx, startIdx; + cc_uint32 copyStart, copyLen, partLen; + + window = s->Window; + curIdx = s->WindowIndex; + copyStart = s->WindowIndex; + copyLen = 0; + +#define INFLATE_FAST_COPY_MAX (INFLATE_WINDOW_SIZE - INFLATE_FASTINF_OUT) + while (s->AvailOut >= INFLATE_FASTINF_OUT && s->AvailIn >= INFLATE_FASTINF_IN && copyLen < INFLATE_FAST_COPY_MAX) { + Huffman_UNSAFE_Decode(s, s->Table.Lits, lit); + + if (lit <= 256) { + if (lit < 256) { + window[curIdx] = (cc_uint8)lit; + s->AvailOut--; copyLen++; + curIdx = (curIdx + 1) & INFLATE_WINDOW_MASK; + } else { + s->State = Inflate_NextBlockState(s); + break; + } + } else { + lenIdx = lit - 257; + bits = len_bits[lenIdx]; + Inflate_UNSAFE_EnsureBits(s, bits); + len = len_base[lenIdx] + Inflate_ReadBits(s, bits); + + Huffman_UNSAFE_Decode(s, s->TableDists, distIdx); + bits = dist_bits[distIdx]; + Inflate_UNSAFE_EnsureBits(s, bits); + dist = dist_base[distIdx] + Inflate_ReadBits(s, bits); + + /* Window infinitely repeats like ...xyz|uvwxyz|uvwxyz|uvw... */ + /* If start and end don't cross a boundary, can avoid masking index */ + startIdx = (curIdx - dist) & INFLATE_WINDOW_MASK; + if (curIdx >= startIdx && (curIdx + len) < INFLATE_WINDOW_SIZE) { + cc_uint8* src = &window[startIdx]; + cc_uint8* dst = &window[curIdx]; + + for (i = 0; i < (len & ~0x3); i += 4) { + *dst++ = *src++; *dst++ = *src++; *dst++ = *src++; *dst++ = *src++; + } + for (; i < len; i++) { *dst++ = *src++; } + } else { + for (i = 0; i < len; i++) { + window[(curIdx + i) & INFLATE_WINDOW_MASK] = window[(startIdx + i) & INFLATE_WINDOW_MASK]; + } + } + curIdx = (curIdx + len) & INFLATE_WINDOW_MASK; + s->AvailOut -= len; copyLen += len; + } + } + + s->WindowIndex = curIdx; + if (!copyLen) return; + + if (copyStart + copyLen < INFLATE_WINDOW_SIZE) { + Mem_Copy(s->Output, &s->Window[copyStart], copyLen); + s->Output += copyLen; + } else { + partLen = INFLATE_WINDOW_SIZE - copyStart; + Mem_Copy(s->Output, &s->Window[copyStart], partLen); + s->Output += partLen; + Mem_Copy(s->Output, s->Window, copyLen - partLen); + s->Output += (copyLen - partLen); + } +} + +void Inflate_Process(struct InflateState* s) { + cc_uint32 len, dist, nlen; + cc_uint32 i, bits; + cc_uint32 blockHeader; + cc_result res; + + /* len/dist table variables */ + cc_uint32 distIdx, lenIdx; + int lit; + /* code lens table variables */ + cc_uint32 count, repeatCount; + cc_uint8 repeatValue; + /* window variables */ + cc_uint32 startIdx, curIdx; + cc_uint32 copyLen, windowCopyLen; + + for (;;) { + switch (s->State) { + case INFLATE_STATE_HEADER: { + Inflate_EnsureBits(s, 3); + blockHeader = Inflate_ReadBits(s, 3); + s->LastBlock = blockHeader & 1; + + switch (blockHeader >> 1) { + case 0: { /* Uncompressed block */ + Inflate_AlignBits(s); + s->State = INFLATE_STATE_UNCOMPRESSED_HEADER; + } break; + + case 1: { /* Fixed/static huffman compressed */ + (void)Huffman_Build(&s->Table.Lits, fixed_lits, INFLATE_MAX_LITS); + (void)Huffman_Build(&s->TableDists, fixed_dists, INFLATE_MAX_DISTS); + s->State = Inflate_NextCompressState(s); + } break; + + case 2: { /* Dynamic huffman compressed */ + s->State = INFLATE_STATE_DYNAMIC_HEADER; + } break; + + case 3: { + Inflate_Fail(s, INF_ERR_BLOCKTYPE); + } break; + + } + break; + } + + case INFLATE_STATE_UNCOMPRESSED_HEADER: { + Inflate_EnsureBits(s, 32); + len = Inflate_ReadBits(s, 16); + nlen = Inflate_ReadBits(s, 16); + + if (len != (nlen ^ 0xFFFFUL)) { + Inflate_Fail(s, INF_ERR_BLOCKTYPE); return; + } + s->Index = len; /* Reuse for 'uncompressed length' */ + s->State = INFLATE_STATE_UNCOMPRESSED_DATA; + } + + /* FALLTHRU */ + case INFLATE_STATE_UNCOMPRESSED_DATA: { + /* read bits left in bit buffer (slow way) */ + while (s->NumBits && s->AvailOut && s->Index) { + *s->Output = Inflate_ReadBits(s, 8); + s->Window[s->WindowIndex] = *s->Output; + + s->WindowIndex = (s->WindowIndex + 1) & INFLATE_WINDOW_MASK; + s->Output++; s->AvailOut--; s->Index--; + } + if (!s->AvailIn || !s->AvailOut) return; + + copyLen = min(s->AvailIn, s->AvailOut); + copyLen = min(copyLen, s->Index); + if (copyLen > 0) { + Mem_Copy(s->Output, s->NextIn, copyLen); + windowCopyLen = INFLATE_WINDOW_SIZE - s->WindowIndex; + windowCopyLen = min(windowCopyLen, copyLen); + + Mem_Copy(&s->Window[s->WindowIndex], s->Output, windowCopyLen); + /* Wrap around remainder of copy to start from beginning of window */ + if (windowCopyLen < copyLen) { + Mem_Copy(s->Window, &s->Output[windowCopyLen], copyLen - windowCopyLen); + } + + s->WindowIndex = (s->WindowIndex + copyLen) & INFLATE_WINDOW_MASK; + s->Output += copyLen; s->AvailOut -= copyLen; s->Index -= copyLen; + s->NextIn += copyLen; s->AvailIn -= copyLen; + } + + if (!s->Index) { s->State = Inflate_NextBlockState(s); } + break; + } + + case INFLATE_STATE_DYNAMIC_HEADER: { + Inflate_EnsureBits(s, 14); + s->NumLits = 257 + Inflate_ReadBits(s, 5); + s->NumDists = 1 + Inflate_ReadBits(s, 5); + s->NumCodeLens = 4 + Inflate_ReadBits(s, 4); + s->Index = 0; + s->State = INFLATE_STATE_DYNAMIC_CODELENS; + } + /* FALLTHRU */ + + case INFLATE_STATE_DYNAMIC_CODELENS: { + while (s->Index < s->NumCodeLens) { + Inflate_EnsureBits(s, 3); + i = codelens_order[s->Index]; + s->Buffer[i] = Inflate_ReadBits(s, 3); + s->Index++; + } + for (i = s->NumCodeLens; i < INFLATE_MAX_CODELENS; i++) { + s->Buffer[codelens_order[i]] = 0; + } + + s->Index = 0; + s->State = INFLATE_STATE_DYNAMIC_LITSDISTS; + res = Huffman_Build(&s->Table.CodeLens, s->Buffer, INFLATE_MAX_CODELENS); + if (res) { Inflate_Fail(s, res); return; } + } + + /* FALLTHRU */ + case INFLATE_STATE_DYNAMIC_LITSDISTS: { + count = s->NumLits + s->NumDists; + while (s->Index < count) { + int bits = Huffman_Decode(s, &s->Table.CodeLens); + if (bits < 16) { + if (bits == -1) return; + s->Buffer[s->Index] = (cc_uint8)bits; + s->Index++; + } else { + s->TmpCodeLens = bits; + s->State = INFLATE_STATE_DYNAMIC_LITSDISTSREPEAT; + break; + } + } + + if (s->Index == count) { + s->Index = 0; + s->State = Inflate_NextCompressState(s); + + res = Huffman_Build(&s->Table.Lits, s->Buffer, s->NumLits); + if (res) { Inflate_Fail(s, res); return; } + res = Huffman_Build(&s->TableDists, s->Buffer + s->NumLits, s->NumDists); + if (res) { Inflate_Fail(s, res); return; } + } + break; + } + + case INFLATE_STATE_DYNAMIC_LITSDISTSREPEAT: { + switch (s->TmpCodeLens) { + case 16: + Inflate_EnsureBits(s, 2); + repeatCount = Inflate_ReadBits(s, 2); + if (!s->Index) { Inflate_Fail(s, INF_ERR_REPEAT_BEG); return; } + repeatCount += 3; repeatValue = s->Buffer[s->Index - 1]; + break; + + case 17: + Inflate_EnsureBits(s, 3); + repeatCount = Inflate_ReadBits(s, 3); + repeatCount += 3; repeatValue = 0; + break; + + case 18: + Inflate_EnsureBits(s, 7); + repeatCount = Inflate_ReadBits(s, 7); + repeatCount += 11; repeatValue = 0; + break; + } + + count = s->NumLits + s->NumDists; + if (s->Index + repeatCount > count) { + Inflate_Fail(s, INF_ERR_REPEAT_END); return; + } + + Mem_Set(&s->Buffer[s->Index], repeatValue, repeatCount); + s->Index += repeatCount; + s->State = INFLATE_STATE_DYNAMIC_LITSDISTS; + break; + } + + case INFLATE_STATE_COMPRESSED_LIT: { + if (!s->AvailOut) return; + lit = Huffman_Decode(s, &s->Table.Lits); + + if (lit < 256) { + if (lit == -1) return; + *s->Output = (cc_uint8)lit; + s->Window[s->WindowIndex] = (cc_uint8)lit; + s->Output++; s->AvailOut--; + s->WindowIndex = (s->WindowIndex + 1) & INFLATE_WINDOW_MASK; + break; + } else if (lit == 256) { + s->State = Inflate_NextBlockState(s); + break; + } else { + s->TmpLit = lit - 257; + s->State = INFLATE_STATE_COMPRESSED_LITEXTRA; + } + } + + case INFLATE_STATE_COMPRESSED_LITEXTRA: { + lenIdx = s->TmpLit; + bits = len_bits[lenIdx]; + Inflate_EnsureBits(s, bits); + s->TmpLit = len_base[lenIdx] + Inflate_ReadBits(s, bits); + s->State = INFLATE_STATE_COMPRESSED_DIST; + } + + /* FALLTHRU */ + case INFLATE_STATE_COMPRESSED_DIST: { + s->TmpDist = Huffman_Decode(s, &s->TableDists); + if (s->TmpDist == -1) return; + s->State = INFLATE_STATE_COMPRESSED_DISTEXTRA; + } + + /* FALLTHRU */ + case INFLATE_STATE_COMPRESSED_DISTEXTRA: { + distIdx = s->TmpDist; + bits = dist_bits[distIdx]; + Inflate_EnsureBits(s, bits); + s->TmpDist = dist_base[distIdx] + Inflate_ReadBits(s, bits); + s->State = INFLATE_STATE_COMPRESSED_DATA; + } + + /* FALLTHRU */ + case INFLATE_STATE_COMPRESSED_DATA: { + if (!s->AvailOut) return; + len = s->TmpLit; dist = s->TmpDist; + len = min(len, s->AvailOut); + + /* TODO: Should we test outside of the loop, whether a masking will be required or not? */ + startIdx = (s->WindowIndex - dist) & INFLATE_WINDOW_MASK; + curIdx = s->WindowIndex; + for (i = 0; i < len; i++) { + cc_uint8 value = s->Window[(startIdx + i) & INFLATE_WINDOW_MASK]; + *s->Output = value; + s->Window[(curIdx + i) & INFLATE_WINDOW_MASK] = value; + s->Output++; + } + + s->WindowIndex = (curIdx + len) & INFLATE_WINDOW_MASK; + s->TmpLit -= len; + s->AvailOut -= len; + if (!s->TmpLit) { s->State = Inflate_NextCompressState(s); } + break; + } + + case INFLATE_STATE_FASTCOMPRESSED: { + Inflate_InflateFast(s); + if (s->State == INFLATE_STATE_FASTCOMPRESSED) { + s->State = Inflate_NextCompressState(s); + } + break; + } + + case INFLATE_STATE_DONE: + return; + } + } +} + +static cc_result Inflate_StreamRead(struct Stream* stream, cc_uint8* data, cc_uint32 count, cc_uint32* modified) { + struct InflateState* state; + cc_uint8* inputEnd; + cc_uint32 read, left; + cc_uint32 startAvailOut; + cc_bool hasInput; + cc_result res; + + *modified = 0; + state = (struct InflateState*)stream->meta.inflate; + state->Output = data; + state->AvailOut = count; + + hasInput = true; + while (state->AvailOut > 0 && hasInput) { + if (state->State == INFLATE_STATE_DONE) return state->result; + + if (!state->AvailIn) { + /* Fully used up input buffer. Cycle back to start. */ + inputEnd = state->Input + INFLATE_MAX_INPUT; + if (state->NextIn == inputEnd) state->NextIn = state->Input; + + left = (cc_uint32)(inputEnd - state->NextIn); + res = state->Source->Read(state->Source, state->NextIn, left, &read); + if (res) return res; + + /* Did we fail to read in more input data? Can't immediately return here, */ + /* because there might be a few bits of data left in the bit buffer */ + hasInput = read > 0; + state->AvailIn += read; + } + + /* Reading data reduces available out */ + startAvailOut = state->AvailOut; + Inflate_Process(state); + *modified += (startAvailOut - state->AvailOut); + } + return 0; +} + +void Inflate_MakeStream2(struct Stream* stream, struct InflateState* state, struct Stream* underlying) { + Stream_Init(stream); + Inflate_Init2(state, underlying); + stream->meta.inflate = state; + stream->Read = Inflate_StreamRead; +} + + +/*########################################################################################################################* +*---------------------------------------------------Deflate (compress)----------------------------------------------------* +*#########################################################################################################################*/ +/* these are copies of len_base and dist_base, with UINT16_MAX instead of 0 for sentinel cutoff */ +static const cc_uint16 deflate_len[30] = { + 3,4,5,6,7,8,9,10,11,13, + 15,17,19,23,27,31,35,43,51,59, + 67,83,99,115,131,163,195,227,258,UInt16_MaxValue +}; +static const cc_uint16 deflate_dist[31] = { + 1,2,3,4,5,7,9,13,17,25, + 33,49,65,97,129,193,257,385,513,769, + 1025,1537,2049,3073,4097,6145,8193,12289,16385,24577,UInt16_MaxValue +}; + +/* Pushes given bits, but does not write them */ +#define Deflate_PushBits(state, value, bits) state->Bits |= (value) << state->NumBits; state->NumBits += (bits); +/* Pushes bits of the huffman codeword bits for the given literal, but does not write them */ +#define Deflate_PushLit(state, value) Deflate_PushBits(state, state->LitsCodewords[value], state->LitsLens[value]) +/* Pushes given bits (reversing for huffman code), but does not write them */ +#define Deflate_PushHuff(state, value, bits) Deflate_PushBits(state, Huffman_ReverseBits(value, bits), bits) +/* Writes given byte to output */ +#define Deflate_WriteByte(state) *state->NextOut++ = state->Bits; state->AvailOut--; state->Bits >>= 8; state->NumBits -= 8; +/* Flushes bits in buffer to output buffer */ +#define Deflate_FlushBits(state) while (state->NumBits >= 8) { Deflate_WriteByte(state); } + +#define MIN_MATCH_LEN 3 +#define MAX_MATCH_LEN 258 + +/* Number of bytes that match (are the same) from a and b */ +static int Deflate_MatchLen(cc_uint8* a, cc_uint8* b, int maxLen) { + int i = 0; + while (i < maxLen && *a == *b) { i++; a++; b++; } + return i; +} + +/* Hashes 3 bytes of data */ +static cc_uint32 Deflate_Hash(cc_uint8* src) { + return (cc_uint32)((src[0] << 8) ^ (src[1] << 4) ^ (src[2])) & DEFLATE_HASH_MASK; +} + +/* Writes a literal to state->Output */ +static void Deflate_Lit(struct DeflateState* state, int lit) { + Deflate_PushLit(state, lit); + Deflate_FlushBits(state); +} + +/* Writes a length-distance pair to state->Output */ +static void Deflate_LenDist(struct DeflateState* state, int len, int dist) { + int j; + /* TODO: Do we actually need the if (len_bits[j]) ????????? does writing 0 bits matter??? */ + + for (j = 0; len >= deflate_len[j + 1]; j++); + Deflate_PushLit(state, j + 257); + if (len_bits[j]) { Deflate_PushBits(state, len - deflate_len[j], len_bits[j]); } + Deflate_FlushBits(state); + + for (j = 0; dist >= deflate_dist[j + 1]; j++); + Deflate_PushHuff(state, j, 5); + if (dist_bits[j]) { Deflate_PushBits(state, dist - deflate_dist[j], dist_bits[j]); } + Deflate_FlushBits(state); +} + +/* Moves "current block" to "previous block", adjusting state if needed. */ +static void Deflate_MoveBlock(struct DeflateState* state) { + int i; + Mem_Copy(state->Input, state->Input + DEFLATE_BLOCK_SIZE, DEFLATE_BLOCK_SIZE); + state->InputPosition = DEFLATE_BLOCK_SIZE; + + /* adjust hash table offsets, removing offsets that are no longer in data at all */ + for (i = 0; i < Array_Elems(state->Head); i++) { + state->Head[i] = state->Head[i] < DEFLATE_BLOCK_SIZE ? 0 : (state->Head[i] - DEFLATE_BLOCK_SIZE); + } + for (i = 0; i < Array_Elems(state->Prev); i++) { + state->Prev[i] = state->Prev[i] < DEFLATE_BLOCK_SIZE ? 0 : (state->Prev[i] - DEFLATE_BLOCK_SIZE); + } +} + +/* Compresses current block of data */ +static cc_result Deflate_FlushBlock(struct DeflateState* state, int len) { + cc_uint32 hash, nextHash; + int bestLen, maxLen, matchLen, depth; + int bestPos, pos, nextPos; + cc_uint16 oldHead; + cc_uint8* input; + cc_uint8* cur; + cc_result res; + + if (!state->WroteHeader) { + state->WroteHeader = true; + Deflate_PushBits(state, 3, 3); /* final block TRUE, block type FIXED */ + } + + /* Based off descriptions from http://www.gzip.org/algorithm.txt and + https://github.com/nothings/stb/blob/master/stb_image_write.h */ + input = state->Input; + cur = input + DEFLATE_BLOCK_SIZE; + + /* Compress current block of data */ + /* Use > instead of >=, because also try match at one byte after current */ + while (len > MIN_MATCH_LEN) { + hash = Deflate_Hash(cur); + maxLen = min(len, MAX_MATCH_LEN); + + bestLen = MIN_MATCH_LEN - 1; /* Match must be at least 3 bytes */ + bestPos = 0; + + /* Find longest match starting at this byte */ + /* Only explore up to 5 previous matches, to avoid slow performance */ + /* (i.e prefer quickly saving maps/screenshots to completely optimal filesize) */ + pos = state->Head[hash]; + for (depth = 0; pos != 0 && depth < 5; depth++) { + matchLen = Deflate_MatchLen(&input[pos], cur, maxLen); + if (matchLen > bestLen) { bestLen = matchLen; bestPos = pos; } + pos = state->Prev[pos]; + } + + /* Insert this entry into the hash chain */ + pos = (int)(cur - input); + oldHead = state->Head[hash]; + state->Head[hash] = pos; + state->Prev[pos] = oldHead; + + /* Lazy evaluation: Find longest match starting at next byte */ + /* If that's longer than the longest match at current byte, throwaway this match */ + if (bestPos) { + nextHash = Deflate_Hash(cur + 1); + nextPos = state->Head[nextHash]; + maxLen = min(len - 1, MAX_MATCH_LEN); + + for (depth = 0; nextPos != 0 && depth < 5; depth++) { + matchLen = Deflate_MatchLen(&input[nextPos], cur + 1, maxLen); + if (matchLen > bestLen) { bestPos = 0; break; } + nextPos = state->Prev[nextPos]; + } + } + + if (bestPos) { + Deflate_LenDist(state, bestLen, pos - bestPos); + len -= bestLen; cur += bestLen; + } else { + Deflate_Lit(state, *cur); + len--; cur++; + } + + /* leave room for a few bytes and literals at end */ + if (state->AvailOut >= 20) continue; + res = Stream_Write(state->Dest, state->Output, DEFLATE_OUT_SIZE - state->AvailOut); + state->NextOut = state->Output; + state->AvailOut = DEFLATE_OUT_SIZE; + if (res) return res; + } + + /* literals for last few bytes */ + while (len > 0) { + Deflate_Lit(state, *cur); + len--; cur++; + } + + res = Stream_Write(state->Dest, state->Output, DEFLATE_OUT_SIZE - state->AvailOut); + state->NextOut = state->Output; + state->AvailOut = DEFLATE_OUT_SIZE; + + Deflate_MoveBlock(state); + return res; +} + +/* Adds data to buffered output data, flushing if needed */ +static cc_result Deflate_StreamWrite(struct Stream* stream, const cc_uint8* data, cc_uint32 total, cc_uint32* modified) { + struct DeflateState* state; + cc_result res; + + state = (struct DeflateState*)stream->meta.inflate; + *modified = 0; + + while (total > 0) { + cc_uint8* dst = &state->Input[state->InputPosition]; + cc_uint32 len = total; + if (state->InputPosition + len >= DEFLATE_BUFFER_SIZE) { + len = DEFLATE_BUFFER_SIZE - state->InputPosition; + } + + Mem_Copy(dst, data, len); + total -= len; + state->InputPosition += len; + *modified += len; + data += len; + + if (state->InputPosition == DEFLATE_BUFFER_SIZE) { + res = Deflate_FlushBlock(state, DEFLATE_BLOCK_SIZE); + if (res) return res; + } + } + return 0; +} + +/* Flushes any buffered data, then writes terminating symbol */ +static cc_result Deflate_StreamClose(struct Stream* stream) { + struct DeflateState* state; + cc_result res; + + state = (struct DeflateState*)stream->meta.inflate; + res = Deflate_FlushBlock(state, state->InputPosition - DEFLATE_BLOCK_SIZE); + if (res) return res; + + /* Write huffman encoded "literal 256" to terminate symbols */ + Deflate_PushLit(state, 256); + Deflate_FlushBits(state); + + /* In case last byte still has a few extra bits */ + if (state->NumBits) { + while (state->NumBits < 8) { Deflate_PushBits(state, 0, 1); } + Deflate_FlushBits(state); + } + + return Stream_Write(state->Dest, state->Output, DEFLATE_OUT_SIZE - state->AvailOut); +} + +/* Constructs a huffman encoding table (for values to codewords) */ +static void Deflate_BuildTable(const cc_uint8* lens, int count, cc_uint16* codewords, cc_uint8* bitlens) { + int i, j, offset, codeword; + struct HuffmanTable table; + + /* NOTE: Can ignore since lens table is not user controlled */ + (void)Huffman_Build(&table, lens, count); + for (i = 0; i < INFLATE_MAX_BITS; i++) { + if (!table.endCodewords[i]) continue; + count = table.endCodewords[i] - table.firstCodewords[i]; + + for (j = 0; j < count; j++) { + offset = table.values[table.firstOffsets[i] + j]; + codeword = table.firstCodewords[i] + j; + bitlens[offset] = i; + codewords[offset] = Huffman_ReverseBits(codeword, i); + } + } +} + +void Deflate_MakeStream(struct Stream* stream, struct DeflateState* state, struct Stream* underlying) { + Stream_Init(stream); + stream->meta.inflate = state; + stream->Write = Deflate_StreamWrite; + stream->Close = Deflate_StreamClose; + + /* First half of buffer is "previous block" */ + state->InputPosition = DEFLATE_BLOCK_SIZE; + state->Bits = 0; + state->NumBits = 0; + + state->NextOut = state->Output; + state->AvailOut = DEFLATE_OUT_SIZE; + state->Dest = underlying; + state->WroteHeader = false; + + Mem_Set(state->Head, 0, sizeof(state->Head)); + Mem_Set(state->Prev, 0, sizeof(state->Prev)); + Deflate_BuildTable(fixed_lits, INFLATE_MAX_LITS, state->LitsCodewords, state->LitsLens); +} + + +/*########################################################################################################################* +*-----------------------------------------------------GZip (compress)-----------------------------------------------------* +*#########################################################################################################################*/ +static cc_result GZip_StreamClose(struct Stream* stream) { + struct GZipState* state = (struct GZipState*)stream->meta.inflate; + cc_uint8 data[8]; + cc_result res; + + if ((res = Deflate_StreamClose(stream))) return res; + Stream_SetU32_LE(&data[0], state->Crc32 ^ 0xFFFFFFFFUL); + Stream_SetU32_LE(&data[4], state->Size); + return Stream_Write(state->Base.Dest, data, sizeof(data)); +} + +static cc_result GZip_StreamWrite(struct Stream* stream, const cc_uint8* data, cc_uint32 count, cc_uint32* modified) { + struct GZipState* state = (struct GZipState*)stream->meta.inflate; + cc_uint32 i, crc32 = state->Crc32; + state->Size += count; + + /* TODO: Optimise this calculation */ + for (i = 0; i < count; i++) { + crc32 = Utils_Crc32Table[(crc32 ^ data[i]) & 0xFF] ^ (crc32 >> 8); + } + + state->Crc32 = crc32; + return Deflate_StreamWrite(stream, data, count, modified); +} + +static cc_result GZip_StreamWriteFirst(struct Stream* stream, const cc_uint8* data, cc_uint32 count, cc_uint32* modified) { + static cc_uint8 header[10] = { 0x1F, 0x8B, 0x08 }; /* GZip header */ + struct GZipState* state = (struct GZipState*)stream->meta.inflate; + cc_result res; + + if ((res = Stream_Write(state->Base.Dest, header, sizeof(header)))) return res; + stream->Write = GZip_StreamWrite; + return GZip_StreamWrite(stream, data, count, modified); +} + +void GZip_MakeStream(struct Stream* stream, struct GZipState* state, struct Stream* underlying) { + Deflate_MakeStream(stream, &state->Base, underlying); + state->Crc32 = 0xFFFFFFFFUL; + state->Size = 0; + stream->Write = GZip_StreamWriteFirst; + stream->Close = GZip_StreamClose; +} + + +/*########################################################################################################################* +*-----------------------------------------------------ZLib (compress)-----------------------------------------------------* +*#########################################################################################################################*/ +static cc_result ZLib_StreamClose(struct Stream* stream) { + struct ZLibState* state = (struct ZLibState*)stream->meta.inflate; + cc_uint8 data[4]; + cc_result res; + + if ((res = Deflate_StreamClose(stream))) return res; + Stream_SetU32_BE(&data[0], state->Adler32); + return Stream_Write(state->Base.Dest, data, sizeof(data)); +} + +static cc_result ZLib_StreamWrite(struct Stream* stream, const cc_uint8* data, cc_uint32 count, cc_uint32* modified) { + struct ZLibState* state = (struct ZLibState*)stream->meta.inflate; + cc_uint32 i, adler32 = state->Adler32; + cc_uint32 s1 = adler32 & 0xFFFF, s2 = (adler32 >> 16) & 0xFFFF; + + /* TODO: Optimise this calculation */ + for (i = 0; i < count; i++) { + #define ADLER32_BASE 65521 + s1 = (s1 + data[i]) % ADLER32_BASE; + s2 = (s2 + s1) % ADLER32_BASE; + } + + state->Adler32 = (s2 << 16) | s1; + return Deflate_StreamWrite(stream, data, count, modified); +} + +static cc_result ZLib_StreamWriteFirst(struct Stream* stream, const cc_uint8* data, cc_uint32 count, cc_uint32* modified) { + static cc_uint8 header[2] = { 0x78, 0x9C }; /* ZLib header */ + struct ZLibState* state = (struct ZLibState*)stream->meta.inflate; + cc_result res; + + if ((res = Stream_Write(state->Base.Dest, header, sizeof(header)))) return res; + stream->Write = ZLib_StreamWrite; + return ZLib_StreamWrite(stream, data, count, modified); +} + +void ZLib_MakeStream(struct Stream* stream, struct ZLibState* state, struct Stream* underlying) { + Deflate_MakeStream(stream, &state->Base, underlying); + state->Adler32 = 1; + stream->Write = ZLib_StreamWriteFirst; + stream->Close = ZLib_StreamClose; +} + + +/*########################################################################################################################* +*--------------------------------------------------------ZipReader--------------------------------------------------------* +*#########################################################################################################################*/ +#define ZIP_MAXNAMELEN 512 +#define ZIP_MAX_ENTRIES 1024 + +/* Stores state for reading and processing entries in a .zip archive */ +struct ZipState { + struct Stream* source; + Zip_SelectEntry SelectEntry; + Zip_ProcessEntry ProcessEntry; + + /* Number of entries selected by SelectEntry */ + int usedEntries; + /* Total number of entries in the archive */ + int totalEntries; + /* Offset to central directory entries */ + cc_uint32 centralDirBeg; + /* Data for each entry in the .zip archive */ + struct ZipEntry entries[ZIP_MAX_ENTRIES]; +}; + +static cc_result Zip_ReadLocalFileHeader(struct ZipState* state, struct ZipEntry* entry) { + struct Stream* stream = state->source; + cc_uint8 header[26]; + cc_string path; char pathBuffer[ZIP_MAXNAMELEN]; + cc_uint32 compressedSize, uncompressedSize; + int method, pathLen, extraLen; + struct Stream portion, compStream; + struct InflateState inflate; + cc_result res; + + if ((res = Stream_Read(stream, header, sizeof(header)))) return res; + pathLen = Stream_GetU16_LE(&header[22]); + if (pathLen > ZIP_MAXNAMELEN) return ZIP_ERR_FILENAME_LEN; + + /* NOTE: ZIP spec says path uses code page 437 for encoding */ + path = String_Init(pathBuffer, pathLen, pathLen); + if ((res = Stream_Read(stream, (cc_uint8*)pathBuffer, pathLen))) return res; + if (!state->SelectEntry(&path)) return 0; + + extraLen = Stream_GetU16_LE(&header[24]); + /* local file may have extra data before actual data (e.g. ZIP64) */ + if ((res = stream->Skip(stream, extraLen))) return res; + + method = Stream_GetU16_LE(&header[4]); + compressedSize = Stream_GetU32_LE(&header[14]); + uncompressedSize = Stream_GetU32_LE(&header[18]); + + /* Some .zip files don't set these in local file header */ + if (!compressedSize) compressedSize = entry->CompressedSize; + if (!uncompressedSize) uncompressedSize = entry->UncompressedSize; + + if (method == 0) { + Stream_ReadonlyPortion(&portion, stream, uncompressedSize); + return state->ProcessEntry(&path, &portion, entry); + } else if (method == 8) { + Stream_ReadonlyPortion(&portion, stream, compressedSize); + Inflate_MakeStream2(&compStream, &inflate, &portion); + return state->ProcessEntry(&path, &compStream, entry); + } else { + Platform_Log1("Unsupported.zip entry compression method: %i", &method); + /* TODO: Should this be an error */ + } + return 0; +} + +static cc_result Zip_ReadCentralDirectory(struct ZipState* state) { + struct Stream* stream = state->source; + struct ZipEntry* entry; + cc_uint8 header[42]; + + cc_string path; char pathBuffer[ZIP_MAXNAMELEN]; + int pathLen, extraLen, commentLen; + cc_result res; + + if ((res = Stream_Read(stream, header, sizeof(header)))) return res; + pathLen = Stream_GetU16_LE(&header[24]); + if (pathLen > ZIP_MAXNAMELEN) return ZIP_ERR_FILENAME_LEN; + + /* NOTE: ZIP spec says path uses code page 437 for encoding */ + path = String_Init(pathBuffer, pathLen, pathLen); + if ((res = Stream_Read(stream, (cc_uint8*)pathBuffer, pathLen))) return res; + + /* skip data following central directory entry header */ + extraLen = Stream_GetU16_LE(&header[26]); + commentLen = Stream_GetU16_LE(&header[28]); + if ((res = stream->Skip(stream, extraLen + commentLen))) return res; + + if (!state->SelectEntry(&path)) return 0; + if (state->usedEntries >= ZIP_MAX_ENTRIES) return ZIP_ERR_TOO_MANY_ENTRIES; + entry = &state->entries[state->usedEntries++]; + + entry->CompressedSize = Stream_GetU32_LE(&header[16]); + entry->UncompressedSize = Stream_GetU32_LE(&header[20]); + entry->LocalHeaderOffset = Stream_GetU32_LE(&header[38]); + return 0; +} + +static cc_result Zip_ReadEndOfCentralDirectory(struct ZipState* state) { + struct Stream* stream = state->source; + cc_uint8 header[18]; + + cc_result res; + if ((res = Stream_Read(stream, header, sizeof(header)))) return res; + + state->totalEntries = Stream_GetU16_LE(&header[6]); + state->centralDirBeg = Stream_GetU32_LE(&header[12]); + return 0; +} + +enum ZipSig { + ZIP_SIG_ENDOFCENTRALDIR = 0x06054b50, + ZIP_SIG_CENTRALDIR = 0x02014b50, + ZIP_SIG_LOCALFILEHEADER = 0x04034b50 +}; + +cc_result Zip_Extract(struct Stream* source, Zip_SelectEntry selector, Zip_ProcessEntry processor) { + struct ZipState state; + cc_uint32 stream_len; + cc_uint32 sig = 0; + int i, count; + + cc_result res; + if ((res = source->Length(source, &stream_len))) return res; + + /* At -22 for nearly all zips, but try a bit further back in case of comment */ + count = min(257, stream_len); + for (i = 22; i < count; i++) { + res = source->Seek(source, stream_len - i); + if (res) return ZIP_ERR_SEEK_END_OF_CENTRAL_DIR; + + if ((res = Stream_ReadU32_LE(source, &sig))) return res; + if (sig == ZIP_SIG_ENDOFCENTRALDIR) break; + } + + state.source = source; + state.SelectEntry = selector; + state.ProcessEntry = processor; + + if (sig != ZIP_SIG_ENDOFCENTRALDIR) return ZIP_ERR_NO_END_OF_CENTRAL_DIR; + res = Zip_ReadEndOfCentralDirectory(&state); + if (res) return res; + + res = source->Seek(source, state.centralDirBeg); + if (res) return ZIP_ERR_SEEK_CENTRAL_DIR; + state.usedEntries = 0; + + /* Read all the central directory entries */ + for (i = 0; i < state.totalEntries; i++) { + if ((res = Stream_ReadU32_LE(source, &sig))) return res; + + if (sig == ZIP_SIG_CENTRALDIR) { + res = Zip_ReadCentralDirectory(&state); + if (res) return res; + } else if (sig == ZIP_SIG_ENDOFCENTRALDIR) { + break; + } else { + return ZIP_ERR_INVALID_CENTRAL_DIR; + } + } + + /* Now read the local file header entries */ + for (i = 0; i < state.usedEntries; i++) { + struct ZipEntry* entry = &state.entries[i]; + res = source->Seek(source, entry->LocalHeaderOffset); + if (res) return ZIP_ERR_SEEK_LOCAL_DIR; + + if ((res = Stream_ReadU32_LE(source, &sig))) return res; + if (sig != ZIP_SIG_LOCALFILEHEADER) return ZIP_ERR_INVALID_LOCAL_DIR; + + res = Zip_ReadLocalFileHeader(&state, entry); + if (res) return res; + } + return 0; +} |