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.

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.

Appendix: 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)