can1357/oh-my-pi · error · ArchiveError
Invalid LZH Unicode path header: odd UTF-16 length
Error message
Invalid LZH Unicode path header: odd UTF-16 length
What it means
LZH level-1/2 headers can carry Unicode (UTF-16LE) filename/directory extended headers (types 0x44/0x45). This library requires those payloads to contain a whole number of UTF-16 code units; an odd byte count means the archive is malformed, so decodeUtf16 throws before decoding.
Source
Thrown at packages/utils/src/ar/lzh.ts:315
if (
!Number.isSafeInteger(start) ||
!Number.isSafeInteger(end) ||
start < 0 ||
end < start ||
end > bytes.byteLength
) {
throw new ArchiveError(`Invalid LZH archive: truncated ${what}`);
}
}
function decodeLegacy(bytes: Uint8Array): string {
let end = bytes.indexOf(0);
if (end < 0) end = bytes.byteLength;
return LEGACY_DECODER.decode(bytes.subarray(0, end));
}
function decodeUtf16(bytes: Uint8Array): string {
if ((bytes.byteLength & 1) !== 0) throw new ArchiveError("Invalid LZH Unicode path header: odd UTF-16 length");
let end = bytes.byteLength;
while (end >= 2 && bytes[end - 1] === 0 && bytes[end - 2] === 0) end -= 2;
return UTF16LE_DECODER.decode(bytes.subarray(0, end));
}
function dosTimeToMs(value: number): number | undefined {
if (value === 0) return undefined;
const year = 1980 + ((value >>> 25) & 0x7f);
const month = (value >>> 21) & 0x0f;
const day = (value >>> 16) & 0x1f;
const hour = (value >>> 11) & 0x1f;
const minute = (value >>> 5) & 0x3f;
const second = (value & 0x1f) * 2;
if (month < 1 || month > 12 || day < 1 || day > 31 || hour > 23 || minute > 59 || second > 59) return undefined;
return Date.UTC(year, month - 1, day, hour, minute, second);
}
interface LzhExtendedFields {View on GitHub (pinned to 9690622007)
Solutions
- Verify the archive with its original source (re-download or re-extract) — odd-length UTF-16 payloads indicate corruption, not a recoverable path
- Try extracting the same archive with a tolerant tool (e.g. 7-Zip or LHA) to confirm which member is damaged; if only one member is bad, skip/repair it upstream
- If you generate archives, fix the writer so the 0x44/0x45 extended-header size includes the 2-byte size trailer and yields an even data length
- As a workaround, strip the Unicode extended headers from the archive (convert paths to the legacy 0x01/0x02 headers)
Example fix
null
Defensive patterns
Strategy: try-catch
Validate before calling
// UTF-16 extended-header data must have even byte length
function isValidUtf16HeaderData(data: Uint8Array): boolean {
return (data.byteLength & 1) === 0;
} Type guard
null
Try / catch
import { ArchiveError } from "@oh-my-pi/pi-utils/ar/error";
try {
const entries = await readArchive(lzhBytes);
} catch (err) {
if (err instanceof ArchiveError && err.message.includes("odd UTF-16 length")) {
// archive has corrupt Unicode path headers — treat as damaged, request a fresh copy
}
throw err;
} Prevention
- Verify archive checksums at ingest time to catch corruption before parsing
- Reject or quarantine archives that fail an external-tool validation pass (e.g. `lha l`) first
- Avoid hand-editing LZH header bytes; always repack via a standard tool
When it happens
Trigger: Reading an LZH archive whose member header contains a 0x44 (Unicode filename) or 0x45 (Unicode directory) extended header whose data section has an odd number of bytes — e.g. the header size field was corrupted, truncated, or written by a tool that computed the extension length incorrectly.
Common situations: Corrupted or partially downloaded .lzh/.lha files; archives produced by non-conformant or buggy Japanese archivers (e.g. old UNLHA32-compatible tools) that miscount extended-header sizes; hand-patched headers.
Related errors
- Invalid LZH common extended header
- Invalid LZH Unix permissions extended header
- Invalid LZH Unix timestamp extended header
- Failed to encode ASAR archive: ${describeError(error)}
- Invalid CAB archive: file name is not valid UTF-8
AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31).
Data as JSON: /api/errors/f23bead15f9f58e0.
Report an issue: GitHub.