can1357/oh-my-pi · error · ArchiveError
Invalid LZH level-2 extended header chain
Error message
Invalid LZH level-2 extended header chain
What it means
After walking the level-2 extended-header chain, the cursor must land exactly on the header end: extensions tile the space contiguously, terminated by a zero size word. A cursor left short of headerEnd means the chain is malformed (overlapping/incorrect sizes that left unexplained trailing bytes).
Source
Thrown at packages/utils/src/ar/lzh.ts:556
let extensionCount = 0;
while (cursor + 2 <= headerEnd) {
const extensionSize = u16(bytes, cursor);
if (extensionSize === 0) {
cursor += 2;
break;
}
if (extensionSize < 3 || cursor + extensionSize > headerEnd)
throw new ArchiveError("Invalid LZH extended header size");
if (++extensionCount > 65_535) throw new ArchiveError("Invalid LZH archive: too many extended headers");
const type = bytes[cursor + 2]!;
const data = bytes.subarray(cursor + 3, cursor + extensionSize);
if (type === 0x01 || type === 0x02 || type === 0x44 || type === 0x45) {
assertArchivePathBytes(data.byteLength, "member path", options.limits.maxPathBytes);
}
processExtendedHeader(type, data, cursor + 3, fields);
cursor += extensionSize;
}
if (cursor !== headerEnd) throw new ArchiveError("Invalid LZH level-2 extended header chain");
dataStart = headerEnd;
packedSize = fields.packedSize ?? packedSize;
}
if (level === 1 && osId === 0x20 && method === "-lh7-") {
throw new ArchiveError("LZH uses the incompatible LHARK -lh7- variant");
}
size = fields.size ?? size;
mtimeMs = fields.mtimeMs ?? mtimeMs;
const mode = fields.mode;
if (fields.commonCrc !== undefined) {
const relative = fields.commonCrcOffset! - offset;
if (crc16WithZeroRange(bytes.subarray(offset, dataStart), relative, relative + 2) !== fields.commonCrc) {
throw new ArchiveError("Invalid LZH common header CRC-16");
}
}
const filename = fields.unicodeFilename ?? fields.filename ?? legacyFilename ?? "";
const directory = fields.unicodeDirectory ?? fields.directory ?? "";View on GitHub (pinned to 9690622007)
Solutions
- Re-extract or re-download the archive and validate against a known checksum.
- Repack the archive with a conformant tool (lha/lhasa) so the extended-header chain is regenerated correctly.
- If the file came from a legacy source, try opening it with a reference tool first to confirm whether it is valid LZH at all.
- If processing untrusted input, catch ArchiveError and reject the file.
Defensive patterns
Strategy: validation
Validate before calling
const bytes = await readAllBytes(source);
if (!sniffLzh(bytes)) throw new Error("not LZH");
if ((await sha256File(path)) !== expectedSha256) throw new Error("archive integrity check failed"); Try / catch
try {
const entries = await readLzh(source, options);
} catch (err) {
if (err instanceof ArchiveError && err.message === "Invalid LZH level-2 extended header chain") {
throw new Error("LZH header chain malformed — repack with a standard LHA tool");
}
throw err;
} Prevention
- Create .lzh archives only with conformant tools (lha/lhasa)
- Round-trip legacy archives through a modern tool once, then keep the repacked copy
- Verify checksums before parsing
When it happens
Trigger: readLzh() on a level-2 archive where extended headers do not exactly fill the header region — e.g. extension sizes that skip bytes or a missing zero terminator within headerEnd.
Common situations: Archives written by buggy or non-conformant LZH implementations; partially corrupted files; hand-edited binaries.
Related errors
- Invalid ARJ ${field}: missing terminator
- Invalid ARJ basic header size
- Invalid LZH common header CRC-16
- Invalid LZH archive: header did not advance
- Invalid ar archive member size
AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31).
Data as JSON: /api/errors/c902508211220c1f.
Report an issue: GitHub.