@henols/c64-re-tools 1.1.0 → 2.0.0-rc.2

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.
Files changed (138) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +190 -37
  3. package/distribution/package.ts +36 -0
  4. package/distribution/plugin.ts +120 -0
  5. package/package.json +34 -40
  6. package/skills/c64-assembler/SKILL.md +73 -0
  7. package/skills/c64-assembler/scripts/assemble.ts +103 -0
  8. package/skills/c64-basic/SKILL.md +67 -0
  9. package/skills/c64-basic/scripts/basic.ts +46 -0
  10. package/skills/c64-disk/SKILL.md +79 -0
  11. package/skills/c64-disk/scripts/disk.ts +65 -0
  12. package/skills/c64-emulator/SKILL.md +124 -0
  13. package/skills/c64-knowledge/SKILL.md +102 -0
  14. package/skills/c64-knowledge/scripts/knowledge.ts +257 -0
  15. package/skills/c64-memory-map/SKILL.md +76 -0
  16. package/skills/c64-memory-map/scripts/memmap.ts +81 -0
  17. package/skills/c64-memory-map/scripts/platform.ts +424 -0
  18. package/skills/c64-provenance/SKILL.md +76 -0
  19. package/skills/c64-provenance/scripts/compare.ts +92 -0
  20. package/skills/c64-provenance/scripts/provenance.ts +83 -0
  21. package/skills/c64-reverse-engineering/SKILL.md +90 -0
  22. package/skills/c64-static-analysis/SKILL.md +96 -0
  23. package/skills/c64-static-analysis/scripts/analyze.ts +175 -0
  24. package/skills/c64-static-analysis/scripts/findings.ts +73 -0
  25. package/skills/c64-testing/SKILL.md +79 -0
  26. package/skills/c64-testing/references/scenario-format.md +76 -0
  27. package/skills/c64-testing/scripts/checklist.ts +51 -0
  28. package/skills/c64-testing/scripts/scenario.ts +473 -0
  29. package/skills/c64-testing/scripts/test.ts +55 -0
  30. package/skills/c64-unpacker/SKILL.md +82 -0
  31. package/skills/c64-unpacker/scripts/evidence.ts +135 -0
  32. package/skills/c64-unpacker/scripts/unpack.ts +216 -0
  33. package/src/c64.ts +281 -0
  34. package/src/cli/ap-sdk.ts +44 -0
  35. package/src/cli/cleanup.ts +194 -0
  36. package/src/cli/main.ts +122 -0
  37. package/src/cli/status.ts +34 -0
  38. package/src/host/main.ts +121 -0
  39. package/src/host/server.ts +409 -0
  40. package/src/host/tools/c1541.ts +255 -0
  41. package/src/host/tools/index.ts +31 -0
  42. package/src/host/tools/petcat.ts +70 -0
  43. package/src/host/tools/status.ts +30 -0
  44. package/src/host/tools/version.ts +41 -0
  45. package/src/host/vice/adapter.ts +761 -0
  46. package/src/host/vice/binary-monitor.ts +389 -0
  47. package/src/host/vice/chips.ts +108 -0
  48. package/src/host/vice/connect.ts +34 -0
  49. package/src/host/vice/process.ts +402 -0
  50. package/src/host/vice/screen.ts +183 -0
  51. package/src/host/vice/session.ts +1316 -0
  52. package/src/host/vice/text-monitor.ts +223 -0
  53. package/src/host-client/connect.ts +241 -0
  54. package/src/host-client/tools.ts +89 -0
  55. package/src/host-client/transfer.ts +100 -0
  56. package/src/host-client/vice-session.ts +254 -0
  57. package/src/knowledge/database.ts +132 -0
  58. package/src/knowledge/history.ts +128 -0
  59. package/src/knowledge/import.ts +344 -0
  60. package/src/knowledge/read.ts +191 -0
  61. package/src/knowledge/schema.ts +140 -0
  62. package/src/knowledge/write.ts +348 -0
  63. package/src/mcp/main.ts +109 -0
  64. package/src/mcp/server.ts +307 -0
  65. package/src/mcp/tools/debug.ts +237 -0
  66. package/src/mcp/tools/execution.ts +116 -0
  67. package/src/mcp/tools/input.ts +69 -0
  68. package/src/mcp/tools/machine.ts +97 -0
  69. package/src/mcp/tools/media.ts +106 -0
  70. package/src/mcp/tools/memory.ts +203 -0
  71. package/src/mcp/tools/video.ts +260 -0
  72. package/src/native/acme.ts +116 -0
  73. package/src/native/discover.ts +141 -0
  74. package/src/native/dxa.ts +166 -0
  75. package/src/native/ghidra/analyze.ts +86 -0
  76. package/src/native/ghidra/index.ts +157 -0
  77. package/src/native/ghidra/language/c64rt_6510.cspec +34 -0
  78. package/src/native/ghidra/language/c64rt_6510.ldefs +15 -0
  79. package/src/native/ghidra/language/c64rt_6510.pspec +15 -0
  80. package/src/native/ghidra/language/c64rt_6510.slaspec +596 -0
  81. package/src/native/ghidra/scripts/C64Export.java +242 -0
  82. package/src/native/ghidra/scripts/C64Prepare.java +60 -0
  83. package/src/native/local.ts +34 -0
  84. package/src/native/processes.ts +350 -0
  85. package/src/native/run.ts +172 -0
  86. package/src/native/staging.ts +83 -0
  87. package/src/native/status.ts +101 -0
  88. package/src/native/types.ts +228 -0
  89. package/src/native/watchdog.ts +78 -0
  90. package/src/project.ts +39 -0
  91. package/src/protocol/framing.ts +132 -0
  92. package/src/protocol/messages.ts +252 -0
  93. package/src/protocol/tools.ts +247 -0
  94. package/src/protocol/vice.ts +1235 -0
  95. package/src/script.ts +59 -0
  96. package/src/time.ts +15 -0
  97. package/src/trace.ts +162 -0
  98. package/THIRD-PARTY-NOTICES.md +0 -26
  99. package/bin/cli.mjs +0 -283
  100. package/skills/acme-build/SKILL.md +0 -260
  101. package/skills/acme-build/scripts/acme.mjs +0 -341
  102. package/skills/acme-build/template.a +0 -53
  103. package/skills/c64-disk-access/SKILL.md +0 -159
  104. package/skills/c64-disk-access/scripts/c1541.mjs +0 -569
  105. package/skills/c64-memory-mapping/SKILL.md +0 -631
  106. package/skills/c64-memory-mapping/memmap.json +0 -8800
  107. package/skills/c64-memory-mapping/scripts/driver.mjs +0 -553
  108. package/skills/c64-petcat/SKILL.md +0 -87
  109. package/skills/c64-petcat/scripts/petcat.mjs +0 -221
  110. package/skills/c64-program-recon/SKILL.md +0 -706
  111. package/skills/c64-program-recon/references/control-flow.md +0 -180
  112. package/skills/c64-program-recon/references/graphics.md +0 -79
  113. package/skills/c64-program-recon/references/observation-hazards.md +0 -143
  114. package/skills/c64-program-recon/references/reconstruction.md +0 -133
  115. package/skills/c64-program-recon/references/sound-and-input.md +0 -74
  116. package/skills/c64-program-recon/references/tool-selection.md +0 -78
  117. package/skills/c64-program-recon/scripts/derive.mjs +0 -364
  118. package/skills/c64-program-recon/scripts/packer-finding.mjs +0 -709
  119. package/skills/c64-program-recon/templates/memory-map.template.md +0 -101
  120. package/skills/c64-provenance-diff/SKILL.md +0 -292
  121. package/skills/c64-provenance-diff/scripts/diff-images.mjs +0 -984
  122. package/skills/c64-provenance-diff/scripts/recovery-schema.mjs +0 -395
  123. package/skills/c64-ram-capture/RELEASES.json.example +0 -17
  124. package/skills/c64-ram-capture/SKILL.md +0 -474
  125. package/skills/c64-ram-capture/scripts/compare-cross-binary.mjs +0 -525
  126. package/skills/c64-ram-capture/scripts/compare.mjs +0 -258
  127. package/skills/c64-ram-capture/scripts/derive-transients.mjs +0 -575
  128. package/skills/c64-ram-capture/scripts/dump-artifacts.mjs +0 -317
  129. package/skills/c64-ram-capture/scripts/mcp-module.mjs +0 -174
  130. package/skills/c64-ram-capture/scripts/project-paths.mjs +0 -81
  131. package/skills/c64-ram-capture/scripts/releases.mjs +0 -109
  132. package/skills/c64-ram-capture/scripts/vsf-slice.mjs +0 -147
  133. package/skills/c64-ram-capture/scripts/watch-loads.mjs +0 -581
  134. package/skills/c64-ram-capture/templates/capture-record.template.md +0 -99
  135. package/skills/c64-ram-capture/transients/README.md +0 -137
  136. package/skills/routine-queue-walker/SKILL.md +0 -394
  137. package/skills/routine-queue-walker/scripts/completeness-report.mjs +0 -465
  138. package/skills/vice-wedge-triage/SKILL.md +0 -269
