@henols/c64-re-tools 2.0.0-rc.8 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (118) hide show
  1. package/README.md +98 -115
  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 +7 -4
  63. package/src/cli/status.ts +70 -3
  64. package/src/graphics.ts +158 -0
  65. package/src/host/main.ts +43 -8
  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 +1 -1
  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/platform.ts +70 -7
  111. package/src/project.ts +180 -3
  112. package/src/protocol/messages.ts +1 -1
  113. package/src/protocol/tools.ts +2 -16
  114. package/src/protocol/vice.ts +124 -15
  115. package/src/script.ts +46 -7
  116. package/src/trace.ts +55 -23
  117. package/skills/c64-assembler/scripts/compare.ts +0 -84
  118. package/skills/c64-source/scripts/source.ts +0 -595
package/README.md CHANGED
@@ -1,47 +1,59 @@
1
1
  # c64-re-tools
2
2
 
3
- An agent-friendly Commodore 64 engineering toolkit for reverse engineering
4
- unknown applications and for developing, debugging, building and testing new
5
- C64 applications.
6
-
7
- An agent drives a real VICE emulator through an MCP server, and uses skills
8
- for the rest: disk images, BASIC, static analysis with DXA and Ghidra, a project
9
- knowledge database, the ACME assembler, unpacking, release comparison and
10
- functional-equivalence tests.
11
-
12
- Architecture in one line: one MCP process owns one VICE instance and is
13
- VICE-only; a Host Runtime on the graphical host owns VICE and the tools that
14
- come with it (c1541, petcat); skill scripts call the Host Runtime for those,
15
- run ACME, DXA and Ghidra themselves, and keep project knowledge local in
16
- `.c64-re-tools/knowledge.db`.
17
- The design is in [`docs/redesign/`](docs/redesign/README.md), and
18
- [`docs/plan.md`](docs/plan.md) tracks the implementation.
3
+ Let an AI agent work on Commodore 64 programs. The agent drives a real VICE
4
+ emulator and runs ACME, DXA and Ghidra. Use it to build new C64 apps and games,
5
+ and to reverse engineer old ones.
19
6
 
20
- ## Install
7
+ It works with Claude Code, Codex, pi, opencode, Gemini, Copilot, Cursor and
8
+ Windsurf.
21
9
 
22
- You need Node.js 24 or newer, and VICE 3.9 or later (`x64sc`, `c1541`, `petcat`) with its ROM files; use the latest VICE release. `status` and the host name the known flaws of an older VICE. ACME, DXA and Ghidra are needed only for the skills that use them: [docs/install-tools.md](docs/install-tools.md) has the steps for each system.
10
+ ## What it can do
23
11
 
24
- Start the Host Runtime and leave it running:
12
+ **Build new apps and games**
25
13
 
26
- ```
27
- npx -y --package=@henols/c64-re-tools@latest c64-re-tools-host
28
- ```
14
+ - Assemble 6510 source with ACME, with errors by file and line, and symbols.
15
+ - Load and run the program in VICE. Set breakpoints and watchpoints, step, and read the CPU history and the call stack.
16
+ - Read, write, search and compare memory, and disassemble it.
17
+ - Show the VIC-II, sprite, SID and CIA state, and capture the screen.
18
+ - Type on the keyboard and move the joystick.
19
+ - Count cycles, profile the code, and wait for a raster line.
20
+ - Run many steps in one go and keep screens, snapshots and coverage as files.
21
+ - Tell what any address or register value means on the C64.
22
+
23
+ **Reverse engineer existing programs**
29
24
 
30
- Install into your project and start the agent:
25
+ - Read D64, D71, D81, G64, T64 and CRT images, list BASIC and find the SYS address.
26
+ - Find packers and capture the unpacked program from the emulator.
27
+ - Analyse the code with DXA and Ghidra. The toolkit brings its own NMOS 6510 language for Ghidra, with all 256 opcodes.
28
+ - Keep names, regions and comments as text in the project, ready for git.
29
+ - Compare releases, and find routines that several programs share.
30
+ - Generate ACME source that rebuilds the program byte for byte, and its documentation.
31
+ - Test that a rebuild behaves as the original: PASS, FAIL or INCONCLUSIVE.
32
+
33
+ ## How it works
34
+
35
+ VICE and the Host Runtime run on your computer, where VICE can open a window.
36
+ The agent runs in your project, preferably in a dev container. It reaches VICE
37
+ through an MCP server, and it runs ACME, DXA and Ghidra through its skills.
38
+
39
+ ## Install
40
+
41
+ You need Node.js 24 or newer and VICE 3.9 or later (`x64sc`, `c1541`, `petcat`)
42
+ with its ROM files on the host. Use the latest VICE release.
43
+
44
+ Start the Host Runtime on the host, and leave it running:
31
45
 
