@henols/c64-re-tools 2.0.0-rc.11 → 2.0.0-rc.13
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 +7 -5
- package/package.json +1 -1
- package/skills/c64-assembler/SKILL.md +11 -2
- package/skills/c64-assembler/scripts/assemble.ts +4 -4
- package/skills/c64-basic/SKILL.md +8 -1
- package/skills/c64-basic/scripts/basic.ts +3 -3
- package/skills/c64-disk/SKILL.md +10 -3
- package/skills/c64-disk/scripts/disk.ts +4 -4
- package/skills/c64-emulator/SKILL.md +4 -0
- package/skills/c64-knowledge/SKILL.md +62 -12
- package/skills/c64-knowledge/scripts/knowledge.ts +352 -102
- package/skills/c64-memory-map/SKILL.md +7 -0
- package/skills/c64-memory-map/scripts/memmap.ts +1 -1
- package/skills/c64-provenance/SKILL.md +56 -3
- package/skills/c64-provenance/scripts/library.ts +584 -0
- package/skills/c64-provenance/scripts/provenance.ts +201 -9
- package/skills/c64-reverse-engineering/SKILL.md +16 -2
- package/skills/c64-source/SKILL.md +38 -7
- package/skills/c64-source/scripts/generate.ts +213 -29
- package/skills/c64-source/scripts/loader.ts +3 -1
- package/skills/c64-source/scripts/opcodes.ts +19 -0
- package/skills/c64-source/scripts/source.ts +387 -35
- package/skills/c64-static-analysis/SKILL.md +11 -2
- package/skills/c64-static-analysis/scripts/analyze.ts +95 -8
- package/skills/c64-static-analysis/scripts/macros.ts +233 -0
- package/skills/c64-testing/SKILL.md +8 -1
- package/skills/c64-testing/references/scenario-format.md +1 -1
- package/skills/c64-testing/scripts/checklist.ts +2 -2
- package/skills/c64-testing/scripts/run.ts +4 -4
- package/skills/c64-testing/scripts/scenario.ts +1 -1
- package/skills/c64-testing/scripts/test.ts +4 -4
- package/skills/c64-unpacker/SKILL.md +11 -4
- package/skills/c64-unpacker/scripts/unpack.ts +4 -4
- package/src/cli/main.ts +5 -3
- package/src/host-client/image.ts +1 -1
- package/src/host-client/transfer.ts +34 -10
- package/src/host-client/vice-session.ts +9 -9
- package/src/knowledge/conflicts.ts +157 -0
- package/src/knowledge/database.ts +150 -38
- package/src/knowledge/history.ts +54 -20
- package/src/knowledge/import.ts +3 -2
- package/src/knowledge/read.ts +169 -20
- package/src/knowledge/schema.ts +115 -72
- package/src/knowledge/text.ts +391 -0
- package/src/knowledge/write.ts +508 -51
- package/src/mcp/output.ts +14 -7
- package/src/mcp/tools/debug.ts +8 -6
- package/src/mcp/tools/media.ts +17 -13
- package/src/mcp/tools/memory.ts +9 -5
- package/src/mcp/tools/video.ts +6 -5
- package/src/platform.ts +28 -5
- package/src/project.ts +166 -3
- package/src/protocol/tools.ts +1 -1
- package/src/script.ts +46 -7
|
@@ -3,27 +3,51 @@
|
|
|
3
3
|
// release is a PRG or an unpacker image.json (D50). With --run, each release
|
|
4
4
|
// runs in its own emulator, and each listed difference gets its first access
|
|
5
5
|
// and whether the run changed it. The separate command gives evidence from
|
|
6
|
-
// one release (D51).
|
|
6
|
+
// one release (D51). The routine and match commands keep routines that several
|
|
7
|
+
// programs share in a library folder and find them in a program (D71).
|
|
7
8
|
|
|
8
|
-
import { posix } from "node:path";
|
|
9
|
+
import { basename, posix, relative } from "node:path";
|
|
9
10
|
import { parseArgs } from "node:util";
|
|
10
11
|
|
|
11
12
|
import { formatC64Address, parseC64Address } from "#src/c64.ts";
|
|
12
13
|
import { readCoverageFiles } from "#src/host-client/coverage.ts";
|
|
14
|
+
import { ImageError, readImage, type Image } from "#src/host-client/image.ts";
|
|
13
15
|
import { checkActionSteps, type ActionStep } from "#src/host-client/steps.ts";
|
|
14
16
|
import { MAX_EXECUTION_COUNT, WireFailure } from "#src/host-client/tools.ts";
|
|
15
17
|
import { readProjectFile, writeProjectFile } from "#src/host-client/transfer.ts";
|
|
16
18
|
import { ViceSessionClient } from "#src/host-client/vice-session.ts";
|
|
17
19
|
import { KnowledgeError, openForRead } from "#src/knowledge/database.ts";
|
|
18
|
-
import {
|
|
19
|
-
|
|
20
|
+
import {
|
|
21
|
+
commentsAt,
|
|
22
|
+
listComments,
|
|
23
|
+
listModules,
|
|
24
|
+
listOperands,
|
|
25
|
+
listReferences,
|
|
26
|
+
listRegions,
|
|
27
|
+
listSymbols,
|
|
28
|
+
operandAt,
|
|
29
|
+
referencesFrom,
|
|
30
|
+
referenceSuppressed,
|
|
31
|
+
regionsOverlapping,
|
|
32
|
+
suppressionsOverlapping,
|
|
33
|
+
symbolAt,
|
|
34
|
+
symbolNamed,
|
|
35
|
+
symbolSuppressed,
|
|
36
|
+
} from "#src/knowledge/read.ts";
|
|
37
|
+
import { CODE_REGION_TYPES, type Origin } from "#src/knowledge/schema.ts";
|
|
38
|
+
import { platformNames } from "#src/platform.ts";
|
|
39
|
+
import { projectRoot } from "#src/project.ts";
|
|
20
40
|
import { runScript, UsageError } from "#src/script.ts";
|
|
21
41
|
import { compareReleases, shiftSuggestion, suggestShifts, type Range, type Release } from "./compare.ts";
|
|
42
|
+
import { findRoutine, libraryFolder, LibraryError, readLibrary, routineFrom, saveRoutine, suggest } from "./library.ts";
|
|
22
43
|
import { watchRelease, type ImageState, type Start } from "./runtime.ts";
|
|
23
44
|
import { separate } from "./separate.ts";
|
|
24
45
|
|
|
25
46
|
const USAGE = `provenance.ts <release> <release> [<release> ...] [options]
|
|
26
47
|
provenance.ts separate <release> [--coverage <file> ...] [--out <file>] [--max-listed <n>]
|
|
48
|
+
provenance.ts routine <image> --library <folder> (--module <address> | --start <address> --end <address>)
|
|
49
|
+
[--name <name>] [--title <text>] [--replace]
|
|
50
|
+
provenance.ts match <image> --library <folder> [--out <file>] [--max-listed <n>]
|
|
27
51
|
|
|
28
52
|
--shift <n>:<bytes> move release n (1 = the first) by a number of bytes, for example 2:-256
|
|
29
53
|
--from <address> compare from this address
|
|
@@ -38,9 +62,22 @@ jump references in knowledge reach each code region from the entry, and what the
|
|
|
38
62
|
program reads or executes outside the image. --out writes all ranges to a file;
|
|
39
63
|
the result lists at most --max-listed of each list (default 50) and its count.
|
|
40
64
|
|
|
65
|
+
routine saves a routine of the project to a library: a plain folder of routine
|
|
66
|
+
files, beside the projects (--library, relative to the working directory, as
|
|
67
|
+
--project). It takes a module (D48) of the knowledge, or --start and --end. Its
|
|
68
|
+
code regions give the instructions; the user and LLM names, regions, comments,
|
|
69
|
+
references and operand expressions go into the file at offsets. --name defaults
|
|
70
|
+
to the user or LLM name at the start, --title to the module title.
|
|
71
|
+
|
|
72
|
+
match finds each routine of the library in an image: the opcodes and other
|
|
73
|
+
fixed bytes equal, and the addresses into the routine moved by one shift.
|
|
74
|
+
Operands that address something outside the routine may differ; the result
|
|
75
|
+
lists them. --out writes the names of the routines at the found addresses as a
|
|
76
|
+
batch for knowledge.ts apply, and the result lists what it left out.
|
|
77
|
+
|
|
41
78
|
A release is a PRG, placed at its load address, or an image.json of the
|
|
42
79
|
c64-unpacker skill, compared inside its segments. Paths are relative to the
|
|
43
|
-
project
|
|
80
|
+
project folder. The result is one JSON object.`;
|
|
44
81
|
|
|
45
82
|
function address(text: string, option: string): number {
|
|
46
83
|
try {
|
|
@@ -158,17 +195,38 @@ async function run(argv: string[]): Promise<unknown> {
|
|
|
158
195
|
coverage: { type: "string", multiple: true },
|
|
159
196
|
out: { type: "string" },
|
|
160
197
|
"max-listed": { type: "string" },
|
|
198
|
+
library: { type: "string" },
|
|
199
|
+
module: { type: "string" },
|
|
200
|
+
start: { type: "string" },
|
|
201
|
+
end: { type: "string" },
|
|
202
|
+
name: { type: "string" },
|
|
203
|
+
title: { type: "string" },
|
|
204
|
+
replace: { type: "boolean" },
|
|
161
205
|
help: { type: "boolean" },
|
|
162
206
|
},
|
|
163
207
|
});
|
|
164
208
|
if (values.help) throw new UsageError("");
|
|
209
|
+
/** Refuses the options that `command` does not take. */
|
|
210
|
+
const only = (command: string, allowed: readonly string[]) => {
|
|
211
|
+
const used = Object.keys(values).filter((option) => option !== "help" && !allowed.includes(option));
|
|
212
|
+
if (used.length > 0) throw new UsageError(`${command} does not take --${used.join(", --")}`);
|
|
213
|
+
};
|
|
165
214
|
if (positionals[0] === "separate") {
|
|
166
|
-
|
|
167
|
-
if (used.length > 0) throw new UsageError(`separate does not take --${used.join(", --")}`);
|
|
215
|
+
only("separate", ["coverage", "out", "max-listed"]);
|
|
168
216
|
if (positionals.length !== 2) throw new UsageError("separate needs one release");
|
|
169
217
|
return separateCommand(positionals[1]!, values.coverage ?? [], values.out, values["max-listed"]);
|
|
170
218
|
}
|
|
171
|
-
if (
|
|
219
|
+
if (positionals[0] === "routine") {
|
|
220
|
+
only("routine", ["library", "module", "start", "end", "name", "title", "replace"]);
|
|
221
|
+
if (positionals.length !== 2) throw new UsageError("routine needs one image");
|
|
222
|
+
return routineCommand(positionals[1]!, values);
|
|
223
|
+
}
|
|
224
|
+
if (positionals[0] === "match") {
|
|
225
|
+
only("match", ["library", "out", "max-listed"]);
|
|
226
|
+
if (positionals.length !== 2) throw new UsageError("match needs one image");
|
|
227
|
+
return matchCommand(positionals[1]!, values.library, values.out, values["max-listed"]);
|
|
228
|
+
}
|
|
229
|
+
only("a comparison", ["shift", "from", "to", "run", "steps"]);
|
|
172
230
|
if (positionals.length < 2) throw new UsageError("give two or more releases");
|
|
173
231
|
const shifts = new Map<number, number>();
|
|
174
232
|
for (const text of values.shift ?? []) {
|
|
@@ -331,4 +389,138 @@ function separateCommand(path: string, coveragePaths: string[], out: string | un
|
|
|
331
389
|
};
|
|
332
390
|
}
|
|
333
391
|
|
|
334
|
-
|
|
392
|
+
const SEMANTIC: readonly Origin[] = ["user", "llm"];
|
|
393
|
+
|
|
394
|
+
function imageOf(path: string): Image {
|
|
395
|
+
try {
|
|
396
|
+
return readImage(path);
|
|
397
|
+
} catch (error) {
|
|
398
|
+
if (error instanceof ImageError) throw new UsageError(error.message);
|
|
399
|
+
throw error;
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
const platform = () => new Map(platformNames().map((entry) => [entry.name, entry.address]));
|
|
404
|
+
|
|
405
|
+
/** routine: saves a module or a range of the project, with its knowledge, as a routine of the library (D71). */
|
|
406
|
+
function routineCommand(path: string, values: { library?: string; module?: string; start?: string; end?: string; name?: string; title?: string; replace?: boolean }): unknown {
|
|
407
|
+
if (values.library === undefined) throw new UsageError("routine needs --library <folder>");
|
|
408
|
+
if (values.module !== undefined && (values.start !== undefined || values.end !== undefined)) throw new UsageError("routine takes --module, or --start and --end, not both");
|
|
409
|
+
if (values.module === undefined && values.start === undefined && values.end === undefined) throw new UsageError("routine needs --module <address>, or --start and --end");
|
|
410
|
+
if (values.module === undefined && (values.start === undefined || values.end === undefined)) throw new UsageError("routine needs both --start and --end");
|
|
411
|
+
const image = imageOf(path);
|
|
412
|
+
const segmentOf = (at: number) => image.segments.find((segment) => at >= segment.start && at <= segment.end);
|
|
413
|
+
const db = openForRead();
|
|
414
|
+
try {
|
|
415
|
+
let start: number;
|
|
416
|
+
let end: number;
|
|
417
|
+
let title = values.title;
|
|
418
|
+
if (values.module !== undefined) {
|
|
419
|
+
start = address(values.module, "--module");
|
|
420
|
+
const modules = listModules(db);
|
|
421
|
+
const module = modules.find((item) => item.start === start);
|
|
422
|
+
if (module === undefined) throw new LibraryError("not-found", `No module starts at ${formatC64Address(start)}. List the modules with knowledge.ts modules, or give --start and --end.`);
|
|
423
|
+
const segment = segmentOf(start);
|
|
424
|
+
if (segment === undefined) throw new UsageError(`The module at ${formatC64Address(start)} is outside the segments of ${path}.`);
|
|
425
|
+
end = (modules.find((item) => item.start > start && item.start <= segment.end)?.start ?? segment.end + 1) - 1;
|
|
426
|
+
title ??= module.title;
|
|
427
|
+
} else {
|
|
428
|
+
start = address(values.start!, "--start");
|
|
429
|
+
end = address(values.end!, "--end");
|
|
430
|
+
if (end < start) throw new UsageError("--end is before --start");
|
|
431
|
+
if (segmentOf(start) === undefined || segmentOf(start) !== segmentOf(end)) throw new UsageError(`${formatC64Address(start)}-${formatC64Address(end)} is not inside one segment of ${path}.`);
|
|
432
|
+
}
|
|
433
|
+
const range = { start, end };
|
|
434
|
+
const name = values.name ?? listSymbols(db, { start, end: start }, undefined, SEMANTIC)[0]?.name;
|
|
435
|
+
if (name === undefined) throw new UsageError(`The knowledge has no user or LLM name at ${formatC64Address(start)}: give --name.`);
|
|
436
|
+
const { routine, leftOut } = routineFrom({
|
|
437
|
+
name,
|
|
438
|
+
...(title === undefined ? {} : { title }),
|
|
439
|
+
program: basename(projectRoot()),
|
|
440
|
+
image: path,
|
|
441
|
+
start,
|
|
442
|
+
end,
|
|
443
|
+
byteAt: image.byteAt,
|
|
444
|
+
analyzed: listRegions(db, range),
|
|
445
|
+
symbols: listSymbols(db, range, undefined, SEMANTIC),
|
|
446
|
+
regions: listRegions(db, range, undefined, SEMANTIC),
|
|
447
|
+
comments: listComments(db, range, SEMANTIC),
|
|
448
|
+
references: listReferences(db, range, undefined, SEMANTIC),
|
|
449
|
+
operands: listOperands(db, range, SEMANTIC),
|
|
450
|
+
platform: platform(),
|
|
451
|
+
});
|
|
452
|
+
const file = saveRoutine(libraryFolder(values.library, { create: true }), routine, values.replace === true);
|
|
453
|
+
return {
|
|
454
|
+
routine: routine.name,
|
|
455
|
+
file: relative(process.cwd(), file),
|
|
456
|
+
start: formatC64Address(start),
|
|
457
|
+
end: formatC64Address(end),
|
|
458
|
+
length: routine.bytes.length,
|
|
459
|
+
instructions: routine.instructions.length,
|
|
460
|
+
relocations: routine.relocations.length,
|
|
461
|
+
variable: routine.variable.length,
|
|
462
|
+
counts: { symbols: routine.symbols.length, regions: routine.regions.length, comments: routine.comments.length, references: routine.references.length, operands: routine.operands.length },
|
|
463
|
+
...(leftOut.length === 0 ? {} : { leftOut }),
|
|
464
|
+
};
|
|
465
|
+
} finally {
|
|
466
|
+
db?.close();
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
/** match: finds the routines of the library in an image, and with --out writes their names there as an apply batch (D71). */
|
|
471
|
+
function matchCommand(path: string, library: string | undefined, out: string | undefined, maxListed: string | undefined): unknown {
|
|
472
|
+
if (library === undefined) throw new UsageError("match needs --library <folder>");
|
|
473
|
+
const max = maxListed ?? "50";
|
|
474
|
+
if (!/^\d{1,5}$/.test(max)) throw new UsageError(`--max-listed must be a whole number, not ${max}`);
|
|
475
|
+
const listed = <T>(items: T[]) => ({ count: items.length, listed: items.slice(0, Number(max)) });
|
|
476
|
+
const routines = readLibrary(libraryFolder(library, { create: false }));
|
|
477
|
+
if (routines.length === 0) throw new LibraryError("not-found", `The library ${library} has no routine files. Save a routine with routine first.`);
|
|
478
|
+
const image = imageOf(path);
|
|
479
|
+
const found = routines.map((routine) => ({ routine, matches: findRoutine(routine, image) }));
|
|
480
|
+
const show = (value: number, size: 1 | 2) => `$${value.toString(16).padStart(2 * size, "0")}`;
|
|
481
|
+
const matches = found
|
|
482
|
+
.flatMap(({ routine, matches: each }) =>
|
|
483
|
+
each.map((match) => ({
|
|
484
|
+
routine: match.routine,
|
|
485
|
+
start: formatC64Address(match.start),
|
|
486
|
+
end: formatC64Address(match.start + routine.bytes.length - 1),
|
|
487
|
+
shift: match.shift,
|
|
488
|
+
differing: match.differing.map((operand) => ({ address: formatC64Address(operand.address), library: show(operand.library, operand.size), program: show(operand.program, operand.size) })),
|
|
489
|
+
})),
|
|
490
|
+
)
|
|
491
|
+
.sort((a, b) => a.start.localeCompare(b.start));
|
|
492
|
+
const result: Record<string, unknown> = { library, routines: routines.length, matches: listed(matches) };
|
|
493
|
+
if (out === undefined) return result;
|
|
494
|
+
const db = openForRead();
|
|
495
|
+
let suggestions: ReturnType<typeof suggest>;
|
|
496
|
+
try {
|
|
497
|
+
suggestions = suggest(
|
|
498
|
+
found,
|
|
499
|
+
{
|
|
500
|
+
symbolAt: (at) => symbolAt(db, at),
|
|
501
|
+
symbolNamed: (name) => symbolNamed(db, name),
|
|
502
|
+
regionsOverlapping: (start, end) => regionsOverlapping(db, start, end),
|
|
503
|
+
commentsAt: (at) => commentsAt(db, at),
|
|
504
|
+
referencesFrom: (at) => referencesFrom(db, at),
|
|
505
|
+
operandAt: (at) => operandAt(db, at),
|
|
506
|
+
symbolSuppressed: (at) => symbolSuppressed(db, at),
|
|
507
|
+
regionSuppressed: (start, end) => suppressionsOverlapping(db, "region", start, end).length > 0,
|
|
508
|
+
referenceSuppressed: (reference) => referenceSuppressed(db, reference),
|
|
509
|
+
},
|
|
510
|
+
image,
|
|
511
|
+
platform(),
|
|
512
|
+
);
|
|
513
|
+
} finally {
|
|
514
|
+
db?.close();
|
|
515
|
+
}
|
|
516
|
+
const { batch, known, leftOut } = suggestions;
|
|
517
|
+
writeProjectFile(out, Buffer.from(batch.length === 0 ? "[]\n" : `[\n${batch.map((write) => ` ${JSON.stringify(write)}`).join(",\n")}\n]\n`), { what: "--out" });
|
|
518
|
+
const operands = batch.some((write) => write.op === "operand");
|
|
519
|
+
return {
|
|
520
|
+
...result,
|
|
521
|
+
batch: { out, writes: batch.length, known, leftOut: listed(leftOut) },
|
|
522
|
+
...(batch.length === 0 ? {} : { apply: `knowledge.ts apply ${out}${operands ? ` --image ${path}` : ""} --reason "names of library routines"` }),
|
|
523
|
+
};
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
await runScript((argv) => run(argv), { usage: USAGE, refusals: [KnowledgeError, LibraryError] });
|
|
@@ -26,6 +26,15 @@ This skill does not do the specialist work itself.
|
|
|
26
26
|
|
|
27
27
|
This skill uses the other skills. It does not use the host runtime or the emulator directly.
|
|
28
28
|
|
|
29
|
+
### Project folder
|
|
30
|
+
|
|
31
|
+
One repository can hold several programs, with one project folder for each program. A project folder holds `.c64-re-tools/`. Work on one project at a time:
|
|
32
|
+
|
|
33
|
+
- From the root of the repository, give `--project <folder>` to each skill script, and `project` to each emulator tool with a path.
|
|
34
|
+
- Write all files in the project folder. Do not write files at the root of the repository.
|
|
35
|
+
- Keep the results, batches and step files that you save for yourself in `.c64-re-tools/local/` of the project. Git ignores that folder.
|
|
36
|
+
- Write the knowledge only with the c64-knowledge script. Do not write or edit the files in `.c64-re-tools/knowledge/` yourself.
|
|
37
|
+
|
|
29
38
|
## Workflow
|
|
30
39
|
|
|
31
40
|
Select the path for the scope:
|
|
@@ -48,6 +57,7 @@ Select the path for the scope:
|
|
|
48
57
|
| The routines, code, data and references of the program | c64-static-analysis |
|
|
49
58
|
| The platform meaning of an address or a register | c64-memory-map |
|
|
50
59
|
| Differences between releases of the program | c64-provenance |
|
|
60
|
+
| A routine that another program of the workspace has, for example a music driver | c64-provenance, with the library |
|
|
51
61
|
| Source code from the knowledge | c64-source |
|
|
52
62
|
| Source code to build | c64-assembler |
|
|
53
63
|
| Proof that a rebuild behaves as the original | c64-testing |
|
|
@@ -68,16 +78,18 @@ Do the stages in this sequence. Go to the next stage only when its exit check is
|
|
|
68
78
|
| 4. Unpack | c64-unpacker: examine the program. Capture a packed program into `unpacked/`. | `image.json` and `entry_state.json` exist, or the evidence shows no packer. |
|
|
69
79
|
| 5. Run | c64-emulator and the run of c64-testing: run each part that the scope names. Write a coverage file for each part into `coverage/`. | Each part that the scope names has a coverage file. |
|
|
70
80
|
| 6. Analyze | c64-static-analysis with `--coverage` for each coverage file. | No coverage conflict is open. |
|
|
71
|
-
| 7. Name | c64-knowledge and c64-memory-map: name each routine in the scope and give it a comment. Record the modules. | Each routine in the scope has a name and a comment. |
|
|
81
|
+
| 7. Name | c64-provenance: if the workspace has a library, find its routines with `match` and apply the batch. Then c64-knowledge and c64-memory-map: name each routine in the scope and give it a comment. Record the modules. | Each routine in the scope has a name and a comment. |
|
|
72
82
|
| 8. Generate | c64-source with `--coverage` into `src/`. For an unpacked program, add `--loader`. | `check.identical` is true. For an unpacked program, `loader.verified` is true. |
|
|
73
83
|
| 9. Build and test | c64-assembler with `--compare` into `build/`. c64-testing scenarios in `tests/`. | The criteria of stage 1 are true. |
|
|
74
84
|
| 10. Report | Write `report.md`. | The report has all its sections. |
|
|
75
85
|
|
|
76
86
|
In stage 5, if you cannot get to a part of the program, ask the user how to get to it. Do stages 7 and 8 again until the source is clear. Each run of c64-source writes the source again.
|
|
77
87
|
|
|
88
|
+
After stage 7, save each routine that other programs can have to the library with c64-provenance `routine`. Examples are a music driver, a decruncher and a loader. Ask the user before you make a library.
|
|
89
|
+
|
|
78
90
|
### Deliverables of a full rebuild
|
|
79
91
|
|
|
80
|
-
- `.c64-re-tools/knowledge
|
|
92
|
+
- `.c64-re-tools/knowledge/` with the names, regions, comments and modules
|
|
81
93
|
- the image and the entry state
|
|
82
94
|
- the coverage files
|
|
83
95
|
- the generated source
|
|
@@ -113,6 +125,8 @@ report.md the report
|
|
|
113
125
|
|
|
114
126
|
If the project has a layout, use that layout.
|
|
115
127
|
|
|
128
|
+
A workspace with several projects keeps its library of routines in `library/` beside the projects. The library is not a project folder.
|
|
129
|
+
|
|
116
130
|
## Knowledge
|
|
117
131
|
|
|
118
132
|
Read:
|
|
@@ -7,14 +7,14 @@ description: Use this skill to generate ACME source code for a C64 program from
|
|
|
7
7
|
|
|
8
8
|
## Purpose
|
|
9
9
|
|
|
10
|
-
Generate ACME source from an image and the knowledge. The same knowledge gives the same source. Make the source better through the knowledge: names, comments, regions and
|
|
10
|
+
Generate ACME source from an image and the knowledge. The same knowledge gives the same source. Make the source better through the knowledge: names, comments, regions, modules and macros. Change the program through patches. Then generate again.
|
|
11
11
|
|
|
12
12
|
This skill does not change the knowledge.
|
|
13
13
|
|
|
14
14
|
## Inputs
|
|
15
15
|
|
|
16
|
-
- The image: an `image.json` from c64-unpacker capture, or a PRG. The path is relative to the project
|
|
17
|
-
- The output directory for the source files, relative to the project
|
|
16
|
+
- The image: an `image.json` from c64-unpacker capture, or a PRG. The path is relative to the project folder.
|
|
17
|
+
- The output directory for the source files, relative to the project folder.
|
|
18
18
|
- Optional entry points for Ghidra.
|
|
19
19
|
- For the loader: the `entry_state.json` next to `image.json`.
|
|
20
20
|
|
|
@@ -36,6 +36,13 @@ node <skill>/scripts/generate.ts unpacked/image.json --out src --loader
|
|
|
36
36
|
| `--space drive8` | Generate the source of drive code or a cartridge bank from the knowledge of that space. `--coverage` and `--loader` are only for `c64`, and platform names are only for `c64`. |
|
|
37
37
|
| `--loader` | Also write `loader.asm` and `loader.prg`. This needs `image.json` with an entry, and `entry_state.json`. |
|
|
38
38
|
|
|
39
|
+
### Project folder
|
|
40
|
+
|
|
41
|
+
- 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/`.
|
|
42
|
+
- 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.
|
|
43
|
+
- Keep the results, batches and step files that you save for yourself in `.c64-re-tools/local/` of the project. Git ignores that folder.
|
|
44
|
+
- 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`.
|
|
45
|
+
|
|
39
46
|
## Workflow
|
|
40
47
|
|
|
41
48
|
1. Get the image. For a packed program, use c64-unpacker capture. For a program without a packer, use the PRG.
|
|
@@ -51,7 +58,8 @@ node <skill>/scripts/generate.ts unpacked/image.json --out src --loader
|
|
|
51
58
|
|
|
52
59
|
- `main.asm` sets the address of each segment and includes the files in address order.
|
|
53
60
|
- Each module in the knowledge is one file. A module starts at its address and stops at the next module or at the end of its segment. Without modules, each segment is one file, `segment_xxxx.asm`.
|
|
54
|
-
- `constants.asm` holds the names of addresses outside the image, for example `EXTCOL = $d020`.
|
|
61
|
+
- `constants.asm` holds the names of addresses outside the image, for example `EXTCOL = $d020`. The file puts the names in groups by memory area, for example zero page, VIC-II registers or CIA 1 registers. Each group is in address order. Drive code and banks have one group only. The side comment of a name tells what the address is. This text is the knowledge comment of the address. Without a comment, the text is the platform meaning. When the knowledge name is not the platform name, the platform name comes first. For an I/O mirror without a meaning, the text gives the register that the mirror repeats. Then `Used:` counts how the source uses the name. The kinds are `read`, `written`, `modified`, `called`, `jumped to`, `jumped through`, `branched to`, `pointer`, `address`, `table` and `macro argument`. `address` is an immediate operand expression. `table` is an entry in a table of addresses.
|
|
62
|
+
- The top of `constants.asm` sets each switch to 0, for example `!ifndef CRACK { CRACK = 0 }`. A build that defines the switch as 1 uses the patches of that switch.
|
|
55
63
|
- A label or a constant gets the name of the address in the knowledge. Without a knowledge name, it gets the platform name of the c64-memory-map skill, for example `EXTCOL` or `CHROUT`. A platform name wins over a name from an analyzer (`dxa`, `ghidra` or `heuristic`), for example `EXTCOL` over `ld020`. A `user` or `llm` name wins over a platform name. An address in the image without a name gets `l_xxxx`.
|
|
56
64
|
- Code regions, and the instructions that Ghidra decodes outside of data regions, become instructions.
|
|
57
65
|
- Data regions become data by their type: `!byte`, `!word`, `!pet`, `!scr`, `!fill`, tables of labels, and sprite bytes in binary.
|
|
@@ -63,9 +71,24 @@ node <skill>/scripts/generate.ts unpacked/image.json --out src --loader
|
|
|
63
71
|
- `!byte` with an instruction as its comment: ACME writes this instruction with a different opcode. The bytes keep the source identical.
|
|
64
72
|
- `not decoded` marks bytes in a code region that Ghidra did not decode.
|
|
65
73
|
- `; not executed $xxxx-$yyyy` starts code that no run of the `--coverage` files executed. The range is the same as in `not_executed.json`.
|
|
74
|
+
- `+inc16 ptr` is a use of a macro of the knowledge. `macros.asm` holds the macros. `main.asm` and `loader.asm` include it. The comments inside a use go to its side comment.
|
|
75
|
+
- `!zone patch_CRACK_100e` starts a patch of the knowledge. `!if CRACK` has the code of the patch and NOP to the end of the range. `else` has the original code. The labels inside the range come before the zone as `name = * + n`. Thus both builds have them.
|
|
66
76
|
|
|
67
77
|
Do not edit the generated files. The next run writes over them.
|
|
68
78
|
|
|
79
|
+
## Switches and macros
|
|
80
|
+
|
|
81
|
+
A switch changes the program only when the build defines it. Thus the source without a define stays identical to the image.
|
|
82
|
+
|
|
83
|
+
1. The script checks the default build with `check`.
|
|
84
|
+
2. Then it builds each switch on its own, with the switch as 1. It does not build two switches together.
|
|
85
|
+
3. The build of a switch must have the same length as the default build. It can change bytes only in the ranges of its patches.
|
|
86
|
+
4. Build the program with a switch on with the c64-assembler skill: `--define CRACK=1`.
|
|
87
|
+
|
|
88
|
+
The script writes a patch only when its range is complete items of one file. A patch cannot cut an instruction. Its switch name cannot be a name of the source.
|
|
89
|
+
|
|
90
|
+
The script writes a macro use only when the use gives the bytes of the image. First it assembles each use alone at its address. Then it checks the full build. A use that fails stays instructions, and `macrosAsInstructions` gives the reason. A label inside the range of a use also keeps the instructions, because the macro line has no place for the label.
|
|
91
|
+
|
|
69
92
|
## The loader
|
|
70
93
|
|
|
71
94
|
`--loader` writes `loader.asm` and assembles it to `loader.prg`. The loader does these steps:
|
|
@@ -86,7 +109,7 @@ The script simulates the copies first. Then it runs `loader.prg` in the emulator
|
|
|
86
109
|
## Knowledge
|
|
87
110
|
|
|
88
111
|
Read:
|
|
89
|
-
- symbols, regions, comments, references, modules
|
|
112
|
+
- symbols, regions, comments, references, modules, operand expressions, patches, macros and macro uses of all origins.
|
|
90
113
|
|
|
91
114
|
Automatic write:
|
|
92
115
|
- none.
|
|
@@ -104,14 +127,18 @@ One JSON object:
|
|
|
104
127
|
- `entriesInData`: each `--entry` in a data region of the knowledge, with the `region` and its `origin`. Ghidra does not decode an entry in a data region.
|
|
105
128
|
- `asBytes`: the instructions that the source has as bytes.
|
|
106
129
|
- `operandExpressions`: the count of instructions that the source has with an operand expression.
|
|
107
|
-
- `operandsAsNumbers`: the operand expressions that the source has as a number with a side comment, each with its `address`, `expression` and `reason`. An expression becomes a number when no instruction with an operand value is at the address, or when a name has no address. An expression that ACME refuses or does not assemble to the image also becomes a number. Then the script assembles the source again, at most
|
|
130
|
+
- `operandsAsNumbers`: the operand expressions that the source has as a number with a side comment, each with its `address`, `expression` and `reason`. An expression becomes a number when no instruction with an operand value is at the address, or when a name has no address. An expression that ACME refuses or does not assemble to the image also becomes a number. Then the script assembles the source again, at most six times.
|
|
108
131
|
- `unusedModules`: modules in the knowledge whose start is in no segment of the image. Often this tells that the image does not hold code that the knowledge describes.
|
|
109
132
|
- `entryOutside`: the entry of `image.json` is in no segment of the image, with a `reason`. The image does not hold the program.
|
|
110
133
|
- `notExecuted`: with `--coverage`, the count of instructions that no run executed, the count of their ranges, and the first ranges. `all` names the file `not_executed.json` in `--out`. It has all of the ranges. Put all of these ranges in the report.
|
|
134
|
+
- `macroUses`: the count of macro uses that the source has.
|
|
135
|
+
- `macrosAsInstructions`: the macro uses that the source has as instructions, each with its `start`, `end`, `macro` and `reason`.
|
|
136
|
+
- `patchesNotWritten`: the patches that the source does not have, each with its range, `switch` and `reason`.
|
|
111
137
|
- `check`: `identical`, and the result for each segment. A different segment gives its first different address.
|
|
138
|
+
- `switches`: the build of each switch. Each item has the `define` for the c64-assembler skill, the patches and the `result` of the build with the switch on. `fits` tells that the build is good, and `changedBytes` counts the changed bytes. `overflow` tells that the code of a patch is longer than its range. `changes outside` gives the first byte outside the ranges that changed. `does not assemble` gives the diagnostics of ACME. `insideNames` lists the labels inside a patch. Code that writes to one of these labels changes the patch when the switch is on.
|
|
112
139
|
- `loader`: the program, the copies in their order, the copier page, `stackByte`, `verified` and `differences`. `limits` lists the CIA values that the loader cannot set. When the script cannot make the loader, `loader` has `verified: false` and a `reason`. The loader includes the source. Thus a source that does not assemble to the image also stops the loader.
|
|
113
140
|
|
|
114
|
-
The exit status is 0 when the source is identical and the emulator check of the loader passes. The exit status is 1 for a failed check, for an entry in no segment, and for an error. The exit status is 2 for a wrong argument.
|
|
141
|
+
The exit status is 0 when the source is identical and the emulator check of the loader passes. The exit status is 1 for a failed check, for an entry in no segment, and for an error. A patch that the source does not have, or a switch that does not fit, also gives 1. The exit status is 2 for a wrong argument.
|
|
115
142
|
|
|
116
143
|
## Failure and conflicts
|
|
117
144
|
|
|
@@ -119,6 +146,10 @@ The exit status is 0 when the source is identical and the emulator check of the
|
|
|
119
146
|
- `check.identical: false`: the script has a defect. Give the result to the user.
|
|
120
147
|
- `operandsAsNumbers`: read each `reason`. Correct the expression with `operand` of the c64-knowledge skill, or remove it with `unoperand`.
|
|
121
148
|
- `invalid-input` that names a symbol or a module: change that symbol or module in the knowledge.
|
|
149
|
+
- `switches` with `overflow`: make the code of the patch shorter, or make the range of the patch larger, with `patch` of the c64-knowledge skill.
|
|
150
|
+
- `switches` with `does not assemble`: read the diagnostics and correct the code of the patch.
|
|
151
|
+
- `patchesNotWritten`: read each `reason`. Move the start or the end of the patch to an instruction, or give the switch another name.
|
|
152
|
+
- `macrosAsInstructions`: read each `reason`. Correct the macro or the args, or remove the use with `unuse`. The source stays identical, so this is not a failure.
|
|
122
153
|
- `entryOutside`: the run fails, but the script still writes and checks the source. With `--loader`, `loader.verified` is `false` with the same `reason`. Capture the image again with c64-unpacker `loads` or `capture`. Make sure that a segment holds the entry.
|
|
123
154
|
- `entriesInData`: an analyzer region of data hides the code at the entry. If the entry is code, record a code region at the entry with the c64-knowledge skill, for example `classify '$0500' '$05ff' code`. A region of the user or the LLM replaces the analyzer region. Then generate again.
|
|
124
155
|
- `loader.verified: false` with a `reason`: the script did not make the loader. The script still writes and checks the source. When no copy order or no free pages exist, or the loader needs too many copies, the reason gives the ranges. Capture the image again with fewer segments, or write the loader by hand.
|