@henols/c64-re-tools 2.0.0-rc.9 → 2.0.1

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 (118) hide show
  1. package/README.md +97 -116
  2. package/package.json +1 -1
  3. package/skills/c64-assembler/SKILL.md +22 -6
  4. package/skills/c64-assembler/scripts/assemble.ts +42 -14
  5. package/skills/c64-assembler/scripts/lib/compare.ts +48 -0
  6. package/skills/c64-basic/SKILL.md +10 -1
  7. package/skills/c64-basic/scripts/basic.ts +3 -3
  8. package/skills/c64-disk/SKILL.md +13 -4
  9. package/skills/c64-disk/scripts/disk.ts +7 -7
  10. package/skills/c64-disk/scripts/{containers.ts → lib/containers.ts} +2 -2
  11. package/skills/c64-docs/SKILL.md +138 -0
  12. package/skills/c64-docs/references/page-types.md +141 -0
  13. package/skills/c64-docs/references/style.md +43 -0
  14. package/skills/c64-docs/scripts/generate.ts +109 -0
  15. package/skills/c64-docs/scripts/lib/links.ts +167 -0
  16. package/skills/c64-docs/scripts/lib/markdown.ts +79 -0
  17. package/skills/c64-docs/scripts/lib/reference.ts +406 -0
  18. package/skills/c64-docs/scripts/lib/source-map.ts +81 -0
  19. package/skills/c64-docs/scripts/link.ts +239 -0
  20. package/skills/c64-emulator/SKILL.md +16 -5
  21. package/skills/c64-knowledge/SKILL.md +140 -18
  22. package/skills/c64-knowledge/scripts/knowledge.ts +737 -112
  23. package/skills/c64-knowledge/scripts/lib/listing.ts +162 -0
  24. package/skills/c64-memory-map/SKILL.md +11 -2
  25. package/skills/c64-memory-map/scripts/memmap.ts +2 -2
  26. package/skills/c64-provenance/SKILL.md +66 -11
  27. package/skills/c64-provenance/scripts/{compare.ts → lib/compare.ts} +3 -3
  28. package/skills/c64-provenance/scripts/lib/library.ts +584 -0
  29. package/skills/c64-provenance/scripts/{runtime.ts → lib/runtime.ts} +2 -2
  30. package/skills/c64-provenance/scripts/{separate.ts → lib/separate.ts} +3 -3
  31. package/skills/c64-provenance/scripts/provenance.ts +242 -40
  32. package/skills/c64-reverse-engineering/SKILL.md +51 -26
  33. package/skills/c64-source/SKILL.md +76 -28
  34. package/skills/c64-source/scripts/generate.ts +425 -127
  35. package/skills/c64-source/scripts/{loader.ts → lib/loader.ts} +84 -25
  36. package/skills/c64-source/scripts/lib/source.ts +1121 -0
  37. package/skills/c64-static-analysis/SKILL.md +35 -18
  38. package/skills/c64-static-analysis/scripts/analyze.ts +184 -55
  39. package/skills/c64-static-analysis/scripts/{coverage.ts → lib/coverage.ts} +19 -25
  40. package/skills/c64-static-analysis/scripts/{findings.ts → lib/findings.ts} +6 -2
  41. package/skills/c64-static-analysis/scripts/{idioms.ts → lib/idioms.ts} +21 -12
  42. package/skills/c64-static-analysis/scripts/{instructions.ts → lib/instructions.ts} +1 -1
  43. package/skills/c64-static-analysis/scripts/lib/macros.ts +233 -0
  44. package/skills/c64-static-analysis/scripts/lib/pitfalls.ts +68 -0
  45. package/skills/c64-static-analysis/scripts/lib/pointers.ts +108 -0
  46. package/skills/c64-testing/SKILL.md +36 -7
  47. package/skills/c64-testing/references/scenario-format.md +12 -8
  48. package/skills/c64-testing/scripts/checklist.ts +3 -3
  49. package/skills/c64-testing/scripts/diff.ts +167 -0
  50. package/skills/c64-testing/scripts/lib/diff.ts +70 -0
  51. package/skills/c64-testing/scripts/{scenario.ts → lib/scenario.ts} +45 -34
  52. package/skills/c64-testing/scripts/run.ts +73 -21
  53. package/skills/c64-testing/scripts/test.ts +5 -5
  54. package/skills/c64-unpacker/SKILL.md +23 -9
  55. package/skills/c64-unpacker/scripts/{evidence.ts → lib/evidence.ts} +1 -1
  56. package/skills/c64-unpacker/scripts/{loads.ts → lib/loads.ts} +4 -4
  57. package/skills/c64-unpacker/scripts/{machine.ts → lib/machine.ts} +4 -4
  58. package/skills/c64-unpacker/scripts/{stages.ts → lib/stages.ts} +17 -5
  59. package/skills/c64-unpacker/scripts/unpack.ts +41 -24
  60. package/src/c64.ts +103 -5
  61. package/src/cli/cleanup.ts +1 -1
  62. package/src/cli/main.ts +14 -5
  63. package/src/cli/status.ts +70 -3
  64. package/src/graphics.ts +158 -0
  65. package/src/host/main.ts +25 -5
  66. package/src/host/server.ts +10 -3
  67. package/src/host/tools/petcat.ts +1 -1
  68. package/src/host/tools/status.ts +1 -1
  69. package/src/host/vice/adapter.ts +4 -3
  70. package/src/host/vice/process.ts +2 -2
  71. package/src/host/vice/screen.ts +1 -42
  72. package/src/host/vice/session.ts +196 -23
  73. package/src/host-client/autostart.ts +1 -1
  74. package/src/host-client/connect.ts +1 -1
  75. package/src/host-client/coverage.ts +2 -29
  76. package/src/host-client/image.ts +66 -0
  77. package/src/host-client/steps.ts +9 -4
  78. package/src/host-client/tools.ts +1 -0
  79. package/src/host-client/transfer.ts +39 -11
  80. package/src/host-client/vice-session.ts +33 -16
  81. package/src/knowledge/conflicts.ts +161 -0
  82. package/src/knowledge/database.ts +150 -38
  83. package/src/knowledge/expression.ts +209 -0
  84. package/src/knowledge/graph.ts +104 -0
  85. package/src/knowledge/history.ts +70 -22
  86. package/src/knowledge/import.ts +64 -55
  87. package/src/knowledge/join.ts +211 -0
  88. package/src/knowledge/program.ts +266 -0
  89. package/src/knowledge/read.ts +258 -27
  90. package/src/knowledge/schema.ts +199 -75
  91. package/src/knowledge/text.ts +415 -0
  92. package/src/knowledge/write.ts +740 -57
  93. package/src/mcp/main.ts +8 -2
  94. package/src/mcp/output.ts +15 -8
  95. package/src/mcp/server.ts +3 -1
  96. package/src/mcp/tools/debug.ts +97 -16
  97. package/src/mcp/tools/execution.ts +38 -5
  98. package/src/mcp/tools/machine.ts +11 -2
  99. package/src/mcp/tools/media.ts +18 -13
  100. package/src/mcp/tools/memory.ts +17 -8
  101. package/src/mcp/tools/video.ts +9 -9
  102. package/src/native/discover.ts +2 -2
  103. package/src/native/ghidra/analyze.ts +2 -2
  104. package/src/native/ghidra/index.ts +1 -1
  105. package/src/native/ghidra/language/c64rt_6510.slaspec +4 -1
  106. package/src/native/ghidra/scripts/C64Export.java +1 -1
  107. package/src/native/ghidra/scripts/C64Prepare.java +7 -1
  108. package/src/native/types.ts +14 -8
  109. package/{skills/c64-source/scripts → src}/opcodes.ts +75 -3
  110. package/src/package.ts +13 -3
  111. package/src/platform.ts +70 -7
  112. package/src/project.ts +180 -3
  113. package/src/protocol/messages.ts +1 -1
  114. package/src/protocol/tools.ts +2 -16
  115. package/src/protocol/vice.ts +124 -15
  116. package/src/script.ts +46 -7
  117. package/skills/c64-assembler/scripts/compare.ts +0 -84
  118. package/skills/c64-source/scripts/source.ts +0 -595
