@henols/c64-re-tools 2.0.0-rc.17 → 2.0.0-rc.19
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-assembler/SKILL.md +5 -4
- package/skills/c64-assembler/scripts/assemble.ts +25 -5
- package/skills/c64-assembler/scripts/lib/compare.ts +11 -47
- package/skills/c64-basic/SKILL.md +3 -2
- package/skills/c64-disk/SKILL.md +3 -2
- package/skills/c64-docs/SKILL.md +10 -5
- package/skills/c64-docs/references/style.md +1 -1
- package/skills/c64-docs/scripts/generate.ts +11 -19
- package/skills/c64-docs/scripts/lib/reference.ts +9 -9
- package/skills/c64-docs/scripts/lib/source-map.ts +0 -2
- package/skills/c64-emulator/SKILL.md +2 -2
- package/skills/c64-knowledge/SKILL.md +38 -19
- package/skills/c64-knowledge/scripts/knowledge.ts +138 -55
- package/skills/c64-memory-map/SKILL.md +3 -2
- package/skills/c64-provenance/SKILL.md +20 -19
- package/skills/c64-provenance/scripts/provenance.ts +67 -57
- package/skills/c64-reverse-engineering/SKILL.md +31 -28
- package/skills/c64-source/SKILL.md +33 -29
- package/skills/c64-source/scripts/generate.ts +46 -61
- package/skills/c64-source/scripts/lib/source.ts +1 -1
- package/skills/c64-static-analysis/SKILL.md +18 -18
- package/skills/c64-static-analysis/scripts/analyze.ts +33 -43
- package/skills/c64-static-analysis/scripts/lib/coverage.ts +2 -10
- package/skills/c64-static-analysis/scripts/lib/idioms.ts +4 -6
- package/skills/c64-testing/SKILL.md +10 -9
- package/skills/c64-testing/references/scenario-format.md +4 -4
- package/skills/c64-testing/scripts/lib/scenario.ts +13 -11
- package/skills/c64-unpacker/SKILL.md +9 -7
- package/src/graphics.ts +17 -7
- package/src/host-client/image.ts +3 -0
- package/src/knowledge/program.ts +258 -0
- package/src/knowledge/read.ts +1 -1
- package/src/knowledge/schema.ts +47 -1
- package/src/knowledge/text.ts +25 -3
- package/src/knowledge/write.ts +43 -1
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.19",
|
|
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",
|
|
@@ -31,7 +31,7 @@ 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
|
|
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
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
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. |
|
|
37
37
|
|
|
@@ -43,8 +43,9 @@ The script prints one JSON object. The target CPU is the 6510. The output is a C
|
|
|
43
43
|
|
|
44
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
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
|
-
-
|
|
47
|
-
-
|
|
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.
|
|
48
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`.
|
|
49
50
|
|
|
50
51
|
## Workflow
|
|
@@ -75,7 +76,7 @@ A symbol from the rebuilt source can give the address of a routine in the rebuil
|
|
|
75
76
|
|
|
76
77
|
The PRG in the project at the output path, with its load range and symbols. Or the source diagnostics when the assembly failed.
|
|
77
78
|
|
|
78
|
-
With `--compare`, `compare` gives `identical` and one check for each segment of the
|
|
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.
|
|
79
80
|
|
|
80
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.
|
|
81
82
|
|
|
@@ -13,6 +13,9 @@ 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
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
|
|
|
@@ -22,8 +25,11 @@ const USAGE = `assemble.ts --source-root <dir> --entry <file> --out <file.prg> [
|
|
|
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
|
|
26
|
-
|
|
28
|
+
--compare check the program byte by byte against the program in the
|
|
29
|
+
knowledge (D83), 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
|
|
27
33
|
--plain write the bytes only, without the 2-byte load address of a PRG
|
|
28
34
|
--all-symbols list also the constants that the source does not use
|
|
29
35
|
|
|
@@ -50,7 +56,8 @@ async function run(argv: string[]): Promise<{ output: unknown; failed: boolean }
|
|
|
50
56
|
include: { type: "string", multiple: true },
|
|
51
57
|
define: { type: "string", multiple: true },
|
|
52
58
|
"set-pc": { type: "string" },
|
|
53
|
-
compare: { type: "
|
|
59
|
+
compare: { type: "boolean" },
|
|
60
|
+
space: { type: "string" },
|
|
54
61
|
plain: { type: "boolean" },
|
|
55
62
|
"all-symbols": { type: "boolean" },
|
|
56
63
|
help: { type: "boolean" },
|
|
@@ -67,6 +74,19 @@ async function run(argv: string[]): Promise<{ output: unknown; failed: boolean }
|
|
|
67
74
|
} catch {
|
|
68
75
|
throw new UsageError(`--out must be a path relative to the project folder that stays inside it, not ${out}`);
|
|
69
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
|
+
}
|
|
89
|
+
}
|
|
70
90
|
const defines = Object.fromEntries((values.define ?? []).map(parseDefine));
|
|
71
91
|
let setPc: number | undefined;
|
|
72
92
|
if (values["set-pc"] !== undefined) {
|
|
@@ -85,7 +105,7 @@ async function run(argv: string[]): Promise<{ output: unknown; failed: boolean }
|
|
|
85
105
|
mkdirSync(dirname(outPath), { recursive: true });
|
|
86
106
|
writeFileSync(outPath, values.plain ? attachments![0]!.subarray(2) : attachments![0]!);
|
|
87
107
|
const range = result.loadRange!;
|
|
88
|
-
const compared =
|
|
108
|
+
const compared = program === undefined ? undefined : compareBuild(attachments![0]!, program);
|
|
89
109
|
// An include file of platform names gives many constants that the program does not use.
|
|
90
110
|
const listed = result.symbols!.filter((symbol) => values["all-symbols"] || symbol.kind === "address" || symbol.used);
|
|
91
111
|
return {
|
|
@@ -114,5 +134,5 @@ await runScript(
|
|
|
114
134
|
if (failed) process.exitCode = 1;
|
|
115
135
|
return output;
|
|
116
136
|
},
|
|
117
|
-
{ usage: USAGE },
|
|
137
|
+
{ usage: USAGE, refusals: [KnowledgeError] },
|
|
118
138
|
);
|
|
@@ -1,12 +1,7 @@
|
|
|
1
|
-
// The byte check of a build (D47): the assembled program against the
|
|
2
|
-
//
|
|
3
|
-
// memory, segment by segment.
|
|
4
|
-
|
|
5
|
-
import { posix } from "node:path";
|
|
1
|
+
// The byte check of a build (D47): the assembled program against the program
|
|
2
|
+
// in the knowledge (D83), segment by segment.
|
|
6
3
|
|
|
7
4
|
import { formatC64Address, prgRange } from "#src/c64.ts";
|
|
8
|
-
import { readProjectFile } from "#src/host-client/transfer.ts";
|
|
9
|
-
import { UsageError } from "#src/script.ts";
|
|
10
5
|
|
|
11
6
|
export interface SegmentCheck {
|
|
12
7
|
start: string;
|
|
@@ -18,47 +13,16 @@ export interface SegmentCheck {
|
|
|
18
13
|
firstDifference?: { address: string; built: number; reference: number };
|
|
19
14
|
}
|
|
20
15
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
const { bytes } = readProjectFile(path);
|
|
26
|
-
if (path.endsWith(".json")) {
|
|
27
|
-
let manifest: { image?: unknown; segments?: unknown };
|
|
28
|
-
try {
|
|
29
|
-
manifest = JSON.parse(bytes.toString("utf8")) as typeof manifest;
|
|
30
|
-
} catch (error) {
|
|
31
|
-
throw new UsageError(`--compare ${path} is not JSON: ${(error as Error).message}`);
|
|
32
|
-
}
|
|
33
|
-
const segments = manifest.segments;
|
|
34
|
-
if (typeof manifest.image !== "string" || !Array.isArray(segments) || segments.length === 0) throw new UsageError(`--compare ${path} is not an image.json: it needs image and segments`);
|
|
35
|
-
const image = readProjectFile(posix.join(posix.dirname(path), manifest.image)).bytes;
|
|
36
|
-
if (image.length !== FULL) throw new UsageError(`the image of ${path} has ${image.length} bytes, not 65536`);
|
|
37
|
-
return {
|
|
38
|
-
memory: (address) => image[address],
|
|
39
|
-
segments: segments.map((segment: { start?: unknown; end?: unknown }) => {
|
|
40
|
-
const parse = (value: unknown) => (typeof value === "string" && /^\$[0-9a-f]{4}$/i.test(value) ? Number.parseInt(value.slice(1), 16) : NaN);
|
|
41
|
-
const range = { start: parse(segment.start), end: parse(segment.end) };
|
|
42
|
-
if (!(range.start <= range.end)) throw new UsageError(`--compare ${path}: a segment needs start and end as $ addresses`);
|
|
43
|
-
return range;
|
|
44
|
-
}),
|
|
45
|
-
};
|
|
46
|
-
}
|
|
47
|
-
if (bytes.length === FULL && !path.endsWith(".prg")) return { memory: (address) => bytes[address], segments: [built] };
|
|
48
|
-
let range: ReturnType<typeof prgRange>;
|
|
49
|
-
try {
|
|
50
|
-
range = prgRange(bytes);
|
|
51
|
-
} catch (error) {
|
|
52
|
-
throw new UsageError(`--compare ${path}: ${(error as Error).message}`);
|
|
53
|
-
}
|
|
54
|
-
return { memory: (address) => (address >= range.start && address <= range.end ? range.body[address - range.start] : undefined), segments: [{ start: range.start, end: range.end }] };
|
|
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;
|
|
55
20
|
}
|
|
56
21
|
|
|
57
22
|
/** Compares the assembled PRG with the reference, segment by segment. */
|
|
58
|
-
export function compareBuild(prg: Uint8Array,
|
|
23
|
+
export function compareBuild(prg: Uint8Array, program: Reference): { identical: boolean; segments: SegmentCheck[] } {
|
|
59
24
|
const built = prgRange(prg);
|
|
60
|
-
const
|
|
61
|
-
const checks = segments.map((segment): SegmentCheck => {
|
|
25
|
+
const checks = program.segments.map((segment): SegmentCheck => {
|
|
62
26
|
const from = Math.max(segment.start, built.start);
|
|
63
27
|
const to = Math.min(segment.end, built.end);
|
|
64
28
|
const shown = { start: formatC64Address(segment.start), end: formatC64Address(segment.end) };
|
|
@@ -67,8 +31,8 @@ export function compareBuild(prg: Uint8Array, path: string): { against: string;
|
|
|
67
31
|
let first: SegmentCheck["firstDifference"];
|
|
68
32
|
for (let address = from; address <= to; address++) {
|
|
69
33
|
const builtByte = built.body[address - built.start]!;
|
|
70
|
-
const referenceByte =
|
|
71
|
-
if (
|
|
34
|
+
const referenceByte = program.byteAt(address);
|
|
35
|
+
if (builtByte === referenceByte) continue;
|
|
72
36
|
differences++;
|
|
73
37
|
first ??= { address: formatC64Address(address), built: builtByte, reference: referenceByte };
|
|
74
38
|
}
|
|
@@ -80,5 +44,5 @@ export function compareBuild(prg: Uint8Array, path: string): { against: string;
|
|
|
80
44
|
...(differences > 0 ? { differences, firstDifference: first! } : {}),
|
|
81
45
|
};
|
|
82
46
|
});
|
|
83
|
-
return {
|
|
47
|
+
return { identical: checks.every((check) => check.result === "identical"), segments: checks };
|
|
84
48
|
}
|
|
@@ -29,8 +29,9 @@ The script prints one JSON object. Addresses are `$` and four hex digits.
|
|
|
29
29
|
|
|
30
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
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
|
-
-
|
|
33
|
-
-
|
|
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.
|
|
34
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`.
|
|
35
36
|
|
|
36
37
|
## Workflow
|
package/skills/c64-disk/SKILL.md
CHANGED
|
@@ -46,8 +46,9 @@ File names show the characters that the C64 shows in a directory list. A byte th
|
|
|
46
46
|
|
|
47
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
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
|
-
-
|
|
50
|
-
-
|
|
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.
|
|
51
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`.
|
|
52
53
|
|
|
53
54
|
## Workflow
|
package/skills/c64-docs/SKILL.md
CHANGED
|
@@ -19,7 +19,7 @@ This skill does not change the knowledge.
|
|
|
19
19
|
## Inputs
|
|
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
|
-
- The
|
|
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
23
|
- The directory of the documentation, for example `docs`. The reference goes into a directory of its own, for example `docs/reference`.
|
|
24
24
|
|
|
25
25
|
## Tools / execution path
|
|
@@ -27,7 +27,7 @@ This skill does not change the knowledge.
|
|
|
27
27
|
The scripts run on this machine. They do not use the emulator or the host runtime.
|
|
28
28
|
|
|
29
29
|
```
|
|
30
|
-
node <skill>/scripts/generate.ts --source src --out docs/reference
|
|
30
|
+
node <skill>/scripts/generate.ts --source src --out docs/reference
|
|
31
31
|
node <skill>/scripts/link.ts docs --source src
|
|
32
32
|
node <skill>/scripts/link.ts docs --source src --check
|
|
33
33
|
```
|
|
@@ -36,7 +36,6 @@ node <skill>/scripts/link.ts docs --source src --check
|
|
|
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
|
-
| `--image unpacked/image.json` | The image. Without it, the tables are not decoded and the graphics page has no images. |
|
|
40
39
|
| `docs` | The directory of the documentation. `link.ts` reads every `.md` file below it. It changes only the pages that you wrote. It checks the pages of the reference. |
|
|
41
40
|
| `--reference <dir>` | The directory of the reference for `link.ts`. The default is `docs/reference` when it exists. |
|
|
42
41
|
| `--check` | `link.ts` writes nothing. It fails when a link is not up to date or a page has a problem. |
|
|
@@ -82,7 +81,13 @@ node <skill>/scripts/link.ts docs --source src --check
|
|
|
82
81
|
- `tables.md`: each table of addresses, jumps and words, entry by entry, with its target. An RTS table holds each target minus 1. The table gives the target.
|
|
83
82
|
- `memory-map.md`: each region in address order.
|
|
84
83
|
- `symbols.md`: each name in alphabetical order.
|
|
85
|
-
- `graphics.md`: each `sprite`, `bitmap` and `screen` region. A sprite or a
|
|
84
|
+
- `graphics.md`: each `sprite`, `bitmap`, `charset` and `screen` region. A sprite, a bitmap or a charset shows in hires and in multicolour, because the knowledge has no graphics mode. A charset shows 16 characters in each row. A screen shows as text.
|
|
85
|
+
|
|
86
|
+
### Images and assets
|
|
87
|
+
|
|
88
|
+
- The reference draws each sprite, bitmap and charset region, and it decodes each table. To get the image of a graphic or the entries of a table, classify its region with c64-knowledge. Do not write a script that draws or decodes them.
|
|
89
|
+
- A script of the project decodes only a format of the program, for example a packed map or a music stream. Put the script in `tools/` of the project. Its output files are the assets, for example in `assets/rooms/`.
|
|
90
|
+
- When the code of the program makes the data, run that code in VICE with a run of c64-testing. Observe the memory, or capture the screen. Do not write a 6502 emulator in the script.
|
|
86
91
|
|
|
87
92
|
In the call graph, a solid arrow is a call, a thick arrow is a table entry, and a dotted arrow is a jump. A table entry goes from each routine that reads the table to each target in code. A table of data addresses gives no arrow.
|
|
88
93
|
|
|
@@ -103,7 +108,7 @@ Do not store:
|
|
|
103
108
|
|
|
104
109
|
- `out`, `index` and `revision`.
|
|
105
110
|
- `pages`, `entries`, `tables`, `images` and `edges`: the counts of the reference.
|
|
106
|
-
- `
|
|
111
|
+
- `program`: `the knowledge`, or a note when the knowledge has no program. Without the program, the tables are not decoded and the graphics page has no images.
|
|
107
112
|
|
|
108
113
|
`link.ts` gives one JSON object:
|
|
109
114
|
|
|
@@ -27,7 +27,7 @@ These rules apply to each page that you write. `link.ts` checks the rules that a
|
|
|
27
27
|
- Use mermaid for the diagrams: `flowchart` for calls and data flow, `stateDiagram-v2` for state machines. Write the name and the address in each node: `"draw_room $09BC"`.
|
|
28
28
|
- A diagram gets no links. Give the links in a table or a list under the diagram.
|
|
29
29
|
- Keep a diagram small: at most about 20 nodes. Make two diagrams before one diagram that a reader cannot read.
|
|
30
|
-
- Embed an image only from the project. It can be an
|
|
30
|
+
- Embed an image only from the project. It can be an image of the reference (`reference/images/`) or a PNG that a skill made. It can also be an asset that a script of the project made from a format of the program, for example `../assets/rooms/room00.png`. `link.ts` checks that each image exists.
|
|
31
31
|
- Give each image one line of text that tells what it shows.
|
|
32
32
|
|
|
33
33
|
## Text
|
|
@@ -1,12 +1,11 @@
|
|
|
1
1
|
// c64-docs generate.ts (D80): the reference pages of a program from the
|
|
2
|
-
// knowledge, the source map of c64-source and the
|
|
2
|
+
// knowledge, the source map of c64-source and the program in the knowledge. The pages are
|
|
3
3
|
// generated and never edited; the pages of a person link to them.
|
|
4
4
|
|
|
5
5
|
import { existsSync, readdirSync, readFileSync, rmSync } from "node:fs";
|
|
6
6
|
import { join, posix } from "node:path";
|
|
7
7
|
import { parseArgs } from "node:util";
|
|
8
8
|
|
|
9
|
-
import { ImageError, readImage } from "#src/host-client/image.ts";
|
|
10
9
|
import { WireFailure } from "#src/host-client/tools.ts";
|
|
11
10
|
import { writeProjectFile } from "#src/host-client/transfer.ts";
|
|
12
11
|
import { conflicts } from "#src/knowledge/conflicts.ts";
|
|
@@ -16,18 +15,18 @@ import { resolveProjectPath } from "#src/project.ts";
|
|
|
16
15
|
import { runScript, UsageError } from "#src/script.ts";
|
|
17
16
|
import { buildReference, GENERATED_MARK } from "./lib/reference.ts";
|
|
18
17
|
import { readSourceMap } from "./lib/source-map.ts";
|
|
18
|
+
import { programImage, type ProgramImage } from "#src/knowledge/program.ts";
|
|
19
19
|
|
|
20
|
-
const USAGE = `generate.ts --source <dir> --out <dir>
|
|
20
|
+
const USAGE = `generate.ts --source <dir> --out <dir>
|
|
21
21
|
|
|
22
22
|
--source <dir> the source directory of c64-source, with its source-map.json,
|
|
23
23
|
relative to the project folder
|
|
24
24
|
--out <dir> the directory for the reference pages, relative to the project
|
|
25
25
|
folder, for example docs/reference; generate.ts writes only
|
|
26
26
|
there and refuses a page in it that it did not write
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
The result is one JSON object.`;
|
|
27
|
+
When the knowledge has the program of the source (knowledge.ts image of the
|
|
28
|
+
c64-knowledge skill, D83), the reference decodes the tables and shows the
|
|
29
|
+
graphics. The result is one JSON object.`;
|
|
31
30
|
|
|
32
31
|
/** The files of an earlier run, or a refusal that names a file of a person. */
|
|
33
32
|
function oldFiles(out: string): string[] {
|
|
@@ -51,7 +50,7 @@ function oldFiles(out: string): string[] {
|
|
|
51
50
|
}
|
|
52
51
|
|
|
53
52
|
function run(argv: string[]): Record<string, unknown> {
|
|
54
|
-
const { values, positionals } = parseArgs({ args: argv, strict: true, allowPositionals: true, options: { source: { type: "string" }, out: { type: "string" },
|
|
53
|
+
const { values, positionals } = parseArgs({ args: argv, strict: true, allowPositionals: true, options: { source: { type: "string" }, out: { type: "string" }, help: { type: "boolean" } } });
|
|
55
54
|
if (values.help) throw new UsageError("");
|
|
56
55
|
if (positionals.length > 0) throw new UsageError(`generate.ts takes no positional arguments, not ${positionals[0]}`);
|
|
57
56
|
if (values.source === undefined) throw new UsageError("give --source <dir>: the source directory of c64-source");
|
|
@@ -64,18 +63,11 @@ function run(argv: string[]): Record<string, unknown> {
|
|
|
64
63
|
}
|
|
65
64
|
}
|
|
66
65
|
const map = readSourceMap(values.source);
|
|
67
|
-
let image: ReturnType<typeof readImage> | undefined;
|
|
68
|
-
if (values.image !== undefined) {
|
|
69
|
-
try {
|
|
70
|
-
image = readImage(values.image);
|
|
71
|
-
} catch (error) {
|
|
72
|
-
if (error instanceof ImageError) throw new UsageError(error.message);
|
|
73
|
-
throw error;
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
66
|
const db = openForRead();
|
|
77
67
|
let knowledge;
|
|
68
|
+
let image: ProgramImage | undefined;
|
|
78
69
|
try {
|
|
70
|
+
image = programImage(db, map.space);
|
|
79
71
|
const found = conflicts(db).length;
|
|
80
72
|
if (found > 0) throw new KnowledgeError("conflict", `The knowledge has ${found} conflict${found === 1 ? "" : "s"} from a git merge. Nothing was written. Run c64-knowledge's knowledge.ts merge, then generate the source and the reference again.`);
|
|
81
73
|
const revision = currentRevision(db);
|
|
@@ -99,12 +91,12 @@ function run(argv: string[]): Record<string, unknown> {
|
|
|
99
91
|
out: posix.normalize(values.out),
|
|
100
92
|
map,
|
|
101
93
|
...knowledge,
|
|
102
|
-
...(image === undefined ? {} : { image: {
|
|
94
|
+
...(image === undefined ? {} : { image: { byteAt: image.byteAt, inImage: (address: number) => image.segments.some((segment) => address >= segment.start && address <= segment.end) } }),
|
|
103
95
|
});
|
|
104
96
|
const directory = resolveProjectPath(values.out);
|
|
105
97
|
for (const path of old) rmSync(join(directory, path), { force: true });
|
|
106
98
|
for (const file of built.files) writeProjectFile(file.path, file.bytes, { what: "--out" });
|
|
107
|
-
return { out: values.out, revision: knowledge.revision, index: posix.join(posix.normalize(values.out), "index.md"), ...built.counts,
|
|
99
|
+
return { out: values.out, revision: knowledge.revision, index: posix.join(posix.normalize(values.out), "index.md"), ...built.counts, program: image === undefined ? "none: put the program into the knowledge with knowledge.ts image to decode the tables and show the graphics" : "the knowledge" };
|
|
108
100
|
}
|
|
109
101
|
|
|
110
102
|
await runScript(run, { usage: USAGE, refusals: [KnowledgeError] });
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
import { posix } from "node:path";
|
|
7
7
|
|
|
8
8
|
import { formatC64Address, POINTER_TABLE_TYPES, pointerTableEntries, type PointerTableType } from "#src/c64.ts";
|
|
9
|
-
import { bitmapFrame, encodePng, PATTERN_PALETTES, scaleFrame, screenText, spriteSheet, type GraphicsMode } from "#src/graphics.ts";
|
|
9
|
+
import { bitmapFrame, charsetFrame, encodePng, PATTERN_PALETTES, scaleFrame, screenText, spriteSheet, type GraphicsMode } from "#src/graphics.ts";
|
|
10
10
|
import { mermaidGraph, ownerOf, routineEdges, routineHeads, type GraphReference, type RoutineHead } from "#src/knowledge/graph.ts";
|
|
11
11
|
import type { CommentRow, ModuleRow, ReferenceRow, RegionRow, SymbolRow } from "#src/knowledge/read.ts";
|
|
12
12
|
import type { RegionType } from "#src/knowledge/schema.ts";
|
|
@@ -25,7 +25,7 @@ export interface ReferenceInput {
|
|
|
25
25
|
comments: CommentRow[];
|
|
26
26
|
references: ReferenceRow[];
|
|
27
27
|
modules: ModuleRow[];
|
|
28
|
-
image?: {
|
|
28
|
+
image?: { byteAt: (address: number) => number; inImage: (address: number) => boolean };
|
|
29
29
|
}
|
|
30
30
|
|
|
31
31
|
export interface ReferenceFile {
|
|
@@ -37,7 +37,7 @@ export interface ReferenceFile {
|
|
|
37
37
|
/** Files that the source map lists but that hold no code of the program. */
|
|
38
38
|
const NOT_PIECES = new Set(["main.asm", "constants.asm", "macros.asm", "loader.asm"]);
|
|
39
39
|
const TABLE_TYPES: readonly RegionType[] = [...POINTER_TABLE_TYPES, "jumps", "words"];
|
|
40
|
-
const GRAPHICS_TYPES: readonly RegionType[] = ["sprite", "bitmap", "screen"];
|
|
40
|
+
const GRAPHICS_TYPES: readonly RegionType[] = ["sprite", "bitmap", "charset", "screen"];
|
|
41
41
|
/** A list longer than this ends with the number of the rest. */
|
|
42
42
|
const MAX_LISTED = 40;
|
|
43
43
|
/** A call graph with more edges is not readable: then it shows the calls only, and at most this many. */
|
|
@@ -299,7 +299,7 @@ export function buildReference(input: ReferenceInput): { files: ReferenceFile[];
|
|
|
299
299
|
{
|
|
300
300
|
const page = posix.join(out, "tables.md");
|
|
301
301
|
const lines = ["# Tables", "", `Each table of addresses, jumps and words, entry by entry. An RTS table holds each target minus 1; the target column gives the target. Index: [the reference](${rel(page, posix.join(out, "index.md"))}).`];
|
|
302
|
-
if (input.image === undefined) lines.push("", "
|
|
302
|
+
if (input.image === undefined) lines.push("", "Put the program into the knowledge with knowledge.ts image, then generate the reference again to decode the tables.");
|
|
303
303
|
for (const table of tables) {
|
|
304
304
|
const name = entryAt.get(table.start);
|
|
305
305
|
lines.push("", `<a id="${tableAnchor(table)}"></a>`, `## ${name === undefined ? `\`${range(table.start, table.end)}\`` : `${codeOfName(page, name.name)} \`${range(table.start, table.end)}\``}`, "", `*${table.type}*`);
|
|
@@ -327,9 +327,9 @@ export function buildReference(input: ReferenceInput): { files: ReferenceFile[];
|
|
|
327
327
|
const lines = [
|
|
328
328
|
"# Graphics",
|
|
329
329
|
"",
|
|
330
|
-
`Each sprite, bitmap and screen region of the knowledge. The knowledge gives no display mode and no colours, so a sprite or
|
|
330
|
+
`Each sprite, bitmap, charset and screen region of the knowledge. The knowledge gives no display mode and no colours, so a sprite, bitmap or charset shows in hires and in multicolour, in pattern colours: black is pixel value 0, white is 1 in hires; in multicolour light grey is 1, grey is 2 and yellow is 3. Index: [the reference](${rel(page, posix.join(out, "index.md"))}).`,
|
|
331
331
|
];
|
|
332
|
-
if (input.image === undefined && graphics.length > 0) lines.push("", "
|
|
332
|
+
if (input.image === undefined && graphics.length > 0) lines.push("", "Put the program into the knowledge with knowledge.ts image, then generate the reference again to show the graphics.");
|
|
333
333
|
for (const region of graphics) {
|
|
334
334
|
const anchor = entryAt.get(region.start)?.name ?? `${region.type}_${region.start.toString(16).padStart(4, "0")}`;
|
|
335
335
|
const name = entryAt.get(region.start);
|
|
@@ -343,9 +343,9 @@ export function buildReference(input: ReferenceInput): { files: ReferenceFile[];
|
|
|
343
343
|
continue;
|
|
344
344
|
}
|
|
345
345
|
for (const mode of ["hires", "multicolour"] as GraphicsMode[]) {
|
|
346
|
-
// A sheet of sprites, or a bitmap of at most 8 rows of cells, is small: each pixel shows as 2 x 2.
|
|
347
|
-
const raw = region.type === "sprite" ? spriteSheet(bytes, mode, region.element === 63 ? 63 : 64) : bitmapFrame(bytes, mode);
|
|
348
|
-
const frame = region.type === "sprite" || raw.height <= 64 ? scaleFrame(raw, 2) : raw;
|
|
346
|
+
// A sheet of sprites, a charset, or a bitmap of at most 8 rows of cells, is small: each pixel shows as 2 x 2.
|
|
347
|
+
const raw = region.type === "sprite" ? spriteSheet(bytes, mode, region.element === 63 ? 63 : 64) : region.type === "charset" ? charsetFrame(bytes, mode) : bitmapFrame(bytes, mode);
|
|
348
|
+
const frame = region.type === "sprite" || region.type === "charset" || raw.height <= 64 ? scaleFrame(raw, 2) : raw;
|
|
349
349
|
const image = posix.join(out, "images", `${anchor}-${mode}.png`);
|
|
350
350
|
files.push({ path: image, bytes: encodePng(frame, PATTERN_PALETTES[mode]) });
|
|
351
351
|
lines.push("", `})`);
|
|
@@ -18,7 +18,6 @@ export interface SourceLine {
|
|
|
18
18
|
export interface SourceMap {
|
|
19
19
|
/** The source directory, relative to the project folder. */
|
|
20
20
|
directory: string;
|
|
21
|
-
image: string;
|
|
22
21
|
space: Space;
|
|
23
22
|
revision: string;
|
|
24
23
|
files: string[];
|
|
@@ -60,7 +59,6 @@ export function readSourceMap(directory: string): SourceMap {
|
|
|
60
59
|
const byName = new Map(labels.map((label) => [label.name, label]));
|
|
61
60
|
return {
|
|
62
61
|
directory,
|
|
63
|
-
image: String(raw.image),
|
|
64
62
|
space: String(raw.space ?? "c64") as Space,
|
|
65
63
|
revision: String(raw.revision),
|
|
66
64
|
files: (raw.files as unknown[]).map(String),
|
|
@@ -51,9 +51,9 @@ Use only the MCP tools. Each MCP session has its own C64. Nothing else can chang
|
|
|
51
51
|
|
|
52
52
|
Each memory and register tool has a `space`. Use `c64` for the computer. Use `drive8` for the CPU of the 1541 disk drive. The memory `view` is `cpu` or `ram`. With `cpu`, you see what the CPU sees now, with ROM and I/O where the memory configuration puts them. With `ram`, you see the RAM under them.
|
|
53
53
|
|
|
54
|
-
For a large result, give `out` with a path in the project. `c64_memory_read`, `c64_disassemble`, `c64_cpu_history` and `c64_screen` then write the result to that file. The answer gives only the file, the number of bytes and a SHA-256. Use it for a memory dump of more than a few hundred bytes, a long listing, or a screen that the user wants to keep. `c64_memmap` action `read` and `c64_snapshot` action `save` also take `out`.
|
|
54
|
+
For a large result, give `out` with a path in the project. `c64_memory_read`, `c64_disassemble`, `c64_cpu_history` and `c64_screen` then write the result to that file. The answer gives only the file, the number of bytes and a SHA-256. Use it for a memory dump of more than a few hundred bytes, a long listing, or a screen that the user wants to keep. `c64_memmap` action `read` and `c64_snapshot` action `save` also take `out`. Write a file that is only for your work in `.c64-re-tools/local/` of the project. Git ignores that folder.
|
|
55
55
|
|
|
56
|
-
`c64_memmap` keeps RAM, ROM and I/O apart. Use action `summary` to see which code ran. Use action `read` with `out` to keep a coverage run as a file, for example
|
|
56
|
+
`c64_memmap` keeps RAM, ROM and I/O apart. Use action `summary` to see which code ran. Use action `read` with `out` to keep a coverage run as a file, for example `.c64-re-tools/local/attract.bin`. Then add it to the knowledge with `coverage` of the c64-knowledge skill. Clear the map before each run that you want to keep apart.
|
|
57
57
|
|
|
58
58
|
- Only the first byte of an instruction gets execute. Thus `read` without `out` gives a new range at each instruction of code. For code, use `summary`, or `read` with `out`.
|
|
59
59
|
- A read on a byte right after executed code, or at a return address, can be a dummy read of the 6502. The CPU reads such a byte but does not use it.
|