@henols/c64-re-tools 2.1.1 → 2.1.2
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 +6 -3
- package/skills/c64-assembler/scripts/assembler.ts +19 -11
- package/skills/c64-assembler/scripts/lib/compare.ts +4 -3
- package/skills/c64-basic/SKILL.md +1 -0
- package/skills/c64-disk/SKILL.md +1 -0
- package/skills/c64-disk/scripts/disk.ts +2 -2
- package/skills/c64-disk/scripts/lib/containers.ts +3 -4
- package/skills/c64-docs/SKILL.md +2 -1
- package/skills/c64-docs/scripts/docs.ts +1 -1
- package/skills/c64-docs/scripts/lib/links.ts +2 -0
- package/skills/c64-emulator/SKILL.md +2 -1
- package/skills/c64-knowledge/SKILL.md +6 -4
- package/skills/c64-knowledge/scripts/knowledge.ts +35 -13
- package/skills/c64-knowledge/scripts/lib/listing.ts +8 -30
- package/skills/c64-memory-map/SKILL.md +1 -0
- package/skills/c64-memory-map/scripts/memory-map.ts +1 -1
- package/skills/c64-provenance/SKILL.md +1 -0
- package/skills/c64-provenance/scripts/provenance.ts +9 -6
- package/skills/c64-source/SKILL.md +1 -0
- package/skills/c64-source/scripts/lib/source.ts +7 -29
- package/skills/c64-source/scripts/source.ts +4 -4
- package/skills/c64-static-analysis/SKILL.md +2 -0
- package/skills/c64-static-analysis/scripts/static-analysis.ts +14 -5
- package/skills/c64-testing/SKILL.md +3 -2
- package/skills/c64-testing/scripts/lib/diff-files.ts +14 -2
- package/skills/c64-testing/scripts/lib/recording.ts +3 -3
- package/skills/c64-testing/scripts/lib/replay.ts +1 -1
- package/skills/c64-testing/scripts/lib/runs.ts +27 -7
- package/skills/c64-testing/scripts/testing.ts +5 -5
- package/skills/c64-unpacker/SKILL.md +8 -5
- package/skills/c64-unpacker/scripts/lib/loads.ts +12 -6
- package/skills/c64-unpacker/scripts/lib/machine.ts +30 -3
- package/skills/c64-unpacker/scripts/lib/stages.ts +15 -7
- package/skills/c64-unpacker/scripts/unpacker.ts +18 -17
- package/src/host/vice/adapter.ts +4 -1
- package/src/host/vice/session.ts +49 -3
- package/src/host-client/image.ts +10 -10
- package/src/host-client/steps.ts +39 -3
- package/src/host-client/transfer.ts +14 -26
- package/src/knowledge/history.ts +55 -2
- package/src/knowledge/import.ts +79 -7
- package/src/knowledge/program.ts +31 -2
- package/src/knowledge/read.ts +7 -4
- package/src/knowledge/schema.ts +3 -2
- package/src/knowledge/text.ts +1 -0
- package/src/knowledge/write.ts +22 -14
- package/src/mcp/inputs.ts +6 -3
- package/src/mcp/server.ts +2 -1
- package/src/mcp/tools/debug.ts +29 -3
- package/src/mcp/tools/machine.ts +3 -1
- package/src/mcp/tools/memory.ts +4 -3
- package/src/media.ts +42 -0
- package/src/opcodes.ts +51 -18
- package/src/project.ts +110 -2
- package/src/protocol/vice.ts +4 -1
- package/src/script.ts +55 -3
package/package.json
CHANGED
|
@@ -31,9 +31,11 @@ node <skill>/scripts/assembler.ts build src main.a --out build/game.prg
|
|
|
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` | 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. |
|
|
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. The script refuses `--space` without `--compare`. |
|
|
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
|
+
| `--max-listed 200` | Show at most 200 symbols in the result. The default is 50. `symbols.count` gives the count of all symbols. |
|
|
38
|
+
| `--out-symbols build/game.json` | Write all symbols to this file as `{"symbols": [...]}`. Give this file to the c64-testing skill as the symbols of the rebuild. |
|
|
37
39
|
|
|
38
40
|
The script copies the full source root. Put all files that the program includes under it. Version control directories stay out.
|
|
39
41
|
|
|
@@ -46,6 +48,7 @@ The target CPU is the 6510. The output is a C64 PRG, or the bytes only with `--p
|
|
|
46
48
|
- An address is `$` and 1 to 4 hex digits, for example `'$c000'` or `'$fb'`, or a name. A name is a symbol, else an analyzer name that only one analyzer gives. Quote an address in a shell.
|
|
47
49
|
- A range is `<start>-<end>`, for example `'$c000-$cfff'`. In a JSON file, a range is `start` and `end`.
|
|
48
50
|
- Bytes in a JSON file are hex in a field `data`, for example `"data": "a900"`.
|
|
51
|
+
- The script examines each `--out` path before it starts work. It refuses a path in `.c64-re-tools/knowledge/` and the index of the knowledge, because only c64-knowledge writes them. It refuses a folder where the command writes a file, and a file where the command writes a folder.
|
|
49
52
|
- The result is one JSON object on one line. A file that the script writes shows as `{"out", "bytes", "sha256"}`. A list shows as `{"count", "listed"}`, and `rest` tells where to get the rest of a long list.
|
|
50
53
|
- An error is `{"error": {"code": "...", "message": "..."}}`. The message tells what is wrong and what to do.
|
|
51
54
|
- The exit status is 0 for done or PASS, 1 for FAIL, 2 for an error and 3 for INCONCLUSIVE.
|
|
@@ -64,7 +67,7 @@ The target CPU is the 6510. The output is a C64 PRG, or the bytes only with `--p
|
|
|
64
67
|
|
|
65
68
|
1. Find the source root and the entry source. The entry source sets its own start address with `* = $0801` or similar.
|
|
66
69
|
2. Run the script.
|
|
67
|
-
3. If `result` is `PASS`, the PRG is at `out`. `loadRange` tells where it loads. `symbols`
|
|
70
|
+
3. If `result` is `PASS`, the PRG is at `out`. `loadRange` tells where it loads. `symbols` is a list with `count` and `listed`. Each item in `listed` is one symbol:
|
|
68
71
|
- Kind `address` has a value in `loadRange`. It gives the `address`.
|
|
69
72
|
- Kind `constant` has a value outside `loadRange`. It gives the `value` as `$` and four or more hex digits. A label in a `!pseudopc` block can be a constant, because its value is the run address, not the load address.
|
|
70
73
|
- `used` is `false` when the source does not use the symbol.
|
|
@@ -92,7 +95,7 @@ A symbol from the rebuilt source can give the address of a routine in the rebuil
|
|
|
92
95
|
- `FAIL` with `assembled: false`: the source has errors. `diagnostics` gives them.
|
|
93
96
|
- `FAIL` with `assembled: true`: `--compare` found differences. The PRG is at `out`.
|
|
94
97
|
|
|
95
|
-
With `--compare`, `compare` gives `identical` and `segments`: one check for each segment of the program in the knowledge. The `result` of a check is `identical`, `different` with `firstDifference`, or `not covered` when the build does not hold the segment.
|
|
98
|
+
With `--compare`, `compare` gives `identical` and `segments`: one check for each segment of the program in the knowledge. The `result` of a check is `identical`, `different` with `firstDifference`, or `not covered` when the build does not hold the segment. `firstDifference` gives the `address`, and the byte of the build (`built`) and of the knowledge (`reference`), each as `$` and two hex digits.
|
|
96
99
|
|
|
97
100
|
## Failure and conflicts
|
|
98
101
|
|
|
@@ -11,8 +11,8 @@ import { knowledgeNames } from "#src/knowledge/names.ts";
|
|
|
11
11
|
import { requireProgram, type ProgramImage } from "#src/knowledge/program.ts";
|
|
12
12
|
import { assemble } from "#src/native/acme.ts";
|
|
13
13
|
import { localToolContext } from "#src/native/local.ts";
|
|
14
|
-
import type { AcmeParams } from "#src/native/types.ts";
|
|
15
|
-
import { OPTIONS, runScript, UsageError } from "#src/script.ts";
|
|
14
|
+
import type { AcmeParams, AssembledSymbol } from "#src/native/types.ts";
|
|
15
|
+
import { listed as listedItems, OPTIONS, runScript, UsageError } from "#src/script.ts";
|
|
16
16
|
import { compareBuild } from "./lib/compare.ts";
|
|
17
17
|
|
|
18
18
|
function parseDefine(text: string): [string, number | boolean] {
|
|
@@ -25,6 +25,13 @@ function parseDefine(text: string): [string, number | boolean] {
|
|
|
25
25
|
throw new UsageError(`--define ${match[1]} needs an integer or true/false, not ${value}`);
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
+
/** A symbol of the build: an address as $ and four hex digits, a constant as $ and at least four hex digits. */
|
|
29
|
+
function showSymbol(symbol: AssembledSymbol) {
|
|
30
|
+
return symbol.kind === "address"
|
|
31
|
+
? { name: symbol.name, kind: "address", address: formatC64Address(symbol.address), used: symbol.used }
|
|
32
|
+
: { name: symbol.name, kind: "constant", value: `$${symbol.value.toString(16).padStart(4, "0")}`, used: symbol.used };
|
|
33
|
+
}
|
|
34
|
+
|
|
28
35
|
await runScript({
|
|
29
36
|
name: "assembler",
|
|
30
37
|
summary: "Assembles project source with ACME and writes the program into the project.",
|
|
@@ -38,14 +45,16 @@ await runScript({
|
|
|
38
45
|
{ name: "<entry>", help: "the main source file, relative to the source root" },
|
|
39
46
|
],
|
|
40
47
|
options: {
|
|
41
|
-
out: { ...OPTIONS.out, value: "<file.prg>", help: "where to write the program, relative to the project folder", required: true },
|
|
48
|
+
out: { ...OPTIONS.out, output: "file", value: "<file.prg>", help: "where to write the program, relative to the project folder", required: true },
|
|
42
49
|
include: { kind: "text", value: "<dir>", multiple: true, help: "an include folder, relative to the source root" },
|
|
43
50
|
define: { kind: "text", value: "NAME=VALUE", multiple: true, help: "an assembler symbol: an integer ($hex allowed) or true/false" },
|
|
44
51
|
"set-pc": { kind: "address", value: "<address>", help: "the start address when the source sets none, for example '$0801'" },
|
|
45
52
|
compare: { kind: "flag", help: "check the program byte by byte against the program in the knowledge, each segment" },
|
|
46
|
-
space: {
|
|
53
|
+
space: { kind: "space", value: "<space>", help: "with --compare: the knowledge space of the program: c64 (the default), drive8, or bank0 to bank255" },
|
|
47
54
|
plain: { kind: "flag", help: "write the bytes only, without the 2-byte load address of a PRG" },
|
|
48
55
|
"all-symbols": { kind: "flag", help: "list also the constants that the source does not use" },
|
|
56
|
+
"max-listed": { ...OPTIONS["max-listed"], help: "list at most n symbols (default 50). The count is always given" },
|
|
57
|
+
"out-symbols": { kind: "path", output: "file", value: "<file.json>", help: "write all symbols to this project file as {\"symbols\": [...]}: the names of the rebuild for c64-testing" },
|
|
49
58
|
},
|
|
50
59
|
notes: "Symbol values are $ and at least four hex digits. Put the program into the knowledge with knowledge.ts image of the c64-knowledge skill before --compare.",
|
|
51
60
|
run: async (call) => {
|
|
@@ -53,6 +62,7 @@ await runScript({
|
|
|
53
62
|
const out = call.string("out")!;
|
|
54
63
|
// Read the program before ACME runs, so a project without it stops at once.
|
|
55
64
|
let program: ProgramImage | undefined;
|
|
65
|
+
if (call.string("space") !== undefined && !call.flag("compare")) throw new UsageError("--space works only with --compare: it names the knowledge space of the program to compare with");
|
|
56
66
|
if (call.flag("compare")) {
|
|
57
67
|
const db = openForRead();
|
|
58
68
|
try {
|
|
@@ -82,7 +92,8 @@ await runScript({
|
|
|
82
92
|
const compared = program === undefined ? undefined : compareBuild(attachments![0]!, program);
|
|
83
93
|
// An include file of platform names gives many constants that the program does not use.
|
|
84
94
|
const all = call.flag("all-symbols");
|
|
85
|
-
const
|
|
95
|
+
const shown = result.symbols!.filter((symbol) => all || symbol.kind === "address" || symbol.used);
|
|
96
|
+
const symbolsPath = call.string("out-symbols");
|
|
86
97
|
return {
|
|
87
98
|
// A build that differs from its reference is a failed check.
|
|
88
99
|
result: compared !== undefined && !compared.identical ? "FAIL" : "PASS",
|
|
@@ -90,12 +101,9 @@ await runScript({
|
|
|
90
101
|
...written,
|
|
91
102
|
...(plain ? { plain: true } : {}),
|
|
92
103
|
loadRange: { start: formatC64Address(range.start), end: formatC64Address(range.end), bytes: range.bytes },
|
|
93
|
-
symbols:
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
: { name: symbol.name, kind: "constant", value: `$${symbol.value.toString(16).padStart(4, "0")}`, used: symbol.used },
|
|
97
|
-
),
|
|
98
|
-
...(all ? {} : { unusedConstants: result.symbols!.length - listed.length }),
|
|
104
|
+
symbols: listedItems(shown, call.count("max-listed")!, showSymbol, "a larger --max-listed, or --out-symbols <file.json> for all symbols"),
|
|
105
|
+
...(symbolsPath === undefined ? {} : { symbolsFile: writeReported(symbolsPath, `${JSON.stringify({ symbols: shown.map(showSymbol) }, null, 1)}\n`, { what: "--out-symbols" }) }),
|
|
106
|
+
...(all ? {} : { unusedConstants: result.symbols!.length - shown.length }),
|
|
99
107
|
diagnostics: result.diagnostics,
|
|
100
108
|
...(compared === undefined ? {} : { compare: compared }),
|
|
101
109
|
};
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// The byte check of a build: the assembled program against the program
|
|
2
2
|
// in the knowledge, segment by segment.
|
|
3
3
|
|
|
4
|
-
import { formatC64Address, prgRange } from "#src/c64.ts";
|
|
4
|
+
import { formatC64Address, formatC64Byte, prgRange } from "#src/c64.ts";
|
|
5
5
|
|
|
6
6
|
export interface SegmentCheck {
|
|
7
7
|
start: string;
|
|
@@ -10,7 +10,8 @@ export interface SegmentCheck {
|
|
|
10
10
|
/** Bytes of the segment that the build does not hold. */
|
|
11
11
|
uncovered?: number;
|
|
12
12
|
differences?: number;
|
|
13
|
-
|
|
13
|
+
/** The first address that differs, with its byte in the build and in the knowledge, each as $ and two hex digits. */
|
|
14
|
+
firstDifference?: { address: string; built: string; reference: string };
|
|
14
15
|
}
|
|
15
16
|
|
|
16
17
|
/** The program that the build is checked against: its segments and its bytes in them. */
|
|
@@ -34,7 +35,7 @@ export function compareBuild(prg: Uint8Array, program: Reference): { identical:
|
|
|
34
35
|
const referenceByte = program.byteAt(address);
|
|
35
36
|
if (builtByte === referenceByte) continue;
|
|
36
37
|
differences++;
|
|
37
|
-
first ??= { address: formatC64Address(address), built: builtByte, reference: referenceByte };
|
|
38
|
+
first ??= { address: formatC64Address(address), built: formatC64Byte(builtByte), reference: formatC64Byte(referenceByte) };
|
|
38
39
|
}
|
|
39
40
|
const uncovered = segment.end - segment.start + 1 - (to - from + 1);
|
|
40
41
|
return {
|
|
@@ -30,6 +30,7 @@ node <skill>/scripts/basic.ts list extracted/game.prg
|
|
|
30
30
|
- An address is `$` and 1 to 4 hex digits, for example `'$c000'` or `'$fb'`, or a name. A name is a symbol, else an analyzer name that only one analyzer gives. Quote an address in a shell.
|
|
31
31
|
- A range is `<start>-<end>`, for example `'$c000-$cfff'`. In a JSON file, a range is `start` and `end`.
|
|
32
32
|
- Bytes in a JSON file are hex in a field `data`, for example `"data": "a900"`.
|
|
33
|
+
- The script examines each `--out` path before it starts work. It refuses a path in `.c64-re-tools/knowledge/` and the index of the knowledge, because only c64-knowledge writes them. It refuses a folder where the command writes a file, and a file where the command writes a folder.
|
|
33
34
|
- The result is one JSON object on one line. A file that the script writes shows as `{"out", "bytes", "sha256"}`. A list shows as `{"count", "listed"}`, and `rest` tells where to get the rest of a long list.
|
|
34
35
|
- An error is `{"error": {"code": "...", "message": "..."}}`. The message tells what is wrong and what to do.
|
|
35
36
|
- The exit status is 0 for done or PASS, 1 for FAIL, 2 for an error and 3 for INCONCLUSIVE.
|
package/skills/c64-disk/SKILL.md
CHANGED
|
@@ -47,6 +47,7 @@ File names show the characters that the C64 shows in a directory list. A byte th
|
|
|
47
47
|
- An address is `$` and 1 to 4 hex digits, for example `'$c000'` or `'$fb'`, or a name. A name is a symbol, else an analyzer name that only one analyzer gives. Quote an address in a shell.
|
|
48
48
|
- A range is `<start>-<end>`, for example `'$c000-$cfff'`. In a JSON file, a range is `start` and `end`.
|
|
49
49
|
- Bytes in a JSON file are hex in a field `data`, for example `"data": "a900"`.
|
|
50
|
+
- The script examines each `--out` path before it starts work. It refuses a path in `.c64-re-tools/knowledge/` and the index of the knowledge, because only c64-knowledge writes them. It refuses a folder where the command writes a file, and a file where the command writes a folder.
|
|
50
51
|
- The result is one JSON object on one line. A file that the script writes shows as `{"out", "bytes", "sha256"}`. A list shows as `{"count", "listed"}`, and `rest` tells where to get the rest of a long list.
|
|
51
52
|
- An error is `{"error": {"code": "...", "message": "..."}}`. The message tells what is wrong and what to do.
|
|
52
53
|
- The exit status is 0 for done or PASS, 1 for FAIL, 2 for an error and 3 for INCONCLUSIVE.
|
|
@@ -123,7 +123,7 @@ await runScript({
|
|
|
123
123
|
name: "read",
|
|
124
124
|
summary: "Writes the bytes of one file into the project. It also reads a .t64 tape file.",
|
|
125
125
|
subjects: [{ ...IMAGE, help: "a disk image or a .t64 tape file, relative to the project folder" }, NAME],
|
|
126
|
-
options: { out: { ...OPTIONS.out, value: "<file>", help: "the file to write, relative to the project folder", required: true } },
|
|
126
|
+
options: { out: { ...OPTIONS.out, output: "file", value: "<file>", help: "the file to write, relative to the project folder", required: true } },
|
|
127
127
|
run: async (call) => {
|
|
128
128
|
const [image, name] = [call.subject(0), call.subject(1)];
|
|
129
129
|
const out = call.string("out")!;
|
|
@@ -145,7 +145,7 @@ await runScript({
|
|
|
145
145
|
name: "crt",
|
|
146
146
|
summary: "Writes each CHIP packet of a cartridge as a PRG, and crt.json with the header and the packets.",
|
|
147
147
|
subjects: [{ name: "<file.crt>", help: "a .crt cartridge file, relative to the project folder" }],
|
|
148
|
-
options: { out: { ...OPTIONS.out, value: "<dir>", help: "the folder for the files, relative to the project folder", required: true } },
|
|
148
|
+
options: { out: { ...OPTIONS.out, output: "folder", value: "<dir>", help: "the folder for the files, relative to the project folder", required: true } },
|
|
149
149
|
run: (call) => {
|
|
150
150
|
const image = call.subject(0);
|
|
151
151
|
kindFor("crt", image, ["crt"]);
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
|
|
6
6
|
import { formatC64Address, petsciiNameToText } from "#src/c64.ts";
|
|
7
7
|
import { Refusal } from "#src/errors.ts";
|
|
8
|
+
import { hasSignature } from "#src/media.ts";
|
|
8
9
|
|
|
9
10
|
/** The CRT hardware types this toolkit reads, by type number. */
|
|
10
11
|
export const CRT_TYPES: Record<number, string> = { 0: "normal", 5: "Ocean", 19: "Magic Desk", 32: "EasyFlash" };
|
|
@@ -30,11 +31,9 @@ export interface Crt {
|
|
|
30
31
|
chips: CrtChip[];
|
|
31
32
|
}
|
|
32
33
|
|
|
33
|
-
const SIGNATURE = "C64 CARTRIDGE ";
|
|
34
|
-
|
|
35
34
|
export function parseCrt(bytes: Uint8Array): Crt {
|
|
36
35
|
const buffer = Buffer.from(bytes.buffer, bytes.byteOffset, bytes.byteLength);
|
|
37
|
-
if (buffer.length < 0x40 ||
|
|
36
|
+
if (buffer.length < 0x40 || !hasSignature("crt", buffer)) throw new Refusal("media-error", "the file is not a CRT: it does not start with C64 CARTRIDGE");
|
|
38
37
|
const headerLength = buffer.readUInt32BE(0x10);
|
|
39
38
|
const type = buffer.readUInt16BE(0x16);
|
|
40
39
|
const exrom = buffer[0x18]!;
|
|
@@ -85,7 +84,7 @@ export interface Tape {
|
|
|
85
84
|
|
|
86
85
|
export function parseT64(bytes: Uint8Array): Tape {
|
|
87
86
|
const buffer = Buffer.from(bytes.buffer, bytes.byteOffset, bytes.byteLength);
|
|
88
|
-
if (buffer.length < 0x40 || !
|
|
87
|
+
if (buffer.length < 0x40 || !hasSignature("t64", buffer)) throw new Refusal("media-error", "the file is not a T64: it does not start with C64");
|
|
89
88
|
const maxEntries = buffer.readUInt16LE(0x22);
|
|
90
89
|
const name = buffer.toString("latin1", 0x28, 0x40).replace(/[\0 ]+$/, "");
|
|
91
90
|
const raw: Array<Omit<TapeEntry, "bytes" | "end"> & { end: number }> = [];
|
package/skills/c64-docs/SKILL.md
CHANGED
|
@@ -49,6 +49,7 @@ node <skill>/scripts/docs.ts link README.md docs --source src --check
|
|
|
49
49
|
- An address is `$` and 1 to 4 hex digits, for example `'$c000'` or `'$fb'`, or a name. A name is a symbol, else an analyzer name that only one analyzer gives. Quote an address in a shell.
|
|
50
50
|
- A range is `<start>-<end>`, for example `'$c000-$cfff'`. In a JSON file, a range is `start` and `end`.
|
|
51
51
|
- Bytes in a JSON file are hex in a field `data`, for example `"data": "a900"`.
|
|
52
|
+
- The script examines each `--out` path before it starts work. It refuses a path in `.c64-re-tools/knowledge/` and the index of the knowledge, because only c64-knowledge writes them. It refuses a folder where the command writes a file, and a file where the command writes a folder.
|
|
52
53
|
- The result is one JSON object on one line. A file that the script writes shows as `{"out", "bytes", "sha256"}`. A list shows as `{"count", "listed"}`, and `rest` tells where to get the rest of a long list.
|
|
53
54
|
- An error is `{"error": {"code": "...", "message": "..."}}`. The message tells what is wrong and what to do.
|
|
54
55
|
- The exit status is 0 for done or PASS, 1 for FAIL, 2 for an error and 3 for INCONCLUSIVE.
|
|
@@ -144,7 +145,7 @@ Do not store:
|
|
|
144
145
|
- `invalid-input` that names an old revision: the knowledge changed after the source. Generate the source again, then run `docs.ts generate`.
|
|
145
146
|
- `invalid-input` that names a page in the reference directory: the page is not a page of `docs.ts generate`. Move the page out of the directory. Do not give `--out` a directory with pages that you wrote.
|
|
146
147
|
- `unknown-name`: the page uses a name that is not in the knowledge. Record the name with c64-knowledge, or correct the page.
|
|
147
|
-
- `wrong-address`: the address after the name is not the address of the name, and it is not a place inside it. The message gives the routine that holds the address. Correct the name or the address. For a value, write `` `scroll_moves` = `$10` ``.
|
|
148
|
+
- `wrong-address`: the address after the name is not the address of the name, and it is not a place inside it. The message gives the routine that holds the address. Correct the name or the address. For a value, write `` `scroll_moves` = `$10` ``. The script does not link such a mention until it is correct.
|
|
148
149
|
- `missing-file`, `missing-anchor` or `missing-line`: a link goes to nothing. Correct the link. An image of an asset must exist in the project.
|
|
149
150
|
- `stale-reference`: the source or the reference is older than the knowledge. Do steps 2 and 3 of the workflow again.
|
|
150
151
|
- `conflict`: the knowledge has conflicts from a git merge. Run `knowledge.ts merge` of c64-knowledge.
|
|
@@ -297,7 +297,7 @@ await runScript({
|
|
|
297
297
|
summary: "Writes the reference pages: a page for each module, and pages for the names, the variables, the tables, the memory map and the graphics.",
|
|
298
298
|
subjects: [{ name: "<source-dir>", help: SOURCE.help }],
|
|
299
299
|
options: {
|
|
300
|
-
out: { ...OPTIONS.out, value: "<dir>", help: "the folder for the reference pages, relative to the project folder, for example docs/reference. generate writes only there and refuses a page in it that it did not write", required: true },
|
|
300
|
+
out: { ...OPTIONS.out, output: "folder", value: "<dir>", help: "the folder for the reference pages, relative to the project folder, for example docs/reference. generate writes only there and refuses a page in it that it did not write", required: true },
|
|
301
301
|
"max-listed": OPTIONS["max-listed"],
|
|
302
302
|
},
|
|
303
303
|
notes: "When the knowledge has the program of the source (knowledge.ts image of the c64-knowledge skill), the reference decodes the tables and shows the graphics.",
|
|
@@ -102,6 +102,8 @@ export function linkPage(text: string, context: LinkContext): { text: string; ch
|
|
|
102
102
|
const expected = `$${(address + delta).toString(16).toUpperCase().padStart(4, "0")}`;
|
|
103
103
|
const holder = context.placeOf(given!);
|
|
104
104
|
report("wrong-address", `\`${name}${offset ?? ""}\` is ${expected} in the knowledge, and the page gives ${addressText}${holder === undefined || holder === name ? "" : `, a place in \`${holder}\``}. Correct the name or the address.${digits!.length <= 2 ? ` For a value, write \`${name}\` = \`${addressText}\`.` : ""}`);
|
|
105
|
+
// A mention with a problem gets no link: the text stays as the person wrote it until it is correct.
|
|
106
|
+
return whole;
|
|
105
107
|
}
|
|
106
108
|
const shownName = `${TICK}${name}${offset ?? ""}${TICK}`;
|
|
107
109
|
const code = context.codeOf(name);
|
|
@@ -57,6 +57,7 @@ For a large result, give `out` with a path in the project. `c64_memory_read`, `c
|
|
|
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.
|
|
60
|
+
- VICE has a known flaw: it uses only one bit of `$01` to find RAM or ROM. At `$A000-$BFFF` it gives `rom` when bit 0 is set, also when bit 1 is clear and the CPU used the RAM there. Many games set `$01` to `$35`. Then code that runs at `$A000-$BFFF` shows as ROM. `emulatorFlaw` in the result tells when this can occur. Every access at `$D000-$DFFF` shows as I/O.
|
|
60
61
|
|
|
61
62
|
`c64_profile` gives the routines that used the most cycles since the session started, the last `clear` or the last `c64_window`. Use action `clear` before the part of the program that you want to measure. The profiler gives the cycles to the routine that a JSR called last. Code that the CPU enters with JMP or a branch counts for that routine. After `c64_window` or a snapshot restore, the profiler of VICE keeps the routines that ran before the restore. Then `c64_profile` can give the cycles of the program to such a routine, for example `$fd50`. Do not trust the routine addresses of `c64_profile` after a restore.
|
|
62
63
|
|
|
@@ -80,7 +81,7 @@ Write operations need a stopped CPU. Stop the CPU with `c64_execution` action `p
|
|
|
80
81
|
|
|
81
82
|
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
83
|
|
|
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
|
+
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`. `c64_status` also gives them. A memory target or a memory condition tests only space `c64`.
|
|
84
85
|
|
|
85
86
|
`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
87
|
|
|
@@ -27,7 +27,7 @@ One of these:
|
|
|
27
27
|
node <skill>/scripts/knowledge.ts <command> [subjects] [options]
|
|
28
28
|
```
|
|
29
29
|
|
|
30
|
-
A symbol is the name of an address from the user or the LLM. An analyzer name is a second name from DXA, Ghidra or an idiom, for example `FUN_17e2`. Each analyzer keeps its own name at an address. `at`, `search` and `export` show the analyzer names. The generated source uses only the symbols, unless c64-source gets `--analyzer-names`. To use an analyzer name in the source, `rename` the address with it.
|
|
30
|
+
A symbol is the name of an address from the user or the LLM. An analyzer name is a second name from DXA, Ghidra or an idiom, for example `FUN_17e2`. Each analyzer keeps its own name at an address. `at`, `search` and `export` show the analyzer names. The generated source uses only the symbols, unless c64-source gets `--analyzer-names`. To use an analyzer name in the source, `rename` the address with it. A symbol name is a label for ACME: a letter or underscore, then letters, digits or underscores. The knowledge refuses the name of a 6502 instruction, for example `lda`, in upper or lower case.
|
|
31
31
|
|
|
32
32
|
| Need | Command |
|
|
33
33
|
|---|---|
|
|
@@ -42,6 +42,7 @@ A symbol is the name of an address from the user or the LLM. An analyzer name is
|
|
|
42
42
|
| What the knowledge holds of the program | `program` |
|
|
43
43
|
| The code of a range with names, comments, access and references | `listing '$0400-$04ff'` |
|
|
44
44
|
| Name the symbol at an address | `rename '$2100' update_player --kind routine` |
|
|
45
|
+
| The rows that one revision added and closed, also memory, entry and coverage rows | `revision 20261015T2145` |
|
|
45
46
|
| Remove a symbol | `remove-symbol '$2100'` |
|
|
46
47
|
| Classify memory | `classify '$3000-$303f' sprite` |
|
|
47
48
|
| Classify a table of records | `classify '$3000-$303b' bytes --element 6` |
|
|
@@ -62,12 +63,12 @@ A symbol is the name of an address from the user or the LLM. An analyzer name is
|
|
|
62
63
|
| Files of generated source | `modules` |
|
|
63
64
|
| The address spaces that hold knowledge | `spaces` |
|
|
64
65
|
| History | `history '$2100'`, `history --name update_player`, `history --macro inc16`, `revisions`, `revision 20261015T2145` |
|
|
65
|
-
| Go back to an earlier state | `revert 20261015T2145` |
|
|
66
|
+
| Go back to an earlier state | `revert 20261015T2145` for all spaces, or `revert 20261015T2145 --space bank3` for one space |
|
|
66
67
|
| Decide the conflicts after a git merge | `merge --reason 'merge of two computers'` |
|
|
67
68
|
|
|
68
69
|
A revision has an id such as `20261015T214501.123-k3f9qz`. The id starts with the date and time. A command takes the full id, or a start of the id that only one revision has. The revision `0` is the knowledge before the first revision.
|
|
69
70
|
|
|
70
|
-
Every command takes `--space`: `c64` (the default), `drive8` for code in the 1541 drive, or `bank0` to `bank255` for cartridge banks. Drive code and banks use the same addresses as the C64, so each space has its own knowledge. A name is unique inside one space. In a batch, a write can name its own `space`.
|
|
71
|
+
Every command takes `--space`: `c64` (the default), `drive8` for code in the 1541 drive, or `bank0` to `bank255` for cartridge banks. `revert` is different: without `--space`, it reverts all spaces. With `--space`, it reverts only that space. Drive code and banks use the same addresses as the C64, so each space has its own knowledge. A name is unique inside one space. In a batch, a write can name its own `space`.
|
|
71
72
|
|
|
72
73
|
Symbol kinds are `label`, `routine`, `variable`, `data` and `vector`. Reference kinds are `call`, `jump`, `read`, `write`, `reference` and `pointer`. Only an operand expression writes a `pointer` reference.
|
|
73
74
|
|
|
@@ -94,6 +95,7 @@ Region types:
|
|
|
94
95
|
- An address is `$` and 1 to 4 hex digits, for example `'$c000'` or `'$fb'`, or a name. A name is a symbol, else an analyzer name that only one analyzer gives. Quote an address in a shell.
|
|
95
96
|
- A range is `<start>-<end>`, for example `'$c000-$cfff'`. In a JSON file, a range is `start` and `end`.
|
|
96
97
|
- Bytes in a JSON file are hex in a field `data`, for example `"data": "a900"`.
|
|
98
|
+
- The script examines each `--out` path before it starts work. It refuses a path in `.c64-re-tools/knowledge/` and the index of the knowledge, because only c64-knowledge writes them. It refuses a folder where the command writes a file, and a file where the command writes a folder.
|
|
97
99
|
- The result is one JSON object on one line. A file that the script writes shows as `{"out", "bytes", "sha256"}`. A list shows as `{"count", "listed"}`, and `rest` tells where to get the rest of a long list.
|
|
98
100
|
- An error is `{"error": {"code": "...", "message": "..."}}`. The message tells what is wrong and what to do.
|
|
99
101
|
- The exit status is 0 for done or PASS, 1 for FAIL, 2 for an error and 3 for INCONCLUSIVE.
|
|
@@ -172,7 +174,7 @@ The ops and their fields:
|
|
|
172
174
|
| `unmacro` | `name` |
|
|
173
175
|
| `use` | `start`, `end`, `macro`, `args` (a list or a comma list) |
|
|
174
176
|
|
|
175
|
-
A `line` comment is a line of its own above the address. A `side` comment is at the end of the line of the address. The script checks an `operand`, `patch` or `use` write against the program of the knowledge in the space of `--space`. All writes become one revision. If the knowledge refuses one write, the script writes nothing, and the error names the write.
|
|
177
|
+
Each write can also have `space`. A `line` comment is a line of its own above the address. A `side` comment is at the end of the line of the address. The script checks an `operand`, `patch` or `use` write against the program of the knowledge in the space of `--space`. All writes become one revision. If the knowledge refuses one write, the script writes nothing, and the error names the write.
|
|
176
178
|
|
|
177
179
|
The c64-provenance skill writes a batch with `match --out`. The batch holds the names of library routines at their addresses in this program. Read the `batch.leftOut` of its result first. Then apply the batch with the command in its `apply`.
|
|
178
180
|
|
|
@@ -151,9 +151,18 @@ function suppression(row: SuppressionRow) {
|
|
|
151
151
|
return { ...spaceOf(row), suppresses: row.entity, ...place, origin: row.origin, revision: row.revision };
|
|
152
152
|
}
|
|
153
153
|
|
|
154
|
+
/** A row of the program in a revision: memory bytes, the entry, or a coverage range. */
|
|
155
|
+
function programRow(entry: Extract<HistoryEntry, { entity: "memory" | "entry" | "coverage" }>) {
|
|
156
|
+
if (entry.entity === "memory") return { ...spaceOf(entry.row), start: formatC64Address(entry.row.address), end: formatC64Address(entry.row.address + entry.row.bytes - 1), bytes: entry.row.bytes, origin: entry.row.origin, revision: entry.row.revision };
|
|
157
|
+
if (entry.entity === "entry") return { ...spaceOf(entry.row), address: formatC64Address(entry.row.address), state: entry.row.state, stages: entry.row.stages, origin: entry.row.origin, revision: entry.row.revision };
|
|
158
|
+
return { ...spaceOf(entry.row), kind: entry.row.kind, start: formatC64Address(entry.row.start), end: formatC64Address(entry.row.end), origin: entry.row.origin, revision: entry.row.revision };
|
|
159
|
+
}
|
|
160
|
+
|
|
154
161
|
function historyEntry(entry: HistoryEntry) {
|
|
155
162
|
const shown =
|
|
156
|
-
entry.entity === "
|
|
163
|
+
entry.entity === "memory" || entry.entity === "entry" || entry.entity === "coverage"
|
|
164
|
+
? programRow(entry)
|
|
165
|
+
: entry.entity === "symbol" || entry.entity === "analyzer-name"
|
|
157
166
|
? symbol(entry.row)
|
|
158
167
|
: entry.entity === "region"
|
|
159
168
|
? region(entry.row)
|
|
@@ -420,7 +429,8 @@ function writeCommand(spec: Omit<CommandSpec, "run">, prepare: (call: Call) => W
|
|
|
420
429
|
return {
|
|
421
430
|
...spec,
|
|
422
431
|
notes: [spec.notes, "A write is one revision. It makes .c64-re-tools/knowledge/ when it is not there."].filter((note) => note !== undefined).join("\n"),
|
|
423
|
-
|
|
432
|
+
// A command can give a WRITE option its own help or default, for example --space of revert.
|
|
433
|
+
options: Object.fromEntries(Object.entries({ ...spec.options, ...WRITE }).map(([name, option]) => [name, spec.options?.[name] ?? option])),
|
|
424
434
|
run: (call) => withWrite(prepare(call)),
|
|
425
435
|
};
|
|
426
436
|
}
|
|
@@ -564,11 +574,11 @@ const READ_COMMANDS: CommandSpec[] = [
|
|
|
564
574
|
return { revisions: list(call, rows, (row) => row, "a larger --max-listed, or --before <revision> with the oldest revision in listed") };
|
|
565
575
|
},
|
|
566
576
|
),
|
|
567
|
-
readCommand({ name: "revision", summary: "The rows that one revision added and closed.", subjects: [REVISION], options: LIST }, (db, call) => {
|
|
577
|
+
readCommand({ name: "revision", summary: "The rows that one revision added and closed: knowledge rows, and memory, entry and coverage rows of the program.", subjects: [REVISION], options: LIST }, (db, call) => {
|
|
568
578
|
const changes = revision(db, call.subject(0));
|
|
569
579
|
return { revision: changes.revision, added: list(call, changes.added, historyEntry, MORE), closed: list(call, changes.closed, historyEntry, MORE) };
|
|
570
580
|
}),
|
|
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) => {
|
|
581
|
+
readCommand({ name: "export", summary: "All current knowledge to a project file as JSON.", options: { ...FILTER, out: { ...OPTIONS.out, output: "file", value: "<file>", help: "the JSON file, relative to the project folder", required: true } } }, (db, call) => {
|
|
572
582
|
const range = rangeOf(call);
|
|
573
583
|
const origins = originsOf(call);
|
|
574
584
|
const space = call.space;
|
|
@@ -595,7 +605,7 @@ const READ_COMMANDS: CommandSpec[] = [
|
|
|
595
605
|
name: "listing",
|
|
596
606
|
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.",
|
|
597
607
|
subjects: [RANGE],
|
|
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 },
|
|
608
|
+
options: { out: { ...OPTIONS.out, output: "file", value: "<file>", help: "write the listing as text to this file in place of the lines in the result" }, ...LIST },
|
|
599
609
|
},
|
|
600
610
|
(db, call) => {
|
|
601
611
|
const { start, end } = call.parseRange(call.subject(0), "<range>");
|
|
@@ -837,11 +847,23 @@ const WRITE_COMMANDS: CommandSpec[] = [
|
|
|
837
847
|
};
|
|
838
848
|
},
|
|
839
849
|
),
|
|
840
|
-
writeCommand(
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
850
|
+
writeCommand(
|
|
851
|
+
{
|
|
852
|
+
name: "revert",
|
|
853
|
+
summary: "Makes the knowledge the same as at one revision: all spaces, or with --space only that space. The revert is a new revision: the history stays complete.",
|
|
854
|
+
subjects: [REVISION],
|
|
855
|
+
options: { space: { kind: "space", value: "<space>", help: "revert only this space: c64, drive8, or bank0 to bank255. The other spaces do not change. Without --space, every space reverts" } },
|
|
856
|
+
},
|
|
857
|
+
(call) => {
|
|
858
|
+
const context = contextOf(call);
|
|
859
|
+
const space = call.string("space");
|
|
860
|
+
const request = { revision: call.subject(0), ...(space === undefined ? {} : { space }) };
|
|
861
|
+
return (db) => {
|
|
862
|
+
const result = revert(db, context, request);
|
|
863
|
+
return { revision: result.revision, restores: result.current.restoredRevision, ...(space === undefined ? { spaces: "all" } : { space }) };
|
|
864
|
+
};
|
|
865
|
+
},
|
|
866
|
+
),
|
|
845
867
|
writeCommand({ name: "merge", summary: "Decides the conflicts that a git merge of two computers' work left: of the rows of one conflict, the newest stays. One revision for all." }, (call) => {
|
|
846
868
|
const context = contextOf(call);
|
|
847
869
|
const platform = new Map(platformNames().map((entry) => [entry.name, entry.address]));
|
|
@@ -885,7 +907,7 @@ const JOIN: CommandSpec = {
|
|
|
885
907
|
name: "join",
|
|
886
908
|
summary: "Joins the batches of agents that worked in parallel into one batch for apply. It writes no knowledge.",
|
|
887
909
|
subjects: [{ name: "<batch>", help: 'a batch: a list of writes, or {"owner": {"start": "$1000", "end": "$1fff"}, "ops": [...]}', list: true }],
|
|
888
|
-
options: { ...SPACE, out: { ...OPTIONS.out, value: "<file>", help: "the joined batch, relative to the project folder", required: true } },
|
|
910
|
+
options: { ...SPACE, out: { ...OPTIONS.out, output: "file", value: "<file>", help: "the joined batch, relative to the project folder", required: true } },
|
|
889
911
|
notes: "At an address in the owner range of a batch, its name, comment, operand and module win, and its regions come last. One name or one module file at two addresses writes nothing.",
|
|
890
912
|
run: (call) => {
|
|
891
913
|
if (call.subjects.length < 2) throw new UsageError("join needs two or more batch files");
|
|
@@ -940,7 +962,7 @@ function batchOperation(call: Call, item: unknown, at: string): BatchOperation {
|
|
|
940
962
|
function batchFields(fields: Record<string, unknown>, at: string, address: (text: string, what: string) => number): BatchOperation {
|
|
941
963
|
const op = oneOf(Object.keys(BATCH_FIELDS) as Array<BatchOperation["op"]>, String(fields.op), `${at}: op`);
|
|
942
964
|
const unknown = Object.keys(fields).filter((key) => key !== "op" && key !== "space" && !BATCH_FIELDS[op].includes(key));
|
|
943
|
-
if (unknown.length > 0) throw new Refusal("invalid-input", `${at}: ${op} takes ${BATCH_FIELDS[op].join(", ")}, not ${unknown.join(", ")}`);
|
|
965
|
+
if (unknown.length > 0) throw new Refusal("invalid-input", `${at}: ${op} takes ${[...BATCH_FIELDS[op], "space"].join(", ")}, not ${unknown.join(", ")}`);
|
|
944
966
|
const text = (key: string) => {
|
|
945
967
|
if (typeof fields[key] !== "string") throw new Refusal("invalid-input", `${at}: ${key} must be a string`);
|
|
946
968
|
return fields[key] as string;
|
|
@@ -1006,7 +1028,7 @@ function batchFields(fields: Record<string, unknown>, at: string, address: (text
|
|
|
1006
1028
|
|
|
1007
1029
|
await runScript({
|
|
1008
1030
|
name: "knowledge",
|
|
1009
|
-
summary: "Reads and writes the knowledge of the project: names, regions, comments, references, the program and its coverage, and the source knowledge. A read needs no knowledge folder. Every command takes --space: c64 (the default), drive8 for 1541 drive code, or bank0 to bank255 for cartridge banks.",
|
|
1031
|
+
summary: "Reads and writes the knowledge of the project: names, regions, comments, references, the program and its coverage, and the source knowledge. A read needs no knowledge folder. Every command takes --space: c64 (the default), drive8 for 1541 drive code, or bank0 to bank255 for cartridge banks. revert without --space reverts all spaces.",
|
|
1010
1032
|
names: knowledgeNames,
|
|
1011
1033
|
commands: [...READ_COMMANDS, ...WRITE_COMMANDS, JOIN],
|
|
1012
1034
|
});
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
// reads files: knowledge.ts gives the lookups.
|
|
5
5
|
|
|
6
6
|
import { COVERAGE, formatC64Address, formatC64Byte } from "#src/c64.ts";
|
|
7
|
-
import { MODE_LENGTH, OPCODES, type Mode } from "#src/opcodes.ts";
|
|
7
|
+
import { instructionWith, MODE_LENGTH, OPCODES, type Mode } from "#src/opcodes.ts";
|
|
8
8
|
|
|
9
9
|
export interface ListingSource {
|
|
10
10
|
/** The image byte at an address, or undefined outside the image. */
|
|
@@ -52,36 +52,13 @@ function accessOf(coverage: Uint8Array | undefined, start: number, length: numbe
|
|
|
52
52
|
return text === "" ? "-" : text;
|
|
53
53
|
}
|
|
54
54
|
|
|
55
|
-
/** The operand of an instruction
|
|
55
|
+
/** The operand value of an instruction as an expression: the name of its target where one exists, else the number. Empty for no operand. */
|
|
56
56
|
function operandOf(mode: Mode, address: number, low: number, high: number, nameOf: ListingSource["nameOf"]): string {
|
|
57
|
-
const word = low | (high << 8);
|
|
58
57
|
const named = (target: number, digits: 2 | 4) => nameOf(target) ?? (digits === 2 ? formatC64Byte(target) : formatC64Address(target));
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
return `#${formatC64Byte(low)}`;
|
|
64
|
-
case "zp":
|
|
65
|
-
return named(low, 2);
|
|
66
|
-
case "zpx":
|
|
67
|
-
return `${named(low, 2)},x`;
|
|
68
|
-
case "zpy":
|
|
69
|
-
return `${named(low, 2)},y`;
|
|
70
|
-
case "abs":
|
|
71
|
-
return named(word, 4);
|
|
72
|
-
case "abx":
|
|
73
|
-
return `${named(word, 4)},x`;
|
|
74
|
-
case "aby":
|
|
75
|
-
return `${named(word, 4)},y`;
|
|
76
|
-
case "ind":
|
|
77
|
-
return `(${named(word, 4)})`;
|
|
78
|
-
case "izx":
|
|
79
|
-
return `(${named(low, 2)},x)`;
|
|
80
|
-
case "izy":
|
|
81
|
-
return `(${named(low, 2)}),y`;
|
|
82
|
-
case "rel":
|
|
83
|
-
return named((address + 2 + (low < 0x80 ? low : low - 0x100)) & 0xffff, 4);
|
|
84
|
-
}
|
|
58
|
+
if (mode === "imp") return "";
|
|
59
|
+
if (mode === "imm") return formatC64Byte(low);
|
|
60
|
+
if (mode === "rel") return named((address + 2 + (low < 0x80 ? low : low - 0x100)) & 0xffff, 4);
|
|
61
|
+
return MODE_LENGTH[mode] === 3 ? named(low | (high << 8), 4) : named(low, 2);
|
|
85
62
|
}
|
|
86
63
|
|
|
87
64
|
/** The lines of start..end: instructions in code regions, decoded from the region start, and bytes elsewhere. */
|
|
@@ -123,8 +100,9 @@ export function listing(source: ListingSource, start: number, end: number): List
|
|
|
123
100
|
const length = MODE_LENGTH[opcode.mode];
|
|
124
101
|
const bytes = Array.from({ length: length - 1 }, (_, offset) => source.byteAt(address + 1 + offset));
|
|
125
102
|
if (address + length - 1 <= region.end && bytes.every((value) => value !== undefined)) {
|
|
103
|
+
// The operand expression of the knowledge in the syntax of the addressing mode, as c64-source writes it.
|
|
126
104
|
const operand = source.operand(address) ?? operandOf(opcode.mode, address, bytes[0] ?? 0, bytes[1] ?? 0, source.nameOf);
|
|
127
|
-
if (address >= start) lines.push(line(address, length,
|
|
105
|
+
if (address >= start) lines.push(line(address, length, instructionWith(byte, operand)));
|
|
128
106
|
address += length;
|
|
129
107
|
continue;
|
|
130
108
|
}
|
|
@@ -43,6 +43,7 @@ Write a value for `decode` and `--bank` as `$` and hex digits, as a decimal numb
|
|
|
43
43
|
- An address is `$` and 1 to 4 hex digits, for example `'$c000'` or `'$fb'`, or a name. A name is a symbol, else an analyzer name that only one analyzer gives. Quote an address in a shell.
|
|
44
44
|
- A range is `<start>-<end>`, for example `'$c000-$cfff'`. In a JSON file, a range is `start` and `end`.
|
|
45
45
|
- Bytes in a JSON file are hex in a field `data`, for example `"data": "a900"`.
|
|
46
|
+
- The script examines each `--out` path before it starts work. It refuses a path in `.c64-re-tools/knowledge/` and the index of the knowledge, because only c64-knowledge writes them. It refuses a folder where the command writes a file, and a file where the command writes a folder.
|
|
46
47
|
- The result is one JSON object on one line. A file that the script writes shows as `{"out", "bytes", "sha256"}`. A list shows as `{"count", "listed"}`, and `rest` tells where to get the rest of a long list.
|
|
47
48
|
- An error is `{"error": {"code": "...", "message": "..."}}`. The message tells what is wrong and what to do.
|
|
48
49
|
- The exit status is 0 for done or PASS, 1 for FAIL, 2 for an error and 3 for INCONCLUSIVE.
|
|
@@ -71,7 +71,7 @@ await runScript({
|
|
|
71
71
|
{
|
|
72
72
|
name: "export",
|
|
73
73
|
summary: "Writes the platform names as an ACME include: NAME = $xxxx.",
|
|
74
|
-
options: { out: { ...OPTIONS.out, value: "<file>", help: "the include file, relative to the project folder", required: true } },
|
|
74
|
+
options: { out: { ...OPTIONS.out, output: "file", value: "<file>", help: "the include file, relative to the project folder", required: true } },
|
|
75
75
|
run: (call) => {
|
|
76
76
|
const names = platformNames();
|
|
77
77
|
const lines = ["; C64 platform names from the c64-memory-map skill, for ACME.", ...names.map((item) => `${item.name} = ${item.address < 0x100 ? formatC64Byte(item.address) : formatC64Address(item.address)}`.padEnd(24) + `; ${item.meaning}`)];
|
|
@@ -56,6 +56,7 @@ With `--frames`, a PRG starts with autostart and RUN. An image starts from its b
|
|
|
56
56
|
- An address is `$` and 1 to 4 hex digits, for example `'$c000'` or `'$fb'`, or a name. A name is a symbol, else an analyzer name that only one analyzer gives. Quote an address in a shell.
|
|
57
57
|
- A range is `<start>-<end>`, for example `'$c000-$cfff'`. In a JSON file, a range is `start` and `end`.
|
|
58
58
|
- Bytes in a JSON file are hex in a field `data`, for example `"data": "a900"`.
|
|
59
|
+
- The script examines each `--out` path before it starts work. It refuses a path in `.c64-re-tools/knowledge/` and the index of the knowledge, because only c64-knowledge writes them. It refuses a folder where the command writes a file, and a file where the command writes a folder.
|
|
59
60
|
- The result is one JSON object on one line. A file that the script writes shows as `{"out", "bytes", "sha256"}`. A list shows as `{"count", "listed"}`, and `rest` tells where to get the rest of a long list.
|
|
60
61
|
- An error is `{"error": {"code": "...", "message": "..."}}`. The message tells what is wrong and what to do.
|
|
61
62
|
- The exit status is 0 for done or PASS, 1 for FAIL, 2 for an error and 3 for INCONCLUSIVE.
|
|
@@ -41,7 +41,7 @@ import {
|
|
|
41
41
|
import { CODE_REGION_TYPES, SEMANTIC_ORIGINS } from "#src/knowledge/schema.ts";
|
|
42
42
|
import { platformNames } from "#src/platform.ts";
|
|
43
43
|
import { projectRoot } from "#src/project.ts";
|
|
44
|
-
import { knowledgeNames } from "#src/knowledge/names.ts";
|
|
44
|
+
import { knowledgeAt, knowledgeNames } from "#src/knowledge/names.ts";
|
|
45
45
|
import { listed, OPTIONS, runScript, UsageError, type Call, type CommandSpec } from "#src/script.ts";
|
|
46
46
|
import { compareReleases, shiftSuggestion, suggestShifts, type Release } from "./lib/compare.ts";
|
|
47
47
|
import { findRoutine, libraryFolder, readLibrary, routineFrom, saveRoutine, suggest } from "./lib/library.ts";
|
|
@@ -107,14 +107,17 @@ async function compare(call: Call): Promise<unknown> {
|
|
|
107
107
|
const shifts = new Map<number, number>();
|
|
108
108
|
for (const text of call.strings("shift")) {
|
|
109
109
|
const match = /^(\d+):([+-]?\d+)$/.exec(text);
|
|
110
|
-
if (match === null
|
|
110
|
+
if (match === null) throw new UsageError(`--shift must be <release number>:<bytes>, for example 2:-256, not ${text}`);
|
|
111
|
+
if (Number(match[1]) < 1 || Number(match[1]) > positionals.length) {
|
|
112
|
+
throw new UsageError(`--shift ${text}: there is no release ${match[1]}. This compare has ${positionals.length} releases: give a release number from 1 to ${positionals.length}`);
|
|
113
|
+
}
|
|
111
114
|
if (shifts.has(Number(match[1]))) throw new UsageError(`--shift gives release ${match[1]} more than one shift (${shifts.get(Number(match[1]))} and ${match[2]}). Give one --shift for each release`);
|
|
112
115
|
shifts.set(Number(match[1]), Number(match[2]));
|
|
113
116
|
}
|
|
114
117
|
const frames = call.count("frames");
|
|
115
118
|
const stepsFile = call.string("steps");
|
|
116
119
|
if (stepsFile !== undefined && frames === undefined) throw new UsageError("--steps works only with --frames");
|
|
117
|
-
const steps: ActionStep[] = stepsFile === undefined ? [] : readActionSteps(stepsFile, "--steps");
|
|
120
|
+
const steps: ActionStep[] = stepsFile === undefined ? [] : readActionSteps(stepsFile, "--steps", knowledgeAt());
|
|
118
121
|
const loaded = positionals.map((path, index) => readRelease(path, shifts.get(index + 1) ?? 0));
|
|
119
122
|
const releases = loaded.map((item) => item.release);
|
|
120
123
|
const range = call.range("range");
|
|
@@ -462,14 +465,14 @@ const COMMANDS: CommandSpec[] = [
|
|
|
462
465
|
{
|
|
463
466
|
name: "separate",
|
|
464
467
|
summary: "Evidence from the program in the knowledge: the unpack stage that wrote each range, its access in the coverage, whether call and jump references, or the run on from the code before it, reach each code region from the entry, and what the program reads or executes outside its segments.",
|
|
465
|
-
options: { out: { ...OPTIONS.out, value: "<file>", help: "write all ranges to this file" }, "max-listed": OPTIONS["max-listed"] },
|
|
468
|
+
options: { out: { ...OPTIONS.out, output: "file", value: "<file>", help: "write all ranges to this file" }, "max-listed": OPTIONS["max-listed"] },
|
|
466
469
|
run: separateCommand,
|
|
467
470
|
},
|
|
468
471
|
{
|
|
469
472
|
name: "leftovers",
|
|
470
473
|
summary: "What the program in the knowledge holds besides its working code: texts in PETSCII, screen codes, ASCII or with bit 7 set (Apple II), assembler source, BASIC lines away from the BASIC start, fills, untouched bytes outside code, copies of earlier bytes, and code regions that nothing reaches and no run executed.",
|
|
471
474
|
options: {
|
|
472
|
-
out: { ...OPTIONS.out, value: "<file>", help: "write all findings to this file" },
|
|
475
|
+
out: { ...OPTIONS.out, output: "file", value: "<file>", help: "write all findings to this file" },
|
|
473
476
|
"min-text": { kind: "count", value: "<n>", min: 4, max: 999, default: 8, help: "the shortest text to find (default 8)" },
|
|
474
477
|
"max-listed": OPTIONS["max-listed"],
|
|
475
478
|
},
|
|
@@ -492,7 +495,7 @@ const COMMANDS: CommandSpec[] = [
|
|
|
492
495
|
summary: "Finds each routine of the library in the program of the knowledge: the opcodes and other fixed bytes equal, and the addresses into the routine moved by one shift.",
|
|
493
496
|
options: {
|
|
494
497
|
library: { ...LIBRARY, help: "the library folder, relative to the project folder, for example ../library" },
|
|
495
|
-
out: { ...OPTIONS.out, value: "<file>", help: "write the names of the routines at the found addresses as a batch for knowledge.ts apply" },
|
|
498
|
+
out: { ...OPTIONS.out, output: "file", value: "<file>", help: "write the names of the routines at the found addresses as a batch for knowledge.ts apply" },
|
|
496
499
|
"max-listed": OPTIONS["max-listed"],
|
|
497
500
|
},
|
|
498
501
|
run: matchCommand,
|