@@ -1,7 +1,7 @@
1
- // The loader of a generated source (D48): a PRG that copies each segment and
1
+ // The loader of a generated source: a PRG that copies each segment and
2
2
  // the entry state's pages $0002-$03ff into place, sets the display registers
3
- // of the VIC-II and the VIC bank, waits for the entry's raster line (D60),
4
- // sets the CIA timers and interrupt masks (D63), sets $00/$01, the stack
3
+ // of the VIC-II and the VIC bank, waits for the entry's raster line,
4
+ // sets the CIA timers and interrupt masks, sets $00/$01, the stack
5
5
  // pointer, A, X, Y and the flags, and jumps to the entry. The copies run
6
6
  // from a free page with SEI and all RAM visible, in an order where no copy
7
7
  // overwrites the packed bytes of a copy that has not run yet. The plan is
@@ -22,9 +22,9 @@ export interface EntryState {
22
22
  port: [number, number];
23
23
  /** $0000-$03ff at the entry. */
24
24
  pages: Uint8Array;
25
- /** The VIC-II at the entry (D60): $d000-$d02e, and the VIC bank (bits 0-1 of $dd00). */
25
+ /** The VIC-II at the entry: $d000-$d02e, and the VIC bank (bits 0-1 of $dd00). */
26
26
  vic?: { registers: Uint8Array; bank: number };
27
- /** CIA 1 and CIA 2 at the entry (D63). */
27
+ /** CIA 1 and CIA 2 at the entry. */
28
28
  cia?: [EntryCia, EntryCia];
29
29
  }
