can1357/oh-my-pi · error · ArchiveError

Invalid CAB archive: invalid LZX repeated offset

Error message

Invalid CAB archive: invalid LZX repeated offset

What it means

During CAB LZX decompression, the decoder reads the three repeated-offset registers (r0/r1/r2) from the block header. The LZX spec requires all three to be initialized to 1 (nonzero); a zero value means the stream is corrupt or not real LZX data, so ArchiveError is thrown from #readBlockHeader.

Source

Thrown at packages/utils/src/ar/codecs/lzx.ts:255

				for (let index = 0; index < alignedLengths.byteLength; index++) alignedLengths[index] = reader.readBits(3);
				this.#alignedTable = new LzxHuffmanTable(alignedLengths);
			}
			readCodeLengths(reader, this.#mainLengths, 0, 256);
			readCodeLengths(reader, this.#mainLengths, 256, this.#mainLengths.byteLength);
			this.#mainTable = new LzxHuffmanTable(this.#mainLengths);
			if (this.#mainLengths[0xe8] !== 0) this.#intelStarted = true;
			readCodeLengths(reader, this.#lengthLengths, 0, this.#lengthLengths.byteLength);
			this.#lengthTable = new LzxHuffmanTable(this.#lengthLengths, true);
			return;
		}
		if (this.#blockType === 3) {
			this.#intelStarted = true;
			reader.alignWord();
			this.#r0 = reader.readUInt32LE();
			this.#r1 = reader.readUInt32LE();
			this.#r2 = reader.readUInt32LE();
			if (this.#r0 === 0 || this.#r1 === 0 || this.#r2 === 0) {
				throw new ArchiveError("Invalid CAB archive: invalid LZX repeated offset");
			}
			this.#uncompressedPadding = (this.#blockLength & 1) !== 0;
			return;
		}
		throw new ArchiveError(`Invalid CAB archive: unsupported LZX block type ${this.#blockType}`);
	}

	#decodeRun(reader: LzxBitReader, output: Uint8Array, outputStart: number, count: number): number {
		if (this.#blockType === 3) {
			for (let index = 0; index < count; index++) this.#writeByte(reader.readByte(), output, outputStart + index);
			return count;
		}
		if (!this.#mainTable || !this.#lengthTable)
			throw new ArchiveError("Invalid CAB archive: missing LZX decode trees");

		let produced = 0;
		while (produced < count) {
			const element = this.#mainTable.decode(reader);

View on GitHub (pinned to 9690622007)

Solutions

  1. Verify the CAB file is intact (re-download or re-extract the archive).
  2. Confirm the folder's compression method is actually LZX and you are passing the correct data slice to decompressFrame.
  3. Treat the file as corrupt: surface ArchiveError to the caller instead of retrying.
Defensive patterns

Strategy: try-catch

Validate before calling

// heuristic pre-check: CAB folder payload should start with plausible LZX bitstream bytes
function looksLikeLzxPayload(bytes, offset) {
  return Number.isInteger(offset) && offset >= 0 && offset + 12 <= bytes.byteLength;
}

Type guard

function hasLzxHeaderRoom(bytes, offset) {
  return offset + 12 <= bytes.byteLength;
}

Try / catch

try {
  const out = folder.decompressFrame(frame);
} catch (err) {
  if (err instanceof ArchiveError && err.message.includes("repeated offset")) {
    // report archive as corrupt
  } else throw err;
}

Prevention

When it happens

Trigger: decompressFrame -> #readBlockHeader parses an LZX block whose header yields r0, r1, or r2 equal to 0 after reading three UInt32LE values.

Common situations: Corrupted or truncated .cab files, wrong offsets into the CAB data (parsing a folder that is not actually LZX-compressed), or hand-crafted/fuzzed archive bytes.

Related errors


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