can1357/oh-my-pi · error · ArchiveError

Invalid LZH -lzs- data: output exceeds declared size

Error message

Invalid LZH -lzs- data: output exceeds declared size

What it means

decompressLzs's emit helper writes literal bytes; if the stream tries to emit more bytes than the declared outSize, the library refuses to overflow the fixed-size output buffer.

Source

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

			throw new ArchiveError(`Invalid ${label} compressed data: history distance is out of range`);
		}
		let sourcePosition = outputPosition - distance - 1;
		for (let index = 0; index < length; index++) output[outputPosition++] = output[sourcePosition++]!;
	}
	if (blockRemaining !== 0) throw new ArchiveError(`Invalid ${label} compressed data: block exceeds declared size`);
	reader.assertZeroPadding();
	return output;
}

function decompressLzs(packed: Uint8Array, outSize: number): Uint8Array {
	const reader = new MsbBitReader(packed, "LZH -lzs-");
	const output = new Uint8Array(outSize);
	const history = new Uint8Array(2048);
	history.fill(0x20);
	let historyPosition = 2048 - 17;
	let outputPosition = 0;
	const emit = (value: number): void => {
		if (outputPosition >= outSize) throw new ArchiveError("Invalid LZH -lzs- data: output exceeds declared size");
		output[outputPosition++] = value;
		history[historyPosition] = value;
		historyPosition = (historyPosition + 1) & 2047;
	};
	while (outputPosition < outSize) {
		if (reader.read(1) !== 0) {
			emit(reader.read(8));
		} else {
			const position = reader.read(11);
			const length = reader.read(4) + 2;
			if (length > outSize - outputPosition)
				throw new ArchiveError("Invalid LZH -lzs- data: match exceeds declared size");
			for (let index = 0; index < length; index++) emit(history[(position + index) & 2047]!);
		}
	}
	reader.assertZeroPadding();
	return output;
}

View on GitHub (pinned to 9690622007)

Solutions

  1. Use the correct (32-bit / extended) original-size field for the member
  2. Re-download/validate the archive (CRC)
  3. Catch ArchiveError and report the member as corrupt

Example fix

// before
const outSize = header.readUInt16LE(13); // 16-bit field truncates large sizes
// after
const outSize = u64(header, 16); // full size field incl. extended header
Defensive patterns

Strategy: try-catch

Validate before calling

if (outSize <= 0 || !Number.isSafeInteger(outSize)) throw new Error("invalid -lzs- output size");

Try / catch

try {
  const out = decompressLzs(packed, outSize);
} catch (err) {
  if (err instanceof ArchiveError) return null;
  throw err;
}

Prevention

When it happens

Trigger: decompressLzs (via the LZH -lzs- read path) on a stream with more literals than outSize allows — corrupt data, or outSize taken from a header smaller than the actual member size (wrong field/offset, damaged extended header).

Common situations: -lzs- members whose size field was edited; reading the wrong header size field (old 16-bit size vs 32-bit); fuzzed inputs.

Related errors


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