@henols/c64-re-tools 2.1.0-rc.1 → 2.1.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.
Files changed (103) hide show
  1. package/package.json +1 -1
  2. package/skills/c64-assembler/SKILL.md +24 -9
  3. package/skills/c64-assembler/scripts/assembler.ts +95 -0
  4. package/skills/c64-basic/SKILL.md +17 -9
  5. package/skills/c64-basic/scripts/basic.ts +26 -36
  6. package/skills/c64-disk/SKILL.md +17 -7
  7. package/skills/c64-disk/scripts/disk.ts +130 -104
  8. package/skills/c64-disk/scripts/lib/containers.ts +10 -12
  9. package/skills/c64-docs/SKILL.md +47 -28
  10. package/skills/c64-docs/references/style.md +6 -6
  11. package/skills/c64-docs/scripts/docs.ts +323 -0
  12. package/skills/c64-docs/scripts/lib/source-map.ts +1 -1
  13. package/skills/c64-emulator/SKILL.md +11 -7
  14. package/skills/c64-knowledge/SKILL.md +34 -21
  15. package/skills/c64-knowledge/scripts/knowledge.ts +597 -718
  16. package/skills/c64-knowledge/scripts/lib/listing.ts +5 -7
  17. package/skills/c64-memory-map/SKILL.md +18 -10
  18. package/skills/c64-memory-map/scripts/memory-map.ts +81 -0
  19. package/skills/c64-provenance/SKILL.md +33 -24
  20. package/skills/c64-provenance/scripts/lib/compare.ts +8 -12
  21. package/skills/c64-provenance/scripts/lib/leftovers.ts +19 -20
  22. package/skills/c64-provenance/scripts/lib/library.ts +15 -25
  23. package/skills/c64-provenance/scripts/lib/runtime.ts +12 -37
  24. package/skills/c64-provenance/scripts/lib/separate.ts +9 -10
  25. package/skills/c64-provenance/scripts/provenance.ts +177 -279
  26. package/skills/c64-reverse-engineering/SKILL.md +13 -12
  27. package/skills/c64-source/SKILL.md +28 -16
  28. package/skills/c64-source/scripts/lib/loader.ts +52 -77
  29. package/skills/c64-source/scripts/lib/source.ts +33 -42
  30. package/skills/c64-source/scripts/{generate.ts → source.ts} +68 -109
  31. package/skills/c64-static-analysis/SKILL.md +24 -13
  32. package/skills/c64-static-analysis/scripts/lib/coverage.ts +3 -4
  33. package/skills/c64-static-analysis/scripts/lib/findings.ts +2 -2
  34. package/skills/c64-static-analysis/scripts/lib/instructions.ts +1 -1
  35. package/skills/c64-static-analysis/scripts/lib/macros.ts +6 -8
  36. package/skills/c64-static-analysis/scripts/{analyze.ts → static-analysis.ts} +58 -118
  37. package/skills/c64-testing/SKILL.md +48 -32
  38. package/skills/c64-testing/references/recording-format.md +48 -19
  39. package/skills/c64-testing/references/scenario-format.md +24 -19
  40. package/skills/c64-testing/scripts/{diff.ts → lib/diff-files.ts} +18 -69
  41. package/skills/c64-testing/scripts/lib/diff.ts +5 -3
  42. package/skills/c64-testing/scripts/lib/play.ts +244 -54
  43. package/skills/c64-testing/scripts/lib/recording.ts +153 -120
  44. package/skills/c64-testing/scripts/{replay.ts → lib/replay.ts} +28 -86
  45. package/skills/c64-testing/scripts/{run.ts → lib/runs.ts} +36 -59
  46. package/skills/c64-testing/scripts/lib/scenario.ts +73 -81
  47. package/skills/c64-testing/scripts/testing.ts +189 -0
  48. package/skills/c64-unpacker/SKILL.md +42 -25
  49. package/skills/c64-unpacker/scripts/lib/evidence.ts +3 -4
  50. package/skills/c64-unpacker/scripts/lib/loads.ts +12 -10
  51. package/skills/c64-unpacker/scripts/lib/machine.ts +0 -2
  52. package/skills/c64-unpacker/scripts/lib/stages.ts +14 -19
  53. package/skills/c64-unpacker/scripts/{unpack.ts → unpacker.ts} +109 -211
  54. package/src/c64.ts +35 -20
  55. package/src/cli/main.ts +2 -2
  56. package/src/errors.ts +38 -0
  57. package/src/graphics.ts +3 -1
  58. package/src/host/server.ts +3 -2
  59. package/src/host/vice/session.ts +4 -4
  60. package/src/host-client/entry-state.ts +106 -0
  61. package/src/host-client/image.ts +21 -10
  62. package/src/host-client/steps.ts +285 -108
  63. package/src/host-client/transfer.ts +21 -15
  64. package/src/host-client/vice-session.ts +1 -1
  65. package/src/knowledge/database.ts +3 -23
  66. package/src/knowledge/expression.ts +8 -11
  67. package/src/knowledge/history.ts +2 -2
  68. package/src/knowledge/import.ts +13 -14
  69. package/src/knowledge/names.ts +50 -0
  70. package/src/knowledge/program.ts +11 -10
  71. package/src/knowledge/read.ts +16 -21
  72. package/src/knowledge/schema.ts +9 -3
  73. package/src/knowledge/text.ts +5 -5
  74. package/src/knowledge/write.ts +93 -137
  75. package/src/mcp/inputs.ts +105 -0
  76. package/src/mcp/output.ts +2 -13
  77. package/src/mcp/server.ts +12 -60
  78. package/src/mcp/tools/debug.ts +17 -12
  79. package/src/mcp/tools/execution.ts +5 -32
  80. package/src/mcp/tools/input.ts +7 -32
  81. package/src/mcp/tools/media.ts +6 -5
  82. package/src/mcp/tools/memory.ts +11 -38
  83. package/src/mcp/tools/video.ts +6 -5
  84. package/src/native/c64run.ts +1 -1
  85. package/src/native/dxa.ts +7 -6
  86. package/src/native/ghidra/analyze.ts +2 -1
  87. package/src/native/ghidra/index.ts +2 -1
  88. package/src/native/processes.ts +4 -3
  89. package/src/native/status.ts +2 -2
  90. package/src/native/types.ts +3 -7
  91. package/src/opcodes.ts +13 -13
  92. package/src/platform.ts +9 -9
  93. package/src/project.ts +16 -26
  94. package/src/protocol/messages.ts +3 -21
  95. package/src/protocol/vice.ts +7 -7
  96. package/src/script.ts +491 -49
  97. package/skills/c64-assembler/scripts/assemble.ts +0 -138
  98. package/skills/c64-docs/scripts/generate.ts +0 -109
  99. package/skills/c64-docs/scripts/link.ts +0 -239
  100. package/skills/c64-memory-map/scripts/memmap.ts +0 -92
  101. package/skills/c64-testing/scripts/checklist.ts +0 -51
  102. package/skills/c64-testing/scripts/record.ts +0 -96
  103. package/skills/c64-testing/scripts/test.ts +0 -65
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@henols/c64-re-tools",
3
- "version": "2.1.0-rc.1",
3
+ "version": "2.1.0",
4
4
  "description": "Agent-friendly Commodore 64 engineering toolkit for reverse engineering, debugging, development and testing.",