32
46
  ```
33
- cd my-c64-project
34
- npx -y @henols/c64-re-tools@latest install --target claude
35
- claude
47
+ npx -y --package=@henols/c64-re-tools@latest c64-re-tools-host
36
48
  ```
37
49
 
38
- `npx -y @henols/c64-re-tools@latest status` shows what works.
39
-
40
- Until 2.0.0 is released, `@latest` is the old 1.1.0. Use the prerelease instead. For the prerelease, a dev container and debugging, see [docs/debug-install.md](docs/debug-install.md).
50
+ `--log-console` prints each call, and `--log-dir <directory>` writes them to a file.
41
51
 
42
52
  ### Dev container
43
53
 
44
- The agent runs in the container, VICE and the Host Runtime on the host. In `.devcontainer/devcontainer.json`:
54
+ This is the preferred way. The container gets ACME, dxa, Java 21 and Ghidra,
55
+ installs the toolkit into the project when it is created, and updates it at
56
+ each start. Add to `.devcontainer/devcontainer.json`:
45
57
 
46
58
  ```json
47
59
  "features": {
@@ -50,127 +62,98 @@ The agent runs in the container, VICE and the Host Runtime on the host. In `.dev
50
62
  "runArgs": ["--add-host=host.docker.internal:host-gateway"]
51
63
  ```
52
64
 
53
- Until 2.0.0 is released, the `latest` URL has no Feature. Use the prerelease: `https://github.com/henols/c64-re-tools/releases/download/v2.0.0-rc.4/devcontainer-feature-c64-re-tools.tgz` with `{ "target": "claude", "channel": "next" }`.
65
+ `target` is the harness, or a comma list such as `claude,codex`. The Feature
66
+ needs a Debian or Ubuntu image. `runArgs` is for Docker on Linux.
67
+
68
+ ### Without a container
54
69
 
55
- The Feature installs ACME, dxa, Java 21 and Ghidra, puts the toolkit into the project when the container is created, and updates it at each start. `target` is a harness or a comma list. `channel` is `latest` (default) or `next`. Debian and Ubuntu images only. `runArgs` is for Docker on Linux. Start the Host Runtime on the host as above.
70
+ Install ACME, dxa and Ghidra as in [docs/install-tools.md](docs/install-tools.md).
71
+ Then, in your project:
56
72
 
57
- ### Settings
73
+ ```
74
+ npx -y @henols/c64-re-tools@latest install --target claude
75
+ ```
58
76
 
59
- | Variable | Read by | Meaning |
60
- | --- | --- | --- |
61
- | `C64RT_VICE` | host | Full path of `x64sc` when it is not on `PATH`. |
62
- | `C64RT_C1541`, `C64RT_PETCAT` | host | Full path of the tool when it is not on `PATH`. |
63
- | `C64RT_ACME`, `C64RT_DXA` | skills, CLI | Full path of the tool when it is not on `PATH`. |
64
- | `C64RT_GHIDRA` | skills, CLI | The Ghidra installation directory, when `analyzeHeadless` is not on `PATH`. |
65
- | `C64RT_HOST` | MCP, skills, CLI | `host:port` of the Host Runtime when it is not on 127.0.0.1, `host.docker.internal` or `host.containers.internal` at 6464. |
66
- | `C64RT_VIDEO` | MCP | `pal` (default) or `ntsc`, fixed for the life of the MCP process. |
67
- | `C64RT_TRACE` | host, MCP, skills, CLI | A directory for a trace of what the programs do, for a person who debugs a run. See [docs/debug-install.md](docs/debug-install.md). Nothing of the trace reaches the agent. |
77
+ `--target` is `claude`, `codex`, `pi`, `opencode`, `gemini`, `copilot`, `cursor`
78
+ or `windsurf`. Without it, `install` installs for all of them.
68
79
 
