// Part of JoeyLib, Copyright (c) 2026 Scott Duensing, under the MIT License. // See LICENSE.md for license information. // SPDX-License-Identifier: MIT // // toolZlib.c - DEFLATE for the JoeyLib host tools (toolZlib.h). // // Inflate decodes canonical Huffman codes a bit at a time (RFC 1951's own // construction): small, and plenty fast for asset files. Deflate is one // fixed-Huffman block over a greedy LZ77 parse with hash chains. #include #include #include "toolZlib.h" #define MAX_BITS 15 #define MAX_LIT_CODES 288 #define MAX_DIST_CODES 30 #define MAX_CODE_LEN_CODES 19 #define END_OF_BLOCK 256 #define FIRST_LENGTH_CODE 257 #define BLOCK_STORED 0 #define BLOCK_FIXED 1 #define BLOCK_DYNAMIC 2 #define OUT_START_BYTES 4096u #define ADLER_MOD 65521u #define ADLER_NMAX 5552u #define CRC_POLY 0xEDB88320u #define GZIP_ID1 0x1Fu #define GZIP_ID2 0x8Bu #define GZIP_CM_DEFLATE 8u #define GZIP_HEADER_BYTES 10u #define GZIP_TRAILER_BYTES 8u #define GZIP_FHCRC 0x02u #define GZIP_FEXTRA 0x04u #define GZIP_FNAME 0x08u #define GZIP_FCOMMENT 0x10u #define ZLIB_CM_DEFLATE 8u #define ZLIB_FDICT 0x20u #define ZLIB_CHECK 31u #define ZLIB_HEADER_0 0x78u #define ZLIB_HEADER_1 0x01u #define WINDOW_BYTES 32768u #define HASH_BITS 15u #define HASH_SIZE (1u << HASH_BITS) #define MIN_MATCH 3u #define MAX_MATCH 258u #define MAX_CHAIN 128u #define NO_POS 0xFFFFFFFFu typedef struct HuffmanT { uint16_t counts[MAX_BITS + 1]; uint16_t symbols[MAX_LIT_CODES]; } HuffmanT; typedef struct InflateT { const uint8_t *in; size_t inLen; size_t inPos; uint32_t bitBuf; uint32_t bitCount; uint8_t *out; size_t outLen; size_t outCap; bool error; } InflateT; typedef struct DeflateT { uint8_t *out; size_t outLen; size_t outCap; uint32_t bitBuf; uint32_t bitCount; bool error; } DeflateT; static const uint16_t kLengthBase[29] = { 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 }; static const uint8_t kLengthExtra[29] = { 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 }; static const uint16_t kDistBase[30] = { 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 }; static const uint8_t kDistExtra[30] = { 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 }; static const uint8_t kCodeLenOrder[MAX_CODE_LEN_CODES] = { 16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15 }; static void deflateBits(DeflateT *d, uint32_t value, uint32_t count); static void deflateByte(DeflateT *d, uint8_t b); static void deflateCode(DeflateT *d, uint32_t code, uint32_t len); static void deflateDistance(DeflateT *d, uint32_t dist); static void deflateFlush(DeflateT *d); static void deflateLength(DeflateT *d, uint32_t len); static void deflateLiteral(DeflateT *d, uint32_t sym); static uint32_t hashAt(const uint8_t *p); static uint32_t inflateBits(InflateT *s, uint32_t count); static bool inflateBlockCodes(InflateT *s, const HuffmanT *lit, const HuffmanT *dist); static bool inflateBuild(HuffmanT *h, const uint8_t *lengths, uint16_t n); static int32_t inflateDecode(InflateT *s, const HuffmanT *h); static bool inflateDynamic(InflateT *s); static bool inflateFixed(InflateT *s); static bool inflateOut(InflateT *s, uint8_t b); static bool inflateRaw(InflateT *s); static bool inflateStored(InflateT *s); static uint32_t readBe32(const uint8_t *p); static uint32_t readLe32(const uint8_t *p); static uint32_t reverseBits(uint32_t code, uint32_t len); static void deflateBits(DeflateT *d, uint32_t value, uint32_t count) { d->bitBuf |= value << d->bitCount; d->bitCount += count; while (d->bitCount >= 8u) { deflateByte(d, (uint8_t)d->bitBuf); d->bitBuf >>= 8; d->bitCount -= 8u; } } static void deflateByte(DeflateT *d, uint8_t b) { uint8_t *grown; if (d->outLen == d->outCap) { grown = (uint8_t *)realloc(d->out, d->outCap * 2u); if (grown == NULL) { d->error = true; return; } d->out = grown; d->outCap *= 2u; } d->out[d->outLen++] = b; } // A Huffman code goes out most significant bit first. static void deflateCode(DeflateT *d, uint32_t code, uint32_t len) { deflateBits(d, reverseBits(code, len), len); } static void deflateDistance(DeflateT *d, uint32_t dist) { uint32_t c; for (c = MAX_DIST_CODES - 1u; kDistBase[c] > dist; c--) { } deflateCode(d, c, 5u); deflateBits(d, dist - kDistBase[c], kDistExtra[c]); } static void deflateFlush(DeflateT *d) { if (d->bitCount > 0u) { deflateByte(d, (uint8_t)d->bitBuf); d->bitBuf = 0u; d->bitCount = 0u; } } static void deflateLength(DeflateT *d, uint32_t len) { uint32_t c; for (c = 28u; kLengthBase[c] > len; c--) { } deflateLiteral(d, FIRST_LENGTH_CODE + c); deflateBits(d, len - kLengthBase[c], kLengthExtra[c]); } // The fixed literal/length code (RFC 1951 3.2.6). static void deflateLiteral(DeflateT *d, uint32_t sym) { if (sym < 144u) { deflateCode(d, 0x30u + sym, 8u); } else if (sym < 256u) { deflateCode(d, 0x190u + (sym - 144u), 9u); } else if (sym < 280u) { deflateCode(d, sym - 256u, 7u); } else { deflateCode(d, 0xC0u + (sym - 280u), 8u); } } static uint32_t hashAt(const uint8_t *p) { return ((uint32_t)p[0] << 10 ^ (uint32_t)p[1] << 5 ^ (uint32_t)p[2]) & (HASH_SIZE - 1u); } static uint32_t inflateBits(InflateT *s, uint32_t count) { uint32_t value; while (s->bitCount < count) { if (s->inPos >= s->inLen) { s->error = true; return 0u; } s->bitBuf |= (uint32_t)s->in[s->inPos++] << s->bitCount; s->bitCount += 8u; } value = s->bitBuf & ((1u << count) - 1u); s->bitBuf >>= count; s->bitCount -= count; return value; } static bool inflateBlockCodes(InflateT *s, const HuffmanT *lit, const HuffmanT *dist) { int32_t sym; uint32_t len; uint32_t back; uint32_t i; for (;;) { sym = inflateDecode(s, lit); if (sym < 0) { return false; } if (sym < END_OF_BLOCK) { if (!inflateOut(s, (uint8_t)sym)) { return false; } continue; } if (sym == END_OF_BLOCK) { return true; } sym -= FIRST_LENGTH_CODE; if (sym >= 29) { return false; } len = kLengthBase[sym] + inflateBits(s, kLengthExtra[sym]); sym = inflateDecode(s, dist); if (sym < 0 || sym >= MAX_DIST_CODES) { return false; } back = kDistBase[sym] + inflateBits(s, kDistExtra[sym]); if (s->error || back > s->outLen) { return false; } for (i = 0; i < len; i++) { if (!inflateOut(s, s->out[s->outLen - back])) { return false; } } } } // Canonical codes from code lengths: counts of each length, symbols in code // order. False when the lengths over-subscribe the code space. static bool inflateBuild(HuffmanT *h, const uint8_t *lengths, uint16_t n) { uint16_t offsets[MAX_BITS + 1]; int32_t left; uint16_t i; memset(h->counts, 0, sizeof(h->counts)); for (i = 0; i < n; i++) { h->counts[lengths[i]]++; } left = 1; for (i = 1; i <= MAX_BITS; i++) { left = (left << 1) - h->counts[i]; if (left < 0) { return false; } } offsets[1] = 0; for (i = 1; i < MAX_BITS; i++) { offsets[i + 1] = (uint16_t)(offsets[i] + h->counts[i]); } for (i = 0; i < n; i++) { if (lengths[i] != 0u) { h->symbols[offsets[lengths[i]]++] = i; } } return true; } static int32_t inflateDecode(InflateT *s, const HuffmanT *h) { int32_t code; int32_t first; int32_t index; int32_t count; uint32_t len; code = 0; first = 0; index = 0; for (len = 1; len <= MAX_BITS; len++) { code |= (int32_t)inflateBits(s, 1u); if (s->error) { return -1; } count = h->counts[len]; if (code - count < first) { return h->symbols[index + (code - first)]; } index += count; first += count; first <<= 1; code <<= 1; } return -1; } static bool inflateDynamic(InflateT *s) { HuffmanT lit; HuffmanT dist; uint8_t lengths[MAX_LIT_CODES + MAX_DIST_CODES]; uint32_t nLit; uint32_t nDist; uint32_t nCode; uint32_t i; uint32_t repeat; int32_t sym; uint8_t value; nLit = inflateBits(s, 5u) + 257u; nDist = inflateBits(s, 5u) + 1u; nCode = inflateBits(s, 4u) + 4u; if (s->error || nLit > MAX_LIT_CODES || nDist > MAX_DIST_CODES) { return false; } memset(lengths, 0, sizeof(lengths)); for (i = 0; i < nCode; i++) { lengths[kCodeLenOrder[i]] = (uint8_t)inflateBits(s, 3u); } if (!inflateBuild(&lit, lengths, MAX_CODE_LEN_CODES)) { return false; } i = 0; while (i < nLit + nDist) { sym = inflateDecode(s, &lit); if (sym < 0) { return false; } if (sym < 16) { lengths[i++] = (uint8_t)sym; continue; } value = 0u; if (sym == 16) { if (i == 0u) { return false; } value = lengths[i - 1u]; repeat = 3u + inflateBits(s, 2u); } else if (sym == 17) { repeat = 3u + inflateBits(s, 3u); } else { repeat = 11u + inflateBits(s, 7u); } if (s->error || i + repeat > nLit + nDist) { return false; } while (repeat-- > 0u) { lengths[i++] = value; } } if (lengths[END_OF_BLOCK] == 0u) { return false; } if (!inflateBuild(&lit, lengths, (uint16_t)nLit) || !inflateBuild(&dist, lengths + nLit, (uint16_t)nDist)) { return false; } return inflateBlockCodes(s, &lit, &dist); } static bool inflateFixed(InflateT *s) { HuffmanT lit; HuffmanT dist; uint8_t lengths[MAX_LIT_CODES]; uint16_t i; for (i = 0; i < 144u; i++) { lengths[i] = 8u; } for (; i < 256u; i++) { lengths[i] = 9u; } for (; i < 280u; i++) { lengths[i] = 7u; } for (; i < MAX_LIT_CODES; i++) { lengths[i] = 8u; } inflateBuild(&lit, lengths, MAX_LIT_CODES); for (i = 0; i < MAX_DIST_CODES; i++) { lengths[i] = 5u; } inflateBuild(&dist, lengths, MAX_DIST_CODES); return inflateBlockCodes(s, &lit, &dist); } static bool inflateOut(InflateT *s, uint8_t b) { uint8_t *grown; if (s->outLen == s->outCap) { grown = (uint8_t *)realloc(s->out, s->outCap * 2u); if (grown == NULL) { return false; } s->out = grown; s->outCap *= 2u; } s->out[s->outLen++] = b; return true; } static bool inflateRaw(InflateT *s) { uint32_t last; uint32_t type; bool ok; do { last = inflateBits(s, 1u); type = inflateBits(s, 2u); if (s->error) { return false; } if (type == BLOCK_STORED) { ok = inflateStored(s); } else if (type == BLOCK_FIXED) { ok = inflateFixed(s); } else if (type == BLOCK_DYNAMIC) { ok = inflateDynamic(s); } else { ok = false; } if (!ok) { return false; } } while (last == 0u); // The wrapper's trailer starts at the next whole byte. s->bitBuf = 0u; s->bitCount = 0u; return true; } static bool inflateStored(InflateT *s) { uint32_t len; uint32_t nlen; s->bitBuf = 0u; s->bitCount = 0u; if (s->inPos + 4u > s->inLen) { return false; } len = (uint32_t)s->in[s->inPos] | (uint32_t)s->in[s->inPos + 1u] << 8; nlen = (uint32_t)s->in[s->inPos + 2u] | (uint32_t)s->in[s->inPos + 3u] << 8; s->inPos += 4u; if ((len ^ 0xFFFFu) != nlen || s->inPos + len > s->inLen) { return false; } while (len-- > 0u) { if (!inflateOut(s, s->in[s->inPos++])) { return false; } } return true; } static uint32_t readBe32(const uint8_t *p) { return (uint32_t)p[0] << 24 | (uint32_t)p[1] << 16 | (uint32_t)p[2] << 8 | (uint32_t)p[3]; } static uint32_t readLe32(const uint8_t *p) { return (uint32_t)p[0] | (uint32_t)p[1] << 8 | (uint32_t)p[2] << 16 | (uint32_t)p[3] << 24; } static uint32_t reverseBits(uint32_t code, uint32_t len) { uint32_t out; uint32_t i; out = 0u; for (i = 0; i < len; i++) { out = (out << 1) | (code & 1u); code >>= 1; } return out; } uint32_t toolAdler32(uint32_t seed, const uint8_t *data, size_t len) { uint32_t a; uint32_t b; size_t chunk; a = seed & 0xFFFFu; b = seed >> 16; while (len > 0u) { chunk = len < ADLER_NMAX ? len : ADLER_NMAX; len -= chunk; while (chunk-- > 0u) { a += *data++; b += a; } a %= ADLER_MOD; b %= ADLER_MOD; } return b << 16 | a; } uint32_t toolCrc32(uint32_t seed, const uint8_t *data, size_t len) { static uint32_t table[256]; static bool built = false; uint32_t c; uint32_t n; uint32_t k; if (!built) { for (n = 0; n < 256u; n++) { c = n; for (k = 0; k < 8u; k++) { c = (c & 1u) != 0u ? CRC_POLY ^ (c >> 1) : c >> 1; } table[n] = c; } built = true; } c = seed ^ 0xFFFFFFFFu; while (len-- > 0u) { c = table[(c ^ *data++) & 0xFFu] ^ (c >> 8); } return c ^ 0xFFFFFFFFu; } bool toolDeflateZlib(const uint8_t *in, size_t inLen, uint8_t **out, size_t *outLen) { DeflateT d; uint32_t *head; uint32_t *prev; uint32_t adler; uint32_t h; uint32_t cand; uint32_t chain; uint32_t bestLen; uint32_t bestDist; uint32_t len; uint32_t limit; size_t pos; size_t k; memset(&d, 0, sizeof(d)); d.outCap = OUT_START_BYTES; d.out = (uint8_t *)malloc(d.outCap); head = (uint32_t *)malloc(HASH_SIZE * sizeof(uint32_t)); prev = (uint32_t *)malloc(WINDOW_BYTES * sizeof(uint32_t)); if (d.out == NULL || head == NULL || prev == NULL) { free(d.out); free(head); free(prev); return false; } for (k = 0; k < HASH_SIZE; k++) { head[k] = NO_POS; } deflateByte(&d, ZLIB_HEADER_0); deflateByte(&d, ZLIB_HEADER_1); deflateBits(&d, 1u, 1u); deflateBits(&d, BLOCK_FIXED, 2u); pos = 0; while (pos < inLen) { bestLen = 0u; bestDist = 0u; if (pos + MIN_MATCH <= inLen) { h = hashAt(in + pos); cand = head[h]; limit = (uint32_t)(inLen - pos < MAX_MATCH ? inLen - pos : MAX_MATCH); for (chain = 0; cand != NO_POS && chain < MAX_CHAIN && pos - cand <= WINDOW_BYTES; chain++) { for (len = 0; len < limit && in[cand + len] == in[pos + len]; len++) { } if (len > bestLen) { bestLen = len; bestDist = (uint32_t)(pos - cand); if (len == limit) { break; } } cand = prev[cand % WINDOW_BYTES]; } } if (bestLen >= MIN_MATCH) { deflateLength(&d, bestLen); deflateDistance(&d, bestDist); } else { bestLen = 1u; deflateLiteral(&d, in[pos]); } for (k = 0; k < bestLen; k++, pos++) { if (pos + MIN_MATCH <= inLen) { h = hashAt(in + pos); prev[pos % WINDOW_BYTES] = head[h]; head[h] = (uint32_t)pos; } } } deflateLiteral(&d, END_OF_BLOCK); deflateFlush(&d); adler = toolAdler32(1u, in, inLen); deflateByte(&d, (uint8_t)(adler >> 24)); deflateByte(&d, (uint8_t)(adler >> 16)); deflateByte(&d, (uint8_t)(adler >> 8)); deflateByte(&d, (uint8_t)adler); free(head); free(prev); if (d.error) { free(d.out); return false; } *out = d.out; *outLen = d.outLen; return true; } bool toolInflate(const uint8_t *in, size_t inLen, ToolZlibFormatE fmt, uint8_t **out, size_t *outLen) { InflateT s; size_t pos; uint8_t flags; uint32_t xlen; pos = 0; if (fmt == TOOL_ZLIB_AUTO) { fmt = (inLen >= 2u && in[0] == GZIP_ID1 && in[1] == GZIP_ID2) ? TOOL_ZLIB_GZIP : TOOL_ZLIB_ZLIB; } if (fmt == TOOL_ZLIB_ZLIB) { if (inLen < 6u || (in[0] & 0x0Fu) != ZLIB_CM_DEFLATE || ((uint32_t)in[0] << 8 | in[1]) % ZLIB_CHECK != 0u || (in[1] & ZLIB_FDICT) != 0u) { return false; } pos = 2u; } else if (fmt == TOOL_ZLIB_GZIP) { if (inLen < GZIP_HEADER_BYTES + GZIP_TRAILER_BYTES || in[0] != GZIP_ID1 || in[1] != GZIP_ID2 || in[2] != GZIP_CM_DEFLATE) { return false; } flags = in[3]; pos = GZIP_HEADER_BYTES; if ((flags & GZIP_FEXTRA) != 0u) { if (pos + 2u > inLen) { return false; } xlen = (uint32_t)in[pos] | (uint32_t)in[pos + 1u] << 8; pos += 2u + xlen; } if ((flags & GZIP_FNAME) != 0u) { while (pos < inLen && in[pos] != 0u) { pos++; } pos++; } if ((flags & GZIP_FCOMMENT) != 0u) { while (pos < inLen && in[pos] != 0u) { pos++; } pos++; } if ((flags & GZIP_FHCRC) != 0u) { pos += 2u; } if (pos >= inLen) { return false; } } memset(&s, 0, sizeof(s)); s.in = in; s.inLen = inLen; s.inPos = pos; s.outCap = OUT_START_BYTES; s.out = (uint8_t *)malloc(s.outCap); if (s.out == NULL) { return false; } if (!inflateRaw(&s)) { free(s.out); return false; } if (fmt == TOOL_ZLIB_ZLIB) { if (s.inPos + 4u > inLen || readBe32(in + s.inPos) != toolAdler32(1u, s.out, s.outLen)) { free(s.out); return false; } } else if (fmt == TOOL_ZLIB_GZIP) { if (s.inPos + GZIP_TRAILER_BYTES > inLen || readLe32(in + s.inPos) != toolCrc32(0u, s.out, s.outLen) || readLe32(in + s.inPos + 4u) != (uint32_t)s.outLen) { free(s.out); return false; } } *out = s.out; *outLen = s.outLen; return true; }