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
- Use the correct (32-bit / extended) original-size field for the member
- Re-download/validate the archive (CRC)
- 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
- Use the 32-bit/extended size field for -lzs- members, not the legacy 16-bit one
- Verify archive checksums
- Treat member size fields as untrusted
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
- Invalid ${label} compressed data: match exceeds declared siz
- Invalid LZH -lzs- data: match exceeds declared size
- Invalid tar octal value: ${value}
- Truncated embedded addon archive entry: ${filename}
- Invalid ARJ method-4 compressed data: match exceeds declared
AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31).
Data as JSON: /api/errors/a09a512bf2f4e2d7.
Report an issue: GitHub.