can1357/oh-my-pi · error · ArchiveError

Unsupported LZH header level ${level}

Error message

Unsupported LZH header level ${level}

What it means

Thrown when parsing an LZH header whose level byte (offset+20) is greater than 2. This library supports LHA header levels 0, 1, and 2 only; level 3 headers use a different structure entirely. The library refuses to guess at unknown formats rather than misparse bytes.

Source

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

	}
}

interface ParsedLzhHeader {
	method: string;
	packedSize: number;
	size: number;
	dataStart: number;
	nextOffset: number;
	crc: number;
	path?: string;
	mtimeMs?: number;
	mode?: number;
}

function parseLzhHeader(bytes: Uint8Array, offset: number, options: FormatReadOptions): ParsedLzhHeader {
	assertRange(bytes, offset, offset + 22, "header");
	const level = bytes[offset + 20]!;
	if (level > 2) throw new ArchiveError(`Unsupported LZH header level ${level}`);
	const method = String.fromCharCode(...bytes.subarray(offset + 2, offset + 7));
	if (!LHA_METHOD_PATTERN.test(method)) throw new ArchiveError(`Invalid LZH compression method '${method}'`);
	let packedSize = u32(bytes, offset + 7);
	let size = u32(bytes, offset + 11);
	let crc = 0;
	let legacyFilename: string | undefined;
	let mtimeMs: number | undefined;
	let osId = 0;
	let dataStart: number;
	const fields: LzhExtendedFields = {};

	if (level < 2) {
		const headerLength = bytes[offset]!;
		const minimum = level === 0 ? 22 : 25;
		if (headerLength < minimum) throw new ArchiveError(`Invalid LZH level-${level} header size`);
		const baseEnd = offset + headerLength + 2;
		assertRange(bytes, offset, baseEnd, "header");
		let sum = 0;

View on GitHub (pinned to 9690622007)

Solutions

  1. Convert the archive to a level-0/1/2 LHA file using lha or 7-Zip before reading
  2. Use a different extraction tool/library that supports LZH level-3 headers
  3. Verify the file is actually an LZH archive (check the -lh?- magic at offset 2)

Example fix

// before: feeding the raw file to the LZH reader
const archive = lzhRead(suspiciousBytes);
// after: probe the method signature first
const method = String.fromCharCode(...bytes.subarray(2, 7));
if (!/^-(lz|lh)[0-9a-z]-$/i.test(method)) throw new Error('not an LZH file');
const archive = lzhRead(bytes);
Defensive patterns

Strategy: fallback

Validate before calling

if (!looksLikeLzh(bytes)) routeToOtherReader(bytes);

Type guard

function looksLikeLzh(b: Uint8Array): boolean { return /^-(lh|lz)[0-9a-z]-$/.test(String.fromCharCode(...b.subarray(2,7))); }

Try / catch

try { return lzhRead(bytes); } catch (err) { if (err instanceof ArchiveError) return fallbackExtractor(bytes); throw err; }

Prevention

When it happens

Trigger: Reading an LHA file that uses level-3 headers (rare, produced by some UnLHA variants), or pointing the reader at a non-LZH file whose bytes coincidentally pass initial framing (e.g. magic check) but have a garbage level byte.

Common situations: Processing obscure archives generated by Japanese LHA tools using level-3 format, or accidentally passing a corrupted/mislabeled file (e.g. a ZIP renamed .lzh) to the LZH reader.

Related errors


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