{"record":{"id":"5eef232e878a39f5","repo":"can1357/oh-my-pi","slug":"unsupported-multi-volume-cab-archive-split-cfdata","errorCode":null,"errorMessage":"Unsupported multi-volume CAB archive: split CFDATA block","messagePattern":"Unsupported multi-volume CAB archive: split CFDATA block","errorType":"exception","errorClass":"ArchiveError","httpStatus":null,"severity":"error","filePath":"packages/utils/src/ar/cab.ts","lineNumber":150,"sourceCode":"\t\tif (description.method > 3) {\n\t\t\tthrow new ArchiveError(`Unsupported CAB compression method: ${description.method}`);\n\t\t}\n\t\tif (description.method === 3 && (description.parameter < 15 || description.parameter > 21)) {\n\t\t\tthrow new ArchiveError(`Unsupported CAB LZX window size: ${description.parameter} bits (expected 15-21)`);\n\t\t}\n\n\t\tconst compressedSize = description.dataEnd - description.dataStart;\n\t\tassertInMemorySize(compressedSize, this.#limits);\n\t\tconst bytes = await readExact(this.#source, description.dataStart, description.dataEnd);\n\t\tlet position = 0;\n\t\tlet outputSize = 0;\n\t\tfor (let block = 0; block < description.blockCount; block++) {\n\t\t\tif (position + DATA_BLOCK_SIZE + this.#dataReserveSize > bytes.byteLength) {\n\t\t\t\tthrow new ArchiveError(\"Invalid CAB archive: truncated CFDATA header\");\n\t\t\t}\n\t\t\tconst compressed = readUInt16LE(bytes, position + 4);\n\t\t\tconst uncompressed = readUInt16LE(bytes, position + 6);\n\t\t\tif (uncompressed === 0) throw new ArchiveError(\"Unsupported multi-volume CAB archive: split CFDATA block\");\n\t\t\tif (uncompressed > MAX_DATA_OUTPUT) {\n\t\t\t\tthrow new ArchiveError(`Invalid CAB archive: CFDATA expands to ${uncompressed} bytes (maximum 32768)`);\n\t\t\t}\n\t\t\tconst payloadStart = position + DATA_BLOCK_SIZE + this.#dataReserveSize;\n\t\t\tconst payloadEnd = payloadStart + compressed;\n\t\t\tif (payloadEnd > bytes.byteLength) throw new ArchiveError(\"Invalid CAB archive: truncated CFDATA payload\");\n\t\t\tconst expectedChecksum = readUInt32LE(bytes, position);\n\t\t\tif (expectedChecksum !== 0) {\n\t\t\t\tconst payloadChecksum = cabChecksum(bytes.subarray(payloadStart, payloadEnd));\n\t\t\t\tconst actualChecksum = cabChecksum(bytes.subarray(position + 4, payloadStart), payloadChecksum);\n\t\t\t\tif (actualChecksum !== expectedChecksum) {\n\t\t\t\t\tthrow new ArchiveError(`Invalid CAB archive: CFDATA block ${block} checksum mismatch`);\n\t\t\t\t}\n\t\t\t}\n\t\t\toutputSize += uncompressed;\n\t\t\tassertInMemorySize(outputSize, this.#limits);\n\t\t\tposition = payloadEnd;\n\t\t}","sourceCodeStart":132,"sourceCodeEnd":168,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/utils/src/ar/cab.ts#L132-L168","documentation":"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.","triggerScenarios":"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).","commonSituations":"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.","solutions":["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).","Extract the readable files from the available volume with `cabextract` and repack them into one single-volume CAB.","If the data genuinely fits, recreate the archive with a size under the volume limit so no split blocks are emitted.","Check your acquisition pipeline to ensure every span of the set is downloaded/kept.","If only part of the set will ever be available, switch to a format (zip) that does not require whole-set availability."],"exampleFix":"// before: reading only the first span\nawait readCabArchive(disk1); // Unsupported multi-volume CAB archive\n// after: gather all spans and merge externally\ncabextract disk1.cab disk2.cab -d out/\nlcab out/ merged.cab\nawait readCabArchive(await Bun.file('merged.cab').bytes());","handlingStrategy":"try-catch","validationCode":"// Detect a spanned set up front: volume headers/labels or a sibling-file check\nconst spans = await findSiblingSpans(cabPath); // e.g. base.1.cab, base.2.cab\nif (spans.total > 1) throw new Error('Multi-volume CAB set detected — merge all spans before reading');","typeGuard":null,"tryCatchPattern":"try {\n  return await readCabArchive(volume);\n} catch (err) {\n  if (err instanceof ArchiveError && err.message.includes('multi-volume')) {\n    throw new Error('This CAB is one disk of a spanned set — supply all volumes and merge first');\n  }\n  throw err;\n}","preventionTips":["Keep every span of a CAB set together when archiving/distributing.","Prefer single-volume CABs (or zip) for programmatic pipelines.","Detect span naming patterns (.1.cab/.2.cab, disk labels) at ingestion.","Extract and merge with cabextract before in-process reading when spans are unavoidable."],"tags":["archive","cab","multi-volume","unsupported"],"backgroundTag":"unsupported-multivolume-archive","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}