5
5
  "license": "MIT",
6
6
  "author": "Henrik Olsson",
@@ -23,7 +23,7 @@ This skill does not run the program and does not change project knowledge.
23
23
  The script runs ACME on this machine. It copies the source root to a temporary directory, assembles it there and writes the program into the project. The host runtime is not necessary.
24
24
 
25
25
  ```
26
- node <skill>/scripts/assemble.ts --source-root src --entry main.a --out build/game.prg
26
+ node <skill>/scripts/assembler.ts build src main.a --out build/game.prg
27
27
  ```
28
28
 
29
29
  | Option | Meaning |
@@ -37,14 +37,26 @@ node <skill>/scripts/assemble.ts --source-root src --entry main.a --out build/ga
37
37
 
38
38
  The script copies the full source root. Put all files that the program includes under it. Version control directories stay out.
39
39
 
40
- The script prints one JSON object. The target CPU is the 6510. The output is a C64 PRG, or the bytes only with `--plain`.
40
+ The target CPU is the 6510. The output is a C64 PRG, or the bytes only with `--plain`.
41
+
42
+ ### Calling the script
43
+
44
+ - Call a script as `node <skill>/scripts/<script>.ts <command> [subjects] [options]`. The command comes first.
45
+ - `<script>.ts --help` lists the commands. `<script>.ts <command> --help` gives the subjects and the options of one command. The help is JSON: `{"text": "..."}`.
46
+ - An address is `$` and 1 to 4 hex digits, for example `'$c000'` or `'$fb'`, or a name. A name is a symbol, else an analyzer name that only one analyzer gives. Quote an address in a shell.
47
+ - A range is `<start>-<end>`, for example `'$c000-$cfff'`. In a JSON file, a range is `start` and `end`.
48
+ - Bytes in a JSON file are hex in a field `data`, for example `"data": "a900"`.
49
+ - The result is one JSON object on one line. A file that the script writes shows as `{"out", "bytes", "sha256"}`. A list shows as `{"count", "listed"}`, and `rest` tells where to get the rest of a long list.
50
+ - An error is `{"error": {"code": "...", "message": "..."}}`. The message tells what is wrong and what to do.
51
+ - The exit status is 0 for done or PASS, 1 for FAIL, 2 for an error and 3 for INCONCLUSIVE.
41
52
 
