can1357/oh-my-pi · error · ArchiveError

Invalid CAB archive: LZX stream uses an empty Huffman tree

Error message

Invalid CAB archive: LZX stream uses an empty Huffman tree

What it means

Thrown by LzxHuffmanTable.decode when attempting to decode a symbol from a tree that was constructed with no symbols (empty === true; only possible for tables built with allowEmpty). Decoding requires reading bits and matching them to a code, which is impossible with zero symbols.

Source

Thrown at packages/utils/src/ar/codecs/lzx.ts:105

			if (code + this.#counts[length]! > 2 ** length) {
				throw new ArchiveError("Invalid CAB archive: oversubscribed LZX Huffman tree");
			}
			this.#firstCodes[length] = code;
			this.#firstSymbols[length] = symbolOffset;
			symbolOffset += this.#counts[length]!;
		}

		this.#symbols = new Uint16Array(symbolCount);
		const next = this.#firstSymbols.slice();
		for (let symbol = 0; symbol < lengths.byteLength; symbol++) {
			const length = lengths[symbol]!;
			if (length !== 0) this.#symbols[next[length]!] = symbol;
			next[length]!++;
		}
	}

	decode(reader: LzxBitReader): number {
		if (this.empty) throw new ArchiveError("Invalid CAB archive: LZX stream uses an empty Huffman tree");
		let code = 0;
		for (let length = 1; length <= 16; length++) {
			code = code * 2 + reader.readBits(1);
			const relative = code - this.#firstCodes[length]!;
			if (relative >= 0 && relative < this.#counts[length]!) {
				return this.#symbols[this.#firstSymbols[length]! + relative]!;
			}
		}
		throw new ArchiveError("Invalid CAB archive: invalid LZX Huffman symbol");
	}
}

function readCodeLengths(reader: LzxBitReader, lengths: Uint8Array, first: number, last: number): void {
	const pretreeLengths = new Uint8Array(20);
	for (let index = 0; index < pretreeLengths.byteLength; index++) pretreeLengths[index] = reader.readBits(4);
	const pretree = new LzxHuffmanTable(pretreeLengths);
	let index = first;
	while (index < last) {

View on GitHub (pinned to 9690622007)

Solutions

  1. Re-acquire or repair the CAB archive; the stream references a tree that has no codes.
  2. Recompress with a standard CAB tool so referenced trees are populated.
  3. Confirm you are decoding with the same LZX variant (CAB LZX) the data was produced with.

Example fix

// before
const extra = this.#lengthTable.decode(reader)
// after
if (!this.#lengthTable || this.#lengthTable.empty) throw new ArchiveError("Invalid CAB archive: length tree required but empty")
const extra = this.#lengthTable.decode(reader)
Defensive patterns

Strategy: try-catch

Validate before calling

if (lengthTable && lengthTable.empty) {
  throw new Error('Stream references an empty length tree — archive is corrupt')
}

Type guard

function isDecodable(table: LzxHuffmanTable | undefined): table is LzxHuffmanTable {
  return table !== undefined && !table.empty
}

Try / catch

try {
  const out = decoder.decompressFrame(bytes, size)
} catch (err) {
  if (err instanceof ArchiveError && err.message.includes('empty Huffman tree')) {
    throw new Error('CAB archive corrupt: empty tree referenced during decode')
  }
  throw err
}

Prevention

When it happens

Trigger: Decoding a match length via an empty secondary length tree whose position table maps to a primary length of 7 (matchLength === NUM_PRIMARY_LENGTHS), i.e. the stream says 'read a length symbol' but the length tree has no symbols.

Common situations: Corrupted CAB archives where the length tree was encoded as all-zero but the compressed data still references it, or a compressor/decoder mismatch (e.g. a stream produced with different tree conventions).

Related errors


AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31). Data as JSON: /api/errors/b3d2ae935772adb6. Report an issue: GitHub.