can1357/oh-my-pi · error · ArchiveError

Invalid CAB archive: LZX frame size ${outputSize} exceeds 32

Error message

Invalid CAB archive: LZX frame size ${outputSize} exceeds 32768 bytes

What it means

CAB LZX frames decode at most FRAME_SIZE (32768) bytes each; decompressFrame validates outputSize is a non-negative integer within that bound before decoding. A larger request means the caller split frames incorrectly or the folder metadata is corrupt.

Source

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

	constructor(windowBits: number) {
		if (!Number.isInteger(windowBits) || windowBits < 15 || windowBits > 21) {
			throw new ArchiveError(`Unsupported CAB LZX window size: ${windowBits} bits (expected 15-21)`);
		}
		this.#window = new Uint8Array(2 ** windowBits);
		const slots = POSITION_SLOTS[windowBits - 15]!;
		this.#mainLengths = new Uint8Array(256 + slots * 8);
		this.#extraBits = new Uint8Array(slots);
		this.#positionBase = new Uint32Array(slots);
		for (let slot = 0; slot < slots; slot++) {
			this.#extraBits[slot] = slot < 4 ? 0 : Math.min(17, Math.floor(slot / 2) - 1);
			if (slot > 0) this.#positionBase[slot] = this.#positionBase[slot - 1]! + 2 ** this.#extraBits[slot - 1]!;
		}
	}

	/** Decode one CAB CFDATA LZX frame while preserving the folder's dictionary and Huffman state. */
	decompressFrame(bytes: Uint8Array, outputSize: number): Uint8Array {
		if (!Number.isInteger(outputSize) || outputSize < 0 || outputSize > FRAME_SIZE) {
			throw new ArchiveError(`Invalid CAB archive: LZX frame size ${outputSize} exceeds 32768 bytes`);
		}
		if (outputSize === 0) return new Uint8Array(0);
		const reader = new LzxBitReader(bytes);
		if (!this.#headerRead) {
			if (reader.readBits(1) !== 0) {
				const high = reader.readBits(16);
				const low = reader.readBits(16);
				this.#intelFileSize = signedUInt32((high * 0x10000 + low) >>> 0);
			}
			this.#headerRead = true;
		}

		const raw = new Uint8Array(outputSize);
		let outputPosition = 0;
		while (outputPosition < outputSize) {
			if (this.#blockRemaining === 0) this.#readBlockHeader(reader);
			const run = Math.min(this.#blockRemaining, outputSize - outputPosition);
			const produced = this.#decodeRun(reader, raw, outputPosition, run);

View on GitHub (pinned to 9690622007)

Solutions

  1. Call decompressFrame once per CFDATA block with that block's own uncompressed size (max 32768).
  2. Split larger outputs into successive 32 KiB frames, reusing the same LzxDecoder to preserve dictionary state.
  3. Validate outputSize with Number.isInteger(size) && size >= 0 && size <= 32768 before calling.
  4. Sanity-check the folder's block sizes against the CAB header; nonsensical sizes indicate corruption.

Example fix

// before
decoder.decompressFrame(block.data, folder.totalUncompressedSize)
// after
decoder.decompressFrame(block.data, block.uncompressedSize) // one CFDATA block per call, ≤ 32768
Defensive patterns

Strategy: validation

Validate before calling

function assertValidFrameSize(outputSize: number): void {
  if (!Number.isInteger(outputSize) || outputSize < 0 || outputSize > 32768) {
    throw new Error(`Frame output size must be an integer in [0, 32768], got ${outputSize}`)
  }
}
assertValidFrameSize(block.uncompressedSize)

Type guard

function isValidFrameSize(v: unknown): v is number {
  return typeof v === 'number' && Number.isInteger(v) && v >= 0 && v <= 32768
}

Try / catch

try {
  return decoder.decompressFrame(data, outputSize)
} catch (err) {
  if (err instanceof ArchiveError && err.message.includes('frame size')) {
    throw new Error('Frame split error: call decompressFrame once per CFDATA block (≤32768 bytes)')
  }
  throw err
}

Prevention

When it happens

Trigger: decoder.decompressFrame(bytes, 40000), passing a negative or non-integer outputSize, or summing multiple CFDATA blocks' uncompressed sizes into one call instead of one call per block.

Common situations: Misreading CFDATA cbUncompressed (which can exceed 32768 only across folder boundaries), feeding an entire folder's data to a single frame call, integer parse errors from archive metadata.

Understand the failure class

Background: "must be positive", "Invalid value": how libraries reject invalid parameter values (ValueError, ArgumentError, INVALID_PARAMETER_VALUE) — this error's family across 28 libraries.

Related errors


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