can1357/oh-my-pi · error · ArchiveError

Invalid ARJ ${field}: missing terminator

Error message

Invalid ARJ ${field}: missing terminator

What it means

ARJ stores header fields (e.g. archive name, file name, comment) as NUL-terminated windows-1252 strings. readCString scans from `start` for a 0 byte before the field's `end` boundary; if none exists the field is malformed, so the reader refuses to guess where the string ends and throws instead of returning garbage or an unterminated value.

Source

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

	}
}

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);
}

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)

View on GitHub (pinned to 9690622007)

Solutions

  1. Re-obtain the archive from a trusted source and verify it with an independent tool (e.g. `arj l archive.arj` or 7-Zip) to confirm corruption.
  2. Check the file is complete: compare its size against the expected size or re-download it.
  3. Verify the file really is ARJ (first bytes 0x60 0xEA) and not another format or a text/PE file misnamed .arj.
  4. If the file is user-supplied input, treat the ArchiveError as normal control flow: reject the input and report which field failed — do not attempt partial parsing.

Example fix

// before: assuming the read succeeds on any input
const { value } = readCString(bytes, start, end, "filename");
// after: validate the terminator up front and fail fast with context
for (let i = start; i < end; i++) {
  if (bytes[i] === 0) break;
  if (i === end - 1) throw new Error(`ARJ header truncated: filename field has no NUL before offset ${end}`);
}
const { value } = readCString(bytes, start, end, "filename");
Defensive patterns

Strategy: try-catch

Validate before calling

// Check the field's byte range contains a NUL before parsing the header
function hasCStringTerminator(bytes: Uint8Array, start: number, end: number): boolean {
  for (let i = start; i < end; i++) if (bytes[i] === 0) return true;
  return false;
}

Try / catch

import { ArchiveError } from "@oh-my-pi/pi-utils/ar/error";
try {
  return readArj(data, options);
} catch (err) {
  if (err instanceof ArchiveError && err.message.includes("missing terminator")) {
    throw new Error(`ARJ header string field is corrupt (no NUL terminator): ${err.message}`);
  }
  throw err;
}

Prevention

When it happens

Trigger: Parsing an ARJ basic header whose name/comment field runs all the way to its declared end without a 0x00 terminator — i.e. the byte range [start, end) contains no NUL. Reached via readArj() -> parseArjBlock() -> header field decoding (filename, archive name).

Common situations: A truncated or corrupted ARJ file (partial download, bad disk copy), a file that is not actually ARJ but happens to start with 0x60 0xEA, or a hand-crafted/fuzzed archive where a header size was edited without inserting the NUL terminator.

Related errors


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