@henols/c64-re-tools 2.0.0-rc.9 → 2.0.0
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/README.md +97 -116
- package/package.json +1 -1
- package/skills/c64-assembler/SKILL.md +22 -6
- package/skills/c64-assembler/scripts/assemble.ts +42 -14
- package/skills/c64-assembler/scripts/lib/compare.ts +48 -0
- package/skills/c64-basic/SKILL.md +10 -1
- package/skills/c64-basic/scripts/basic.ts +3 -3
- package/skills/c64-disk/SKILL.md +13 -4
- package/skills/c64-disk/scripts/disk.ts +7 -7
- package/skills/c64-disk/scripts/{containers.ts → lib/containers.ts} +2 -2
- package/skills/c64-docs/SKILL.md +138 -0
- package/skills/c64-docs/references/page-types.md +141 -0
- package/skills/c64-docs/references/style.md +43 -0
- package/skills/c64-docs/scripts/generate.ts +109 -0
- package/skills/c64-docs/scripts/lib/links.ts +167 -0
- package/skills/c64-docs/scripts/lib/markdown.ts +79 -0
- package/skills/c64-docs/scripts/lib/reference.ts +406 -0
- package/skills/c64-docs/scripts/lib/source-map.ts +81 -0
- package/skills/c64-docs/scripts/link.ts +239 -0
- package/skills/c64-emulator/SKILL.md +16 -5
- package/skills/c64-knowledge/SKILL.md +140 -18
- package/skills/c64-knowledge/scripts/knowledge.ts +737 -112
- package/skills/c64-knowledge/scripts/lib/listing.ts +162 -0
- package/skills/c64-memory-map/SKILL.md +11 -2
- package/skills/c64-memory-map/scripts/memmap.ts +2 -2
- package/skills/c64-provenance/SKILL.md +66 -11
- package/skills/c64-provenance/scripts/{compare.ts → lib/compare.ts} +3 -3
- package/skills/c64-provenance/scripts/lib/library.ts +584 -0
- package/skills/c64-provenance/scripts/{runtime.ts → lib/runtime.ts} +2 -2
- package/skills/c64-provenance/scripts/{separate.ts → lib/separate.ts} +3 -3
- package/skills/c64-provenance/scripts/provenance.ts +242 -40
- package/skills/c64-reverse-engineering/SKILL.md +51 -26
- package/skills/c64-source/SKILL.md +76 -28
- package/skills/c64-source/scripts/generate.ts +425 -127
- package/skills/c64-source/scripts/{loader.ts → lib/loader.ts} +84 -25
- package/skills/c64-source/scripts/lib/source.ts +1121 -0
- package/skills/c64-static-analysis/SKILL.md +35 -18
- package/skills/c64-static-analysis/scripts/analyze.ts +184 -55
- package/skills/c64-static-analysis/scripts/{coverage.ts → lib/coverage.ts} +19 -25
- package/skills/c64-static-analysis/scripts/{findings.ts → lib/findings.ts} +6 -2
- package/skills/c64-static-analysis/scripts/{idioms.ts → lib/idioms.ts} +21 -12
- package/skills/c64-static-analysis/scripts/{instructions.ts → lib/instructions.ts} +1 -1
- package/skills/c64-static-analysis/scripts/lib/macros.ts +233 -0
- package/skills/c64-static-analysis/scripts/lib/pitfalls.ts +68 -0
- package/skills/c64-static-analysis/scripts/lib/pointers.ts +108 -0
- package/skills/c64-testing/SKILL.md +36 -7
- package/skills/c64-testing/references/scenario-format.md +12 -8
- package/skills/c64-testing/scripts/checklist.ts +3 -3
- package/skills/c64-testing/scripts/diff.ts +167 -0
- package/skills/c64-testing/scripts/lib/diff.ts +70 -0
- package/skills/c64-testing/scripts/{scenario.ts → lib/scenario.ts} +45 -34
- package/skills/c64-testing/scripts/run.ts +73 -21
- package/skills/c64-testing/scripts/test.ts +5 -5
- package/skills/c64-unpacker/SKILL.md +23 -9
- package/skills/c64-unpacker/scripts/{evidence.ts → lib/evidence.ts} +1 -1
- package/skills/c64-unpacker/scripts/{loads.ts → lib/loads.ts} +4 -4
- package/skills/c64-unpacker/scripts/{machine.ts → lib/machine.ts} +4 -4
- package/skills/c64-unpacker/scripts/{stages.ts → lib/stages.ts} +17 -5
- package/skills/c64-unpacker/scripts/unpack.ts +41 -24
- package/src/c64.ts +103 -5
- package/src/cli/cleanup.ts +1 -1
- package/src/cli/main.ts +7 -4
- package/src/cli/status.ts +70 -3
- package/src/graphics.ts +158 -0
- package/src/host/main.ts +18 -4
- package/src/host/server.ts +10 -3
- package/src/host/tools/petcat.ts +1 -1
- package/src/host/tools/status.ts +1 -1
- package/src/host/vice/adapter.ts +4 -3
- package/src/host/vice/process.ts +2 -2
- package/src/host/vice/screen.ts +1 -42
- package/src/host/vice/session.ts +196 -23
- package/src/host-client/autostart.ts +1 -1
- package/src/host-client/connect.ts +1 -1
- package/src/host-client/coverage.ts +2 -29
- package/src/host-client/image.ts +66 -0
- package/src/host-client/steps.ts +9 -4
- package/src/host-client/tools.ts +1 -0
- package/src/host-client/transfer.ts +39 -11
- package/src/host-client/vice-session.ts +33 -16
- package/src/knowledge/conflicts.ts +161 -0
- package/src/knowledge/database.ts +150 -38
- package/src/knowledge/expression.ts +209 -0
- package/src/knowledge/graph.ts +104 -0
- package/src/knowledge/history.ts +70 -22
- package/src/knowledge/import.ts +64 -55
- package/src/knowledge/join.ts +211 -0
- package/src/knowledge/program.ts +266 -0
- package/src/knowledge/read.ts +258 -27
- package/src/knowledge/schema.ts +199 -75
- package/src/knowledge/text.ts +415 -0
- package/src/knowledge/write.ts +740 -57
- package/src/mcp/main.ts +1 -1
- package/src/mcp/output.ts +15 -8
- package/src/mcp/server.ts +3 -1
- package/src/mcp/tools/debug.ts +97 -16
- package/src/mcp/tools/execution.ts +38 -5
- package/src/mcp/tools/machine.ts +11 -2
- package/src/mcp/tools/media.ts +18 -13
- package/src/mcp/tools/memory.ts +17 -8
- package/src/mcp/tools/video.ts +9 -9
- package/src/native/discover.ts +2 -2
- package/src/native/ghidra/analyze.ts +2 -2
- package/src/native/ghidra/index.ts +1 -1
- package/src/native/ghidra/language/c64rt_6510.slaspec +4 -1
- package/src/native/ghidra/scripts/C64Export.java +1 -1
- package/src/native/ghidra/scripts/C64Prepare.java +7 -1
- package/src/native/types.ts +14 -8
- package/{skills/c64-source/scripts → src}/opcodes.ts +75 -3
- package/src/platform.ts +70 -7
- package/src/project.ts +180 -3
- package/src/protocol/messages.ts +1 -1
- package/src/protocol/tools.ts +2 -16
- package/src/protocol/vice.ts +124 -15
- package/src/script.ts +46 -7
- package/skills/c64-assembler/scripts/compare.ts +0 -84
- package/skills/c64-source/scripts/source.ts +0 -595
package/src/mcp/main.ts
CHANGED
|
@@ -46,7 +46,7 @@ function videoStandard(env: NodeJS.ProcessEnv): VideoStandard {
|
|
|
46
46
|
* One session at a time (1 MCP = 1 session = 1 VICE). It opens at start; if
|
|
47
47
|
* that fails (for example the host was not running yet), the next tool call
|
|
48
48
|
* tries again. When the host connection is lost, the next call opens a new
|
|
49
|
-
* session
|
|
49
|
+
* session. A lost session that the emulator answered first gives its
|
|
50
50
|
* machine-state-lost once, unless a pending call got it already. Close stops
|
|
51
51
|
* an open that is still waiting for the host at once.
|
|
52
52
|
*/
|
package/src/mcp/output.ts
CHANGED
|
@@ -1,14 +1,21 @@
|
|
|
1
|
-
// Tool results written to a project file instead of the context
|
|
2
|
-
// MCP server runs on the agent's machine
|
|
3
|
-
//
|
|
1
|
+
// Tool results written to a project file instead of the context. The
|
|
2
|
+
// MCP server runs on the agent's machine, so it writes the file itself; the
|
|
3
|
+
// host never sees the path. The project is the folder that `project` names,
|
|
4
|
+
// or the one that the project rules find from the folder the agent started in.
|
|
4
5
|
|
|
5
6
|
import { createHash } from "node:crypto";
|
|
6
7
|
|
|
7
8
|
import { z } from "zod";
|
|
8
9
|
|
|
9
|
-
import { writeProjectFile } from "../host-client/transfer.ts";
|
|
10
|
+
import { projectFolder, writeProjectFile } from "../host-client/transfer.ts";
|
|
10
11
|
|
|
11
|
-
export const OutInput = z.string().min(1).describe("write the result to this file, relative to the project
|
|
12
|
+
export const OutInput = z.string().min(1).describe("write the result to this file, relative to the project folder, in place of returning it. An existing file is overwritten");
|
|
13
|
+
|
|
14
|
+
/** The project of a tool with a path. */
|
|
15
|
+
export const ProjectInput = z
|
|
16
|
+
.string()
|
|
17
|
+
.min(1)
|
|
18
|
+
.describe("the project folder, relative to the folder where the agent started. Give it when that folder is not in a project and has projects below it. The paths of this call are relative to the project folder");
|
|
12
19
|
|
|
13
20
|
/** What a result says in place of the bytes it wrote. */
|
|
14
21
|
export interface Written {
|
|
@@ -24,9 +31,9 @@ export const WrittenOutput = {
|
|
|
24
31
|
sha256: z.string().regex(/^[0-9a-f]{64}$/).optional(),
|
|
25
32
|
};
|
|
26
33
|
|
|
27
|
-
/** Writes `data` to the
|
|
28
|
-
export function writeOut(path: string, data: Uint8Array | string,
|
|
34
|
+
/** Writes `data` to the file `path` of the project that `project` names (or the project lookup finds), and gives what the result shows in its place. */
|
|
35
|
+
export function writeOut(path: string, data: Uint8Array | string, project: string | undefined): Written {
|
|
29
36
|
const bytes = typeof data === "string" ? Buffer.from(data, "utf8") : data;
|
|
30
|
-
writeProjectFile(path, bytes, { what: "out",
|
|
37
|
+
writeProjectFile(path, bytes, { what: "out", root: projectFolder(project) });
|
|
31
38
|
return { out: path, bytes: bytes.length, sha256: createHash("sha256").update(bytes).digest("hex") };
|
|
32
39
|
}
|
package/src/mcp/server.ts
CHANGED
|
@@ -41,6 +41,7 @@ export type ViceSessionApi = Pick<
|
|
|
41
41
|
| "snapshotExport"
|
|
42
42
|
| "snapshotRestore"
|
|
43
43
|
| "cpuHistory"
|
|
44
|
+
| "trace"
|
|
44
45
|
| "backtrace"
|
|
45
46
|
| "timing"
|
|
46
47
|
| "profile"
|
|
@@ -265,7 +266,7 @@ async function runCall(
|
|
|
265
266
|
try {
|
|
266
267
|
output = await tool.run(parsed.data, await session());
|
|
267
268
|
} catch (error) {
|
|
268
|
-
// A session that the emulator never answered had no machine state: the call goes again on a new session
|
|
269
|
+
// A session that the emulator never answered had no machine state: the call goes again on a new session.
|
|
269
270
|
if (!(error instanceof LostBeforeAnswer)) throw error;
|
|
270
271
|
output = await tool.run(parsed.data, await session());
|
|
271
272
|
}
|
|
@@ -300,6 +301,7 @@ const INSTRUCTIONS =
|
|
|
300
301
|
"These tools control one live Commodore 64 emulator that belongs to this session. " +
|
|
301
302
|
"Addresses are four hex digits after a dollar sign, for example $c000. " +
|
|
302
303
|
"Memory data is lowercase hex without separators. " +
|
|
304
|
+
"Other register values and chip values are decimal numbers. " +
|
|
303
305
|
"A read does not change the run state of the machine.";
|
|
304
306
|
|
|
305
307
|
export function createMcpServer(options: { tools: readonly ToolDefinition[]; session: SessionSource; log?: (line: string) => void }): Server {
|
package/src/mcp/tools/debug.ts
CHANGED
|
@@ -5,10 +5,10 @@ import { z } from "zod";
|
|
|
5
5
|
|
|
6
6
|
import { COVERAGE, executedRamCode, EXECUTE, formatC64Address, READ, WRITE, type CodeRange } from "../../c64.ts";
|
|
7
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";
|
|
8
|
+
import { CHECKPOINT_ACTIONS, MAX_BACKTRACE, MAX_HISTORY, MAX_TRACE_FRAMES, MAX_TRACE_HITS, MAX_TRACE_POINTS, TRACE_KINDS, TRACE_ENDS, 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
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
|
|
10
|
+
import { OutInput, ProjectInput, writeOut, WrittenOutput } from "../output.ts";
|
|
11
|
+
import { AddressInput, AddressOutput, Byte, ConditionInput, ConditionOutput, defineTool, HexData, requireFields, showCondition, SpaceInput, toCondition } from "../server.ts";
|
|
12
12
|
import { spaced } from "./memory.ts";
|
|
13
13
|
|
|
14
14
|
/** No default here: a filled-in default would count as a field the other actions refuse. */
|
|
@@ -135,7 +135,7 @@ export const c64CpuHistory = defineTool({
|
|
|
135
135
|
"In space c64, it also gives the raster line and cycle when the instruction started. " +
|
|
136
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
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(),
|
|
138
|
+
inputSchema: z.object({ limit: z.number().int().min(1).max(MAX_HISTORY).default(50), space: SpaceInput, out: OutInput.optional(), project: ProjectInput.optional() }).strict(),
|
|
139
139
|
outputSchema: z.object({
|
|
140
140
|
entries: z.array(
|
|
141
141
|
z.object({
|
|
@@ -155,7 +155,8 @@ export const c64CpuHistory = defineTool({
|
|
|
155
155
|
}),
|
|
156
156
|
readOnly: false,
|
|
157
157
|
async run(input, session) {
|
|
158
|
-
const { out, ...request } = input;
|
|
158
|
+
const { out, project, ...request } = input;
|
|
159
|
+
if (out === undefined && project !== undefined) throw new WireFailure("invalid-input", "project is used only with out.");
|
|
159
160
|
const { entries } = await session.cpuHistory(request);
|
|
160
161
|
if (out === undefined) return { entries: entries.map((entry) => ({ ...entry, address: formatC64Address(entry.address) })) };
|
|
161
162
|
const byte = (value: number) => value.toString(16).padStart(2, "0");
|
|
@@ -163,7 +164,71 @@ export const c64CpuHistory = defineTool({
|
|
|
163
164
|
const raster = entry.rasterLine === undefined ? "" : ` line ${entry.rasterLine} cycle ${entry.rasterCycle ?? 0}`;
|
|
164
165
|
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
|
});
|
|
166
|
-
return { count: entries.length, ...writeOut(out, `${lines.join("\n")}\n
|
|
167
|
+
return { count: entries.length, ...writeOut(out, `${lines.join("\n")}\n`, project) };
|
|
168
|
+
},
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
/** The hits that a trace result gives inline; out gives all of them. */
|
|
172
|
+
const TRACE_LISTED = 200;
|
|
173
|
+
|
|
174
|
+
export const c64Trace = defineTool({
|
|
175
|
+
name: "c64_trace",
|
|
176
|
+
title: "Trace accesses and instructions",
|
|
177
|
+
description:
|
|
178
|
+
"Run the machine and record each hit of the points. The program does not stop for you. " +
|
|
179
|
+
"A point is exec (an instruction at an address from start to end), read or write (an access from start to end). " +
|
|
180
|
+
`Give 1 to ${MAX_TRACE_POINTS} points. ` +
|
|
181
|
+
"Each hit gives the instruction (pc, bytes, text) and the registers a, x, y and sp before it. " +
|
|
182
|
+
"It also gives the frame from 0, and the raster line and cycle where the instruction started. " +
|
|
183
|
+
"A read or write hit also gives the address that the instruction used, and the byte there after the access. " +
|
|
184
|
+
`The trace ends after frames frames (1 to ${MAX_TRACE_FRAMES}), at maxHits hits (at most ${MAX_TRACE_HITS}), or at a breakpoint, a watchpoint or a JAM. ` +
|
|
185
|
+
`The result lists the first ${TRACE_LISTED} hits. With out, it writes all hits as JSON lines to that project file. ` +
|
|
186
|
+
"Each hit stops the machine for a short time. Thus a point with many hits in each frame makes the trace slow. Use a small range and a maxHits. " +
|
|
187
|
+
"The machine is stopped after the trace.",
|
|
188
|
+
inputSchema: z
|
|
189
|
+
.object({
|
|
190
|
+
points: z.array(z.object({ kind: z.enum(TRACE_KINDS), start: AddressInput, end: AddressInput.optional() }).strict()).min(1).max(MAX_TRACE_POINTS),
|
|
191
|
+
frames: z.number().int().min(1).max(MAX_TRACE_FRAMES),
|
|
192
|
+
maxHits: z.number().int().min(1).max(MAX_TRACE_HITS).default(MAX_TRACE_HITS),
|
|
193
|
+
out: OutInput.optional(),
|
|
194
|
+
project: ProjectInput.optional(),
|
|
195
|
+
})
|
|
196
|
+
.strict(),
|
|
197
|
+
outputSchema: z.object({
|
|
198
|
+
end: z.enum(TRACE_ENDS),
|
|
199
|
+
frames: z.number().int().min(0),
|
|
200
|
+
pc: AddressOutput,
|
|
201
|
+
count: z.number().int().min(0),
|
|
202
|
+
hits: z.array(
|
|
203
|
+
z.object({
|
|
204
|
+
point: z.number().int().min(0),
|
|
205
|
+
pc: AddressOutput,
|
|
206
|
+
bytes: HexData,
|
|
207
|
+
text: z.string(),
|
|
208
|
+
a: Byte,
|
|
209
|
+
x: Byte,
|
|
210
|
+
y: Byte,
|
|
211
|
+
sp: Byte,
|
|
212
|
+
frame: z.number().int().min(0),
|
|
213
|
+
rasterLine: z.number().int().min(0),
|
|
214
|
+
rasterCycle: z.number().int().min(0),
|
|
215
|
+
address: AddressOutput.optional(),
|
|
216
|
+
value: Byte.optional(),
|
|
217
|
+
}),
|
|
218
|
+
),
|
|
219
|
+
...WrittenOutput,
|
|
220
|
+
}),
|
|
221
|
+
readOnly: false,
|
|
222
|
+
async run(input, session) {
|
|
223
|
+
const { out, project, points, ...request } = input;
|
|
224
|
+
if (out === undefined && project !== undefined) throw new WireFailure("invalid-input", "project is used only with out.");
|
|
225
|
+
const ranges = points.map((point) => ({ kind: point.kind, start: point.start, end: point.end ?? point.start }));
|
|
226
|
+
if (ranges.some((point) => point.end < point.start)) throw new WireFailure("invalid-input", "A point end must not be before its start.");
|
|
227
|
+
const traced = await session.trace({ points: ranges, ...request });
|
|
228
|
+
const shown = traced.hits.map(({ address, ...hit }) => ({ ...hit, pc: formatC64Address(hit.pc), ...(address === undefined ? {} : { address: formatC64Address(address) }) }));
|
|
229
|
+
const summary = { end: traced.end, frames: traced.frames, pc: formatC64Address(traced.pc), count: traced.hits.length, hits: shown.slice(0, TRACE_LISTED) };
|
|
230
|
+
if (out === undefined) return summary;
|
|
231
|
+
return { ...summary, ...writeOut(out, `${shown.map((hit) => JSON.stringify(hit)).join("\n")}\n`, project) };
|
|
167
232
|
},
|
|
168
233
|
});
|
|
169
234
|
|
|
@@ -207,19 +272,33 @@ export const c64Profile = defineTool({
|
|
|
207
272
|
name: "c64_profile",
|
|
208
273
|
title: "Cycle profile",
|
|
209
274
|
description:
|
|
210
|
-
"
|
|
275
|
+
"Action read gets the routines where the C64 CPU spent the most cycles since the session started, the last clear or the last c64_window. " +
|
|
276
|
+
"Action clear sets all counts to zero, so that the next read measures only what runs after it. " +
|
|
277
|
+
"The emulator gives the cycles to the routine that a JSR called last. " +
|
|
278
|
+
"Code that the CPU enters with JMP or a branch counts for that routine. " +
|
|
211
279
|
"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). ` +
|
|
280
|
+
`limit is the maximum number of routines for read (1 to ${MAX_PROFILE}, default 20). ` +
|
|
213
281
|
"Each entry gives the routine address, its total cycles with the routines it calls, and its own cycles. " +
|
|
214
282
|
"percent is the part of all cycles that the routine used itself. " +
|
|
215
283
|
"Cycle counts are decimal strings.",
|
|
216
|
-
inputSchema: z
|
|
284
|
+
inputSchema: z
|
|
285
|
+
.object({
|
|
286
|
+
action: z.enum(["read", "clear"]).default("read"),
|
|
287
|
+
limit: z.number().int().min(1).max(MAX_PROFILE).optional().describe("read only. Default 20"),
|
|
288
|
+
})
|
|
289
|
+
.strict(),
|
|
217
290
|
outputSchema: z.object({
|
|
218
|
-
entries: z.array(z.object({ address: AddressOutput, totalCycles: z.string(), selfCycles: z.string(), percent: z.number() })),
|
|
291
|
+
entries: z.array(z.object({ address: AddressOutput, totalCycles: z.string(), selfCycles: z.string(), percent: z.number() })).optional(),
|
|
292
|
+
cleared: z.boolean().optional(),
|
|
219
293
|
}),
|
|
220
|
-
readOnly:
|
|
294
|
+
readOnly: false,
|
|
221
295
|
async run(input, session) {
|
|
222
|
-
const {
|
|
296
|
+
const { action, ...fields } = input;
|
|
297
|
+
if (action === "clear") {
|
|
298
|
+
requireFields("clear", fields, []);
|
|
299
|
+
return session.profile({ action: "clear" });
|
|
300
|
+
}
|
|
301
|
+
const { entries } = await session.profile({ action: "read", limit: fields.limit ?? 20 });
|
|
223
302
|
return { entries: entries.map((entry) => ({ ...entry, address: formatC64Address(entry.address) })) };
|
|
224
303
|
},
|
|
225
304
|
});
|
|
@@ -250,7 +329,8 @@ export const c64Memmap = defineTool({
|
|
|
250
329
|
"If more ranges follow, the result also gives truncated: true and next. Read again with start set to next. " +
|
|
251
330
|
"With out, read writes a coverage map to that project file, one byte for each address from start to end. " +
|
|
252
331
|
"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. " +
|
|
332
|
+
"Execute is set only on the first byte of each executed instruction. Thus read gives a new range at each instruction of code. For code, use summary or read with out. " +
|
|
333
|
+
"A read on a byte right after executed code, or at a return address, can be a dummy read that the CPU does not use. " +
|
|
254
334
|
"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
335
|
`Each list has at most ${MAX_SUMMARY_RANGES} ranges. ` +
|
|
256
336
|
"Action clear sets all access records to zero.",
|
|
@@ -261,6 +341,7 @@ export const c64Memmap = defineTool({
|
|
|
261
341
|
end: AddressInput.optional().describe("read and summary. Default $ffff"),
|
|
262
342
|
maxRanges: z.number().int().min(1).max(MAX_MEMMAP_RANGES).optional().describe("read without out only. Default 256"),
|
|
263
343
|
out: OutInput.optional().describe("read only: write a coverage map to this project file, one byte for each address. An existing file is overwritten"),
|
|
344
|
+
project: ProjectInput.optional().describe("read with out only: the project folder, relative to the folder where the agent started. Give it when that folder is not in a project and has projects below it"),
|
|
264
345
|
})
|
|
265
346
|
.strict(),
|
|
266
347
|
outputSchema: z.object({
|
|
@@ -307,8 +388,8 @@ export const c64Memmap = defineTool({
|
|
|
307
388
|
};
|
|
308
389
|
}
|
|
309
390
|
if (fields.out !== undefined) {
|
|
310
|
-
requireFields("read", fields, ["start", "end", "out"]);
|
|
311
|
-
return writeOut(fields.out, await readCoverage(session, start, end));
|
|
391
|
+
requireFields("read", fields, ["start", "end", "out", "project"]);
|
|
392
|
+
return writeOut(fields.out, await readCoverage(session, start, end), fields.project);
|
|
312
393
|
}
|
|
313
394
|
requireFields("read", fields, ["start", "end", "maxRanges"]);
|
|
314
395
|
const result = await session.memmap({ action: "read", start, end, maxRanges: fields.maxRanges ?? 256 });
|
|
@@ -324,4 +405,4 @@ export const c64Memmap = defineTool({
|
|
|
324
405
|
},
|
|
325
406
|
});
|
|
326
407
|
|
|
327
|
-
export const debugTools = [c64Breakpoint, c64Watchpoint, c64CpuHistory, c64Backtrace, c64Profile, c64Memmap, c64Timing];
|
|
408
|
+
export const debugTools = [c64Breakpoint, c64Watchpoint, c64CpuHistory, c64Trace, c64Backtrace, c64Profile, c64Memmap, c64Timing];
|
|
@@ -3,8 +3,9 @@
|
|
|
3
3
|
import { z } from "zod";
|
|
4
4
|
|
|
5
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";
|
|
6
|
+
import { COMPARISONS, EXECUTION_ACTIONS, MAX_EXECUTION_COUNT, MAX_HISTORY, MAX_TIMEOUT_FRAMES, POINT_STOP_REASONS, RUN_STATES, SPACES, STOP_REASONS, type RunTarget } from "../../protocol/vice.ts";
|
|
7
|
+
import { AddressInput, AddressOutput, Byte, ConditionInput, defineTool, MemoryViewInput, SpaceInput, toCondition, ToolOutput } from "../server.ts";
|
|
8
|
+
import { c64Observe } from "./video.ts";
|
|
8
9
|
|
|
9
10
|
export const c64Execution = defineTool({
|
|
10
11
|
name: "c64_execution",
|
|
@@ -15,7 +16,9 @@ export const c64Execution = defineTool({
|
|
|
15
16
|
"next executes count instructions (default 1) and treats a JSR and its subroutine as one instruction. " +
|
|
16
17
|
"until-return runs until the current subroutine returns (after the next RTS or RTI). " +
|
|
17
18
|
"advance-frames runs exactly count video frames (count is necessary). It stops at the same raster position, count frames later. " +
|
|
18
|
-
"A breakpoint
|
|
19
|
+
"A breakpoint, a watchpoint or a JAM opcode can stop it before that. advancedFrames tells how many frames ran. " +
|
|
20
|
+
"stopReason tells why it stopped: frames (all frames ran), breakpoint, watchpoint or jam. pointId gives the id of the breakpoint or watchpoint. " +
|
|
21
|
+
"To wait for a breakpoint or watchpoint after resume, use advance-frames with a large count. " +
|
|
19
22
|
"step, next, until-return and advance-frames first stop a running CPU. Use count only with step, next and advance-frames. " +
|
|
20
23
|
"step, next and until-return work only in space c64. For the drive, use c64_run_until with an address in space drive8.",
|
|
21
24
|
inputSchema: z
|
|
@@ -36,6 +39,8 @@ export const c64Execution = defineTool({
|
|
|
36
39
|
pc: AddressOutput.optional(),
|
|
37
40
|
executed: z.number().int().min(0).max(MAX_EXECUTION_COUNT).optional().describe("instructions executed by step or next"),
|
|
38
41
|
advancedFrames: z.number().int().min(0).max(MAX_EXECUTION_COUNT).optional().describe("frames advance-frames ran"),
|
|
42
|
+
stopReason: z.enum(["frames", ...POINT_STOP_REASONS]).optional().describe("advance-frames: why it stopped"),
|
|
43
|
+
pointId: z.number().int().min(1).optional().describe("the id of the breakpoint or watchpoint that stopped advance-frames"),
|
|
39
44
|
}),
|
|
40
45
|
readOnly: false,
|
|
41
46
|
async run(input, session) {
|
|
@@ -46,6 +51,8 @@ export const c64Execution = defineTool({
|
|
|
46
51
|
...(result.pc === undefined ? {} : { pc: formatC64Address(result.pc) }),
|
|
47
52
|
...(result.executed === undefined ? {} : { executed: result.executed }),
|
|
48
53
|
...(result.advancedFrames === undefined ? {} : { advancedFrames: result.advancedFrames }),
|
|
54
|
+
...(result.stopReason === undefined ? {} : { stopReason: result.stopReason }),
|
|
55
|
+
...(result.pointId === undefined ? {} : { pointId: result.pointId }),
|
|
49
56
|
};
|
|
50
57
|
},
|
|
51
58
|
});
|
|
@@ -88,15 +95,32 @@ export const c64RunUntil = defineTool({
|
|
|
88
95
|
"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
96
|
`timeoutFrames (1 to ${MAX_TIMEOUT_FRAMES}, default 3000) limits the run in video frames. ` +
|
|
90
97
|
"reached tells if the target occurred. stopReason tells why the CPU stopped: target, breakpoint, watchpoint, jam (a JAM opcode) or timeout. " +
|
|
98
|
+
"pointId gives the id of the breakpoint or watchpoint that stopped the CPU. " +
|
|
91
99
|
"space tells which CPU stopped: c64, or drive8 for a stop in the disk drive. pc is the address in that space. " +
|
|
100
|
+
"With observe, the result also gives what c64_observe gives at the stop, in observed. " +
|
|
101
|
+
`With history (1 to ${MAX_HISTORY}), it also gives the last instructions before the stop, as c64_cpu_history gives them. ` +
|
|
102
|
+
"Thus one call runs to a place and reads the machine there. " +
|
|
92
103
|
"The CPU is always stopped after this tool.",
|
|
93
104
|
inputSchema: z
|
|
94
105
|
.object({
|
|
95
106
|
target: RunTargetInput,
|
|
96
107
|
timeoutFrames: z.number().int().min(1).max(MAX_TIMEOUT_FRAMES).default(3000),
|
|
108
|
+
observe: c64Observe.inputSchema.optional().describe("what to read at the stop, as for c64_observe"),
|
|
109
|
+
history: z.number().int().min(1).max(MAX_HISTORY).optional().describe("the number of instructions before the stop to give"),
|
|
97
110
|
})
|
|
98
111
|
.strict(),
|
|
99
|
-
outputSchema: z.object({
|
|
112
|
+
outputSchema: z.object({
|
|
113
|
+
reached: z.boolean(),
|
|
114
|
+
stopReason: z.enum(STOP_REASONS),
|
|
115
|
+
state: z.enum(RUN_STATES),
|
|
116
|
+
pc: AddressOutput,
|
|
117
|
+
space: z.enum(SPACES),
|
|
118
|
+
pointId: z.number().int().min(1).optional().describe("the id of the breakpoint or watchpoint that stopped the CPU"),
|
|
119
|
+
observed: c64Observe.outputSchema.optional(),
|
|
120
|
+
history: z
|
|
121
|
+
.array(z.object({ address: AddressOutput, bytes: z.string(), text: z.string(), a: Byte, x: Byte, y: Byte, sp: Byte, rasterLine: z.number().int().min(0).optional(), rasterCycle: z.number().int().min(0).optional() }))
|
|
122
|
+
.optional(),
|
|
123
|
+
}),
|
|
100
124
|
readOnly: false,
|
|
101
125
|
async run(input, session) {
|
|
102
126
|
const { target: given } = input;
|
|
@@ -110,7 +134,16 @@ export const c64RunUntil = defineTool({
|
|
|
110
134
|
target = given.cycle === undefined ? { kind: "raster", line: given.line } : { kind: "raster", line: given.line, cycle: given.cycle };
|
|
111
135
|
}
|
|
112
136
|
const result = await session.runUntil({ target, timeoutFrames: input.timeoutFrames });
|
|
113
|
-
|
|
137
|
+
const answer = { ...result, pc: formatC64Address(result.pc) };
|
|
138
|
+
const history =
|
|
139
|
+
input.history === undefined
|
|
140
|
+
? {}
|
|
141
|
+
: { history: (await session.cpuHistory({ limit: input.history, space: result.space })).entries.map((entry) => ({ ...entry, address: formatC64Address(entry.address) })) };
|
|
142
|
+
if (input.observe === undefined) return { ...answer, ...history };
|
|
143
|
+
// The observation at the stop: an image of the screen comes with the result as c64_observe gives it.
|
|
144
|
+
const observed = await c64Observe.run(input.observe, session);
|
|
145
|
+
if (observed instanceof ToolOutput) return new ToolOutput({ ...answer, ...history, observed: observed.structured }, observed.content);
|
|
146
|
+
return { ...answer, ...history, observed };
|
|
114
147
|
},
|
|
115
148
|
});
|
|
116
149
|
|
package/src/mcp/tools/machine.ts
CHANGED
|
@@ -3,8 +3,9 @@
|
|
|
3
3
|
import { z } from "zod";
|
|
4
4
|
|
|
5
5
|
import { formatC64Address } from "../../c64.ts";
|
|
6
|
+
import { PACKAGE } from "../../package.ts";
|
|
6
7
|
import { VIDEO_STANDARDS } from "../../protocol/messages.ts";
|
|
7
|
-
import { RESET_MODES, RUN_STATES, WINDOW_ACTIONS } from "../../protocol/vice.ts";
|
|
8
|
+
import { POINT_STOP_REASONS, RESET_MODES, RUN_STATES, WINDOW_ACTIONS } from "../../protocol/vice.ts";
|
|
8
9
|
import { AddressOutput, defineTool } from "../server.ts";
|
|
9
10
|
|
|
10
11
|
export const c64Status = defineTool({
|
|
@@ -14,7 +15,9 @@ export const c64Status = defineTool({
|
|
|
14
15
|
"Get the state of the C64. The result tells if the CPU runs or has stopped, the video standard (pal or ntsc), " +
|
|
15
16
|
"and if warp mode is on. It also tells if the emulator window is open (window). " +
|
|
16
17
|
"When the CPU has stopped, the result also gives the program counter (pc). " +
|
|
17
|
-
"
|
|
18
|
+
"When a breakpoint, a watchpoint or a JAM opcode stopped the CPU, stopReason tells which, and pointId gives the id of the breakpoint or watchpoint. " +
|
|
19
|
+
"cartridge is true while a cartridge from c64_autostart is attached: a reset keeps it, and an autostart of another file detaches it. " +
|
|
20
|
+
"version is the c64-re-tools version of this MCP server.",
|
|
18
21
|
inputSchema: z.object({}).strict(),
|
|
19
22
|
outputSchema: z.object({
|
|
20
23
|
state: z.enum(RUN_STATES),
|
|
@@ -22,7 +25,10 @@ export const c64Status = defineTool({
|
|
|
22
25
|
warp: z.boolean(),
|
|
23
26
|
window: z.boolean().describe("true when the emulator window is open (c64_window)"),
|
|
24
27
|
pc: AddressOutput.optional().describe("program counter. Only when the CPU is stopped"),
|
|
28
|
+
stopReason: z.enum(POINT_STOP_REASONS).optional().describe("what stopped the CPU. Only when a breakpoint, a watchpoint or a JAM opcode stopped it"),
|
|
29
|
+
pointId: z.number().int().min(1).optional().describe("the id of the breakpoint or watchpoint that stopped the CPU"),
|
|
25
30
|
cartridge: z.boolean().describe("true while a cartridge from c64_autostart is attached"),
|
|
31
|
+
version: z.string().describe("the c64-re-tools version of this MCP server"),
|
|
26
32
|
}),
|
|
27
33
|
readOnly: true,
|
|
28
34
|
async run(_input, session) {
|
|
@@ -33,7 +39,10 @@ export const c64Status = defineTool({
|
|
|
33
39
|
warp: status.warp,
|
|
34
40
|
window: status.window,
|
|
35
41
|
cartridge: status.cartridge,
|
|
42
|
+
version: PACKAGE.version,
|
|
36
43
|
...(status.pc === undefined ? {} : { pc: formatC64Address(status.pc) }),
|
|
44
|
+
...(status.stopReason === undefined ? {} : { stopReason: status.stopReason }),
|
|
45
|
+
...(status.pointId === undefined ? {} : { pointId: status.pointId }),
|
|
37
46
|
};
|
|
38
47
|
},
|
|
39
48
|
});
|
package/src/mcp/tools/media.ts
CHANGED
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
// Media tools: c64_autostart, c64_program_load, c64_disk_attach and
|
|
2
|
-
// c64_snapshot. Paths are project
|
|
3
|
-
// the Host Runtime receives only its bytes.
|
|
2
|
+
// c64_snapshot. Paths are relative to the project folder; the host-client
|
|
3
|
+
// reads the file and the Host Runtime receives only its bytes.
|
|
4
4
|
|
|
5
5
|
import { z } from "zod";
|
|
6
6
|
|
|
7
7
|
import { formatC64Address } from "../../c64.ts";
|
|
8
|
-
import { readProjectFile } from "../../host-client/transfer.ts";
|
|
8
|
+
import { projectFolder, readProjectFile } from "../../host-client/transfer.ts";
|
|
9
9
|
import { AUTOSTART_TYPES, DISK_TYPES, MAX_SNAPSHOTS, RUN_STATES } from "../../protocol/vice.ts";
|
|
10
|
-
import { OutInput, writeOut, WrittenOutput } from "../output.ts";
|
|
10
|
+
import { OutInput, ProjectInput, writeOut, WrittenOutput } from "../output.ts";
|
|
11
11
|
import { AddressInput, AddressOutput, defineTool, requireFields, TransientName } from "../server.ts";
|
|
12
12
|
|
|
13
|
-
const ProjectPath = z.string().min(1).describe("path relative to the project
|
|
13
|
+
const ProjectPath = z.string().min(1).describe("path relative to the project folder, for example build/game.prg");
|
|
14
14
|
|
|
15
15
|
const list = (types: readonly string[]) => types.map((type) => `.${type}`).join(", ");
|
|
16
16
|
|
|
@@ -23,10 +23,12 @@ export const c64Autostart = defineTool({
|
|
|
23
23
|
"A cartridge stays in the C64 through every reset, as on a real C64. An autostart of a file that is not a cartridge detaches it first. " +
|
|
24
24
|
"Both joysticks are centred first: joystick port 1 uses the keyboard lines, and a held direction changes the RUN that the autostart types. " +
|
|
25
25
|
"Without index, a disk image loads like LOAD\"*\",8,1. index selects one directory entry by its number in the c64-disk directory list (0 is the first entry). " +
|
|
26
|
+
"The tool refuses an index of a DEL entry, or an index after the last entry. " +
|
|
26
27
|
"The machine runs when this tool returns, and the load continues.",
|
|
27
28
|
inputSchema: z
|
|
28
29
|
.object({
|
|
29
30
|
path: ProjectPath,
|
|
31
|
+
project: ProjectInput.optional(),
|
|
30
32
|
index: z.number().int().min(0).max(0xfffe).optional().describe("directory entry of a disk image, 0 is the first entry. Without it, the first file loads as with LOAD\"*\""),
|
|
31
33
|
run: z.boolean().default(true).describe("true runs the program after it loads"),
|
|
32
34
|
})
|
|
@@ -39,7 +41,7 @@ export const c64Autostart = defineTool({
|
|
|
39
41
|
readOnly: false,
|
|
40
42
|
async run(input, session) {
|
|
41
43
|
// VICE numbers the directory entries from 1, and its 0 loads "*".
|
|
42
|
-
return session.autostart({ path: input.path, index: input.index === undefined ? 0 : input.index + 1, run: input.run });
|
|
44
|
+
return session.autostart({ path: input.path, index: input.index === undefined ? 0 : input.index + 1, run: input.run, root: projectFolder(input.project) });
|
|
43
45
|
},
|
|
44
46
|
});
|
|
45
47
|
|
|
@@ -53,13 +55,15 @@ export const c64ProgramLoad = defineTool({
|
|
|
53
55
|
inputSchema: z
|
|
54
56
|
.object({
|
|
55
57
|
path: ProjectPath,
|
|
58
|
+
project: ProjectInput.optional(),
|
|
56
59
|
address: AddressInput.optional().describe("load here instead of at the file's own load address"),
|
|
57
60
|
})
|
|
58
61
|
.strict(),
|
|
59
62
|
outputSchema: z.object({ state: z.enum(RUN_STATES), loadAddress: AddressOutput, size: z.number().int().min(1).max(0x10000) }),
|
|
60
63
|
readOnly: false,
|
|
61
64
|
async run(input, session) {
|
|
62
|
-
const
|
|
65
|
+
const root = projectFolder(input.project);
|
|
66
|
+
const result = await session.programLoad(input.address === undefined ? { path: input.path, root } : { path: input.path, address: input.address, root });
|
|
63
67
|
return { state: result.state, loadAddress: formatC64Address(result.loadAddress), size: result.size };
|
|
64
68
|
},
|
|
65
69
|
});
|
|
@@ -71,11 +75,11 @@ export const c64DiskAttach = defineTool({
|
|
|
71
75
|
`Put a disk image from the project into drive 8 (${list(DISK_TYPES)}). ` +
|
|
72
76
|
"Nothing loads or runs. To load from the disk, type LOAD on the C64 or use c64_autostart. " +
|
|
73
77
|
"This does not change the run state of the machine.",
|
|
74
|
-
inputSchema: z.object({ path: ProjectPath }).strict(),
|
|
78
|
+
inputSchema: z.object({ path: ProjectPath, project: ProjectInput.optional() }).strict(),
|
|
75
79
|
outputSchema: z.object({ attached: z.boolean() }),
|
|
76
80
|
readOnly: false,
|
|
77
81
|
async run(input, session) {
|
|
78
|
-
return session.diskAttach(input);
|
|
82
|
+
return session.diskAttach({ path: input.path, root: projectFolder(input.project) });
|
|
79
83
|
},
|
|
80
84
|
});
|
|
81
85
|
|
|
@@ -94,6 +98,7 @@ export const c64Snapshot = defineTool({
|
|
|
94
98
|
name: TransientName.optional().describe("save and discard, and restore without file: 1 to 64 letters, digits, dots, underscores or hyphens"),
|
|
95
99
|
out: OutInput.optional().describe("save only: also write the snapshot to this project file, for example snapshots/room0.vsf. An existing file is overwritten"),
|
|
96
100
|
file: ProjectPath.optional().describe("restore only: a .vsf file in the project, in place of name"),
|
|
101
|
+
project: ProjectInput.optional().describe("save with out, and restore with file: the project folder, relative to the folder where the agent started. Give it when that folder is not in a project and has projects below it"),
|
|
97
102
|
})
|
|
98
103
|
.strict(),
|
|
99
104
|
outputSchema: z.object({
|
|
@@ -113,13 +118,13 @@ export const c64Snapshot = defineTool({
|
|
|
113
118
|
return session.snapshot({ action });
|
|
114
119
|
}
|
|
115
120
|
if (action === "restore" && fields.file !== undefined) {
|
|
116
|
-
requireFields(action, fields, ["file"], ["file"]);
|
|
117
|
-
return session.snapshotRestore(readProjectFile(fields.file).bytes);
|
|
121
|
+
requireFields(action, fields, ["file", "project"], ["file"]);
|
|
122
|
+
return session.snapshotRestore(readProjectFile(fields.file, { root: projectFolder(fields.project) }).bytes);
|
|
118
123
|
}
|
|
119
124
|
if (action === "save") {
|
|
120
|
-
requireFields(action, fields, ["name", "out"], ["name"]);
|
|
125
|
+
requireFields(action, fields, fields.out === undefined ? ["name"] : ["name", "out", "project"], ["name"]);
|
|
121
126
|
const saved = await session.snapshot({ action, name: fields.name! });
|
|
122
|
-
return fields.out === undefined ? saved : { ...saved, ...writeOut(fields.out, await session.snapshotExport(fields.name!)) };
|
|
127
|
+
return fields.out === undefined ? saved : { ...saved, ...writeOut(fields.out, await session.snapshotExport(fields.name!), fields.project) };
|
|
123
128
|
}
|
|
124
129
|
requireFields(action, fields, ["name"], ["name"]);
|
|
125
130
|
return session.snapshot({ action, name: fields.name! });
|
package/src/mcp/tools/memory.ts
CHANGED
|
@@ -6,12 +6,14 @@ import { z } from "zod";
|
|
|
6
6
|
import { formatC64Address } from "../../c64.ts";
|
|
7
7
|
import { WireFailure } from "../../protocol/messages.ts";
|
|
8
8
|
import { MAX_COMPARE_DIFFERENCES, MAX_COMPARE_SIZE, MAX_DISASSEMBLE, MAX_MEMORY_READ, MAX_MEMORY_WRITE, MAX_SEARCH_PATTERN, MAX_SEARCH_RESULTS, type RegisterValues } from "../../protocol/vice.ts";
|
|
9
|
-
import { OutInput, writeOut, WrittenOutput } from "../output.ts";
|
|
9
|
+
import { OutInput, ProjectInput, writeOut, WrittenOutput } from "../output.ts";
|
|
10
10
|
import { AddressInput, AddressOutput, Byte, defineTool, HexData, HexDataInput, MemoryViewInput, requireFields, SpaceInput } from "../server.ts";
|
|
11
11
|
|
|
12
|
-
/** With out, a read or a disassembly can be larger than one answer of the session: it is read in parts
|
|
12
|
+
/** With out, a read or a disassembly can be larger than one answer of the session: it is read in parts. */
|
|
13
13
|
const MAX_MEMORY_READ_OUT = 0x10000;
|
|
14
14
|
const MAX_DISASSEMBLE_OUT = 32768;
|
|
15
|
+
/** A memory byte in a result: two lowercase hex digits, as memory data is. */
|
|
16
|
+
const HexByte = z.string().regex(/^[0-9a-f]{2}$/);
|
|
15
17
|
|
|
16
18
|
export const c64MemoryRead = defineTool({
|
|
17
19
|
name: "c64_memory_read",
|
|
@@ -28,12 +30,14 @@ export const c64MemoryRead = defineTool({
|
|
|
28
30
|
space: SpaceInput,
|
|
29
31
|
view: MemoryViewInput,
|
|
30
32
|
out: OutInput.optional(),
|
|
33
|
+
project: ProjectInput.optional(),
|
|
31
34
|
})
|
|
32
35
|
.strict(),
|
|
33
36
|
outputSchema: z.object({ address: AddressOutput, size: z.number().int().min(1).max(MAX_MEMORY_READ_OUT), data: HexData.optional(), ...WrittenOutput }),
|
|
34
37
|
readOnly: false,
|
|
35
38
|
async run(input, session) {
|
|
36
|
-
const { out, ...read } = input;
|
|
39
|
+
const { out, project, ...read } = input;
|
|
40
|
+
if (out === undefined && project !== undefined) throw new WireFailure("invalid-input", "project is used only with out.");
|
|
37
41
|
if (out === undefined) {
|
|
38
42
|
if (read.size > MAX_MEMORY_READ) throw new WireFailure("invalid-input", `size can be at most ${MAX_MEMORY_READ} without out.`);
|
|
39
43
|
const result = await session.memoryRead(read);
|
|
@@ -45,7 +49,7 @@ export const c64MemoryRead = defineTool({
|
|
|
45
49
|
const part = await session.memoryRead({ ...read, address: at, size: Math.min(MAX_MEMORY_READ, read.address + read.size - at) });
|
|
46
50
|
parts.push(Buffer.from(part.data, "hex"));
|
|
47
51
|
}
|
|
48
|
-
return { address: formatC64Address(read.address), size: read.size, ...writeOut(out, Buffer.concat(parts)) };
|
|
52
|
+
return { address: formatC64Address(read.address), size: read.size, ...writeOut(out, Buffer.concat(parts), project) };
|
|
49
53
|
},
|
|
50
54
|
});
|
|
51
55
|
|
|
@@ -180,6 +184,7 @@ export const c64MemoryCompare = defineTool({
|
|
|
180
184
|
description:
|
|
181
185
|
`Compare two memory ranges of size bytes (1 to ${MAX_COMPARE_SIZE}). Each range has its own address, space and view. ` +
|
|
182
186
|
`The result tells if they are equal, how many bytes differ, and the first ${MAX_COMPARE_DIFFERENCES} differences with their offset. ` +
|
|
187
|
+
"The bytes of a difference are lowercase hex. " +
|
|
183
188
|
"This does not change the run state of the machine.",
|
|
184
189
|
inputSchema: z
|
|
185
190
|
.object({ left: Location, right: Location, size: z.number().int().min(1).max(MAX_COMPARE_SIZE) })
|
|
@@ -187,11 +192,13 @@ export const c64MemoryCompare = defineTool({
|
|
|
187
192
|
outputSchema: z.object({
|
|
188
193
|
equal: z.boolean(),
|
|
189
194
|
differentBytes: z.number().int().min(0),
|
|
190
|
-
firstDifferences: z.array(z.object({ offset: z.number().int().min(0), left:
|
|
195
|
+
firstDifferences: z.array(z.object({ offset: z.number().int().min(0), left: HexByte, right: HexByte })),
|
|
191
196
|
}),
|
|
192
197
|
readOnly: true,
|
|
193
198
|
async run(input, session) {
|
|
194
|
-
|
|
199
|
+
const result = await session.memoryCompare(input);
|
|
200
|
+
const hex = (byte: number) => byte.toString(16).padStart(2, "0");
|
|
201
|
+
return { ...result, firstDifferences: result.firstDifferences.map((difference) => ({ ...difference, left: hex(difference.left), right: hex(difference.right) })) };
|
|
195
202
|
},
|
|
196
203
|
});
|
|
197
204
|
|
|
@@ -210,12 +217,14 @@ export const c64Disassemble = defineTool({
|
|
|
210
217
|
space: SpaceInput,
|
|
211
218
|
view: MemoryViewInput,
|
|
212
219
|
out: OutInput.optional(),
|
|
220
|
+
project: ProjectInput.optional(),
|
|
213
221
|
})
|
|
214
222
|
.strict(),
|
|
215
223
|
outputSchema: z.object({ instructions: z.array(z.object({ address: AddressOutput, bytes: HexData, text: z.string() })).optional(), count: z.number().int().min(0).optional(), ...WrittenOutput }),
|
|
216
224
|
readOnly: false,
|
|
217
225
|
async run(input, session) {
|
|
218
|
-
const { out, ...request } = input;
|
|
226
|
+
const { out, project, ...request } = input;
|
|
227
|
+
if (out === undefined && project !== undefined) throw new WireFailure("invalid-input", "project is used only with out.");
|
|
219
228
|
if (out === undefined) {
|
|
220
229
|
if (request.count > MAX_DISASSEMBLE) throw new WireFailure("invalid-input", `count can be at most ${MAX_DISASSEMBLE} without out.`);
|
|
221
230
|
const result = await session.disassemble(request);
|
|
@@ -238,7 +247,7 @@ export const c64Disassemble = defineTool({
|
|
|
238
247
|
address = instruction.address + instruction.bytes.length / 2;
|
|
239
248
|
}
|
|
240
249
|
}
|
|
241
|
-
return { count: lines.length, ...writeOut(out, `${lines.join("\n")}\n
|
|
250
|
+
return { count: lines.length, ...writeOut(out, `${lines.join("\n")}\n`, project) };
|
|
242
251
|
},
|
|
243
252
|
});
|
|
244
253
|
|