can1357/oh-my-pi · error · ArchiveError

Invalid ${label} temporary Huffman table size

Error message

Invalid ${label} temporary Huffman table size

What it means

readTemporaryTree() reads a 5-bit encodedCount declaring how many code lengths follow for the 19-symbol temporary table. Values above 19 (symbolCount) are impossible in a valid LH stream, so the library rejects them rather than reading out of bounds. encodedCount === 0 is legal and selects the single-symbol fallback.

Source

Thrown at packages/utils/src/ar/lzh.ts:147

	}
}

function readCodeLength(reader: MsbBitReader, label: string): number {
	let length = reader.read(3);
	if (length === 7) {
		while (reader.read(1) !== 0) {
			length++;
			if (length > 16) throw new ArchiveError(`Invalid ${label} Huffman table: code is too long`);
		}
	}
	return length;
}

function readTemporaryTree(reader: MsbBitReader, label: string): CanonicalHuffman {
	const symbolCount = 19;
	const encodedCount = reader.read(5);
	if (encodedCount === 0) return CanonicalHuffman.single(reader.read(5), symbolCount, label);
	if (encodedCount > symbolCount) throw new ArchiveError(`Invalid ${label} temporary Huffman table size`);
	const lengths = new Uint8Array(symbolCount);
	let index = 0;
	while (index < encodedCount) {
		lengths[index++] = readCodeLength(reader, label);
		if (index === 3) {
			const skipped = reader.read(2);
			if (index + skipped > encodedCount) throw new ArchiveError(`Invalid ${label} temporary Huffman table`);
			index += skipped;
		}
	}
	return CanonicalHuffman.build(lengths, symbolCount, label);
}

function readCommandTree(reader: MsbBitReader, temporary: CanonicalHuffman, label: string): CanonicalHuffman {
	const symbolCount = 510;
	const encodedCount = reader.read(9);
	if (encodedCount === 0) return CanonicalHuffman.single(reader.read(9), symbolCount, label);
	if (encodedCount > symbolCount) throw new ArchiveError(`Invalid ${label} command Huffman table size`);

View on GitHub (pinned to 9690622007)

Solutions

  1. Verify the archive's method id (LH5/LH6/LH7) matches the decompressor being used
  2. Recheck header-size computation so the bit reader starts exactly at the table section
  3. Re-obtain a clean copy if external validation fails
  4. Catch ArchiveError and report corruption; do not retry

Example fix

// before: assuming LH5 table layout for every archive
const tree = readTemporaryTree(reader, 'temporary');
// after: dispatch on the archive's declared method
if (method === 'LH4') { /* different table routine */ } else { const tree = readTemporaryTree(reader, 'temporary'); }
Defensive patterns

Strategy: try-catch

Validate before calling

// Cheap pre-checks before invoking the decompressor:
if (data.length < 2) throw new Error('compressed block too small');
// method byte sanity (extracted from the LHA header you parsed):
if (!/^L[HZ][4567]$/.test(method)) throw new Error(`unsupported method ${method}`);

Type guard

function isSupportedLhMethod(method: string): boolean {
  return ['LH4', 'LH5', 'LH6', 'LH7'].includes(method);
}

Try / catch

try {
  const out = decompressLhStatic(data, size);
} catch (err) {
  if (err instanceof ArchiveError && err.message.includes('table size')) {
    throw new Error('archive table header is corrupt or method variant mismatched');
  }
  throw err;
}

Prevention

When it happens

Trigger: DecompressLhStatic encountering a corrupted or misaligned table header where the 5-bit count is garbage (e.g. data section parsed as table, or LH-variant mismatch), or fuzzed input with counts like 20-31.

Common situations: Wrong start offset from a misparsed LHA header, corrupt downloads, mixing LH4-style streams with an LH5+ parser, fuzzing.

Related errors


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