felixrieseberg/windows95 · error

unexpected sector size

Error message

unexpected sector size

What it means

extractFat32 is a minimal read-only FAT32 walker that hard-codes a 512-byte sector size in every offset calculation (FAT entry offsets use `off & 511`, cluster buffers use `secPerClus * 512`, and the sector reader contract is 512 bytes per LBA). Before parsing any directory or FAT structure it validates the BPB's bytes-per-sector field (UInt16LE at offset 11 of the partition's boot sector). If the volume was not formatted with 512-byte sectors, the walker's arithmetic would silently read garbage, so it throws this error defensively.

Source

Thrown at src/renderer/utils/fat32-extract.ts:29

 * the output isn't 200 MB of possibly-mismatched OS binaries.
 */
export async function extractFat32(
  readSector: (lba: number) => Buffer,
  isDirty: (lba: number) => boolean,
  outDir: string,
): Promise<number> {
  // First partition from the MBR.
  const mbr = readSector(0);
  const partLba = mbr.readUInt32LE(0x1be + 8);

  const bpb = readSector(partLba);
  const bytesPerSec = bpb.readUInt16LE(11);
  const secPerClus = bpb.readUInt8(13);
  const rsvd = bpb.readUInt16LE(14);
  const nFats = bpb.readUInt8(16);
  const secPerFat = bpb.readUInt32LE(36);
  const rootClus = bpb.readUInt32LE(44);
  if (bytesPerSec !== 512) throw new Error("unexpected sector size");

  const fatLba = partLba + rsvd;
  const dataLba = partLba + rsvd + nFats * secPerFat;
  const clusLba = (c: number) => dataLba + (c - 2) * secPerClus;

  const fatSecCache = new Map<number, Buffer>();
  const nextCluster = (c: number) => {
    const off = c * 4;
    const sec = fatLba + (off >> 9);
    let b = fatSecCache.get(sec);
    if (!b) fatSecCache.set(sec, (b = readSector(sec)));
    return b.readUInt32LE(off & 511) & 0x0fffffff;
  };

  const chain = (c: number) => {
    const out: number[] = [];
    while (c >= 2 && c < 0x0ffffff8 && out.length < 1 << 20) {
      out.push(c);

View on GitHub (pinned to 051065e5ae)

Solutions

  1. Verify the target partition is a standard 512-byte-sector FAT32 volume (run `fdisk -l` or inspect BPB offset 11; value must be 512).
  2. If the image genuinely uses larger sectors, reformat or convert the image to 512-byte sectors (e.g. with a tool like `qemu-img convert` plus reformat) before extraction.
  3. Check the MBR partition entry: confirm partLba (offset 0x1be+8) points at the FAT32 VBR, not some other partition or raw data.
  4. If you control the image pipeline, format with `mkfs.vfat -S 512` or default settings to guarantee 512-byte sectors.
  5. If you must support non-512 sectors, generalize the walker: replace all hardcoded 512/511 constants with the bytesPerSec value read from the BPB.

Example fix

// before (fat32-extract.ts)
if (bytesPerSec !== 512) throw new Error("unexpected sector size");

// after — caller validates the image before extraction:
const bpb = readSector(partLba);
const bytesPerSec = bpb.readUInt16LE(11);
if (bytesPerSec !== 512) {
  throw new Error(`image uses ${bytesPerSec}-byte sectors; reformat with 512-byte sectors before recovery`);
}
Defensive patterns

Strategy: validation

Validate before calling

import * as fs from "fs";

export function assertFat32SectorSize(imagePath: string): void {
  const fd = fs.openSync(imagePath, "r");
  try {
    const mbr = Buffer.alloc(512);
    fs.readSync(fd, mbr, 0, 512, 0);
    const partLba = mbr.readUInt32LE(0x1be + 8);
    if (partLba === 0) throw new Error("no MBR partition entry");
    const bpb = Buffer.alloc(512);
    fs.readSync(fd, bpb, 0, 512, partLba * 512);
    const bytesPerSec = bpb.readUInt16LE(11);
    if (bytesPerSec !== 512) {
      throw new Error(`partition uses ${bytesPerSec}-byte sectors; only 512 is supported`);
    }
  } finally {
    fs.closeSync(fd);
  }
}
// call before recoverLegacyDisk():
// assertFat32SectorSize(CONSTANTS.IMAGE_PATH);

Type guard

function isFat32BootSector(b: Buffer): boolean {
  const bytesPerSec = b.readUInt16LE(11);
  const secPerClus = b.readUInt8(13);
  return (
    bytesPerSec === 512 &&
    secPerClus > 0 &&
    (secPerClus & (secPerClus - 1)) === 0 &&
    b.readUInt8(510) === 0x55 && b.readUInt8(511) === 0xaa
  );
}

Try / catch

try {
  const { dir, files } = await recoverLegacyDisk(statePath, outDir);
} catch (e) {
  if (e instanceof Error && e.message === "unexpected sector size") {
    // image is not a 512-byte-sector FAT32 volume; abort recovery with guidance
    throw new Error("Disk image must be a 512-byte-sector FAT32 volume; reformat or convert the image first.", { cause: e });
  }
  throw e;
}

Prevention

When it happens

Trigger: Calling extractFat32 (directly or via recoverLegacyDisk) against a disk image whose first MBR partition's BPB declares bytesPerSec != 512 — e.g. a 1024- or 2048-byte-sector FAT32 volume, or the partition LBA resolved to a non-FAT region so offset 11 contains unrelated bytes.

Common situations: Extracting from disk images created for media/industrial devices that use 1K/2K sector emulation; pointing the extractor at an exFAT or non-FAT partition where the BPB layout doesn't apply; an MBR entry pointing to the wrong partition start (partLba) so a non-boot-sector buffer is parsed; re-purposing the walker on an image formatted with large-sector geometry.


AI-assisted analysis of felixrieseberg/windows95@051065e5ae (2026-08-31). Data as JSON: /api/errors/0f1ff59224370e4c. Report an issue: GitHub.