@@ -0,0 +1,103 @@
1
+ // The c64-assembler script: assembles project source with ACME, which it runs
2
+ // itself, and writes the program into the project.
3
+
4
+ import { mkdirSync, writeFileSync } from "node:fs";
5
+ import { dirname } from "node:path";
6
+ import { parseArgs } from "node:util";
7
+
8
+ import { formatC64Address, parseC64Address } from "#src/c64.ts";
9
+ import { readProjectTree } from "#src/host-client/transfer.ts";
10
+ import { assemble } from "#src/native/acme.ts";
11
+ import { localToolContext } from "#src/native/local.ts";
12
+ import type { AcmeParams } from "#src/native/types.ts";
13
+ import { resolveProjectPath } from "#src/project.ts";
14
+ import { runScript, UsageError } from "#src/script.ts";
15
+
16
+ const USAGE = `assemble.ts --source-root <dir> --entry <file> --out <file.prg> [options]
17
+
18
+ --source-root <dir> project directory to assemble from; all of it is sent
19
+ --entry <file> the main source file, relative to the source root
20
+ --out <file.prg> where to write the program, relative to the project
21
+ --include <dir> include directory relative to the source root (repeatable)
22
+ --define NAME=VALUE assembler symbol: an integer ($hex allowed) or true/false (repeatable)
23
+ --set-pc <address> start address when the source sets none, for example '$0801'
24
+
25
+ The result is one JSON object. Addresses are $ and four hex digits.`;
26
+
27
+ function parseDefine(text: string): [string, number | boolean] {
28
+ const match = /^([A-Za-z_][A-Za-z0-9_]*)=(.+)$/.exec(text);
29
+ if (match === null) throw new UsageError(`--define needs NAME=VALUE, not ${text}`);
30
+ const value = match[2]!;
31
+ if (value === "true" || value === "false") return [match[1]!, value === "true"];
32
+ if (/^\$[0-9a-fA-F]+$/.test(value)) return [match[1]!, Number.parseInt(value.slice(1), 16)];
33
+ if (/^-?\d+$/.test(value)) return [match[1]!, Number(value)];
34
+ throw new UsageError(`--define ${match[1]} needs an integer or true/false, not ${value}`);
35
+ }
36
+
37
+ async function run(argv: string[]): Promise<{ output: unknown; failed: boolean }> {
38
+ const { values } = parseArgs({
39
+ args: argv,
40
+ strict: true,
41
+ options: {
42
+ "source-root": { type: "string" },
43
+ entry: { type: "string" },
44
+ out: { type: "string" },
45
+ include: { type: "string", multiple: true },
46
+ define: { type: "string", multiple: true },
47
+ "set-pc": { type: "string" },
48
+ help: { type: "boolean" },
49
+ },
50
+ });
51
+ if (values.help) throw new UsageError("");
52
+ const sourceRoot = values["source-root"];
53
+ const entry = values.entry;
54
+ const out = values.out;
55
+ if (sourceRoot === undefined || entry === undefined || out === undefined) throw new UsageError("--source-root, --entry and --out are necessary");
56
+ let outPath: string;
57
+ try {
58
+ outPath = resolveProjectPath(out);
59
+ } catch {
60
+ throw new UsageError(`--out must be a path relative to the project directory that stays inside it, not ${out}`);
61
+ }
62
+ const defines = Object.fromEntries((values.define ?? []).map(parseDefine));
63
+ let setPc: number | undefined;
64
+ if (values["set-pc"] !== undefined) {
65
+ try {
66
+ setPc = parseC64Address(values["set-pc"]);
67
+ } catch {
68
+ throw new UsageError(`--set-pc must be $ followed by four hex digits, not ${values["set-pc"]}`);
69
+ }
70
+ }
71
+ const tree = readProjectTree(sourceRoot);
72
+ if (!tree.files.some((file) => file.path === entry)) throw new UsageError(`--entry ${entry} is not a file in ${sourceRoot}`);
73
+ const params: AcmeParams = { files: tree.files, entrySource: entry, includeDirs: values.include ?? [], defines, ...(setPc === undefined ? {} : { setPc }) };
74
+
75
+ const { result, attachments } = await assemble(params, tree.contents, localToolContext());
76
+ if (!result.assembled) return { output: { assembled: false, diagnostics: result.diagnostics }, failed: true };
77
+ mkdirSync(dirname(outPath), { recursive: true });
78
+ writeFileSync(outPath, attachments![0]!);
79
+ const range = result.loadRange!;
80
+ return {
81
+ output: {
82
+ assembled: true,
83
+ output: out,
84
+ loadRange: { start: formatC64Address(range.start), end: formatC64Address(range.end), bytes: range.bytes },
85
+ symbols: result.symbols!.map((symbol) =>
86
+ symbol.kind === "address"
87
+ ? { name: symbol.name, kind: "address", address: formatC64Address(symbol.address), used: symbol.used }
88
+ : { name: symbol.name, kind: "constant", value: symbol.value, used: symbol.used },
89
+ ),
90
+ diagnostics: result.diagnostics,
91
+ },
92
+ failed: false,
93
+ };
94
+ }
95
+
96
+ await runScript(
97
+ async () => {
98
+ const { output, failed } = await run(process.argv.slice(2));
99
+ if (failed) process.exitCode = 1;
100
+ return output;
101
+ },
102
+ { usage: USAGE },
103
+ );
@@ -0,0 +1,67 @@
1
+ ---
2
+ name: c64-basic
3
+ description: Use this skill to decode a C64 BASIC V2 program (PRG) into a readable listing. It also finds where the BASIC gives control to machine code with SYS or USR. Use it on the start file of a game or of a demo. Do not use it on machine code or to run the program. Give the handoff address to the c64-static-analysis or c64-emulator skill.
4
+ ---
5
+
6
+ # c64-basic
7
+
8
+ ## Purpose
9
+
10
+ Decode a C64 BASIC V2 program. Find the handoff from BASIC to machine code.
11
+
12
+ This skill does not run the program and does not change project knowledge.
13
+
14
+ ## Inputs
15
+
16
+ - The program: a PRG file, relative to the project directory.
17
+
18
+ ## Tools / execution path
19
+
20
+ The script sends the program to the host runtime. The host decodes it with petcat and finds the handoffs.
21
+
22
+ ```
23
+ node <skill>/scripts/basic.ts extracted/game.prg
24
+ ```
25
+
26
+ The script prints one JSON object. Addresses are `$` and four hex digits.
27
+
28
+ ## Workflow
29
+
30
+ 1. Run the script on the program.
31
+ 2. Read `listing`. Each line starts with its BASIC line number. Keywords show in lower case. Control characters in strings show in braces, for example `{clr}`.
32
+ 3. Read `handoffs`. Each handoff has a `kind` (`sys` or `usr`) and the BASIC `line`.
33
+ - A `sys` handoff with an `address`: BASIC calls the machine code at that address.
34
+ - A handoff with `computed: true`: the address comes from a calculation at run time. Use the c64-emulator skill to find the address.
35
+ 4. Compare the address with `loadRange` and `basicEnd`. From `basicEnd` to the end of `loadRange`, the file holds bytes that are not BASIC. Usually this is the machine code that the handoff calls.
36
+
37
+ The address ranges are for a load to the load address of the file. A load without `,1` puts a BASIC program at `$0801`. Most loaders have `$0801` as their load address.
38
+
39
+ ## Knowledge
40
+
41
+ Automatic write:
42
+ - none.
43
+
44
+ Do not store:
45
+ - the listing.
46
+
47
+ The entry point of the machine code can be durable knowledge. The c64-reverse-engineering skill decides that and records it with the c64-knowledge skill.
48
+
49
+ ## Result
50
+
51
+ The listing and the handoff addresses. Or `decoded: false` with a reason when the file is not a BASIC program.
52
+
53
+ The exit status is 0 for a listing. The exit status is 1 for `decoded: false` and for an error. The exit status is 2 for a wrong argument.
54
+
55
+ ## Failure and conflicts
56
+
57
+ - `decoded: false`: the file is not a BASIC program. This is a normal result. Usually the file is machine code. Give it to the c64-static-analysis skill.
58
+ - An empty `handoffs` list: the BASIC does not start machine code with SYS or USR. Do not make up a handoff. Look for a POKE into a vector or a LOAD of another file in the listing.
59
+ - `installation-incomplete`: petcat is not on the host. Tell the user the message.
60
+ - `operation-failed` with a message about the host runtime: the host runtime does not run. Tell the user to start the host runtime with `npx -y --package=@henols/c64-re-tools@latest c64-re-tools-host`.
61
+ - `not-found` or `invalid-input`: the path or an argument is wrong. Read the message and correct the call.
62
+
63
+ ## Handoffs
64
+
65
+ - Machine code at the handoff address → c64-static-analysis
66
+ - Run the program to the handoff → c64-emulator
67
+ - Record the entry point → c64-knowledge
@@ -0,0 +1,46 @@
1
+ // The c64-basic script: decodes a project C64 BASIC V2 program with petcat
2
+ // through the Host Runtime and reports its machine-code handoffs.
3
+
4
+ import { statSync } from "node:fs";
5
+ import { parseArgs } from "node:util";
6
+
7
+ import { formatC64Address } from "#src/c64.ts";
8
+ import { decodeBasic } from "#src/host-client/tools.ts";
9
+ import { resolveProjectPath } from "#src/project.ts";
10
+ import { runScript, UsageError } from "#src/script.ts";
11
+
12
+ const USAGE = `basic.ts <program>
13
+
14
+ <program> is a PRG file relative to the project directory. The result is one
15
+ JSON object. Addresses are $ and four hex digits.`;
16
+
17
+ async function run(argv: string[]): Promise<{ output: unknown; failed: boolean }> {
18
+ const { values, positionals } = parseArgs({ args: argv, strict: true, allowPositionals: true, options: { help: { type: "boolean" } } });
19
+ if (values.help) throw new UsageError("");
20
+ if (positionals.length !== 1) throw new UsageError("give one program");
21
+ const program = positionals[0]!;
22
+ const result = await decodeBasic({ program });
23
+ if (!result.decoded) return { output: { decoded: false, reason: result.reason }, failed: true };
24
+ const size = statSync(resolveProjectPath(program)).size - 2;
25
+ const end = Math.min(0xffff, result.loadAddress + size - 1);
26
+ return {
27
+ output: {
28
+ decoded: true,
29
+ loadRange: { start: formatC64Address(result.loadAddress), end: formatC64Address(end) },
30
+ // From here to the end of the load range, the file holds no BASIC (often machine code).
31
+ basicEnd: result.basicEnd <= end ? formatC64Address(result.basicEnd) : null,
32
+ listing: result.listing,
33
+ handoffs: result.handoffs.map((handoff) => ("address" in handoff ? { ...handoff, address: formatC64Address(handoff.address) } : handoff)),
34
+ },
35
+ failed: false,
36
+ };
37
+ }
38
+
39
+ await runScript(
40
+ async () => {
41
+ const { output, failed } = await run(process.argv.slice(2));
42
+ if (failed) process.exitCode = 1;
43
+ return output;
44
+ },
45
+ { usage: USAGE },
46
+ );
@@ -0,0 +1,79 @@
1
+ ---
2
+ name: c64-disk
3
+ description: Use this skill to examine a C64 disk image (D64, D71, D81 or G64) and to extract files from it into the project. It shows the directory, the BAM, the directory entry of a file and the sector chain of a file. Do not use it to run a program or to decode BASIC. Give an extracted file to the c64-basic, c64-unpacker or c64-static-analysis skill.
4
+ ---
5
+
6
+ # c64-disk
7
+
8
+ ## Purpose
9
+
10
+ Examine a C64 disk image. Extract a file from the image into the project.
11
+
12
+ This skill does not change the disk image and does not change project knowledge.
13
+
14
+ ## Inputs
15
+
16
+ - The disk image: a `.d64`, `.d71`, `.d81` or `.g64` file, relative to the project directory.
17
+ - For one file: the file name as the directory shows it.
18
+ - To extract a file: the output path, relative to the project directory.
19
+
20
+ ## Tools / execution path
21
+
22
+ The script sends the image to the host runtime. The host runs c1541 and sends the result back. The script writes an extracted file into the project.
23
+
24
+ ```
25
+ node <skill>/scripts/disk.ts <action> <image> [name] [--out <file>]
26
+ ```
27
+
28
+ | Need | Command |
29
+ |---|---|
30
+ | The disk name, the free blocks and the files | `directory original/game.d64` |
31
+ | The free and used sectors of each track | `bam original/game.d64` |
32
+ | The type, size and first block of one file | `entry original/game.d64 GAME` |
33
+ | The track and sector of each block of one file | `chain original/game.d64 GAME` |
34
+ | Extract one file | `read original/game.d64 GAME --out extracted/game.prg` |
35
+
36
+ The script prints one JSON object.
37
+
38
+ File names show the characters that the C64 shows in a directory list. A byte that has no character shows as `{$xx}`, for example `{$c1}`. Write the name in the same form. The letter case of a name is not important. Put the name in single quotes in a shell, for example `'GAME {$c1}'`.
39
+
40
+ ## Workflow
41
+
42
+ 1. Run `directory`. Find the file in `entries`. Each entry has a `name`, a `type`, a size in `blocks` and the `closed` and `locked` flags.
43
+ 2. Run `read` with an output path to extract the file. A PRG file keeps its 2-byte load address at the start.
44
+ 3. Run `entry` or `chain` only when the disk structure is important, for example for a loader that reads sectors directly.
45
+ 4. Run `bam` only to find used sectors that no file uses.
46
+
47
+ ## Knowledge
48
+
49
+ Automatic write:
50
+ - none.
51
+
52
+ Do not store:
53
+ - the directory list
54
+ - sector chains.
55
+
56
+ A downstream skill records the meaning of the extracted content.
57
+
58
+ ## Result
59
+
60
+ The directory, the BAM, an entry or a sector chain as JSON. Or the extracted file in the project at the output path.
61
+
62
+ The exit status is 0 for a result with the file. The exit status is 1 for `found: false` and for an error. The exit status is 2 for a wrong argument.
63
+
64
+ ## Failure and conflicts
65
+
66
+ - `found: false`: the image has no file with that name. This is a normal result. Read the directory again and compare the names.
67
+ - `media-error`: the image has a defect or is not a disk image. The message tells the problem, for example a sector chain that loops. Tell the user. Do not change the image.
68
+ - `installation-incomplete`: c1541 is not on the host. Tell the user the message.
69
+ - `operation-failed` with a message about the host runtime: the host runtime does not run. Tell the user to start the host runtime with `npx -y --package=@henols/c64-re-tools@latest c64-re-tools-host`.
70
+ - `invalid-input`: an argument or a path is wrong. Read the message and correct the call.
71
+
72
+ A file that is not `closed` was not written fully. Its data can be incomplete.
73
+
74
+ ## Handoffs
75
+
76
+ - BASIC program → c64-basic
77
+ - Packed program → c64-unpacker
78
+ - Machine code → c64-static-analysis
79
+ - Run the program → c64-emulator
@@ -0,0 +1,65 @@
1
+ // The c64-disk script: inspects a project disk image with c1541 through the
2
+ // Host Runtime and extracts files into the project.
3
+
4
+ import { mkdirSync, writeFileSync } from "node:fs";
5
+ import { dirname } from "node:path";
6
+ import { parseArgs } from "node:util";
7
+
8
+ import { inspectDisk } from "#src/host-client/tools.ts";
9
+ import { resolveProjectPath } from "#src/project.ts";
10
+ import { runScript, UsageError } from "#src/script.ts";
11
+
12
+ const USAGE = `disk.ts <action> <image> [name] [--out <file>]
13
+
14
+ directory <image> disk name, id, free blocks and the files
15
+ bam <image> free and used sectors of each track
16
+ entry <image> <name> the directory entry of one file
17
+ chain <image> <name> the track and sector of each block of one file
18
+ read <image> <name> --out <file> write the bytes of one file into the project
19
+
20
+ <image> is a .d64, .d71, .d81 or .g64 file, relative to the project directory.
21
+ <name> is the file name as the directory shows it. Write a byte that has no
22
+ character as {$xx}, for example {$c1}. The result is one JSON object.`;
23
+
24
+ const ACTIONS = ["directory", "bam", "entry", "chain", "read"] as const;
25
+ type Action = (typeof ACTIONS)[number];
26
+
27
+ async function run(argv: string[]): Promise<{ output: unknown; failed: boolean }> {
28
+ const { values, positionals } = parseArgs({ args: argv, strict: true, allowPositionals: true, options: { out: { type: "string" }, help: { type: "boolean" } } });
29
+ if (values.help) throw new UsageError("");
30
+ const [action, image, name, ...extra] = positionals;
31
+ if (!ACTIONS.includes(action as Action)) throw new UsageError(`the action must be one of ${ACTIONS.join(", ")}`);
32
+ if (image === undefined) throw new UsageError("the disk image is necessary");
33
+ const named = action === "entry" || action === "chain" || action === "read";
34
+ if (named && name === undefined) throw new UsageError(`${action} needs a file name`);
35
+ if ((!named && name !== undefined) || extra.length > 0) throw new UsageError("too many arguments");
36
+ if (action === "read" && values.out === undefined) throw new UsageError("read needs --out <file>");
37
+ if (action !== "read" && values.out !== undefined) throw new UsageError("only read takes --out");
38
+ let outPath: string | undefined;
39
+ if (values.out !== undefined) {
40
+ try {
41
+ outPath = resolveProjectPath(values.out);
42
+ } catch {
43
+ throw new UsageError(`--out must be a path relative to the project directory that stays inside it, not ${values.out}`);
44
+ }
45
+ }
46
+
47
+ const result = await inspectDisk({ image, action: action as Action, ...(name === undefined ? {} : { name }) });
48
+ if ("found" in result && !result.found) return { output: { found: false }, failed: true };
49
+ if (result.action === "read" && result.found) {
50
+ mkdirSync(dirname(outPath!), { recursive: true });
51
+ writeFileSync(outPath!, result.data!);
52
+ return { output: { found: true, name: result.name, bytes: result.bytes, output: values.out }, failed: false };
53
+ }
54
+ const { action: _action, ...output } = result;
55
+ return { output, failed: false };
56
+ }
57
+
58
+ await runScript(
59
+ async () => {
60
+ const { output, failed } = await run(process.argv.slice(2));
61
+ if (failed) process.exitCode = 1;
62
+ return output;
63
+ },
64
+ { usage: USAGE },
65
+ );
@@ -0,0 +1,124 @@
1
+ ---
2
+ name: c64-emulator
3
+ description: Use this skill to operate and debug the live C64 emulator through the c64-re-tools MCP tools, whose names start with c64_. It loads programs and disks, controls and steps the CPU, and sets breakpoints and watchpoints. It reads and writes memory and registers, types keys, moves a joystick, advances frames and captures the screen. Do not use it for static analysis, project knowledge, assembly or disk inspection.
4
+ ---
5
+
6
+ # c64-emulator
7
+
8
+ ## Purpose
9
+
10
+ Operate and debug the one live C64 that belongs to this MCP session. Get runtime evidence and runtime state from it.
11
+
12
+ This skill does not analyse code statically. It does not write project knowledge. It does not assemble source code.
13
+
14
+ ## Inputs
15
+
16
+ - The machine action or the runtime question.
17
+ - The target address, state or condition, when the question needs one.
18
+ - The project path of the program or disk to load, when the question needs one.
19
+
20
+ Addresses are four hex digits after a dollar sign, for example `$c000`. Resolve a symbol name to its address before you use a tool. The c64-knowledge skill gives the address of a known symbol.
21
+
22
+ ## Tools / execution path
23
+
24
+ Use only the MCP tools. Each MCP session has its own C64. Nothing else can change that C64.
25
+
26
+ | Need | Tool |
27
+ |---|---|
28
+ | Run state, video standard, warp mode, window | `c64_status` |
29
+ | Reset, power cycle | `c64_reset` |
30
+ | Fast emulation | `c64_warp` |
31
+ | Show the C64 to the user in a window, hide it again | `c64_window` |
32
+ | Stop, start, step, step over, step out, exact frames | `c64_execution` |
33
+ | Run until an address, a memory value or a raster position | `c64_run_until` |
34
+ | Stop on an instruction address | `c64_breakpoint` |
35
+ | Stop on a memory read or write | `c64_watchpoint` |
36
+ | Memory | `c64_memory_read`, `c64_memory_write`, `c64_memory_search`, `c64_memory_compare` |
37
+ | CPU registers | `c64_registers` |
38
+ | Code as it is in memory now | `c64_disassemble` |
39
+ | Executed instructions, call chain | `c64_cpu_history`, `c64_backtrace` |
40
+ | Where the CPU spends cycles, which memory it uses | `c64_profile`, `c64_memmap` |
41
+ | Cycle count between two points | `c64_timing` |
42
+ | Video chip, sprites, CIAs, sound chip | `c64_vicii`, `c64_sprite`, `c64_cia`, `c64_sid` |
43
+ | Screen image, screen compare | `c64_screen` |
44
+ | Several of these from one moment | `c64_observe` |
45
+ | Keys, joystick | `c64_keyboard`, `c64_joystick` |
46
+ | Program and disk media | `c64_autostart`, `c64_program_load`, `c64_disk_attach` |
47
+ | Machine state to go back to | `c64_snapshot` |
48
+
49
+ Each memory and register tool has a `space`. Use `c64` for the computer. Use `drive8` for the CPU of the 1541 disk drive. The memory `view` is `cpu` or `ram`. With `cpu`, you see what the CPU sees now, with ROM and I/O where the memory configuration puts them. With `ram`, you see the RAM under them.
50
+
51
+ ## Workflow
52
+
53
+ 1. Read the project knowledge for the addresses in the question. Use the c64-knowledge skill.
54
+ 2. Get the machine state with `c64_status`.
55
+ 3. Load the subject if it is not loaded. Use `c64_autostart` to start it as a user does. Use `c64_program_load` to put a PRG in memory with no start.
56
+ 4. Set the stop condition. Use a breakpoint, a watchpoint or a `c64_run_until` target. Add a typed condition when the stop must occur only in one state. The `list` result shows the condition of each breakpoint and each watchpoint.
57
+ 5. Run, step or give input. Prefer `c64_run_until` and `advance-frames` to waits. They stop at the same emulated time on every run.
58
+ 6. Read only the state that answers the question. To read several things from one moment, use `c64_observe`.
59
+ 7. Interpret the evidence. Keep the observed facts apart from your conclusions.
60
+
61
+ Write operations need a stopped CPU. Stop the CPU with `c64_execution` action `pause` first.
62
+
63
+ `c64_reset` with `run` false stops the CPU at the first instruction of the reset routine. The result gives that address in `pc`.
64
+
65
+ Step, next and until-return work only in space `c64`. The emulator cannot stop the disk drive CPU after one instruction. To find when the drive executes an address, use `c64_run_until` with an address target in space `drive8`, or a breakpoint in space `drive8`. The drive can execute a few more instructions before it stops. The computer completes its current instruction. The `pc` in the result is the drive address where it stopped.
66
+
67
+ `c64_keyboard` puts keys in the keyboard queue of the KERNAL. A program that reads the keyboard hardware directly does not see them. Many games do this. For such a program, use `c64_joystick`, or open the window and ask the user to press the key.
68
+
69
+ Before a risky experiment, save the state with `c64_snapshot`. Restore it to try again from the same point.
70
+
71
+ The emulator has no window. When the user must type, play or look at the C64, do these steps:
72
+
73
+ 1. Open the window with `c64_window` action `open`.
74
+ 2. Tell the user that the window is open and what to do in it.
75
+ 3. Wait until the user tells you that they finished.
76
+ 4. Close the window with `c64_window` action `close`.
77
+
78
+ The machine, the disk in drive 8, the breakpoints, the watchpoints, the joysticks, warp mode and `c64_timing` stay the same. The CPU stays running or stopped. The result field `notCarried` names the data that the move clears. A second `open` or `close` changes nothing.
79
+
80
+ A read does not change the run state. A running machine continues to run after a read.
81
+
82
+ ## Knowledge
83
+
84
+ Read:
85
+ - the current knowledge for each address and range that the question touches.
86
+
87
+ Automatic write:
88
+ - none.
89
+
90
+ Write after interpretation:
91
+ - a symbol, a region or a comment that the runtime evidence justifies. Record it with the c64-knowledge skill.
92
+
93
+ Do not store:
94
+ - breakpoints or watchpoints
95
+ - memory reads
96
+ - temporary machine state
97
+ - a screen capture, unless the user asks for it as an artifact
98
+
99
+ ## Result
100
+
101
+ The result is the runtime state or evidence that the question asks for.
102
+
103
+ If you cannot get the machine into the necessary state, say which state you could not get. Say what you tried.
104
+
105
+ ## Failure and conflicts
106
+
107
+ - `machine-state-lost` and the message says that the VICE window closed: the window closed before `c64_window` action `close`. The emulator has no window again. The machine state is the same as at the time when the window opened. The breakpoints and the watchpoints stay. Do not use results from the window time. Read the machine state again, then continue.
108
+ - `machine-unavailable` and the message says that the VICE window closed: the session starts the emulator again. Wait a few seconds. Then do the operation again.
109
+ - `machine-state-lost` with a different message: the emulator stopped and its state is gone. Do not continue with results from before. Tell the user to restart the MCP server.
110
+ - `machine-unavailable` and the message names a pause or a dialog in the emulator window: this occurs only while the window is open. Do not send more operations. Tell the user to resume the emulator (Pause, Alt+P) or to close the dialog in the VICE window. Then do the operation again. The machine state does not change.
111
+ - `machine-unavailable` with a different message: the host runtime does not run, or it does not answer. Tell the user to start the host runtime with `npx -y --package=@henols/c64-re-tools@latest c64-re-tools-host`.
112
+ - `machine-running`: stop the CPU first, then try again.
113
+ - `installation-incomplete`: tell the user the message. Do not try to repair the installation.
114
+ - `c64_run_until` with `stopReason` `timeout`: the target did not occur in the frame limit. This is evidence too. Report it.
115
+ - `c64_run_until` with `stopReason` `jam`: the CPU executed a JAM opcode and stopped. Report the address.
116
+
117
+ If the runtime evidence does not agree with the current knowledge, do not change the knowledge. Report the conflict and hand it to c64-knowledge.
118
+
119
+ ## Handoffs
120
+
121
+ - Static explanation of code → c64-static-analysis
122
+ - Durable conclusion or a knowledge conflict → c64-knowledge
123
+ - Comparison or assertion between runs → c64-testing
124
+ - Meaning of a C64 address or register → c64-memory-map
@@ -0,0 +1,102 @@
1
+ ---
2
+ name: c64-knowledge
3
+ description: Use this skill to read, write, correct and review the durable knowledge of a C64 project in .c64-re-tools/knowledge.db. It gives the knowledge at an address, and it names symbols, classifies memory regions, sets comments and records references. It also shows the history of each change, and it can revert to an earlier revision. Do not use it for live emulator state or for static analysis.
4
+ ---
5
+
6
+ # c64-knowledge
7
+
8
+ ## Purpose
9
+
10
+ Read, write, correct and review the durable understanding of a C64 project. The knowledge is in `.c64-re-tools/knowledge.db` in the project directory. Git can version that file.
11
+
12
+ This skill is the only write path for conclusions of the user and the LLM. It does not run an emulator or an analyzer.
13
+
14
+ ## Inputs
15
+
16
+ One of these:
17
+
18
+ - an address, a range or a symbol name to look up
19
+ - text to search for
20
+ - a change to a symbol, a region, a comment or a reference, with a short reason
21
+ - a request to see the history or a revision.
22
+
23
+ ## Tools / execution path
24
+
25
+ Run the script in the project directory. The project directory is the directory that the harness started in.
26
+
27
+ ```
28
+ node <skill>/scripts/knowledge.ts <command> [arguments] [options]
29
+ ```
30
+
31
+ The script prints one JSON object. An error prints `{"error": {"code": ..., "message": ...}}`. Addresses are `$` and four hex digits. Quote them in a shell, for example `'$2100'`.
32
+
33
+ | Need | Command |
34
+ |---|---|
35
+ | Everything known at one address | `at '$2100'` |
36
+ | Find names and comment text | `search player` |
37
+ | Lists in a range | `symbols`, `regions`, `comments`, `references` with `--start` and `--end` |
38
+ | Name the symbol at an address | `rename '$2100' update_player --kind routine` |
39
+ | Remove a symbol | `remove-symbol '$2100'` |
40
+ | Classify memory | `classify '$3000' '$303f' sprite` |
41
+ | Make memory unknown again | `unclassify '$3000' '$303f'` |
42
+ | Set or remove a comment | `comment '$2100' line Reads joystick 2.` / `uncomment '$2100' line` |
43
+ | Add or remove a reference | `reference '$2000' '$2100' call` / `unreference ...` |
44
+ | History | `history '$2100'`, `history --name update_player`, `revisions`, `revision 12` |
45
+ | Go back to an earlier state | `revert 12` |
46
+
47
+ Symbol kinds are `label`, `routine`, `variable`, `data` and `vector`. Region types are `code`, `bytes`, `words`, `addresses`, `addresses-split`, `petscii`, `screen`, `bitmap` and `sprite`. Reference kinds are `call`, `jump`, `read`, `write` and `reference`.
48
+
49
+ ## Workflow
50
+
51
+ Read:
52
+
53
+ 1. Use `at` for the addresses in the question before other work.
54
+ 2. Use `history` when the current knowledge is surprising or disputed.
55
+
56
+ Write:
57
+
58
+ 1. Read the current value with `at`. Note the `revision` in the result.
59
+ 2. Decide the change. Keep observed facts apart from your conclusions.
60
+ 3. Write it with `--reason` and `--expect-revision` set to the revision you read.
61
+ 4. Read the result. A `revision` of `null` means that nothing changed.
62
+
63
+ Each write is one revision. A classification inside a region splits that region, so regions never overlap.
64
+
65
+ ## Knowledge
66
+
67
+ Write after interpretation:
68
+
69
+ - a symbol name that tells what a routine or a variable does
70
+ - a region type that the evidence supports
71
+ - a comment that explains behavior that a name cannot show
72
+ - a reference that the evidence supports.
73
+
74
+ Do not store:
75
+
76
+ - temporary investigation state
77
+ - a guess, only because a routine needs a temporary name
78
+ - confidence or status words in comment text.
79
+
80
+ The script records the origin `llm`. Use `--origin user` only when the user decides the change.
81
+
82
+ ## Result
83
+
84
+ For a read: the current or historical knowledge in compact JSON.
85
+
86
+ For a write: the new revision and the current value after the write. If the write replaces a symbol or a region of a different origin, `previous` gives that row as it was before the write. For example, an `llm` write over a `user` name gives `previous`. Read `previous` and tell the user about the change.
87
+
88
+ The exit status is 0 for a result. The exit status is 1 for an error from the knowledge, for example `conflict`. The exit status is 2 for a wrong argument.
89
+
90
+ ## Failure and conflicts
91
+
92
+ - `conflict`: the change contradicts current knowledge. For example, the name belongs to another address. Read that address and its history, then decide.
93
+ - `stale-revision`: the knowledge changed after you read it. Read it again, then decide again.
94
+ - `not-found`: there is nothing to remove or no such revision.
95
+ - `invalid-database` or `unsupported-schema`: tell the user the message. Do not edit the file.
96
+
97
+ A revert makes a new revision. The history stays complete.
98
+
99
+ ## Handoffs
100
+
101
+ - Runtime evidence → c64-emulator
102
+ - Static evidence → c64-static-analysis