42
53
  ### Project folder
43
54
 
44
55
  - Paths are relative to the project folder. The project folder is the nearest folder at or above the working directory that holds `.c64-re-tools/`.
45
56
  - If the working directory is not in a project and has projects below it, the script refuses and lists them. Then give `--project <folder>` to each script.
46
57
  - Write each file that you make for your work in `.c64-re-tools/local/` of the project. Git ignores that folder. Examples are results, batches, step files, runs, snapshots and memory dumps.
47
- - Move a file out of `.c64-re-tools/local/` only when it is the evidence for a finding. Move it before you give it to `--evidence` of c64-knowledge. `--evidence` refuses a file in `.c64-re-tools/local/`.
58
+ - Move a file out of `.c64-re-tools/local/` only when it is the evidence for a finding. Move it before you give it to `--evidence test:<file>` or `--evidence file:<file>` of c64-knowledge. These two refuse a file in `.c64-re-tools/local/`.
59
+ - Do not move a coverage map after `coverage` of c64-knowledge imported it. `--evidence coverage:<map>` names the map by its path at the import. The knowledge keeps the coverage and the SHA-256 of the map, so the evidence stays valid.
48
60
  - Put a run that is evidence in `runs/<name>/`, with its run file, its results and the snapshot files that it uses. Change the paths in the run file to the new paths. Then git keeps them, and another person can do the run again.
49
61
  - A shell redirect (`>`) is relative to the working directory, not to the project folder. From the folder above the project, write `<folder>/.c64-re-tools/local/result.json`.
50
62
 
@@ -52,11 +64,11 @@ The script prints one JSON object. The target CPU is the 6510. The output is a C
52
64
 
53
65
  1. Find the source root and the entry source. The entry source sets its own start address with `* = $0801` or similar.
54
66
  2. Run the script.
55
- 3. If `assembled` is `true`, the PRG is at `output`. `loadRange` tells where it loads. `symbols` gives each symbol:
67
+ 3. If `result` is `PASS`, the PRG is at `out`. `loadRange` tells where it loads. `symbols` gives each symbol:
56
68
  - Kind `address` has a value in `loadRange`. It gives the `address`.
57
69
  - Kind `constant` has a value outside `loadRange`. It gives the `value` as `$` and four or more hex digits. A label in a `!pseudopc` block can be a constant, because its value is the run address, not the load address.
58
70
  - `used` is `false` when the source does not use the symbol.
59
- 4. If `assembled` is `false`, read `diagnostics`. Each one has a severity, a file, a line and a message. Correct the source and run the script again.
71
+ 4. If `result` is `FAIL` and `assembled` is `false`, read `diagnostics`. Each one has a severity, a file, a line and a message. Correct the source and run the script again.
60
72
  5. To run the program, give the PRG path to the c64-emulator skill.
61
73
 
62
74
  The source of the c64-source skill has switches. Each switch is 0 when the build does not define it. `--define CRACK=1` puts in the patches of the switch `CRACK`. With `--compare`, only the bytes of those patches are different.
@@ -74,15 +86,18 @@ A symbol from the rebuilt source can give the address of a routine in the rebuil
74
86
 
75
87
  ## Result
76
88
 
77
- The PRG in the project at the output path, with its load range and symbols. Or the source diagnostics when the assembly failed.
89
+ `result` is `PASS` or `FAIL`.
78
90
 
79
- With `--compare`, `compare` gives `identical` and one check for each segment of the program in the knowledge. A check is `identical`, `different` with the first different address, or `not covered` when the program does not hold the segment.
91
+ - `PASS`: the PRG is in the project at `out`, with its load range and symbols.
92
+ - `FAIL` with `assembled: false`: the source has errors. `diagnostics` gives them.
93
+ - `FAIL` with `assembled: true`: `--compare` found differences. The PRG is at `out`.
80
94
 
