summary refs log tree commit diff
path: root/src/Deflate.c
diff options
context:
space:
mode:
authorWlodekM <[email protected]>2024-06-16 10:35:45 +0300
committerWlodekM <[email protected]>2024-06-16 10:35:45 +0300
commitabef6da56913f1c55528103e60a50451a39628b1 (patch)
treeb3c8092471ecbb73e568cd0d336efa0e7871ee8d /src/Deflate.c
initial commit
Diffstat (limited to 'src/Deflate.c')
-rw-r--r--src/Deflate.c1317
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;
+}