can1357/oh-my-pi · error · ArchiveError

Invalid ${label} compressed data: empty block

Error message

Invalid ${label} compressed data: empty block

What it means

Each Huffman block begins with a 16-bit command count. A zero count is invalid because decompression still needs to fill the output; the library throws rather than looping forever.

Source

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

/** @internal Decode the static-Huffman LZSS stream shared by LZH and ARJ methods 1-3. */
export function decompressLhStatic(
	packed: Uint8Array,
	outSize: number,
	dictionarySize: number,
	positionBits: number,
	positionSymbols: number,
	label: string,
): Uint8Array {
	const reader = new MsbBitReader(packed, label);
	const output = new Uint8Array(outSize);
	let outputPosition = 0;
	let blockRemaining = 0;
	let commands: CanonicalHuffman | undefined;
	let positions: CanonicalHuffman | undefined;
	while (outputPosition < outSize) {
		if (blockRemaining === 0) {
			blockRemaining = reader.read(16);
			if (blockRemaining === 0) throw new ArchiveError(`Invalid ${label} compressed data: empty block`);
			const temporary = readTemporaryTree(reader, label);
			commands = readCommandTree(reader, temporary, label);
			positions = readPositionTree(reader, positionBits, positionSymbols, label);
		}
		blockRemaining--;
		const symbol = commands!.decode(reader);
		if (symbol < 256) {
			output[outputPosition++] = symbol;
			continue;
		}
		const length = symbol - 256 + 3;
		if (length > outSize - outputPosition) {
			throw new ArchiveError(`Invalid ${label} compressed data: match exceeds declared size`);
		}
		const positionCode = positions!.decode(reader);
		let distance = positionCode;
		if (positionCode > 1) {
			const lowBitCount = positionCode - 1;

View on GitHub (pinned to 9690622007)

Solutions

  1. Verify the compressed-data start offset (extended headers parsed/skipped correctly)
  2. Re-obtain the archive and check CRC
  3. Confirm the method variant is supported before decompressing
  4. Catch ArchiveError for untrusted input

Example fix

// before
const data = packed.slice(headerSize); // forgot to skip extended headers
// after
const data = packed.slice(headerSize + extendedHeaderBytes);
Defensive patterns

Strategy: try-catch

Validate before calling

if (packed.length < 2) throw new Error("compressed member too short to contain a block header");

Try / catch

try {
  const out = decompressLhStatic(packed, outSize, positionBits, positionSymbols, label);
} catch (err) {
  if (err instanceof ArchiveError) reportCorruptEntry(entryName, err.message);
  else throw err;
}

Prevention

When it happens

Trigger: decompressLhStatic reads a block whose 16-bit blockRemaining header is 0 — corrupt data, zero-filled region, or reader positioned at the wrong offset (e.g. wrong extended-header skip before data start).

Common situations: Truncated archives padded with zeros; incorrect handling of -lh4- vs -lh5- header differences causing offset drift; fuzzed inputs.

Related errors


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