can1357/oh-my-pi · error · ArchiveError

Invalid ARJ method-4 compressed data: truncated bitstream

Error message

Invalid ARJ method-4 compressed data: truncated bitstream

What it means

ARJ compression method 4 (the built-in variant this reader decompresses) uses an MSB-first bit reader over the packed stream. read(count) throws when the stream runs out of bits before `count` are available (or count is not an integer in 0..24), meaning the compressed data is truncated relative to what the decoder still needs.

Source

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

			throw new ArchiveError("Invalid ARJ extended header CRC32");
		}
		cursor += extensionSize + 4;
		assertIndexSize(cursor - offset, options.limits, "header metadata");
	}
	return { bodyStart, bodySize, nextOffset: cursor, metadataSize: cursor - offset, isEnd: false };
}

class ArjBitReader {
	readonly #bytes: Uint8Array;
	#position = 0;

	constructor(bytes: Uint8Array) {
		this.#bytes = bytes;
	}

	read(count: number): number {
		if (!Number.isInteger(count) || count < 0 || count > 24 || this.#position + count > this.#bytes.byteLength * 8) {
			throw new ArchiveError("Invalid ARJ method-4 compressed data: truncated bitstream");
		}
		let value = 0;
		for (let index = 0; index < count; index++) {
			const position = this.#position++;
			value = value * 2 + ((this.#bytes[position >>> 3]! >>> (7 - (position & 7))) & 1);
		}
		return value;
	}

	assertZeroPadding(): void {
		while (this.#position < this.#bytes.byteLength * 8) {
			if (this.read(1) !== 0) throw new ArchiveError("Invalid ARJ method-4 compressed data: non-zero trailing bits");
		}
	}
}

function decompressArjMethod4(packed: Uint8Array, outSize: number): Uint8Array {
	const reader = new ArjBitReader(packed);

View on GitHub (pinned to 9690622007)

Solutions

  1. Check the member's compressed size in the index against the actual available bytes; re-download the archive if shorter.
  2. Test the archive externally (`arj t` / 7-Zip) to confirm which member is corrupt.
  3. Re-extract that member with a reference ARJ tool; if it also fails, the packed data is unrecoverable — restore from backup.
  4. If you slice packed data out yourself, copy exactly the compressedSize bytes the header declares, not a rounded or padded amount.

Example fix

// before: slicing packed bytes with a wrong length
const packed = bytes.subarray(dataStart, dataStart + guessedSize);
// after: use the declared compressed size and bounds-check against the buffer
const packed = bytes.subarray(dataStart, dataStart + entry.compressedSize);
if (packed.byteLength !== entry.compressedSize) {
  throw new Error(`Member ${entry.name}: truncated packed data`);
}
Defensive patterns

Strategy: validation

Validate before calling

// Ensure the member's full packed payload is present before decompressing
function packedDataComplete(bytes: Uint8Array, dataOffset: number, compressedSize: number): boolean {
  return dataOffset >= 0 && compressedSize >= 0 && dataOffset + compressedSize <= bytes.byteLength;
}

Try / catch

try {
  return decompressArjMethod4(packed, originalSize);
} catch (err) {
  if (err instanceof ArchiveError && err.message.includes("truncated bitstream")) {
    throw new Error(`Member data truncated: expected ${packed.byteLength}+ bits of packed data`);
  }
  throw err;
}

Prevention

When it happens

Trigger: decompressArjMethod4() decodes more symbols than the packed stream can supply: packed data was truncated, outSize does not match the actual compressed stream, or the packed bytes were altered so the Huffman/LZ state machine consumes bits off the end.

Common situations: A partially downloaded or truncated member (compressed size in the header larger than the bytes actually present), a corrupted stream mid-decode, or a hand-spliced extraction where the packed payload was copied with the wrong length.

Related errors


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