can1357/oh-my-pi · error · ArchiveError

Unsupported multi-volume CAB archive (previous/next cabinet

Error message

Unsupported multi-volume CAB archive (previous/next cabinet link)

What it means

CAB's CFHEADER flags bit 0 (prev cabinet) and bit 1 (next cabinet) indicate a multi-volume cabinet set linked via szCabinetPrev/szCabinetNext. The library handles only single-volume cabinets and throws ArchiveError when either bit is set.

Source

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

	if (!hasSignature(fixed)) throw new ArchiveError(`Invalid CAB archive: expected ${CAB_SIGNATURE} signature`);
	if (readUInt32LE(fixed, 4) !== 0 || readUInt32LE(fixed, 12) !== 0 || readUInt32LE(fixed, 20) !== 0) {
		throw new ArchiveError("Invalid CAB archive: reserved CFHEADER fields must be zero");
	}
	const cabinetSize = readUInt32LE(fixed, 8);
	if (cabinetSize < FIXED_HEADER_SIZE || cabinetSize > source.size) {
		throw new ArchiveError("Invalid CAB archive: declared cabinet size is out of bounds");
	}
	const fileTableOffset = readUInt32LE(fixed, 16);
	if (fileTableOffset < FIXED_HEADER_SIZE || fileTableOffset > cabinetSize) {
		throw new ArchiveError("Invalid CAB archive: CFFILE table offset is out of bounds");
	}
	if (fixed[24] !== 3 || fixed[25] !== 1) {
		throw new ArchiveError(`Unsupported CAB format version ${fixed[25]}.${fixed[24]} (expected 1.3)`);
	}
	const folderCount = readUInt16LE(fixed, 26);
	const fileCount = readUInt16LE(fixed, 28);
	const flags = readUInt16LE(fixed, 30);
	if (flags & 0x0003) throw new ArchiveError("Unsupported multi-volume CAB archive (previous/next cabinet link)");
	assertEntryCount(folderCount + fileCount, options.limits);
	if (folderCount === 0 && fileCount !== 0)
		throw new ArchiveError("Invalid CAB archive: files exist without a folder");

	let headerReserveSize = 0;
	let folderReserveSize = 0;
	let dataReserveSize = 0;
	let folderTableOffset = FIXED_HEADER_SIZE;
	if (flags & 0x0004) {
		const reserveHeader = await readExact(source, FIXED_HEADER_SIZE, FIXED_HEADER_SIZE + 4, cabinetSize);
		headerReserveSize = readUInt16LE(reserveHeader, 0);
		folderReserveSize = reserveHeader[2]!;
		dataReserveSize = reserveHeader[3]!;
		if (headerReserveSize > 60_000)
			throw new ArchiveError("Invalid CAB archive: CFHEADER reserve area exceeds 60000 bytes");
		folderTableOffset += 4 + headerReserveSize;
	}
	const folderRecordSize = 8 + folderReserveSize;

View on GitHub (pinned to 9690622007)

Solutions

  1. Locate the first volume of the set and merge/concatenate the volumes with a dedicated splitter tool before reading, if the producer supports it.
  2. Extract each volume's contents separately with cabextract and process them as independent archives.
  3. Repackage the data into a single self-contained cabinet (e.g. with makecab without spanning) if you control the source.
  4. If you only need entries from one volume, verify that volume's flags bit is clear before reading.

Example fix

// before
const entries = await readCab(await Bun.file("disk2.cab").arrayBuffer()); // flags set: part of spanned set
// after
import { $ } from "bun";
await $`cabextract -d out disk1.cab disk2.cab`.quiet(); // extract volumes with a spanning-aware tool
Defensive patterns

Strategy: validation

Validate before calling

const buf = new Uint8Array(await Bun.file(path).arrayBuffer());
const flags = buf[30] | (buf[31]! << 8);
if (flags & 0x0003) throw new Error("Multi-volume CAB set detected; extract volumes with cabextract first");

Try / catch

try {
	const entries = await readCab(source);
} catch (err) {
	if (err instanceof ArchiveError && err.message.includes("multi-volume CAB")) {
		// delegate to cabextract or process each volume separately
		return null;
	}
	throw err;
}

Prevention

When it happens

Trigger: Calling readCab() on a CAB where the flags word (offset 30) has 0x0001 or 0x0002 set — i.e. part of a split .cab set such as disk-spanned installers (data1.cab/data2.cab style sets produced by MakeCAB /i or DIAMOND).

Common situations: Multi-disk software distributions from the 90s/2000s, large InstallShield/MSI package sets split across several .cab files, and archives where a continuation cabinet references its neighbors.

Related errors


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