@henols/c64-re-tools 1.1.0 → 2.0.0-rc.3
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/LICENSE +21 -0
- package/README.md +138 -37
- package/distribution/package.ts +36 -0
- package/distribution/plugin.ts +120 -0
- package/package.json +34 -40
- package/skills/c64-assembler/SKILL.md +73 -0
- package/skills/c64-assembler/scripts/assemble.ts +103 -0
- package/skills/c64-basic/SKILL.md +67 -0
- package/skills/c64-basic/scripts/basic.ts +46 -0
- package/skills/c64-disk/SKILL.md +79 -0
- package/skills/c64-disk/scripts/disk.ts +65 -0
- package/skills/c64-emulator/SKILL.md +124 -0
- package/skills/c64-knowledge/SKILL.md +102 -0
- package/skills/c64-knowledge/scripts/knowledge.ts +257 -0
- package/skills/c64-memory-map/SKILL.md +76 -0
- package/skills/c64-memory-map/scripts/memmap.ts +81 -0
- package/skills/c64-memory-map/scripts/platform.ts +424 -0
- package/skills/c64-provenance/SKILL.md +76 -0
- package/skills/c64-provenance/scripts/compare.ts +92 -0
- package/skills/c64-provenance/scripts/provenance.ts +83 -0
- package/skills/c64-reverse-engineering/SKILL.md +90 -0
- package/skills/c64-static-analysis/SKILL.md +96 -0
- package/skills/c64-static-analysis/scripts/analyze.ts +175 -0
- package/skills/c64-static-analysis/scripts/findings.ts +73 -0
- package/skills/c64-testing/SKILL.md +79 -0
- package/skills/c64-testing/references/scenario-format.md +76 -0
- package/skills/c64-testing/scripts/checklist.ts +51 -0
- package/skills/c64-testing/scripts/scenario.ts +473 -0
- package/skills/c64-testing/scripts/test.ts +55 -0
- package/skills/c64-unpacker/SKILL.md +82 -0
- package/skills/c64-unpacker/scripts/evidence.ts +135 -0
- package/skills/c64-unpacker/scripts/unpack.ts +216 -0
- package/src/c64.ts +281 -0
- package/src/cli/ap-sdk.ts +44 -0
- package/src/cli/cleanup.ts +194 -0
- package/src/cli/main.ts +122 -0
- package/src/cli/status.ts +34 -0
- package/src/host/main.ts +106 -0
- package/src/host/server.ts +376 -0
- package/src/host/tools/c1541.ts +255 -0
- package/src/host/tools/index.ts +31 -0
- package/src/host/tools/petcat.ts +70 -0
- package/src/host/tools/status.ts +30 -0
- package/src/host/tools/version.ts +41 -0
- package/src/host/vice/adapter.ts +761 -0
- package/src/host/vice/binary-monitor.ts +389 -0
- package/src/host/vice/chips.ts +108 -0
- package/src/host/vice/connect.ts +34 -0
- package/src/host/vice/process.ts +402 -0
- package/src/host/vice/screen.ts +183 -0
- package/src/host/vice/session.ts +1316 -0
- package/src/host/vice/text-monitor.ts +223 -0
- package/src/host-client/connect.ts +238 -0
- package/src/host-client/tools.ts +89 -0
- package/src/host-client/transfer.ts +100 -0
- package/src/host-client/vice-session.ts +254 -0
- package/src/knowledge/database.ts +132 -0
- package/src/knowledge/history.ts +128 -0
- package/src/knowledge/import.ts +344 -0
- package/src/knowledge/read.ts +191 -0
- package/src/knowledge/schema.ts +140 -0
- package/src/knowledge/write.ts +348 -0
- package/src/mcp/main.ts +106 -0
- package/src/mcp/server.ts +307 -0
- package/src/mcp/tools/debug.ts +237 -0
- package/src/mcp/tools/execution.ts +116 -0
- package/src/mcp/tools/input.ts +69 -0
- package/src/mcp/tools/machine.ts +97 -0
- package/src/mcp/tools/media.ts +106 -0
- package/src/mcp/tools/memory.ts +203 -0
- package/src/mcp/tools/video.ts +260 -0
- package/src/native/acme.ts +116 -0
- package/src/native/discover.ts +141 -0
- package/src/native/dxa.ts +166 -0
- package/src/native/ghidra/analyze.ts +86 -0
- package/src/native/ghidra/index.ts +157 -0
- package/src/native/ghidra/language/c64rt_6510.cspec +34 -0
- package/src/native/ghidra/language/c64rt_6510.ldefs +15 -0
- package/src/native/ghidra/language/c64rt_6510.pspec +15 -0
- package/src/native/ghidra/language/c64rt_6510.slaspec +596 -0
- package/src/native/ghidra/scripts/C64Export.java +242 -0
- package/src/native/ghidra/scripts/C64Prepare.java +60 -0
- package/src/native/local.ts +34 -0
- package/src/native/processes.ts +350 -0
- package/src/native/run.ts +172 -0
- package/src/native/staging.ts +83 -0
- package/src/native/status.ts +101 -0
- package/src/native/types.ts +228 -0
- package/src/native/watchdog.ts +78 -0
- package/src/project.ts +39 -0
- package/src/protocol/framing.ts +132 -0
- package/src/protocol/messages.ts +243 -0
- package/src/protocol/tools.ts +247 -0
- package/src/protocol/vice.ts +1235 -0
- package/src/script.ts +59 -0
- package/src/time.ts +15 -0
- package/src/trace.ts +162 -0
- package/THIRD-PARTY-NOTICES.md +0 -26
- package/bin/cli.mjs +0 -283
- package/skills/acme-build/SKILL.md +0 -260
- package/skills/acme-build/scripts/acme.mjs +0 -341
- package/skills/acme-build/template.a +0 -53
- package/skills/c64-disk-access/SKILL.md +0 -159
- package/skills/c64-disk-access/scripts/c1541.mjs +0 -569
- package/skills/c64-memory-mapping/SKILL.md +0 -631
- package/skills/c64-memory-mapping/memmap.json +0 -8800
- package/skills/c64-memory-mapping/scripts/driver.mjs +0 -553
- package/skills/c64-petcat/SKILL.md +0 -87
- package/skills/c64-petcat/scripts/petcat.mjs +0 -221
- package/skills/c64-program-recon/SKILL.md +0 -706
- package/skills/c64-program-recon/references/control-flow.md +0 -180
- package/skills/c64-program-recon/references/graphics.md +0 -79
- package/skills/c64-program-recon/references/observation-hazards.md +0 -143
- package/skills/c64-program-recon/references/reconstruction.md +0 -133
- package/skills/c64-program-recon/references/sound-and-input.md +0 -74
- package/skills/c64-program-recon/references/tool-selection.md +0 -78
- package/skills/c64-program-recon/scripts/derive.mjs +0 -364
- package/skills/c64-program-recon/scripts/packer-finding.mjs +0 -709
- package/skills/c64-program-recon/templates/memory-map.template.md +0 -101
- package/skills/c64-provenance-diff/SKILL.md +0 -292
- package/skills/c64-provenance-diff/scripts/diff-images.mjs +0 -984
- package/skills/c64-provenance-diff/scripts/recovery-schema.mjs +0 -395
- package/skills/c64-ram-capture/RELEASES.json.example +0 -17
- package/skills/c64-ram-capture/SKILL.md +0 -474
- package/skills/c64-ram-capture/scripts/compare-cross-binary.mjs +0 -525
- package/skills/c64-ram-capture/scripts/compare.mjs +0 -258
- package/skills/c64-ram-capture/scripts/derive-transients.mjs +0 -575
- package/skills/c64-ram-capture/scripts/dump-artifacts.mjs +0 -317
- package/skills/c64-ram-capture/scripts/mcp-module.mjs +0 -174
- package/skills/c64-ram-capture/scripts/project-paths.mjs +0 -81
- package/skills/c64-ram-capture/scripts/releases.mjs +0 -109
- package/skills/c64-ram-capture/scripts/vsf-slice.mjs +0 -147
- package/skills/c64-ram-capture/scripts/watch-loads.mjs +0 -581
- package/skills/c64-ram-capture/templates/capture-record.template.md +0 -99
- package/skills/c64-ram-capture/transients/README.md +0 -137
- package/skills/routine-queue-walker/SKILL.md +0 -394
- package/skills/routine-queue-walker/scripts/completeness-report.mjs +0 -465
- package/skills/vice-wedge-triage/SKILL.md +0 -269
|
@@ -0,0 +1,69 @@
|
|
|
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
|
+
export const c64Keyboard = defineTool({
|
|
11
|
+
name: "c64_keyboard",
|
|
12
|
+
title: "Type on the C64 keyboard",
|
|
13
|
+
description:
|
|
14
|
+
`Type up to ${MAX_KEYBOARD_BYTES} keys into the C64 keyboard queue. The C64 reads them while it runs. ` +
|
|
15
|
+
"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. " +
|
|
16
|
+
'Mode text types text, for example "RUN\\n". Letters of either case type the normal (unshifted) key. A line break types RETURN. ' +
|
|
17
|
+
"Mode petscii types PETSCII codes 1 to 255 in bytes, for example [147] to clear the screen. " +
|
|
18
|
+
"This does not change the run state of the machine.",
|
|
19
|
+
inputSchema: z
|
|
20
|
+
.object({
|
|
21
|
+
mode: z.enum(["text", "petscii"]),
|
|
22
|
+
text: z.string().min(1).optional().describe("mode text: the text to type"),
|
|
23
|
+
bytes: z.array(z.number().int().min(1).max(255)).min(1).optional().describe("mode petscii: PETSCII codes to type"),
|
|
24
|
+
})
|
|
25
|
+
.strict(),
|
|
26
|
+
outputSchema: z.object({ queuedBytes: z.number().int().min(1).max(MAX_KEYBOARD_BYTES) }),
|
|
27
|
+
readOnly: false,
|
|
28
|
+
async run(input, session) {
|
|
29
|
+
let petscii: Uint8Array;
|
|
30
|
+
if (input.mode === "text") {
|
|
31
|
+
if (input.text === undefined || input.bytes !== undefined) throw new WireFailure("invalid-input", "Mode text takes text and no bytes.");
|
|
32
|
+
try {
|
|
33
|
+
petscii = textToPetscii(input.text);
|
|
34
|
+
} catch (error) {
|
|
35
|
+
throw new WireFailure("invalid-input", `${(error as Error).message}. Use mode petscii for other keys.`);
|
|
36
|
+
}
|
|
37
|
+
} else {
|
|
38
|
+
if (input.bytes === undefined || input.text !== undefined) throw new WireFailure("invalid-input", "Mode petscii takes bytes and no text.");
|
|
39
|
+
petscii = Uint8Array.from(input.bytes);
|
|
40
|
+
}
|
|
41
|
+
if (petscii.length > MAX_KEYBOARD_BYTES) {
|
|
42
|
+
throw new WireFailure("invalid-input", `The keyboard queue takes at most ${MAX_KEYBOARD_BYTES} keys at once. This input has ${petscii.length} keys.`);
|
|
43
|
+
}
|
|
44
|
+
return session.keyboard(petscii);
|
|
45
|
+
},
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
export const c64Joystick = defineTool({
|
|
49
|
+
name: "c64_joystick",
|
|
50
|
+
title: "Move a joystick",
|
|
51
|
+
description:
|
|
52
|
+
"Set the joystick in control port 1 or 2: a direction (or center) and the fire button. " +
|
|
53
|
+
"The C64 sees this state until you change it. To release the joystick, set direction center and fire false. " +
|
|
54
|
+
"Most games read port 2. This does not change the run state of the machine.",
|
|
55
|
+
inputSchema: z
|
|
56
|
+
.object({
|
|
57
|
+
port: z.union([z.literal(1), z.literal(2)]).describe("control port 1 or 2"),
|
|
58
|
+
direction: z.enum(JOYSTICK_DIRECTIONS),
|
|
59
|
+
fire: z.boolean().default(false),
|
|
60
|
+
})
|
|
61
|
+
.strict(),
|
|
62
|
+
outputSchema: z.object({ port: z.union([z.literal(1), z.literal(2)]), direction: z.enum(JOYSTICK_DIRECTIONS), fire: z.boolean() }),
|
|
63
|
+
readOnly: false,
|
|
64
|
+
async run(input, session) {
|
|
65
|
+
return session.joystick(input);
|
|
66
|
+
},
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
export const inputTools = [c64Keyboard, c64Joystick];
|
|
@@ -0,0 +1,97 @@
|
|
|
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
|
+
inputSchema: z.object({}).strict(),
|
|
18
|
+
outputSchema: z.object({
|
|
19
|
+
state: z.enum(RUN_STATES),
|
|
20
|
+
videoStandard: z.enum(VIDEO_STANDARDS),
|
|
21
|
+
warp: z.boolean(),
|
|
22
|
+
window: z.boolean().describe("true when the emulator window is open (c64_window)"),
|
|
23
|
+
pc: AddressOutput.optional().describe("program counter. Only when the CPU is stopped"),
|
|
24
|
+
}),
|
|
25
|
+
readOnly: true,
|
|
26
|
+
async run(_input, session) {
|
|
27
|
+
const status = await session.status();
|
|
28
|
+
return {
|
|
29
|
+
state: status.state,
|
|
30
|
+
videoStandard: status.videoStandard,
|
|
31
|
+
warp: status.warp,
|
|
32
|
+
window: status.window,
|
|
33
|
+
...(status.pc === undefined ? {} : { pc: formatC64Address(status.pc) }),
|
|
34
|
+
};
|
|
35
|
+
},
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
export const c64Window = defineTool({
|
|
39
|
+
name: "c64_window",
|
|
40
|
+
title: "Emulator window",
|
|
41
|
+
description:
|
|
42
|
+
"Show the C64 in a window on the host, or hide it again. The emulator has no window until you open one. " +
|
|
43
|
+
"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. " +
|
|
44
|
+
"Then close the window. The machine, the disk in drive 8, breakpoints, watchpoints, joysticks, warp mode and c64_timing go along. " +
|
|
45
|
+
"The run state of the machine does not change. The result names the data that does not go along (notCarried). " +
|
|
46
|
+
"If the user closes the window, the machine goes back to the state of when the window opened.",
|
|
47
|
+
inputSchema: z.object({ action: z.enum(WINDOW_ACTIONS).describe("open shows the window. close hides it") }).strict(),
|
|
48
|
+
outputSchema: z.object({
|
|
49
|
+
window: z.boolean().describe("true when the window is open"),
|
|
50
|
+
state: z.enum(RUN_STATES),
|
|
51
|
+
notCarried: z.array(z.string()).describe("emulator data that the move cleared. Empty when nothing moved"),
|
|
52
|
+
}),
|
|
53
|
+
readOnly: false,
|
|
54
|
+
async run(input, session) {
|
|
55
|
+
return session.window(input.action);
|
|
56
|
+
},
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
export const c64Reset = defineTool({
|
|
60
|
+
name: "c64_reset",
|
|
61
|
+
title: "Reset the C64",
|
|
62
|
+
description:
|
|
63
|
+
"Reset the C64. Mode soft is the reset button. Mode hard is a power cycle that also resets the disk drive. " +
|
|
64
|
+
"With run false (the default), the CPU stops at the first instruction of the reset routine, and the result gives that program counter (pc). " +
|
|
65
|
+
"With run true, the C64 starts normally. Breakpoints and watchpoints stay.",
|
|
66
|
+
inputSchema: z
|
|
67
|
+
.object({
|
|
68
|
+
mode: z.enum(RESET_MODES).describe("soft is the reset button. hard is a power cycle"),
|
|
69
|
+
run: z.boolean().default(false).describe("false stops at the reset routine. true lets the C64 start"),
|
|
70
|
+
})
|
|
71
|
+
.strict(),
|
|
72
|
+
outputSchema: z.object({
|
|
73
|
+
state: z.enum(RUN_STATES),
|
|
74
|
+
pc: AddressOutput.optional().describe("program counter. Only when run is false"),
|
|
75
|
+
}),
|
|
76
|
+
readOnly: false,
|
|
77
|
+
async run(input, session) {
|
|
78
|
+
const result = await session.reset(input);
|
|
79
|
+
return result.pc === undefined ? { state: result.state } : { state: result.state, pc: formatC64Address(result.pc) };
|
|
80
|
+
},
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
export const c64Warp = defineTool({
|
|
84
|
+
name: "c64_warp",
|
|
85
|
+
title: "Warp mode",
|
|
86
|
+
description:
|
|
87
|
+
"Turn warp mode on or off. In warp mode the C64 runs as fast as the host can run it. " +
|
|
88
|
+
"Warp mode does not change what the C64 does. This does not change the run state of the machine.",
|
|
89
|
+
inputSchema: z.object({ enabled: z.boolean().describe("true turns warp mode on") }).strict(),
|
|
90
|
+
outputSchema: z.object({ enabled: z.boolean() }),
|
|
91
|
+
readOnly: false,
|
|
92
|
+
async run(input, session) {
|
|
93
|
+
return session.warp(input.enabled);
|
|
94
|
+
},
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
export const machineTools = [c64Status, c64Reset, c64Warp, c64Window];
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
// Media tools: c64_autostart, c64_program_load, c64_disk_attach and
|
|
2
|
+
// c64_snapshot. Paths are project-relative; the host-client reads the file and
|
|
3
|
+
// the Host Runtime receives only its bytes.
|
|
4
|
+
|
|
5
|
+
import { z } from "zod";
|
|
6
|
+
|
|
7
|
+
import { formatC64Address } from "../../c64.ts";
|
|
8
|
+
import { AUTOSTART_TYPES, DISK_TYPES, MAX_SNAPSHOTS, RUN_STATES } from "../../protocol/vice.ts";
|
|
9
|
+
import { AddressInput, AddressOutput, defineTool, requireFields, TransientName } from "../server.ts";
|
|
10
|
+
|
|
11
|
+
const ProjectPath = z.string().min(1).describe("path relative to the project directory, for example build/game.prg");
|
|
12
|
+
|
|
13
|
+
const list = (types: readonly string[]) => types.map((type) => `.${type}`).join(", ");
|
|
14
|
+
|
|
15
|
+
export const c64Autostart = defineTool({
|
|
16
|
+
name: "c64_autostart",
|
|
17
|
+
title: "Autostart a program or image",
|
|
18
|
+
description:
|
|
19
|
+
`Start a program, or a disk, tape or cartridge image, from the project (${list(AUTOSTART_TYPES)}). ` +
|
|
20
|
+
"The C64 resets and loads the file. With run true (the default), it also runs the file. A disk image stays in drive 8. " +
|
|
21
|
+
"index selects a file on a disk image (default 0, the first file). " +
|
|
22
|
+
"The machine runs when this tool returns, and the load continues.",
|
|
23
|
+
inputSchema: z
|
|
24
|
+
.object({
|
|
25
|
+
path: ProjectPath,
|
|
26
|
+
index: z.number().int().min(0).max(0xffff).default(0).describe("file number on a disk image. 0 is the first file"),
|
|
27
|
+
run: z.boolean().default(true).describe("true runs the program after it loads"),
|
|
28
|
+
})
|
|
29
|
+
.strict(),
|
|
30
|
+
outputSchema: z.object({ state: z.enum(RUN_STATES) }),
|
|
31
|
+
readOnly: false,
|
|
32
|
+
async run(input, session) {
|
|
33
|
+
return session.autostart(input);
|
|
34
|
+
},
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
export const c64ProgramLoad = defineTool({
|
|
38
|
+
name: "c64_program_load",
|
|
39
|
+
title: "Load a PRG into memory",
|
|
40
|
+
description:
|
|
41
|
+
"Load a PRG file from the project into memory, with no reset and no start. The CPU stops and stays stopped. " +
|
|
42
|
+
"The bytes go where the CPU writes them. Without address, the program goes to the load address in its first two bytes. " +
|
|
43
|
+
"With address, it goes there instead.",
|
|
44
|
+
inputSchema: z
|
|
45
|
+
.object({
|
|
46
|
+
path: ProjectPath,
|
|
47
|
+
address: AddressInput.optional().describe("load here instead of at the file's own load address"),
|
|
48
|
+
})
|
|
49
|
+
.strict(),
|
|
50
|
+
outputSchema: z.object({ state: z.enum(RUN_STATES), loadAddress: AddressOutput, size: z.number().int().min(1).max(0x10000) }),
|
|
51
|
+
readOnly: false,
|
|
52
|
+
async run(input, session) {
|
|
53
|
+
const result = await session.programLoad(input.address === undefined ? { path: input.path } : { path: input.path, address: input.address });
|
|
54
|
+
return { state: result.state, loadAddress: formatC64Address(result.loadAddress), size: result.size };
|
|
55
|
+
},
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
export const c64DiskAttach = defineTool({
|
|
59
|
+
name: "c64_disk_attach",
|
|
60
|
+
title: "Attach a disk image",
|
|
61
|
+
description:
|
|
62
|
+
`Put a disk image from the project into drive 8 (${list(DISK_TYPES)}). ` +
|
|
63
|
+
"Nothing loads or runs. To load from the disk, type LOAD on the C64 or use c64_autostart. " +
|
|
64
|
+
"This does not change the run state of the machine.",
|
|
65
|
+
inputSchema: z.object({ path: ProjectPath }).strict(),
|
|
66
|
+
outputSchema: z.object({ attached: z.boolean() }),
|
|
67
|
+
readOnly: false,
|
|
68
|
+
async run(input, session) {
|
|
69
|
+
return session.diskAttach(input);
|
|
70
|
+
},
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
export const c64Snapshot = defineTool({
|
|
74
|
+
name: "c64_snapshot",
|
|
75
|
+
title: "Machine snapshots",
|
|
76
|
+
description:
|
|
77
|
+
"Save and restore the whole machine state, attached disks included. Action save keeps a snapshot under a name you choose. " +
|
|
78
|
+
"Action restore puts the machine back in that state and stops the CPU. The c64_timing stopwatch keeps its reading. " +
|
|
79
|
+
`Action list gives the names, and action discard removes one. A session keeps at most ${MAX_SNAPSHOTS} snapshots until it ends.`,
|
|
80
|
+
inputSchema: z
|
|
81
|
+
.object({
|
|
82
|
+
action: z.enum(["save", "restore", "list", "discard"]),
|
|
83
|
+
name: TransientName.optional().describe("save, restore and discard: 1 to 64 letters, digits, dots, underscores or hyphens"),
|
|
84
|
+
})
|
|
85
|
+
.strict(),
|
|
86
|
+
outputSchema: z.object({
|
|
87
|
+
saved: z.boolean().optional(),
|
|
88
|
+
name: z.string().optional(),
|
|
89
|
+
restored: z.boolean().optional(),
|
|
90
|
+
state: z.enum(RUN_STATES).optional(),
|
|
91
|
+
snapshots: z.array(z.string()).optional(),
|
|
92
|
+
discarded: z.boolean().optional(),
|
|
93
|
+
}),
|
|
94
|
+
readOnly: false,
|
|
95
|
+
async run(input, session) {
|
|
96
|
+
const { action, name } = input;
|
|
97
|
+
if (action === "list") {
|
|
98
|
+
requireFields(action, { name }, []);
|
|
99
|
+
return session.snapshot({ action });
|
|
100
|
+
}
|
|
101
|
+
requireFields(action, { name }, ["name"], ["name"]);
|
|
102
|
+
return session.snapshot({ action, name: name! });
|
|
103
|
+
},
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
export const mediaTools = [c64Autostart, c64ProgramLoad, c64DiskAttach, c64Snapshot];
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
// Memory and CPU tools: c64_memory_read, c64_memory_write, c64_memory_search,
|
|
2
|
+
// c64_memory_compare, c64_registers and c64_disassemble.
|
|
3
|
+
|
|
4
|
+
import { z } from "zod";
|
|
5
|
+
|
|
6
|
+
import { formatC64Address } from "../../c64.ts";
|
|
7
|
+
import { WireFailure } from "../../protocol/messages.ts";
|
|
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 { AddressInput, AddressOutput, Byte, defineTool, HexData, HexDataInput, MemoryViewInput, requireFields, SpaceInput } from "../server.ts";
|
|
10
|
+
|
|
11
|
+
export const c64MemoryRead = defineTool({
|
|
12
|
+
name: "c64_memory_read",
|
|
13
|
+
title: "Read C64 memory",
|
|
14
|
+
description:
|
|
15
|
+
`Read 1 to ${MAX_MEMORY_READ} bytes of memory, starting at an address. ` +
|
|
16
|
+
"The range must not go past $ffff. Use space drive8 to read the memory of the 1541 disk drive. " +
|
|
17
|
+
"This does not change the run state of the machine.",
|
|
18
|
+
inputSchema: z
|
|
19
|
+
.object({
|
|
20
|
+
address: AddressInput,
|
|
21
|
+
size: z.number().int().min(1).max(MAX_MEMORY_READ).describe(`number of bytes, 1 to ${MAX_MEMORY_READ}`),
|
|
22
|
+
space: SpaceInput,
|
|
23
|
+
view: MemoryViewInput,
|
|
24
|
+
})
|
|
25
|
+
.strict(),
|
|
26
|
+
outputSchema: z.object({ address: AddressOutput, size: z.number().int().min(1).max(MAX_MEMORY_READ), data: HexData }),
|
|
27
|
+
readOnly: true,
|
|
28
|
+
async run(input, session) {
|
|
29
|
+
const result = await session.memoryRead(input);
|
|
30
|
+
return { address: formatC64Address(result.address), size: result.data.length / 2, data: result.data };
|
|
31
|
+
},
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
export const c64MemoryWrite = defineTool({
|
|
35
|
+
name: "c64_memory_write",
|
|
36
|
+
title: "Write C64 memory",
|
|
37
|
+
description:
|
|
38
|
+
`Write 1 to ${MAX_MEMORY_WRITE} bytes to memory, starting at an address. First stop the CPU with c64_execution action pause. ` +
|
|
39
|
+
"The range must not go past $ffff. With view cpu, the write goes where the CPU writes now. " +
|
|
40
|
+
"A write to a ROM address goes to the RAM under it, and a write to I/O goes to the chip. " +
|
|
41
|
+
"With view ram, the write goes to RAM (c64 only).",
|
|
42
|
+
inputSchema: z
|
|
43
|
+
.object({
|
|
44
|
+
address: AddressInput,
|
|
45
|
+
data: HexDataInput.describe("bytes as hex, two digits per byte, no separators, for example a9008d20d0"),
|
|
46
|
+
space: SpaceInput,
|
|
47
|
+
view: MemoryViewInput,
|
|
48
|
+
})
|
|
49
|
+
.strict(),
|
|
50
|
+
outputSchema: z.object({ address: AddressOutput, bytesWritten: z.number().int().min(1).max(MAX_MEMORY_WRITE) }),
|
|
51
|
+
readOnly: false,
|
|
52
|
+
async run(input, session) {
|
|
53
|
+
const result = await session.memoryWrite(input);
|
|
54
|
+
return { address: formatC64Address(result.address), bytesWritten: result.bytesWritten };
|
|
55
|
+
},
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
const Flags = z.object({
|
|
59
|
+
n: z.boolean(),
|
|
60
|
+
v: z.boolean(),
|
|
61
|
+
b: z.boolean(),
|
|
62
|
+
d: z.boolean(),
|
|
63
|
+
i: z.boolean(),
|
|
64
|
+
z: z.boolean(),
|
|
65
|
+
c: z.boolean(),
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
const RegisterValuesInput = z
|
|
69
|
+
.object({
|
|
70
|
+
pc: AddressInput.optional(),
|
|
71
|
+
a: Byte.optional(),
|
|
72
|
+
x: Byte.optional(),
|
|
73
|
+
y: Byte.optional(),
|
|
74
|
+
sp: Byte.optional(),
|
|
75
|
+
flags: Flags.partial().strict().optional().describe("any of n, v, b, d, i, z and c. Flags that you do not name keep their value"),
|
|
76
|
+
})
|
|
77
|
+
.strict();
|
|
78
|
+
|
|
79
|
+
export const c64Registers = defineTool({
|
|
80
|
+
name: "c64_registers",
|
|
81
|
+
title: "C64 CPU registers",
|
|
82
|
+
description:
|
|
83
|
+
"Get or set the CPU registers: pc, a, x, y, sp and the status flags n, v, b, d, i, z and c. " +
|
|
84
|
+
"Action get reads them. This does not change the run state of the machine. " +
|
|
85
|
+
"Action set writes the registers that values names and returns all registers. Stop the CPU before you use set. " +
|
|
86
|
+
"Use space drive8 for the CPU of the 1541 disk drive.",
|
|
87
|
+
inputSchema: z
|
|
88
|
+
.object({
|
|
89
|
+
action: z.enum(["get", "set"]),
|
|
90
|
+
space: SpaceInput,
|
|
91
|
+
values: RegisterValuesInput.optional().describe("set only: the registers to write"),
|
|
92
|
+
})
|
|
93
|
+
.strict(),
|
|
94
|
+
outputSchema: z.object({ pc: AddressOutput, a: Byte, x: Byte, y: Byte, sp: Byte, flags: Flags }),
|
|
95
|
+
readOnly: false,
|
|
96
|
+
async run(input, session) {
|
|
97
|
+
let registers;
|
|
98
|
+
if (input.action === "get") {
|
|
99
|
+
requireFields(input.action, { values: input.values }, []);
|
|
100
|
+
registers = await session.registersGet(input.space);
|
|
101
|
+
} else {
|
|
102
|
+
requireFields(input.action, { values: input.values }, ["values"], ["values"]);
|
|
103
|
+
const values: RegisterValues = {};
|
|
104
|
+
for (const key of ["pc", "a", "x", "y", "sp"] as const) {
|
|
105
|
+
const value = input.values?.[key];
|
|
106
|
+
if (value !== undefined) values[key] = value;
|
|
107
|
+
}
|
|
108
|
+
const flags = Object.fromEntries(Object.entries(input.values?.flags ?? {}).filter(([, value]) => value !== undefined));
|
|
109
|
+
if (Object.keys(flags).length > 0) values.flags = flags;
|
|
110
|
+
if (Object.keys(values).length === 0) {
|
|
111
|
+
throw new WireFailure("invalid-input", "values must name at least one register for action set.");
|
|
112
|
+
}
|
|
113
|
+
registers = await session.registersSet(input.space, values);
|
|
114
|
+
}
|
|
115
|
+
return { ...registers, pc: formatC64Address(registers.pc) };
|
|
116
|
+
},
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
/** "a9 ?? 8d 20 d0": hex bytes and ?? wildcards, separated by spaces. */
|
|
120
|
+
const SearchPattern = z
|
|
121
|
+
.string()
|
|
122
|
+
.regex(/^\s*(?:[0-9a-fA-F]{2}|\?\?)(?:\s+(?:[0-9a-fA-F]{2}|\?\?))*\s*$/, "must be hex bytes and ?? wildcards separated by spaces, for example a9 ?? 8d")
|
|
123
|
+
.transform((value) => value.trim().split(/\s+/).map((token) => (token === "??" ? null : Number.parseInt(token, 16))))
|
|
124
|
+
.describe("hex bytes and ?? (any byte), separated by spaces, for example a9 ?? 8d 20 d0");
|
|
125
|
+
|
|
126
|
+
export const c64MemorySearch = defineTool({
|
|
127
|
+
name: "c64_memory_search",
|
|
128
|
+
title: "Search C64 memory",
|
|
129
|
+
description:
|
|
130
|
+
`Find a byte pattern of 1 to ${MAX_SEARCH_PATTERN} bytes in memory from start to end. ?? matches any byte. ` +
|
|
131
|
+
`The result gives the start address of each match, at most maxResults (1 to ${MAX_SEARCH_RESULTS}, default 100). ` +
|
|
132
|
+
"This does not change the run state of the machine.",
|
|
133
|
+
inputSchema: z
|
|
134
|
+
.object({
|
|
135
|
+
start: AddressInput,
|
|
136
|
+
end: AddressInput.optional().describe("last address to search. Default $ffff"),
|
|
137
|
+
pattern: SearchPattern,
|
|
138
|
+
space: SpaceInput,
|
|
139
|
+
view: MemoryViewInput,
|
|
140
|
+
maxResults: z.number().int().min(1).max(MAX_SEARCH_RESULTS).default(100),
|
|
141
|
+
})
|
|
142
|
+
.strict(),
|
|
143
|
+
outputSchema: z.object({ matches: z.array(AddressOutput) }),
|
|
144
|
+
readOnly: true,
|
|
145
|
+
async run(input, session) {
|
|
146
|
+
if (input.pattern.length > MAX_SEARCH_PATTERN) {
|
|
147
|
+
throw new WireFailure("invalid-input", `pattern has ${input.pattern.length} bytes. The limit is ${MAX_SEARCH_PATTERN}.`);
|
|
148
|
+
}
|
|
149
|
+
if (input.pattern.every((token) => token === null)) throw new WireFailure("invalid-input", "pattern needs at least one byte that is not ??.");
|
|
150
|
+
const result = await session.memorySearch({ ...input, end: input.end ?? 0xffff });
|
|
151
|
+
return { matches: result.matches.map(formatC64Address) };
|
|
152
|
+
},
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
const Location = z
|
|
156
|
+
.object({ address: AddressInput, space: SpaceInput, view: MemoryViewInput })
|
|
157
|
+
.strict();
|
|
158
|
+
|
|
159
|
+
export const c64MemoryCompare = defineTool({
|
|
160
|
+
name: "c64_memory_compare",
|
|
161
|
+
title: "Compare C64 memory",
|
|
162
|
+
description:
|
|
163
|
+
`Compare two memory ranges of size bytes (1 to ${MAX_COMPARE_SIZE}). Each range has its own address, space and view. ` +
|
|
164
|
+
`The result tells if they are equal, how many bytes differ, and the first ${MAX_COMPARE_DIFFERENCES} differences with their offset. ` +
|
|
165
|
+
"This does not change the run state of the machine.",
|
|
166
|
+
inputSchema: z
|
|
167
|
+
.object({ left: Location, right: Location, size: z.number().int().min(1).max(MAX_COMPARE_SIZE) })
|
|
168
|
+
.strict(),
|
|
169
|
+
outputSchema: z.object({
|
|
170
|
+
equal: z.boolean(),
|
|
171
|
+
differentBytes: z.number().int().min(0),
|
|
172
|
+
firstDifferences: z.array(z.object({ offset: z.number().int().min(0), left: Byte, right: Byte })),
|
|
173
|
+
}),
|
|
174
|
+
readOnly: true,
|
|
175
|
+
async run(input, session) {
|
|
176
|
+
return session.memoryCompare(input);
|
|
177
|
+
},
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
export const c64Disassemble = defineTool({
|
|
181
|
+
name: "c64_disassemble",
|
|
182
|
+
title: "Disassemble live C64 memory",
|
|
183
|
+
description:
|
|
184
|
+
`Disassemble count instructions (1 to ${MAX_DISASSEMBLE}) from an address, as the bytes are in the live machine now. ` +
|
|
185
|
+
"Use it to read code that a program changed or unpacked at run time. It includes the undocumented opcodes. " +
|
|
186
|
+
"This does not change the run state of the machine.",
|
|
187
|
+
inputSchema: z
|
|
188
|
+
.object({
|
|
189
|
+
address: AddressInput,
|
|
190
|
+
count: z.number().int().min(1).max(MAX_DISASSEMBLE),
|
|
191
|
+
space: SpaceInput,
|
|
192
|
+
view: MemoryViewInput,
|
|
193
|
+
})
|
|
194
|
+
.strict(),
|
|
195
|
+
outputSchema: z.object({ instructions: z.array(z.object({ address: AddressOutput, bytes: HexData, text: z.string() })) }),
|
|
196
|
+
readOnly: true,
|
|
197
|
+
async run(input, session) {
|
|
198
|
+
const result = await session.disassemble(input);
|
|
199
|
+
return { instructions: result.instructions.map((instruction) => ({ ...instruction, address: formatC64Address(instruction.address) })) };
|
|
200
|
+
},
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
export const memoryTools = [c64MemoryRead, c64MemoryWrite, c64MemorySearch, c64MemoryCompare, c64Registers, c64Disassemble];
|