@henols/c64-re-tools 2.0.1 → 2.1.0-rc.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/README.md +8 -4
- package/package.json +1 -1
- package/skills/c64-assembler/SKILL.md +24 -9
- package/skills/c64-assembler/scripts/assembler.ts +95 -0
- package/skills/c64-basic/SKILL.md +17 -9
- package/skills/c64-basic/scripts/basic.ts +26 -36
- package/skills/c64-disk/SKILL.md +17 -7
- package/skills/c64-disk/scripts/disk.ts +130 -104
- package/skills/c64-disk/scripts/lib/containers.ts +10 -12
- package/skills/c64-docs/SKILL.md +47 -28
- package/skills/c64-docs/references/style.md +6 -6
- package/skills/c64-docs/scripts/docs.ts +323 -0
- package/skills/c64-docs/scripts/lib/source-map.ts +1 -1
- package/skills/c64-emulator/SKILL.md +11 -7
- package/skills/c64-knowledge/SKILL.md +34 -21
- package/skills/c64-knowledge/scripts/knowledge.ts +597 -718
- package/skills/c64-knowledge/scripts/lib/listing.ts +5 -7
- package/skills/c64-memory-map/SKILL.md +18 -10
- package/skills/c64-memory-map/scripts/memory-map.ts +81 -0
- package/skills/c64-provenance/SKILL.md +82 -26
- package/skills/c64-provenance/scripts/lib/compare.ts +8 -12
- package/skills/c64-provenance/scripts/lib/leftovers.ts +442 -0
- package/skills/c64-provenance/scripts/lib/library.ts +15 -25
- package/skills/c64-provenance/scripts/lib/runtime.ts +12 -37
- package/skills/c64-provenance/scripts/lib/separate.ts +9 -10
- package/skills/c64-provenance/scripts/provenance.ts +222 -252
- package/skills/c64-reverse-engineering/SKILL.md +18 -15
- package/skills/c64-source/SKILL.md +28 -16
- package/skills/c64-source/scripts/lib/loader.ts +52 -77
- package/skills/c64-source/scripts/lib/source.ts +33 -42
- package/skills/c64-source/scripts/{generate.ts → source.ts} +68 -109
- package/skills/c64-static-analysis/SKILL.md +24 -13
- package/skills/c64-static-analysis/scripts/lib/coverage.ts +3 -4
- package/skills/c64-static-analysis/scripts/lib/findings.ts +2 -2
- package/skills/c64-static-analysis/scripts/lib/instructions.ts +1 -1
- package/skills/c64-static-analysis/scripts/lib/macros.ts +6 -8
- package/skills/c64-static-analysis/scripts/{analyze.ts → static-analysis.ts} +58 -118
- package/skills/c64-testing/SKILL.md +94 -19
- package/skills/c64-testing/references/recording-format.md +125 -0
- package/skills/c64-testing/references/scenario-format.md +24 -19
- package/skills/c64-testing/scripts/{diff.ts → lib/diff-files.ts} +18 -69
- package/skills/c64-testing/scripts/lib/diff.ts +5 -3
- package/skills/c64-testing/scripts/lib/play.ts +334 -0
- package/skills/c64-testing/scripts/lib/recording.ts +662 -0
- package/skills/c64-testing/scripts/lib/replay.ts +105 -0
- package/skills/c64-testing/scripts/{run.ts → lib/runs.ts} +36 -59
- package/skills/c64-testing/scripts/lib/scenario.ts +73 -81
- package/skills/c64-testing/scripts/testing.ts +189 -0
- package/skills/c64-unpacker/SKILL.md +42 -25
- package/skills/c64-unpacker/scripts/lib/evidence.ts +3 -4
- package/skills/c64-unpacker/scripts/lib/loads.ts +12 -10
- package/skills/c64-unpacker/scripts/lib/machine.ts +0 -2
- package/skills/c64-unpacker/scripts/lib/stages.ts +14 -19
- package/skills/c64-unpacker/scripts/{unpack.ts → unpacker.ts} +109 -211
- package/src/c64.ts +35 -20
- package/src/cli/main.ts +2 -2
- package/src/errors.ts +38 -0
- package/src/graphics.ts +3 -1
- package/src/host/server.ts +3 -2
- package/src/host/vice/session.ts +4 -4
- package/src/host-client/entry-state.ts +106 -0
- package/src/host-client/image.ts +21 -10
- package/src/host-client/steps.ts +285 -108
- package/src/host-client/transfer.ts +21 -15
- package/src/host-client/vice-session.ts +1 -1
- package/src/knowledge/database.ts +3 -23
- package/src/knowledge/expression.ts +8 -11
- package/src/knowledge/history.ts +2 -2
- package/src/knowledge/import.ts +13 -14
- package/src/knowledge/names.ts +50 -0
- package/src/knowledge/program.ts +11 -10
- package/src/knowledge/read.ts +16 -21
- package/src/knowledge/schema.ts +9 -3
- package/src/knowledge/text.ts +5 -5
- package/src/knowledge/write.ts +93 -137
- package/src/mcp/inputs.ts +105 -0
- package/src/mcp/output.ts +2 -13
- package/src/mcp/server.ts +12 -60
- package/src/mcp/tools/debug.ts +17 -12
- package/src/mcp/tools/execution.ts +5 -32
- package/src/mcp/tools/input.ts +7 -32
- package/src/mcp/tools/media.ts +6 -5
- package/src/mcp/tools/memory.ts +11 -38
- package/src/mcp/tools/video.ts +6 -5
- package/src/native/c64run.ts +162 -0
- package/src/native/discover.ts +8 -0
- package/src/native/dxa.ts +7 -6
- package/src/native/ghidra/analyze.ts +2 -1
- package/src/native/ghidra/index.ts +2 -1
- package/src/native/processes.ts +4 -3
- package/src/native/status.ts +25 -4
- package/src/native/types.ts +3 -7
- package/src/opcodes.ts +13 -13
- package/src/platform.ts +9 -9
- package/src/project.ts +16 -26
- package/src/protocol/messages.ts +3 -21
- package/src/protocol/vice.ts +7 -7
- package/src/script.ts +491 -49
- package/skills/c64-assembler/scripts/assemble.ts +0 -138
- package/skills/c64-docs/scripts/generate.ts +0 -109
- package/skills/c64-docs/scripts/link.ts +0 -239
- package/skills/c64-memory-map/scripts/memmap.ts +0 -92
- package/skills/c64-testing/scripts/checklist.ts +0 -51
- package/skills/c64-testing/scripts/test.ts +0 -65
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
// the bytes from coverage files, and the references in and out. Nothing here
|
|
4
4
|
// reads files: knowledge.ts gives the lookups.
|
|
5
5
|
|
|
6
|
-
import { COVERAGE } from "#src/c64.ts";
|
|
6
|
+
import { COVERAGE, formatC64Address, formatC64Byte } from "#src/c64.ts";
|
|
7
7
|
import { MODE_LENGTH, OPCODES, type Mode } from "#src/opcodes.ts";
|
|
8
8
|
|
|
9
9
|
export interface ListingSource {
|
|
@@ -41,8 +41,6 @@ export interface ListingLine {
|
|
|
41
41
|
}
|
|
42
42
|
|
|
43
43
|
const CODE = new Set(["code", "jumps"]);
|
|
44
|
-
const hex2 = (value: number) => `$${value.toString(16).padStart(2, "0")}`;
|
|
45
|
-
const hex4 = (value: number) => `$${value.toString(16).padStart(4, "0")}`;
|
|
46
44
|
const DATA_PER_LINE = 8;
|
|
47
45
|
|
|
48
46
|
/** The access class of bytes from the coverage: x, r and w as they occur, or - for none. */
|
|
@@ -57,12 +55,12 @@ function accessOf(coverage: Uint8Array | undefined, start: number, length: numbe
|
|
|
57
55
|
/** The operand of an instruction in ACME syntax, with the name of its target where one exists. */
|
|
58
56
|
function operandOf(mode: Mode, address: number, low: number, high: number, nameOf: ListingSource["nameOf"]): string {
|
|
59
57
|
const word = low | (high << 8);
|
|
60
|
-
const named = (target: number, digits: 2 | 4) => nameOf(target) ?? (digits === 2 ?
|
|
58
|
+
const named = (target: number, digits: 2 | 4) => nameOf(target) ?? (digits === 2 ? formatC64Byte(target) : formatC64Address(target));
|
|
61
59
|
switch (mode) {
|
|
62
60
|
case "imp":
|
|
63
61
|
return "";
|
|
64
62
|
case "imm":
|
|
65
|
-
return `#${
|
|
63
|
+
return `#${formatC64Byte(low)}`;
|
|
66
64
|
case "zp":
|
|
67
65
|
return named(low, 2);
|
|
68
66
|
case "zpx":
|
|
@@ -138,7 +136,7 @@ export function listing(source: ListingSource, start: number, end: number): List
|
|
|
138
136
|
values.push(source.byteAt(at)!);
|
|
139
137
|
at++;
|
|
140
138
|
}
|
|
141
|
-
if (address >= start) lines.push(line(address, values.length, `!byte ${values.map(
|
|
139
|
+
if (address >= start) lines.push(line(address, values.length, `!byte ${values.map(formatC64Byte).join(",")}`));
|
|
142
140
|
address = at;
|
|
143
141
|
}
|
|
144
142
|
return lines;
|
|
@@ -149,7 +147,7 @@ export function listingText(lines: readonly ListingLine[]): string {
|
|
|
149
147
|
const out: string[] = [];
|
|
150
148
|
for (const item of lines) {
|
|
151
149
|
for (const comment of item.lineComments ?? []) out.push(` ; ${comment}`);
|
|
152
|
-
const head = `${
|
|
150
|
+
const head = `${formatC64Address(item.address)} ${item.bytes.padEnd(9)} ${(item.access ?? "").padEnd(3)}`;
|
|
153
151
|
const body = (item.label === undefined ? "" : `${item.label} `).padEnd(20) + item.text;
|
|
154
152
|
const notes = [
|
|
155
153
|
...(item.comment === undefined ? [] : [item.comment]),
|
|
@@ -21,10 +21,10 @@ This skill does not read the emulator and does not change project knowledge.
|
|
|
21
21
|
The script looks up local reference data. It does not use the host runtime.
|
|
22
22
|
|
|
23
23
|
```
|
|
24
|
-
node <skill>/scripts/
|
|
25
|
-
node <skill>/scripts/
|
|
26
|
-
node <skill>/scripts/
|
|
27
|
-
node <skill>/scripts/
|
|
24
|
+
node <skill>/scripts/memory-map.ts at '$d020' '$0314' '$ffd2'
|
|
25
|
+
node <skill>/scripts/memory-map.ts decode '$d018' '$15' --bank '$97'
|
|
26
|
+
node <skill>/scripts/memory-map.ts list vic
|
|
27
|
+
node <skill>/scripts/memory-map.ts export --out src/hw.asm
|
|
28
28
|
```
|
|
29
29
|
|
|
30
30
|
| Command | Result |
|
|
@@ -34,16 +34,26 @@ node <skill>/scripts/memmap.ts export --out src/hw.asm
|
|
|
34
34
|
| `list <vic\|sid\|cia1\|cia2\|kernal\|system>` | All entries of that group. |
|
|
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
|
-
Write
|
|
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.
|
|
38
38
|
|
|
39
|
-
|
|
39
|
+
### Calling the script
|
|
40
|
+
|
|
41
|
+
- Call a script as `node <skill>/scripts/<script>.ts <command> [subjects] [options]`. The command comes first.
|
|
42
|
+
- `<script>.ts --help` lists the commands. `<script>.ts <command> --help` gives the subjects and the options of one command. The help is JSON: `{"text": "..."}`.
|
|
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
|
+
- A range is `<start>-<end>`, for example `'$c000-$cfff'`. In a JSON file, a range is `start` and `end`.
|
|
45
|
+
- Bytes in a JSON file are hex in a field `data`, for example `"data": "a900"`.
|
|
46
|
+
- 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
|
+
- An error is `{"error": {"code": "...", "message": "..."}}`. The message tells what is wrong and what to do.
|
|
48
|
+
- The exit status is 0 for done or PASS, 1 for FAIL, 2 for an error and 3 for INCONCLUSIVE.
|
|
40
49
|
|
|
41
50
|
### Project folder
|
|
42
51
|
|
|
43
52
|
- Paths are relative to the project folder. The project folder is the nearest folder at or above the working directory that holds `.c64-re-tools/`.
|
|
44
53
|
- If the working directory is not in a project and has projects below it, the script refuses and lists them. Then give `--project <folder>` to each script.
|
|
45
54
|
- Write each file that you make for your work in `.c64-re-tools/local/` of the project. Git ignores that folder. Examples are results, batches, step files, runs, snapshots and memory dumps.
|
|
46
|
-
- Move a file out of `.c64-re-tools/local/` only when it is the evidence for a finding. Move it before you give it to `--evidence
|
|
55
|
+
- Move a file out of `.c64-re-tools/local/` only when it is the evidence for a finding. Move it before you give it to `--evidence test:<file>` or `--evidence file:<file>` of c64-knowledge. These two refuse a file in `.c64-re-tools/local/`.
|
|
56
|
+
- Do not move a coverage map after `coverage` of c64-knowledge imported it. `--evidence coverage:<map>` names the map by its path at the import. The knowledge keeps the coverage and the SHA-256 of the map, so the evidence stays valid.
|
|
47
57
|
- Put a run that is evidence in `runs/<name>/`, with its run file, its results and the snapshot files that it uses. Change the paths in the run file to the new paths. Then git keeps them, and another person can do the run again.
|
|
48
58
|
- A shell redirect (`>`) is relative to the working directory, not to the project folder. From the folder above the project, write `<folder>/.c64-re-tools/local/result.json`.
|
|
49
59
|
|
|
@@ -71,9 +81,7 @@ A conclusion about the application can be project knowledge. For example: "the p
|
|
|
71
81
|
|
|
72
82
|
## Result
|
|
73
83
|
|
|
74
|
-
The platform meaning of each address, or the decoded meaning of a register value. For `export`: the
|
|
75
|
-
|
|
76
|
-
The exit status is 0 for a result. The exit status is 2 for a wrong argument.
|
|
84
|
+
The platform meaning of each address, or the decoded meaning of a register value. For `export`: the count of names in `names`, and the file as `{out, bytes, sha256}`.
|
|
77
85
|
|
|
78
86
|
## Failure and conflicts
|
|
79
87
|
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
// The c64-memory-map script: the platform meaning of C64 addresses and of
|
|
2
|
+
// values in platform registers. Local lookup only; nothing is stored. export
|
|
3
|
+
// writes the platform names to a project file as an ACME include.
|
|
4
|
+
|
|
5
|
+
import { formatC64Address, formatC64Byte } from "#src/c64.ts";
|
|
6
|
+
import { writeReported } from "#src/host-client/transfer.ts";
|
|
7
|
+
import { knowledgeNames } from "#src/knowledge/names.ts";
|
|
8
|
+
import { decode, IO, lookup, platformNames, ROM, SYSTEM, type Entry } from "#src/platform.ts";
|
|
9
|
+
import { OPTIONS, runScript, UsageError } from "#src/script.ts";
|
|
10
|
+
|
|
11
|
+
const show = (entry: Entry) => ({ start: formatC64Address(entry.start), ...(entry.end !== entry.start ? { end: formatC64Address(entry.end) } : {}), name: entry.name, meaning: entry.meaning });
|
|
12
|
+
|
|
13
|
+
const LISTS: Record<string, () => Entry[]> = {
|
|
14
|
+
vic: () => IO[0]!.registers,
|
|
15
|
+
sid: () => IO[1]!.registers,
|
|
16
|
+
cia1: () => IO[2]!.registers,
|
|
17
|
+
cia2: () => IO[3]!.registers,
|
|
18
|
+
kernal: () => ROM,
|
|
19
|
+
system: () => SYSTEM,
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
await runScript({
|
|
23
|
+
name: "memory-map",
|
|
24
|
+
summary: "The platform meaning of C64 addresses and of values in platform registers.",
|
|
25
|
+
names: knowledgeNames,
|
|
26
|
+
commands: [
|
|
27
|
+
{
|
|
28
|
+
name: "at",
|
|
29
|
+
summary: "The platform meaning of each address: its name, area and meaning.",
|
|
30
|
+
subjects: [{ name: "<address>", help: "an address or a name of the knowledge", list: true }],
|
|
31
|
+
run: (call) => ({
|
|
32
|
+
addresses: call.subjects.map((text) => {
|
|
33
|
+
const found = lookup(call.parseAddress(text, "<address>"));
|
|
34
|
+
// The chip registers, color RAM and the cartridge I/O areas show only while $01 maps I/O in.
|
|
35
|
+
const io = found.address >= 0xd000 && found.address <= 0xdfff;
|
|
36
|
+
return {
|
|
37
|
+
...found,
|
|
38
|
+
address: formatC64Address(found.address),
|
|
39
|
+
...(found.mirrorOf === undefined ? {} : { mirrorOf: formatC64Address(found.mirrorOf) }),
|
|
40
|
+
...(found.entryStart === undefined ? {} : { entryStart: formatC64Address(found.entryStart) }),
|
|
41
|
+
...(io ? { visible: "when $01 shows I/O at $D000 (the default)" } : {}),
|
|
42
|
+
};
|
|
43
|
+
}),
|
|
44
|
+
}),
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
name: "decode",
|
|
48
|
+
summary: "The meaning of a value in a platform register.",
|
|
49
|
+
subjects: [
|
|
50
|
+
{ name: "<register>", help: "the register: $0001, $d011, $d016, $d018, $d010, $d015-$d01f, $dc0d, $dd00 or $dd0d" },
|
|
51
|
+
{ name: "<value>", help: "the value of the register: $ and hex, decimal, or % and binary, 0 to 255" },
|
|
52
|
+
],
|
|
53
|
+
options: { bank: { kind: "number", value: "<value>", help: "the value of $dd00, for the VIC-II bank of a $d018 value", max: 0xff } },
|
|
54
|
+
run: (call) => {
|
|
55
|
+
const register = call.parseAddress(call.subject(0), "<register>");
|
|
56
|
+
const value = call.parseNumber(call.subject(1), "<value>", 0, 0xff);
|
|
57
|
+
return { register: formatC64Address(register), value: formatC64Byte(value), name: lookup(register).name, ...decode(register, value, call.count("bank")) };
|
|
58
|
+
},
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
name: "list",
|
|
62
|
+
summary: "The registers of a chip, the KERNAL jump table, or the system areas.",
|
|
63
|
+
subjects: [{ name: "<list>", help: `one of ${Object.keys(LISTS).join(", ")}` }],
|
|
64
|
+
run: (call) => {
|
|
65
|
+
const list = LISTS[call.subject(0)];
|
|
66
|
+
if (list === undefined) throw new UsageError(`<list> must be one of ${Object.keys(LISTS).join(", ")}, not ${call.subject(0)}`);
|
|
67
|
+
return { entries: list().map(show) };
|
|
68
|
+
},
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
name: "export",
|
|
72
|
+
summary: "Writes the platform names as an ACME include: NAME = $xxxx.",
|
|
73
|
+
options: { out: { ...OPTIONS.out, value: "<file>", help: "the include file, relative to the project folder", required: true } },
|
|
74
|
+
run: (call) => {
|
|
75
|
+
const names = platformNames();
|
|
76
|
+
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}`)];
|
|
77
|
+
return { names: names.length, ...writeReported(call.string("out")!, `${lines.join("\n")}\n`, { what: "--out" }) };
|
|
78
|
+
},
|
|
79
|
+
},
|
|
80
|
+
],
|
|
81
|
+
});
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: c64-provenance
|
|
3
|
-
description: Use this skill to compare two or more releases of a C64 program, for example cracked versions from different groups. It finds the bytes where the releases differ and shows which releases agree there. It can run each release and tell how the program uses each difference. It also gives evidence for each range of the program in the knowledge. The evidence is the unpack stage, the access, and the code that the entry does not reach. Use the result to separate the content of the application from changes that a release added. It also keeps a library of routines that several programs share. It finds them in the program of the knowledge and writes their names as a batch for c64-knowledge. Do not use it to analyze the code.
|
|
3
|
+
description: Use this skill to compare two or more releases of a C64 program, for example cracked versions from different groups. It finds the bytes where the releases differ and shows which releases agree there. It can run each release and tell how the program uses each difference. It also gives evidence for each range of the program in the knowledge. The evidence is the unpack stage, the access, and the code that the entry does not reach. It also finds leftovers in one program: texts, assembler source, BASIC lines, fills, unused bytes, copies and dead code. It finds text of other computers, for example Apple II source. Leftovers tell about the development, the cracks and the recracks. Use the result to separate the content of the application from changes that a release added. It also keeps a library of routines that several programs share. It finds them in the program of the knowledge and writes their names as a batch for c64-knowledge. Do not use it to analyze the code.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# c64-provenance
|
|
@@ -9,13 +9,15 @@ description: Use this skill to compare two or more releases of a C64 program, fo
|
|
|
9
9
|
|
|
10
10
|
Compare releases of a C64 program. Find the bytes where they differ and which releases agree. Use this to find which bytes are probably the original application and which are changes of one release.
|
|
11
11
|
|
|
12
|
+
Find the leftovers in one program. A leftover is a part of the program that the program does not need. Examples are a crack text, an old copy of a routine and a piece of assembler source.
|
|
13
|
+
|
|
12
14
|
Keep a library of routines that several programs share, for example a music driver or a decruncher. Find the routines of the library in a program, and get their names for that program.
|
|
13
15
|
|
|
14
16
|
This skill does not change project knowledge.
|
|
15
17
|
|
|
16
18
|
## Inputs
|
|
17
19
|
|
|
18
|
-
- Two or more releases, relative to the project folder. A release is a PRG file or an `image.json` from c64-unpacker capture. Extract releases from disk images with the c64-disk skill. Unpack packed releases with the c64-unpacker skill first.
|
|
20
|
+
- Two or more releases, relative to the project folder. A release is `knowledge` (the program in the knowledge), a PRG file, or an `image.json` from c64-unpacker capture with its `entry_state.json` beside it. Extract releases from disk images with the c64-disk skill. Unpack packed releases with the c64-unpacker skill first.
|
|
19
21
|
- Optional: an address range to compare.
|
|
20
22
|
- Optional: a shift for a release that loads at a different address.
|
|
21
23
|
- Optional: a number of frames to run each release, and a steps file.
|
|
@@ -24,35 +26,47 @@ This skill does not change project knowledge.
|
|
|
24
26
|
|
|
25
27
|
## Tools / execution path
|
|
26
28
|
|
|
27
|
-
The script compares the
|
|
29
|
+
The script compares the releases locally. Only `--frames` uses the host runtime: each release runs in its own emulator.
|
|
28
30
|
|
|
29
31
|
```
|
|
30
|
-
node <skill>/scripts/provenance.ts releases/a.prg releases/b.prg releases/c.prg
|
|
31
|
-
node <skill>/scripts/provenance.ts releases/a.prg releases/b.prg --shift 2:-256 --
|
|
32
|
-
node <skill>/scripts/provenance.ts
|
|
32
|
+
node <skill>/scripts/provenance.ts compare releases/a.prg releases/b.prg releases/c.prg
|
|
33
|
+
node <skill>/scripts/provenance.ts compare releases/a.prg releases/b.prg --shift 2:-256 --range '$0900-$09ff'
|
|
34
|
+
node <skill>/scripts/provenance.ts compare knowledge .c64-re-tools/local/b/image.json --frames 600 --steps .c64-re-tools/local/start-game.json
|
|
33
35
|
node <skill>/scripts/provenance.ts separate --out .c64-re-tools/local/separate.json
|
|
36
|
+
node <skill>/scripts/provenance.ts leftovers --out .c64-re-tools/local/leftovers.json
|
|
34
37
|
```
|
|
35
38
|
|
|
36
39
|
| Option | Meaning |
|
|
37
40
|
|---|---|
|
|
38
41
|
| `--shift 2:-256` | Move release 2 (the first release is 1) by -256 bytes. Use it again for more releases. |
|
|
39
|
-
| `--
|
|
40
|
-
| `--
|
|
41
|
-
| `--
|
|
42
|
-
| `--steps steps/start-game.json` | With `--run`: the action steps of the c64-unpacker steps file to run before the frames, for example a key to start the game. |
|
|
42
|
+
| `--range '$0900-$09ff'` | Compare only the addresses in this range. |
|
|
43
|
+
| `--frames 600` | Run each release for this many frames, and give each listed difference its runtime evidence. |
|
|
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. |
|
|
43
45
|
|
|
44
|
-
The script puts each PRG at its load address. An image has bytes only in its segments. The script compares the addresses where all releases have bytes.
|
|
46
|
+
The script puts each PRG at its load address. An image has bytes only in its segments. The script compares the addresses where all releases have bytes.
|
|
45
47
|
|
|
46
48
|
With `--shift`, the addresses of `segments`, `common` and `differences` are the release addresses plus the `shift` of the release. The `start` and `end` of `runtime` are the addresses in the release itself, without the shift.
|
|
47
49
|
|
|
48
|
-
With `--
|
|
50
|
+
With `--frames`, a PRG starts with autostart and RUN. An image starts from its bytes: the script writes the segments and the entry state from `entry_state.json` into the emulator, and starts at the entry. Watchpoints on each difference stop the run at its first access.
|
|
51
|
+
|
|
52
|
+
### Calling the script
|
|
53
|
+
|
|
54
|
+
- Call a script as `node <skill>/scripts/<script>.ts <command> [subjects] [options]`. The command comes first.
|
|
55
|
+
- `<script>.ts --help` lists the commands. `<script>.ts <command> --help` gives the subjects and the options of one command. The help is JSON: `{"text": "..."}`.
|
|
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
|
+
- A range is `<start>-<end>`, for example `'$c000-$cfff'`. In a JSON file, a range is `start` and `end`.
|
|
58
|
+
- Bytes in a JSON file are hex in a field `data`, for example `"data": "a900"`.
|
|
59
|
+
- 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
|
+
- An error is `{"error": {"code": "...", "message": "..."}}`. The message tells what is wrong and what to do.
|
|
61
|
+
- The exit status is 0 for done or PASS, 1 for FAIL, 2 for an error and 3 for INCONCLUSIVE.
|
|
49
62
|
|
|
50
63
|
### Project folder
|
|
51
64
|
|
|
52
65
|
- Paths are relative to the project folder. The project folder is the nearest folder at or above the working directory that holds `.c64-re-tools/`.
|
|
53
66
|
- If the working directory is not in a project and has projects below it, the script refuses and lists them. Then give `--project <folder>` to each script.
|
|
54
67
|
- Write each file that you make for your work in `.c64-re-tools/local/` of the project. Git ignores that folder. Examples are results, batches, step files, runs, snapshots and memory dumps.
|
|
55
|
-
- Move a file out of `.c64-re-tools/local/` only when it is the evidence for a finding. Move it before you give it to `--evidence
|
|
68
|
+
- Move a file out of `.c64-re-tools/local/` only when it is the evidence for a finding. Move it before you give it to `--evidence test:<file>` or `--evidence file:<file>` of c64-knowledge. These two refuse a file in `.c64-re-tools/local/`.
|
|
69
|
+
- Do not move a coverage map after `coverage` of c64-knowledge imported it. `--evidence coverage:<map>` names the map by its path at the import. The knowledge keeps the coverage and the SHA-256 of the map, so the evidence stays valid.
|
|
56
70
|
- Put a run that is evidence in `runs/<name>/`, with its run file, its results and the snapshot files that it uses. Change the paths in the run file to the new paths. Then git keeps them, and another person can do the run again.
|
|
57
71
|
- A shell redirect (`>`) is relative to the working directory, not to the project folder. From the folder above the project, write `<folder>/.c64-re-tools/local/result.json`.
|
|
58
72
|
|
|
@@ -63,7 +77,7 @@ With `--run`, a PRG starts with autostart and RUN. An image starts from its byte
|
|
|
63
77
|
|
|
64
78
|
`suggestedShifts` has at most 3 shifts for each release. The shift with the most `matchingSequences` is first. Data that did not move and code that moved give different shifts. Try each shift. `current: true` marks the shift of this run.
|
|
65
79
|
3. Read `differences`. Each one has an address range, the bytes of each release, and `groups`: the releases that have the same bytes there.
|
|
66
|
-
4. To know how the program uses the differences, run the script again with `--
|
|
80
|
+
4. To know how the program uses the differences, run the script again with `--frames` and the frames that get the program to the part you examine. Read `runtime` of each difference:
|
|
67
81
|
- `first`: the first access in that release: `read`, `write`, `execute` or `none`. An instruction that reads and then writes the same byte, for example INC, counts as `read`. The operand bytes of an executed instruction count as `execute`, for example the value of `LDA #$03`.
|
|
68
82
|
- `changed`: `true` when the bytes at the end of the run are not the bytes of the release.
|
|
69
83
|
- A range that each release writes before it reads is probably runtime state, not a difference of the releases.
|
|
@@ -85,24 +99,66 @@ Use `separate` when you have only one release. It reads the program, its stages
|
|
|
85
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.
|
|
86
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.
|
|
87
101
|
|
|
88
|
-
Each list has `count` and `listed`. `listed` has the first ranges, at most `--max-listed
|
|
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.
|
|
103
|
+
|
|
104
|
+
## Leftovers
|
|
105
|
+
|
|
106
|
+
Use `leftovers` to find what the program holds in addition to its working code. It reads the program, its stages, the coverage and the code regions from the knowledge, as `separate` does. It gives evidence, not a decision. Each list has `count` and `listed`, as in `separate`. `--out` writes all findings.
|
|
107
|
+
|
|
108
|
+
| List | What it holds |
|
|
109
|
+
|---|---|
|
|
110
|
+
| `text` | Text, and lines of assembler source. `encoding` is `petscii`, `screen`, `ascii` or `high-bit`. `reversed: true` marks screen codes with bit 7 set. `source: true` marks lines of assembler source. A line end in `text` is a new line. `mnemonics` counts 6502 instructions in the text. |
|
|
111
|
+
| `basic` | Two or more BASIC lines away from the BASIC start. `linkedAt` is the address where BASIC linked the lines. It can be another address than the address of the bytes now. |
|
|
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
|
+
| `residue` | Bytes that no run touched and that are no text, BASIC, fill or code region. `bytes` shows the first 16 bytes. |
|
|
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 no call or jump reaches from the entry, and that no run executed. |
|
|
116
|
+
|
|
117
|
+
Text from another computer can show the tools of the developer:
|
|
118
|
+
|
|
119
|
+
- `ascii` is text in ASCII, with small letters. A cross assembler on another computer can leave it.
|
|
120
|
+
- `high-bit` is text with bit 7 set. The Apple II keeps text in this form, with `$8d` at the end of a line. Apple II assembler source, for example from Merlin, has `DFB` lines and `*` comments. A C64 text in shifted letters with `$a0` spaces is also `high-bit`.
|
|
121
|
+
- Data of another computer, for example a table of Atari key codes, is not text. The script does not find it. Examine `residue` and the data that no code reads, and use your knowledge of the other computers.
|
|
122
|
+
|
|
123
|
+
Each finding has `stages`: the unpack stages that wrote its bytes. Each finding also has `access`: the accesses of its bytes in the coverage. A stage before the last stage is a depacker, an intro or a loader.
|
|
124
|
+
|
|
125
|
+
| Option | Meaning |
|
|
126
|
+
|---|---|
|
|
127
|
+
| `--min-text 6` | The minimum number of characters in a text. The default is 8. A short text, for example a group name, can need 6. |
|
|
128
|
+
|
|
129
|
+
Do these steps:
|
|
130
|
+
|
|
131
|
+
1. Unpack the program with `stages` or `capture` of the c64-unpacker skill. Put the capture into the knowledge.
|
|
132
|
+
2. Add the coverage of runs that use many parts of the program. Without coverage, `residue` is empty and no finding has `access`. `noCoverage` tells this.
|
|
133
|
+
3. Run c64-static-analysis. Without code regions, `deadCode` is empty, and `residue` can include code.
|
|
134
|
+
4. Run `leftovers`. When you have two or more releases, also compare them.
|
|
135
|
+
5. Read `text` first: names, dates, greetings and credits. Then read `copies`, `residue`, `basic` and `deadCode`.
|
|
136
|
+
6. Examine each important finding with the c64-emulator skill or the c64-static-analysis skill.
|
|
137
|
+
7. Record each confirmed finding with the c64-knowledge skill. Classify a confirmed PETSCII text as a `petscii` region, and a screen code text as a `screen` region. Classify other text as `bytes`. Write a comment that tells what the leftover is.
|
|
138
|
+
|
|
139
|
+
Read the findings with care:
|
|
140
|
+
|
|
141
|
+
- A text is a run of bytes that looks like text. Some runs are data, for example a character map. A text can include one byte before it or after it that is not part of the text.
|
|
142
|
+
- A copy is not always a leftover. A program can copy its code to another address before it runs the code.
|
|
143
|
+
- A text in an early stage is often a crack intro. A text that no run read can be an old text of an earlier release.
|
|
144
|
+
- Keep the fact apart from the conclusion. "Text at $4770" is a fact. "A recrack by group X" is a conclusion.
|
|
89
145
|
|
|
90
146
|
## Library of routines
|
|
91
147
|
|
|
92
148
|
A library is a folder of routine files. Each file holds one routine: its bytes and its knowledge. Keep one library beside the project folders, for example `library/` at the root of the workspace. A library is not a project folder. Commit it with the projects.
|
|
93
149
|
|
|
94
|
-
`--library` is relative to the
|
|
150
|
+
`--library` is relative to the project folder, as all other paths. For example, `../library` is the folder `library/` beside the project.
|
|
95
151
|
|
|
96
152
|
```
|
|
97
|
-
node <skill>/scripts/provenance.ts routine
|
|
98
|
-
node <skill>/scripts/provenance.ts routine
|
|
99
|
-
node <skill>/scripts/provenance.ts match --library library --out .c64-re-tools/local/match.json --project game-b
|
|
153
|
+
node <skill>/scripts/provenance.ts routine '$1000' --library ../library --project game-a
|
|
154
|
+
node <skill>/scripts/provenance.ts routine '$1000-$1026' --name play --library ../library --project game-a
|
|
155
|
+
node <skill>/scripts/provenance.ts match --library ../library --out .c64-re-tools/local/match.json --project game-b
|
|
100
156
|
```
|
|
101
157
|
|
|
102
158
|
To save a routine:
|
|
103
159
|
|
|
104
160
|
1. Put the program into the knowledge, and name the routine there first. The file keeps the user and LLM names, comments, regions, references and operand expressions in the range. It also keeps the code regions of each origin.
|
|
105
|
-
2. Run `routine` with the start of a module
|
|
161
|
+
2. Run `routine` with the start address of a module, or with a range `<start>-<end>`. A module ends at the next module or at the end of its segment.
|
|
106
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.
|
|
107
163
|
- `--title` is the title of the routine. When you do not give it, the script uses the module title.
|
|
108
164
|
- A routine has at least 16 bytes. Its last instruction must end in the range.
|
|
@@ -114,7 +170,7 @@ To find the routines in a program:
|
|
|
114
170
|
- The opcodes and the other bytes must be equal.
|
|
115
171
|
- An address into the routine must point into the routine at its new address. `shift` is the address in the program minus the address in the library.
|
|
116
172
|
- An operand that addresses something outside the routine can be different. `differing` lists each one with its value in the library and in the program. Examine them. For example, the program can use other variables or another SID voice.
|
|
117
|
-
2. Give `--out` to get a batch for `knowledge.ts apply`. The batch has the names, regions, comments, references and operand expressions of each routine at its addresses in the program. `match` does not change the knowledge.
|
|
173
|
+
2. Give `--out` to get a batch for `knowledge.ts apply`. `batch` shows the file as `{out, bytes, sha256}`. The batch has the names, regions, comments, references and operand expressions of each routine at its addresses in the program. `match` does not change the knowledge.
|
|
118
174
|
3. Read `batch.leftOut` before you apply the batch. The script leaves out a write in these conditions:
|
|
119
175
|
- The program has a user or LLM row at that place.
|
|
120
176
|
- The program uses the name at another address.
|
|
@@ -128,17 +184,17 @@ To find the routines in a program:
|
|
|
128
184
|
Automatic write:
|
|
129
185
|
- none.
|
|
130
186
|
|
|
131
|
-
Record a confirmed conclusion as a normal comment, region or symbol with the c64-knowledge skill. Do not record the full comparison.
|
|
187
|
+
Record a confirmed conclusion as a normal comment, region or symbol with the c64-knowledge skill. Do not record the full comparison or the full list of leftovers.
|
|
132
188
|
|
|
133
189
|
`match --out` writes a batch file and does not change the knowledge. Apply the batch with the c64-knowledge skill.
|
|
134
190
|
|
|
135
191
|
## Result
|
|
136
192
|
|
|
137
|
-
The differing ranges with the bytes of each release and the groups that agree, and the share of identical bytes. `common` gives the ranges where all releases have bytes. `releases` gives the segments and the `shift` of each release. With `--
|
|
193
|
+
The differing ranges with the bytes of each release and the groups that agree, and the share of identical bytes. `common` gives the ranges where all releases have bytes. `releases` gives the segments and the `shift` of each release. With `--frames`, each difference also has `runtime`, and `run` tells if each release started, or why not.
|
|
138
194
|
|
|
139
|
-
|
|
195
|
+
For `leftovers`: the lists of findings, with `entry` and the number of `stages`. `noEntry`, `noStages` and `noCoverage` tell which knowledge is missing.
|
|
140
196
|
|
|
141
|
-
The
|
|
197
|
+
The runtime evidence and the leftovers are evidence only. The script does not decide which bytes are original.
|
|
142
198
|
|
|
143
199
|
## Failure and conflicts
|
|
144
200
|
|
|
@@ -147,7 +203,7 @@ The exit status is 0 for a result. The exit status is 1 for an error. The exit s
|
|
|
147
203
|
- `common` is empty: the releases do not cover the same addresses. Use `--shift`, or compare a range that all releases cover.
|
|
148
204
|
- `run.releases` shows `started: false`: a PRG without a BASIC start, or an image without `entry_state.json`. The reason tells which. The other releases still give evidence.
|
|
149
205
|
- `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 emulator script.
|
|
150
|
-
- `moreDifferences: true`: the script shows only the first differences. Use `--
|
|
206
|
+
- `moreDifferences: true`: the script shows only the first differences. Use `--range` to see the others.
|
|
151
207
|
- `not-found`: the project has no file at that path.
|
|
152
208
|
- `not-found` from `match`: the library folder does not exist, or it has no routine files. Save a routine with `routine` first.
|
|
153
209
|
- `conflict` from `routine`: the library has a routine with that name. Give another `--name`, or give `--replace` to save over it.
|
|
@@ -5,15 +5,12 @@
|
|
|
5
5
|
// releases differ and which releases agree there. It never decides which
|
|
6
6
|
// bytes are original: that needs evidence beyond one comparison.
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
start: number;
|
|
10
|
-
end: number;
|
|
11
|
-
}
|
|
8
|
+
import { type AddressRange, formatC64Address } from "#src/c64.ts";
|
|
12
9
|
|
|
13
10
|
export interface Release {
|
|
14
11
|
name: string;
|
|
15
12
|
/** Where the release has bytes, after the shift, in address order. */
|
|
16
|
-
segments:
|
|
13
|
+
segments: AddressRange[];
|
|
17
14
|
/** The byte at an address inside a segment. */
|
|
18
15
|
byteAt: (address: number) => number;
|
|
19
16
|
}
|
|
@@ -29,7 +26,7 @@ export interface DifferingRange {
|
|
|
29
26
|
|
|
30
27
|
export interface Comparison {
|
|
31
28
|
/** The addresses where every release has bytes. */
|
|
32
|
-
common:
|
|
29
|
+
common: AddressRange[];
|
|
33
30
|
identicalBytes: number;
|
|
34
31
|
differingBytes: number;
|
|
35
32
|
differences: DifferingRange[];
|
|
@@ -43,8 +40,8 @@ const JOIN_GAP = 4;
|
|
|
43
40
|
export const covers = (release: Release, address: number) => release.segments.some((segment) => address >= segment.start && address <= segment.end);
|
|
44
41
|
|
|
45
42
|
/** The ranges where every release has bytes, inside from..to. */
|
|
46
|
-
export function commonRanges(releases: Release[], from = 0x0000, to = 0xffff):
|
|
47
|
-
const ranges:
|
|
43
|
+
export function commonRanges(releases: Release[], from = 0x0000, to = 0xffff): AddressRange[] {
|
|
44
|
+
const ranges: AddressRange[] = [];
|
|
48
45
|
for (let address = from; address <= to; address++) {
|
|
49
46
|
if (!releases.every((release) => covers(release, address))) continue;
|
|
50
47
|
const last = ranges.at(-1);
|
|
@@ -67,7 +64,7 @@ export function compareReleases(releases: Release[], options: { from?: number; t
|
|
|
67
64
|
}
|
|
68
65
|
// A joined range stays inside one common range, so every release has all of its bytes.
|
|
69
66
|
const inSameCommon = (a: number, b: number) => common.some((range) => a >= range.start && b <= range.end);
|
|
70
|
-
const ranges:
|
|
67
|
+
const ranges: AddressRange[] = [];
|
|
71
68
|
for (const address of differing) {
|
|
72
69
|
const last = ranges.at(-1);
|
|
73
70
|
if (last !== undefined && address - last.end <= JOIN_GAP && inSameCommon(last.start, address)) last.end = address;
|
|
@@ -137,9 +134,8 @@ export type ShiftSuggestion = { release: number; shift: number; matchingSequence
|
|
|
137
134
|
*/
|
|
138
135
|
export function shiftSuggestion(releases: Release[], applied: number[], index: number, found: { shift: number; matches: number }): ShiftSuggestion {
|
|
139
136
|
const outside = (release: Release, by: number) => release.segments.find((segment) => segment.start + by < 0 || segment.end + by > 0xffff);
|
|
140
|
-
const
|
|
141
|
-
|
|
142
|
-
`release ${number} moved by ${by} puts ${hex(segment.start)}-${hex(segment.end)} ${segment.start + by < 0 ? "below $0000" : "past $ffff"}`;
|
|
137
|
+
const why = (number: number, by: number, segment: AddressRange) =>
|
|
138
|
+
`release ${number} moved by ${by} puts ${formatC64Address(segment.start)}-${formatC64Address(segment.end)} ${segment.start + by < 0 ? "below $0000" : "past $ffff"}`;
|
|
143
139
|
const own = outside(releases[index]!, found.shift);
|
|
144
140
|
if (own === undefined) return { release: index + 1, shift: applied[index]! + found.shift, matchingSequences: found.matches };
|
|
145
141
|
const reasons = [why(index + 1, found.shift, own)];
|