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
- Test the archive with `arj l` or 7-Zip; if those also fail, the file is corrupt — restore from backup or re-download.
- 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.
- Confirm your code does not pass hand-computed offsets into the reader; always chain from the offsets returned by prior parseArjBlock calls.
- 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
- Chain block offsets only from parseArjBlock return values — never compute them by hand.
- Test downloaded archives (sha256 + `arj t`) before automated parsing.
- Keep options.limits enabled so hostile size fields fail fast.
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
- Invalid ARJ ${field}: missing terminator
- Invalid LZH level-2 extended header chain
- Invalid ar archive member size
- Invalid ar archive member header
- Empty ar archive long name at offset ${offset}
AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31).
Data as JSON: /api/errors/3473aa691c908920.
Report an issue: GitHub.