@henols/c64-re-tools 2.0.0-rc.5 → 2.0.0-rc.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@henols/c64-re-tools",
3
- "version": "2.0.0-rc.5",
3
+ "version": "2.0.0-rc.6",
4
4
  "description": "Agent-friendly Commodore 64 engineering toolkit for reverse engineering, debugging, development and testing.",
5
5
  "license": "MIT",
6
6
  "author": "Henrik Olsson",
package/src/host/main.ts CHANGED
@@ -6,7 +6,7 @@ import { DEFAULT_HOST_PORT } from "../protocol/messages.ts";
6
6
  import { ProcessSupervisor } from "../native/processes.ts";
7
7
  import { startTrace, trace } from "../trace.ts";
8
8
  import { ListenError, parsePort, startHostServer, stopSignal } from "./server.ts";
9
- import { viceFlaws } from "../native/discover.ts";
9
+ import { viceFlaws, type ViceVersion } from "../native/discover.ts";
10
10
  import { createToolDispatcher } from "./tools/index.ts";
11
11
  import { checkViceStarts, findVice } from "./vice/process.ts";
12
12
  import { viceSessionFactory } from "./vice/session.ts";
@@ -70,15 +70,16 @@ async function main(): Promise<number> {
70
70
  supervisor.startWatchdog("host");
71
71
 
72
72
  // Finding x64sc is not enough: VICE without its ROM files exits at once.
73
- let flaws: string[];
73
+ let vice: ViceVersion;
74
74
  try {
75
- flaws = viceFlaws(await checkViceStarts({ supervisor }));
75
+ vice = await checkViceStarts({ supervisor });
76
76
  } catch (error) {
77
77
  process.stderr.write(`${(error as Error).message}\n`);
78
78
  await supervisor.stopAll();
79
79
  return 1;
80
80
  }
81
- log("VICE starts");
81
+ log(`VICE ${vice.major}.${vice.minor}${vice.revision === undefined ? "" : ` r${vice.revision}`} starts`);
82
+ const flaws = viceFlaws(vice);
82
83
  // A known VICE flaw is named, not worked around (D56).
83
84
  for (const flaw of flaws) log(`Known VICE flaw: ${flaw}`);
84
85
 
@@ -89,7 +90,7 @@ async function main(): Promise<number> {
89
90
  port,
90
91
  log,
91
92
  createViceSession: viceSessionFactory({ supervisor, log, flaws }),
92
- tools: createToolDispatcher({ supervisor, log }),
93
+ tools: createToolDispatcher({ supervisor, log, vice }),
93
94
  });
