@henols/c64-re-tools 2.0.0-rc.13 → 2.0.0-rc.15

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 (58) hide show
  1. package/package.json +1 -1
  2. package/skills/c64-assembler/SKILL.md +7 -2
  3. package/skills/c64-assembler/scripts/assemble.ts +13 -5
  4. package/skills/c64-disk/SKILL.md +1 -1
  5. package/skills/c64-disk/scripts/disk.ts +1 -1
  6. package/skills/c64-emulator/SKILL.md +8 -4
  7. package/skills/c64-memory-map/SKILL.md +2 -2
  8. package/skills/c64-provenance/SKILL.md +1 -1
  9. package/skills/c64-provenance/scripts/provenance.ts +4 -4
  10. package/skills/c64-reverse-engineering/SKILL.md +2 -1
  11. package/skills/c64-source/SKILL.md +1 -1
  12. package/skills/c64-source/scripts/generate.ts +3 -3
  13. package/skills/c64-static-analysis/SKILL.md +4 -3
  14. package/skills/c64-static-analysis/scripts/analyze.ts +16 -10
  15. package/skills/c64-static-analysis/scripts/{coverage.ts → lib/coverage.ts} +20 -0
  16. package/skills/c64-static-analysis/scripts/{idioms.ts → lib/idioms.ts} +14 -3
  17. package/skills/c64-testing/SKILL.md +3 -2
  18. package/skills/c64-testing/references/scenario-format.md +1 -1
  19. package/skills/c64-testing/scripts/checklist.ts +1 -1
  20. package/skills/c64-testing/scripts/{scenario.ts → lib/scenario.ts} +11 -9
  21. package/skills/c64-testing/scripts/run.ts +4 -4
  22. package/skills/c64-testing/scripts/test.ts +1 -1
  23. package/skills/c64-unpacker/SKILL.md +4 -1
  24. package/skills/c64-unpacker/scripts/unpack.ts +4 -4
  25. package/src/c64.ts +10 -0
  26. package/src/cli/cleanup.ts +1 -1
  27. package/src/cli/main.ts +2 -1
  28. package/src/cli/status.ts +69 -2
  29. package/src/host/main.ts +14 -1
  30. package/src/host/vice/session.ts +71 -15
  31. package/src/host-client/steps.ts +7 -2
  32. package/src/host-client/vice-session.ts +5 -3
  33. package/src/mcp/server.ts +1 -0
  34. package/src/mcp/tools/debug.ts +22 -7
  35. package/src/mcp/tools/execution.ts +17 -3
  36. package/src/mcp/tools/machine.ts +11 -2
  37. package/src/mcp/tools/media.ts +1 -0
  38. package/src/mcp/tools/memory.ts +7 -2
  39. package/src/mcp/tools/video.ts +3 -4
  40. package/src/native/ghidra/language/c64rt_6510.slaspec +4 -1
  41. package/src/protocol/vice.ts +32 -6
  42. /package/skills/c64-assembler/scripts/{compare.ts → lib/compare.ts} +0 -0
  43. /package/skills/c64-disk/scripts/{containers.ts → lib/containers.ts} +0 -0
  44. /package/skills/c64-provenance/scripts/{compare.ts → lib/compare.ts} +0 -0
  45. /package/skills/c64-provenance/scripts/{library.ts → lib/library.ts} +0 -0
  46. /package/skills/c64-provenance/scripts/{runtime.ts → lib/runtime.ts} +0 -0
  47. /package/skills/c64-provenance/scripts/{separate.ts → lib/separate.ts} +0 -0
  48. /package/skills/c64-source/scripts/{loader.ts → lib/loader.ts} +0 -0
  49. /package/skills/c64-source/scripts/{opcodes.ts → lib/opcodes.ts} +0 -0
  50. /package/skills/c64-source/scripts/{source.ts → lib/source.ts} +0 -0
  51. /package/skills/c64-static-analysis/scripts/{findings.ts → lib/findings.ts} +0 -0
  52. /package/skills/c64-static-analysis/scripts/{instructions.ts → lib/instructions.ts} +0 -0
  53. /package/skills/c64-static-analysis/scripts/{macros.ts → lib/macros.ts} +0 -0
  54. /package/skills/c64-static-analysis/scripts/{pointers.ts → lib/pointers.ts} +0 -0
  55. /package/skills/c64-unpacker/scripts/{evidence.ts → lib/evidence.ts} +0 -0
  56. /package/skills/c64-unpacker/scripts/{loads.ts → lib/loads.ts} +0 -0
  57. /package/skills/c64-unpacker/scripts/{machine.ts → lib/machine.ts} +0 -0
  58. /package/skills/c64-unpacker/scripts/{stages.ts → lib/stages.ts} +0 -0