30
30
 
@@ -37,7 +37,7 @@ export interface EntryCia {
37
37
  }
38
38
 
39
39
  /**
40
- * The VIC-II registers that the loader sets (D60), each with the bits that a
40
+ * The VIC-II registers that the loader sets, each with the bits that a
41
41
  * read gives back: not the raster, the light pen, the interrupt registers or
42
42
  * the collisions. The raster line comes from a wait instead.
43
43
  */
@@ -125,20 +125,31 @@ export interface LoaderPlan {
125
125
  stackByte: number;
126
126
  end: number;
127
127
  text: string;
128
- /** The CIA timers and masks that the loader sets, when the entry state has them (D63). */
128
+ /** The CIA timers and masks that the loader sets, when the entry state has them. */
129
129
  cia?: CiaPlan;
130
130
  }
131
131
 
132
+ /**
133
+ * What the loader cannot set as it was at the entry. An entry state
134
+ * without CIA values (from a capture of an older version) gives no timers at all.
135
+ */
136
+ export function loaderLimits(plan: { cia?: CiaPlan }): string[] {
137
+ if (plan.cia === undefined) {
138
+ return ["The entry state has no CIA values, so the loader does not set the CIA timers. Capture the program again with c64-unpacker to get them."];
139
+ }
140
+ return plan.cia.limits;
141
+ }
142
+
132
143
  /** The label of the loader's jump to the entry, in ACME's symbol list. */
133
144
  export const LOADER_JUMP = "c64_source_loader_jump";
134
145
 
135
146
  const LOAD = 0x0801;
136
147
  const STUB = 0x080d;
137
- /** sei, ldx, the 2-page move loop and jmp. */
138
- const STUB_SIZE = 21;
148
+ /** sei, all RAM, ldx, the 2-page move loop and jmp. */
149
+ const STUB_SIZE = 25;
139
150
  /** The copier code and its three variables, without the table. */
140
151
  const COPIER_CODE_SIZE = 167;
141
- /** The VIC-II code of the copier (D60), without its two tables of one byte per register. */
152
+ /** The VIC-II code of the copier, without its two tables of one byte per register. */
142
153
  const VIC_CODE_SIZE = 44;
143
154
  const VIC_SIZE = VIC_CODE_SIZE + 2 * VIC_DISPLAY_REGISTERS.length;
144
155
  /** The copier and the stub's move of it: two pages. */