81
- The exit status is 0 for a PRG. The exit status is 1 for `assembled: false`, for a `--compare` that is not identical, and for an error. The exit status is 2 for a wrong argument.
95
+ With `--compare`, `compare` gives `identical` and `segments`: one check for each segment of the program in the knowledge. The `result` of a check is `identical`, `different` with `firstDifference`, or `not covered` when the build does not hold the segment.
82
96
 
83
97
  ## Failure and conflicts
84
98
 
85
- - `assembled: false`: the source has errors. This is a normal result. Correct the source.
99
+ - `FAIL` with `assembled: false`: the source has errors. Correct the source.
100
+ - `FAIL` with `assembled: true`: the build is not identical to the program in the knowledge. Read `compare.segments`.
86
101
  - `installation-incomplete`, or `machine-unavailable` with a message that the host runtime does not run: stop the task. Give the message to the user and wait for the user. The message gives the remedy. Do not make a replacement for the tool or the host runtime, for example your own disk parser, disassembler or emulator script.
87
102
  - `invalid-input`: an option or a path is wrong. Read the message and correct the call.
88
103
 
@@ -0,0 +1,95 @@
1
+ // The c64-assembler script: assembles project source with ACME, which it runs
2
+ // itself, and writes the program into the project.
3
+
4
+ import { formatC64Address } from "#src/c64.ts";
5
+ import { Refusal } from "#src/errors.ts";
6
+ import { readProjectTree, writeReported } from "#src/host-client/transfer.ts";
7
+ import { openForRead } from "#src/knowledge/database.ts";
8
+ import { knowledgeNames } from "#src/knowledge/names.ts";
9
+ import { requireProgram, type ProgramImage } from "#src/knowledge/program.ts";
10
+ import { assemble } from "#src/native/acme.ts";
11
+ import { localToolContext } from "#src/native/local.ts";
12
+ import type { AcmeParams } from "#src/native/types.ts";
13
+ import { OPTIONS, runScript, UsageError } from "#src/script.ts";
14
+ import { compareBuild } from "./lib/compare.ts";
15
+
16
+ function parseDefine(text: string): [string, number | boolean] {
17
+ const match = /^([A-Za-z_][A-Za-z0-9_]*)=(.+)$/.exec(text);
18
+ if (match === null) throw new UsageError(`--define needs NAME=VALUE, not ${text}`);
19
+ const value = match[2]!;
20
+ if (value === "true" || value === "false") return [match[1]!, value === "true"];
21
+ if (/^\$[0-9a-fA-F]+$/.test(value)) return [match[1]!, Number.parseInt(value.slice(1), 16)];
22
+ if (/^-?\d+$/.test(value)) return [match[1]!, Number(value)];
23
+ throw new UsageError(`--define ${match[1]} needs an integer or true/false, not ${value}`);
24
+ }
25
+
26
+ await runScript({
27
+ name: "assembler",
28
+ summary: "Assembles project source with ACME and writes the program into the project.",
29
+ names: knowledgeNames,
30
+ commands: [
31
+ {
32
+ name: "build",
33
+ summary: "Assembles the source and writes the program. result is PASS when it assembles (and with --compare, when it is the program of the knowledge), else FAIL with the diagnostics or the differences.",
34
+ subjects: [
35
+ { name: "<source-root>", help: "the folder in the project to assemble from: all of it is sent" },
36
+ { name: "<entry>", help: "the main source file, relative to the source root" },
37
+ ],
38
+ options: {
39
+ out: { ...OPTIONS.out, value: "<file.prg>", help: "where to write the program, relative to the project folder", required: true },
40
+ include: { kind: "text", value: "<dir>", multiple: true, help: "an include folder, relative to the source root" },
41
+ define: { kind: "text", value: "NAME=VALUE", multiple: true, help: "an assembler symbol: an integer ($hex allowed) or true/false" },
42
+ "set-pc": { kind: "address", value: "<address>", help: "the start address when the source sets none, for example '$0801'" },
43
+ compare: { kind: "flag", help: "check the program byte by byte against the program in the knowledge, each segment" },
44
+ space: { ...OPTIONS.space, help: "with --compare: the knowledge space of the program: c64 (the default), drive8, or bank0 to bank255" },
45
+ plain: { kind: "flag", help: "write the bytes only, without the 2-byte load address of a PRG" },
46
+ "all-symbols": { kind: "flag", help: "list also the constants that the source does not use" },
47
+ },
48
+ notes: "Symbol values are $ and at least four hex digits. Put the program into the knowledge with knowledge.ts image of the c64-knowledge skill before --compare.",
49
+ run: async (call) => {
50
+ const [sourceRoot, entry] = [call.subject(0), call.subject(1)];
51
+ const out = call.string("out")!;
52
+ // Read the program before ACME runs, so a project without it stops at once.
53
+ let program: ProgramImage | undefined;
54
+ if (call.flag("compare")) {
55
+ const db = openForRead();
56
+ try {
57
+ program = requireProgram(db, call.space);
58
+ } finally {
59
+ db?.close();
60
+ }
61
+ }
62
+ const setPc = call.address("set-pc");
63
+ const tree = readProjectTree(sourceRoot);
64
+ if (!tree.files.some((file) => file.path === entry)) throw new Refusal("not-found", `${entry} is not a file in ${sourceRoot}. Give the main source file relative to the source root.`);
65
+ const params: AcmeParams = { files: tree.files, entrySource: entry, includeDirs: call.strings("include"), defines: Object.fromEntries(call.strings("define").map(parseDefine)), ...(setPc === undefined ? {} : { setPc }) };
66
+
67
+ const { result, attachments } = await assemble(params, tree.contents, localToolContext());
68
+ if (!result.assembled) return { result: "FAIL", assembled: false, diagnostics: result.diagnostics };
69
+ const plain = call.flag("plain");
70
+ const written = writeReported(out, plain ? attachments![0]!.subarray(2) : attachments![0]!, { what: "--out" });
71
+ const range = result.loadRange!;
72
+ const compared = program === undefined ? undefined : compareBuild(attachments![0]!, program);
73
+ // An include file of platform names gives many constants that the program does not use.
74
+ const all = call.flag("all-symbols");
75
+ const listed = result.symbols!.filter((symbol) => all || symbol.kind === "address" || symbol.used);
76
+ return {
77
+ // A build that differs from its reference is a failed check.
78
+ result: compared !== undefined && !compared.identical ? "FAIL" : "PASS",
79
+ assembled: true,
80
+ ...written,
81
+ ...(plain ? { plain: true } : {}),
82
+ loadRange: { start: formatC64Address(range.start), end: formatC64Address(range.end), bytes: range.bytes },
83
+ symbols: listed.map((symbol) =>
84
+ symbol.kind === "address"
85
+ ? { name: symbol.name, kind: "address", address: formatC64Address(symbol.address), used: symbol.used }
86
+ : { name: symbol.name, kind: "constant", value: `$${symbol.value.toString(16).padStart(4, "0")}`, used: symbol.used },
87
+ ),
88
+ ...(all ? {} : { unusedConstants: result.symbols!.length - listed.length }),
89
+ diagnostics: result.diagnostics,
90
+ ...(compared === undefined ? {} : { compare: compared }),
91
+ };
92
+ },
93
+ },
94
+ ],
95
+ });
@@ -20,23 +20,33 @@ This skill does not run the program and does not change project knowledge.
20
20
  The script sends the program to the host runtime. The host decodes it with petcat and finds the handoffs.