69
- Ghidra runs with its own settings directory for each request. The toolkit
70
- brings its own NMOS 6510 language (all 256 opcodes); your Ghidra installation
71
- and settings stay unchanged.
80
+ Check what works:
72
81
 
73
- ## Use it with an agent
82
+ ```
83
+ npx -y @henols/c64-re-tools@latest status
84
+ ```
74
85
 
75
- 1. Install for your harness: `--target claude`, `codex`, `pi`, `opencode`, `gemini`, `copilot`, `cursor` or `windsurf`. Without `--target`, `install` installs for all of them.
76
- 2. Start the Host Runtime and leave it running.
77
- 3. Start the harness in the project, for example `claude` or `codex`, or open the project in Cursor or Windsurf. The harness starts the MCP server and finds the skills by itself.
78
- 4. Ask for the result you want, in plain words. The agent picks the skills and the `c64_*` tools.
86
+ ## Use it
79
87
 
80
- Examples:
88
+ Start the agent in the project, for example `claude`, and ask for what you want:
81
89
 
82
90
  ```
83
- Reverse engineer original/game.d64. Find how it starts, unpack it if it is packed,
84
- and name the main routines in project knowledge.
91
+ Write a game in ACME where a sprite moves with the joystick and the border
92
+ flashes when it hits the edge. Build it, run it, and show me the screen.
85
93
 
86
- Find the routine that draws the score, and explain how it works.
94
+ The music stops when the title screen starts. Find out why.
87
95
 
88
- Write a program in ACME that scrolls a text at the bottom of the screen.
89
- Build it, run it in the emulator, and write a test for it.
96
+ Reverse engineer original/game.d64. Find how it starts, unpack it if it is
97
+ packed, and name the main routines.
90
98
 
91
- Show me the emulator window.
99
+ Find the routine that draws the score, and explain how it works.
92
100
  ```
93
101
 
94
- The agent keeps what it learns about the program in `.c64-re-tools/knowledge.db`. Commit that file with the project.
102
+ The agent keeps what it learns about a program as text in
103
+ `.c64-re-tools/knowledge/`. Commit that folder with the project. If a command
104
+ reports conflicts after a `git pull`, run `knowledge.ts merge`.
105
+
106
+ Several programs in one repository: install once at the root and give each
107
+ program its own folder. A folder with `.c64-re-tools/` is a project.
95
108
 
96
109
  ## Skills
97
110
 
98
111
  | Skill | Use |
99
112
  | --- | --- |
100
- | c64-reverse-engineering | Coordinates an investigation and picks the specialist for the next unknown. |
101
- | c64-emulator | Runs and observes the program in VICE through the MCP tools. |
102
- | c64-static-analysis | A fast first pass with DXA and a deeper pass with Ghidra into knowledge, with seeds from knowledge and decompilation. |
103
- | c64-knowledge | Reads and writes symbols, regions, comments and references, with history. |
113
+ | c64-assembler | Builds ACME source, with errors and symbols. |
114
+ | c64-emulator | Runs, debugs and observes the program in VICE. |
115
+ | c64-memory-map | What an address or a register value means. |
116
+ | c64-testing | Runs scenarios: original against rebuild, or many steps on one program. |
104
117
  | c64-disk | Directory, BAM, entries, sector chains and extraction of disk images. |
105
118
  | c64-basic | BASIC V2 listings and the SYS/USR handoff to machine code. |
