@henols/c64-re-tools 2.0.0-rc.20 → 2.0.0-rc.21
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/skills/c64-docs/SKILL.md +10 -9
- package/skills/c64-docs/references/page-types.md +38 -10
- package/skills/c64-docs/scripts/link.ts +25 -11
- package/skills/c64-reverse-engineering/SKILL.md +3 -2
- package/skills/c64-source/scripts/generate.ts +2 -2
- package/skills/c64-unpacker/scripts/unpack.ts +0 -1
- package/src/host-client/coverage.ts +0 -27
- package/src/host-client/image.ts +4 -7
- package/src/mcp/tools/debug.ts +1 -1
- package/src/opcodes.ts +0 -4
- package/src/protocol/tools.ts +0 -14
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@henols/c64-re-tools",
|
|
3
|
-
"version": "2.0.0-rc.
|
|
3
|
+
"version": "2.0.0-rc.21",
|
|
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",
|
package/skills/c64-docs/SKILL.md
CHANGED
|
@@ -7,7 +7,7 @@ description: Use this skill to write the documentation of a C64 program. The doc
|
|
|
7
7
|
|
|
8
8
|
## Purpose
|
|
9
9
|
|
|
10
|
-
Write documentation that tells how a program works, and that points to the code. The documentation is Markdown in the project
|
|
10
|
+
Write documentation that tells how a program works, and that points to the code. The documentation is Markdown in the project. The entry page is `README.md` in the project folder. The other pages and the reference are in `docs/`. The documentation has two parts:
|
|
11
11
|
|
|
12
12
|
- **The reference.** `generate.ts` generates it from the knowledge, the source map of c64-source and the image. It has a page for each module. The page gives the routines, their callers and callees, the data and a call graph. It also has pages for the names, the variables, the tables, the memory map and the graphics. Do not edit these pages. The next run writes over them.
|
|
13
13
|
- **The pages that you write.** They tell how the program works. They give the flows, the state machines, the data formats and the task of each part. Use the page types in `references/page-types.md` and the rules in `references/style.md`.
|
|
@@ -20,7 +20,7 @@ This skill does not change the knowledge.
|
|
|
20
20
|
|
|
21
21
|
- The source directory of c64-source, with its `source-map.json`, for example `src`. The source must be from the current knowledge revision.
|
|
22
22
|
- The program in the knowledge. Put it in with `image` of the c64-knowledge skill. The reference decodes the tables and shows the graphics from it.
|
|
23
|
-
- The directory of the
|
|
23
|
+
- The entry page `README.md` and the directory of the other pages, for example `docs`. The reference goes into a directory of its own, for example `docs/reference`.
|
|
24
24
|
|
|
25
25
|
## Tools / execution path
|
|
26
26
|
|
|
@@ -28,16 +28,16 @@ The scripts run on this machine. They do not use the emulator or the host runtim
|
|
|
28
28
|
|
|
29
29
|
```
|
|
30
30
|
node <skill>/scripts/generate.ts --source src --out docs/reference
|
|
31
|
-
node <skill>/scripts/link.ts docs --source src
|
|
32
|
-
node <skill>/scripts/link.ts docs --source src --check
|
|
31
|
+
node <skill>/scripts/link.ts README.md docs --source src
|
|
32
|
+
node <skill>/scripts/link.ts README.md docs --source src --check
|
|
33
33
|
```
|
|
34
34
|
|
|
35
35
|
| Option | Meaning |
|
|
36
36
|
|---|---|
|
|
37
37
|
| `--source src` | The source directory of c64-source. Both scripts read `src/source-map.json`. |
|
|
38
38
|
| `--out docs/reference` | The directory of the reference. `generate.ts` writes only there. It refuses to start when the directory holds a page that it did not write. |
|
|
39
|
-
| `docs` | The
|
|
40
|
-
| `--reference <dir>` | The directory of the reference for `link.ts`. The default is `
|
|
39
|
+
| `README.md docs` | The pages of the documentation: `.md` files and directories. `link.ts` reads each file and every `.md` file below each directory. It changes only the pages that you wrote. It checks the pages of the reference. |
|
|
40
|
+
| `--reference <dir>` | The directory of the reference for `link.ts`. The default is the `reference` directory of the first directory that has one, for example `docs/reference`. |
|
|
41
41
|
| `--check` | `link.ts` writes nothing. It fails when a link is not up to date or a page has a problem. |
|
|
42
42
|
|
|
43
43
|
### Project folder
|
|
@@ -50,15 +50,16 @@ node <skill>/scripts/link.ts docs --source src --check
|
|
|
50
50
|
1. Read the knowledge with the c64-knowledge skill. The documentation can only be as good as the knowledge. Each routine needs a name and a comment. Each table needs a region and a name. Each file of the source needs a module with a title. Record what is missing with c64-knowledge.
|
|
51
51
|
2. Generate the source with c64-source. Make sure that `check.identical` is `true`. The script also writes `source-map.json`.
|
|
52
52
|
3. Run `generate.ts`. Read `index.md` of the reference. A module with no routines, or a routine with no comment, shows what the knowledge does not have yet.
|
|
53
|
-
4. Make the plan of the pages. Start from the modules and the call graphs. Find the main loop, the interrupt chain, the dispatch tables, the state machines and the data formats. Each subsystem gets one page
|
|
53
|
+
4. Make the plan of the pages. Start from the modules and the call graphs. Find the main loop, the interrupt chain, the dispatch tables, the state machines and the data formats. Each subsystem gets one page in `docs/`. The entry page `README.md` in the project folder lists the pages. Use the page types in `references/page-types.md`.
|
|
54
54
|
5. Write each page. Read the code and the knowledge for each fact. Run the program with c64-testing or c64-emulator when a fact needs a run. Write each name with its address, for example `` `draw_room` `$09BC` ``. Mark each fact with its evidence. Several subagents can write the pages at the same time, one page each. Give each subagent `references/page-types.md` and `references/style.md`.
|
|
55
55
|
6. Record each new fact in the knowledge first: a name, a comment, a region or a module. Then the source and the reference show it too. A fact only in a page is lost at the next change of the knowledge.
|
|
56
56
|
7. Run `link.ts`. Read `problems` and correct each one. Then run `link.ts --check`. Make sure that `problems.count` is 0 and that `notUpToDate.links` is 0.
|
|
57
|
-
8.
|
|
57
|
+
8. When the project folder is in a workspace with other projects, find `README.md` at the root of the workspace. This page lists each program of the workspace. Add one row for the program, with a link to `<project>/README.md` and one sentence. If the page does not exist, make it. Use the workspace index in `references/page-types.md`. `link.ts` does not read this page, because the page is not in the project folder.
|
|
58
|
+
9. After each change to the knowledge, do steps 2, 3 and 7 again. The links then go to the new lines.
|
|
58
59
|
|
|
59
60
|
## The links
|
|
60
61
|
|
|
61
|
-
`link.ts` changes these forms in the pages that you write. It does not change a code block, a mermaid diagram or an HTML comment.
|
|
62
|
+
`link.ts` changes these forms in the pages that you write. It does not change a code block, a mermaid diagram or an HTML comment. The examples are for a page in `docs/`. In `README.md`, the links start with `src/` and `docs/reference/`.
|
|
62
63
|
|
|
63
64
|
| You write | `link.ts` writes |
|
|
64
65
|
|---|---|
|
|
@@ -4,15 +4,16 @@ Each page that you write is one of these types. The types come from the document
|
|
|
4
4
|
|
|
5
5
|
## 1. The entry page
|
|
6
6
|
|
|
7
|
-
One entry page for the program
|
|
7
|
+
One entry page for the program: `README.md` in the project folder. A large subsystem can have an entry page of its own in `docs/`, for example `docs/rooms.md`. The entry page of the program has these parts, in this order:
|
|
8
8
|
|
|
9
9
|
1. **One paragraph.** What the program is, and which release the documentation is for.
|
|
10
10
|
2. **The table of pages.** One row for each page, with the link and one sentence about its content.
|
|
11
11
|
3. **The layers.** A mermaid diagram of the layers of the program. The layers are the data, the code that runs one time, the main loop and the interrupts. Under the diagram, a numbered list tells how the layers work together.
|
|
12
12
|
4. **The flow of a user.** A mermaid diagram of the steps of the player or user from start to end. An example is the order of the levels.
|
|
13
13
|
5. **Findings.** A table of the facts that a reader does not expect, each with its evidence. Put a bug of the original program here.
|
|
14
|
-
6. **The reference.** A link to `reference/index.md`.
|
|
14
|
+
6. **The reference.** A link to `docs/reference/index.md`.
|
|
15
15
|
7. **Still open.** What is not known yet, and which run can find it.
|
|
16
|
+
8. **The release and the rebuild.** What the release holds, for example a crack with a cracktro and a mover. Which part is the clean program of the knowledge. Which changes of the release to the machine state stay, and how the loader sets them. How the source assembles to the clean program, and which scenarios compare the original with the rebuild.
|
|
16
17
|
|
|
17
18
|
```markdown
|
|
18
19
|
# <Program>: <subject>
|
|
@@ -21,7 +22,7 @@ One entry page for the program, for example `docs/README.md` or `docs/rooms.md`
|
|
|
21
22
|
|
|
22
23
|
| Page | Content |
|
|
23
24
|
|---|---|
|
|
24
|
-
| [parts/main_loop.md](parts/main_loop.md) | **The main loop.** <one sentence> |
|
|
25
|
+
| [parts/main_loop.md](docs/parts/main_loop.md) | **The main loop.** <one sentence> |
|
|
25
26
|
|
|
26
27
|
## 1. The layers
|
|
27
28
|
|
|
@@ -31,23 +32,50 @@ flowchart TB
|
|
|
31
32
|
...
|
|
32
33
|
~~~
|
|
33
34
|
|
|
34
|
-
## 2.
|
|
35
|
+
## 2. The flow of a player
|
|
36
|
+
|
|
37
|
+
...
|
|
38
|
+
|
|
39
|
+
## 3. Findings
|
|
35
40
|
|
|
36
41
|
| Finding | Evidence |
|
|
37
42
|
|---|---|
|
|
38
43
|
| <fact> | Observed: <what run showed it> |
|
|
39
44
|
|
|
40
|
-
##
|
|
45
|
+
## 4. The reference
|
|
46
|
+
|
|
47
|
+
[docs/reference/index.md](docs/reference/index.md)
|
|
48
|
+
|
|
49
|
+
## 5. Still open
|
|
41
50
|
|
|
42
51
|
- <question> — <the run that can answer it>
|
|
52
|
+
|
|
53
|
+
## 6. The release and the rebuild
|
|
54
|
+
|
|
55
|
+
- **The release.** <the file on the disk, and what the release adds to the program>
|
|
56
|
+
- **The rebuild.** <the source, the loader and the scenarios>
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## 2. The workspace index
|
|
60
|
+
|
|
61
|
+
A workspace with several projects has one `README.md` at its root. It lists each program of the workspace. It has a title, one sentence and a table with one row for each program. Each row has a link to the entry page of the program and one sentence about the program.
|
|
62
|
+
|
|
63
|
+
```markdown
|
|
64
|
+
# C64 games
|
|
65
|
+
|
|
66
|
+
Reverse-engineered C64 games. Each game has its own folder with the original disk, the rebuilt source and the documentation.
|
|
67
|
+
|
|
68
|
+
| Game | |
|
|
69
|
+
|---|---|
|
|
70
|
+
| [Bruce Lee](bruce_lee/README.md) | Datasoft 1984. How the program works, from the cold start to the last room. |
|
|
43
71
|
```
|
|
44
72
|
|
|
45
|
-
##
|
|
73
|
+
## 3. The subsystem page
|
|
46
74
|
|
|
47
75
|
One page for each subsystem, for example the main loop, the interrupt chain, the room generation or the sound player. It has these parts:
|
|
48
76
|
|
|
49
77
|
1. **The scope.** A list of what the page covers, and the sources (the source files, the assets).
|
|
50
|
-
2. **The conventions.** The terms of the page, for example "a pass is one run of `main_loop` `$1BC2`". Also the evidence labels. Use the conventions block of section
|
|
78
|
+
2. **The conventions.** The terms of the page, for example "a pass is one run of `main_loop` `$1BC2`". Also the evidence labels. Use the conventions block of section 6.
|
|
51
79
|
3. **The overview.** A short text diagram or a mermaid flowchart of the calls, from the entry of the subsystem to its end.
|
|
52
80
|
4. **The catalogue.** A table of the data of the subsystem. Each row has the name, the address, the layout (bytes, entries, the meaning of each byte), the readers and the writers.
|
|
53
81
|
5. **The step-by-step walk.** For each main routine: a numbered list of what it does, in the order of the code. Each step gives the names and addresses. Add pseudo-code when a loop or a condition is not clear from the list.
|
|
@@ -71,7 +99,7 @@ stateDiagram-v2
|
|
|
71
99
|
| Waiting `$00` | `move_timer` = 0 | Moving `$01` | `update_slider` `$3F8C` |
|
|
72
100
|
```
|
|
73
101
|
|
|
74
|
-
##
|
|
102
|
+
## 4. The entity sections
|
|
75
103
|
|
|
76
104
|
A program often has many items of one kind: levels, rooms, enemies, tunes. Give each item one section of the same form, in a page for a group of items. Each section has these parts:
|
|
77
105
|
|
|
@@ -83,7 +111,7 @@ A program often has many items of one kind: levels, rooms, enemies, tunes. Give
|
|
|
83
111
|
|
|
84
112
|
End the group page with a table of the dependencies between the items.
|
|
85
113
|
|
|
86
|
-
##
|
|
114
|
+
## 5. The data-format page
|
|
87
115
|
|
|
88
116
|
One page for each format of data, for example a packed map, a music stream or a sprite format. It has these parts:
|
|
89
117
|
|
|
@@ -101,7 +129,7 @@ One page for each format of data, for example a packed map, a music stream or a
|
|
|
101
129
|
| `$01-$7F` | The next byte, `b` times. |
|
|
102
130
|
```
|
|
103
131
|
|
|
104
|
-
##
|
|
132
|
+
## 6. The conventions block
|
|
105
133
|
|
|
106
134
|
Put this block near the top of each subsystem page. Change the terms for the program.
|
|
107
135
|
|
|
@@ -16,13 +16,14 @@ import { checkLinks, linkPage, type Files, type Problem } from "./lib/links.ts";
|
|
|
16
16
|
import { GENERATED_MARK } from "./lib/reference.ts";
|
|
17
17
|
import { readSourceMap } from "./lib/source-map.ts";
|
|
18
18
|
|
|
19
|
-
const USAGE = `link.ts <
|
|
19
|
+
const USAGE = `link.ts <pages>... --source <dir> [--reference <dir>] [--check]
|
|
20
20
|
|
|
21
|
-
<
|
|
22
|
-
|
|
21
|
+
<pages>... .md files and directories of the documentation, relative to
|
|
22
|
+
the project folder, for example README.md docs; link.ts reads
|
|
23
|
+
each file and every .md file below each directory
|
|
23
24
|
--source <dir> the source directory of c64-source, with its source-map.json
|
|
24
|
-
--reference <dir> the reference pages of generate.ts; the default is
|
|
25
|
-
|
|
25
|
+
--reference <dir> the reference pages of generate.ts; the default is the
|
|
26
|
+
reference/ folder of the first directory that has one
|
|
26
27
|
--check write nothing; fail when a link is not up to date or a page
|
|
27
28
|
has a problem
|
|
28
29
|
|
|
@@ -34,9 +35,14 @@ const MAX_LISTED = 50;
|
|
|
34
35
|
/** A name outside the image covers the addresses up to the next name, at most this many. */
|
|
35
36
|
const MAX_BODY = 256;
|
|
36
37
|
|
|
38
|
+
const isDirectory = (path: string) => {
|
|
39
|
+
const real = resolveProjectPath(path);
|
|
40
|
+
return existsSync(real) && statSync(real).isDirectory();
|
|
41
|
+
};
|
|
42
|
+
|
|
37
43
|
function markdownFiles(directory: string): string[] {
|
|
38
44
|
const root = resolveProjectPath(directory);
|
|
39
|
-
if (!
|
|
45
|
+
if (!isDirectory(directory)) throw new WireFailure("not-found", `There is no directory ${directory} in the project folder.`);
|
|
40
46
|
const found: string[] = [];
|
|
41
47
|
const walk = (relative: string) => {
|
|
42
48
|
for (const entry of readdirSync(join(root, relative), { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name))) {
|
|
@@ -79,15 +85,19 @@ const files: Files = {
|
|
|
79
85
|
function run(argv: string[]): { output: Record<string, unknown>; failed: boolean } {
|
|
80
86
|
const { values, positionals } = parseArgs({ args: argv, strict: true, allowPositionals: true, options: { source: { type: "string" }, reference: { type: "string" }, check: { type: "boolean" }, help: { type: "boolean" } } });
|
|
81
87
|
if (values.help) throw new UsageError("");
|
|
82
|
-
if (positionals.length
|
|
88
|
+
if (positionals.length === 0) throw new UsageError("give the pages: .md files and directories of the documentation, for example README.md docs");
|
|
83
89
|
if (values.source === undefined) throw new UsageError("give --source <dir>: the source directory of c64-source");
|
|
84
|
-
const
|
|
85
|
-
for (const [option, path] of [["<
|
|
90
|
+
const given = [...new Set(positionals.map((path) => posix.normalize(path)))];
|
|
91
|
+
for (const [option, path] of [...given.map((path) => ["<pages>", path] as const), ["--source", values.source], ["--reference", values.reference]] as const) {
|
|
86
92
|
if (path === undefined) continue;
|
|
87
93
|
if (!files.inside(path)) throw new UsageError(`${option} must be a path relative to the project folder that stays inside it, not ${path}`);
|
|
88
94
|
}
|
|
95
|
+
for (const path of given) {
|
|
96
|
+
if (!path.endsWith(".md") && files.exists(path) && !isDirectory(path)) throw new UsageError(`<pages> takes .md files and directories, not ${path}`);
|
|
97
|
+
}
|
|
98
|
+
const directories = given.filter((path) => !path.endsWith(".md") || isDirectory(path));
|
|
89
99
|
const map = readSourceMap(values.source);
|
|
90
|
-
const reference = values.reference === undefined ? (
|
|
100
|
+
const reference = values.reference === undefined ? directories.map((path) => posix.join(path, "reference")).find((path) => isDirectory(path)) : posix.normalize(values.reference);
|
|
91
101
|
const problems: Problem[] = [];
|
|
92
102
|
const db = openForRead();
|
|
93
103
|
let symbols;
|
|
@@ -158,7 +168,11 @@ function run(argv: string[]): { output: Record<string, unknown>; failed: boolean
|
|
|
158
168
|
managed: (path: string) => inside(path, posix.normalize(map.directory)) || inside(path, reference),
|
|
159
169
|
};
|
|
160
170
|
|
|
161
|
-
const pages =
|
|
171
|
+
const pages = [...new Set(given.flatMap((path) => {
|
|
172
|
+
if (directories.includes(path)) return markdownFiles(path);
|
|
173
|
+
if (files.text(path) === undefined) throw new WireFailure("not-found", `There is no file ${path} in the project folder.`);
|
|
174
|
+
return [path];
|
|
175
|
+
}))];
|
|
162
176
|
const changedPages: string[] = [];
|
|
163
177
|
let links = 0;
|
|
164
178
|
for (const page of pages) {
|
|
@@ -87,7 +87,7 @@ Do the stages in this sequence. Go to the next stage only when its exit check is
|
|
|
87
87
|
| 7. Name | c64-provenance: if the workspace has a library, find its routines with `match` and apply the batch. Then c64-knowledge and c64-memory-map: name each routine in the scope and give it a comment. Record the modules. When the scope has hundreds of routines, ask the user which routines to name. The source gives the other routines an `l_xxxx` label. | Each routine that the user selected has a name and a comment. |
|
|
88
88
|
| 8. Generate | c64-source into `src/`. For an unpacked program, add `--loader`. | `check.identical` is true. For an unpacked program, `loader.verified` is true. |
|
|
89
89
|
| 9. Build and test | c64-assembler with `--compare` into `build/`. c64-testing scenarios in `tests/`. | The criteria of stage 1 are true. |
|
|
90
|
-
| 10. Document | When the criteria ask for documentation: c64-docs into `docs/`. | `link.ts --check` of c64-docs gives no problem. |
|
|
90
|
+
| 10. Document | When the criteria ask for documentation: c64-docs into `README.md` and `docs/`. | `link.ts --check` of c64-docs gives no problem. |
|
|
91
91
|
| 11. Hand over | Find each file that you made outside `.c64-re-tools/local/` and that is not a deliverable. Move it into `.c64-re-tools/local/`. Then give the answer (below). | Each file that you made outside `.c64-re-tools/local/` is a deliverable. |
|
|
92
92
|
|
|
93
93
|
In stage 5, if you cannot get to a part of the program, ask the user how to get to it. Do stages 7 and 8 again until the source is clear. Each run of c64-source writes the source again.
|
|
@@ -100,7 +100,7 @@ After stage 7, save each routine that other programs can have to the library wit
|
|
|
100
100
|
- the generated source
|
|
101
101
|
- the built program and the loader
|
|
102
102
|
- the c64-testing scenarios
|
|
103
|
-
- the documentation in `docs/`, when the criteria ask for it
|
|
103
|
+
- the documentation in `README.md` and `docs/`, when the criteria ask for it
|
|
104
104
|
- the runs in `runs/` that the knowledge gives as evidence
|
|
105
105
|
- the scripts in `tools/` for the formats of this program, when you wrote them.
|
|
106
106
|
|
|
@@ -128,6 +128,7 @@ tests/ c64-testing scenarios
|
|
|
128
128
|
runs/ the runs that the knowledge gives as evidence
|
|
129
129
|
docs/ the documentation from c64-docs, with the reference in docs/reference/
|
|
130
130
|
tools/ scripts for the formats of this program only
|
|
131
|
+
README.md the entry page of the documentation from c64-docs
|
|
131
132
|
```
|
|
132
133
|
|
|
133
134
|
Commit each folder of the layout and `.c64-re-tools/knowledge/`. The knowledge holds the program, the entry state and the coverage, so the project needs no other folder for them. Put all other files that you make in `.c64-re-tools/local/`, also the captures and the coverage maps after you put them into the knowledge. Do not let git ignore a folder of the layout. Do not change the `.gitignore` files of the project.
|
|
@@ -8,10 +8,10 @@
|
|
|
8
8
|
// passes, 1 when one fails, 2 for a bad call.
|
|
9
9
|
|
|
10
10
|
import { existsSync, readdirSync, readFileSync, rmSync } from "node:fs";
|
|
11
|
-
import { join
|
|
11
|
+
import { join } from "node:path";
|
|
12
12
|
import { parseArgs } from "node:util";
|
|
13
13
|
|
|
14
|
-
import { COVERAGE, coverageSeedKinds, formatC64Address, parseC64Address,
|
|
14
|
+
import { COVERAGE, coverageSeedKinds, formatC64Address, parseC64Address, textToPetscii } from "#src/c64.ts";
|
|
15
15
|
import { autostartLoaded, RUN_LIMIT_FRAMES } from "#src/host-client/autostart.ts";
|
|
16
16
|
import { MAX_MEMORY_READ, WireFailure } from "#src/host-client/tools.ts";
|
|
17
17
|
import { writeProjectFile } from "#src/host-client/transfer.ts";
|
|
@@ -104,7 +104,6 @@ async function runSteps(client: ViceSessionClient, steps: ActionStep[]): Promise
|
|
|
104
104
|
async function trace(program: string, options: { frames: number; entry?: number; steps: ActionStep[] }): Promise<{ output: Record<string, unknown>; failed: boolean }> {
|
|
105
105
|
const { bytes } = readProjectFile(program);
|
|
106
106
|
if (bytes.length < 3) throw new UsageError("the file is too short for a PRG");
|
|
107
|
-
const load = bytes[0]! | (bytes[1]! << 8);
|
|
108
107
|
if (options.entry === undefined && basicStart(bytes) === undefined) throw new UsageError(NO_BASIC);
|
|
109
108
|
const client = await ViceSessionClient.open({ videoStandard: "pal" });
|
|
110
109
|
try {
|
|
@@ -3,38 +3,11 @@
|
|
|
3
3
|
|
|
4
4
|
import { COVERAGE, type CodeRange } from "../c64.ts";
|
|
5
5
|
import { MAX_MEMMAP_RANGES, type MemmapRange } from "../protocol/vice.ts";
|
|
6
|
-
import { readProjectFile } from "./transfer.ts";
|
|
7
6
|
import type { ViceSessionClient } from "./vice-session.ts";
|
|
8
7
|
|
|
9
8
|
/** A full coverage file: offset n is address n (D40). */
|
|
10
9
|
export const FULL_COVERAGE = 0x10000;
|
|
11
10
|
|
|
12
|
-
/** Reads full coverage files from the project, one map per file. `fail` refuses a file of another size. */
|
|
13
|
-
export function readCoverageMaps(paths: string[], fail: (message: string) => never): Uint8Array[] {
|
|
14
|
-
return paths.map((path) => {
|
|
15
|
-
const { bytes } = readProjectFile(path);
|
|
16
|
-
if (bytes.length !== FULL_COVERAGE) {
|
|
17
|
-
fail(`--coverage ${path} has ${bytes.length} bytes; a full coverage file has 65536, one for each address. Write it with c64_memmap action read and out, without start and end.`);
|
|
18
|
-
}
|
|
19
|
-
return bytes;
|
|
20
|
-
});
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
/** Joins coverage maps: an address has every flag that any run gave it. */
|
|
24
|
-
export function joinCoverage(maps: readonly Uint8Array[]): Uint8Array {
|
|
25
|
-
const joined = new Uint8Array(FULL_COVERAGE);
|
|
26
|
-
for (const map of maps) for (let address = 0; address < FULL_COVERAGE; address++) joined[address]! |= map[address]!;
|
|
27
|
-
return joined;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
/**
|
|
31
|
-
* Reads full coverage files from the project and joins them: an address has
|
|
32
|
-
* every flag that any run gave it. `fail` refuses a file of another size.
|
|
33
|
-
*/
|
|
34
|
-
export function readCoverageFiles(paths: string[], fail: (message: string) => never): Uint8Array | undefined {
|
|
35
|
-
return paths.length === 0 ? undefined : joinCoverage(readCoverageMaps(paths, fail));
|
|
36
|
-
}
|
|
37
|
-
|
|
38
11
|
/** The coverage bytes of start..end, read from the session in parts. */
|
|
39
12
|
export async function readCoverage(session: Pick<ViceSessionClient, "memmap">, start = 0x0000, end = 0xffff): Promise<Uint8Array> {
|
|
40
13
|
const map = new Uint8Array(end - start + 1);
|
package/src/host-client/image.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
//
|
|
2
|
-
//
|
|
1
|
+
// An image.json of the c64-unpacker skill (a 64 KiB memory and its segments)
|
|
2
|
+
// or a PRG, read from the project to put it into the knowledge (D83).
|
|
3
3
|
|
|
4
4
|
import { posix } from "node:path";
|
|
5
5
|
|
|
@@ -12,12 +12,9 @@ export class ImageError extends Error {
|
|
|
12
12
|
}
|
|
13
13
|
|
|
14
14
|
export interface Image {
|
|
15
|
-
path: string;
|
|
16
15
|
segments: Array<{ start: number; end: number }>;
|
|
17
16
|
/** The byte at an address inside a segment. */
|
|
18
17
|
byteAt: (address: number) => number;
|
|
19
|
-
/** The bytes that Ghidra analyzes, and how. */
|
|
20
|
-
analyzed: { bytes: Buffer; kind: "prg" | "flat64k" };
|
|
21
18
|
entry?: number;
|
|
22
19
|
/** entry_state.json next to image.json. */
|
|
23
20
|
statePath?: string;
|
|
@@ -35,7 +32,7 @@ export function readImage(path: string): Image {
|
|
|
35
32
|
} catch (error) {
|
|
36
33
|
throw new ImageError(`${path}: ${(error as Error).message}`);
|
|
37
34
|
}
|
|
38
|
-
return {
|
|
35
|
+
return { segments: [{ start: range.start, end: range.end }], byteAt: (address) => range.body[address - range.start]! };
|
|
39
36
|
}
|
|
40
37
|
const fail = (what: string): never => {
|
|
41
38
|
throw new ImageError(`${path} is not an image.json of the c64-unpacker skill: ${what}`);
|
|
@@ -59,7 +56,7 @@ export function readImage(path: string): Image {
|
|
|
59
56
|
const directory = posix.dirname(path);
|
|
60
57
|
const memory = readProjectFile(posix.join(directory, manifest.image as string)).bytes;
|
|
61
58
|
if (memory.length !== 0x10000) fail(`its image has ${memory.length} bytes, not 65536`);
|
|
62
|
-
const image: Image = {
|
|
59
|
+
const image: Image = { segments, byteAt: (at) => memory[at]!, statePath: posix.join(directory, "entry_state.json") };
|
|
63
60
|
if (manifest.entry !== undefined) image.entry = address(manifest.entry, "entry");
|
|
64
61
|
if (Array.isArray(manifest.stages)) image.stages = manifest.stages as Array<Record<string, unknown>>;
|
|
65
62
|
return image;
|
package/src/mcp/tools/debug.ts
CHANGED
|
@@ -8,7 +8,7 @@ import { WireFailure } from "../../protocol/messages.ts";
|
|
|
8
8
|
import { CHECKPOINT_ACTIONS, MAX_BACKTRACE, MAX_HISTORY, MAX_TRACE_FRAMES, MAX_TRACE_HITS, MAX_TRACE_POINTS, TRACE_KINDS, TRACE_ENDS, MAX_MEMMAP_RANGES, MAX_MEMORY_READ, MAX_PROFILE, MAX_WATCH_SIZE, SPACES, TIMING_ACTIONS, WATCH_ACCESS, type Breakpoint, type Watchpoint } from "../../protocol/vice.ts";
|
|
9
9
|
import { readCoverage } from "../../host-client/coverage.ts";
|
|
10
10
|
import { OutInput, ProjectInput, writeOut, WrittenOutput } from "../output.ts";
|
|
11
|
-
import { AddressInput, AddressOutput, Byte, ConditionInput, ConditionOutput, defineTool, HexData, requireFields, showCondition, SpaceInput, toCondition
|
|
11
|
+
import { AddressInput, AddressOutput, Byte, ConditionInput, ConditionOutput, defineTool, HexData, requireFields, showCondition, SpaceInput, toCondition } from "../server.ts";
|
|
12
12
|
import { spaced } from "./memory.ts";
|
|
13
13
|
|
|
14
14
|
/** No default here: a filled-in default would count as a field the other actions refuse. */
|
package/src/opcodes.ts
CHANGED
|
@@ -56,10 +56,6 @@ export function takesAddress(mode: Mode): boolean {
|
|
|
56
56
|
return mode !== "imp" && mode !== "imm";
|
|
57
57
|
}
|
|
58
58
|
|
|
59
|
-
export function isZeroPageMode(mode: Mode): boolean {
|
|
60
|
-
return mode === "zp" || mode === "zpx" || mode === "zpy" || mode === "izx" || mode === "izy";
|
|
61
|
-
}
|
|
62
|
-
|
|
63
59
|
/** How an instruction uses the address of its operand, in the order a summary lists them. */
|
|
64
60
|
export const USE_KINDS = ["read", "written", "modified", "called", "jumped to", "jumped through", "branched to", "pointer", "address", "table", "macro argument"] as const;
|
|
65
61
|
export type UseKind = (typeof USE_KINDS)[number];
|
package/src/protocol/tools.ts
CHANGED
|
@@ -109,20 +109,6 @@ export function isRelativePath(value: unknown): value is string {
|
|
|
109
109
|
return typeof value === "string" && value.length <= 1024 && RELATIVE_PATH.test(value) && !value.includes("//") && !value.endsWith("/");
|
|
110
110
|
}
|
|
111
111
|
|
|
112
|
-
/** Validates a transferred source tree against its attachments. Throws WireFailure(invalid-input). */
|
|
113
|
-
export function validateSourceTree(value: unknown, attachments: readonly Uint8Array[]): SourceTree {
|
|
114
|
-
if (!Array.isArray(value) || value.length > MAX_TREE_FILES) invalid(`files must list at most ${MAX_TREE_FILES} files`);
|
|
115
|
-
if (value.length !== attachments.length) invalid("each listed file needs exactly one attachment");
|
|
116
|
-
const seen = new Set<string>();
|
|
117
|
-
return value.map((entry, index) => {
|
|
118
|
-
if (!isObject(entry) || !isRelativePath(entry.path)) invalid("each file needs a relative path inside the source root");
|
|
119
|
-
if (seen.has(entry.path)) invalid(`the file ${entry.path} is listed twice`);
|
|
120
|
-
seen.add(entry.path);
|
|
121
|
-
if (entry.size !== attachments[index]!.length) invalid(`the size of ${entry.path} does not match its bytes`);
|
|
122
|
-
return { path: entry.path, size: entry.size as number };
|
|
123
|
-
});
|
|
124
|
-
}
|
|
125
|
-
|
|
126
112
|
/** Refuses anything but exactly one attachment with at least one byte, named by what it must hold. */
|
|
127
113
|
function oneFile(attachments: readonly Uint8Array[], what: string): void {
|
|
128
114
|
if (attachments.length !== 1 || attachments[0]!.length === 0) invalid(`${what} must be the one attachment, and it must not be empty`);
|