felixrieseberg/windows95 · error
hda block cache not reachable after restore
Error message
hda block cache not reachable after restore
What it means
recoverLegacyDisk boots a throwaway v86 emulator with autostart:false, restores a legacy state file, and then expects to reach the async HDA's internal block cache (the map of 256-byte blocks the guest wrote) via the deep path emulator.v86.cpu.devices.ide.primary.master.buffer.block_cache. That path is an implementation detail of v86 and is guarded only by optional chaining. If any link is missing — the emulator object shape changed, the IDE device was never attached, hda.async wasn't enabled, or restore_state didn't populate the buffer — the code throws this error because without the overlay cache it cannot reconstruct the written sectors of the old disk.
Source
Thrown at src/renderer/utils/recover-legacy-disk.ts:59
disable_speaker: true,
});
await new Promise<void>((resolve) =>
emulator.add_listener("emulator-loaded", resolve),
);
let files = 0;
const baseFd = fs.openSync(CONSTANTS.IMAGE_PATH, "r");
try {
const state = fs.readFileSync(legacyStatePath);
await emulator.restore_state(state.buffer);
const buf = emulator.v86?.cpu?.devices?.ide?.primary?.master?.buffer as {
block_cache: Map<number, Uint8Array>;
block_cache_is_write: Set<number>;
};
if (!buf?.block_cache) {
throw new Error("hda block cache not reachable after restore");
}
// v86 caches in 256-byte blocks; FAT works in 512-byte sectors.
const sec = Buffer.allocUnsafe(512);
const readSector = (lba: number) => {
const lo = buf.block_cache_is_write.has(lba * 2)
? buf.block_cache.get(lba * 2)
: undefined;
const hi = buf.block_cache_is_write.has(lba * 2 + 1)
? buf.block_cache.get(lba * 2 + 1)
: undefined;
if (lo && hi) return Buffer.concat([lo, hi]);
fs.readSync(baseFd, sec, 0, 512, lba * 512);
if (lo) sec.set(lo, 0);
if (hi) sec.set(hi, 256);
return Buffer.from(sec);
};
const isDirty = (lba: number) =>View on GitHub (pinned to 051065e5ae)
Solutions
- Confirm the V86 constructor config includes `hda: { async: true, size: <exact image size> }` — without async mode the buffer/block_cache structure doesn't exist.
- Check the installed v86 version: dump `emulator.v86.cpu.devices.ide` in a debug run and update the access path (e.g. `ide0` vs `ide.primary`, `buffer` wrapper changes) to match the bundled libv86.js.
- Re-verify the legacy state file is valid and matches the current image/geometry (STATE_VERSION); a bad restore can leave devices uninitialized — try restoring with a known-good state.
- Log which link in the chain is undefined (`emulator.v86?`, `.cpu?`, `.devices?`, `.ide?`, `.primary?`, `.master?`, `.buffer?`) to pinpoint whether it's a config problem vs an API change.
- Pin/downgrade v86 to the version the recovery code was written against, or vendor a patched fork with a stable accessor for the hda block cache.
Example fix
// before
const buf = emulator.v86?.cpu?.devices?.ide?.primary?.master?.buffer;
if (!buf?.block_cache) throw new Error("hda block cache not reachable after restore");
// after — fail with a diagnostic instead of a bare throw:
const ide = emulator.v86?.cpu?.devices?.ide ?? emulator.v86?.cpu?.devices?.ide0;
const buf = ide?.primary?.master?.buffer ?? ide?.master?.buffer;
if (!buf?.block_cache) {
console.error("ide device tree:", JSON.stringify(Object.keys(emulator.v86?.cpu?.devices ?? {})));
throw new Error("hda block cache not reachable — check hda.async:true and v86 version compatibility");
} Defensive patterns
Strategy: type-guard
Validate before calling
// After emulator-loaded and restore_state, before relying on the cache:
function resolveHdaBuffer(emulator: any): { block_cache: Map<number, Uint8Array>; block_cache_is_write: Set<number> } | null {
const ide = emulator?.v86?.cpu?.devices?.ide;
const buf = ide?.primary?.master?.buffer;
return buf && buf.block_cache instanceof Map && buf.block_cache_is_write instanceof Set ? buf : null;
}
// Also verify construction options:
// new V86({ hda: { url: IMAGE_PATH, async: true, size }, autostart: false, ... }) Type guard
function hasBlockCache(buf: unknown): buf is { block_cache: Map<number, Uint8Array>; block_cache_is_write: Set<number> } {
return (
typeof buf === "object" && buf !== null &&
"block_cache" in buf &&
(buf as any).block_cache instanceof Map &&
"block_cache_is_write" in buf &&
(buf as any).block_cache_is_write instanceof Set
);
} Try / catch
try {
const { dir, files } = await recoverLegacyDisk(legacyStatePath, outDir);
} catch (e) {
if (e instanceof Error && e.message.includes("hda block cache not reachable")) {
// v86 internals changed or hda.async missing — surface actionable guidance
throw new Error("Recovery failed: v86 HDA block cache unavailable. Ensure hda.async:true and that the bundled v86 version matches the recovery code's expected device tree.", { cause: e });
}
throw e;
} Prevention
- Always construct V86 with `hda: { async: true, size: <exact byte size> }` — the block cache only exists in async mode.
- Pin the v86 version (vendor v86.wasm/libv86.js) and re-run the recovery smoke test after every v86 upgrade.
- Add a startup assertion that walks the device-tree path and logs which segment is undefined, so regressions are diagnosable.
- Validate restore_state succeeded (listen for emulator-loaded / restore completion events) before touching internal buffers.
- Keep a known-good legacy state-vN.bin fixture and assert recoverLegacyDisk works on it in CI to catch v86 API drift early.
When it happens
Trigger: Calling recoverLegacyDisk with a v86 build whose internal device tree differs (e.g. ide0/ide1 naming, buffer wrapper class without block_cache); constructing V86 without hda.async:true; the state restore failing silently or completing before the async HDA buffer is attached; passing a state saved from a machine configured without a primary-master hda.
Common situations: Upgrading v86 (build/v86.wasm + libv86.js) where the internal ide device structure was refactored; a corrupt or mismatched legacy state-vN.bin that restore_state rejects or partially applies; forgetting the `async: true` hda option; running against a different hda image size so the IDE drive isn't registered as expected.
AI-assisted analysis of felixrieseberg/windows95@051065e5ae (2026-08-31).
Data as JSON: /api/errors/8e384c408563263b.
Report an issue: GitHub.