`Run-time error '76': Path not found` on finishing a game. Error 76 is VB6's "path not found", and the path it could not find was the game's own. Confining the prefix removed `z:`, which left `C:` as the only drive — and the executable was in the plugin checkout, which `C:` does not cover. So wine gave the game `\\?\unix\home\pastilhas\...\Minesweeper X.exe` as its own location (confirmed with `winepath`), VB6 file I/O cannot write to a path of that shape, and the first thing that needs to is the auto-saved recording. Which happens at the END of a game, so the failure arrived after somebody had played. The comment in prefix.ts already claimed "the game keeps its own directory, its settings and its replays". It did not. It does now: `installGame` copies the 68 KB executable into `C:\Minesweeper` on every start and the game runs from there, with cwd set to match so relative writes stop landing in C:\windows. Copied rather than symlinked — a symlink out of drive_c is a door out of the prefix, which is the exact thing confinePrefix exists to close. The cost is that replays now live inside prefix/, so deleting it to unwedge wine takes them along; that is in the README rather than left to be discovered. Verified end to end rather than by inspection: set Auto-Save Recording to All, played a beginner game to a loss, and `Beg 1.16 - Aug 23 2026 22.09.18 PM (Lost).mvr` appeared in C:\Minesweeper. Before this, that same moment was the error box. The default is "High scores", which is why an ordinary losing game never reproduced it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Officer-Session: de3ed4e6-f88a-429c-94e6-a760336ba83b
55 lines
2.7 KiB
TypeScript
55 lines
2.7 KiB
TypeScript
import { join } from 'node:path';
|
|
import { mkdirSync } from 'node:fs';
|
|
import { DATA_PATH } from '@@/data-path';
|
|
|
|
/**
|
|
* Where the session's state lives, and why none of it is in the plugin's own directory.
|
|
*
|
|
* A plugin directory is a git checkout that `plugins:update` will `git pull` over and `plugins:delete` will
|
|
* remove. The wine prefix is ~600 MB of generated state that takes minutes to rebuild — putting it there
|
|
* would mean an update fights the working tree and a reinstall costs a fresh download of the VB6 runtime.
|
|
*
|
|
* So it goes under `DATA_PATH/minesweeper/`, the same convention music's cache uses. The game executable
|
|
* DOES ship in the repository: it is 68 KB, it is the thing being run rather than state produced by running
|
|
* it, and its checksum is in the README.
|
|
*/
|
|
export const STATE_DIR = join(DATA_PATH, 'minesweeper');
|
|
|
|
/** The wine prefix — a `win32` one, because the game is an i386 PE. Generated; never committed. */
|
|
export const WINEPREFIX = join(STATE_DIR, 'prefix');
|
|
|
|
/**
|
|
* Runtime scratch: pid files, logs, and the VNC socket.
|
|
*
|
|
* Created mode 0700 and that is load-bearing rather than tidy — it is the ONLY thing standing between
|
|
* another local account and an unauthenticated VNC display. x11vnc runs with `-nopw`, which is correct
|
|
* precisely because this directory refuses everyone but the owner. See `vnc.ts`.
|
|
*/
|
|
export const RUN_DIR = join(STATE_DIR, 'run');
|
|
|
|
/** x11vnc's listening socket. A path, not a port: unreachable without traversing RUN_DIR. */
|
|
export const VNC_SOCKET = join(RUN_DIR, 'vnc.sock');
|
|
|
|
/** Ships with the plugin. `import.meta.dir` is `<plugin>/sidecar`. This is the SOURCE, not what runs. */
|
|
export const GAME_EXE = join(import.meta.dir, '..', 'game', 'Minesweeper X.exe');
|
|
|
|
/**
|
|
* Where the game actually runs from, and where its settings and replays land.
|
|
*
|
|
* Inside the prefix, because once `confinePrefix` removes `z:` there is no drive letter for anywhere else
|
|
* on the machine. Launched from the plugin directory, the game's own path came back as
|
|
* `\\?\unix\home\…`, VB6 could not write beside its executable, and finishing a game raised
|
|
* `Run-time error '76': Path not found` as it tried to save the replay.
|
|
*
|
|
* The cost is that this is inside `prefix/`, so deleting the prefix — still the right fix for a wedged
|
|
* wine — now takes the replays with it. The alternative was a symlink out of `drive_c`, which is a door
|
|
* out of the prefix and exactly what the confinement exists to close.
|
|
*/
|
|
export const GAME_DIR = join(WINEPREFIX, 'drive_c', 'Minesweeper');
|
|
export const GAME_IN_PREFIX = join(GAME_DIR, 'Minesweeper X.exe');
|
|
|
|
export function ensureDirs(): void {
|
|
mkdirSync(STATE_DIR, { recursive: true, mode: 0o700 });
|
|
mkdirSync(RUN_DIR, { recursive: true, mode: 0o700 });
|
|
}
|