can1357/oh-my-pi · error · ArchiveError

Invalid ARJ header signature

Error message

Invalid ARJ header signature

What it means

Every ARJ file begins with the two-byte magic 0x60 0xEA followed by a 16-bit header size. parseArjBlock checks these first two bytes before reading anything else; a mismatch means the data is not an ARJ archive (or the reader's offset is wrong), so it throws immediately rather than misparsing random bytes.

Source

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

function readCString(bytes: Uint8Array, start: number, end: number, field: string): { value: string; next: number } {
	let terminator = start;
	while (terminator < end && bytes[terminator] !== 0) terminator++;
	if (terminator === end) throw new ArchiveError(`Invalid ARJ ${field}: missing terminator`);
	return { value: LEGACY_DECODER.decode(bytes.subarray(start, terminator)), next: terminator + 1 };
}

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;

View on GitHub (pinned to 9690622007)

Solutions

  1. Inspect the first bytes: run `xxd -l 16 file.arj` and confirm they are `60 ea`. If not, the file is not ARJ.
  2. Identify the real format (`file file.arj`) and use the matching reader in this library (zip/tar/lzh readers) instead of the ARJ one.
  3. Re-download or re-extract the archive; a corrupted first sector produces exactly this failure.
  4. If you drive parseArjBlock yourself, make sure the offset is a real block start (offsets come from previous parseArjBlock results, never manual arithmetic).

Example fix

// before: blindly parsing whatever the user supplied
const entries = readArj(userUpload);
// after: verify the magic before parsing
const head = new Uint8Array(userUpload.slice(0, 2));
if (head[0] !== 0x60 || head[1] !== 0xea) {
  throw new Error("Not an ARJ archive: bad magic (expected 60 EA)");
}
const entries = readArj(userUpload);
Defensive patterns

Strategy: validation

Validate before calling

function isArjMagic(bytes: Uint8Array): boolean {
  return bytes.byteLength >= 2 && bytes[0] === 0x60 && bytes[1] === 0xea;
}
if (!isArjMagic(data)) throw new Error("Not an ARJ archive: bad magic bytes");

Type guard

function isArjBuffer(data: unknown): data is Uint8Array {
  return data instanceof Uint8Array && data.byteLength >= 2 && data[0] === 0x60 && data[1] === 0xea;
}

Try / catch

try {
  return readArj(data);
} catch (err) {
  if (err instanceof ArchiveError && err.message === "Invalid ARJ header signature") {
    throw new Error("Input is not an ARJ archive (missing 60 EA signature)");
  }
  throw err;
}

Prevention

When it happens

Trigger: Calling readArj()/parseArjBlock() at an offset whose first two bytes are not 0x60 0xEA: a non-ARJ file passed to the reader, reading past the last local header block with a wrong end-of-archive detection, or an offset/desync bug in custom seeking code.

Common situations: Wrong file extension (e.g. a .zip or .exe renamed .arj), a text editor or download manager that altered/trimmed the file, pointing the reader at the wrong offset after hand-editing an index, or passing a Buffer that includes an HTTP header prepended to the archive.

Related errors


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