@henols/c64-re-tools 2.1.0-rc.1 → 2.1.0
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 +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 +33 -24
- package/skills/c64-provenance/scripts/lib/compare.ts +8 -12
- package/skills/c64-provenance/scripts/lib/leftovers.ts +19 -20
- 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 +177 -279
- package/skills/c64-reverse-engineering/SKILL.md +13 -12
- 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 +48 -32
- package/skills/c64-testing/references/recording-format.md +48 -19
- 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 +244 -54
- package/skills/c64-testing/scripts/lib/recording.ts +153 -120
- package/skills/c64-testing/scripts/{replay.ts → lib/replay.ts} +28 -86
- 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 +1 -1
- 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 +2 -2
- 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/record.ts +0 -96
- package/skills/c64-testing/scripts/test.ts +0 -65
|
@@ -19,47 +19,64 @@ This skill does not change project knowledge.
|
|
|
19
19
|
## Tools / execution path
|
|
20
20
|
|
|
21
21
|
```
|
|
22
|
-
node <skill>/scripts/
|
|
23
|
-
node <skill>/scripts/
|
|
24
|
-
node <skill>/scripts/
|
|
25
|
-
node <skill>/scripts/
|
|
26
|
-
node <skill>/scripts/
|
|
22
|
+
node <skill>/scripts/unpacker.ts inspect original/game.prg
|
|
23
|
+
node <skill>/scripts/unpacker.ts trace original/game.prg
|
|
24
|
+
node <skill>/scripts/unpacker.ts stages original/game.prg --steps .c64-re-tools/local/input.json
|
|
25
|
+
node <skill>/scripts/unpacker.ts capture original/game.prg --until '$c000' --out .c64-re-tools/local/capture
|
|
26
|
+
node <skill>/scripts/unpacker.ts loads original/game.d64 --steps .c64-re-tools/local/parts.json --out .c64-re-tools/local/parts --drive
|
|
27
27
|
```
|
|
28
28
|
|
|
29
|
-
`inspect` examines the file locally. `trace` and `
|
|
29
|
+
`inspect` examines the file locally. `trace`, `stages`, `capture` and `loads` start their own emulator through the host runtime and start the program. `trace` runs the program and finds the memory that the program wrote and then executed. `stages` runs the program and finds each stage: a decruncher, an intro, a copier, and then the program. For each stage it gives the entry, the frames, the written memory (`wrote`) and the entry of the next stage (`next`). `capture` stops the program when the CPU gets to the `--until` address. In `stages` and `capture`, a `frames` step and a `runUntil` step to an address without a condition count as run time. `stages` refuses a different `runUntil` step.
|
|
30
30
|
|
|
31
31
|
| Option | Meaning |
|
|
32
32
|
|---|---|
|
|
33
|
-
| `--until '$c000'` |
|
|
34
|
-
| `--out .c64-re-tools/local/capture` |
|
|
35
|
-
| `--range '$c000
|
|
33
|
+
| `--until '$c000'` | `capture`: the address where the unpacked program starts. The capture stops there after the steps. To capture a later arrival at the address, add a `runUntil` step to an address after the first arrival. |
|
|
34
|
+
| `--out .c64-re-tools/local/capture` | `capture` and `loads`: the folder for the result, relative to the project folder. |
|
|
35
|
+
| `--range '$c000-$cfff'` | `capture` only: write this range as the one segment. Without it, the segments are the blocks that the stages wrote. |
|
|
36
36
|
| `--exclude '$a000-$a9ff'` | `capture` only: remove this range from the segments. You can give it more than once. `image.json` lists the ranges in `excluded`. The entry must not be in the range. |
|
|
37
|
-
| `--timeout-frames 3000` |
|
|
38
|
-
| `--frames 3000` | `trace`
|
|
37
|
+
| `--timeout-frames 3000` | `capture` only: how many frames the program can run before the capture stops (default 3000). |
|
|
38
|
+
| `--frames 3000` | `trace` and `stages`: how many frames the program runs (default 3000). |
|
|
39
39
|
| `--entry '$c000'` | `trace`, `stages` and `capture`: start the program at this address. Without it, the script types RUN. A file that is not a BASIC program at `$0801` needs it. |
|
|
40
|
-
| `--steps input.json` | Input for the program, right after it starts: a JSON list of steps. Use it when an intro waits for a key or a menu waits for a choice.
|
|
40
|
+
| `--steps input.json` | Input for the program, right after it starts: a JSON list of steps. Use it when an intro waits for a key or a menu waits for a choice. |
|
|
41
41
|
| `--drive` | `loads` only: each capture also writes the 2 KiB RAM of the 1541 drive as `drive8.prg`. |
|
|
42
42
|
| `--min-block 64` | `loads` only: the smallest block of changed memory that becomes a segment (default 64). The result counts smaller changes as `smallChanges`. |
|
|
43
43
|
| `--min-stage 64` | `stages` and `capture`: the smallest block of written memory that starts a new stage when the CPU executes it (default 64). |
|
|
44
44
|
|
|
45
|
-
The capture reads RAM, also the RAM under the ROMs and the I/O area. The script
|
|
45
|
+
The capture reads RAM, also the RAM under the ROMs and the I/O area. The script refuses an option that the command does not use.
|
|
46
46
|
|
|
47
|
-
A steps file
|
|
47
|
+
A steps file is a JSON list of steps. Each step has the input of the MCP tool of the same action. An address in a step can be a name. For example, wait 50 frames and type a space:
|
|
48
48
|
|
|
49
49
|
```json
|
|
50
|
-
[{ "frames": 50 }, { "type": " " }]
|
|
50
|
+
[{ "frames": 50 }, { "type": { "mode": "text", "text": " " } }]
|
|
51
51
|
```
|
|
52
52
|
|
|
53
|
-
|
|
53
|
+
- `{ "write": { "address": "$c000", "data": "0102" } }` writes bytes.
|
|
54
|
+
- `{ "registers": { "pc": "start", "a": 1 } }` sets registers. Only `pc` takes a name.
|
|
55
|
+
- `{ "joystick": { "port": 2, "direction": "up", "fire": false } }` sets a joystick.
|
|
56
|
+
- `{ "type": { "mode": "text", "text": "RUN\n" } }` or `{ "type": { "mode": "petscii", "bytes": [147] } }` puts keys in the keyboard queue of the KERNAL.
|
|
57
|
+
- `{ "frames": 50 }` runs 1 to 30000 frames.
|
|
58
|
+
- `{ "runUntil": { "target": { "kind": "address", "address": "main_loop" }, "timeoutFrames": 300 } }` runs until the target. The target kinds are `address`, `memory` and `raster`. `timeoutFrames` is 1 to 30000 (default 3000).
|
|
54
59
|
|
|
55
|
-
A
|
|
60
|
+
A program that reads the keyboard matrix does not see the keys of a `type` step. These scripts refuse a `press` step. A wait for any key that reads all keyboard columns can end without a key.
|
|
61
|
+
|
|
62
|
+
### Calling the script
|
|
63
|
+
|
|
64
|
+
- Call a script as `node <skill>/scripts/<script>.ts <command> [subjects] [options]`. The command comes first.
|
|
65
|
+
- `<script>.ts --help` lists the commands. `<script>.ts <command> --help` gives the subjects and the options of one command. The help is JSON: `{"text": "..."}`.
|
|
66
|
+
- 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.
|
|
67
|
+
- A range is `<start>-<end>`, for example `'$c000-$cfff'`. In a JSON file, a range is `start` and `end`.
|
|
68
|
+
- Bytes in a JSON file are hex in a field `data`, for example `"data": "a900"`.
|
|
69
|
+
- 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.
|
|
70
|
+
- An error is `{"error": {"code": "...", "message": "..."}}`. The message tells what is wrong and what to do.
|
|
71
|
+
- The exit status is 0 for done or PASS, 1 for FAIL, 2 for an error and 3 for INCONCLUSIVE.
|
|
56
72
|
|
|
57
73
|
### Project folder
|
|
58
74
|
|
|
59
75
|
- 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/`.
|
|
60
76
|
- 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.
|
|
61
77
|
- 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.
|
|
62
|
-
- 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
|
|
78
|
+
- 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/`.
|
|
79
|
+
- 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.
|
|
63
80
|
- 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.
|
|
64
81
|
- 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`.
|
|
65
82
|
|
|
@@ -102,10 +119,10 @@ A multi-load program loads more parts from the disk while it runs, for example o
|
|
|
102
119
|
|
|
103
120
|
```json
|
|
104
121
|
[
|
|
105
|
-
{ "runUntil": { "
|
|
122
|
+
{ "runUntil": { "target": { "kind": "address", "address": "$080d" }, "timeoutFrames": 6000 } },
|
|
106
123
|
{ "capture": "main" },
|
|
107
|
-
{ "type": " " },
|
|
108
|
-
{ "runUntil": { "
|
|
124
|
+
{ "type": { "mode": "text", "text": " " } },
|
|
125
|
+
{ "runUntil": { "target": { "kind": "address", "address": "$c000" }, "timeoutFrames": 6000 } },
|
|
109
126
|
{ "capture": "level1" }
|
|
110
127
|
]
|
|
111
128
|
```
|
|
@@ -128,14 +145,14 @@ Do not record the unpacker in the project knowledge, unless the user asks to exa
|
|
|
128
145
|
|
|
129
146
|
The packing evidence, the stages, or the unpacked program with its image, segments and entry state in a directory of the project.
|
|
130
147
|
|
|
131
|
-
|
|
148
|
+
`trace`, `stages`, `capture` and `loads` give `result: "PASS"` or `result: "FAIL"`. A FAIL has `reason`. `capture` gives the written files in `files`, each as `{out, bytes, sha256}`.
|
|
132
149
|
|
|
133
|
-
|
|
150
|
+
For `loads`: `captures`, with the name, the PC, the segments, the files and the drive file of each capture. `keyWait: true` tells that the CPU was in the KERNAL wait for a key.
|
|
134
151
|
|
|
135
152
|
## Failure and conflicts
|
|
136
153
|
|
|
137
|
-
- `traced: false`: the emulator did not load the program into memory in the time limit. The reason gives the load address. Make sure that the file is a PRG that the C64 can load.
|
|
138
|
-
- `captured: false`: the program did not get to the address in the time limit. The reason tells where it stopped. Find a better address with the c64-emulator skill, or give more frames. If it stopped in a key wait, give `--steps` with a
|
|
154
|
+
- `traced: false` or `followed: false`: the emulator did not load the program into memory in the time limit. The reason gives the load address. Make sure that the file is a PRG that the C64 can load.
|
|
155
|
+
- `captured: false`: the program did not get to the address in the time limit. The reason tells where it stopped. Find a better address with the c64-emulator skill, or give more frames. If it stopped in a key wait, give `--steps` with a `type` step.
|
|
139
156
|
- A step did not reach its target: the reason names the step. Change the step or give it more frames.
|
|
140
157
|
- `loaded: false` and no capture has a segment: the program did not load a part, or it did not start. `keyWait: true` on a capture tells that the C64 waited at READY. or at an INPUT statement. For a tape image, read `emulatorFlaw`. A known VICE flaw can stop a tape load. Tell the user to install the latest VICE.
|
|
141
158
|
- Do not name a packer from weak evidence. The script never names one.
|
|
@@ -2,8 +2,7 @@
|
|
|
2
2
|
// over and how compressed the bytes look. It never names a packer.
|
|
3
3
|
|
|
4
4
|
import { basicLineText, findHandoffs, formatC64Address, instructionLength, parseTokenized } from "#src/c64.ts";
|
|
5
|
-
|
|
6
|
-
export class PackingError extends Error {}
|
|
5
|
+
import { Refusal } from "#src/errors.ts";
|
|
7
6
|
|
|
8
7
|
/** A 256-byte block above this many bits per byte looks compressed. */
|
|
9
8
|
const HIGH_ENTROPY = 6.9;
|
|
@@ -112,7 +111,7 @@ function startSignals(program: Uint8Array, load: number, end: number, sys: numbe
|
|
|
112
111
|
}
|
|
113
112
|
|
|
114
113
|
export function inspect(program: Uint8Array): PackingEvidence {
|
|
115
|
-
if (program.length < 3) throw new
|
|
114
|
+
if (program.length < 3) throw new Refusal("invalid-input", "the file is too short for a PRG");
|
|
116
115
|
const load = program[0]! | (program[1]! << 8);
|
|
117
116
|
const end = load + program.length - 3;
|
|
118
117
|
const basic = basicStart(program);
|
|
@@ -175,7 +174,7 @@ export async function readMemoryMap(read: (start: number) => Promise<{ ranges: A
|
|
|
175
174
|
for (let start: number | undefined = 0; start !== undefined; ) {
|
|
176
175
|
const found: { ranges: AccessRange[]; next?: number } = await read(start);
|
|
177
176
|
ranges.push(...found.ranges);
|
|
178
|
-
if (found.next !== undefined && found.next <= start) throw new
|
|
177
|
+
if (found.next !== undefined && found.next <= start) throw new Refusal("operation-failed", `the memory map did not move on from ${start}`);
|
|
179
178
|
start = found.next;
|
|
180
179
|
}
|
|
181
180
|
return ranges;
|
|
@@ -4,16 +4,18 @@
|
|
|
4
4
|
// image with its entry state, and with --drive the drive RAM as a PRG for
|
|
5
5
|
// knowledge space drive8. No tool guesses where a load ends: the steps decide.
|
|
6
6
|
|
|
7
|
-
import { COVERAGE, formatC64Address
|
|
7
|
+
import { COVERAGE, formatC64Address } from "#src/c64.ts";
|
|
8
8
|
import { autostartLoaded, LOAD_LIMIT_FRAMES, RUN_LIMIT_FRAMES, typeRun } from "#src/host-client/autostart.ts";
|
|
9
9
|
import { readCoverage } from "#src/host-client/coverage.ts";
|
|
10
|
-
import {
|
|
10
|
+
import { ACTION_STEP_KINDS, checkActionStep, isObject, runActionStep, type ActionStep, type ActionStepKind } from "#src/host-client/steps.ts";
|
|
11
|
+
import { knowledgeAt } from "#src/knowledge/names.ts";
|
|
11
12
|
import { WireFailure } from "#src/host-client/tools.ts";
|
|
12
|
-
import { readProjectFile,
|
|
13
|
+
import { readProjectFile, writeReported, type Written } from "#src/host-client/transfer.ts";
|
|
13
14
|
import { ViceSessionClient } from "#src/host-client/vice-session.ts";
|
|
14
15
|
import { UsageError } from "#src/script.ts";
|
|
15
16
|
import { entryState, json, machineState, readRam, vectorsOf } from "./machine.ts";
|
|
16
17
|
|
|
18
|
+
|
|
17
19
|
export type LoadStep = ActionStep | { capture: string };
|
|
18
20
|
|
|
19
21
|
/** A capture name: letters, digits, dots, underscores or hyphens. */
|
|
@@ -53,10 +55,8 @@ export function readLoadSteps(path: string): LoadStep[] {
|
|
|
53
55
|
}
|
|
54
56
|
const kinds = Object.keys(step).filter((key) => (ACTION_STEP_KINDS as readonly string[]).includes(key));
|
|
55
57
|
if (kinds.length !== 1) return fail(`${at} must have exactly one of ${[...ACTION_STEP_KINDS, "capture"].join(", ")}`);
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
for (const key of Object.keys(step)) if (!allowed.includes(key)) fail(`${at} has the key ${key}, which a ${kind} step does not have`);
|
|
59
|
-
checkActionStep(kind, step, at, fail, false);
|
|
58
|
+
// The step with its defaults filled in, as the shared schema of the action steps gives it.
|
|
59
|
+
(value as unknown[])[index] = checkActionStep(kinds[0] as ActionStepKind, step, at, fail);
|
|
60
60
|
});
|
|
61
61
|
if (names.size === 0) fail("the steps have no capture step");
|
|
62
62
|
return value as LoadStep[];
|
|
@@ -134,9 +134,9 @@ export async function loads(program: string, options: { out: string; steps: Load
|
|
|
134
134
|
const captures: Array<Record<string, unknown>> = [];
|
|
135
135
|
for (const [index, step] of options.steps.entries()) {
|
|
136
136
|
if (!("capture" in step)) {
|
|
137
|
-
const notReached = await runActionStep(client, step,
|
|
137
|
+
const notReached = await runActionStep(client, step, knowledgeAt());
|
|
138
138
|
if (notReached !== undefined && "runUntil" in step) {
|
|
139
|
-
const reason = `Step ${index + 1} did not reach its target in ${step.timeoutFrames} frames. It stopped by ${notReached.stopReason} at ${formatC64Address(notReached.pc)}.`;
|
|
139
|
+
const reason = `Step ${index + 1} did not reach its target in ${step.runUntil.timeoutFrames} frames. It stopped by ${notReached.stopReason} at ${formatC64Address(notReached.pc)}.`;
|
|
140
140
|
return { output: { loaded: false, reason, captures }, failed: true };
|
|
141
141
|
}
|
|
142
142
|
continue;
|
|
@@ -157,7 +157,8 @@ export async function loads(program: string, options: { out: string; steps: Load
|
|
|
157
157
|
const state = await machineState(client);
|
|
158
158
|
const vectors = vectorsOf(state, memory);
|
|
159
159
|
const directory = `${options.out}/${step.capture}`;
|
|
160
|
-
const
|
|
160
|
+
const written: Written[] = [];
|
|
161
|
+
const write = (name: string, bytes: Uint8Array) => written.push(writeReported(`${directory}/${name}`, bytes, { what: "--out" }));
|
|
161
162
|
write("image.bin", memory);
|
|
162
163
|
const files = segments.map((segment) => {
|
|
163
164
|
const name = `segment-${hex4(segment.start)}-${hex4(segment.end)}.prg`;
|
|
@@ -187,6 +188,7 @@ export async function loads(program: string, options: { out: string; steps: Load
|
|
|
187
188
|
captures.push({
|
|
188
189
|
capture: step.capture,
|
|
189
190
|
out: directory,
|
|
191
|
+
files: written,
|
|
190
192
|
pc: formatC64Address(registers.pc),
|
|
191
193
|
...(keyWait ? { keyWait: true } : {}),
|
|
192
194
|
segments: segments.map(showRange),
|
|
@@ -11,8 +11,6 @@ const MAX_READ = 4096;
|
|
|
11
11
|
|
|
12
12
|
/** The I/O areas of the entry state, read without side effects. */
|
|
13
13
|
export const IO_AREAS = { vic: [0xd000, 0x2f], sid: [0xd400, 0x1d], cia1: [0xdc00, 0x10], cia2: [0xdd00, 0x10] } as const;
|
|
14
|
-
/** Frames from a hard reset to the READY prompt, with margin. */
|
|
15
|
-
export const READY_FRAMES = 200;
|
|
16
14
|
|
|
17
15
|
/** What no register read of a CIA gives. */
|
|
18
16
|
export interface CiaHidden {
|
|
@@ -8,15 +8,10 @@
|
|
|
8
8
|
// to the stage, is the stage's own code too: a packer hands off by JMP,
|
|
9
9
|
// RTS or RTI and never runs its own code again.
|
|
10
10
|
|
|
11
|
-
import { COVERAGE } from "#src/c64.ts";
|
|
11
|
+
import { type AddressRange, COVERAGE } from "#src/c64.ts";
|
|
12
12
|
import { writtenBlocks } from "#src/host-client/coverage.ts";
|
|
13
13
|
|
|
14
|
-
|
|
15
|
-
start: number;
|
|
16
|
-
end: number;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
const contains = (range: Range, address: number) => address >= range.start && address <= range.end;
|
|
14
|
+
const contains = (range: AddressRange, address: number) => address >= range.start && address <= range.end;
|
|
20
15
|
|
|
21
16
|
/** The flags of two maps from $0000 in one. */
|
|
22
17
|
export function mergeMaps(into: Uint8Array, from: Uint8Array): void {
|
|
@@ -28,7 +23,7 @@ export function mergeMaps(into: Uint8Array, from: Uint8Array): void {
|
|
|
28
23
|
* code that can start a stage: every executed address except own code that
|
|
29
24
|
* the stage has not written.
|
|
30
25
|
*/
|
|
31
|
-
export function stageCandidates(stage: Uint8Array, chunk: Uint8Array, own: ReadonlySet<number>, minimum: number, called: readonly
|
|
26
|
+
export function stageCandidates(stage: Uint8Array, chunk: Uint8Array, own: ReadonlySet<number>, minimum: number, called: readonly AddressRange[] = []): AddressRange[] {
|
|
32
27
|
const combined = Uint8Array.from(stage);
|
|
33
28
|
mergeMaps(combined, chunk);
|
|
34
29
|
return writtenBlocks(combined, minimum).filter((block) => {
|
|
@@ -47,10 +42,10 @@ export function stageCandidates(stage: Uint8Array, chunk: Uint8Array, own: Reado
|
|
|
47
42
|
* them except own code that is not written yet, and write watches over that
|
|
48
43
|
* own code.
|
|
49
44
|
*/
|
|
50
|
-
export function stagePoints(blocks: readonly
|
|
51
|
-
const exec:
|
|
52
|
-
const watch:
|
|
53
|
-
const add = (list:
|
|
45
|
+
export function stagePoints(blocks: readonly AddressRange[], own: ReadonlySet<number>, written: (address: number) => boolean, called: readonly AddressRange[] = []): { exec: AddressRange[]; watch: AddressRange[] } {
|
|
46
|
+
const exec: AddressRange[] = [];
|
|
47
|
+
const watch: AddressRange[] = [];
|
|
48
|
+
const add = (list: AddressRange[], address: number) => {
|
|
54
49
|
const last = list[list.length - 1];
|
|
55
50
|
if (last !== undefined && last.end === address - 1) last.end = address;
|
|
56
51
|
else list.push({ start: address, end: address });
|
|
@@ -66,7 +61,7 @@ export function stagePoints(blocks: readonly Range[], own: ReadonlySet<number>,
|
|
|
66
61
|
}
|
|
67
62
|
|
|
68
63
|
/** True when the address lies in one of the ranges. */
|
|
69
|
-
export function inRanges(ranges: readonly
|
|
64
|
+
export function inRanges(ranges: readonly AddressRange[], address: number): boolean {
|
|
70
65
|
return ranges.some((range) => contains(range, address));
|
|
71
66
|
}
|
|
72
67
|
|
|
@@ -86,8 +81,8 @@ export function callReturn(stack: Uint8Array, sp: number, byteAt: (address: numb
|
|
|
86
81
|
* The code that ran in a block: its executed opcode bytes with the two bytes
|
|
87
82
|
* after each, which can be operands, joined and cut at the block's end.
|
|
88
83
|
*/
|
|
89
|
-
export function executedIn(map: Uint8Array, block:
|
|
90
|
-
const ranges:
|
|
84
|
+
export function executedIn(map: Uint8Array, block: AddressRange): AddressRange[] {
|
|
85
|
+
const ranges: AddressRange[] = [];
|
|
91
86
|
for (let address = block.start; address <= block.end; address++) {
|
|
92
87
|
if ((map[address]! & COVERAGE.ramExecute) === 0) continue;
|
|
93
88
|
const end = Math.min(block.end, address + 2);
|
|
@@ -109,16 +104,16 @@ export function executedIn(map: Uint8Array, block: Range): Range[] {
|
|
|
109
104
|
* program with a leftover. `writtenBy` is the last stage that wrote into the
|
|
110
105
|
* range.
|
|
111
106
|
*/
|
|
112
|
-
export function captureSegments(stages: ReadonlyArray<{ entry: number; wrote: readonly
|
|
107
|
+
export function captureSegments(stages: ReadonlyArray<{ entry: number; wrote: readonly AddressRange[] }>, entry: number): Array<AddressRange & { writtenBy: number }> {
|
|
113
108
|
const last = stages.length - 1;
|
|
114
109
|
const replaced = (from: number, address: number) => stages.slice(from).some((stage) => inRanges(stage.wrote, address));
|
|
115
|
-
const leftover = (block:
|
|
110
|
+
const leftover = (block: AddressRange, writer: number) =>
|
|
116
111
|
!contains(block, entry) && stages.some((stage, index) => index > writer && index < last && contains(block, stage.entry) && !replaced(index, stage.entry));
|
|
117
112
|
const blocks = stages
|
|
118
113
|
.flatMap((stage, index) => stage.wrote.map((range) => ({ start: range.start, end: range.end, writtenBy: index })))
|
|
119
114
|
.filter((block) => !leftover(block, block.writtenBy))
|
|
120
115
|
.sort((a, b) => a.start - b.start);
|
|
121
|
-
const segments: Array<
|
|
116
|
+
const segments: Array<AddressRange & { writtenBy: number }> = [];
|
|
122
117
|
for (const block of blocks) {
|
|
123
118
|
const previous = segments[segments.length - 1];
|
|
124
119
|
if (previous !== undefined && block.start <= previous.end + 1) {
|
|
@@ -130,7 +125,7 @@ export function captureSegments(stages: ReadonlyArray<{ entry: number; wrote: re
|
|
|
130
125
|
}
|
|
131
126
|
|
|
132
127
|
/** The segments without the bytes of `holes`: a segment that a hole cuts gives its parts, each with the fields of the segment. */
|
|
133
|
-
export function withoutRanges<T extends
|
|
128
|
+
export function withoutRanges<T extends AddressRange>(segments: readonly T[], holes: readonly AddressRange[]): T[] {
|
|
134
129
|
let pieces = [...segments];
|
|
135
130
|
for (const hole of holes) {
|
|
136
131
|
pieces = pieces.flatMap((piece) => {
|