can1357/oh-my-pi · error · ArchiveError

Invalid CAB archive: truncated LZX data

Error message

Invalid CAB archive: truncated LZX data

What it means

readByte() found the input exhausted while reading a raw byte (uncompressed block data, padding byte, or 32-bit repeated-offset values). Unlike the bitstream truncation error, this fires on byte-aligned reads: the buffer passed to decompressFrame() is shorter than the block headers and raw data inside it require.

Source

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

			}
			const take = Math.min(needed, this.#remaining);
			value = value * 2 ** take + ((this.#word >>> (this.#remaining - take)) & (2 ** take - 1));
			this.#remaining -= take;
			needed -= take;
		}
		return value;
	}

	alignWord(): void {
		this.#remaining = 0;
	}

	readByte(): number {
		if (this.#remaining !== 0) {
			throw new ArchiveError("Invalid CAB archive: misaligned LZX byte stream");
		}
		if (this.#offset >= this.#bytes.byteLength) {
			throw new ArchiveError("Invalid CAB archive: truncated LZX data");
		}
		return this.#bytes[this.#offset++]!;
	}

	readUInt32LE(): number {
		const b0 = this.readByte();
		const b1 = this.readByte();
		const b2 = this.readByte();
		const b3 = this.readByte();
		return (b0 | (b1 << 8) | (b2 << 16) | (b3 << 24)) >>> 0;
	}
}

class LzxHuffmanTable {
	readonly #counts = new Uint32Array(17);
	readonly #firstCodes = new Uint32Array(17);
	readonly #firstSymbols = new Uint32Array(17);
	readonly #symbols: Uint16Array;

View on GitHub (pinned to 9690622007)

Solutions

  1. Pass exactly cbCompressed bytes of the CFDATA block; verify cbCompressed is large enough for uncompressed blocks (padding + blockLength bytes).
  2. Re-download/re-extract the CAB and validate with cabextract or 7z to rule out simple truncation.
  3. Audit the caller's block iteration: each folder's cfdata blocks must be fed contiguously with correct sizes to one LzxDecoder instance.
  4. If parsing CAB yourself, check for reserved-area/CFDATA offset handling errors that make you slice the wrong region.

Example fix

// before
const block = data.subarray(offset, offset + 4096); // arbitrary chunk
const out = decoder.decompressFrame(block, 32768);
// after
const size = cfdatum.cbCompressed;
if (offset + size > data.byteLength) throw new Error("cab truncated");
const out = decoder.decompressFrame(data.subarray(offset, offset + size), cfdatum.cbUncompressed);
Defensive patterns

Strategy: validation

Validate before calling

const end = offset + cfdatum.cbCompressed;
if (end > data.byteLength) throw new Error("CAB data truncated");
// uncompressed LZX blocks need padding + blockLength raw bytes inside cbCompressed
if (cfdatum.cbCompressed < cfdatum.cbUncompressed) throw new Error("cbCompressed < cbUncompressed on LZX folder — parser bug");

Type guard

function canReadBytes(data: Uint8Array, offset: number, count: number): boolean {
  return count >= 0 && offset >= 0 && offset + count <= data.byteLength;
}

Try / catch

try {
  return decoder.decompressFrame(frameBytes, outputSize);
} catch (e) {
  if (e instanceof Error && e.message.includes("truncated LZX data")) {
    throw new Error("Ran out of CFDATA bytes mid-block — CAB truncated or block sizes misparsed");
  }
  throw e;
}

Prevention

When it happens

Trigger: Calling decompressFrame with a CFDATA block containing a type-3 (uncompressed) block whose stored bytes extend past the buffer end: cbCompressed smaller than the actual block content, subarray cut mid-block, or a corrupt blockLength (up to 65535) that overruns the frame data.

Common situations: Truncated .cab downloads; CAB parsers that mis-handle uncompressed LZX blocks (they store 12 padding bits plus raw bytes, changing the length accounting); passing the wrong block's size fields to the decoder.

Related errors


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