@@ -12,6 +12,8 @@ import { AddressInput, AddressOutput, Byte, defineTool, HexData, HexDataInput, M
12
12
  /** With out, a read or a disassembly can be larger than one answer of the session: it is read in parts (D39). */
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",
@@ -182,6 +184,7 @@ export const c64MemoryCompare = defineTool({
182
184
  description:
183
185
  `Compare two memory ranges of size bytes (1 to ${MAX_COMPARE_SIZE}). Each range has its own address, space and view. ` +
184
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. " +
185
188
  "This does not change the run state of the machine.",
186
189
  inputSchema: z
187
190
  .object({ left: Location, right: Location, size: z.number().int().min(1).max(MAX_COMPARE_SIZE) })
@@ -189,11 +192,13 @@ export const c64MemoryCompare = defineTool({
189
192
  outputSchema: z.object({
190
193
  equal: z.boolean(),
191
194
  differentBytes: z.number().int().min(0),
192
- firstDifferences: z.array(z.object({ offset: z.number().int().min(0), left: Byte, right: Byte })),
195
+ firstDifferences: z.array(z.object({ offset: z.number().int().min(0), left: HexByte, right: HexByte })),
193
196
  }),
194
197
  readOnly: true,
195
198
  async run(input, session) {
196
- return session.memoryCompare(input);
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) })) };
197
202
  },
198
203
  });
199
204
 
@@ -3,7 +3,7 @@
3
3
 
4
4
  import { z } from "zod";
5
5
 
6
- import { formatC64Address } from "../../c64.ts";
6
+ import { formatC64Address, formatSprite, formatVicii } from "../../c64.ts";
7
7
  import { WireFailure } from "../../protocol/messages.ts";
8
8
  import { CIA_SELECTIONS, MAX_MASK_RECTANGLES, MAX_MEMORY_READ, MAX_OBSERVE_BYTES, MAX_OBSERVE_RANGES, SPACES, VICII_MODES, type ObserveParams } from "../../protocol/vice.ts";
9
9
  import { OutInput, ProjectInput, writeOut, WrittenOutput } from "../output.ts";
@@ -31,8 +31,7 @@ export const c64Vicii = defineTool({
31
31
  }),
32
32
  readOnly: true,
33
33
  async run(_input, session) {
34
- const state = await session.vicii();
35
- return { ...state, screenAddress: formatC64Address(state.screenAddress), graphicsAddress: formatC64Address(state.graphicsAddress) };
34
+ return formatVicii(await session.vicii());
36
35
  },
37
36
  });
38
37
 
@@ -62,7 +61,7 @@ export const c64Sprite = defineTool({
62
61
  readOnly: true,
63
62
  async run(input, session) {
64
63
  const result = await session.sprites(input.sprites ?? []);
65
- return { sprites: result.sprites.map((sprite) => ({ ...sprite, dataAddress: formatC64Address(sprite.dataAddress) })) };
64
+ return { sprites: result.sprites.map(formatSprite) };
66
65
  },
67
66
  });
68
67
 
@@ -263,13 +263,16 @@ REL: target is rel [ target = inst_next + rel; ] { export *:2 target; }
263
263
  }
264
264
 
265
265
  # BRK skips its padding byte: the return address is two bytes on.
266
+ # The vector at $FFFE is in ROM unless the program banks the KERNAL out, and
267
+ # the analysis cannot know the banking. Thus BRK ends the flow, and Ghidra
268
+ # does not follow the RAM bytes at $FFFE of a flat image (R2-07).
266
269
  :BRK is op=0x00 {
267
270
  *:2 (SP - 1) = inst_next + 1;
268
271
  SP = SP - 2;
269
272
  pushP(1);
270
273
  I = 1;
271
274
  local vector:2 = 0xfffe;
272
- goto [*:2 vector];
275
+ return [*:2 vector];
273
276
  }
274
277
 
275
278
  :CLC is op=0x18 { C = 0; }
@@ -337,8 +337,16 @@ export interface MachineStatus {
337
337
  pc?: number;
338
338
  /** True while a cartridge from an autostart is attached (D64). */
339
339
  cartridge: boolean;
340
+ /** Present only while stopped by a user breakpoint or watchpoint, or by a JAM. */
341
+ stopReason?: PointStopReason;
342
+ /** The id of the breakpoint or watchpoint that stopped the machine. */
343
+ pointId?: number;
340
344
  }
341
345
 
