@henols/c64-re-tools 2.1.0-rc.2 → 2.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/skills/c64-assembler/SKILL.md +1 -0
- package/skills/c64-assembler/scripts/assembler.ts +10 -0
- package/skills/c64-disk/SKILL.md +2 -2
- package/skills/c64-disk/scripts/disk.ts +24 -10
- package/skills/c64-disk/scripts/lib/containers.ts +9 -9
- package/skills/c64-docs/scripts/lib/links.ts +3 -3
- package/skills/c64-docs/scripts/lib/reference.ts +2 -2
- package/skills/c64-emulator/SKILL.md +8 -2
- package/skills/c64-knowledge/SKILL.md +3 -1
- package/skills/c64-knowledge/scripts/knowledge.ts +59 -43
- package/skills/c64-memory-map/SKILL.md +1 -1
- package/skills/c64-memory-map/scripts/memory-map.ts +3 -2
- package/skills/c64-provenance/SKILL.md +5 -4
- package/skills/c64-provenance/scripts/lib/library.ts +18 -6
- package/skills/c64-provenance/scripts/lib/separate.ts +45 -6
- package/skills/c64-provenance/scripts/provenance.ts +5 -4
- package/skills/c64-source/SKILL.md +2 -0
- package/skills/c64-source/scripts/lib/source.ts +2 -2
- package/skills/c64-source/scripts/source.ts +10 -6
- package/skills/c64-static-analysis/scripts/lib/idioms.ts +6 -2
- package/skills/c64-static-analysis/scripts/static-analysis.ts +4 -3
- package/skills/c64-testing/SKILL.md +11 -1
- package/skills/c64-testing/references/recording-format.md +2 -0
- package/skills/c64-testing/references/scenario-format.md +6 -0
- package/skills/c64-testing/scripts/lib/diff-files.ts +63 -13
- package/skills/c64-testing/scripts/lib/diff.ts +3 -3
- package/skills/c64-testing/scripts/lib/recording.ts +47 -12
- package/skills/c64-testing/scripts/lib/replay.ts +11 -0
- package/skills/c64-testing/scripts/lib/runs.ts +64 -4
- package/skills/c64-testing/scripts/lib/scenario.ts +43 -6
- package/skills/c64-testing/scripts/testing.ts +15 -2
- package/skills/c64-unpacker/SKILL.md +9 -5
- package/skills/c64-unpacker/scripts/lib/evidence.ts +4 -5
- package/skills/c64-unpacker/scripts/lib/loads.ts +3 -2
- package/skills/c64-unpacker/scripts/unpacker.ts +75 -25
- package/src/host/vice/session.ts +99 -10
- package/src/host-client/image.ts +32 -0
- package/src/host-client/steps.ts +106 -6
- package/src/host-client/tools.ts +1 -1
- package/src/host-client/transfer.ts +35 -8
- package/src/knowledge/expression.ts +1 -1
- package/src/knowledge/history.ts +19 -2
- package/src/knowledge/names.ts +2 -1
- package/src/knowledge/program.ts +1 -16
- package/src/knowledge/read.ts +43 -0
- package/src/knowledge/schema.ts +3 -2
- package/src/knowledge/text.ts +1 -0
- package/src/knowledge/write.ts +40 -6
- package/src/mcp/output.ts +5 -0
- package/src/mcp/server.ts +19 -4
- package/src/mcp/tools/debug.ts +7 -6
- package/src/mcp/tools/execution.ts +18 -5
- package/src/mcp/tools/media.ts +14 -2
- package/src/mcp/tools/memory.ts +7 -3
- package/src/mcp/tools/video.ts +5 -3
- package/src/project.ts +1 -1
- package/src/protocol/vice.ts +4 -1
package/package.json
CHANGED
|
@@ -100,6 +100,7 @@ With `--compare`, `compare` gives `identical` and `segments`: one check for each
|
|
|
100
100
|
- `FAIL` with `assembled: true`: the build is not identical to the program in the knowledge. Read `compare.segments`.
|
|
101
101
|
- `installation-incomplete`, or `machine-unavailable` with a message that the host runtime does not run: stop the task. Give the message to the user and wait for the user. The message gives the remedy. Do not make a replacement for the tool or the host runtime, for example your own disk parser, disassembler or emulator script.
|
|
102
102
|
- `invalid-input`: an option or a path is wrong. Read the message and correct the call.
|
|
103
|
+
- `not-found`: the source root has no main source file with that name, or no folder with files for an `--include`. Give a path relative to the source root.
|
|
103
104
|
|
|
104
105
|
## Handoffs
|
|
105
106
|
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
// The c64-assembler script: assembles project source with ACME, which it runs
|
|
2
2
|
// itself, and writes the program into the project.
|
|
3
3
|
|
|
4
|
+
import { posix } from "node:path";
|
|
5
|
+
|
|
4
6
|
import { formatC64Address } from "#src/c64.ts";
|
|
5
7
|
import { Refusal } from "#src/errors.ts";
|
|
6
8
|
import { readProjectTree, writeReported } from "#src/host-client/transfer.ts";
|
|
@@ -62,6 +64,14 @@ await runScript({
|
|
|
62
64
|
const setPc = call.address("set-pc");
|
|
63
65
|
const tree = readProjectTree(sourceRoot);
|
|
64
66
|
if (!tree.files.some((file) => file.path === entry)) throw new Refusal("not-found", `${entry} is not a file in ${sourceRoot}. Give the main source file relative to the source root.`);
|
|
67
|
+
// ACME sees only the files of the source root: an include folder holds some of them. A path out of the root is refused later.
|
|
68
|
+
for (const folder of call.strings("include")) {
|
|
69
|
+
const inside = posix.normalize(folder.replaceAll("\\", "/")).replace(/\/+$/, "");
|
|
70
|
+
if (inside === "." || inside === ".." || inside.startsWith("../") || posix.isAbsolute(inside)) continue;
|
|
71
|
+
if (!tree.files.some((file) => file.path.startsWith(`${inside}/`))) {
|
|
72
|
+
throw new Refusal("not-found", `There is no include folder ${folder} with files in the source root ${sourceRoot}. Give --include a folder relative to the source root.`);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
65
75
|
const params: AcmeParams = { files: tree.files, entrySource: entry, includeDirs: call.strings("include"), defines: Object.fromEntries(call.strings("define").map(parseDefine)), ...(setPc === undefined ? {} : { setPc }) };
|
|
66
76
|
|
|
67
77
|
const { result, attachments } = await assemble(params, tree.contents, localToolContext());
|
package/skills/c64-disk/SKILL.md
CHANGED
|
@@ -97,9 +97,9 @@ The directory, the BAM, an entry or a sector chain as JSON.
|
|
|
97
97
|
## Failure and conflicts
|
|
98
98
|
|
|
99
99
|
- `not-found`: the image file is missing, or the image has no file with that name. Read the directory again and compare the names.
|
|
100
|
-
- `media-error`: the image has a defect or is not
|
|
100
|
+
- `media-error`: the image, the T64 file or the CRT file has a defect, or its content is not of its type. A CRT with a hardware type that the script does not read also gives `media-error`. The message tells the problem, for example a sector chain that loops. Tell the user. Do not change the file.
|
|
101
101
|
- `installation-incomplete`, or `machine-unavailable` with a message that the host runtime does not run: stop the task. Give the message to the user and wait for the user. The message gives the remedy. Do not make a replacement for the tool or the host runtime, for example your own disk parser, disassembler or emulator script.
|
|
102
|
-
- `invalid-input`: an argument or a path is wrong. Read the message and correct the call.
|
|
102
|
+
- `invalid-input`: an argument or a path is wrong. A file with an extension that the command does not take is also wrong. The message gives each type that the command takes. Read the message and correct the call.
|
|
103
103
|
|
|
104
104
|
A file that is not `closed` was not written fully. Its data can be incomplete.
|
|
105
105
|
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
import { formatC64Address } from "#src/c64.ts";
|
|
6
6
|
import { isRefusal, Refusal } from "#src/errors.ts";
|
|
7
|
-
import { inspectDisk } from "#src/host-client/tools.ts";
|
|
7
|
+
import { DISK_IMAGE_TYPES, inspectDisk } from "#src/host-client/tools.ts";
|
|
8
8
|
import { readProjectFile, writeReported } from "#src/host-client/transfer.ts";
|
|
9
9
|
import { OPTIONS, runScript, type Call, type SubjectSpec } from "#src/script.ts";
|
|
10
10
|
import { parseCrt, parseT64, tapeFile } from "./lib/containers.ts";
|
|
@@ -13,14 +13,30 @@ const IMAGE: SubjectSpec = { name: "<image>", help: "a .d64, .d71, .d81 or .g64
|
|
|
13
13
|
const NAME: SubjectSpec = { name: "<name>", help: "the file name as the directory shows it. Write a byte that has no character as {$xx}, for example {$c1}" };
|
|
14
14
|
|
|
15
15
|
type Kind = "disk" | "t64" | "crt";
|
|
16
|
-
|
|
16
|
+
/** The kind of a file by its extension; undefined for a file that no command takes. */
|
|
17
|
+
function kindOf(image: string): Kind | undefined {
|
|
18
|
+
const extension = image.toLowerCase().split(".").at(-1) ?? "";
|
|
19
|
+
if (extension === "t64" || extension === "crt") return extension;
|
|
20
|
+
return (DISK_IMAGE_TYPES as readonly string[]).includes(extension) ? "disk" : undefined;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const KIND_TEXT: Record<Kind, string> = { disk: "a .d64, .d71, .d81 or .g64 disk image", t64: "a .t64 tape file", crt: "a .crt cartridge file" };
|
|
24
|
+
|
|
25
|
+
/** The kind of the image of a command; refuses a file of a kind that the command does not take, and names every kind that it takes. */
|
|
26
|
+
function kindFor(command: string, image: string, takes: readonly Kind[]): Kind {
|
|
27
|
+
const kind = kindOf(image);
|
|
28
|
+
if (kind !== undefined && takes.includes(kind)) return kind;
|
|
29
|
+
const kinds = takes.map((item) => KIND_TEXT[item]);
|
|
30
|
+
const list = kinds.length === 1 ? kinds[0]! : `${kinds.slice(0, -1).join(", ")} or ${kinds.at(-1)}`;
|
|
31
|
+
throw new Refusal("invalid-input", `${image} is not a file that ${command} takes. Give ${list}.`);
|
|
32
|
+
}
|
|
17
33
|
|
|
18
34
|
/** Runs `body` and names the file in a refusal of its content. */
|
|
19
35
|
function about<T>(image: string, body: () => T): T {
|
|
20
36
|
try {
|
|
21
37
|
return body();
|
|
22
38
|
} catch (error) {
|
|
23
|
-
if (isRefusal(error) && error.code === "
|
|
39
|
+
if (isRefusal(error) && error.code === "media-error") throw new Refusal(error.code, `${image}: ${error.message}`);
|
|
24
40
|
throw error;
|
|
25
41
|
}
|
|
26
42
|
}
|
|
@@ -55,9 +71,7 @@ function cartridge(image: string) {
|
|
|
55
71
|
}
|
|
56
72
|
|
|
57
73
|
/** A command that takes only a disk image. */
|
|
58
|
-
const diskOnly = (call: Call, action: string) =>
|
|
59
|
-
if (kindOf(call.subject(0)) !== "disk") throw new Refusal("invalid-input", `${action} needs a .d64, .d71, .d81 or .g64 disk image, not ${call.subject(0)}`);
|
|
60
|
-
};
|
|
74
|
+
const diskOnly = (call: Call, action: string) => kindFor(action, call.subject(0), ["disk"]);
|
|
61
75
|
|
|
62
76
|
await runScript({
|
|
63
77
|
name: "disk",
|
|
@@ -69,7 +83,7 @@ await runScript({
|
|
|
69
83
|
subjects: [{ ...IMAGE, help: "a disk image, a .t64 tape file or a .crt cartridge file, relative to the project folder" }],
|
|
70
84
|
run: async (call) => {
|
|
71
85
|
const image = call.subject(0);
|
|
72
|
-
const kind =
|
|
86
|
+
const kind = kindFor("directory", image, ["disk", "t64", "crt"]);
|
|
73
87
|
if (kind === "t64") return tapeDirectory(image);
|
|
74
88
|
if (kind === "crt") {
|
|
75
89
|
const { header, chips } = cartridge(image);
|
|
@@ -113,8 +127,8 @@ await runScript({
|
|
|
113
127
|
run: async (call) => {
|
|
114
128
|
const [image, name] = [call.subject(0), call.subject(1)];
|
|
115
129
|
const out = call.string("out")!;
|
|
116
|
-
|
|
117
|
-
|
|
130
|
+
if (kindOf(image) === "crt") throw new Refusal("invalid-input", `read takes a disk image or a .t64 tape file. Use the command crt for the cartridge file ${image}.`);
|
|
131
|
+
const kind = kindFor("read", image, ["disk", "t64"]);
|
|
118
132
|
if (kind === "t64") {
|
|
119
133
|
const { bytes } = readProjectFile(image);
|
|
120
134
|
const tape = about(image, () => parseT64(bytes));
|
|
@@ -134,7 +148,7 @@ await runScript({
|
|
|
134
148
|
options: { out: { ...OPTIONS.out, value: "<dir>", help: "the folder for the files, relative to the project folder", required: true } },
|
|
135
149
|
run: (call) => {
|
|
136
150
|
const image = call.subject(0);
|
|
137
|
-
|
|
151
|
+
kindFor("crt", image, ["crt"]);
|
|
138
152
|
const out = call.string("out")!;
|
|
139
153
|
const { crt, header, chips } = cartridge(image);
|
|
140
154
|
const files = crt.chips.map((chip, index) => writeReported(`${out}/${chips[index]!.file}`, Buffer.concat([Buffer.from([chip.address & 0xff, chip.address >> 8]), chip.data]), { what: "--out" }));
|
|
@@ -34,32 +34,32 @@ const SIGNATURE = "C64 CARTRIDGE ";
|
|
|
34
34
|
|
|
35
35
|
export function parseCrt(bytes: Uint8Array): Crt {
|
|
36
36
|
const buffer = Buffer.from(bytes.buffer, bytes.byteOffset, bytes.byteLength);
|
|
37
|
-
if (buffer.length < 0x40 || buffer.toString("latin1", 0, 16) !== SIGNATURE) throw new Refusal("
|
|
37
|
+
if (buffer.length < 0x40 || buffer.toString("latin1", 0, 16) !== SIGNATURE) throw new Refusal("media-error", "the file is not a CRT: it does not start with C64 CARTRIDGE");
|
|
38
38
|
const headerLength = buffer.readUInt32BE(0x10);
|
|
39
39
|
const type = buffer.readUInt16BE(0x16);
|
|
40
40
|
const exrom = buffer[0x18]!;
|
|
41
41
|
const game = buffer[0x19]!;
|
|
42
42
|
const typeName = CRT_TYPES[type];
|
|
43
43
|
if (typeName === undefined) {
|
|
44
|
-
throw new Refusal("
|
|
44
|
+
throw new Refusal("media-error", `the CRT has hardware type ${type}; this toolkit reads types ${Object.entries(CRT_TYPES).map(([number, name]) => `${number} (${name})`).join(", ")}`);
|
|
45
45
|
}
|
|
46
46
|
const name = buffer.toString("latin1", 0x20, 0x40).replace(/\0+$/, "");
|
|
47
47
|
const chips: CrtChip[] = [];
|
|
48
48
|
let offset = Math.max(headerLength, 0x40);
|
|
49
49
|
while (offset + 0x10 <= buffer.length) {
|
|
50
|
-
if (buffer.toString("latin1", offset, offset + 4) !== "CHIP") throw new Refusal("
|
|
50
|
+
if (buffer.toString("latin1", offset, offset + 4) !== "CHIP") throw new Refusal("media-error", `the CRT has no CHIP packet at offset ${offset}`);
|
|
51
51
|
const packetLength = buffer.readUInt32BE(offset + 4);
|
|
52
52
|
const chipType = buffer.readUInt16BE(offset + 8);
|
|
53
53
|
const bank = buffer.readUInt16BE(offset + 10);
|
|
54
54
|
const address = buffer.readUInt16BE(offset + 12);
|
|
55
55
|
const size = buffer.readUInt16BE(offset + 14);
|
|
56
|
-
if (packetLength < 0x10 || offset + packetLength > buffer.length) throw new Refusal("
|
|
57
|
-
if (bank > 255) throw new Refusal("
|
|
58
|
-
if (size > packetLength - 0x10 || address + size > 0x10000) throw new Refusal("
|
|
56
|
+
if (packetLength < 0x10 || offset + packetLength > buffer.length) throw new Refusal("media-error", `the CHIP packet at offset ${offset} runs past the end of the file`);
|
|
57
|
+
if (bank > 255) throw new Refusal("media-error", `the CHIP packet at offset ${offset} has bank ${bank}; banks go up to 255`);
|
|
58
|
+
if (size > packetLength - 0x10 || address + size > 0x10000) throw new Refusal("media-error", `the CHIP packet at offset ${offset} has a wrong size ${size} at ${formatC64Address(address)}`);
|
|
59
59
|
if (size > 0) chips.push({ bank, address, size, chipType, data: buffer.subarray(offset + 0x10, offset + 0x10 + size) });
|
|
60
60
|
offset += packetLength;
|
|
61
61
|
}
|
|
62
|
-
if (chips.length === 0) throw new Refusal("
|
|
62
|
+
if (chips.length === 0) throw new Refusal("media-error", "the CRT has no CHIP packet with data");
|
|
63
63
|
const crt: Crt = { name, type, typeName, exrom, game, chips };
|
|
64
64
|
if (type === 0) crt.mode = exrom === 0 && game === 1 ? "8k" : exrom === 0 && game === 0 ? "16k" : "ultimax";
|
|
65
65
|
return crt;
|
|
@@ -85,7 +85,7 @@ export interface Tape {
|
|
|
85
85
|
|
|
86
86
|
export function parseT64(bytes: Uint8Array): Tape {
|
|
87
87
|
const buffer = Buffer.from(bytes.buffer, bytes.byteOffset, bytes.byteLength);
|
|
88
|
-
if (buffer.length < 0x40 || !buffer.toString("latin1", 0, 32).startsWith("C64")) throw new Refusal("
|
|
88
|
+
if (buffer.length < 0x40 || !buffer.toString("latin1", 0, 32).startsWith("C64")) throw new Refusal("media-error", "the file is not a T64: it does not start with C64");
|
|
89
89
|
const maxEntries = buffer.readUInt16LE(0x22);
|
|
90
90
|
const name = buffer.toString("latin1", 0x28, 0x40).replace(/[\0 ]+$/, "");
|
|
91
91
|
const raw: Array<Omit<TapeEntry, "bytes" | "end"> & { end: number }> = [];
|
|
@@ -107,7 +107,7 @@ export function parseT64(bytes: Uint8Array): Tape {
|
|
|
107
107
|
// Many T64 files have a wrong end address: the next file or the end of the file bounds each one.
|
|
108
108
|
const offsets = [...raw.map((entry) => entry.offset), buffer.length].sort((a, b) => a - b);
|
|
109
109
|
const entries = raw.map((entry): TapeEntry => {
|
|
110
|
-
if (entry.offset >= buffer.length) throw new Refusal("
|
|
110
|
+
if (entry.offset >= buffer.length) throw new Refusal("media-error", `the T64 entry ${entry.name} starts past the end of the file`);
|
|
111
111
|
const available = offsets.find((offset) => offset > entry.offset)! - entry.offset;
|
|
112
112
|
const stated = entry.end - entry.start;
|
|
113
113
|
const bytes = stated > 0 && stated <= available ? stated : Math.min(available, 0x10000 - entry.start);
|
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
// its line in the source; the address after it links to the entry of the name
|
|
3
3
|
// in the reference, or, for an address inside the routine or the data of the
|
|
4
4
|
// name ("draw_room $0AAF", a place in draw_room), to the line of that address. A link that points into the source or the reference is
|
|
5
|
-
// managed:
|
|
6
|
-
// person wrote them;
|
|
5
|
+
// managed: docs.ts link writes it again when a line moves. Other links stay as the
|
|
6
|
+
// person wrote them; docs.ts link only checks that their targets exist.
|
|
7
7
|
|
|
8
8
|
import { posix } from "node:path";
|
|
9
9
|
|
|
@@ -24,7 +24,7 @@ export interface LinkContext {
|
|
|
24
24
|
lineOf: (address: number) => string | undefined;
|
|
25
25
|
/** The name of the routine or data that holds an address, for a message. */
|
|
26
26
|
placeOf: (address: number) => string | undefined;
|
|
27
|
-
/** True for a project path that
|
|
27
|
+
/** True for a project path that docs.ts link manages: a file of the source or a page of the reference. */
|
|
28
28
|
managed: (path: string) => boolean;
|
|
29
29
|
}
|
|
30
30
|
|
|
@@ -12,7 +12,7 @@ import type { CommentRow, ModuleRow, ReferenceRow, RegionRow, SymbolRow } from "
|
|
|
12
12
|
import type { RegionType } from "#src/knowledge/schema.ts";
|
|
13
13
|
import type { SourceMap } from "./source-map.ts";
|
|
14
14
|
|
|
15
|
-
/** The first line of every generated page. A page without it is a page of a person, and
|
|
15
|
+
/** The first line of every generated page. A page without it is a page of a person, and docs.ts generate never writes over it. */
|
|
16
16
|
export const GENERATED_MARK = "<!-- Generated by c64-docs";
|
|
17
17
|
|
|
18
18
|
export interface ReferenceInput {
|
|
@@ -183,7 +183,7 @@ export function buildReference(input: ReferenceInput): { files: ReferenceFile[];
|
|
|
183
183
|
}
|
|
184
184
|
const titleOf = (file: string) => input.modules.find((module) => module.file === file)?.title;
|
|
185
185
|
|
|
186
|
-
const header = `${GENERATED_MARK} from knowledge revision ${input.revision} and ${map.pathOf("source-map.json")}. Do not edit: change the knowledge, generate the source again, then run
|
|
186
|
+
const header = `${GENERATED_MARK} from knowledge revision ${input.revision} and ${map.pathOf("source-map.json")}. Do not edit: change the knowledge, generate the source again, then run docs.ts generate. -->`;
|
|
187
187
|
const files: ReferenceFile[] = [];
|
|
188
188
|
const write = (path: string, lines: string[]) => files.push({ path, bytes: Buffer.from(`${[header, ...lines].join("\n").replace(/\n{3,}/g, "\n\n")}\n`, "utf8") });
|
|
189
189
|
|
|
@@ -80,7 +80,7 @@ Write operations need a stopped CPU. Stop the CPU with `c64_execution` action `p
|
|
|
80
80
|
|
|
81
81
|
A cartridge from `c64_autostart` stays in the C64 through every reset, as on a real C64. `c64_status` gives `cartridge: true` while the C64 has it, and `cartridge: false` when it does not. `c64_autostart` of a file that is not a cartridge removes the cartridge first. `c64_autostart` also sets both joysticks to the center first. `joysticksCentred` gives the ports that it changed.
|
|
82
82
|
|
|
83
|
-
Step, next and until-return work only in space `c64`. The emulator cannot stop the disk drive CPU after one instruction. To find when the drive executes an address, use `c64_run_until` with an address target in space `drive8`, or a breakpoint in space `drive8`. The drive can execute a few more instructions before it stops. The computer completes its current instruction. The `pc` in the result is the drive address where it stopped.
|
|
83
|
+
Step, next and until-return work only in space `c64`. The emulator cannot stop the disk drive CPU after one instruction. To find when the drive executes an address, use `c64_run_until` with an address target in space `drive8`, or a breakpoint in space `drive8`. The drive can execute a few more instructions before it stops. The computer completes its current instruction. The `pc` in the result is the drive address where it stopped. `space` in the result is `drive8`. A breakpoint or a watchpoint in space `drive8` that stops `advance-frames` gives the same `pc` and `space`.
|
|
84
84
|
|
|
85
85
|
`c64_keyboard` puts keys in the keyboard queue of the KERNAL. A program that reads the keyboard matrix directly does not see them. Many games do this. No MCP tool presses a key of the keyboard matrix. For such a program:
|
|
86
86
|
|
|
@@ -137,7 +137,13 @@ An error is `{"error": {"code": "...", "message": "..."}}`. The message tells wh
|
|
|
137
137
|
- `machine-running`: stop the CPU first, then try again.
|
|
138
138
|
- `installation-incomplete`, or `machine-unavailable` with a message that the host runtime does not run: stop the task. Give the message to the user and wait for the user. The message gives the remedy. Do not make a replacement for the tool or the host runtime, for example your own disk parser, disassembler or emulator script. Do not try to repair the installation.
|
|
139
139
|
- `c64_run_until` with `stopReason` `timeout`: the target did not occur in the frame limit. This is evidence too. Report it.
|
|
140
|
-
- `
|
|
140
|
+
- `stopReason` `jam`: the CPU executed a JAM opcode and stopped. Report the address. The CPU stays stopped, as on a real C64. Each run gives `jam` again. Step, next, until-return and resume refuse with `operation-failed`. Only `c64_reset`, a snapshot restore or `c64_autostart` starts the CPU again.
|
|
141
|
+
- After a JAM, VICE records no CPU history until the next reset. If the CPU ran again after a snapshot restore, `c64_cpu_history` refuses with `operation-failed`. `c64_run_until` then gives `historyMissing` in place of `history`. Use `c64_reset`, or use `c64_trace`.
|
|
142
|
+
- `invalid-input`: the message names the field and the rule. Change the call to obey the rule.
|
|
143
|
+
- `not-found`: a file, a snapshot, a baseline or a point that the call names does not exist.
|
|
144
|
+
- `unsupported-in-space`: the action works only in space `c64`.
|
|
145
|
+
- `limit-exceeded`: the call goes past a limit of the session, for example the number of snapshots. The message names the limit.
|
|
146
|
+
- `operation-failed`: the emulator did not do the operation. The message tells why.
|
|
141
147
|
|
|
142
148
|
If the runtime evidence does not agree with the current knowledge, do not change the knowledge. Report the conflict and hand it to c64-knowledge.
|
|
143
149
|
|
|
@@ -139,6 +139,7 @@ The knowledge holds the program, so a project needs no image or coverage files.
|
|
|
139
139
|
- `image` writes only the bytes that changed. The same image again writes nothing. A new image replaces the program of the space. The history keeps the old bytes.
|
|
140
140
|
- `coverage` joins the maps. It keeps each address that a run executed, read or wrote. It also keeps each address that one run wrote and executed: self-modified code. `coverage` puts each map into the space of `--space`. You can add the same map to each space. A map that the space has is not added again.
|
|
141
141
|
- The capture and the maps are not necessary after the import. Keep them in `.c64-re-tools/local/`.
|
|
142
|
+
- A `revert` also gives back the coverage maps of that revision. A `revert` to a revision before an import removes the map and its ranges. Then you can import the map again, and `--evidence coverage:<map>` refuses it until you do.
|
|
142
143
|
|
|
143
144
|
For more than a few writes, use `apply -` and give the writes on standard input as one JSON list. Each write has `op` and the arguments of the command with the same name, for example:
|
|
144
145
|
|
|
@@ -213,6 +214,7 @@ Give one selector:
|
|
|
213
214
|
| `--value <symbol>` | The symbol plus the offset | `lda bar_colours-1,x` |
|
|
214
215
|
| `--expr <text>` | ACME expression text with names and numbers | `lda #(sprite_bank + 5*64) / 64` |
|
|
215
216
|
|
|
217
|
+
- The address is the start of an instruction in a code region. The script decodes the instructions from the start of the region, as `listing` does. It refuses an address in no code region and an operand byte of an instruction. Classify the code first.
|
|
216
218
|
- `--offset` adds a whole number to the symbol. Write a negative offset as `--offset=-1`.
|
|
217
219
|
- A name is a symbol of the knowledge or a platform name of the c64-memory-map skill.
|
|
218
220
|
- `--expr` takes `$`, `%` and decimal numbers, names, brackets, the operators `+ - * / % << >> >>> & | XOR`, and `<`, `>`, `-` and `!` before an operand.
|
|
@@ -277,7 +279,7 @@ The script records the origin `llm`. Use `--by user` only when the user decides
|
|
|
277
279
|
|
|
278
280
|
## Result
|
|
279
281
|
|
|
280
|
-
For a read: the current or historical knowledge in compact JSON.
|
|
282
|
+
For a read: the current or historical knowledge in compact JSON. Each list of a list command has `count` and `listed`. `--max-listed` limits `listed`. The default is 50. When the list is longer, `rest` tells how to get the other items. For `revisions`, give `--before` with the oldest revision in `listed`.
|
|
281
283
|
|
|
282
284
|
For a write: the new revision and the current value after the write. If the write replaces a symbol, a region or an operand expression of a different origin, `previous` gives that row as it was before the write. For example, an `llm` write over a `user` name gives `previous`. Read `previous` and tell the user about the change. For `apply`, `replaced` lists each write that replaced a row. Each item has the number of the write, its `op` and its `previous`.
|
|
283
285
|
|
|
@@ -17,11 +17,12 @@ import { operandText } from "#src/knowledge/expression.ts";
|
|
|
17
17
|
import { historyAt, historyNamed, historyOfMacro, revision, revisionNote, revisions, type HistoryEntry } from "#src/knowledge/history.ts";
|
|
18
18
|
import { joinBatches, type Batch, type JoinChoice, type JoinClash } from "#src/knowledge/join.ts";
|
|
19
19
|
import { knowledgeNames } from "#src/knowledge/names.ts";
|
|
20
|
-
import { addCoverage,
|
|
20
|
+
import { addCoverage, programCoverage, programImage, requireProgram, setImage, type ImageRequest } from "#src/knowledge/program.ts";
|
|
21
21
|
import {
|
|
22
22
|
analyzerNamesAt,
|
|
23
23
|
at,
|
|
24
24
|
commentsAt,
|
|
25
|
+
coverageRuns,
|
|
25
26
|
currentRevision,
|
|
26
27
|
listAnalyzerNames,
|
|
27
28
|
listComments,
|
|
@@ -83,7 +84,7 @@ import {
|
|
|
83
84
|
} from "#src/knowledge/write.ts";
|
|
84
85
|
import { platformNames } from "#src/platform.ts";
|
|
85
86
|
import { isInside, LOCAL_DIRECTORY, PROJECT_DIRECTORY, projectRelative, projectRoot, resolveProjectPath } from "#src/project.ts";
|
|
86
|
-
import { OPTIONS, runScript, UsageError, type Call, type CommandSpec, type OptionSpec, type SubjectSpec } from "#src/script.ts";
|
|
87
|
+
import { listed, OPTIONS, runScript, UsageError, type Call, type CommandSpec, type OptionSpec, type SubjectSpec } from "#src/script.ts";
|
|
87
88
|
import { listing, listingText, type ListingSource } from "./lib/listing.ts";
|
|
88
89
|
|
|
89
90
|
function oneOf<T extends string>(values: readonly T[], value: string | undefined, what: string): T {
|
|
@@ -226,13 +227,17 @@ function listingSource(db: DatabaseSync | undefined, space: string): ListingSour
|
|
|
226
227
|
};
|
|
227
228
|
}
|
|
228
229
|
|
|
229
|
-
/**
|
|
230
|
+
/** Every value of --origin, for the message of a wrong one. */
|
|
231
|
+
const ORIGIN_VALUES = `one of ${ORIGINS.join(", ")}, semantic or analyzer, or a comma list of them. semantic is ${SEMANTIC_ORIGINS.join(" and ")}. analyzer is ${ANALYZERS.slice(0, -1).join(", ")} and ${ANALYZERS.at(-1)}`;
|
|
232
|
+
|
|
233
|
+
/** --origin for reads: one origin, semantic (user and llm), analyzer (dxa, ghidra and heuristic), or a comma list. */
|
|
230
234
|
function originFilter(value: string): Origin[] {
|
|
231
235
|
const origins = new Set<Origin>();
|
|
232
236
|
for (const part of value.split(",")) {
|
|
233
237
|
if (part === "semantic") for (const origin of SEMANTIC_ORIGINS) origins.add(origin);
|
|
234
238
|
else if (part === "analyzer") for (const origin of ANALYZERS) origins.add(origin);
|
|
235
|
-
else
|
|
239
|
+
else if ((ORIGINS as readonly string[]).includes(part)) origins.add(part as Origin);
|
|
240
|
+
else throw new UsageError(`--origin cannot be ${JSON.stringify(part)}. Give ${ORIGIN_VALUES}`);
|
|
236
241
|
}
|
|
237
242
|
return [...origins];
|
|
238
243
|
}
|
|
@@ -371,6 +376,20 @@ const WRITE: Record<string, OptionSpec> = {
|
|
|
371
376
|
"expect-revision": OPTIONS["expect-revision"],
|
|
372
377
|
};
|
|
373
378
|
|
|
379
|
+
/** A list command: --max-listed limits each list of its result. */
|
|
380
|
+
const LIST = { "max-listed": OPTIONS["max-listed"] };
|
|
381
|
+
/** Where to get the rest of a list that a --range limits. */
|
|
382
|
+
const RANGE_REST = "a larger --max-listed, a smaller --range, or knowledge.ts export --out <file> for all current knowledge";
|
|
383
|
+
const MORE = "a larger --max-listed";
|
|
384
|
+
|
|
385
|
+
/** A list in a result: its count and its first --max-listed items, with rest when it is cut. */
|
|
386
|
+
function list<T, U>(call: Call, items: readonly T[], show: (item: T) => U, rest: string) {
|
|
387
|
+
return listed(items, call.count("max-listed")!, show, rest);
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
/** All rows: the lists count every row, and --max-listed limits only what the result shows. */
|
|
391
|
+
const ALL = Number.MAX_SAFE_INTEGER;
|
|
392
|
+
|
|
374
393
|
/** The rows from --range, or every address. */
|
|
375
394
|
const rangeOf = (call: Call) => call.range("range") ?? { start: 0x0000, end: 0xffff };
|
|
376
395
|
const originsOf = (call: Call) => {
|
|
@@ -459,50 +478,50 @@ const READ_COMMANDS: CommandSpec[] = [
|
|
|
459
478
|
name: "search",
|
|
460
479
|
summary: "Symbol names, analyzer names and comment texts that hold the text.",
|
|
461
480
|
subjects: [{ name: "<text>", help: "the text to find" }],
|
|
462
|
-
options: { origin: OPTIONS.origin,
|
|
481
|
+
options: { origin: OPTIONS.origin, ...LIST },
|
|
463
482
|
},
|
|
464
483
|
(db, call) => {
|
|
465
|
-
const found = search(db, call.subject(0),
|
|
466
|
-
|
|
484
|
+
const found = search(db, call.subject(0), ALL, originsOf(call), call.space);
|
|
485
|
+
const rest = "a larger --max-listed, or a longer <text>";
|
|
486
|
+
return { symbols: list(call, found.symbols, symbol, rest), analyzerNames: list(call, found.analyzerNames, symbol, rest), comments: list(call, found.comments, comment, rest) };
|
|
467
487
|
},
|
|
468
488
|
),
|
|
469
489
|
readCommand(
|
|
470
|
-
{ name: "symbols", summary: "The symbols, or with --analyzer the second names that DXA, Ghidra and the idioms gave.", options: { ...FILTER, kind: SYMBOL_KIND, analyzer: { kind: "flag", help: "the analyzer names in place of the symbols" } } },
|
|
490
|
+
{ name: "symbols", summary: "The symbols, or with --analyzer the second names that DXA, Ghidra and the idioms gave.", options: { ...FILTER, kind: SYMBOL_KIND, analyzer: { kind: "flag", help: "the analyzer names in place of the symbols" }, ...LIST } },
|
|
471
491
|
(db, call) => {
|
|
472
492
|
const kind = call.string("kind") as (typeof SYMBOL_KINDS)[number] | undefined;
|
|
473
|
-
if (call.flag("analyzer")) return { analyzerNames: listAnalyzerNames(db, rangeOf(call), kind, originsOf(call), call.space)
|
|
474
|
-
return { symbols: listSymbols(db, rangeOf(call), kind, originsOf(call), call.space)
|
|
493
|
+
if (call.flag("analyzer")) return { analyzerNames: list(call, listAnalyzerNames(db, rangeOf(call), kind, originsOf(call), call.space), symbol, RANGE_REST) };
|
|
494
|
+
return { symbols: list(call, listSymbols(db, rangeOf(call), kind, originsOf(call), call.space), symbol, RANGE_REST) };
|
|
475
495
|
},
|
|
476
496
|
),
|
|
477
|
-
readCommand({ name: "regions", summary: "The regions: code, data, tables and the other types.", options: { ...FILTER, type: { kind: "choice", value: "<type>", choices: REGION_TYPES, help: `one of ${REGION_TYPES.join(", ")}` } } }, (db, call) => ({
|
|
478
|
-
regions: listRegions(db, rangeOf(call), call.string("type") as (typeof REGION_TYPES)[number] | undefined, originsOf(call), call.space)
|
|
497
|
+
readCommand({ name: "regions", summary: "The regions: code, data, tables and the other types.", options: { ...FILTER, type: { kind: "choice", value: "<type>", choices: REGION_TYPES, help: `one of ${REGION_TYPES.join(", ")}` }, ...LIST } }, (db, call) => ({
|
|
498
|
+
regions: list(call, listRegions(db, rangeOf(call), call.string("type") as (typeof REGION_TYPES)[number] | undefined, originsOf(call), call.space), region, RANGE_REST),
|
|
479
499
|
})),
|
|
480
|
-
readCommand({ name: "comments", summary: "The comments.", options: FILTER }, (db, call) => ({ comments: listComments(db, rangeOf(call), originsOf(call), call.space)
|
|
481
|
-
readCommand({ name: "references", summary: "The references: calls, jumps, reads, writes and pointers.", options: { ...FILTER, kind: REFERENCE_KIND } }, (db, call) => ({
|
|
482
|
-
references: listReferences(db, rangeOf(call), call.string("kind") as (typeof REFERENCE_KINDS)[number] | undefined, originsOf(call), call.space)
|
|
500
|
+
readCommand({ name: "comments", summary: "The comments.", options: { ...FILTER, ...LIST } }, (db, call) => ({ comments: list(call, listComments(db, rangeOf(call), originsOf(call), call.space), comment, RANGE_REST) })),
|
|
501
|
+
readCommand({ name: "references", summary: "The references: calls, jumps, reads, writes and pointers.", options: { ...FILTER, kind: REFERENCE_KIND, ...LIST } }, (db, call) => ({
|
|
502
|
+
references: list(call, listReferences(db, rangeOf(call), call.string("kind") as (typeof REFERENCE_KINDS)[number] | undefined, originsOf(call), call.space), reference, RANGE_REST),
|
|
483
503
|
})),
|
|
484
|
-
readCommand({ name: "suppressions", summary: "The places that removals keep free of analyzer facts, with the reason of each.", options: { range: OPTIONS.range } }, (db, call) => {
|
|
504
|
+
readCommand({ name: "suppressions", summary: "The places that removals keep free of analyzer facts, with the reason of each.", options: { range: OPTIONS.range, ...LIST } }, (db, call) => {
|
|
485
505
|
const rows = listSuppressions(db, rangeOf(call), call.space);
|
|
486
|
-
const reasons = reasonsOf(db, rows);
|
|
487
|
-
return { suppressions: rows
|
|
506
|
+
const reasons = reasonsOf(db, rows.slice(0, call.count("max-listed")!));
|
|
507
|
+
return { suppressions: list(call, rows, (row) => ({ ...suppression(row), ...(reasons.has(row.revision) ? { reason: reasons.get(row.revision) } : {}) }), RANGE_REST) };
|
|
488
508
|
}),
|
|
489
|
-
readCommand({ name: "modules", summary: "Where the files of the generated source start.", options: FILTER }, (db, call) => ({ modules: listModules(db, rangeOf(call), originsOf(call), call.space)
|
|
490
|
-
readCommand({ name: "operands", summary: "The operand expressions of instructions.", options: FILTER }, (db, call) => ({ operands: listOperands(db, rangeOf(call), originsOf(call), call.space)
|
|
491
|
-
readCommand({ name: "patches", summary: "The code that a switch puts in.", options: { ...FILTER, switch: { kind: "text", value: "<switch>", help: "only the patches of this switch" } } }, (db, call) => ({
|
|
492
|
-
patches: listPatches(db, rangeOf(call), call.string("switch"), originsOf(call), call.space)
|
|
509
|
+
readCommand({ name: "modules", summary: "Where the files of the generated source start.", options: { ...FILTER, ...LIST } }, (db, call) => ({ modules: list(call, listModules(db, rangeOf(call), originsOf(call), call.space), module, RANGE_REST) })),
|
|
510
|
+
readCommand({ name: "operands", summary: "The operand expressions of instructions.", options: { ...FILTER, ...LIST } }, (db, call) => ({ operands: list(call, listOperands(db, rangeOf(call), originsOf(call), call.space), operand, RANGE_REST) })),
|
|
511
|
+
readCommand({ name: "patches", summary: "The code that a switch puts in.", options: { ...FILTER, switch: { kind: "text", value: "<switch>", help: "only the patches of this switch" }, ...LIST } }, (db, call) => ({
|
|
512
|
+
patches: list(call, listPatches(db, rangeOf(call), call.string("switch"), originsOf(call), call.space), patch, RANGE_REST),
|
|
493
513
|
})),
|
|
494
|
-
readCommand({ name: "switches", summary: "Each switch with the ranges of its patches." }, (db, call) => {
|
|
514
|
+
readCommand({ name: "switches", summary: "Each switch with the ranges of its patches.", options: LIST }, (db, call) => {
|
|
495
515
|
const switches = new Map<string, PatchRow[]>();
|
|
496
516
|
for (const row of listPatches(db, undefined, undefined, undefined, call.space)) switches.set(row.switch, [...(switches.get(row.switch) ?? []), row]);
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
};
|
|
517
|
+
const show = ([name, rows]: [string, PatchRow[]]) => ({ name, define: `--define ${name}=1`, patches: rows.map((row) => ({ start: formatC64Address(row.start), end: formatC64Address(row.end) })) });
|
|
518
|
+
return { switches: list(call, [...switches].sort(([a], [b]) => (a < b ? -1 : 1)), show, MORE) };
|
|
500
519
|
}),
|
|
501
|
-
readCommand({ name: "macros", summary: "The macros of the generated source.", options: { origin: OPTIONS.origin } }, (db, call) => ({ macros: listMacros(db, originsOf(call), call.space)
|
|
502
|
-
readCommand({ name: "uses", summary: "The instructions written as a macro.", options: { ...FILTER, macro: { kind: "text", value: "<name>", help: "only the uses of this macro" } } }, (db, call) => ({
|
|
503
|
-
uses: listMacroUses(db, rangeOf(call), call.string("macro"), originsOf(call), call.space)
|
|
520
|
+
readCommand({ name: "macros", summary: "The macros of the generated source.", options: { origin: OPTIONS.origin, ...LIST } }, (db, call) => ({ macros: list(call, listMacros(db, originsOf(call), call.space), macro, MORE) })),
|
|
521
|
+
readCommand({ name: "uses", summary: "The instructions written as a macro.", options: { ...FILTER, macro: { kind: "text", value: "<name>", help: "only the uses of this macro" }, ...LIST } }, (db, call) => ({
|
|
522
|
+
uses: list(call, listMacroUses(db, rangeOf(call), call.string("macro"), originsOf(call), call.space), macroUse, RANGE_REST),
|
|
504
523
|
})),
|
|
505
|
-
readCommand({ name: "spaces", summary: "The address spaces that hold knowledge." }, (db) => ({ spaces: listSpaces(db) })),
|
|
524
|
+
readCommand({ name: "spaces", summary: "The address spaces that hold knowledge.", options: LIST }, (db, call) => ({ spaces: list(call, listSpaces(db), (space) => space, MORE) })),
|
|
506
525
|
readCommand({ name: "program", summary: "The program in the knowledge: its segments and entry, and the coverage maps that coverage imported, with the ranges." }, (db, call) => {
|
|
507
526
|
const image = programImage(db, call.space);
|
|
508
527
|
const ranges = db === undefined ? [] : (db.prepare("SELECT kind, count(*) AS count FROM current_coverage WHERE space = ? GROUP BY kind ORDER BY kind").all(call.space) as Array<{ kind: string; count: number }>);
|
|
@@ -526,7 +545,7 @@ const READ_COMMANDS: CommandSpec[] = [
|
|
|
526
545
|
name: "history",
|
|
527
546
|
summary: "The rows at an address, of a name or of a macro, through all revisions, each with the reason and the evidence of its revision.",
|
|
528
547
|
subjects: [{ ...ADDRESS, optional: true }],
|
|
529
|
-
options: { name: { kind: "text", value: "<name>", help: "the history of a name in place of an address" }, macro: { kind: "text", value: "<name>", help: "the history of a macro in place of an address" } },
|
|
548
|
+
options: { name: { kind: "text", value: "<name>", help: "the history of a name in place of an address" }, macro: { kind: "text", value: "<name>", help: "the history of a macro in place of an address" }, ...LIST },
|
|
530
549
|
notes: "Give exactly one of <address>, --name and --macro.",
|
|
531
550
|
},
|
|
532
551
|
(db, call) => {
|
|
@@ -534,23 +553,20 @@ const READ_COMMANDS: CommandSpec[] = [
|
|
|
534
553
|
const macroName = call.string("macro");
|
|
535
554
|
if ([call.subjects.length > 0, name !== undefined, macroName !== undefined].filter(Boolean).length !== 1) throw new UsageError("history needs exactly one of <address>, --name and --macro");
|
|
536
555
|
const entries = macroName !== undefined ? historyOfMacro(db, macroName, call.space) : name !== undefined ? historyNamed(db, name, call.space) : historyAt(db, call.parseAddress(call.subject(0), "<address>"), call.space);
|
|
537
|
-
return { history: entries
|
|
556
|
+
return { history: list(call, entries, (entry) => ({ ...historyEntry(entry), ...revisionNote(db, entry.from) }), MORE) };
|
|
538
557
|
},
|
|
539
558
|
),
|
|
540
559
|
readCommand(
|
|
541
|
-
{ name: "revisions", summary: "The revisions, newest first.", options: {
|
|
560
|
+
{ name: "revisions", summary: "The revisions, newest first.", options: { before: { kind: "text", value: "<revision>", help: "only the revisions before this one" }, ...LIST } },
|
|
542
561
|
(db, call) => {
|
|
543
|
-
const options: { limit?: number; before?: string } = {};
|
|
544
|
-
const limit = call.count("limit");
|
|
545
|
-
if (limit !== undefined) options.limit = limit;
|
|
546
562
|
const before = call.string("before");
|
|
547
|
-
|
|
548
|
-
return { revisions:
|
|
563
|
+
const rows = revisions(db, before === undefined ? { limit: ALL } : { limit: ALL, before });
|
|
564
|
+
return { revisions: list(call, rows, (row) => row, "a larger --max-listed, or --before <revision> with the oldest revision in listed") };
|
|
549
565
|
},
|
|
550
566
|
),
|
|
551
|
-
readCommand({ name: "revision", summary: "The rows that one revision added and closed.", subjects: [REVISION] }, (db, call) => {
|
|
567
|
+
readCommand({ name: "revision", summary: "The rows that one revision added and closed.", subjects: [REVISION], options: LIST }, (db, call) => {
|
|
552
568
|
const changes = revision(db, call.subject(0));
|
|
553
|
-
return { revision: changes.revision, added: changes.added
|
|
569
|
+
return { revision: changes.revision, added: list(call, changes.added, historyEntry, MORE), closed: list(call, changes.closed, historyEntry, MORE) };
|
|
554
570
|
}),
|
|
555
571
|
readCommand({ name: "export", summary: "All current knowledge to a project file as JSON.", options: { ...FILTER, out: { ...OPTIONS.out, value: "<file>", help: "the JSON file, relative to the project folder", required: true } } }, (db, call) => {
|
|
556
572
|
const range = rangeOf(call);
|
|
@@ -579,14 +595,14 @@ const READ_COMMANDS: CommandSpec[] = [
|
|
|
579
595
|
name: "listing",
|
|
580
596
|
summary: "The program as ACME lines with names, operand expressions, comments, the access of each line in the coverage (x executed, r read, w written) and the references in and out.",
|
|
581
597
|
subjects: [RANGE],
|
|
582
|
-
options: { out: { ...OPTIONS.out, value: "<file>", help: "write the listing as text to this file in place of the lines in the result" } },
|
|
598
|
+
options: { out: { ...OPTIONS.out, value: "<file>", help: "write the listing as text to this file in place of the lines in the result" }, ...LIST },
|
|
583
599
|
},
|
|
584
600
|
(db, call) => {
|
|
585
601
|
const { start, end } = call.parseRange(call.subject(0), "<range>");
|
|
586
602
|
const lines = listing(listingSource(db, call.space), start, end);
|
|
587
603
|
const out = call.string("out");
|
|
588
604
|
if (out !== undefined) return { lines: lines.length, ...writeReported(out, listingText(lines), { what: "--out" }) };
|
|
589
|
-
return { lines: lines
|
|
605
|
+
return { lines: list(call, lines, (row) => ({ ...row, address: formatC64Address(row.address) }), "a larger --max-listed, a smaller <range>, or --out <file> for all lines") };
|
|
590
606
|
},
|
|
591
607
|
),
|
|
592
608
|
];
|
|
@@ -821,7 +837,7 @@ const WRITE_COMMANDS: CommandSpec[] = [
|
|
|
821
837
|
};
|
|
822
838
|
},
|
|
823
839
|
),
|
|
824
|
-
writeCommand({ name: "revert", summary: "
|
|
840
|
+
writeCommand({ name: "revert", summary: "Makes the knowledge the same as at one revision. The revert is a new revision: the history stays complete.", subjects: [REVISION] }, (call) => {
|
|
825
841
|
const context = contextOf(call);
|
|
826
842
|
const request = { revision: call.subject(0) };
|
|
827
843
|
return (db) => ({ revision: revert(db, context, request).revision });
|
|
@@ -31,7 +31,7 @@ node <skill>/scripts/memory-map.ts export --out src/hw.asm
|
|
|
31
31
|
|---|---|
|
|
32
32
|
| `at <address> ...` | For each address: `kind` (`platform` or `application`), the area, the name and the meaning. |
|
|
33
33
|
| `decode <register> <value>` | The meaning of the value. Use `--bank` with the value of `$DD00` to get the addresses for `$D018`. The decoders are for `$01`, `$D010`, `$D011`, `$D015` to `$D01F`, `$D016`, `$D018`, `$DC0D`, `$DD00` and `$DD0D`. |
|
|
34
|
-
| `list <vic\|sid\|cia1\|cia2\|kernal\|system>` |
|
|
34
|
+
| `list <vic\|sid\|cia1\|cia2\|kernal\|system>` | The entries of that group as a list: `count` and `listed`. `--max-listed` limits `listed`. The default is 50. |
|
|
35
35
|
| `export --out <file>` | Writes all platform names to a project file as an ACME include: one line for each name, with its meaning as a comment. A zero-page name is `NAME = $xx`. Other names are `NAME = $xxxx`. |
|
|
36
36
|
|
|
37
37
|
Write a value for `decode` and `--bank` as `$` and hex digits, as a decimal number, or as `%` and binary digits. Put a `$` value in single quotes in a shell.
|
|
@@ -6,7 +6,7 @@ import { formatC64Address, formatC64Byte } from "#src/c64.ts";
|
|
|
6
6
|
import { writeReported } from "#src/host-client/transfer.ts";
|
|
7
7
|
import { knowledgeNames } from "#src/knowledge/names.ts";
|
|
8
8
|
import { decode, IO, lookup, platformNames, ROM, SYSTEM, type Entry } from "#src/platform.ts";
|
|
9
|
-
import { OPTIONS, runScript, UsageError } from "#src/script.ts";
|
|
9
|
+
import { listed, OPTIONS, runScript, UsageError } from "#src/script.ts";
|
|
10
10
|
|
|
11
11
|
const show = (entry: Entry) => ({ start: formatC64Address(entry.start), ...(entry.end !== entry.start ? { end: formatC64Address(entry.end) } : {}), name: entry.name, meaning: entry.meaning });
|
|
12
12
|
|
|
@@ -61,10 +61,11 @@ await runScript({
|
|
|
61
61
|
name: "list",
|
|
62
62
|
summary: "The registers of a chip, the KERNAL jump table, or the system areas.",
|
|
63
63
|
subjects: [{ name: "<list>", help: `one of ${Object.keys(LISTS).join(", ")}` }],
|
|
64
|
+
options: { "max-listed": OPTIONS["max-listed"] },
|
|
64
65
|
run: (call) => {
|
|
65
66
|
const list = LISTS[call.subject(0)];
|
|
66
67
|
if (list === undefined) throw new UsageError(`<list> must be one of ${Object.keys(LISTS).join(", ")}, not ${call.subject(0)}`);
|
|
67
|
-
return { entries: list().
|
|
68
|
+
return { entries: listed(list(), call.count("max-listed")!, show, "a larger --max-listed, or memory-map.ts export --out <file> for every platform name") };
|
|
68
69
|
},
|
|
69
70
|
},
|
|
70
71
|
{
|
|
@@ -38,7 +38,7 @@ node <skill>/scripts/provenance.ts leftovers --out .c64-re-tools/local/leftovers
|
|
|
38
38
|
|
|
39
39
|
| Option | Meaning |
|
|
40
40
|
|---|---|
|
|
41
|
-
| `--shift 2:-256` | Move release 2 (the first release is 1) by -256 bytes. Use it again for more releases. |
|
|
41
|
+
| `--shift 2:-256` | Move release 2 (the first release is 1) by -256 bytes. Use it again for more releases. Give one shift for each release. |
|
|
42
42
|
| `--range '$0900-$09ff'` | Compare only the addresses in this range. |
|
|
43
43
|
| `--frames 600` | Run each release for this many frames, and give each listed difference its runtime evidence. |
|
|
44
44
|
| `--steps .c64-re-tools/local/start-game.json` | With `--frames`: a JSON list of action steps to run before the frames, for example a key to start the game. The steps are the steps of a c64-unpacker steps file. |
|
|
@@ -96,7 +96,7 @@ Use `separate` when you have only one release. It reads the program, its stages
|
|
|
96
96
|
- `ranges`: the program in ranges with the same stage and access. `stage` is the last unpack stage that wrote the range. The last stage is the program. An earlier stage is a depacker, an intro or a loader. Only a capture of c64-unpacker `capture` has stages. For a program from `loads` or a PRG, `noStages` tells this.
|
|
97
97
|
- `access`: from the coverage of the knowledge: `executed`, `read`, `written`, `read-written` or `untouched`. `untouched` bytes did not take part in the runs. A `read` byte right after executed code, or at a return address, can be a dummy read of the 6502 that the program does not use.
|
|
98
98
|
- `codeRegions.total`: the number of code regions in the knowledge.
|
|
99
|
-
- `codeRegions.unreachable`: code regions of the knowledge that no chain of call and jump references reaches from the entry. Run c64-static-analysis first, so that the knowledge has the code regions and the references.
|
|
99
|
+
- `codeRegions.unreachable`: code regions of the knowledge that no chain of call and jump references reaches from the entry. A region also reaches the code after it when its last instruction is not RTS, RTI, JMP, BRK or JAM: the CPU continues there. Run c64-static-analysis first, so that the knowledge has the code regions and the references.
|
|
100
100
|
- `outside`: RAM outside the program that the program `read` or `executed` but did not write, and code it `generated`. A loader or an earlier stage put the bytes that the program did not write. A `generated` range can have gaps of two bytes or less: these are operand bytes.
|
|
101
101
|
|
|
102
102
|
Each list has `count` and `listed`. `listed` has the first ranges, at most `--max-listed` (default 50). `--out` writes all of them and the result shows the file as `{out, bytes, sha256}`. Run more coverage runs for parts that are `untouched`, and add them with `coverage` of the c64-knowledge skill, before you decide.
|
|
@@ -112,7 +112,7 @@ Use `leftovers` to find what the program holds in addition to its working code.
|
|
|
112
112
|
| `fill` | One byte that repeats, at least 16 times, or a pattern of two to four bytes that repeats, at least 32 bytes in total. `pattern` is the repeated bytes. |
|
|
113
113
|
| `residue` | Bytes that no run touched and that are no text, BASIC, fill or code region. `bytes` shows the first 16 bytes. |
|
|
114
114
|
| `copies` | Bytes that are the same as earlier bytes of the program, at least 32 bytes. `copyOf` is the address of the earlier bytes. |
|
|
115
|
-
| `deadCode` | Code regions of the knowledge that
|
|
115
|
+
| `deadCode` | Code regions of the knowledge that the entry does not reach, as in `codeRegions.unreachable` of `separate`, and that no run executed. |
|
|
116
116
|
|
|
117
117
|
Text from another computer can show the tools of the developer:
|
|
118
118
|
|
|
@@ -162,7 +162,8 @@ To save a routine:
|
|
|
162
162
|
- `--name` is the name of the routine and of its file. When you do not give it, the script uses the user or LLM name at the start.
|
|
163
163
|
- `--title` is the title of the routine. When you do not give it, the script uses the module title.
|
|
164
164
|
- A routine has at least 16 bytes. Its last instruction must end in the range.
|
|
165
|
-
3.
|
|
165
|
+
3. The result shows the routine file as `{out, bytes, sha256}`. `out` is relative to the project folder.
|
|
166
|
+
4. Read `leftOut`. It lists the rows that the file does not keep, and why. For example, the file does not keep an operand expression that uses a name outside the routine.
|
|
166
167
|
|
|
167
168
|
To find the routines in a program:
|
|
168
169
|
|