21
21
 
22
22
  ```
23
- node <skill>/scripts/basic.ts extracted/game.prg
23
+ node <skill>/scripts/basic.ts list extracted/game.prg
24
24
  ```
25
25
 
26
- The script prints one JSON object. Addresses are `$` and four hex digits.
26
+ ### Calling the script
27
+
28
+ - Call a script as `node <skill>/scripts/<script>.ts <command> [subjects] [options]`. The command comes first.
29
+ - `<script>.ts --help` lists the commands. `<script>.ts <command> --help` gives the subjects and the options of one command. The help is JSON: `{"text": "..."}`.
30
+ - An address is `$` and 1 to 4 hex digits, for example `'$c000'` or `'$fb'`, or a name. A name is a symbol, else an analyzer name that only one analyzer gives. Quote an address in a shell.
31
+ - A range is `<start>-<end>`, for example `'$c000-$cfff'`. In a JSON file, a range is `start` and `end`.
32
+ - Bytes in a JSON file are hex in a field `data`, for example `"data": "a900"`.
33
+ - The result is one JSON object on one line. A file that the script writes shows as `{"out", "bytes", "sha256"}`. A list shows as `{"count", "listed"}`, and `rest` tells where to get the rest of a long list.
34
+ - An error is `{"error": {"code": "...", "message": "..."}}`. The message tells what is wrong and what to do.
35
+ - The exit status is 0 for done or PASS, 1 for FAIL, 2 for an error and 3 for INCONCLUSIVE.
27
36
 