106
- | c64-unpacker | Packing evidence, and capture of the unpacked program from the emulator. |
107
- | c64-memory-map | Platform meaning of addresses and register values. |
108
- | c64-provenance | Byte comparison of releases. |
109
- | c64-source | ACME source from the image and the knowledge, checked byte by byte, and a loader for the entry state. |
110
- | c64-assembler | ACME builds with diagnostics and symbols. |
111
- | c64-testing | Original-against-rebuild scenarios with PASS, FAIL or INCONCLUSIVE. |
112
-
113
- ## Examples
119
+ | c64-unpacker | Packing evidence, and capture of the unpacked program. |
120
+ | c64-static-analysis | Analysis with DXA and Ghidra into the knowledge. |
121
+ | c64-knowledge | Symbols, regions, comments and references, with history. |
122
+ | c64-provenance | Compares releases, and keeps a library of shared routines. |
123
+ | c64-source | ACME source from the knowledge, checked byte for byte. |
124
+ | c64-docs | Documentation of a program from its knowledge and source. |
125
+ | c64-reverse-engineering | Leads an investigation and picks the next skill. |
114
126
 
115
- Reverse engineering, from an unknown disk to a verified rebuild (the commands
116
- that the skills run for the agent; `<skills>` is the installed skills
117
- directory):
127
+ ## Settings
118
128
 
119
- ```
120
- node <skills>/c64-disk/scripts/disk.ts directory original/game.d64
121
- node <skills>/c64-disk/scripts/disk.ts read original/game.d64 GAME --out extracted/game.prg
122
- node <skills>/c64-basic/scripts/basic.ts extracted/game.prg # the SYS address, e.g. $080d
123
- node <skills>/c64-unpacker/scripts/unpack.ts inspect extracted/game.prg
124
- node <skills>/c64-static-analysis/scripts/analyze.ts extracted/game.prg --analyzer dxa --listing analysis/game.lst
125
- node <skills>/c64-static-analysis/scripts/analyze.ts extracted/game.prg --entry '$080d'
126
- node <skills>/c64-knowledge/scripts/knowledge.ts rename '$080d' main --kind routine --reason 'entry from BASIC'
127
- node <skills>/c64-static-analysis/scripts/analyze.ts extracted/game.prg --entry '$080d' --decompile '$080d'
128
- node <skills>/c64-assembler/scripts/assemble.ts --source-root src --entry game.a --out build/game.prg > build/game.json
129
- node <skills>/c64-testing/scripts/test.ts tests/counts.json # PASS, FAIL or INCONCLUSIVE
130
- ```
131
-
132
- Development: build with the c64-assembler skill, then load and debug with the
133
- MCP tools (`c64_program_load`, `c64_breakpoint`, `c64_run_until`,
134
- `c64_memory_read`, `c64_screen`), and test with the c64-testing skill.
129
+ | Variable | Meaning |
130
+ | --- | --- |
131
+ | `C64RT_VICE`, `C64RT_C1541`, `C64RT_PETCAT` | Full path of the VICE tool, when it is not on `PATH` (host). |
132
+ | `C64RT_ACME`, `C64RT_DXA` | Full path of the tool, when it is not on `PATH`. |
133
+ | `C64RT_GHIDRA` | The Ghidra installation directory, when `analyzeHeadless` is not on `PATH`. |
134
+ | `C64RT_HOST` | `host:port` of the Host Runtime, when it is not on 127.0.0.1, `host.docker.internal` or `host.containers.internal` at port 6464. |
135
+ | `C64RT_VIDEO` | `pal` (default) or `ntsc`. |
136
+ | `C64RT_TRACE` | A directory for a trace of a run. See [docs/debug-install.md](docs/debug-install.md). |
135
137
 
136
138
  ## Developing
137
139
 
138
- To work on the toolkit itself, Node 24 or newer and pnpm are required; install pnpm yourself (for example
139
- `corepack enable pnpm`). `packageManager` in `package.json` pins its version.
140
+ To work on the toolkit itself you need Node 24 or newer and pnpm.
140
141
 
141
142
  ```
142
143
  pnpm install --frozen-lockfile
143
- pnpm typecheck # tsc checks everything; it emits nothing
144
- pnpm test # node --test on src/**/*.test.ts and test/**/*.test.ts
144
+ pnpm typecheck
145
+ pnpm test
146
+ pnpm host # the Host Runtime from this checkout
145
147
  ```
146
148
 
147
- Unit tests sit beside their source as `src/**/*.test.ts`; `test/` holds
148
- integration and end-to-end tests. Node runs the TypeScript directly (type
149
- stripping), so relative imports name the `.ts` file. Skill scripts import
150
- `src/` as `#src/...` (the `imports` map in `package.json`); an installed skill
151
- gets the `src/` modules it reaches and its own `package.json` with that map.
152
-
153
- Tests that need real tools are opt-in. Without them they are reported as
154
- skipped, never as passed:
149
+ Tests that need real tools are skipped unless you turn them on:
155
150
 
