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

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 (159) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +154 -37
  3. package/distribution/package.ts +21 -0
  4. package/distribution/plugin.ts +122 -0
  5. package/package.json +34 -40
  6. package/skills/c64-assembler/SKILL.md +76 -0
  7. package/skills/c64-assembler/scripts/assemble.ts +110 -0
  8. package/skills/c64-assembler/scripts/compare.ts +84 -0
  9. package/skills/c64-basic/SKILL.md +66 -0
  10. package/skills/c64-basic/scripts/basic.ts +46 -0
  11. package/skills/c64-disk/SKILL.md +93 -0
  12. package/skills/c64-disk/scripts/containers.ts +124 -0
  13. package/skills/c64-disk/scripts/disk.ts +120 -0
  14. package/skills/c64-emulator/SKILL.md +134 -0
  15. package/skills/c64-knowledge/SKILL.md +166 -0
  16. package/skills/c64-knowledge/scripts/knowledge.ts +492 -0
  17. package/skills/c64-memory-map/SKILL.md +79 -0
  18. package/skills/c64-memory-map/scripts/memmap.ts +92 -0
  19. package/skills/c64-provenance/SKILL.md +110 -0
  20. package/skills/c64-provenance/scripts/compare.ts +152 -0
  21. package/skills/c64-provenance/scripts/provenance.ts +334 -0
  22. package/skills/c64-provenance/scripts/runtime.ts +191 -0
  23. package/skills/c64-provenance/scripts/separate.ts +140 -0
  24. package/skills/c64-reverse-engineering/SKILL.md +144 -0
  25. package/skills/c64-source/SKILL.md +126 -0
  26. package/skills/c64-source/scripts/generate.ts +490 -0
  27. package/skills/c64-source/scripts/loader.ts +571 -0
  28. package/skills/c64-source/scripts/opcodes.ts +61 -0
  29. package/skills/c64-source/scripts/source.ts +595 -0
  30. package/skills/c64-static-analysis/SKILL.md +119 -0
  31. package/skills/c64-static-analysis/scripts/analyze.ts +275 -0
  32. package/skills/c64-static-analysis/scripts/coverage.ts +78 -0
  33. package/skills/c64-static-analysis/scripts/findings.ts +75 -0
  34. package/skills/c64-static-analysis/scripts/idioms.ts +149 -0
  35. package/skills/c64-static-analysis/scripts/instructions.ts +105 -0
  36. package/skills/c64-testing/SKILL.md +103 -0
  37. package/skills/c64-testing/references/scenario-format.md +116 -0
  38. package/skills/c64-testing/scripts/checklist.ts +51 -0
  39. package/skills/c64-testing/scripts/run.ts +189 -0
  40. package/skills/c64-testing/scripts/scenario.ts +493 -0
  41. package/skills/c64-testing/scripts/test.ts +65 -0
  42. package/skills/c64-unpacker/SKILL.md +135 -0
  43. package/skills/c64-unpacker/scripts/evidence.ts +207 -0
  44. package/skills/c64-unpacker/scripts/loads.ts +209 -0
  45. package/skills/c64-unpacker/scripts/machine.ts +80 -0
  46. package/skills/c64-unpacker/scripts/stages.ts +130 -0
  47. package/skills/c64-unpacker/scripts/unpack.ts +683 -0
  48. package/src/c64.ts +411 -0
  49. package/src/cli/ap-sdk.ts +44 -0
  50. package/src/cli/cleanup.ts +194 -0
  51. package/src/cli/main.ts +125 -0
  52. package/src/cli/status.ts +61 -0
  53. package/src/host/main.ts +134 -0
  54. package/src/host/server.ts +392 -0
  55. package/src/host/tools/c1541.ts +284 -0
  56. package/src/host/tools/index.ts +35 -0
  57. package/src/host/tools/petcat.ts +77 -0
  58. package/src/host/tools/status.ts +47 -0
  59. package/src/host/tools/version.ts +41 -0
  60. package/src/host/vice/adapter.ts +804 -0
  61. package/src/host/vice/binary-monitor.ts +389 -0
  62. package/src/host/vice/chips.ts +118 -0
  63. package/src/host/vice/connect.ts +34 -0
  64. package/src/host/vice/process.ts +413 -0
  65. package/src/host/vice/screen.ts +184 -0
  66. package/src/host/vice/session.ts +1426 -0
  67. package/src/host/vice/text-monitor.ts +223 -0
  68. package/src/host-client/autostart.ts +55 -0
  69. package/src/host-client/connect.ts +241 -0
  70. package/src/host-client/coverage.ts +63 -0
  71. package/src/host-client/steps.ts +170 -0
  72. package/src/host-client/tools.ts +89 -0
  73. package/src/host-client/transfer.ts +126 -0
  74. package/src/host-client/vice-session.ts +316 -0
  75. package/src/knowledge/database.ts +132 -0
  76. package/src/knowledge/history.ts +163 -0
  77. package/src/knowledge/import.ts +391 -0
  78. package/src/knowledge/read.ts +342 -0
  79. package/src/knowledge/schema.ts +229 -0
  80. package/src/knowledge/write.ts +663 -0
  81. package/src/mcp/main.ts +134 -0
  82. package/src/mcp/output.ts +32 -0
  83. package/src/mcp/server.ts +316 -0
  84. package/src/mcp/tools/debug.ts +327 -0
  85. package/src/mcp/tools/execution.ts +117 -0
  86. package/src/mcp/tools/input.ts +72 -0
  87. package/src/mcp/tools/machine.ts +100 -0
  88. package/src/mcp/tools/media.ts +129 -0
  89. package/src/mcp/tools/memory.ts +250 -0
  90. package/src/mcp/tools/video.ts +271 -0
  91. package/src/native/acme.ts +116 -0
  92. package/src/native/discover.ts +179 -0
  93. package/src/native/dxa.ts +166 -0
  94. package/src/native/ghidra/analyze.ts +86 -0
  95. package/src/native/ghidra/index.ts +173 -0
  96. package/src/native/ghidra/language/c64rt_6510.cspec +34 -0
  97. package/src/native/ghidra/language/c64rt_6510.ldefs +15 -0
  98. package/src/native/ghidra/language/c64rt_6510.pspec +15 -0
  99. package/src/native/ghidra/language/c64rt_6510.slaspec +596 -0
  100. package/src/native/ghidra/scripts/C64Export.java +274 -0
  101. package/src/native/ghidra/scripts/C64Prepare.java +60 -0
  102. package/src/native/local.ts +34 -0
  103. package/src/native/processes.ts +350 -0
  104. package/src/native/run.ts +172 -0
  105. package/src/native/staging.ts +83 -0
  106. package/src/native/status.ts +113 -0
  107. package/src/native/types.ts +257 -0
  108. package/src/native/watchdog.ts +78 -0
  109. package/src/package.ts +21 -0
  110. package/src/platform.ts +441 -0
  111. package/src/project.ts +39 -0
  112. package/src/protocol/framing.ts +132 -0
  113. package/src/protocol/messages.ts +259 -0
  114. package/src/protocol/tools.ts +266 -0
  115. package/src/protocol/vice.ts +1348 -0
  116. package/src/script.ts +60 -0
  117. package/src/time.ts +15 -0
  118. package/src/trace.ts +194 -0
  119. package/THIRD-PARTY-NOTICES.md +0 -26
  120. package/bin/cli.mjs +0 -283
  121. package/skills/acme-build/SKILL.md +0 -260
  122. package/skills/acme-build/scripts/acme.mjs +0 -341
  123. package/skills/acme-build/template.a +0 -53
  124. package/skills/c64-disk-access/SKILL.md +0 -159
  125. package/skills/c64-disk-access/scripts/c1541.mjs +0 -569
  126. package/skills/c64-memory-mapping/SKILL.md +0 -631
  127. package/skills/c64-memory-mapping/memmap.json +0 -8800
  128. package/skills/c64-memory-mapping/scripts/driver.mjs +0 -553
  129. package/skills/c64-petcat/SKILL.md +0 -87
  130. package/skills/c64-petcat/scripts/petcat.mjs +0 -221
  131. package/skills/c64-program-recon/SKILL.md +0 -706
  132. package/skills/c64-program-recon/references/control-flow.md +0 -180
  133. package/skills/c64-program-recon/references/graphics.md +0 -79
  134. package/skills/c64-program-recon/references/observation-hazards.md +0 -143
  135. package/skills/c64-program-recon/references/reconstruction.md +0 -133
  136. package/skills/c64-program-recon/references/sound-and-input.md +0 -74
  137. package/skills/c64-program-recon/references/tool-selection.md +0 -78
  138. package/skills/c64-program-recon/scripts/derive.mjs +0 -364
  139. package/skills/c64-program-recon/scripts/packer-finding.mjs +0 -709
  140. package/skills/c64-program-recon/templates/memory-map.template.md +0 -101
  141. package/skills/c64-provenance-diff/SKILL.md +0 -292
  142. package/skills/c64-provenance-diff/scripts/diff-images.mjs +0 -984
  143. package/skills/c64-provenance-diff/scripts/recovery-schema.mjs +0 -395
  144. package/skills/c64-ram-capture/RELEASES.json.example +0 -17
  145. package/skills/c64-ram-capture/SKILL.md +0 -474
  146. package/skills/c64-ram-capture/scripts/compare-cross-binary.mjs +0 -525
  147. package/skills/c64-ram-capture/scripts/compare.mjs +0 -258
  148. package/skills/c64-ram-capture/scripts/derive-transients.mjs +0 -575
  149. package/skills/c64-ram-capture/scripts/dump-artifacts.mjs +0 -317
  150. package/skills/c64-ram-capture/scripts/mcp-module.mjs +0 -174
  151. package/skills/c64-ram-capture/scripts/project-paths.mjs +0 -81
  152. package/skills/c64-ram-capture/scripts/releases.mjs +0 -109
  153. package/skills/c64-ram-capture/scripts/vsf-slice.mjs +0 -147
  154. package/skills/c64-ram-capture/scripts/watch-loads.mjs +0 -581
  155. package/skills/c64-ram-capture/templates/capture-record.template.md +0 -99
  156. package/skills/c64-ram-capture/transients/README.md +0 -137
  157. package/skills/routine-queue-walker/SKILL.md +0 -394
  158. package/skills/routine-queue-walker/scripts/completeness-report.mjs +0 -465
  159. package/skills/vice-wedge-triage/SKILL.md +0 -269