28
37
  ### Project folder
29
38
 
30
39
  - Paths are relative to the project folder. The project folder is the nearest folder at or above the working directory that holds `.c64-re-tools/`.
31
40
  - If the working directory is not in a project and has projects below it, the script refuses and lists them. Then give `--project <folder>` to each script.
32
41
  - Write each file that you make for your work in `.c64-re-tools/local/` of the project. Git ignores that folder. Examples are results, batches, step files, runs, snapshots and memory dumps.
33
- - Move a file out of `.c64-re-tools/local/` only when it is the evidence for a finding. Move it before you give it to `--evidence` of c64-knowledge. `--evidence` refuses a file in `.c64-re-tools/local/`.
42
+ - Move a file out of `.c64-re-tools/local/` only when it is the evidence for a finding. Move it before you give it to `--evidence test:<file>` or `--evidence file:<file>` of c64-knowledge. These two refuse a file in `.c64-re-tools/local/`.
43
+ - Do not move a coverage map after `coverage` of c64-knowledge imported it. `--evidence coverage:<map>` names the map by its path at the import. The knowledge keeps the coverage and the SHA-256 of the map, so the evidence stays valid.
34
44
  - Put a run that is evidence in `runs/<name>/`, with its run file, its results and the snapshot files that it uses. Change the paths in the run file to the new paths. Then git keeps them, and another person can do the run again.
35
45
  - A shell redirect (`>`) is relative to the working directory, not to the project folder. From the folder above the project, write `<folder>/.c64-re-tools/local/result.json`.
36
46
 
37
47
  ## Workflow
38
48
 
39
- 1. Run the script on the program.
49
+ 1. Run `list` on the program.
40
50
  2. Read `listing`. Each line starts with its BASIC line number. Keywords show in lower case. Control characters in strings show in braces, for example `{clr}`.
41
51
  3. Read `handoffs`. Each handoff has a `kind` (`sys` or `usr`) and the BASIC `line`.
42
52
  - A `sys` handoff with an `address`: BASIC calls the machine code at that address. The script calculates a SYS argument with numbers, π, `+`, `-`, `*`, `/` and brackets. For example, `SYS π*656` calls `$080c`.
@@ -57,16 +67,14 @@ The entry point of the machine code can be durable knowledge. The c64-reverse-en
57
67
 
58
68
  ## Result
59
69
 
60
- The listing and the handoff addresses. Or `decoded: false` with a reason when the file is not a BASIC program.
61
-
62
- The exit status is 0 for a listing. The exit status is 1 for `decoded: false` and for an error. The exit status is 2 for a wrong argument.
70
+ The listing and the handoff addresses.
63
71
 
64
72
  ## Failure and conflicts
65
73
 
66
- - `decoded: false`: the file is not a BASIC program. This is a normal result. Usually the file is machine code. Give it to the c64-static-analysis skill. A memory dump also gives `decoded: false`: its first two bytes are not a load address, and its bytes go past `$ffff`.
74
+ - `invalid-input` with "is no BASIC program": the file is not a BASIC program. Usually the file is machine code. Give it to the c64-static-analysis skill. A memory dump also gives this error: its first two bytes are not a load address, and its bytes go past `$ffff`.
67
75
  - An empty `handoffs` list: the BASIC does not start machine code with SYS or USR. Do not make up a handoff. Look for a POKE into a vector or a LOAD of another file in the listing.
68
76
  - `installation-incomplete`, or `machine-unavailable` with a message that the host runtime does not run: stop the task. Give the message to the user and wait for the user. The message gives the remedy. Do not make a replacement for the tool or the host runtime, for example your own disk parser, disassembler or emulator script.
69
- - `not-found` or `invalid-input`: the path or an argument is wrong. Read the message and correct the call.
77
+ - `not-found` or another `invalid-input`: the path or an argument is wrong. Read the message and correct the call.
70
78
 
71
79
  ## Handoffs
72
80
 
@@ -2,45 +2,35 @@
2
2
  // through the Host Runtime and reports its machine-code handoffs.
3
3
 
4
4
  import { statSync } from "node:fs";
5
- import { parseArgs } from "node:util";
6
5
 