156
151
  ```
157
152
  C64RT_LIVE_VICE=/usr/bin/x64sc C64RT_GHIDRA=/opt/ghidra pnpm test
158
153
  ```
159
154
 
160
- ACME, DXA, c1541 and petcat tests run when the tools are on `PATH`.
161
-
162
- To release, push a tag. See [docs/releasing.md](docs/releasing.md).
163
-
164
- ## Layout
165
-
166
- ```
167
- src/ # mcp, host, host-client, native, knowledge, cli; unit tests beside the source
168
- skills/ # the skills: SKILL.md, scripts (TypeScript) and references
169
- distribution/ # the AP SDK plugin definition: skills with the src/ modules they reach
170
- test/ # fixtures, integration and end-to-end tests
171
- docs/redesign/ # the design
172
- ```
155
+ To release, see [docs/releasing.md](docs/releasing.md).
173
156
 
174
157
  ## License
175
158
 
176
- MIT. See [`LICENSE`](./LICENSE).
159
+ MIT. See [LICENSE](LICENSE).
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@henols/c64-re-tools",
3
- "version": "2.0.0-rc.8",
3
+ "version": "2.0.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",
@@ -13,9 +13,9 @@ This skill does not run the program and does not change project knowledge.
13
13
 
14
14
  ## Inputs
15
15
 
16
- - The source root: the project directory that holds all source files of the program.
16
+ - The source root: the folder in the project that holds all source files of the program.
17
17
  - The entry source: the main file, relative to the source root.
18
- - The output path: where to write the PRG, relative to the project directory.
18
+ - The output path: where to write the PRG, relative to the project folder.
19
19
  - Optional include directories, symbol definitions and a start address.
20
20
 
21
21
  ## Tools / execution path
@@ -31,20 +31,36 @@ node <skill>/scripts/assemble.ts --source-root src --entry main.a --out build/ga
31
31
  | `--include lib` | An include directory, relative to the source root. Use it again for more directories. |
32
32
  | `--define DEBUG=1` | An assembler symbol: an integer, also as `$` hex, or `true` or `false`. Use it again for more symbols. |
33
33
  | `--set-pc '$0801'` | The start address when the source does not set one. |
34
- | `--compare unpacked/image.json` | Check the program byte by byte against a reference. The reference is the `image.json` of the c64-unpacker skill, a PRG, or 64 KiB of memory. |
34
+ | `--compare` | Check the program byte by byte against the program in the knowledge, each segment. Put it in with `image` of the c64-knowledge skill. `--space drive8` or `--space bank0` compares with the program of that space. |
35
+ | `--plain` | Write only the bytes of the program, without the 2-byte load address of a PRG. `loadRange` still tells where the bytes go. |
36
+ | `--all-symbols` | List all symbols. Without it, the list does not include the constants that the source does not use. `unusedConstants` gives their count. |
35
37
 
36
38
  The script copies the full source root. Put all files that the program includes under it. Version control directories stay out.
37
39
 