346
+ /** What stopped the machine outside a request that stops it. */
347
+ export const POINT_STOP_REASONS = ["breakpoint", "watchpoint", "jam"] as const;
348
+ export type PointStopReason = (typeof POINT_STOP_REASONS)[number];
349
+
342
350
  export interface MemoryReadParams {
343
351
  address: number;
344
352
  size: number;
@@ -406,6 +414,10 @@ export interface ExecutionResult {
406
414
  pc?: number;
407
415
  executed?: number;
408
416
  advancedFrames?: number;
417
+ /** advance-frames only: frames when all frames ran, else what stopped the run early. */
418
+ stopReason?: "frames" | PointStopReason;
419
+ /** The id of the breakpoint or watchpoint that stopped the run. */
420
+ pointId?: number;
409
421
  }
410
422
 
411
423
  /** c64_run_until targets. */
@@ -425,6 +437,8 @@ export interface RunUntilResult {
425
437
  /** The PC of the CPU in `space`: the CPU that stopped. */
426
438
  pc: number;
427
439
  space: Space;
440
+ /** The id of the breakpoint or watchpoint that stopped the run. */
441
+ pointId?: number;
428
442
  }
429
443
 
430
444
  export interface ViceOperations {
@@ -469,7 +483,8 @@ export interface ViceOperations {
469
483
  backtrace: { params: { depth: number; space: Space }; result: { frames: BacktraceFrame[] } };
470
484
  /** `cycles` is a decimal string. */
471
485
  timing: { params: { action: (typeof TIMING_ACTIONS)[number] }; result: { started: boolean } | { cycles: string } };
472
- profile: { params: { limit: number }; result: { entries: ProfileEntry[] } };
486
+ /** clear starts the profiler again from zero. */
487
+ profile: { params: { action: "read"; limit: number } | { action: "clear" }; result: { entries: ProfileEntry[] } | { cleared: boolean } };
473
488
  memmap: {
474
489
  params: { action: "read"; start: number; end: number; maxRanges: number } | { action: "clear" };
475
490
  /** `next`: more ranges follow from this address; read again from there. */
@@ -646,9 +661,11 @@ export function validateViceParams<O extends ViceOperation>(op: O, params: unkno
646
661
  return result as ViceOperations[O]["params"];
647
662
  }
648
663
  case "profile": {
649
- onlyFields(params, ["limit"]);
664
+ onlyFields(params, params.action === "clear" ? ["action"] : ["action", "limit"]);
665
+ if (params.action === "clear") return { action: "clear" } as ViceOperations[O]["params"];
666
+ if (params.action !== "read") invalid("action must be read or clear");
650
667
  if (!isInteger(params.limit, 1, MAX_PROFILE)) invalid(`limit must be an integer from 1 to ${MAX_PROFILE}`);
651
- return { limit: params.limit } as ViceOperations[O]["params"];
668
+ return { action: "read", limit: params.limit } as ViceOperations[O]["params"];
652
669
  }
653
670
  case "memmap": {
654
671
  onlyFields(params, params.action === "clear" ? ["action"] : ["action", "start", "end", "maxRanges"]);
@@ -1011,7 +1028,9 @@ export function validateViceResult<O extends ViceOperation>(op: O, value: unknow
1011
1028
  typeof value.warp === "boolean" &&
1012
1029
  typeof value.window === "boolean" &&
1013
1030
  (value.pc === undefined || isInteger(value.pc, 0, 0xffff)) &&
1014
- typeof value.cartridge === "boolean";
1031
+ typeof value.cartridge === "boolean" &&
1032
+ (value.stopReason === undefined || isOneOf(POINT_STOP_REASONS, value.stopReason)) &&
1033
+ (value.pointId === undefined || isInteger(value.pointId, 1, Number.MAX_SAFE_INTEGER));
1015
1034
  if (!ok) throw new ProtocolError("status result is malformed");
1016
1035
  break;
1017
1036
  }
@@ -1080,7 +1099,8 @@ export function validateViceResult<O extends ViceOperation>(op: O, value: unknow
1080
1099
  isOneOf(STOP_REASONS, value.stopReason) &&
1081
1100
  isOneOf(RUN_STATES, value.state) &&
1082
1101
  isInteger(value.pc, 0, 0xffff) &&
1083
- isOneOf(SPACES, value.space);
1102
+ isOneOf(SPACES, value.space) &&
1103
+ (value.pointId === undefined || isInteger(value.pointId, 1, Number.MAX_SAFE_INTEGER));
1084
1104
  if (!ok) throw new ProtocolError("runUntil result is malformed");
1085
1105
  break;
1086
1106
  }
@@ -1089,7 +1109,9 @@ export function validateViceResult<O extends ViceOperation>(op: O, value: unknow
1089
1109
  isOneOf(RUN_STATES, value.state) &&
1090
1110
  (value.pc === undefined || isInteger(value.pc, 0, 0xffff)) &&
1091
1111
  (value.executed === undefined || isInteger(value.executed, 0, MAX_EXECUTION_COUNT)) &&
1092
- (value.advancedFrames === undefined || isInteger(value.advancedFrames, 0, MAX_EXECUTION_COUNT));
1112
+ (value.advancedFrames === undefined || isInteger(value.advancedFrames, 0, MAX_EXECUTION_COUNT)) &&
1113
+ (value.stopReason === undefined || value.stopReason === "frames" || isOneOf(POINT_STOP_REASONS, value.stopReason)) &&
1114
+ (value.pointId === undefined || isInteger(value.pointId, 1, Number.MAX_SAFE_INTEGER));
1093
1115
  if (!ok) throw new ProtocolError("execution result is malformed");
1094
1116
  break;
1095
1117
  }
@@ -1198,6 +1220,10 @@ export function validateViceResult<O extends ViceOperation>(op: O, value: unknow
1198
1220
  break;
1199
1221
  }
1200
1222
  case "profile": {
1223
+ if ("cleared" in value) {
1224
+ if (value.cleared !== true) throw new ProtocolError("profile result is malformed");
1225
+ break;
1226
+ }
1201
1227
  const ok =
1202
1228
  Array.isArray(value.entries) &&
1203
1229
  value.entries.every(