@@ -0,0 +1,327 @@
1
+ // Debug tools: c64_breakpoint, c64_watchpoint, c64_cpu_history, c64_backtrace,
2
+ // c64_profile, c64_memmap and c64_timing.
3
+
4
+ import { z } from "zod";
5
+
6
+ import { COVERAGE, executedRamCode, EXECUTE, formatC64Address, READ, WRITE, type CodeRange } from "../../c64.ts";
7
+ import { WireFailure } from "../../protocol/messages.ts";
8
+ import { CHECKPOINT_ACTIONS, MAX_BACKTRACE, MAX_HISTORY, MAX_MEMMAP_RANGES, MAX_MEMORY_READ, MAX_PROFILE, MAX_WATCH_SIZE, SPACES, TIMING_ACTIONS, WATCH_ACCESS, type Breakpoint, type Watchpoint } from "../../protocol/vice.ts";
9
+ import { readCoverage } from "../../host-client/coverage.ts";
10
+ import { OutInput, writeOut, WrittenOutput } from "../output.ts";
11
+ import { AddressInput, AddressOutput, Byte, ConditionInput, ConditionOutput, defineTool, HexData, requireFields, showCondition, SpaceInput, toCondition, type ViceSessionApi } from "../server.ts";
12
+ import { spaced } from "./memory.ts";
13
+
14
+ /** No default here: a filled-in default would count as a field the other actions refuse. */
15
+ const OptionalSpace = z.enum(SPACES).optional().describe("add only: c64 (default) or drive8, the 1541 disk drive CPU");
16
+
17
+ const Id = z.number().int().min(1).describe("the id that action add returned");
18
+
19
+ const BreakpointOutput = z.object({
20
+ id: z.number().int().min(1),
21
+ address: AddressOutput,
22
+ space: z.enum(SPACES),
23
+ enabled: z.boolean(),
24
+ condition: ConditionOutput.optional(),
25
+ });
26
+ const WatchpointOutput = BreakpointOutput.extend({ size: z.number().int().min(1).max(MAX_WATCH_SIZE), access: z.enum(WATCH_ACCESS) });
27
+
28
+ function showBreakpoint(point: Breakpoint) {
29
+ const { condition, ...rest } = point;
30
+ return { ...rest, address: formatC64Address(point.address), ...(condition === undefined ? {} : { condition: showCondition(condition) }) };
31
+ }
32
+
33
+ function showWatchpoint(point: Watchpoint) {
34
+ const { condition, ...rest } = point;
35
+ return { ...rest, address: formatC64Address(point.address), ...(condition === undefined ? {} : { condition: showCondition(condition) }) };
36
+ }
37
+
38
+ export const c64Breakpoint = defineTool({
39
+ name: "c64_breakpoint",
40
+ title: "C64 breakpoints",
41
+ description:
42
+ "Manage breakpoints. A breakpoint stops the CPU when it is about to execute the instruction at an address. " +
43
+ "Action add sets one at address in space c64 or drive8. With a condition, it stops only when the condition is true. " +
44
+ "add returns the new id. Breakpoints and watchpoints take their ids from one counter, so the ids of one kind can have gaps. " +
45
+ "Actions remove, enable and disable take that id. remove returns removed true and the id. Action list gives all breakpoints. Each breakpoint shows its condition, when it has one. " +
46
+ "Breakpoints stay through a reset.",
47
+ inputSchema: z
48
+ .object({
49
+ action: z.enum(CHECKPOINT_ACTIONS),
50
+ address: AddressInput.optional().describe("add only"),
51
+ space: OptionalSpace,
52
+ condition: ConditionInput.optional().describe("add only: stop only when this is true"),
53
+ id: Id.optional().describe("remove, enable and disable only"),
54
+ })
55
+ .strict(),
56
+ outputSchema: BreakpointOutput.partial().extend({ removed: z.literal(true).optional(), breakpoints: z.array(BreakpointOutput).optional() }),
57
+ readOnly: false,
58
+ async run(input, session) {
59
+ const { action, ...fields } = input;
60
+ if (action === "list") {
61
+ requireFields(action, fields, []);
62
+ const result = await session.breakpoint({ action });
63
+ return { breakpoints: result.breakpoints.map(showBreakpoint) };
64
+ }
65
+ if (action !== "add") {
66
+ requireFields(action, fields, ["id"], ["id"]);
67
+ if (action === "remove") return session.breakpoint({ action, id: fields.id! });
68
+ return showBreakpoint(await session.breakpoint({ action, id: fields.id! }));
69
+ }
70
+ requireFields(action, fields, ["address", "space", "condition"], ["address"]);
71
+ const added = await session.breakpoint({
72
+ action,
73
+ address: fields.address!,
74
+ space: fields.space ?? "c64",
75
+ ...(fields.condition === undefined ? {} : { condition: toCondition(fields.condition) }),
76
+ });
77
+ return showBreakpoint(added);
78
+ },
79
+ });
80
+
81
+ export const c64Watchpoint = defineTool({
82
+ name: "c64_watchpoint",
83
+ title: "C64 watchpoints",
84
+ description:
85
+ "Manage watchpoints. A watchpoint stops the CPU when an instruction reads or writes memory in a range. " +
86
+ `Action add sets one at address for size bytes (1 to ${MAX_WATCH_SIZE}, default 1), with access read, write or read-write. ` +
87
+ "With a condition, it stops only when the condition is true. For a write, a memory condition sees the memory after the write. " +
88
+ "add returns the new id. Breakpoints and watchpoints take their ids from one counter, so the ids of one kind can have gaps. " +
89
+ "Actions remove, enable and disable take that id. remove returns removed true and the id. Action list gives all watchpoints. Each watchpoint shows its condition, when it has one.",
90
+ inputSchema: z
91
+ .object({
92
+ action: z.enum(CHECKPOINT_ACTIONS),
93
+ address: AddressInput.optional().describe("add only"),
94
+ size: z.number().int().min(1).max(MAX_WATCH_SIZE).optional().describe("add only. Default 1"),
95
+ access: z.enum(WATCH_ACCESS).optional().describe("add only"),
96
+ space: OptionalSpace,
97
+ condition: ConditionInput.optional().describe("add only: stop only when this is true"),
98
+ id: Id.optional().describe("remove, enable and disable only"),
99
+ })
100
+ .strict(),
101
+ outputSchema: WatchpointOutput.partial().extend({ removed: z.literal(true).optional(), watchpoints: z.array(WatchpointOutput).optional() }),
102
+ readOnly: false,
103
+ async run(input, session) {
104
+ const { action, ...fields } = input;
105
+ if (action === "list") {
106
+ requireFields(action, fields, []);
107
+ const result = await session.watchpoint({ action });
108
+ return { watchpoints: result.watchpoints.map(showWatchpoint) };
109
+ }
110
+ if (action !== "add") {
111
+ requireFields(action, fields, ["id"], ["id"]);
112
+ if (action === "remove") return session.watchpoint({ action, id: fields.id! });
113
+ return showWatchpoint(await session.watchpoint({ action, id: fields.id! }));
114
+ }
115
+ requireFields(action, fields, ["address", "size", "access", "space", "condition"], ["address", "access"]);
116
+ const added = await session.watchpoint({
117
+ action,
118
+ address: fields.address!,
119
+ size: fields.size ?? 1,
120
+ access: fields.access!,
121
+ space: fields.space ?? "c64",
122
+ ...(fields.condition === undefined ? {} : { condition: toCondition(fields.condition) }),
123
+ });
124
+ return showWatchpoint(added);
125
+ },
126
+ });
127
+
128
+ export const c64CpuHistory = defineTool({
129
+ name: "c64_cpu_history",
130
+ title: "CPU history",
131
+ description:
132
+ "Get the last instructions that the CPU executed, oldest first. " +
133
+ `limit is the number of instructions (1 to ${MAX_HISTORY}, default 50). ` +
134
+ "Each entry gives the address, the bytes, the instruction and the registers a, x, y and sp before the instruction executed. " +
135
+ "In space c64, it also gives the raster line and cycle when the instruction started. " +
136
+ "With out, it writes the entries as text lines to that project file, and the result gives the file in place of the entries. " +
137
+ "This does not change the run state of the machine.",
138
+ inputSchema: z.object({ limit: z.number().int().min(1).max(MAX_HISTORY).default(50), space: SpaceInput, out: OutInput.optional() }).strict(),
139
+ outputSchema: z.object({
140
+ entries: z.array(
141
+ z.object({
142
+ address: AddressOutput,
143
+ bytes: HexData,
144
+ text: z.string(),
145
+ a: Byte,
146
+ x: Byte,
147
+ y: Byte,
148
+ sp: Byte,
149
+ rasterLine: z.number().int().min(0).optional(),
150
+ rasterCycle: z.number().int().min(0).optional(),
151
+ }),
152
+ ).optional(),
153
+ count: z.number().int().min(0).optional(),
154
+ ...WrittenOutput,
155
+ }),
156
+ readOnly: false,
157
+ async run(input, session) {
158
+ const { out, ...request } = input;
159
+ const { entries } = await session.cpuHistory(request);
160
+ if (out === undefined) return { entries: entries.map((entry) => ({ ...entry, address: formatC64Address(entry.address) })) };
161
+ const byte = (value: number) => value.toString(16).padStart(2, "0");
162
+ const lines = entries.map((entry) => {
163
+ const raster = entry.rasterLine === undefined ? "" : ` line ${entry.rasterLine} cycle ${entry.rasterCycle ?? 0}`;
164
+ return `${formatC64Address(entry.address)} ${spaced(entry.bytes).padEnd(9)} ${entry.text.padEnd(16)} a=${byte(entry.a)} x=${byte(entry.x)} y=${byte(entry.y)} sp=${byte(entry.sp)}${raster}`;
165
+ });
166
+ return { count: entries.length, ...writeOut(out, `${lines.join("\n")}\n`) };
167
+ },
168
+ });
169
+
170
+ export const c64Backtrace = defineTool({
171
+ name: "c64_backtrace",
172
+ title: "Call backtrace",
173
+ description:
174
+ `Get the chain of subroutine calls that leads to the current instruction, most recent first, at most depth calls (1 to ${MAX_BACKTRACE}, default 16). ` +
175
+ "Each frame gives the entry address of the routine and the address it returns to. " +
176
+ "The chain comes from the JSR return addresses on the stack, so it is a best estimate. Interrupts are not in the chain.",
177
+ inputSchema: z.object({ depth: z.number().int().min(1).max(MAX_BACKTRACE).default(16), space: SpaceInput }).strict(),
178
+ outputSchema: z.object({ frames: z.array(z.object({ address: AddressOutput, returnAddress: AddressOutput.optional() })) }),
179
+ readOnly: true,
180
+ async run(input, session) {
181
+ const { frames } = await session.backtrace(input);
182
+ return {
183
+ frames: frames.map((frame) => ({
184
+ address: formatC64Address(frame.address),
185
+ ...(frame.returnAddress === undefined ? {} : { returnAddress: formatC64Address(frame.returnAddress) }),
186
+ })),
187
+ };
188
+ },
189
+ });
190
+
191
+ export const c64Timing = defineTool({
192
+ name: "c64_timing",
193
+ title: "Cycle stopwatch",
194
+ description:
195
+ "A stopwatch that counts C64 CPU cycles. Action start sets it to zero. Action read gives the cycles since the last start, " +
196
+ "or since the session started, as a decimal string. A reset and c64_autostart start it again from the reset. " +
197
+ "A PAL frame has 19656 cycles and an NTSC frame 17095.",
198
+ inputSchema: z.object({ action: z.enum(TIMING_ACTIONS) }).strict(),
199
+ outputSchema: z.object({ started: z.boolean().optional(), cycles: z.string().regex(/^\d+$/).optional() }),
200
+ readOnly: false,
201
+ async run(input, session) {
202
+ return session.timing(input.action);
203
+ },
204
+ });
205
+
206
+ export const c64Profile = defineTool({
207
+ name: "c64_profile",
208
+ title: "Cycle profile",
209
+ description:
210
+ "Get the routines where the C64 CPU spent the most cycles since the session started or since the last c64_window. " +
211
+ "After c64_window or a snapshot restore, the emulator can give the cycles to a routine that ran before the restore, for example $fd50. Do not trust the routine addresses then. " +
212
+ `limit is the maximum number of routines (1 to ${MAX_PROFILE}, default 20). ` +
213
+ "Each entry gives the routine address, its total cycles with the routines it calls, and its own cycles. " +
214
+ "percent is the part of all cycles that the routine used itself. " +
215
+ "Cycle counts are decimal strings.",
216
+ inputSchema: z.object({ limit: z.number().int().min(1).max(MAX_PROFILE).default(20) }).strict(),
217
+ outputSchema: z.object({
218
+ entries: z.array(z.object({ address: AddressOutput, totalCycles: z.string(), selfCycles: z.string(), percent: z.number() })),
219
+ }),
220
+ readOnly: true,
221
+ async run(input, session) {
222
+ const { entries } = await session.profile(input.limit);
223
+ return { entries: entries.map((entry) => ({ ...entry, address: formatC64Address(entry.address) })) };
224
+ },
225
+ });
226
+
227
+ /** The most code ranges a memmap summary lists. */
228
+ const MAX_SUMMARY_RANGES = 64;
229
+
230
+ /** Where an access happened: RAM, ROM or I/O. */
231
+ function sources(flags: number): Array<"ram" | "rom" | "io"> {
232
+ const where: Array<"ram" | "rom" | "io"> = [];
233
+ if (flags & (COVERAGE.ramRead | COVERAGE.ramWrite | COVERAGE.ramExecute)) where.push("ram");
234
+ if (flags & (COVERAGE.romRead | COVERAGE.romExecute)) where.push("rom");
235
+ if (flags & (COVERAGE.ioRead | COVERAGE.ioWrite | COVERAGE.ioExecute)) where.push("io");
236
+ return where;
237
+ }
238
+
239
+ const count = (map: Uint8Array, bit: number) => map.reduce((sum, flags) => sum + ((flags & bit) === 0 ? 0 : 1), 0);
240
+ const RangeOutput = z.object({ start: AddressOutput, end: AddressOutput });
241
+ const showRanges = (ranges: CodeRange[]) => ranges.slice(0, MAX_SUMMARY_RANGES).map((range) => ({ start: formatC64Address(range.start), end: formatC64Address(range.end) }));
242
+
243
+ export const c64Memmap = defineTool({
244
+ name: "c64_memmap",
245
+ title: "Memory access map",
246
+ description:
247
+ "Get how the C64 CPU used memory since the session started or since the last clear. RAM, ROM and I/O are apart. " +
248
+ "Action read gives ranges of addresses with the same access (execute, read, write) from start to end. Each range also tells if the access was in RAM, ROM or I/O. " +
249
+ `It gives at most maxRanges ranges (1 to ${MAX_MEMMAP_RANGES}, default 256). Addresses the CPU did not touch are not in the result. ` +
250
+ "If more ranges follow, the result also gives truncated: true and next. Read again with start set to next. " +
251
+ "With out, read writes a coverage map to that project file, one byte for each address from start to end. " +
252
+ "Bits 0 to 2 are RAM read, write and execute. Bits 3 and 4 are ROM read and execute. Bits 5 to 7 are I/O read, write and execute. " +
253
+ "Execute is set only on the first byte of each executed instruction. " +
254
+ "Action summary gives the counts for RAM, ROM and I/O. It also gives the executed code in RAM with its operand bytes, and the RAM that was written and executed. " +
255
+ `Each list has at most ${MAX_SUMMARY_RANGES} ranges. ` +
256
+ "Action clear sets all access records to zero.",
257
+ inputSchema: z
258
+ .object({
259
+ action: z.enum(["read", "summary", "clear"]),
260
+ start: AddressInput.optional().describe("read and summary. Default $0000"),
261
+ end: AddressInput.optional().describe("read and summary. Default $ffff"),
262
+ maxRanges: z.number().int().min(1).max(MAX_MEMMAP_RANGES).optional().describe("read without out only. Default 256"),
263
+ out: OutInput.optional().describe("read only: write a coverage map to this project file, one byte for each address. An existing file is overwritten"),
264
+ })
265
+ .strict(),
266
+ outputSchema: z.object({
267
+ ranges: z.array(z.object({ start: AddressOutput, end: AddressOutput, execute: z.boolean(), read: z.boolean(), write: z.boolean(), in: z.array(z.enum(["ram", "rom", "io"])) })).optional(),
268
+ truncated: z.boolean().optional(),
269
+ next: AddressOutput.optional(),
270
+ ram: z.object({ executed: z.number().int(), read: z.number().int(), written: z.number().int() }).optional(),
271
+ rom: z.object({ executed: z.number().int(), read: z.number().int() }).optional(),
272
+ io: z.object({ executed: z.number().int(), read: z.number().int(), written: z.number().int() }).optional(),
273
+ code: z.array(RangeOutput).optional(),
274
+ codeTruncated: z.boolean().optional(),
275
+ writtenAndExecuted: z.array(RangeOutput).optional(),
276
+ writtenAndExecutedTruncated: z.boolean().optional(),
277
+ cleared: z.boolean().optional(),
278
+ ...WrittenOutput,
279
+ }),
280
+ readOnly: false,
281
+ async run(input, session) {
282
+ const { action, ...fields } = input;
283
+ if (action === "clear") {
284
+ requireFields("clear", fields, []);
285
+ return session.memmap({ action: "clear" });
286
+ }
287
+ const start = fields.start ?? 0x0000;
288
+ const end = fields.end ?? 0xffff;
289
+ if (end < start) throw new WireFailure("invalid-input", "end must not be before start.");
290
+ if (action === "summary") {
291
+ requireFields("summary", fields, ["start", "end"]);
292
+ const map = await readCoverage(session, start, end);
293
+ // The opcodes give the instruction lengths: RAM as it is now.
294
+ const parts: Buffer[] = [];
295
+ for (let at = 0; at < 0x10000; at += MAX_MEMORY_READ) parts.push(Buffer.from((await session.memoryRead({ address: at, size: MAX_MEMORY_READ, space: "c64", view: "ram" })).data, "hex"));
296
+ const memory = Buffer.concat(parts);
297
+ const code = executedRamCode(map, start, memory);
298
+ const written = executedRamCode(map.map((flags) => ((flags & COVERAGE.ramWrite) === 0 ? 0 : flags)), start, memory);
299
+ return {
300
+ ram: { executed: count(map, COVERAGE.ramExecute), read: count(map, COVERAGE.ramRead), written: count(map, COVERAGE.ramWrite) },
301
+ rom: { executed: count(map, COVERAGE.romExecute), read: count(map, COVERAGE.romRead) },
302
+ io: { executed: count(map, COVERAGE.ioExecute), read: count(map, COVERAGE.ioRead), written: count(map, COVERAGE.ioWrite) },
303
+ code: showRanges(code),
304
+ ...(code.length > MAX_SUMMARY_RANGES ? { codeTruncated: true } : {}),
305
+ writtenAndExecuted: showRanges(written),
306
+ ...(written.length > MAX_SUMMARY_RANGES ? { writtenAndExecutedTruncated: true } : {}),
307
+ };
308
+ }
309
+ if (fields.out !== undefined) {
310
+ requireFields("read", fields, ["start", "end", "out"]);
311
+ return writeOut(fields.out, await readCoverage(session, start, end));
312
+ }
313
+ requireFields("read", fields, ["start", "end", "maxRanges"]);
314
+ const result = await session.memmap({ action: "read", start, end, maxRanges: fields.maxRanges ?? 256 });
315
+ const ranges = result.ranges.map((range) => ({
316
+ start: formatC64Address(range.start),
317
+ end: formatC64Address(range.end),
318
+ execute: (range.flags & EXECUTE) !== 0,
319
+ read: (range.flags & READ) !== 0,
320
+ write: (range.flags & WRITE) !== 0,
321
+ in: sources(range.flags),
322
+ }));
323
+ return result.next === undefined ? { ranges } : { ranges, truncated: true, next: formatC64Address(result.next) };
324
+ },
325
+ });
326
+
327
+ export const debugTools = [c64Breakpoint, c64Watchpoint, c64CpuHistory, c64Backtrace, c64Profile, c64Memmap, c64Timing];
@@ -0,0 +1,117 @@
1
+ // Execution tools: c64_execution and c64_run_until.
2
+
3
+ import { z } from "zod";
4
+
5
+ import { formatC64Address } from "../../c64.ts";
6
+ import { COMPARISONS, EXECUTION_ACTIONS, MAX_EXECUTION_COUNT, MAX_TIMEOUT_FRAMES, RUN_STATES, SPACES, STOP_REASONS, type RunTarget } from "../../protocol/vice.ts";
7
+ import { AddressInput, AddressOutput, Byte, ConditionInput, defineTool, MemoryViewInput, SpaceInput, toCondition } from "../server.ts";
8
+
9
+ export const c64Execution = defineTool({
10
+ name: "c64_execution",
11
+ title: "Control C64 execution",
12
+ description:
13
+ "Control the CPU. pause stops the CPU and gives the program counter (pc). resume lets the CPU run. " +
14
+ "step executes count instructions (default 1) and goes into subroutines. " +
15
+ "next executes count instructions (default 1) and treats a JSR and its subroutine as one instruction. " +
16
+ "until-return runs until the current subroutine returns (after the next RTS or RTI). " +
17
+ "advance-frames runs exactly count video frames (count is necessary). It stops at the same raster position, count frames later. " +
18
+ "A breakpoint or watchpoint can stop it before that. advancedFrames tells how many frames ran. " +
19
+ "step, next, until-return and advance-frames first stop a running CPU. Use count only with step, next and advance-frames. " +
20
+ "step, next and until-return work only in space c64. For the drive, use c64_run_until with an address in space drive8.",
21
+ inputSchema: z
22
+ .object({
23
+ action: z.enum(EXECUTION_ACTIONS),
24
+ count: z
25
+ .number()
26
+ .int()
27
+ .min(1)
28
+ .max(MAX_EXECUTION_COUNT)
29
+ .optional()
30
+ .describe(`step and next: instructions (default 1). advance-frames: frames (necessary). 1 to ${MAX_EXECUTION_COUNT}`),
31
+ space: SpaceInput,
32
+ })
33
+ .strict(),
34
+ outputSchema: z.object({
35
+ state: z.enum(RUN_STATES),
36
+ pc: AddressOutput.optional(),
37
+ executed: z.number().int().min(0).max(MAX_EXECUTION_COUNT).optional().describe("instructions executed by step or next"),
38
+ advancedFrames: z.number().int().min(0).max(MAX_EXECUTION_COUNT).optional().describe("frames advance-frames ran"),
39
+ }),
40
+ readOnly: false,
41
+ async run(input, session) {
42
+ const params = { action: input.action, space: input.space, ...(input.count === undefined ? {} : { count: input.count }) };
43
+ const result = await session.execution(params);
44
+ return {
45
+ state: result.state,
46
+ ...(result.pc === undefined ? {} : { pc: formatC64Address(result.pc) }),
47
+ ...(result.executed === undefined ? {} : { executed: result.executed }),
48
+ ...(result.advancedFrames === undefined ? {} : { advancedFrames: result.advancedFrames }),
49
+ };
50
+ },
51
+ });
52
+
53
+ const RunTargetInput = z.discriminatedUnion("kind", [
54
+ z
55
+ .object({
56
+ kind: z.literal("address"),
57
+ address: AddressInput,
58
+ space: SpaceInput,
59
+ condition: ConditionInput.optional().describe("stop at the address only when this is true"),
60
+ })
61
+ .strict(),
62
+ z
63
+ .object({
64
+ kind: z.literal("memory"),
65
+ address: AddressInput,
66
+ operator: z.enum(COMPARISONS),
67
+ value: Byte,
68
+ space: SpaceInput,
69
+ view: MemoryViewInput,
70
+ })
71
+ .strict(),
72
+ z
73
+ .object({
74
+ kind: z.literal("raster"),
75
+ line: z.number().int().min(0).describe("raster line: PAL 0-311, NTSC 0-262"),
76
+ cycle: z.number().int().min(0).optional().describe("cycle in the line: PAL 0-62, NTSC 0-64. Default 0"),
77
+ })
78
+ .strict(),
79
+ ]);
80
+
81
+ export const c64RunUntil = defineTool({
82
+ name: "c64_run_until",
83
+ title: "Run the C64 until a target",
84
+ description:
85
+ "Run the CPU until a target occurs, then stop. " +
86
+ "Target kind address stops before the instruction at an address executes. An optional condition must also be true. " +
87
+ "Kind memory compares the byte at address to value with operator. It stops after a write that makes this comparison true. " +
88
+ "Kind raster stops at the first instruction at or after a raster line and cycle. If the machine is there now, it stops at the next frame's pass. " +
89
+ `timeoutFrames (1 to ${MAX_TIMEOUT_FRAMES}, default 3000) limits the run in video frames. ` +
90
+ "reached tells if the target occurred. stopReason tells why the CPU stopped: target, breakpoint, watchpoint, jam (a JAM opcode) or timeout. " +
91
+ "space tells which CPU stopped: c64, or drive8 for a stop in the disk drive. pc is the address in that space. " +
92
+ "The CPU is always stopped after this tool.",
93
+ inputSchema: z
94
+ .object({
95
+ target: RunTargetInput,
96
+ timeoutFrames: z.number().int().min(1).max(MAX_TIMEOUT_FRAMES).default(3000),
97
+ })
98
+ .strict(),
99
+ outputSchema: z.object({ reached: z.boolean(), stopReason: z.enum(STOP_REASONS), state: z.enum(RUN_STATES), pc: AddressOutput, space: z.enum(SPACES) }),
100
+ readOnly: false,
101
+ async run(input, session) {
102
+ const { target: given } = input;
103
+ let target: RunTarget;
104
+ if (given.kind === "address") {
105
+ target = { kind: "address", address: given.address, space: given.space };
106
+ if (given.condition !== undefined) target.condition = toCondition(given.condition);
107
+ } else if (given.kind === "memory") {
108
+ target = given;
109
+ } else {
110
+ target = given.cycle === undefined ? { kind: "raster", line: given.line } : { kind: "raster", line: given.line, cycle: given.cycle };
111
+ }
112
+ const result = await session.runUntil({ target, timeoutFrames: input.timeoutFrames });
113
+ return { ...result, pc: formatC64Address(result.pc) };
114
+ },
115
+ });
116
+
117
+ export const executionTools = [c64Execution, c64RunUntil];
@@ -0,0 +1,72 @@
1
+ // Input tools: c64_keyboard and c64_joystick.
2
+
3
+ import { z } from "zod";
4
+
5
+ import { textToPetscii } from "../../c64.ts";
6
+ import { WireFailure } from "../../protocol/messages.ts";
7
+ import { JOYSTICK_DIRECTIONS, MAX_KEYBOARD_BYTES } from "../../protocol/vice.ts";
8
+ import { defineTool } from "../server.ts";
9
+
10
+ const EMULATOR_FLAW = z.string().optional().describe("a known flaw of the emulator version that affects input, and the version that fixes it");
11
+
12
+ export const c64Keyboard = defineTool({
13
+ name: "c64_keyboard",
14
+ title: "Type on the C64 keyboard",
15
+ description:
16
+ `Type up to ${MAX_KEYBOARD_BYTES} keys into the C64 keyboard queue. The C64 reads them while it runs. ` +
17
+ "Only a program that reads keys through the KERNAL sees them. A program that reads the keyboard hardware directly does not see them. Many games do that. " +
18
+ "No command presses one key of the keyboard matrix: for one specific key, ask the user to press it in the window. " +
19
+ 'Mode text types text, for example "RUN\\n". Letters of either case type the normal (unshifted) key. A line break types RETURN. ' +
20
+ "Mode petscii types PETSCII codes 1 to 255 in bytes, for example [147] to clear the screen. " +
21
+ "This does not change the run state of the machine. emulatorFlaw in the result names a known flaw of the emulator version that affects input, and the version that fixes it.",
22
+ inputSchema: z
23
+ .object({
24
+ mode: z.enum(["text", "petscii"]),
25
+ text: z.string().min(1).optional().describe("mode text: the text to type"),
26
+ bytes: z.array(z.number().int().min(1).max(255)).min(1).optional().describe("mode petscii: PETSCII codes to type"),
27
+ })
28
+ .strict(),
29
+ outputSchema: z.object({ queuedBytes: z.number().int().min(1).max(MAX_KEYBOARD_BYTES), emulatorFlaw: EMULATOR_FLAW }),
30
+ readOnly: false,
31
+ async run(input, session) {
32
+ let petscii: Uint8Array;
33
+ if (input.mode === "text") {
34
+ if (input.text === undefined || input.bytes !== undefined) throw new WireFailure("invalid-input", "Mode text takes text and no bytes.");
35
+ try {
36
+ petscii = textToPetscii(input.text);
37
+ } catch (error) {
38
+ throw new WireFailure("invalid-input", `${(error as Error).message}. Use mode petscii for other keys.`);
39
+ }
40
+ } else {
41
+ if (input.bytes === undefined || input.text !== undefined) throw new WireFailure("invalid-input", "Mode petscii takes bytes and no text.");
42
+ petscii = Uint8Array.from(input.bytes);
43
+ }
44
+ if (petscii.length > MAX_KEYBOARD_BYTES) {
45
+ throw new WireFailure("invalid-input", `The keyboard queue takes at most ${MAX_KEYBOARD_BYTES} keys at once. This input has ${petscii.length} keys.`);
46
+ }
47
+ return session.keyboard(petscii);
48
+ },
49
+ });
50
+
51
+ export const c64Joystick = defineTool({
52
+ name: "c64_joystick",
53
+ title: "Move a joystick",
54
+ description:
55
+ "Set the joystick in control port 1 or 2: a direction (or center) and the fire button. " +
56
+ "The C64 sees this state until you change it. To release the joystick, set direction center and fire false. " +
57
+ "Most games read port 2. This does not change the run state of the machine. emulatorFlaw in the result names a known flaw of the emulator version that affects input, and the version that fixes it.",
58
+ inputSchema: z
59
+ .object({
60
+ port: z.union([z.literal(1), z.literal(2)]).describe("control port 1 or 2"),
61
+ direction: z.enum(JOYSTICK_DIRECTIONS),
62
+ fire: z.boolean().default(false),
63
+ })
64
+ .strict(),
65
+ outputSchema: z.object({ port: z.union([z.literal(1), z.literal(2)]), direction: z.enum(JOYSTICK_DIRECTIONS), fire: z.boolean(), emulatorFlaw: EMULATOR_FLAW }),
66
+ readOnly: false,
67
+ async run(input, session) {
68
+ return session.joystick(input);
69
+ },
70
+ });
71
+
72
+ export const inputTools = [c64Keyboard, c64Joystick];
@@ -0,0 +1,100 @@
1
+ // Machine tools: c64_status, c64_reset, c64_warp and c64_window.
2
+
3
+ import { z } from "zod";
4
+
5
+ import { formatC64Address } from "../../c64.ts";
6
+ import { VIDEO_STANDARDS } from "../../protocol/messages.ts";
7
+ import { RESET_MODES, RUN_STATES, WINDOW_ACTIONS } from "../../protocol/vice.ts";
8
+ import { AddressOutput, defineTool } from "../server.ts";
9
+
10
+ export const c64Status = defineTool({
11
+ name: "c64_status",
12
+ title: "C64 status",
13
+ description:
14
+ "Get the state of the C64. The result tells if the CPU runs or has stopped, the video standard (pal or ntsc), " +
15
+ "and if warp mode is on. It also tells if the emulator window is open (window). " +
16
+ "When the CPU has stopped, the result also gives the program counter (pc). " +
17
+ "cartridge is true while a cartridge from c64_autostart is attached: a reset keeps it, and an autostart of another file detaches it.",
18
+ inputSchema: z.object({}).strict(),
19
+ outputSchema: z.object({
20
+ state: z.enum(RUN_STATES),
21
+ videoStandard: z.enum(VIDEO_STANDARDS),
22
+ warp: z.boolean(),
23
+ window: z.boolean().describe("true when the emulator window is open (c64_window)"),
24
+ pc: AddressOutput.optional().describe("program counter. Only when the CPU is stopped"),
25
+ cartridge: z.boolean().describe("true while a cartridge from c64_autostart is attached"),
26
+ }),
27
+ readOnly: true,
28
+ async run(_input, session) {
29
+ const status = await session.status();
30
+ return {
31
+ state: status.state,
32
+ videoStandard: status.videoStandard,
33
+ warp: status.warp,
34
+ window: status.window,
35
+ cartridge: status.cartridge,
36
+ ...(status.pc === undefined ? {} : { pc: formatC64Address(status.pc) }),
37
+ };
38
+ },
39
+ });
40
+
41
+ export const c64Window = defineTool({
42
+ name: "c64_window",
43
+ title: "Emulator window",
44
+ description:
45
+ "Show the C64 in a window on the host, or hide it again. The emulator has no window until you open one. " +
46
+ "Open the window when the user must type, play or look at the C64. Then tell the user. Wait until the user tells you that they finished. " +
47
+ "Then close the window. The machine, the disk in drive 8, breakpoints, watchpoints, joysticks, warp mode and c64_timing go along. " +
48
+ "The run state of the machine does not change. The result names the data that does not go along (notCarried). " +
49
+ "If the user closes the window, the machine goes back to the state of when the window opened.",
50
+ inputSchema: z.object({ action: z.enum(WINDOW_ACTIONS).describe("open shows the window. close hides it") }).strict(),
51
+ outputSchema: z.object({
52
+ window: z.boolean().describe("true when the window is open"),
53
+ state: z.enum(RUN_STATES),
54
+ notCarried: z.array(z.string()).describe("emulator data that the move cleared. Empty when nothing moved"),
55
+ }),
56
+ readOnly: false,
57
+ async run(input, session) {
58
+ return session.window(input.action);
59
+ },
60
+ });
61
+
62
+ export const c64Reset = defineTool({
63
+ name: "c64_reset",
64
+ title: "Reset the C64",
65
+ description:
66
+ "Reset the C64. Mode soft is the reset button. Mode hard is a power cycle that also resets the disk drive. " +
67
+ "With run false (the default), the CPU stops at the first instruction of the reset routine, and the result gives that program counter (pc). " +
68
+ "With run true, the C64 starts normally. Breakpoints and watchpoints stay.",
69
+ inputSchema: z
70
+ .object({
71
+ mode: z.enum(RESET_MODES).describe("soft is the reset button. hard is a power cycle"),
72
+ run: z.boolean().default(false).describe("false stops at the reset routine. true lets the C64 start"),
73
+ })
74
+ .strict(),
75
+ outputSchema: z.object({
76
+ state: z.enum(RUN_STATES),
77
+ pc: AddressOutput.optional().describe("program counter. Only when run is false"),
78
+ }),
79
+ readOnly: false,
80
+ async run(input, session) {
81
+ const result = await session.reset(input);
82
+ return result.pc === undefined ? { state: result.state } : { state: result.state, pc: formatC64Address(result.pc) };
83
+ },
84
+ });
85
+
86
+ export const c64Warp = defineTool({
87
+ name: "c64_warp",
88
+ title: "Warp mode",
89
+ description:
90
+ "Turn warp mode on or off. In warp mode the C64 runs as fast as the host can run it. " +
91
+ "Warp mode does not change what the C64 does. This does not change the run state of the machine.",
92
+ inputSchema: z.object({ enabled: z.boolean().describe("true turns warp mode on") }).strict(),
93
+ outputSchema: z.object({ enabled: z.boolean() }),
94
+ readOnly: false,
95
+ async run(input, session) {
96
+ return session.warp(input.enabled);
97
+ },
98
+ });
99
+
100
+ export const machineTools = [c64Status, c64Reset, c64Warp, c64Window];