38
- The script prints one JSON object. The target CPU is the 6510 and the output is always a C64 PRG.
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`.
41
+
42
+ ### Project folder
43
+
44
+ - 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
+ - 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
+ - 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/`.
48
+ - 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
+ - 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`.
39
50
 
40
51
  ## Workflow
41
52
 
42
53
  1. Find the source root and the entry source. The entry source sets its own start address with `* = $0801` or similar.
43
54
  2. Run the script.
44
- 3. If `assembled` is `true`, the PRG is at `output`. `loadRange` tells where it loads. `symbols` gives each symbol as an `address` or a `value`.
55
+ 3. If `assembled` is `true`, the PRG is at `output`. `loadRange` tells where it loads. `symbols` gives each symbol:
56
+ - Kind `address` has a value in `loadRange`. It gives the `address`.
57
+ - 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
+ - `used` is `false` when the source does not use the symbol.
45
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.
46
60
  5. To run the program, give the PRG path to the c64-emulator skill.
47
61
 
62
+ 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.
63
+
48
64
  ## Knowledge
49
65
 
50
66
  Automatic write:
@@ -60,7 +76,7 @@ A symbol from the rebuilt source can give the address of a routine in the rebuil
60
76
 
61
77
  The PRG in the project at the output path, with its load range and symbols. Or the source diagnostics when the assembly failed.
62
78
 
63
- With `--compare`, `compare` gives `identical` and one check for each segment of the reference. A check is `identical`, `different` with the first different address, or `not covered` when the program does not hold the segment.
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.
64
80
 
65
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.
66
82
 
@@ -12,20 +12,28 @@ import { localToolContext } from "#src/native/local.ts";
12
12
  import type { AcmeParams } from "#src/native/types.ts";
13
13
  import { resolveProjectPath } from "#src/project.ts";
14
14
  import { runScript, UsageError } from "#src/script.ts";
15
- import { compareBuild } from "./compare.ts";
15
+ import { compareBuild } from "./lib/compare.ts";
16
+ import { KnowledgeError, openForRead } from "#src/knowledge/database.ts";
17
+ import { requireProgram, type ProgramImage } from "#src/knowledge/program.ts";
18
+ import { DEFAULT_SPACE, SPACE } from "#src/knowledge/schema.ts";
16
19
 
17
20
  const USAGE = `assemble.ts --source-root <dir> --entry <file> --out <file.prg> [options]
18
21
 
19
- --source-root <dir> project directory to assemble from; all of it is sent
22
+ --source-root <dir> folder in the project to assemble from; all of it is sent
20
23
  --entry <file> the main source file, relative to the source root
21
24
  --out <file.prg> where to write the program, relative to the project
22
25
  --include <dir> include directory relative to the source root (repeatable)
23
26
  --define NAME=VALUE assembler symbol: an integer ($hex allowed) or true/false (repeatable)
24
27
  --set-pc <address> start address when the source sets none, for example '$0801'
25
- --compare <file> check the program byte by byte against an image.json of the
26
- c64-unpacker skill (each segment), a PRG, or 64 KiB of memory
28
+ --compare check the program byte by byte against the program in the
29
+ knowledge, each segment; put it in with knowledge.ts
30
+ image of the c64-knowledge skill
31
+ --space <space> with --compare: the knowledge space of the program, c64 (the
32
+ default), drive8, or bank0 to bank255
33
+ --plain write the bytes only, without the 2-byte load address of a PRG
34
+ --all-symbols list also the constants that the source does not use
27
35
 
28
- The result is one JSON object. Addresses are $ and four hex digits.`;
36
+ The result is one JSON object. Addresses and symbol values are $ and at least four hex digits.`;
29
37
 
30
38
  function parseDefine(text: string): [string, number | boolean] {
31
39
  const match = /^([A-Za-z_][A-Za-z0-9_]*)=(.+)$/.exec(text);
@@ -48,7 +56,10 @@ async function run(argv: string[]): Promise<{ output: unknown; failed: boolean }
48
56
  include: { type: "string", multiple: true },
49
57
  define: { type: "string", multiple: true },
50
58
  "set-pc": { type: "string" },
51
- compare: { type: "string" },
59
+ compare: { type: "boolean" },
60
+ space: { type: "string" },
61
+ plain: { type: "boolean" },
62
+ "all-symbols": { type: "boolean" },
52
63
  help: { type: "boolean" },
53
64
  },
54
65
  });
@@ -61,7 +72,20 @@ async function run(argv: string[]): Promise<{ output: unknown; failed: boolean }
61
72
  try {
62
73
  outPath = resolveProjectPath(out);
63
74
  } catch {
64
- throw new UsageError(`--out must be a path relative to the project directory that stays inside it, not ${out}`);
75
+ throw new UsageError(`--out must be a path relative to the project folder that stays inside it, not ${out}`);
76
+ }
77
+ if (values.space !== undefined && !values.compare) throw new UsageError("--space works only with --compare");
78
+ const space = values.space ?? DEFAULT_SPACE;
79
+ if (!SPACE.test(space)) throw new UsageError("--space must be c64, drive8, or bank0 to bank255");
80
+ // Read the program before ACME runs, so a project without it stops at once.
81
+ let program: ProgramImage | undefined;
82
+ if (values.compare) {
83
+ const db = openForRead();
84
+ try {
85
+ program = requireProgram(db, space);
86
+ } finally {
87
+ db?.close();
88
+ }
65
89
  }
