run the game from inside the prefix, so it can save a replay
`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
This commit is contained in:
+12
-5
@@ -1,7 +1,7 @@
|
||||
import type { Subprocess } from 'bun';
|
||||
import { existsSync, rmSync } from 'node:fs';
|
||||
import { GAME_EXE, RUN_DIR, VNC_SOCKET, ensureDirs } from './paths';
|
||||
import { WINE_ENV, confineAndVerify, ensurePrefix, prefixReady } from './prefix';
|
||||
import { GAME_DIR, GAME_EXE, GAME_IN_PREFIX, RUN_DIR, VNC_SOCKET, ensureDirs } from './paths';
|
||||
import { WINE_ENV, confineAndVerify, ensurePrefix, installGame, prefixReady } from './prefix';
|
||||
import { run, waitFor } from './run';
|
||||
|
||||
/**
|
||||
@@ -212,8 +212,8 @@ export async function status(): Promise<SessionStatus> {
|
||||
};
|
||||
}
|
||||
|
||||
const spawn = (cmd: string[], env: Record<string, string>, logName: string): Proc => {
|
||||
const proc = Bun.spawn(cmd, { env: { ...process.env, ...env }, stdin: 'ignore', stdout: 'pipe', stderr: 'pipe' });
|
||||
const spawn = (cmd: string[], env: Record<string, string>, logName: string, cwd?: string): Proc => {
|
||||
const proc = Bun.spawn(cmd, { env: { ...process.env, ...env }, cwd, stdin: 'ignore', stdout: 'pipe', stderr: 'pipe' });
|
||||
// Wine and x11vnc both say useful things only when something is wrong. Prefixed so `pm2 logs
|
||||
// officer-minesweeper` shows which process is complaining.
|
||||
for (const stream of [proc.stdout, proc.stderr]) {
|
||||
@@ -270,8 +270,15 @@ async function startInner(log: (line: string) => void): Promise<SessionStatus> {
|
||||
|
||||
// ── the game ──
|
||||
if (!alive(game)) {
|
||||
// Into the prefix first. Run from the plugin directory the game has no DOS path of its own, and the
|
||||
// first thing that needs one is saving a replay — which happens at the END of a game, so the failure
|
||||
// arrives after somebody has played rather than when they start.
|
||||
await installGame();
|
||||
|
||||
log('launching Minesweeper X under wine');
|
||||
game = spawn(['wine', GAME_EXE], env, 'wine');
|
||||
// cwd as well as the exe path: without it wine reports "could not open working directory" and starts
|
||||
// the process in C:\windows, so anything the game writes relatively lands there instead.
|
||||
game = spawn(['wine', GAME_IN_PREFIX], env, 'wine', GAME_DIR);
|
||||
|
||||
const drew = await waitFor(async () => !!(await windowGeometry()), 30_000);
|
||||
if (!drew) {
|
||||
|
||||
Reference in New Issue
Block a user