can1357/oh-my-pi · error · ArchiveError

Invalid ARJ basic header size

Error message

Invalid ARJ basic header size

What it means

After the signature, ARJ carries a 16-bit basic-header body size. The spec requires a minimum of 30 bytes (fixed header fields) and this reader caps it at ARJ_MAX_BASIC_HEADER (2600) to bound memory use. A size outside that window cannot be a valid ARJ header, so the parser rejects it before allocating or reading the body.

Source

Thrown at packages/utils/src/ar/arj.ts:69

}

interface ArjBlock {
	bodyStart: number;
	bodySize: number;
	nextOffset: number;
	metadataSize: number;
	isEnd: boolean;
}

function parseArjBlock(bytes: Uint8Array, offset: number, options: FormatReadOptions): ArjBlock {
	assertRange(bytes, offset, offset + 4, "header signature");
	if (bytes[offset] !== ARJ_SIGNATURE_0 || bytes[offset + 1] !== ARJ_SIGNATURE_1) {
		throw new ArchiveError("Invalid ARJ header signature");
	}
	const bodySize = u16(bytes, offset + 2);
	if (bodySize === 0)
		return { bodyStart: offset + 4, bodySize: 0, nextOffset: offset + 4, metadataSize: 4, isEnd: true };
	if (bodySize < 30 || bodySize > ARJ_MAX_BASIC_HEADER) throw new ArchiveError("Invalid ARJ basic header size");
	const bodyStart = offset + 4;
	const bodyEnd = bodyStart + bodySize;
	assertRange(bytes, bodyStart, bodyEnd + 4, "basic header");
	if (crc32(bytes.subarray(bodyStart, bodyEnd)) !== u32(bytes, bodyEnd)) {
		throw new ArchiveError("Invalid ARJ basic header CRC32");
	}
	let cursor = bodyEnd + 4;
	let extensionCount = 0;
	for (;;) {
		assertRange(bytes, cursor, cursor + 2, "extended header size");
		const extensionSize = u16(bytes, cursor);
		cursor += 2;
		if (extensionSize === 0) break;
		if (++extensionCount > 65_535) throw new ArchiveError("Invalid ARJ archive: too many extended headers");
		assertRange(bytes, cursor, cursor + extensionSize + 4, "extended header");
		if (crc32(bytes.subarray(cursor, cursor + extensionSize)) !== u32(bytes, cursor + extensionSize)) {
			throw new ArchiveError("Invalid ARJ extended header CRC32");
		}

View on GitHub (pinned to 9690622007)

Solutions

  1. Test the archive with `arj l` or 7-Zip; if those also fail, the file is corrupt — restore from backup or re-download.
  2. If only one member is bad, try extracting the archive with a tolerant external tool and re-pack it before feeding it to this library.
  3. Confirm your code does not pass hand-computed offsets into the reader; always chain from the offsets returned by prior parseArjBlock calls.
  4. If you are generating ARJ files with another tool, check that tool emits header sizes in 30..2600 and re-encode with a spec-compliant archiver.

Example fix

// before: parsing a stored offset without validating provenance
const block = parseArjBlock(bytes, myHandComputedOffset, options);
// after: derive offsets from the parser and pre-check the size field
const sig = bytes[myOffset] === 0x60 && bytes[myOffset + 1] === 0xea;
const size = bytes[myOffset + 2]! | (bytes[myOffset + 3]! << 8);
if (!sig || size < 30 || size > 2600) {
  throw new Error(`No valid ARJ header at offset ${myOffset} (size=${size})`);
}
const block = parseArjBlock(bytes, myOffset, options);
Defensive patterns

Strategy: validation

Validate before calling

// Confirm the u16 header size at a candidate block offset is within the spec window
function looksLikeArjHeader(bytes: Uint8Array, offset: number): boolean {
  if (offset + 4 > bytes.byteLength) return false;
  if (bytes[offset] !== 0x60 || bytes[offset + 1] !== 0xea) return false;
  const size = bytes[offset + 2]! | (bytes[offset + 3]! << 8);
  return size === 0 || (size >= 30 && size <= 2600);
}

Try / catch

try {
  return readArj(data, options);
} catch (err) {
  if (err instanceof ArchiveError && err.message.includes("basic header size")) {
    throw new Error("ARJ file corrupt or misaligned: header size field out of range");
  }
  throw err;
}

Prevention

When it happens

Trigger: parseArjBlock() reads a u16 at offset+2 that is < 30 or > 2600. Caused by corrupt size fields, parsing a non-header offset that happens to pass the signature check, or a hostile archive with an oversized size field (resource-exhaustion attempt).

Common situations: Bit-rot or truncation corrupting the little-endian size bytes (e.g. size read as 0x0005 after a byte flip), fuzzed/malicious archives testing the size cap, or desynchronized custom offset code landing two bytes off so an unrelated u16 is read as the size.

Related errors


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