66
90
  const defines = Object.fromEntries((values.define ?? []).map(parseDefine));
67
91
  let setPc: number | undefined;
@@ -79,19 +103,23 @@ async function run(argv: string[]): Promise<{ output: unknown; failed: boolean }
79
103
  const { result, attachments } = await assemble(params, tree.contents, localToolContext());
80
104
  if (!result.assembled) return { output: { assembled: false, diagnostics: result.diagnostics }, failed: true };
81
105
  mkdirSync(dirname(outPath), { recursive: true });
82
- writeFileSync(outPath, attachments![0]!);
106
+ writeFileSync(outPath, values.plain ? attachments![0]!.subarray(2) : attachments![0]!);
83
107
  const range = result.loadRange!;
84
- const compared = values.compare === undefined ? undefined : compareBuild(attachments![0]!, values.compare);
108
+ const compared = program === undefined ? undefined : compareBuild(attachments![0]!, program);
109
+ // An include file of platform names gives many constants that the program does not use.
110
+ const listed = result.symbols!.filter((symbol) => values["all-symbols"] || symbol.kind === "address" || symbol.used);
85
111
  return {
86
112
  output: {
87
113
  assembled: true,
88
114
  output: out,
115
+ ...(values.plain ? { plain: true } : {}),
89
116
  loadRange: { start: formatC64Address(range.start), end: formatC64Address(range.end), bytes: range.bytes },
90
- symbols: result.symbols!.map((symbol) =>
117
+ symbols: listed.map((symbol) =>
91
118
  symbol.kind === "address"
92
119
  ? { name: symbol.name, kind: "address", address: formatC64Address(symbol.address), used: symbol.used }
93
- : { name: symbol.name, kind: "constant", value: symbol.value, used: symbol.used },
120
+ : { name: symbol.name, kind: "constant", value: `$${symbol.value.toString(16).padStart(4, "0")}`, used: symbol.used },
94
121
  ),
122
+ ...(values["all-symbols"] ? {} : { unusedConstants: result.symbols!.length - listed.length }),
95
123
  diagnostics: result.diagnostics,
96
124
  ...(compared === undefined ? {} : { compare: compared }),
97
125
  },
@@ -101,10 +129,10 @@ async function run(argv: string[]): Promise<{ output: unknown; failed: boolean }
101
129
  }
102
130
 
103
131
  await runScript(
104
- async () => {
105
- const { output, failed } = await run(process.argv.slice(2));
132
+ async (argv) => {
133
+ const { output, failed } = await run(argv);
106
134
  if (failed) process.exitCode = 1;
107
135
  return output;
108
136
  },
109
- { usage: USAGE },
137
+ { usage: USAGE, refusals: [KnowledgeError] },
110
138
  );
@@ -0,0 +1,48 @@
1
+ // The byte check of a build: the assembled program against the program
2
+ // in the knowledge, segment by segment.
3
+
4
+ import { formatC64Address, prgRange } from "#src/c64.ts";
5
+
6
+ export interface SegmentCheck {
7
+ start: string;
8
+ end: string;
9
+ result: "identical" | "different" | "not covered";
10
+ /** Bytes of the segment that the build does not hold. */
11
+ uncovered?: number;
12
+ differences?: number;
13
+ firstDifference?: { address: string; built: number; reference: number };
14
+ }
15
+
16
+ /** The program that the build is checked against: its segments and its bytes in them. */
17
+ export interface Reference {
18
+ segments: ReadonlyArray<{ start: number; end: number }>;
19
+ byteAt: (address: number) => number;
20
+ }
21
+
22
+ /** Compares the assembled PRG with the reference, segment by segment. */
23
+ export function compareBuild(prg: Uint8Array, program: Reference): { identical: boolean; segments: SegmentCheck[] } {
24
+ const built = prgRange(prg);
25
+ const checks = program.segments.map((segment): SegmentCheck => {
26
+ const from = Math.max(segment.start, built.start);
27
+ const to = Math.min(segment.end, built.end);
28
+ const shown = { start: formatC64Address(segment.start), end: formatC64Address(segment.end) };
29
+ if (from > to) return { ...shown, result: "not covered", uncovered: segment.end - segment.start + 1 };
30
+ let differences = 0;
31
+ let first: SegmentCheck["firstDifference"];
32
+ for (let address = from; address <= to; address++) {
33
+ const builtByte = built.body[address - built.start]!;
34
+ const referenceByte = program.byteAt(address);
35
+ if (builtByte === referenceByte) continue;
36
+ differences++;
37
+ first ??= { address: formatC64Address(address), built: builtByte, reference: referenceByte };
38
+ }
39
+ const uncovered = segment.end - segment.start + 1 - (to - from + 1);
40
+ return {
41
+ ...shown,
42
+ result: differences > 0 ? "different" : uncovered > 0 ? "not covered" : "identical",
43
+ ...(uncovered > 0 ? { uncovered } : {}),
44
+ ...(differences > 0 ? { differences, firstDifference: first! } : {}),
45
+ };
46
+ });
47
+ return { identical: checks.every((check) => check.result === "identical"), segments: checks };
48
+ }
@@ -13,7 +13,7 @@ This skill does not run the program and does not change project knowledge.
13
13
 
14
14
  ## Inputs
15
15
 
16
- - The program: a PRG file, relative to the project directory.
16
+ - The program: a PRG file, relative to the project folder.
17
17
 
18
18
  ## Tools / execution path
19
19
 
@@ -25,6 +25,15 @@ node <skill>/scripts/basic.ts extracted/game.prg
25
25
 
26
26
  The script prints one JSON object. Addresses are `$` and four hex digits.
27
27
 
28
+ ### Project folder
29
+
30
+ - 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
+ - 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
+ - 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/`.
34
+ - 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
+ - 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
+
28
37
  ## Workflow
29
38
 
30
39
  1. Run the script on the program.
@@ -11,7 +11,7 @@ import { runScript, UsageError } from "#src/script.ts";
11
11
 
12
12
  const USAGE = `basic.ts <program>
13
13
 
14
- <program> is a PRG file relative to the project directory. The result is one
14
+ <program> is a PRG file relative to the project folder. The result is one
15
15
  JSON object. Addresses are $ and four hex digits.`;
16
16
 
17
17
  async function run(argv: string[]): Promise<{ output: unknown; failed: boolean }> {
@@ -37,8 +37,8 @@ async function run(argv: string[]): Promise<{ output: unknown; failed: boolean }
37
37
  }
38
38
 
39
39
  await runScript(
40
- async () => {
41
- const { output, failed } = await run(process.argv.slice(2));
40
+ async (argv) => {
41
+ const { output, failed } = await run(argv);
42
42
  if (failed) process.exitCode = 1;
43
43
  return output;
44
44
  },
@@ -13,10 +13,10 @@ This skill does not change the disk image and does not change project knowledge.
13
13
 
14
14
  ## Inputs
15
15
 
16
- - The disk image: a `.d64`, `.d71`, `.d81` or `.g64` file, relative to the project directory.
16
+ - The disk image: a `.d64`, `.d71`, `.d81` or `.g64` file, relative to the project folder.
17
17
  - For one file: the file name as the directory shows it.
18
- - To extract a file: the output path, relative to the project directory.
19
- - A T64 tape file or a CRT cartridge file, relative to the project directory.
18
+ - To extract a file: the output path, relative to the project folder.
19
+ - A T64 tape file or a CRT cartridge file, relative to the project folder.
20
20
 
21
21
  ## Tools / execution path
22
22
 
@@ -40,7 +40,16 @@ node <skill>/scripts/disk.ts <action> <image> [name] [--out <file>]
40
40
 
41
41
  The script prints one JSON object.
42
42
 
43
- 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}'`.
43
+ 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
+
45
+ ### Project folder
46
+
47
+ - 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
+ - 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
+ - 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/`.
51
+ - 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
+ - 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`.
44
53
 
45
54
  ## Workflow
46
55