@@ -193,6 +204,31 @@ export function simulate(memory: Int16Array, copies: Copy[], expected: (address:
193
204
  return undefined;
194
205
  }
195
206
 
207
+ /** True when a read of the address with $01 = $37, as RUN leaves it, gives a ROM byte. */
208
+ const underRom = (address: number) => (address >= 0xa000 && address <= 0xbfff) || address >= 0xe000;
209
+
210
+ /**
211
+ * Why a loader stopped before its jump, from the RAM after the stop: the first
212
+ * byte of the moved copier that is not the byte of the loader file, else, when
213
+ * the copier ran, the first byte that a copy did not write as planned.
214
+ */
215
+ export function stopCause(ram: Uint8Array, plan: LoaderPlan, copierSource: number, fileByte: (address: number) => number, copied: boolean, expected: (address: number) => number): string | undefined {
216
+ for (let offset = 0; offset <= plan.end - copierSource; offset++) {
217
+ const got = ram[plan.copier + offset]!;
218
+ const wanted = fileByte(copierSource + offset);
219
+ if (got === wanted) continue;
220
+ const rom = underRom(copierSource + offset) ? `; ${hex4(copierSource + offset)} is under a ROM, so the move read the ROM` : "";
221
+ return `The copier at ${hex4(plan.copier + offset)} holds ${hex2(got)}, not ${hex2(wanted)} from ${hex4(copierSource + offset)} of the loader file${rom}.`;
222
+ }
223
+ if (!copied) return undefined;
224
+ for (const copy of plan.copies) {
225
+ for (let address = copy.target.start; address <= copy.target.end; address++) {
226
+ if (ram[address] !== expected(address)) return `The copy of ${copy.what} wrote ${hex2(ram[address]!)} at ${hex4(address)}, not ${hex2(expected(address))}.`;
227
+ }
228
+ }
229
+ return undefined;
230
+ }
231
+
196
232
  /** The VIC-II and its bank from the io part of an entry state, when it has them. */
197
233
  function readVic(io: Record<string, any> | undefined, fail: (what: string) => never): { vic: NonNullable<EntryState["vic"]> } | Record<string, never> {
198
234
  if (io?.vic === undefined) return {};
@@ -260,7 +296,7 @@ interface Op {
260
296
  }
261
297
 
262
298
  /**
263
- * The copier's CIA part (D63), after the raster wait: the timers and
299
+ * The copier's CIA part, after the raster wait: the timers and
264
300
  * interrupt masks of the entry state. A running timer is stopped, loaded
265
301
  * with its counter at the entry plus the cycles to the entry, started, and
266
302
  * given its latch, so it counts to the entry's value just at the entry.
@@ -354,9 +390,19 @@ export interface LoaderInput {
354
390
  byteAt: (address: number) => number;
355
391
  state: EntryState;
356
392
  entry: number;
393
+ /** The label of the entry in the source: the loader jumps to it, so the jump follows the code when it moves. */
394
+ entryName?: string;
357
395
  header: string[];
396
+ /** The source has macros.asm: the loader includes it after the constants. */
397
+ macros?: boolean;
358
398
  }
359
399
 
400
+ /** The label of a block: its first byte and the byte after it in the loader file, and its first address where it runs. */
401
+ const blockLabel = (start: number, part: "from" | "end" | "to") => `loader_${part}_${start.toString(16).padStart(4, "0")}`;
402
+ const plus = (label: string, offset: number) => (offset === 0 ? label : `${label}+${offset}`);
403
+ /** The label of the copier in the loader file, which the stub moves to its two pages. */
404
+ export const COPIER_SOURCE = "loader_copier_src";
405
+
360
406
  /** Plans the loader: the packed layout, the copy order and the copier page, and writes loader.asm. Refuses by name when no plan exists. */
361
407
  export function planLoader(input: LoaderInput): LoaderPlan {
362
408
  const { state } = input;
@@ -371,12 +417,15 @@ export function planLoader(input: LoaderInput): LoaderPlan {
371
417
  let at = STUB + STUB_SIZE;
372
418
  const plain: Array<Omit<Copy, "backward">> = [];
373
419
  const packedAt: number[] = [];
420
+ /** The block start of each copy in `plain`, which names its labels. */
421
+ const blockOf: number[] = [];
374
422
  for (const block of blocks) {
375
423
  packedAt.push(at);
376
424
  const target = { start: Math.max(block.range.start, 0x0002), end: block.range.end };
377
425
  if (target.start <= target.end) {
378
426
  const source = { start: at + (target.start - block.range.start), end: at + (target.end - block.range.start) };
379
427
  plain.push({ what: block.what, target, source });
428
+ blockOf.push(block.range.start);
380
429
  }
381
430
  at += block.range.end - block.range.start + 1;
382
431
  }
@@ -405,25 +454,30 @@ export function planLoader(input: LoaderInput): LoaderPlan {
405
454
  const copy = plain[index]!;
406
455
  return { ...copy, backward: copy.target.start > copy.source.start && overlaps(copy.target, copy.source) };
407
456
  });
457
+ const copyBlocks = order.map((index) => blockOf[index]!);
408
458
 
409
459
  const stackByte = 0x0100 + state.sp;
410
- const lines: string[] = [...input.header.map((line) => `; ${line}`), "", "!cpu 6510", '!source "constants.asm"', "", `* = ${hex4(LOAD)}`];
460
+ const lines: string[] = [...input.header.map((line) => `; ${line}`), "", "!cpu 6510", '!source "constants.asm"', ...(input.macros ? ['!source "macros.asm"'] : []), "", `* = ${hex4(LOAD)}`];
411
461
  lines.push(` !byte $0b,$08,$0a,$00,$9e,${[...String(STUB)].map((digit) => hex2(digit.charCodeAt(0))).join(",")},$00,$00,$00 ; 10 SYS ${STUB}`);
412
462
  lines.push(
413
463
  "!zone loader_stub {",
414
464
  " sei",
465
+ // The copier lies at the end of the loader file, which can be at $a000 or higher, under the BASIC ROM.
466
+ " lda #$34",
467
+ " sta $01",
415
468
  " ldx #0",
416
- `- lda ${hex4(copierImage)},x`,
469
+ `- lda ${COPIER_SOURCE},x`,
417
470
  ` sta ${hex4(copier)},x`,
418
- ` lda ${hex4(copierImage + 0x100)},x`,
471
+ ` lda ${COPIER_SOURCE}+$100,x`,
419
472
  ` sta ${hex4(copier + 0x100)},x`,
420
473
  " inx",
421
474
  " bne -",
422
475
  ` jmp ${hex4(copier)}`,
423
476
  "}",
424
477
  );
478
+ // The labels around each block give the copy table its numbers, so a block can change its length.
425
479
  for (const block of blocks) {
426
- lines.push("", `; ${block.what}`, `!pseudopc ${hex4(block.pseudo)} {`);
480
+ lines.push("", `; ${block.what}`, blockLabel(block.range.start, "from"), `!pseudopc ${hex4(block.pseudo)} {`, blockLabel(block.range.start, "to"));
427
481
  if (block.files !== undefined) for (const file of block.files) lines.push(`!source "${file}"`);
428
482
  else {
429
483
  for (let address = block.range.start; address <= block.range.end; address += 16) {
@@ -431,12 +485,13 @@ export function planLoader(input: LoaderInput): LoaderPlan {
431
485
  lines.push(` !byte ${Array.from({ length: last - address + 1 }, (_, offset) => hex2(block.bytes(address + offset))).join(",")}`);
432
486
  }
433
487
  }
434
- lines.push("}");
488
+ lines.push("}", blockLabel(block.range.start, "end"));
435
489
  }
436
490
  const [port0, port1] = state.port;
437
491
  lines.push(
438
492
  "",
439
493
  `; The copier runs at ${hex4(copier)}. The flags pass through ${hex4(stackByte)}, the free stack byte below the stack pointer.`,
494
+ COPIER_SOURCE,
440
495
  `!pseudopc ${hex4(copier)} {`,
441
496
  "!zone loader_copier {",
442
497
  " lda #$34",
@@ -507,8 +562,8 @@ export function planLoader(input: LoaderInput): LoaderPlan {
507
562
  ` ldx #${hex2(state.x)}`,
508
563
  ` ldy #${hex2(state.y)}`,
509
564
  " plp",
510
- // A global label: the check of the loader stops here, before an interrupt can run (F-32).
511
- `${LOADER_JUMP} jmp ${hex4(input.entry)}`,
565
+ // A global label: the check of the loader stops here, before an interrupt can run.
566
+ `${LOADER_JUMP} jmp ${input.entryName ?? hex4(input.entry)}`,
512
567
  ".count !word 0",
513
568
  ".index !byte 0",
514
569
  ".back !byte 0",
@@ -520,12 +575,16 @@ export function planLoader(input: LoaderInput): LoaderPlan {
520
575
  ]),
521
576
  ".table",
522
577
  );
523
- for (const copy of copies) {
524
- const length = copy.target.end - copy.target.start + 1;
525
- const read = copy.backward ? copy.source.end : copy.source.start;
526
- const write = copy.backward ? copy.target.end : copy.target.start;
527
- lines.push(` !word ${hex4(length)},${hex4(read)},${hex4(write)} : !byte ${copy.backward ? 1 : 0} ; ${copy.what}`);
528
- }
578
+ copies.forEach((copy, index) => {
579
+ // A block that starts at $0000 skips $00/$01, which the loader sets last.
580
+ const start = copyBlocks[index]!;
581
+ const skip = copy.target.start - start;
582
+ const [from, end, to] = [blockLabel(start, "from"), blockLabel(start, "end"), blockLabel(start, "to")];
583
+ const length = skip === 0 ? `${end} - ${from}` : `${end} - ${from} - ${skip}`;
584
+ const read = copy.backward ? `${end} - 1` : plus(from, skip);
585
+ const write = copy.backward ? `${to} + ${end} - ${from} - 1` : plus(to, skip);
586
+ lines.push(` !word ${length}, ${read}, ${write} : !byte ${copy.backward ? 1 : 0} ; ${copy.what}`);
587
+ });
529
588
  lines.push(" !word 0", "}", "}");
530
589
 
531
590
  return {
@@ -540,7 +599,7 @@ export function planLoader(input: LoaderInput): LoaderPlan {
540
599
  }
541
600
 
542
601
  /**
543
- * The copier's VIC-II part (D60): with the I/O area visible it sets the
602
+ * The copier's VIC-II part: with the I/O area visible it sets the
544
603
  * display registers and the VIC bank of the entry state, then waits for the
545
604
  * entry's raster line, so the program starts in the same phase of the frame.
546
605
  */