94
95
  } catch (error) {
95
96
  if (error instanceof ListenError && error.code === "EADDRINUSE") {
@@ -20,7 +20,7 @@ export interface ViceSessionHandle {
20
20
  }
21
21
 
22
22
  /** Starts a VICE session. Throws WireFailure with an actionable code when it cannot. */
23
- export type ViceSessionFactory = (options: { videoStandard: VideoStandard }) => Promise<ViceSessionHandle>;
23
+ export type ViceSessionFactory = (options: { videoStandard: VideoStandard; log?: (line: string) => void }) => Promise<ViceSessionHandle>;
24
24
 
25
25
  /**
26
26
  * Runs one native-tool request. `signal` aborts when the client goes away; the
@@ -95,9 +95,12 @@ class Connection {
95
95
  constructor(socket: Socket, options: HostServerOptions, log: (line: string) => void) {
96
96
  this.#socket = socket;
97
97
  this.#options = options;
98
- this.#log = log;
98
+ // Every line names its connection, so the console shows which client starts and stops what.
99
+ this.#log = (line) => log(`connection ${this.#id}: ${line}`);
99
100
  this.closed = new Promise((resolve) => (this.#resolveClosed = resolve));
100
- trace().event("connection.open", { connection: this.#id, remote: `${socket.remoteAddress}:${socket.remotePort}`, local: socket.localAddress });
101
+ const remote = `${socket.remoteAddress}:${socket.remotePort}`;
102
+ this.#log(`opened from ${remote}`);
103
+ trace().event("connection.open", { connection: this.#id, remote, local: socket.localAddress });
101
104
  this.#handshakeTimer = setTimeout(
102
105
  () => this.#abort("no hello before the handshake timeout"),
103
106
  options.handshakeTimeoutMs ?? DEFAULT_HANDSHAKE_TIMEOUT_MS,
@@ -180,11 +183,13 @@ class Connection {
180
183
  this.#isTool = true;
181
184
  this.#state = "open";
182
185
  this.#send({ type: "ready" });
186
+ this.#log("ready for tool requests");
183
187
  trace().event("connection.ready", { connection: this.#id, role: "tool" });
184
188
  this.#restartHeartbeat();
185
189
  return;
186
190
  }
187
191
  this.#state = "starting";
192
+ this.#log(`starts a VICE session (${message.videoStandard ?? "pal"})`);
188
193
  this.#contain(this.#startSession(message.videoStandard ?? "pal"));
189
194
  return;
190
195
  }
@@ -206,6 +211,7 @@ class Connection {
206
211
  this.#session = session;
207
212
  this.#state = "open";
208
213
  this.#send({ type: "ready" });
214
+ this.#log(`VICE session ready after ${Math.round(performance.now() - started)} ms`);
209
215
  trace().event("connection.ready", { connection: this.#id, role: "vice-session", videoStandard, ms: elapsedMs(started) });
210
216
  this.#restartHeartbeat();
211
217
  }
@@ -213,7 +219,7 @@ class Connection {
213
219
  /** The new session, or undefined after the failure is sent to the client. Never rejects, also when the factory throws at once. */
214
220
  async #createSession(videoStandard: VideoStandard): Promise<ViceSessionHandle | undefined> {
215
221
  try {
216
- return await this.#options.createViceSession({ videoStandard });
222
+ return await this.#options.createViceSession({ videoStandard, log: this.#log });
217
223
  } catch (error) {
218
224
  this.#log(`VICE session failed to start: ${error instanceof Error ? error.message : String(error)}`);
219
225
  trace().event("connection.session-failed", { connection: this.#id, error });
@@ -275,11 +281,13 @@ class Connection {
275
281
  this.#toolAbort.abort();
276
282
  const session = this.#session ?? (await this.#starting);
277
283
  this.#session = undefined;
284
+ if (session !== undefined) this.#log("the client is gone; stopping its VICE session");
278
285
  try {
279
286
  await session?.close();
280
287
  } catch (error) {
281
288
  this.#log(`session close failed: ${error instanceof Error ? error.message : String(error)}`);
282
289
  }
290
+ this.#log("closed");
283
291
  this.#resolveClosed();
284
292
  }
285
293
  }
@@ -3,6 +3,7 @@
3
3
  // ACME, DXA and Ghidra themselves. One entry per operation, never a
4
4
  // generic executable route.
5
5
 
6
+ import type { ViceVersion } from "../../native/discover.ts";
6
7
  import type { ProcessSupervisor } from "../../native/processes.ts";
7
8
  import type { ToolContext } from "../../native/run.ts";
8
9
  import type { ToolOperation, ToolOperations } from "../../protocol/tools.ts";
@@ -24,8 +25,11 @@ const ADAPTERS: { [O in ToolOperation]: Adapter<O> } = {
24
25
  "petcat.decode": (_params, attachments, context) => decode(attachments[0]!, context),
25
26
  };
26
27
 
27
- export function createToolDispatcher(options: { supervisor: ProcessSupervisor; env?: NodeJS.ProcessEnv; log?: (line: string) => void }): ToolDispatcher {
28
+ /** `vice`: the version that the host read from the emulator at start, for host.status. */
29
+ export function createToolDispatcher(options: { supervisor: ProcessSupervisor; env?: NodeJS.ProcessEnv; log?: (line: string) => void; vice?: ViceVersion }): ToolDispatcher {
28
30
  return (op, params, attachments, signal) => {
29
- return ADAPTERS[op](params, attachments, { supervisor: options.supervisor, signal, env: options.env, log: options.log });
31
+ const context = { supervisor: options.supervisor, signal, env: options.env, log: options.log };
32
+ if (op === "host.status") return hostStatus(context, options.vice) as ReturnType<ToolDispatcher>;
33
+ return ADAPTERS[op](params, attachments, context);
30
34
  };
31
35
  }
@@ -1,9 +1,12 @@
1
1
  // host.status: VICE and the tools that come with it, each found the way its
2
2
  // operations find it and run once with a version option. A tool from a
3
3
  // VICE older than MIN_VICE is reported as not working, as its operations refuse it.
4
+ // VICE itself is reported with the version that the host read from the
5
+ // running emulator at start: the Homebrew x64sc of VICE 3.10 on macOS stops
6
+ // on -version ("argv[0] is NULL") although the emulator runs.
4
7
 
5
8
  import type { ToolStatus } from "../../protocol/tools.ts";
6
- import { C1541, findTool, isOlderThanMinimum, MIN_VICE, PETCAT, VICE, viceFlaws, viceVersionOf, type ToolSpec } from "../../native/discover.ts";
9
+ import { C1541, findTool, isOlderThanMinimum, MIN_VICE, PETCAT, VICE, viceFlaws, viceVersionOf, type ToolSpec, type ViceVersion } from "../../native/discover.ts";
7
10
  import type { ToolContext } from "../../native/run.ts";
8
11
  import { probeTools } from "../../native/status.ts";
9
12
  import { findVice } from "../vice/process.ts";
@@ -16,16 +19,21 @@ function requireMinimum(status: ToolStatus, tool: ToolSpec): ToolStatus {
16
19
  return { ...status, runs: false, problem: `${found}, and c64-re-tools needs VICE ${MIN_VICE.major}.${MIN_VICE.minor} or later. ${tool.remedy}` };
17
20
  }
18
21
 
19
- export async function hostStatus(context: ToolContext): Promise<{ result: { tools: ToolStatus[] } }> {
22
+ /** The version text of a VICE version, as x64sc -version prints it. */
23
+ const versionText = (version: ViceVersion) => `x64sc (VICE ${version.major}.${version.minor}${version.revision === undefined ? "" : ` SVN r${version.revision}`})`;
24
+
25
+ /** `started`: the version that the host read from the emulator at start; without it, VICE is probed with -version too. */
26
+ export async function hostStatus(context: ToolContext, started?: ViceVersion): Promise<{ result: { tools: ToolStatus[] } }> {
20
27
  const specs = [VICE, C1541, PETCAT];
21
- const tools = await probeTools(
28
+ const probed = await probeTools(
22
29
  [
23
- { name: VICE.name, find: findVice, args: ["-version"], pattern: /^x64sc \(VICE \d/ },
30
+ ...(started === undefined ? [{ name: VICE.name, find: findVice, args: ["-version"], pattern: /^x64sc \(VICE \d/ }] : []),
24
31
  { name: C1541.name, find: (env) => findTool(C1541, env), args: ["-version"], pattern: /^c1541 \(VICE \d/ },
25
32
  { name: PETCAT.name, find: (env) => findTool(PETCAT, env), args: ["-version"], pattern: /^petcat \(VICE \d/ },
26
33
  ],
27
34
  context,
28
35
  );
36
+ const tools = started === undefined ? probed : [{ name: VICE.name, found: true, path: findVice(context.env ?? process.env), version: versionText(started), runs: true }, ...probed];
29
37
  const checked = tools.map((status, index) => requireMinimum(status, specs[index]!));
30
38
  // VICE runs with its known flaws; status names them (D56).
31
39
  const vice = checked[0]!;
@@ -307,6 +307,7 @@ async function launchOnce(options: LaunchOptions): Promise<ViceProcess> {
307
307
  clearInterval(slow);
308
308
  }
309
309
  if (failure !== undefined) throw failure;
310
+ log(`VICE stopped (pid ${child.pid}, in ${Date.now() - started} ms)`);
310
311
  })();
311
312
  return stopping;
312
313
  };
@@ -325,7 +326,7 @@ async function launchOnce(options: LaunchOptions): Promise<ViceProcess> {
325
326
  if (error instanceof WireFailure) throw error;
326
327
  throw new WireFailure("machine-unavailable", "The emulator could not be started on the host.");
327
328
  }
328
- log(`VICE started (pid ${child.pid}, monitor ports ${port} and ${textPort})`);
329
+ log(`VICE started (${mode}, pid ${child.pid}, monitor ports ${port} and ${textPort})`);
329
330
  trace().event("vice.ready", { pid: child.pid, ms: elapsedMs(launched) });
330
331
  return { pid: child.pid, monitor, text, exited: child.exited, outputTail, stop };
331
332
  }
@@ -988,6 +988,7 @@ export class ViceSession implements ViceSessionHandle {
988
988
  if (this.#held !== undefined) throw new WireFailure("machine-unavailable", this.#heldWhy);
989
989
  if (this.#launch === undefined) throw new WireFailure("operation-failed", "This session cannot start another emulator.");
990
990
  const running = this.#state === "running";
991
+ this.#log(mode === "window" ? "moving the machine to a VICE with a window" : "moving the machine back to a headless VICE");
991
992
  await this.#stop();
992
993
  const handover: Handover = {
993
994
  file: join(this.#workDir, mode === "window" ? "window-open.vsf" : "window-close.vsf"),
@@ -1354,8 +1355,7 @@ export function viceSessionFactory(options: {
1354
1355
  /** The known flaws of the VICE version that the host checked at start. */
1355
1356
  flaws?: readonly string[];
1356
1357
  }): ViceSessionFactory {
1357
- const log = options.log ?? (() => {});
1358
- return async ({ videoStandard }) => {
1358
+ return async ({ videoStandard, log = options.log ?? (() => {}) }) => {
1359
1359
  const launch: ViceLauncher = (mode, launchLog) =>
1360
1360
  launchVice({
1361
1361
  videoStandard,