can1357/oh-my-pi · error · ArchiveError

Unsupported multi-volume CAB archive: split CFDATA block

Error message

Unsupported multi-volume CAB archive: split CFDATA block

What it means

A CFDATA block with an uncompressed-size field of 0 signals a split block — the entry's data continues in the next volume of a multi-volume CAB set. This library supports only single-volume archives, so split blocks are rejected with a clear 'Unsupported multi-volume' error rather than silently returning partial file contents. Note the trigger is the uncompressed field being exactly 0, not the compressed field.

Source

Thrown at packages/utils/src/ar/cab.ts:150

		if (description.method > 3) {
			throw new ArchiveError(`Unsupported CAB compression method: ${description.method}`);
		}
		if (description.method === 3 && (description.parameter < 15 || description.parameter > 21)) {
			throw new ArchiveError(`Unsupported CAB LZX window size: ${description.parameter} bits (expected 15-21)`);
		}

		const compressedSize = description.dataEnd - description.dataStart;
		assertInMemorySize(compressedSize, this.#limits);
		const bytes = await readExact(this.#source, description.dataStart, description.dataEnd);
		let position = 0;
		let outputSize = 0;
		for (let block = 0; block < description.blockCount; block++) {
			if (position + DATA_BLOCK_SIZE + this.#dataReserveSize > bytes.byteLength) {
				throw new ArchiveError("Invalid CAB archive: truncated CFDATA header");
			}
			const compressed = readUInt16LE(bytes, position + 4);
			const uncompressed = readUInt16LE(bytes, position + 6);
			if (uncompressed === 0) throw new ArchiveError("Unsupported multi-volume CAB archive: split CFDATA block");
			if (uncompressed > MAX_DATA_OUTPUT) {
				throw new ArchiveError(`Invalid CAB archive: CFDATA expands to ${uncompressed} bytes (maximum 32768)`);
			}
			const payloadStart = position + DATA_BLOCK_SIZE + this.#dataReserveSize;
			const payloadEnd = payloadStart + compressed;
			if (payloadEnd > bytes.byteLength) throw new ArchiveError("Invalid CAB archive: truncated CFDATA payload");
			const expectedChecksum = readUInt32LE(bytes, position);
			if (expectedChecksum !== 0) {
				const payloadChecksum = cabChecksum(bytes.subarray(payloadStart, payloadEnd));
				const actualChecksum = cabChecksum(bytes.subarray(position + 4, payloadStart), payloadChecksum);
				if (actualChecksum !== expectedChecksum) {
					throw new ArchiveError(`Invalid CAB archive: CFDATA block ${block} checksum mismatch`);
				}
			}
			outputSize += uncompressed;
			assertInMemorySize(outputSize, this.#limits);
			position = payloadEnd;
		}

View on GitHub (pinned to 9690622007)

Solutions

  1. Obtain and concatenate all volumes of the CAB set, then merge them into a single archive before reading (e.g. with `mscab` tooling or by copying files out with cabextract per volume and repacking).
  2. Extract the readable files from the available volume with `cabextract` and repack them into one single-volume CAB.
  3. If the data genuinely fits, recreate the archive with a size under the volume limit so no split blocks are emitted.
  4. Check your acquisition pipeline to ensure every span of the set is downloaded/kept.
  5. If only part of the set will ever be available, switch to a format (zip) that does not require whole-set availability.

Example fix

// before: reading only the first span
await readCabArchive(disk1); // Unsupported multi-volume CAB archive
// after: gather all spans and merge externally
cabextract disk1.cab disk2.cab -d out/
lcab out/ merged.cab
await readCabArchive(await Bun.file('merged.cab').bytes());
Defensive patterns

Strategy: try-catch

Validate before calling

// Detect a spanned set up front: volume headers/labels or a sibling-file check
const spans = await findSiblingSpans(cabPath); // e.g. base.1.cab, base.2.cab
if (spans.total > 1) throw new Error('Multi-volume CAB set detected — merge all spans before reading');

Try / catch

try {
  return await readCabArchive(volume);
} catch (err) {
  if (err instanceof ArchiveError && err.message.includes('multi-volume')) {
    throw new Error('This CAB is one disk of a spanned set — supply all volumes and merge first');
  }
  throw err;
}

Prevention

When it happens

Trigger: readAll() on a CAB that is one disk/span of a multi-volume set, where the final CFDATA block of a folder is marked split (cbUncomp == 0).

Common situations: CABs split across floppy images or spanned installer media; only one part of a .cab/.1.cab/.2.cab set was copied; MakeCAB /SET output spanning multiple disks; download managers that fetched only the first span.

Related errors


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