Files
uc2/lib/src/compress.c
T
Eremey Valetov 3dcfb3c4c4 License audit: SPDX headers + per-file provenance (closes 7cbbf97)
Add SPDX-License-Identifier to every source file in lib/ and cli/.
Files derived from Bobrowski's libunuc2 retain LGPL-3.0-only;
cli/src/main.c (derived from his GPL-licensed unuc2 tool) and all
new Phase 2-7 work by Valetov are GPL-3.0-or-later.  No silent
LGPL-to-GPL upgrade has been applied.

CREDITS.md now lists each Bobrowski-derived file specifically rather
than crediting libunuc2 as generic 'inspiration'.

docs/license-audit.md records the full per-file provenance table,
the LGPL-3.0 -> GPL-3.0 chain rationale (LGPL sec. 4 Combined Works
is the operative clause; LGPL sec. 3 single-direction upgrade is
documented but not exercised), and confirms that:
- the 2015 LGPL-3.0 release in original/UC2_source/ is preserved
  unchanged;
- the 2020-2021 LGPL/GPL releases in original/unuc2-0.6/ are preserved
  unchanged;
- lib/src/super.bin is bit-identical to upstream and to de Vries's
  1992 distribution data.
2026-05-03 12:20:19 -04:00

1107 lines
31 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/* SPDX-License-Identifier: GPL-3.0-or-later */
/* UC2 LZ77+Huffman compressor.
Produces bitstreams compatible with Bobrowski's decompressor (decompress.c).
Algorithm: LZ77 sliding window with hash-chain match finding,
Huffman entropy coding, delta-coded tree serialization.
Copyright (c) 2026 Eremey Valetov
License: GPL-3.0 */
#include <string.h>
#include <stdlib.h>
#include <assert.h>
#include "uc2/libuc2.h"
#include "uc2/uc2_rans.h"
#include "uc2_internal.h"
/* ---------- bitstream output ---------- */
struct bits_out {
u8 *buf;
unsigned pos; /* byte position in output */
unsigned capacity;
u16 word; /* accumulator */
int bits_left; /* bits remaining in word (16 = empty) */
int (*write)(void *ctx, const void *ptr, unsigned len);
void *ctx;
};
static void bitsout_init(struct bits_out *bo,
int (*write)(void *ctx, const void *ptr, unsigned len),
void *ctx, u8 *buf, unsigned capacity)
{
bo->buf = buf;
bo->pos = 0;
bo->capacity = capacity;
bo->word = 0;
bo->bits_left = 16;
bo->write = write;
bo->ctx = ctx;
}
static int bitsout_flush_buf(struct bits_out *bo)
{
if (bo->pos > 0) {
int r = bo->write(bo->ctx, bo->buf, bo->pos);
if (r < 0) return r;
bo->pos = 0;
}
return 0;
}
static int bitsout_emit_word(struct bits_out *bo, u16 w)
{
if (bo->pos + 2 > bo->capacity) {
int r = bitsout_flush_buf(bo);
if (r < 0) return r;
}
bo->buf[bo->pos++] = w & 0xff;
bo->buf[bo->pos++] = w >> 8;
return 0;
}
/* Put bits MSB-first into 16-bit LE words */
static int bitsout_put(struct bits_out *bo, unsigned val, int nbits)
{
assert(nbits <= 16);
int left = bo->bits_left;
if (nbits <= left) {
bo->word |= (u16)(val << (left - nbits));
bo->bits_left = left - nbits;
if (bo->bits_left == 0) {
int r = bitsout_emit_word(bo, bo->word);
if (r < 0) return r;
bo->word = 0;
bo->bits_left = 16;
}
} else {
/* Split across word boundary */
int first = left;
int second = nbits - first;
bo->word |= (u16)(val >> second);
int r = bitsout_emit_word(bo, bo->word);
if (r < 0) return r;
bo->word = (u16)(val << (16 - second));
bo->bits_left = 16 - second;
}
return 0;
}
static int bitsout_finish(struct bits_out *bo)
{
if (bo->bits_left < 16) {
int r = bitsout_emit_word(bo, bo->word);
if (r < 0) return r;
}
return bitsout_flush_buf(bo);
}
/* ---------- Huffman tree generation ---------- */
/* Faithful port of the original UC2 Pro's TreeGen/RepairLengths/CodeGen
from TREEGEN.CPP. Matching the original exactly is required for bitstream
compatibility with the 1992 UC2 Pro decompressor (which uses an ASM kernel
that depends on the exact same Huffman tree shapes). */
#define NO_MORE (-1)
/* Sift-down for min-heap. Uses >= for child comparison (prefer right child
on ties), matching the original Reheap() at TREEGEN.CPP:138. */
static void reheap(int entry, int *heap, int heap_len, const u32 *freq)
{
int val = heap[entry];
while (entry <= (heap_len >> 1)) {
int child = entry << 1;
if (child < heap_len)
if (freq[heap[child]] >= freq[heap[child + 1]])
child++;
if (freq[val] < freq[heap[child]])
break;
heap[entry] = heap[child];
entry = child;
}
heap[entry] = val;
}
static void treegen(const u32 *freqs, int nsym, int max_code_bits, u8 *lengths)
{
/* Frequency array extended for internal nodes (original uses upper half
of a 2*nsym WORD array; we use u32 for safety). */
u32 freq[2 * NumSymbols];
int heap[NumSymbols + 1];
int father[2 * NumSymbols];
int heap_len = 0;
memset(lengths, 0, nsym);
for (int i = 0; i < nsym; i++)
freq[i] = freqs[i];
/* BuildHeap: select non-zero symbols, then heapify */
for (int i = 0; i < nsym; i++)
if (freq[i])
heap[++heap_len] = i;
if (heap_len > 1) {
/* Heapify */
for (int i = heap_len; i > 0; i--)
reheap(i, heap, heap_len, freq);
/* BuildCodeTree: extract-one-then-combine pattern (TREEGEN.CPP:154-181).
The first extracted min becomes one child; the current heap[1] after
reheap becomes the other. The combined node replaces heap[1]. */
while (heap_len > 1) {
int extracted = heap[1];
heap[1] = heap[heap_len--];
reheap(1, heap, heap_len, freq);
int internal = heap_len + nsym - 1;
freq[internal] = freq[heap[1]] + freq[extracted];
father[extracted] = internal;
father[heap[1]] = -internal; /* negative = right child */
heap[1] = internal;
reheap(1, heap, heap_len, freq);
}
father[nsym] = 0; /* root sentinel */
/* GenCodeLengths: walk father chain to compute depths */
for (int i = 0; i < nsym; i++) {
if (freq[i]) {
int depth = 0;
int p = father[i];
while (p) {
if (p < 0) p = -p;
p = father[p];
depth++;
}
lengths[i] = (u8)depth;
}
}
/* RepairLengths: faithful port of TREEGEN.CPP:218-364.
Uses sorted linked lists to redistribute code lengths while
maintaining ascending symbol order within each length group. */
struct { int ch; int next; } elems[NumSymbols];
int scl[MaxCodeBits + 2];
int length_count[MaxCodeBits + 2];
for (int i = 0; i <= max_code_bits; i++)
scl[i] = NO_MORE;
memset(length_count, 0, sizeof length_count);
/* Sort symbols by length (iterate high-to-low, prepend → ascending order) */
int pp = 0;
for (int i = nsym - 1; i >= 0; i--) {
if (lengths[i] != 0) {
elems[pp].ch = i;
int len = lengths[i];
if (len > max_code_bits)
len = lengths[i] = (u8)max_code_bits;
length_count[len]++;
elems[pp].next = scl[len];
scl[len] = pp++;
}
}
/* Calculate DCode (Kraft sum) */
u32 dcode = 0;
for (int i = 1; i <= max_code_bits; i++)
dcode += (1UL << (max_code_bits - i)) * (u32)length_count[i];
/* Find first set bit in DCode (error position) */
int bit = 0;
while (bit < max_code_bits && !(dcode & (1UL << bit)))
bit++;
/* Repair loop */
while (bit != max_code_bits) {
int ilen = max_code_bits - bit;
/* Find shortest length with symbols */
int j = ilen;
while (j > 0 && !length_count[j - 1])
j--;
j--; /* j is the length to lengthen from */
/* Extract first element from scl[j] and lengthen it */
int epp = scl[j];
scl[j] = elems[epp].next;
/* Insert into scl[j+1] maintaining ascending symbol order */
if (scl[j + 1] == NO_MORE || elems[scl[j + 1]].ch > elems[epp].ch) {
elems[epp].next = scl[j + 1];
scl[j + 1] = epp;
} else {
int qp = scl[j + 1];
int rp = elems[qp].next;
while (rp != NO_MORE && elems[rp].ch < elems[epp].ch) {
qp = rp;
rp = elems[rp].next;
}
elems[epp].next = rp;
elems[qp].next = epp;
}
lengths[elems[epp].ch]++;
dcode -= 1UL << (max_code_bits - j - 1);
length_count[j]--;
length_count[j + 1]++;
/* Fill freed space by shortening codes from longest levels */
if (ilen - j > 1) {
int space = (1 << (ilen - j - 1)) - 1;
while (space > 0) {
int count = length_count[ilen] < space ? length_count[ilen] : space;
/* Shorten 'count' elements from scl[ilen] to scl[ilen-1] */
int headp = scl[ilen];
int lastp = headp;
for (int k = 0; k < count; k++) {
lengths[elems[lastp].ch]--;
if (k + 1 < count)
lastp = elems[lastp].next;
}
if (count > 0) {
/* Detach shortened chain from scl[ilen] */
int tailp = elems[lastp].next;
scl[ilen] = tailp;
elems[lastp].next = NO_MORE;
/* Merge chain into scl[ilen-1] in sorted order */
int *insert_at = &scl[ilen - 1];
int cp = headp;
while (*insert_at != NO_MORE && cp != NO_MORE) {
if (elems[*insert_at].ch > elems[cp].ch) {
int nextp = elems[cp].next;
int save = *insert_at;
*insert_at = cp;
elems[cp].next = save;
insert_at = &elems[cp].next;
cp = nextp;
} else {
insert_at = &elems[*insert_at].next;
}
}
if (cp != NO_MORE)
*insert_at = cp;
}
length_count[ilen - 1] += count;
length_count[ilen] -= count;
dcode += (1UL << (max_code_bits - ilen)) * (u32)count;
space -= count;
space <<= 1;
ilen++;
}
}
/* Re-check DCode */
while (bit < max_code_bits && !(dcode & (1UL << bit)))
bit++;
}
} else if (heap_len == 1) {
int sym = heap[1];
lengths[sym] = 1;
lengths[(sym + 1) % nsym] = 1;
} else {
lengths[0] = 1;
lengths[1] = 1;
}
}
/* Generate canonical Huffman codes from lengths (sorted by length, then symbol) */
static void codegen(const u8 *lengths, int nsym, u16 *codes)
{
u16 code = 0;
for (int len = 1; len <= MaxCodeBits; len++) {
for (int i = 0; i < nsym; i++) {
if (lengths[i] == len)
codes[i] = code++;
}
code <<= 1;
}
}
/* ---------- Huffman tree encoding (delta-coded, as in TREEENC.CPP) ---------- */
/* Encode a Huffman tree to the bitstream using delta coding against
the previous block's tree (stored in symprev).
Bitstream format (matches decompress.c ht_dec):
tree-changed:1 — 0 = use default tree, 1 = new tree follows
t:2 — (has_hi<<1)|has_lo
tlengths:15×3 — tree-encoding tree (delta/repeat code lengths)
stream:var — delta-coded symbol lengths via tree-encoding tree
The present[] array must exactly match the decoder's rle[][] table:
t=0: {9,10,12,13, 32..127, 256..343} = 188 symbols
t=1: {0..127, 256..343} = 216 symbols
t=2: {9,10,12,13, 32..343} = 316 symbols
t=3: {0..343} = 344 symbols
RLE repeat code: RepeatCode followed by count c.
Decoder emits c + MinRepeat - 1 copies of the previous value. */
static int tree_enc(struct bits_out *bo, const u8 lengths[NumSymbols], u8 symprev[NumSymbols])
{
int r;
/* tree-changed = 0 means "use default tree" (resets symprev) */
u8 def_tree[NumSymbols];
uc2_default_lengths(def_tree);
if (memcmp(lengths, def_tree, NumSymbols) == 0) {
r = bitsout_put(bo, 0, 1);
if (r < 0) return r;
memcpy(symprev, def_tree, NumSymbols);
return 0;
}
r = bitsout_put(bo, 1, 1); /* new tree */
if (r < 0) return r;
/* has_lo: need full 0..127 encoding?
Symbols 9,10,12,13 (tab,LF,FF,CR) are always individually coded.
has_lo must be set if ANY of {0..8, 11, 14..31} have non-zero length. */
int has_lo = 0;
for (int i = 0; i <= 8 && !has_lo; i++)
if (lengths[i] > 0) has_lo = 1;
if (!has_lo && lengths[11] > 0) has_lo = 1;
if (!has_lo)
for (int i = 14; i <= 31; i++)
if (lengths[i] > 0) { has_lo = 1; break; }
int has_hi = 0;
for (int i = 128; i < 256; i++)
if (lengths[i] > 0) { has_hi = 1; break; }
int t = (has_hi << 1) | has_lo;
r = bitsout_put(bo, t, 2);
if (r < 0) return r;
/* Build present[] to exactly match decoder's rle[][] regions */
int present[NumSymbols];
memset(present, 0, sizeof present);
switch (t) {
case 0: /* no lo, no hi */
present[9] = present[10] = 1;
present[12] = present[13] = 1;
for (int i = 32; i < 128; i++) present[i] = 1;
for (int i = 256; i < NumSymbols; i++) present[i] = 1;
break;
case 1: /* has_lo */
for (int i = 0; i < 128; i++) present[i] = 1;
for (int i = 256; i < NumSymbols; i++) present[i] = 1;
break;
case 2: /* has_hi */
present[9] = present[10] = 1;
present[12] = present[13] = 1;
for (int i = 32; i < NumSymbols; i++) present[i] = 1;
break;
case 3: /* both */
for (int i = 0; i < NumSymbols; i++) present[i] = 1;
break;
}
/* Generate delta stream for present symbols */
u8 stream[NumSymbols];
int stream_len = 0;
for (int i = 0; i < NumSymbols; i++)
if (present[i])
stream[stream_len++] = ivval[symprev[i]][lengths[i]];
/* Compute frequencies matching the RLE pattern in the original
InsertExtraCode: trigger at run > MinRepeat (>6), one RepeatCode
per chunk of max RepeatCode+MinRepeat (20), remainder handled by
next iteration. */
u32 tfreqs[NumLenCodes];
memset(tfreqs, 0, sizeof tfreqs);
for (int i = 0; i < stream_len; ) {
int run = 1;
while (i + run < stream_len && stream[i + run] == stream[i])
run++;
if (run > (int)MinRepeat) {
int chunk = run;
if (chunk > (int)(RepeatCode + MinRepeat))
chunk = RepeatCode + MinRepeat;
tfreqs[stream[i]]++;
tfreqs[RepeatCode]++;
tfreqs[chunk - MinRepeat]++;
i += chunk;
} else {
tfreqs[stream[i]]++;
i++;
}
}
/* Generate tree-encoding tree (15 symbols, max 7-bit codes) */
u8 tlengths[NumLenCodes];
treegen(tfreqs, NumLenCodes, 7, tlengths);
u16 tcodes[NumLenCodes];
codegen(tlengths, NumLenCodes, tcodes);
/* Write tree-encoding tree lengths (15 × 3 bits) */
for (int i = 0; i < NumLenCodes; i++) {
r = bitsout_put(bo, tlengths[i], 3);
if (r < 0) return r;
}
/* Write delta-coded symbol stream with RLE.
Matches original InsertExtraCode + encoding loop:
- Trigger at run > MinRepeat (>6, i.e. >=7)
- One RepeatCode per run chunk, max RepeatCode+MinRepeat (20)
- Remainder handled by next iteration */
for (int i = 0; i < stream_len; ) {
int run = 1;
while (i + run < stream_len && stream[i + run] == stream[i])
run++;
if (run > (int)MinRepeat) {
int chunk = run;
if (chunk > (int)(RepeatCode + MinRepeat))
chunk = RepeatCode + MinRepeat;
r = bitsout_put(bo, tcodes[stream[i]], tlengths[stream[i]]);
if (r < 0) return r;
int c = chunk - MinRepeat;
r = bitsout_put(bo, tcodes[RepeatCode], tlengths[RepeatCode]);
if (r < 0) return r;
r = bitsout_put(bo, tcodes[c], tlengths[c]);
if (r < 0) return r;
i += chunk;
} else {
r = bitsout_put(bo, tcodes[stream[i]], tlengths[stream[i]]);
if (r < 0) return r;
i++;
}
}
/* Update symprev for next block */
for (int i = 0; i < NumSymbols; i++)
symprev[i] = lengths[i];
return 0;
}
/* ---------- LZ77 compressor core ---------- */
struct compressor {
/* Sliding window (64KB circular buffer, u16 index wraps naturally) */
u8 data[UC2_BUF_SIZE];
/* Hash chains */
u16 head[8192]; /* hash -> most recent position */
u16 prev[UC2_BUF_SIZE]; /* position -> previous position with same hash */
/* Current position and limits */
u16 pos; /* current compression position */
u16 end; /* end of valid data */
unsigned data_len; /* total bytes loaded so far */
/* Intermediate buffer for literals/distances/lengths */
u16 ibuf[32768];
unsigned ibuf_pos;
/* Frequency counts for current block */
u32 bd_freq[NumByteSym + NumDistSym];
u32 l_freq[NumLenSym];
/* Previous tree lengths (for delta coding) */
u8 symprev[NumSymbols];
int block_count;
/* Output bitstream */
struct bits_out bo;
u8 outbuf[4096];
/* Compression parameters */
unsigned max_search;
unsigned lazy_depth;
unsigned lazy_limit;
unsigned give_up;
int use_rans; /* 1 = rANS entropy coding (method 10) */
/* Total compressed bytes written */
unsigned compressed_bytes;
};
static inline u16 hash3(const u8 *p)
{
return (u16)(p[0] ^ (p[1] << 3) ^ ((0x7f & p[2]) << 6));
}
/* Find longest match at current position. Returns match length (0 if none). */
static unsigned find_match(struct compressor *c, u16 pos, unsigned max_depth,
unsigned give_up, unsigned *match_dist)
{
unsigned best_len = UC2_MIN_MATCH - 1;
unsigned best_dist = 0;
u16 h = hash3(c->data + pos);
u16 chain = c->head[h];
unsigned depth = 0;
while (depth < max_depth) {
u16 dist = (u16)(pos - chain);
if (dist == 0 || dist > UC2_MAX_DIST)
break;
/* Quick filter: check byte at best_len position first */
if (c->data[(u16)(chain + best_len)] == c->data[(u16)(pos + best_len)]) {
/* Full comparison */
unsigned len = 0;
unsigned max_len = UC2_MAX_LEN;
u16 avail = (u16)(c->end - pos);
if (max_len > avail) max_len = avail;
while (len < max_len &&
c->data[(u16)(chain + len)] == c->data[(u16)(pos + len)])
len++;
if (len > best_len) {
best_len = len;
best_dist = dist;
if (len >= give_up)
break;
}
}
chain = c->prev[chain];
depth++;
if ((u16)(pos - chain) > UC2_MAX_DIST)
break;
}
*match_dist = best_dist;
return best_len >= UC2_MIN_MATCH ? best_len : 0;
}
static void hash_enter(struct compressor *c, u16 pos)
{
u16 h = hash3(c->data + pos);
c->prev[pos] = c->head[h];
c->head[h] = pos;
}
/* Encode a distance into the intermediate buffer */
static void encode_dist(struct compressor *c, unsigned dist)
{
unsigned sym, extra, nbits;
if (dist <= 15) {
sym = dist - 1 + NumByteSym; /* symbols 256..270 */
c->bd_freq[sym]++;
c->ibuf[c->ibuf_pos++] = (u16)(dist + 256 - 1);
return;
}
if (dist <= 255) {
unsigned slot = (dist - 16) / 16;
sym = slot + 15 + NumByteSym; /* symbols 271..285 */
extra = (dist - 16) % 16;
nbits = 4;
} else if (dist <= 4095) {
unsigned slot = (dist - 256) / 256;
sym = slot + 30 + NumByteSym; /* symbols 286..300 */
extra = (dist - 256) % 256;
nbits = 8;
} else {
unsigned slot = (dist - 4096) / 4096;
sym = slot + 45 + NumByteSym; /* symbols 301..315 */
extra = (dist - 4096) % 4096;
nbits = 12;
}
c->bd_freq[sym]++;
c->ibuf[c->ibuf_pos++] = (u16)(sym - NumByteSym + 256);
c->ibuf[c->ibuf_pos++] = (u16)extra;
(void)nbits;
}
static void encode_len(struct compressor *c, unsigned len)
{
unsigned sym, extra;
if (len <= 10) {
sym = len - 3;
c->l_freq[sym]++;
c->ibuf[c->ibuf_pos++] = (u16)len;
return;
}
if (len <= 26) {
sym = (len - 11) / 2 + 8;
extra = (len - 11) % 2;
} else if (len <= 90) {
sym = (len - 27) / 8 + 16;
extra = (len - 27) % 8;
} else if (len <= 154) {
sym = 24;
extra = len - 91;
} else if (len <= 666) {
sym = 25;
extra = len - 155;
} else if (len <= 2714) {
sym = 26;
extra = len - 667;
} else {
sym = 27;
extra = len - 2715;
}
c->l_freq[sym]++;
c->ibuf[c->ibuf_pos++] = (u16)len;
(void)extra;
}
/* Distance/length encoding tables (for Huffman encoding phase) */
static const struct { u16 base; u8 bits; } dist_enc[] = {
/* 0..14: dist 1..15, 0 extra bits */
{1,0},{2,0},{3,0},{4,0},{5,0},{6,0},{7,0},{8,0},
{9,0},{10,0},{11,0},{12,0},{13,0},{14,0},{15,0},
/* 15..29: dist 16..240 base, 4 extra bits */
{16,4},{32,4},{48,4},{64,4},{80,4},{96,4},{112,4},{128,4},
{144,4},{160,4},{176,4},{192,4},{208,4},{224,4},{240,4},
/* 30..44: dist 256..3840 base, 8 extra bits */
{256,8},{512,8},{768,8},{1024,8},{1280,8},{1536,8},{1792,8},{2048,8},
{2304,8},{2560,8},{2816,8},{3072,8},{3328,8},{3584,8},{3840,8},
/* 45..59: dist 4096..61440 base, 12 extra bits */
{4096,12},{8192,12},{12288,12},{16384,12},{20480,12},{24576,12},
{28672,12},{32768,12},{36864,12},{40960,12},{45056,12},{49152,12},
{53248,12},{57344,12},{61440,12},
};
static const struct { u16 base; u8 bits; } len_enc[] = {
{3,0},{4,0},{5,0},{6,0},{7,0},{8,0},{9,0},{10,0},
{11,1},{13,1},{15,1},{17,1},{19,1},{21,1},{23,1},{25,1},
{27,3},{35,3},{43,3},{51,3},{59,3},{67,3},{75,3},{83,3},
{91,6},{155,9},{667,11},{2715,15},
};
/* Find the distance symbol for a given distance */
static int dist_to_sym(unsigned dist)
{
for (int i = NumDistSym - 1; i >= 0; i--)
if (dist >= dist_enc[i].base)
return i;
return 0;
}
/* Find the length symbol for a given length */
static int len_to_sym(unsigned len)
{
for (int i = NumLenSym - 1; i >= 0; i--)
if (len >= len_enc[i].base)
return i;
return 0;
}
/* Flush intermediate buffer: generate Huffman trees and encode data */
static int flush_block(struct compressor *c, int is_last)
{
int r;
/* Generate Huffman trees from frequency data */
u8 lengths[NumSymbols];
/* The original compressor uses the default tree (tree-changed=0) for
the first block when ibuf has fewer than 256 entries
(ULTRACMP.CPP:1105). For larger blocks, generate a custom tree.
This matches the original's behavior: small data uses the default
tree (compatible), large data uses optimal trees. */
if (c->block_count == 0 && c->ibuf_pos < 256) {
uc2_default_lengths(lengths);
} else {
treegen(c->bd_freq, NumByteSym + NumDistSym, MaxCodeBits, lengths);
treegen(c->l_freq, NumLenSym, MaxCodeBits, lengths + NumByteSym + NumDistSym);
}
/* Emit block-present flag */
r = bitsout_put(&c->bo, 1, 1);
if (r < 0) return r;
/* Encode and emit Huffman tree */
r = tree_enc(&c->bo, lengths, c->symprev);
if (r < 0) return r;
/* Generate canonical codes */
u16 bd_codes[NumByteSym + NumDistSym];
u16 l_codes[NumLenSym];
codegen(lengths, NumByteSym + NumDistSym, bd_codes);
codegen(lengths + NumByteSym + NumDistSym, NumLenSym, l_codes);
/* Encode buffered literals/matches.
ibuf format: literal = byte (0..255), distance = sym_idx + 256,
followed by extra value (if dist_enc[sym_idx].bits > 0),
followed by raw length value. */
unsigned i = 0;
while (i < c->ibuf_pos) {
u16 val = c->ibuf[i++];
if (val < 256) {
/* Literal byte */
r = bitsout_put(&c->bo, bd_codes[val], lengths[val]);
if (r < 0) return r;
} else {
/* Distance: val - 256 is the distance symbol index */
int dsym = val - 256;
r = bitsout_put(&c->bo, bd_codes[NumByteSym + dsym],
lengths[NumByteSym + dsym]);
if (r < 0) return r;
if (dist_enc[dsym].bits > 0) {
u16 extra = c->ibuf[i++];
r = bitsout_put(&c->bo, extra, dist_enc[dsym].bits);
if (r < 0) return r;
}
/* Length follows distance */
u16 len = c->ibuf[i++];
int lsym = len_to_sym(len);
r = bitsout_put(&c->bo, l_codes[lsym],
lengths[NumByteSym + NumDistSym + lsym]);
if (r < 0) return r;
if (len_enc[lsym].bits > 0) {
r = bitsout_put(&c->bo, len - len_enc[lsym].base,
len_enc[lsym].bits);
if (r < 0) return r;
}
}
}
/* Emit end-of-block marker: distance = EOB_MARK, length = 3 */
{
int dsym = dist_to_sym(UC2_EOB_MARK);
r = bitsout_put(&c->bo, bd_codes[NumByteSym + dsym],
lengths[NumByteSym + dsym]);
if (r < 0) return r;
if (dist_enc[dsym].bits > 0) {
r = bitsout_put(&c->bo, UC2_EOB_MARK - dist_enc[dsym].base,
dist_enc[dsym].bits);
if (r < 0) return r;
}
/* Length = 3 (symbol 0) */
r = bitsout_put(&c->bo, l_codes[0],
lengths[NumByteSym + NumDistSym]);
if (r < 0) return r;
}
/* Reset intermediate buffer and frequencies */
c->ibuf_pos = 0;
c->block_count++;
memset(c->bd_freq, 0, sizeof c->bd_freq);
memset(c->l_freq, 0, sizeof c->l_freq);
/* If last block, emit end-of-stream (block-present = 0) */
if (is_last) {
r = bitsout_put(&c->bo, 0, 1);
if (r < 0) return r;
}
return 0;
}
/* Flush block using rANS entropy coding (method 10).
Encodes symbol IDs with rANS and extra bits separately. */
static int flush_block_rans(struct compressor *c, int is_last)
{
int r;
/* Collect symbol IDs and extra bits from ibuf */
int *syms = malloc((c->ibuf_pos + 2) * sizeof(int));
/* Extra bits: pairs of (value, nbits) */
struct { u16 val; u8 bits; } *extras = malloc((c->ibuf_pos + 2) * sizeof *extras);
if (!syms || !extras) { free(syms); free(extras); return -1; }
int nsyms = 0, nextras = 0;
unsigned i = 0;
while (i < c->ibuf_pos) {
u16 val = c->ibuf[i++];
if (val < 256) {
syms[nsyms++] = val;
} else {
int dsym = val - 256;
syms[nsyms++] = NumByteSym + dsym;
if (dist_enc[dsym].bits > 0) {
extras[nextras].val = c->ibuf[i++];
extras[nextras].bits = dist_enc[dsym].bits;
nextras++;
}
u16 len = c->ibuf[i++];
int lsym = len_to_sym(len);
syms[nsyms++] = NumByteSym + NumDistSym + lsym;
if (len_enc[lsym].bits > 0) {
extras[nextras].val = (u16)(len - len_enc[lsym].base);
extras[nextras].bits = len_enc[lsym].bits;
nextras++;
}
}
}
/* Add EOB marker symbols */
int eob_dsym = dist_to_sym(UC2_EOB_MARK);
syms[nsyms++] = NumByteSym + eob_dsym;
if (dist_enc[eob_dsym].bits > 0) {
extras[nextras].val = (u16)(UC2_EOB_MARK - dist_enc[eob_dsym].base);
extras[nextras].bits = dist_enc[eob_dsym].bits;
nextras++;
}
syms[nsyms++] = NumByteSym + NumDistSym + 0; /* length = 3 */
/* Build rANS frequency table from all symbols */
u32 freqs[NumSymbols];
memset(freqs, 0, sizeof freqs);
for (int j = 0; j < nsyms; j++)
freqs[syms[j]]++;
struct uc2_rans_table tab;
uc2_rans_build_table(&tab, freqs, NumSymbols);
/* Encode symbols with rANS (reverse order) */
struct uc2_rans_enc enc;
uc2_rans_enc_init(&enc, &tab);
for (int j = nsyms - 1; j >= 0; j--)
uc2_rans_encode(&enc, syms[j]);
uint8_t *rans_data;
size_t rans_len = uc2_rans_enc_finish(&enc, &rans_data);
uc2_rans_enc_free(&enc);
/* rANS block format (method 10):
[block-present:1=1] [nsyms:16] [rans_len:16]
[freq_table:344×12bits] [rans_data] [extra_bits]
No Huffman tree — method ID in COMPRESS record signals rANS. */
r = bitsout_put(&c->bo, 1, 1); /* block-present */
if (r < 0) goto out;
/* Write symbol count and rANS data length */
r = bitsout_put(&c->bo, (unsigned)(nsyms >> 8), 8);
if (r < 0) goto out;
r = bitsout_put(&c->bo, (unsigned)(nsyms & 0xFF), 8);
if (r < 0) goto out;
r = bitsout_put(&c->bo, (unsigned)(rans_len >> 8), 8);
if (r < 0) goto out;
r = bitsout_put(&c->bo, (unsigned)(rans_len & 0xFF), 8);
if (r < 0) goto out;
/* Write normalized frequency table (344 × 12 bits) */
for (int j = 0; j < NumSymbols; j++) {
r = bitsout_put(&c->bo, tab.freq[j], 12);
if (r < 0) goto out;
}
/* Write rANS encoded data (byte-aligned) */
for (size_t j = 0; j < rans_len; j++) {
r = bitsout_put(&c->bo, rans_data[j], 8);
if (r < 0) goto out;
}
/* Write extra bits */
for (int j = 0; j < nextras; j++) {
r = bitsout_put(&c->bo, extras[j].val, extras[j].bits);
if (r < 0) goto out;
}
/* Reset */
c->ibuf_pos = 0;
c->block_count++;
memset(c->bd_freq, 0, sizeof c->bd_freq);
memset(c->l_freq, 0, sizeof c->l_freq);
if (is_last) {
r = bitsout_put(&c->bo, 0, 1); /* block-present = 0 */
if (r < 0) goto out;
}
r = 0;
out:
free(syms);
free(extras);
free(rans_data);
return r;
}
/* ---------- Public compression API ---------- */
struct compress_ctx {
struct compressor comp;
struct csum csum;
unsigned total_in;
unsigned total_out;
int finished;
};
/* Counting writer: wraps user writer to track compressed size */
struct count_writer {
int (*write)(void *ctx, const void *ptr, unsigned len);
void *ctx;
unsigned *count;
};
static int count_write(void *ctx, const void *ptr, unsigned len)
{
struct count_writer *cw = ctx;
*cw->count += len;
return cw->write(cw->ctx, ptr, len);
}
int uc2_compress_ex(
int level,
const void *master, unsigned master_size,
int (*read)(void *context, void *buf, unsigned len),
void *read_ctx,
int (*write)(void *context, const void *ptr, unsigned len),
void *write_ctx,
unsigned size,
unsigned short *checksum_out,
unsigned *compressed_size_out)
{
struct compress_ctx *ctx = calloc(1, sizeof *ctx);
if (!ctx) return UC2_UserFault;
struct compressor *c = &ctx->comp;
/* Set compression parameters based on level.
Levels 2-5: Huffman (backward compatible with original UC2 Pro).
Levels 6-9: rANS entropy coding (better compression, UC2 v3 only). */
c->use_rans = (level >= 6);
int lz_level = c->use_rans ? level - 4 : level; /* rANS 6→2, 7→3, 8→4, 9→5 */
switch (lz_level) {
case 2: c->max_search = 15; c->lazy_depth = 2; c->lazy_limit = 15; c->give_up = 25; break;
case 3: c->max_search = 70; c->lazy_depth = 10; c->lazy_limit = 30; c->give_up = 50; break;
case 5: c->max_search = 10000; c->lazy_depth = 5000; c->lazy_limit = 200; c->give_up = 100; break;
default: /* level 4 or 8 = Tight */
c->max_search = 600; c->lazy_depth = 50; c->lazy_limit = 40; c->give_up = 100; break;
}
/* Initialize */
csum_init(&ctx->csum);
uc2_default_lengths(c->symprev);
memset(c->head, 0, sizeof c->head);
memset(c->bd_freq, 0, sizeof c->bd_freq);
memset(c->l_freq, 0, sizeof c->l_freq);
c->ibuf_pos = 0;
c->block_count = 0;
struct count_writer cw = { .write = write, .ctx = write_ctx, .count = &ctx->total_out };
bitsout_init(&c->bo, count_write, &cw, c->outbuf, sizeof c->outbuf);
/* Read all input data into circular buffer and compress */
unsigned remaining = size;
u16 load_pos = 0;
/* Pre-fill circular buffer with master data (LZ77 dictionary prefix) */
if (master && master_size > 0) {
unsigned ms = master_size;
if (ms > UC2_BUF_SIZE - UC2_MIN_MATCH)
ms = UC2_BUF_SIZE - UC2_MIN_MATCH;
memcpy(c->data, master, ms);
for (unsigned i = 0; i + 2 < ms; i++)
hash_enter(c, (u16)i);
load_pos = (u16)ms;
c->pos = load_pos;
c->end = load_pos;
}
/* Pre-count EOB distance symbol frequency so the tree includes it */
c->bd_freq[NumByteSym + dist_to_sym(UC2_EOB_MARK)]++;
c->l_freq[0]++; /* length = 3 for EOB marker */
while (remaining > 0) {
/* Load a chunk into the circular buffer */
unsigned chunk = remaining;
if (chunk > UC2_READ_SIZE) chunk = UC2_READ_SIZE;
int nread = read(read_ctx, c->data + load_pos, chunk);
if (nread <= 0) break;
csum_update(&ctx->csum, c->data + load_pos, nread);
remaining -= nread;
load_pos = (u16)(load_pos + nread);
c->end = load_pos;
/* Compress loaded data */
while ((u16)(c->end - c->pos) >= UC2_MIN_MATCH) {
/* Enter current position into hash */
if ((u16)(c->end - c->pos) >= 3)
hash_enter(c, c->pos);
unsigned dist;
unsigned len = find_match(c, c->pos, c->max_search, c->give_up, &dist);
if (len == 0) {
/* Literal */
c->bd_freq[c->data[c->pos]]++;
c->ibuf[c->ibuf_pos++] = c->data[c->pos];
c->pos++;
} else {
/* Lazy evaluation: if match is short, check next position */
if (len < c->lazy_limit && (u16)(c->end - c->pos) > len) {
unsigned dist2;
if ((u16)(c->end - (u16)(c->pos + 1)) >= 3)
hash_enter(c, (u16)(c->pos + 1));
unsigned len2 = find_match(c, (u16)(c->pos + 1),
c->lazy_depth, c->give_up, &dist2);
if (len2 > len) {
/* Better match at next position — emit literal */
c->bd_freq[c->data[c->pos]]++;
c->ibuf[c->ibuf_pos++] = c->data[c->pos];
c->pos++;
len = len2;
dist = dist2;
}
}
/* Emit match */
encode_dist(c, dist);
encode_len(c, len);
/* Enter skipped positions into hash */
for (unsigned j = 1; j < len && (u16)(c->end - (u16)(c->pos + j)) >= 3; j++)
hash_enter(c, (u16)(c->pos + j));
c->pos = (u16)(c->pos + len);
}
/* Flush block if intermediate buffer is getting full */
if (c->ibuf_pos > 27000) {
int r = c->use_rans ? flush_block_rans(c, 0) : flush_block(c, 0);
if (r < 0) { free(ctx); return r; }
/* Re-add EOB marker frequency for next block */
c->bd_freq[NumByteSym + dist_to_sym(UC2_EOB_MARK)]++;
c->l_freq[0]++;
}
}
}
/* Handle trailing bytes (less than MIN_MATCH) */
while (c->pos != c->end) {
c->bd_freq[c->data[c->pos]]++;
c->ibuf[c->ibuf_pos++] = c->data[c->pos];
c->pos++;
}
/* Flush final block */
int r = c->use_rans ? flush_block_rans(c, 1) : flush_block(c, 1);
if (r < 0) { free(ctx); return r; }
r = bitsout_finish(&c->bo);
if (r < 0) { free(ctx); return r; }
if (checksum_out)
*checksum_out = csum_get(&ctx->csum);
if (compressed_size_out)
*compressed_size_out = ctx->total_out;
free(ctx);
return 0;
}
int uc2_compress(
int level,
int (*read)(void *context, void *buf, unsigned len),
void *read_ctx,
int (*write)(void *context, const void *ptr, unsigned len),
void *write_ctx,
unsigned size,
unsigned short *checksum_out,
unsigned *compressed_size_out)
{
return uc2_compress_ex(level, NULL, 0, read, read_ctx, write, write_ctx,
size, checksum_out, compressed_size_out);
}