7
6
  import { formatC64Address } from "#src/c64.ts";
7
+ import { Refusal } from "#src/errors.ts";
8
8
  import { decodeBasic } from "#src/host-client/tools.ts";
9
9
  import { resolveProjectPath } from "#src/project.ts";
10
- import { runScript, UsageError } from "#src/script.ts";
10
+ import { runScript } from "#src/script.ts";
11
11
 
12
- const USAGE = `basic.ts <program>
13
-
14
- <program> is a PRG file relative to the project folder. The result is one
15
- JSON object. Addresses are $ and four hex digits.`;
16
-
17
- async function run(argv: string[]): Promise<{ output: unknown; failed: boolean }> {
18
- const { values, positionals } = parseArgs({ args: argv, strict: true, allowPositionals: true, options: { help: { type: "boolean" } } });
19
- if (values.help) throw new UsageError("");
20
- if (positionals.length !== 1) throw new UsageError("give one program");
21
- const program = positionals[0]!;
22
- const result = await decodeBasic({ program });
23
- if (!result.decoded) return { output: { decoded: false, reason: result.reason }, failed: true };
24
- const size = statSync(resolveProjectPath(program)).size - 2;
25
- const end = result.loadAddress + size - 1;
26
- return {
27
- output: {
28
- decoded: true,
29
- loadRange: { start: formatC64Address(result.loadAddress), end: formatC64Address(end) },
30
- // From here to the end of the load range, the file holds no BASIC (often machine code).
31
- basicEnd: result.basicEnd <= end ? formatC64Address(result.basicEnd) : null,
32
- listing: result.listing,
33
- handoffs: result.handoffs.map((handoff) => ("address" in handoff ? { ...handoff, address: formatC64Address(handoff.address) } : handoff)),
12
+ await runScript({
13
+ name: "basic",
14
+ summary: "Decodes a C64 BASIC V2 program with petcat and reports its machine-code handoffs: SYS and USR.",
15
+ commands: [
16
+ {
17
+ name: "list",
18
+ summary: "The BASIC lines of a PRG, where the BASIC ends, and the handoffs to machine code.",
19
+ subjects: [{ name: "<program>", help: "a PRG file, relative to the project folder" }],
20
+ run: async (call) => {
21
+ const program = call.subject(0);
22
+ const result = await decodeBasic({ program });
23
+ if (!result.decoded) throw new Refusal("invalid-input", `${program} is no BASIC program: ${result.reason}`);
24
+ const size = statSync(resolveProjectPath(program)).size - 2;
25
+ const end = result.loadAddress + size - 1;
26
+ return {
27
+ loadRange: { start: formatC64Address(result.loadAddress), end: formatC64Address(end) },
28
+ // From here to the end of the load range, the file holds no BASIC (often machine code).
29
+ basicEnd: result.basicEnd <= end ? formatC64Address(result.basicEnd) : null,
30
+ listing: result.listing,
31
+ handoffs: result.handoffs.map((handoff) => ("address" in handoff ? { ...handoff, address: formatC64Address(handoff.address) } : handoff)),
32
+ };
33
+ },
34
34
  },
35
- failed: false,
36
- };
37
- }
38
-
39
- await runScript(
40
- async (argv) => {
41
- const { output, failed } = await run(argv);
42
- if (failed) process.exitCode = 1;
43
- return output;
44
- },
45
- { usage: USAGE },
46
- );
35
+ ],
36
+ });
@@ -23,7 +23,7 @@ This skill does not change the disk image and does not change project knowledge.
23
23
  The script sends a disk image to the host runtime. The host runs c1541 and sends the result back. The script writes an extracted file into the project. It reads T64 and CRT files itself.
24
24
 
25
25
  ```
26
- node <skill>/scripts/disk.ts <action> <image> [name] [--out <file>]
26
+ node <skill>/scripts/disk.ts <command> <image> [name] [--out <file>]
27
27
  ```
28
28
 
29
29
  | Need | Command |
@@ -38,16 +38,26 @@ node <skill>/scripts/disk.ts <action> <image> [name] [--out <file>]
38
38
  | The banks of a cartridge | `directory original/game.crt` |
39
39
  | Write each bank of a cartridge as a PRG | `crt original/game.crt --out cart` |
40
40
 
41
- The script prints one JSON object.
42
-
43
41
  File names show the characters that the C64 shows in a directory list. A byte that has no character shows as `{$xx}`, for example `{$c1}`. Write the name in the same form. The letter case of a name is not important. Put the name in single quotes in a shell, for example `'GAME {$c1}'`. A name that starts with `-` needs `--` before it, after all options, for example `read d.d64 --out x.prg -- '-----'`.
44
42
 
43
+ ### Calling the script
44
+
45
+ - Call a script as `node <skill>/scripts/<script>.ts <command> [subjects] [options]`. The command comes first.
46
+ - `<script>.ts --help` lists the commands. `<script>.ts <command> --help` gives the subjects and the options of one command. The help is JSON: `{"text": "..."}`.
47
+ - An address is `$` and 1 to 4 hex digits, for example `'$c000'` or `'$fb'`, or a name. A name is a symbol, else an analyzer name that only one analyzer gives. Quote an address in a shell.
48
+ - A range is `<start>-<end>`, for example `'$c000-$cfff'`. In a JSON file, a range is `start` and `end`.
49
+ - Bytes in a JSON file are hex in a field `data`, for example `"data": "a900"`.
50
+ - The result is one JSON object on one line. A file that the script writes shows as `{"out", "bytes", "sha256"}`. A list shows as `{"count", "listed"}`, and `rest` tells where to get the rest of a long list.
51
+ - An error is `{"error": {"code": "...", "message": "..."}}`. The message tells what is wrong and what to do.
52
+ - The exit status is 0 for done or PASS, 1 for FAIL, 2 for an error and 3 for INCONCLUSIVE.
53
+
45
54
  ### Project folder
46
55
 
47
56
  - Paths are relative to the project folder. The project folder is the nearest folder at or above the working directory that holds `.c64-re-tools/`.
48
57
  - If the working directory is not in a project and has projects below it, the script refuses and lists them. Then give `--project <folder>` to each script.
49
58
  - Write each file that you make for your work in `.c64-re-tools/local/` of the project. Git ignores that folder. Examples are results, batches, step files, runs, snapshots and memory dumps.
50
- - Move a file out of `.c64-re-tools/local/` only when it is the evidence for a finding. Move it before you give it to `--evidence` of c64-knowledge. `--evidence` refuses a file in `.c64-re-tools/local/`.
59
+ - Move a file out of `.c64-re-tools/local/` only when it is the evidence for a finding. Move it before you give it to `--evidence test:<file>` or `--evidence file:<file>` of c64-knowledge. These two refuse a file in `.c64-re-tools/local/`.
60
+ - Do not move a coverage map after `coverage` of c64-knowledge imported it. `--evidence coverage:<map>` names the map by its path at the import. The knowledge keeps the coverage and the SHA-256 of the map, so the evidence stays valid.
51
61
  - Put a run that is evidence in `runs/<name>/`, with its run file, its results and the snapshot files that it uses. Change the paths in the run file to the new paths. Then git keeps them, and another person can do the run again.
52
62
  - A shell redirect (`>`) is relative to the working directory, not to the project folder. From the folder above the project, write `<folder>/.c64-re-tools/local/result.json`.
53
63
 
@@ -80,13 +90,13 @@ The script reads T64 and CRT files itself. The host runtime is not necessary for
80
90
 
81
91
  ## Result
82
92
 
83
- The directory, the BAM, an entry or a sector chain as JSON. Or the extracted file in the project at the output path.
93
+ The directory, the BAM, an entry or a sector chain as JSON.
84
94
 
85
- The exit status is 0 for a result with the file. The exit status is 1 for `found: false` and for an error. The exit status is 2 for a wrong argument.
95
+ `read` writes the file at the output path. Its result gives the `name` and the file as `{out, bytes, sha256}`. `crt` gives the header, `chips` and `files`: one item for each PRG and one for `crt.json`.
86
96
 
87
97
  ## Failure and conflicts
88
98
 
89
- - `found: false`: the image has no file with that name. This is a normal result. Read the directory again and compare the names.
99
+ - `not-found`: the image file is missing, or the image has no file with that name. Read the directory again and compare the names.
90
100
  - `media-error`: the image has a defect or is not a disk image. The message tells the problem, for example a sector chain that loops. Tell the user. Do not change the image.
91
101
  - `installation-incomplete`, or `machine-unavailable` with a message that the host runtime does not run: stop the task. Give the message to the user and wait for the user. The message gives the remedy. Do not make a replacement for the tool or the host runtime, for example your own disk parser, disassembler or emulator script.
92
102
  - `invalid-input`: an argument or a path is wrong. Read the message and correct the call.