{"record":{"id":"8e384c408563263b","repo":"felixrieseberg/windows95","slug":"hda-block-cache-not-reachable-after-restore","errorCode":null,"errorMessage":"hda block cache not reachable after restore","messagePattern":"hda block cache not reachable after restore","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/renderer/utils/recover-legacy-disk.ts","lineNumber":59,"sourceCode":"    disable_speaker: true,\n  });\n\n  await new Promise<void>((resolve) =>\n    emulator.add_listener(\"emulator-loaded\", resolve),\n  );\n\n  let files = 0;\n  const baseFd = fs.openSync(CONSTANTS.IMAGE_PATH, \"r\");\n  try {\n    const state = fs.readFileSync(legacyStatePath);\n    await emulator.restore_state(state.buffer);\n\n    const buf = emulator.v86?.cpu?.devices?.ide?.primary?.master?.buffer as {\n      block_cache: Map<number, Uint8Array>;\n      block_cache_is_write: Set<number>;\n    };\n    if (!buf?.block_cache) {\n      throw new Error(\"hda block cache not reachable after restore\");\n    }\n\n    // v86 caches in 256-byte blocks; FAT works in 512-byte sectors.\n    const sec = Buffer.allocUnsafe(512);\n    const readSector = (lba: number) => {\n      const lo = buf.block_cache_is_write.has(lba * 2)\n        ? buf.block_cache.get(lba * 2)\n        : undefined;\n      const hi = buf.block_cache_is_write.has(lba * 2 + 1)\n        ? buf.block_cache.get(lba * 2 + 1)\n        : undefined;\n      if (lo && hi) return Buffer.concat([lo, hi]);\n      fs.readSync(baseFd, sec, 0, 512, lba * 512);\n      if (lo) sec.set(lo, 0);\n      if (hi) sec.set(hi, 256);\n      return Buffer.from(sec);\n    };\n    const isDirty = (lba: number) =>","sourceCodeStart":41,"sourceCodeEnd":77,"githubUrl":"https://github.com/felixrieseberg/windows95/blob/051065e5ae815203a0a9038e01eefdf3c10519f1/src/renderer/utils/recover-legacy-disk.ts#L41-L77","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before\nconst buf = emulator.v86?.cpu?.devices?.ide?.primary?.master?.buffer;\nif (!buf?.block_cache) throw new Error(\"hda block cache not reachable after restore\");\n\n// after — fail with a diagnostic instead of a bare throw:\nconst ide = emulator.v86?.cpu?.devices?.ide ?? emulator.v86?.cpu?.devices?.ide0;\nconst buf = ide?.primary?.master?.buffer ?? ide?.master?.buffer;\nif (!buf?.block_cache) {\n  console.error(\"ide device tree:\", JSON.stringify(Object.keys(emulator.v86?.cpu?.devices ?? {})));\n  throw new Error(\"hda block cache not reachable — check hda.async:true and v86 version compatibility\");\n}","handlingStrategy":"type-guard","validationCode":"// After emulator-loaded and restore_state, before relying on the cache:\nfunction resolveHdaBuffer(emulator: any): { block_cache: Map<number, Uint8Array>; block_cache_is_write: Set<number> } | null {\n  const ide = emulator?.v86?.cpu?.devices?.ide;\n  const buf = ide?.primary?.master?.buffer;\n  return buf && buf.block_cache instanceof Map && buf.block_cache_is_write instanceof Set ? buf : null;\n}\n// Also verify construction options:\n// new V86({ hda: { url: IMAGE_PATH, async: true, size }, autostart: false, ... })","typeGuard":"function hasBlockCache(buf: unknown): buf is { block_cache: Map<number, Uint8Array>; block_cache_is_write: Set<number> } {\n  return (\n    typeof buf === \"object\" && buf !== null &&\n    \"block_cache\" in buf &&\n    (buf as any).block_cache instanceof Map &&\n    \"block_cache_is_write\" in buf &&\n    (buf as any).block_cache_is_write instanceof Set\n  );\n}","tryCatchPattern":"try {\n  const { dir, files } = await recoverLegacyDisk(legacyStatePath, outDir);\n} catch (e) {\n  if (e instanceof Error && e.message.includes(\"hda block cache not reachable\")) {\n    // v86 internals changed or hda.async missing — surface actionable guidance\n    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 });\n  }\n  throw e;\n}","preventionTips":["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."],"tags":["v86","emulator","restore","internal-api","hda"],"backgroundTag":"v86-internal-api-not-reachable","analyzedSha":"051065e5ae815203a0a9038e01eefdf3c10519f1","analyzedAt":"2026-08-31T18:49:04.616Z","schemaVersion":2},"datasetVersion":"2026-08-31T19:17:28.585Z"}