@henols/c64-re-tools 2.0.0-rc.5 → 2.0.0-rc.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/skills/c64-assembler/SKILL.md +1 -1
- package/skills/c64-basic/SKILL.md +1 -1
- package/skills/c64-disk/SKILL.md +3 -3
- package/skills/c64-disk/scripts/disk.ts +2 -1
- package/skills/c64-emulator/SKILL.md +3 -1
- package/skills/c64-knowledge/SKILL.md +1 -1
- package/skills/c64-provenance/SKILL.md +7 -5
- package/skills/c64-provenance/scripts/compare.ts +26 -0
- package/skills/c64-provenance/scripts/provenance.ts +24 -10
- package/skills/c64-provenance/scripts/separate.ts +12 -4
- package/skills/c64-reverse-engineering/SKILL.md +2 -2
- package/skills/c64-source/SKILL.md +15 -9
- package/skills/c64-source/scripts/generate.ts +36 -10
- package/skills/c64-source/scripts/loader.ts +88 -6
- package/skills/c64-source/scripts/source.ts +40 -9
- package/skills/c64-static-analysis/SKILL.md +4 -4
- package/skills/c64-static-analysis/scripts/analyze.ts +13 -6
- package/skills/c64-static-analysis/scripts/coverage.ts +4 -4
- package/skills/c64-static-analysis/scripts/idioms.ts +14 -9
- package/skills/c64-testing/SKILL.md +1 -1
- package/skills/c64-testing/scripts/scenario.ts +2 -2
- package/skills/c64-unpacker/SKILL.md +9 -7
- package/skills/c64-unpacker/scripts/loads.ts +12 -1
- package/skills/c64-unpacker/scripts/stages.ts +66 -3
- package/skills/c64-unpacker/scripts/unpack.ts +96 -30
- package/src/c64.ts +14 -5
- package/src/cli/status.ts +26 -3
- package/src/host/main.ts +6 -5
- package/src/host/server.ts +12 -4
- package/src/host/tools/c1541.ts +34 -5
- package/src/host/tools/index.ts +6 -2
- package/src/host/tools/petcat.ts +5 -2
- package/src/host/tools/status.ts +17 -6
- package/src/host/vice/adapter.ts +23 -9
- package/src/host/vice/process.ts +9 -3
- package/src/host/vice/session.ts +49 -12
- package/src/host-client/connect.ts +10 -10
- package/src/host-client/coverage.ts +19 -10
- package/src/host-client/tools.ts +5 -5
- package/src/host-client/vice-session.ts +1 -1
- package/src/knowledge/schema.ts +7 -0
- package/src/knowledge/write.ts +5 -0
- package/src/mcp/tools/debug.ts +3 -2
- package/src/mcp/tools/machine.ts +4 -1
- package/src/mcp/tools/media.ts +5 -1
- package/src/mcp/tools/video.ts +2 -0
- package/src/native/discover.ts +2 -1
- package/src/platform.ts +3 -2
- package/src/protocol/tools.ts +19 -4
- package/src/protocol/vice.ts +11 -2
package/package.json
CHANGED
|
@@ -67,7 +67,7 @@ The exit status is 0 for a PRG. The exit status is 1 for `assembled: false`, for
|
|
|
67
67
|
## Failure and conflicts
|
|
68
68
|
|
|
69
69
|
- `assembled: false`: the source has errors. This is a normal result. Correct the source.
|
|
70
|
-
- `installation-incomplete`, or a message that the host runtime does not run: stop the task. Give the message to the user and wait for the user. The message gives the remedy. Do not make a replacement for the tool or the host runtime, for example your own disk parser, disassembler or emulator script.
|
|
70
|
+
- `installation-incomplete`, or `machine-unavailable` with a message that the host runtime does not run: stop the task. Give the message to the user and wait for the user. The message gives the remedy. Do not make a replacement for the tool or the host runtime, for example your own disk parser, disassembler or emulator script.
|
|
71
71
|
- `invalid-input`: an option or a path is wrong. Read the message and correct the call.
|
|
72
72
|
|
|
73
73
|
## Handoffs
|
|
@@ -56,7 +56,7 @@ The exit status is 0 for a listing. The exit status is 1 for `decoded: false` an
|
|
|
56
56
|
|
|
57
57
|
- `decoded: false`: the file is not a BASIC program. This is a normal result. Usually the file is machine code. Give it to the c64-static-analysis skill.
|
|
58
58
|
- An empty `handoffs` list: the BASIC does not start machine code with SYS or USR. Do not make up a handoff. Look for a POKE into a vector or a LOAD of another file in the listing.
|
|
59
|
-
- `installation-incomplete`, or a message that the host runtime does not run: stop the task. Give the message to the user and wait for the user. The message gives the remedy. Do not make a replacement for the tool or the host runtime, for example your own disk parser, disassembler or emulator script.
|
|
59
|
+
- `installation-incomplete`, or `machine-unavailable` with a message that the host runtime does not run: stop the task. Give the message to the user and wait for the user. The message gives the remedy. Do not make a replacement for the tool or the host runtime, for example your own disk parser, disassembler or emulator script.
|
|
60
60
|
- `not-found` or `invalid-input`: the path or an argument is wrong. Read the message and correct the call.
|
|
61
61
|
|
|
62
62
|
## Handoffs
|
package/skills/c64-disk/SKILL.md
CHANGED
|
@@ -29,7 +29,7 @@ node <skill>/scripts/disk.ts <action> <image> [name] [--out <file>]
|
|
|
29
29
|
| Need | Command |
|
|
30
30
|
|---|---|
|
|
31
31
|
| The disk name, the free blocks and the files | `directory original/game.d64` |
|
|
32
|
-
| The free and used sectors of each track | `bam original/game.d64` |
|
|
32
|
+
| The free and used sectors of each track, and the used sectors that no file uses | `bam original/game.d64` |
|
|
33
33
|
| The type, size and first block of one file | `entry original/game.d64 GAME` |
|
|
34
34
|
| The track and sector of each block of one file | `chain original/game.d64 GAME` |
|
|
35
35
|
| Extract one file | `read original/game.d64 GAME --out extracted/game.prg` |
|
|
@@ -47,7 +47,7 @@ File names show the characters that the C64 shows in a directory list. A byte th
|
|
|
47
47
|
1. Run `directory`. Find the file in `entries`. Each entry has a `name`, a `type`, a size in `blocks` and the `closed` and `locked` flags.
|
|
48
48
|
2. Run `read` with an output path to extract the file. A PRG file keeps its 2-byte load address at the start.
|
|
49
49
|
3. Run `entry` or `chain` only when the disk structure is important, for example for a loader that reads sectors directly.
|
|
50
|
-
4. Run `bam` only to find used sectors that no file uses.
|
|
50
|
+
4. Run `bam` only to find used sectors that no file uses. `unreferenced` lists them. The list does not include the header, the directory and the BAM blocks. These sectors can hold data that a loader reads directly.
|
|
51
51
|
|
|
52
52
|
## Knowledge
|
|
53
53
|
|
|
@@ -79,7 +79,7 @@ The exit status is 0 for a result with the file. The exit status is 1 for `found
|
|
|
79
79
|
|
|
80
80
|
- `found: false`: the image has no file with that name. This is a normal result. Read the directory again and compare the names.
|
|
81
81
|
- `media-error`: the image has a defect or is not a disk image. The message tells the problem, for example a sector chain that loops. Tell the user. Do not change the image.
|
|
82
|
-
- `installation-incomplete`, or a message that the host runtime does not run: stop the task. Give the message to the user and wait for the user. The message gives the remedy. Do not make a replacement for the tool or the host runtime, for example your own disk parser, disassembler or emulator script.
|
|
82
|
+
- `installation-incomplete`, or `machine-unavailable` with a message that the host runtime does not run: stop the task. Give the message to the user and wait for the user. The message gives the remedy. Do not make a replacement for the tool or the host runtime, for example your own disk parser, disassembler or emulator script.
|
|
83
83
|
- `invalid-input`: an argument or a path is wrong. Read the message and correct the call.
|
|
84
84
|
|
|
85
85
|
A file that is not `closed` was not written fully. Its data can be incomplete.
|
|
@@ -16,7 +16,8 @@ import { ContainerError, parseCrt, parseT64, tapeFile } from "./containers.ts";
|
|
|
16
16
|
const USAGE = `disk.ts <action> <image> [name] [--out <file>]
|
|
17
17
|
|
|
18
18
|
directory <image> disk name, id, free blocks and the files
|
|
19
|
-
bam <image> free and used sectors of each track
|
|
19
|
+
bam <image> free and used sectors of each track, and the
|
|
20
|
+
used sectors that no file holds
|
|
20
21
|
entry <image> <name> the directory entry of one file
|
|
21
22
|
chain <image> <name> the track and sector of each block of one file
|
|
22
23
|
read <image> <name> --out <file> write the bytes of one file into the project
|
|
@@ -64,7 +64,9 @@ For a large result, give `out` with a path in the project. `c64_memory_read`, `c
|
|
|
64
64
|
|
|
65
65
|
Write operations need a stopped CPU. Stop the CPU with `c64_execution` action `pause` first.
|
|
66
66
|
|
|
67
|
-
`c64_reset` with `run` false stops the CPU at the first instruction of the reset routine. The result gives that address in `pc`.
|
|
67
|
+
`c64_reset` with `run` false stops the CPU at the first instruction of the reset routine. The result gives that address in `pc`. A reset and `c64_autostart` set `c64_timing` to zero.
|
|
68
|
+
|
|
69
|
+
A cartridge from `c64_autostart` stays in the C64 through every reset, as on a real C64. `c64_status` gives `cartridge: true` while the C64 has it. `c64_autostart` of a file that is not a cartridge removes the cartridge first.
|
|
68
70
|
|
|
69
71
|
Step, next and until-return work only in space `c64`. The emulator cannot stop the disk drive CPU after one instruction. To find when the drive executes an address, use `c64_run_until` with an address target in space `drive8`, or a breakpoint in space `drive8`. The drive can execute a few more instructions before it stops. The computer completes its current instruction. The `pc` in the result is the drive address where it stopped.
|
|
70
72
|
|
|
@@ -115,7 +115,7 @@ A `line` comment is a line of its own above the address. A `side` comment is at
|
|
|
115
115
|
|
|
116
116
|
## Modules
|
|
117
117
|
|
|
118
|
-
A module tells the c64-source skill where a file of the generated source starts. The file runs to the next module or to the end of its segment. Each address has one module, and each file name has one module. A file name has letters, digits, underscores or hyphens, and ends in `.asm`. The title becomes the first line of the file. A `module` write without a title keeps the title that the module has. Modules have a history, and `revert` restores them.
|
|
118
|
+
A module tells the c64-source skill where a file of the generated source starts. The file runs to the next module or to the end of its segment. Each address has one module, and each file name has one module. A file name has letters, digits, underscores or hyphens, and ends in `.asm`. The c64-source skill writes `main.asm`, `loader.asm` and `constants.asm` itself. Thus a module cannot have one of these names, in upper or lower case. The title becomes the first line of the file. A `module` write without a title keeps the title that the module has. Modules have a history, and `revert` restores them.
|
|
119
119
|
|
|
120
120
|
## Removals
|
|
121
121
|
|
|
@@ -40,12 +40,14 @@ node <skill>/scripts/provenance.ts separate unpacked/image.json --coverage cover
|
|
|
40
40
|
|
|
41
41
|
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. It prints one JSON object.
|
|
42
42
|
|
|
43
|
+
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.
|
|
44
|
+
|
|
43
45
|
With `--run`, 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.
|
|
44
46
|
|
|
45
47
|
## Workflow
|
|
46
48
|
|
|
47
49
|
1. Run the script on the releases.
|
|
48
|
-
2. Read `identicalShare`. If it is low and `suggestedShifts` is present, the releases are at different addresses. Run the script again with `--shift`.
|
|
50
|
+
2. Read `identicalShare`. If it is low and `suggestedShifts` is present, the releases are at different addresses. Run the script again with the `--shift` of `suggestedShifts`. `shiftsNotSuggested` tells why a found shift does not fit in `$0000-$ffff`.
|
|
49
51
|
3. Read `differences`. Each one has an address range, the bytes of each release, and `groups`: the releases that have the same bytes there.
|
|
50
52
|
4. To know how the program uses the differences, run the script again with `--run` and the frames that get the program to the part you examine. Read `runtime` of each difference:
|
|
51
53
|
- `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`.
|
|
@@ -66,9 +68,9 @@ Use `separate` when you have only one release. It gives evidence, not a decision
|
|
|
66
68
|
- `ranges`: the image in ranges with the same stage and access. `stage` is the last unpack stage that wrote the range. In `image.json`, the last stage is the program. An earlier stage is a depacker, an intro or a loader. Only an image from c64-unpacker `capture` has stages. For an image from `loads`, `noStages` tells this.
|
|
67
69
|
- `access`: from the joined `--coverage` files: `executed`, `read`, `written`, `read-written` or `untouched`. `untouched` bytes did not take part in the runs.
|
|
68
70
|
- `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.
|
|
69
|
-
- `outside`: RAM outside the image 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.
|
|
71
|
+
- `outside`: RAM outside the image 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.
|
|
70
72
|
|
|
71
|
-
|
|
73
|
+
Each list has `count` and `listed`. `listed` has the first ranges, at most `--max-listed`. `--out` writes all of them. Run more coverage runs for parts that are `untouched` before you decide.
|
|
72
74
|
|
|
73
75
|
## Knowledge
|
|
74
76
|
|
|
@@ -79,7 +81,7 @@ Record a confirmed conclusion as a normal comment, region or symbol with the c64
|
|
|
79
81
|
|
|
80
82
|
## Result
|
|
81
83
|
|
|
82
|
-
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. With `--run`, each difference also has `runtime`, and `run` tells if each release started, or why not.
|
|
84
|
+
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 `--run`, each difference also has `runtime`, and `run` tells if each release started, or why not.
|
|
83
85
|
|
|
84
86
|
The runtime evidence is evidence only. The script does not decide which bytes are original.
|
|
85
87
|
|
|
@@ -91,7 +93,7 @@ The exit status is 0 for a result. The exit status is 1 for an error. The exit s
|
|
|
91
93
|
- Do not call a byte original only because one release has it. A majority of releases is also not proof.
|
|
92
94
|
- `common` is empty: the releases do not cover the same addresses. Use `--shift`, or compare a range that all releases cover.
|
|
93
95
|
- `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.
|
|
94
|
-
- `installation-incomplete`, or 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.
|
|
96
|
+
- `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.
|
|
95
97
|
- `moreDifferences: true`: the script shows only the first differences. Use `--from` and `--to` to see the others.
|
|
96
98
|
- `not-found`: the project has no file at that path.
|
|
97
99
|
- `invalid-input`: a file, an address or a shift is wrong. A path that goes out of the project directory is also wrong, also through a link. Read the message.
|
|
@@ -121,3 +121,29 @@ export function suggestShift(base: Release, other: Release): { shift: number; ma
|
|
|
121
121
|
for (const [shift, matches] of votes) if (best === undefined || matches > best.matches) best = { shift, matches };
|
|
122
122
|
return best;
|
|
123
123
|
}
|
|
124
|
+
|
|
125
|
+
export type ShiftSuggestion = { release: number; shift: number; matchingSequences: number } | { release: number; matchingSequences: number; reason: string };
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* The --shift to suggest for release `index` (0 = the first) from `found`,
|
|
129
|
+
* the best shift of that release over the first one (F-25). The shift is the
|
|
130
|
+
* full value for --shift: `applied` holds the shifts already given. A shift
|
|
131
|
+
* that moves a segment out of $0000-$ffff is not suggested: with two releases,
|
|
132
|
+
* the opposite shift of the first release is suggested when it fits, else the
|
|
133
|
+
* result gives the reason.
|
|
134
|
+
*/
|
|
135
|
+
export function shiftSuggestion(releases: Release[], applied: number[], index: number, found: { shift: number; matches: number }): ShiftSuggestion {
|
|
136
|
+
const outside = (release: Release, by: number) => release.segments.find((segment) => segment.start + by < 0 || segment.end + by > 0xffff);
|
|
137
|
+
const hex = (value: number) => `$${value.toString(16).padStart(4, "0")}`;
|
|
138
|
+
const why = (number: number, by: number, segment: Range) =>
|
|
139
|
+
`release ${number} moved by ${by} puts ${hex(segment.start)}-${hex(segment.end)} ${segment.start + by < 0 ? "below $0000" : "past $ffff"}`;
|
|
140
|
+
const own = outside(releases[index]!, found.shift);
|
|
141
|
+
if (own === undefined) return { release: index + 1, shift: applied[index]! + found.shift, matchingSequences: found.matches };
|
|
142
|
+
const reasons = [why(index + 1, found.shift, own)];
|
|
143
|
+
if (releases.length === 2) {
|
|
144
|
+
const base = outside(releases[0]!, -found.shift);
|
|
145
|
+
if (base === undefined) return { release: 1, shift: applied[0]! - found.shift, matchingSequences: found.matches };
|
|
146
|
+
reasons.push(why(1, -found.shift, base));
|
|
147
|
+
}
|
|
148
|
+
return { release: index + 1, matchingSequences: found.matches, reason: `No shift fits: ${reasons.join(", and ")}.` };
|
|
149
|
+
}
|
|
@@ -18,7 +18,7 @@ import { KnowledgeError, openForRead } from "#src/knowledge/database.ts";
|
|
|
18
18
|
import { listReferences, listRegions } from "#src/knowledge/read.ts";
|
|
19
19
|
import { CODE_REGION_TYPES } from "#src/knowledge/schema.ts";
|
|
20
20
|
import { runScript, UsageError } from "#src/script.ts";
|
|
21
|
-
import { compareReleases, suggestShift, type Range, type Release } from "./compare.ts";
|
|
21
|
+
import { compareReleases, shiftSuggestion, suggestShift, type Range, type Release } from "./compare.ts";
|
|
22
22
|
import { watchRelease, type ImageState, type Start } from "./runtime.ts";
|
|
23
23
|
import { separate } from "./separate.ts";
|
|
24
24
|
|
|
@@ -36,7 +36,7 @@ separate gives evidence from one release: the unpack stage that wrote each range
|
|
|
36
36
|
its access in the --coverage files (64 KiB each, repeatable), whether call and
|
|
37
37
|
jump references in knowledge reach each code region from the entry, and what the
|
|
38
38
|
program reads or executes outside the image. --out writes all ranges to a file;
|
|
39
|
-
the result lists at most --max-listed of
|
|
39
|
+
the result lists at most --max-listed of each list (default 50) and its count.
|
|
40
40
|
|
|
41
41
|
A release is a PRG, placed at its load address, or an image.json of the
|
|
42
42
|
c64-unpacker skill, compared inside its segments. Paths are relative to the
|
|
@@ -208,12 +208,14 @@ async function run(argv: string[]): Promise<unknown> {
|
|
|
208
208
|
identicalShare < 0.5
|
|
209
209
|
? releases.slice(1).flatMap((release, index) => {
|
|
210
210
|
const best = suggestShift(releases[0]!, release);
|
|
211
|
-
return best === undefined || best.shift === 0 ? [] : [
|
|
211
|
+
return best === undefined || best.shift === 0 ? [] : [shiftSuggestion(releases, loaded.map((item) => item.shift), index + 1, best)];
|
|
212
212
|
})
|
|
213
213
|
: [];
|
|
214
|
+
const suggested = suggestions.filter((suggestion) => "shift" in suggestion);
|
|
215
|
+
const notSuggested = suggestions.filter((suggestion) => "reason" in suggestion);
|
|
214
216
|
|
|
215
217
|
// The runtime evidence: one emulator for each release, the differences at the release's own addresses.
|
|
216
|
-
const runtime = new Map<string, Array<{ first: string; changed: boolean }>>();
|
|
218
|
+
const runtime = new Map<string, Array<Range & { first: string; changed: boolean }>>();
|
|
217
219
|
const runs: Record<string, unknown> = {};
|
|
218
220
|
if (frames !== undefined && comparison.differences.length > 0) {
|
|
219
221
|
for (const item of loaded) {
|
|
@@ -229,7 +231,7 @@ async function run(argv: string[]): Promise<unknown> {
|
|
|
229
231
|
if (!watched.started) runs[name] = { started: false, reason: watched.reason };
|
|
230
232
|
else {
|
|
231
233
|
runs[name] = { started: true };
|
|
232
|
-
runtime.set(name, watched.accesses);
|
|
234
|
+
runtime.set(name, watched.accesses.map((access, index) => ({ ...ranges[index]!, ...access })));
|
|
233
235
|
}
|
|
234
236
|
} finally {
|
|
235
237
|
await client.close();
|
|
@@ -238,7 +240,7 @@ async function run(argv: string[]): Promise<unknown> {
|
|
|
238
240
|
}
|
|
239
241
|
|
|
240
242
|
return {
|
|
241
|
-
releases: loaded.map((item) => ({ name: item.release.name, segments: item.release.segments.map((segment) => ({ start: formatC64Address(segment.start), end: formatC64Address(segment.end) })) })),
|
|
243
|
+
releases: loaded.map((item) => ({ name: item.release.name, shift: item.shift, segments: item.release.segments.map((segment) => ({ start: formatC64Address(segment.start), end: formatC64Address(segment.end) })) })),
|
|
242
244
|
common: comparison.common.map((range) => ({ start: formatC64Address(range.start), end: formatC64Address(range.end) })),
|
|
243
245
|
identicalBytes: comparison.identicalBytes,
|
|
244
246
|
differingBytes: comparison.differingBytes,
|
|
@@ -248,10 +250,21 @@ async function run(argv: string[]): Promise<unknown> {
|
|
|
248
250
|
end: formatC64Address(difference.end),
|
|
249
251
|
values: Object.fromEntries(Object.entries(difference.values).map(([name, bytes]) => [name, hexBytes(bytes.slice(0, 32)) + (bytes.length > 32 ? " ..." : "")])),
|
|
250
252
|
groups: difference.groups,
|
|
251
|
-
|
|
253
|
+
// runtime gives the release's own addresses: with a shift, they are not the addresses of the difference (F-24).
|
|
254
|
+
...(runtime.size === 0
|
|
255
|
+
? {}
|
|
256
|
+
: {
|
|
257
|
+
runtime: Object.fromEntries(
|
|
258
|
+
[...runtime].map(([name, accesses]) => {
|
|
259
|
+
const { start, end, first, changed } = accesses[index]!;
|
|
260
|
+
return [name, { start: formatC64Address(start), end: formatC64Address(end), first, changed }];
|
|
261
|
+
}),
|
|
262
|
+
),
|
|
263
|
+
}),
|
|
252
264
|
})),
|
|
253
265
|
...(comparison.moreDifferences ? { moreDifferences: true } : {}),
|
|
254
|
-
...(
|
|
266
|
+
...(suggested.length > 0 ? { suggestedShifts: suggested } : {}),
|
|
267
|
+
...(notSuggested.length > 0 ? { shiftsNotSuggested: notSuggested } : {}),
|
|
255
268
|
...(frames === undefined
|
|
256
269
|
? {}
|
|
257
270
|
: { run: { frames, steps: steps.length, releases: runs, ...(comparison.differences.length === 0 ? { skipped: "The releases have no differences, so no release ran." } : {}) } }),
|
|
@@ -288,6 +301,7 @@ function separateCommand(path: string, coveragePaths: string[], out: string | un
|
|
|
288
301
|
const ranges = found.ranges.map((range) => ({ ...show(range), ...(range.stage === undefined ? {} : { stage: range.stage }), ...(range.access === undefined ? {} : { access: range.access }) }));
|
|
289
302
|
const codeRegions = found.codeRegions.map((region) => ({ ...show(region), type: region.type, ...(region.reachable === undefined ? {} : { reachable: region.reachable }), ...(region.executed === undefined ? {} : { executed: region.executed }) }));
|
|
290
303
|
const outside = { read: found.outside.read.map(show), executed: found.outside.executed.map(show), generated: found.outside.generated.map(show) };
|
|
304
|
+
const listed = <T>(items: T[]) => ({ count: items.length, listed: items.slice(0, Number(max)) });
|
|
291
305
|
if (out !== undefined) writeProjectFile(out, Buffer.from(`${JSON.stringify({ release: path, ranges, codeRegions, outside }, null, 1)}\n`), { what: "--out" });
|
|
292
306
|
const bytes = (filter: (range: (typeof found.ranges)[number]) => boolean) => found.ranges.filter(filter).reduce((sum, range) => sum + range.end - range.start + 1, 0);
|
|
293
307
|
const lastStage = loaded.stages === undefined ? undefined : loaded.stages.length - 1;
|
|
@@ -301,12 +315,12 @@ function separateCommand(path: string, coveragePaths: string[], out: string | un
|
|
|
301
315
|
bytesByStage: Object.fromEntries([...[...Array(lastStage + 1).keys()].map((stage): [string, number] => [String(stage), bytes((range) => range.stage === stage)]), ["none", bytes((range) => range.stage === undefined)]]),
|
|
302
316
|
}),
|
|
303
317
|
...(coverage === undefined ? {} : { bytesByAccess: Object.fromEntries((["executed", "read", "written", "read-written", "untouched"] as const).map((access) => [access, bytes((range) => range.access === access)])) }),
|
|
304
|
-
ranges:
|
|
318
|
+
ranges: listed(ranges),
|
|
305
319
|
codeRegions: {
|
|
306
320
|
count: codeRegions.length,
|
|
307
321
|
...(loaded.entry === undefined ? {} : { unreachable: codeRegions.filter((region) => region.reachable === false) }),
|
|
308
322
|
},
|
|
309
|
-
...(coverage === undefined ? {} : { outside }),
|
|
323
|
+
...(coverage === undefined ? {} : { outside: { read: listed(outside.read), executed: listed(outside.executed), generated: listed(outside.generated) } }),
|
|
310
324
|
...(out === undefined ? {} : { out }),
|
|
311
325
|
};
|
|
312
326
|
}
|
|
@@ -42,18 +42,20 @@ export interface Separation {
|
|
|
42
42
|
/**
|
|
43
43
|
* RAM outside the image and outside pages 0-3, from the coverage: `read`
|
|
44
44
|
* and `executed` the program never wrote (a loader or an earlier stage put
|
|
45
|
-
* those bytes there), and `generated` it wrote and then executed
|
|
45
|
+
* those bytes there), and `generated` it wrote and then executed, in
|
|
46
|
+
* pieces of code with gaps of up to two operand bytes.
|
|
46
47
|
*/
|
|
47
48
|
outside: { read: Range[]; executed: Range[]; generated: Range[] };
|
|
48
49
|
}
|
|
49
50
|
|
|
50
51
|
const SYSTEM_PAGES_END = 0x03ff;
|
|
51
52
|
|
|
52
|
-
|
|
53
|
+
/** Joins addresses in order into ranges; `gap` is the most bytes between two addresses in one range. */
|
|
54
|
+
function merge(addresses: number[], gap = 0, joins: (from: number, to: number) => boolean = () => true): Range[] {
|
|
53
55
|
const ranges: Range[] = [];
|
|
54
56
|
for (const address of addresses) {
|
|
55
57
|
const last = ranges.at(-1);
|
|
56
|
-
if (last !== undefined && last.end
|
|
58
|
+
if (last !== undefined && address - last.end - 1 <= gap && joins(last.end, address)) last.end = address;
|
|
57
59
|
else ranges.push({ start: address, end: address });
|
|
58
60
|
}
|
|
59
61
|
return ranges;
|
|
@@ -128,5 +130,11 @@ export function separate(input: SeparationInput): Separation {
|
|
|
128
130
|
else if (!written && (input.coverage[address]! & COVERAGE.ramRead) !== 0) read.push(address);
|
|
129
131
|
}
|
|
130
132
|
}
|
|
131
|
-
|
|
133
|
+
// The image has no bytes of generated code, so only its opcode bytes count as executed. VICE marks only
|
|
134
|
+
// opcode bytes: a gap of at most two operand bytes outside the image stays inside one piece of code (F-28).
|
|
135
|
+
const outsideImage = (from: number, to: number) => {
|
|
136
|
+
for (let address = from + 1; address < to; address++) if (inImage(address)) return false;
|
|
137
|
+
return true;
|
|
138
|
+
};
|
|
139
|
+
return { ranges, codeRegions, outside: { read: merge(read), executed: merge(ranOutside), generated: merge(generated, 2, outsideImage) } };
|
|
132
140
|
}
|
|
@@ -92,7 +92,7 @@ In stage 5, if you cannot get to a part of the program, ask the user how to get
|
|
|
92
92
|
1. Acceptance: each criterion of stage 1 and its result, and each exception that the user accepted.
|
|
93
93
|
2. The program: what the program does, in a few sentences.
|
|
94
94
|
3. Modules: the file, the address range and the title of each module.
|
|
95
|
-
4. Untested code: the `
|
|
95
|
+
4. Untested code: all ranges in the `not_executed.json` file of c64-source. Tell why no run executed them, if you know.
|
|
96
96
|
5. Open questions: what the evidence does not explain.
|
|
97
97
|
|
|
98
98
|
Keep observed facts apart from your conclusions.
|
|
@@ -133,7 +133,7 @@ A clear answer for the scope, with the durable conclusions in the project knowle
|
|
|
133
133
|
|
|
134
134
|
## Failure and conflicts
|
|
135
135
|
|
|
136
|
-
- A skill reports `installation-incomplete`, or a message that the host runtime does not run: stop the whole task. Give the message to the user and wait for the user. Do not continue with other skills. Do not make a replacement for the tool or the host runtime, for example your own disk parser, disassembler or emulator script.
|
|
136
|
+
- A skill reports `installation-incomplete`, or `machine-unavailable` with a message that the host runtime does not run: stop the whole task. Give the message to the user and wait for the user. Do not continue with other skills. Do not make a replacement for the tool or the host runtime, for example your own disk parser, disassembler or emulator script.
|
|
137
137
|
- If the evidence does not support a conclusion, keep the unknown and get more evidence. Do not make up a meaning.
|
|
138
138
|
- Before you replace current knowledge from the user or the LLM, read its history and the evidence for it.
|
|
139
139
|
- A conflict from the static analysis: find the correct answer at run time with the c64-emulator skill.
|
|
@@ -32,7 +32,7 @@ node <skill>/scripts/generate.ts unpacked/image.json --out src --loader
|
|
|
32
32
|
|---|---|
|
|
33
33
|
| `--out src` | The directory for the source files. The script writes over the files that it generated before. |
|
|
34
34
|
| `--entry '$080d'` | An entry point for Ghidra. Use it again for more entry points. The entry of `image.json`, the routines and the code regions in the knowledge are entry points too. |
|
|
35
|
-
| `--coverage coverage/attract.bin` | A coverage file of 64 KiB from `c64_memmap` with `out`. Use it again for more runs. Code that no run executed gets the line `; not executed
|
|
35
|
+
| `--coverage coverage/attract.bin` | A coverage file of 64 KiB from `c64_memmap` with `out`. Use it again for more runs. Code that no run executed gets the line `; not executed` with its range. `notExecuted` lists it. |
|
|
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
|
|
|
@@ -56,10 +56,11 @@ node <skill>/scripts/generate.ts unpacked/image.json --out src --loader
|
|
|
56
56
|
- Code regions, and the instructions that Ghidra decodes outside of data regions, become instructions.
|
|
57
57
|
- Data regions become data by their type: `!byte`, `!word`, `!pet`, `!scr`, `!fill`, tables of labels, and sprite bytes in binary.
|
|
58
58
|
- `lda+2 $0012` is an absolute operand below $0100. `+1` is a zero-page operand with a label.
|
|
59
|
+
- `sta NMINV+1` is an address inside a platform entry of more than one byte, for example the high byte of a vector.
|
|
59
60
|
- `name = * + 1` is a label inside an instruction, for example for code that changes its own operand.
|
|
60
61
|
- `!byte` with an instruction as its comment: ACME writes this instruction with a different opcode. The bytes keep the source identical.
|
|
61
62
|
- `not decoded` marks bytes in a code region that Ghidra did not decode.
|
|
62
|
-
- `; not executed` starts code that no run of the `--coverage` files executed.
|
|
63
|
+
- `; not executed $xxxx-$yyyy` starts code that no run of the `--coverage` files executed. The range is the same as in `not_executed.json`.
|
|
63
64
|
|
|
64
65
|
Do not edit the generated files. The next run writes over them.
|
|
65
66
|
|
|
@@ -70,11 +71,12 @@ Do not edit the generated files. The next run writes over them.
|
|
|
70
71
|
1. `10 SYS 2061` starts it.
|
|
71
72
|
2. It moves its copier to two free pages. The copier runs with interrupts off and with all RAM visible.
|
|
72
73
|
3. It copies each segment and the pages $0002-$03ff of the entry state into place. The script selects an order in which no copy writes over the packed bytes of a copy that has not run yet.
|
|
73
|
-
4. It sets
|
|
74
|
+
4. It sets the display registers of the VIC-II and the VIC bank from `io` in the entry state. Then it waits for the raster line of the entry.
|
|
75
|
+
5. It sets $00/$01, the stack pointer, A, X, Y and the flags, and jumps to the entry.
|
|
74
76
|
|
|
75
|
-
The flags go through the free stack byte below the stack pointer (`stackByte`). The loader changes this byte. The loader does not set the
|
|
77
|
+
The flags go through the free stack byte below the stack pointer (`stackByte`). The loader changes this byte. The loader does not set the SID, the CIA timers or the interrupt registers of the VIC-II. They stay as the KERNAL set them. Thus a scenario can still find a difference in an interrupt or a timer. Then use `runUntil` with `raster` in the c64-testing skill.
|
|
76
78
|
|
|
77
|
-
The script simulates the copies first. Then it runs `loader.prg` in the emulator to its jump to the entry. It compares the memory, $00/$01
|
|
79
|
+
The script simulates the copies first. Then it runs `loader.prg` in the emulator to its jump to the entry. It compares the memory, $00/$01, the registers, the display registers of the VIC-II, the VIC bank and the raster line with the entry state there. `loader.verified: true` tells that all of them are equal. An interrupt can run right at the entry, as in the original program.
|
|
78
80
|
|
|
79
81
|
## Knowledge
|
|
80
82
|
|
|
@@ -94,22 +96,26 @@ One JSON object:
|
|
|
94
96
|
- `files`: the files in the output directory.
|
|
95
97
|
- `instructions`, `dataBytes` and `undecodedBytes`.
|
|
96
98
|
- `labels`: `named` labels from the knowledge and `generated` labels `l_xxxx`.
|
|
99
|
+
- `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.
|
|
97
100
|
- `asBytes`: the instructions that the source has as bytes.
|
|
98
|
-
- `unusedModules`: modules
|
|
99
|
-
- `
|
|
101
|
+
- `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.
|
|
102
|
+
- `entryOutside`: the entry of `image.json` is in no segment of the image, with a `reason`. The image does not hold the program.
|
|
103
|
+
- `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.
|
|
100
104
|
- `check`: `identical`, and the result for each segment. A different segment gives its first different address.
|
|
101
105
|
- `loader`: the program, the copies in their order, the copier page, `stackByte`, `verified` and `differences`. 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.
|
|
102
106
|
|
|
103
|
-
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 and for an error. The exit status is 2 for a wrong argument.
|
|
107
|
+
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.
|
|
104
108
|
|
|
105
109
|
## Failure and conflicts
|
|
106
110
|
|
|
107
111
|
- `check.diagnostics`: ACME did not assemble the source. A name in the knowledge can be wrong for ACME. Read the diagnostics and change the name with c64-knowledge.
|
|
108
112
|
- `check.identical: false`: the script has a defect. Give the result to the user.
|
|
109
113
|
- `invalid-input` that names a symbol or a module: change that symbol or module in the knowledge.
|
|
114
|
+
- `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.
|
|
115
|
+
- `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.
|
|
110
116
|
- `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.
|
|
111
117
|
- `loader.verified: false` with `differences`: the loader ran, and `differences` gives the bytes and registers that are not equal.
|
|
112
|
-
- `installation-incomplete`, or 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 disassembler or emulator script.
|
|
118
|
+
- `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 disassembler or emulator script.
|
|
113
119
|
|
|
114
120
|
## Handoffs
|
|
115
121
|
|
|
@@ -25,9 +25,9 @@ import { GHIDRA_LIMITS, type AssembledSymbol, type Diagnostic } from "#src/nativ
|
|
|
25
25
|
import { platformNames } from "#src/platform.ts";
|
|
26
26
|
import { resolveProjectPath } from "#src/project.ts";
|
|
27
27
|
import { runScript, UsageError } from "#src/script.ts";
|
|
28
|
-
import { LOADER_JUMP, planLoader, readEntryState, simulate, type EntryState, type LoaderPlan } from "./loader.ts";
|
|
28
|
+
import { LOADER_JUMP, planLoader, rasterLine, readEntryState, simulate, VIC_DISPLAY_REGISTERS, type EntryState, type LoaderPlan } from "./loader.ts";
|
|
29
29
|
import { OPCODES } from "./opcodes.ts";
|
|
30
|
-
import { instructionText, layout, literalOperand, probeForms, render, type Layout, type Range } from "./source.ts";
|
|
30
|
+
import { entriesInData, entryOutsideImage, instructionText, layout, literalOperand, probeForms, render, type Layout, type Range } from "./source.ts";
|
|
31
31
|
|
|
32
32
|
const USAGE = `generate.ts <image> --out <dir> [options]
|
|
33
33
|
|
|
@@ -36,7 +36,8 @@ const USAGE = `generate.ts <image> --out <dir> [options]
|
|
|
36
36
|
--out <dir> the directory for the source files, relative to the project
|
|
37
37
|
--entry <address> an entry point for Ghidra, for example '$080d' (repeatable)
|
|
38
38
|
--coverage <file> a coverage map of 64 KiB from c64_memmap (repeatable): code
|
|
39
|
-
that no run executed is marked and listed
|
|
39
|
+
that no run executed is marked and listed; <dir>/not_executed.json
|
|
40
|
+
has all of its ranges
|
|
40
41
|
--space <space> the knowledge space of the image: c64 (the default), drive8, or
|
|
41
42
|
bank0 to bank255; --coverage and --loader are for c64 only
|
|
42
43
|
--loader also write loader.asm and loader.prg: the loader copies the
|
|
@@ -276,6 +277,15 @@ async function runLoader(path: string, program: Buffer, plan: LoaderPlan, image:
|
|
|
276
277
|
differ("sp", registers.sp, state.sp);
|
|
277
278
|
const flags = (["n", "v", "d", "i", "z", "c"] as const).reduce((value, name, index) => value | (registers.flags[name] ? [0x80, 0x40, 0x08, 0x04, 0x02, 0x01][index]! : 0), 0);
|
|
278
279
|
differ("flags", flags, state.flags & 0xcf);
|
|
280
|
+
if (state.vic !== undefined) {
|
|
281
|
+
// The display registers and the raster line (D60): the loader waited for the line, so the jump comes in it or the next one.
|
|
282
|
+
const vic = Buffer.from((await client.ioRead(0xd000, 0x2f)).data, "hex");
|
|
283
|
+
for (const { register, mask } of VIC_DISPLAY_REGISTERS) differ(formatC64Address(0xd000 + register), vic[register]! & mask, state.vic.registers[register]! & mask);
|
|
284
|
+
differ("the VIC bank", Buffer.from((await client.ioRead(0xdd00, 1)).data, "hex")[0]! & 0x03, state.vic.bank);
|
|
285
|
+
const line = rasterLine(vic);
|
|
286
|
+
const wanted = rasterLine(state.vic.registers);
|
|
287
|
+
if (line !== wanted && line !== wanted + 1) differences.push(`the raster line is ${line}, not ${wanted}`);
|
|
288
|
+
}
|
|
279
289
|
return { verified: differences.length === 0, differences: differences.slice(0, MAX_LISTED) };
|
|
280
290
|
} finally {
|
|
281
291
|
await client.close();
|
|
@@ -284,8 +294,10 @@ async function runLoader(path: string, program: Buffer, plan: LoaderPlan, image:
|
|
|
284
294
|
|
|
285
295
|
/** The first words of every file that the script writes. */
|
|
286
296
|
const GENERATED_MARK = "; Generated by c64-source";
|
|
297
|
+
/** The file with every range of instructions that no run executed (D49). */
|
|
298
|
+
const NOT_EXECUTED_FILE = "not_executed.json";
|
|
287
299
|
|
|
288
|
-
/** Removes the .asm files of an earlier run that this run does not write, and an earlier loader.prg; other files stay. */
|
|
300
|
+
/** Removes the .asm files of an earlier run that this run does not write, and an earlier loader.prg and not_executed.json; other files stay. */
|
|
289
301
|
function removeOldFiles(out: string, keep: ReadonlySet<string>): void {
|
|
290
302
|
const directory = resolveProjectPath(out);
|
|
291
303
|
if (!existsSync(directory)) return;
|
|
@@ -293,7 +305,7 @@ function removeOldFiles(out: string, keep: ReadonlySet<string>): void {
|
|
|
293
305
|
if (!entry.isFile()) continue;
|
|
294
306
|
const name = entry.name;
|
|
295
307
|
const path = join(directory, name);
|
|
296
|
-
if (name === "loader.prg") rmSync(path, { force: true });
|
|
308
|
+
if (name === "loader.prg" || name === NOT_EXECUTED_FILE) rmSync(path, { force: true });
|
|
297
309
|
else if (name.endsWith(".asm") && !keep.has(name) && readFileSync(path, "utf8").startsWith(GENERATED_MARK)) rmSync(path, { force: true });
|
|
298
310
|
}
|
|
299
311
|
}
|
|
@@ -344,16 +356,18 @@ async function run(argv: string[]): Promise<{ output: Record<string, unknown>; f
|
|
|
344
356
|
referenceTargets: knowledge.references.map((reference) => reference.to),
|
|
345
357
|
...(coverage === undefined ? {} : { coverage }),
|
|
346
358
|
// The platform names are C64 names: drive code and banks get none.
|
|
347
|
-
...(space === DEFAULT_SPACE ? { platform:
|
|
359
|
+
...(space === DEFAULT_SPACE ? { platform: platformNames() } : {}),
|
|
348
360
|
});
|
|
349
361
|
const asBytes = await probe(laid);
|
|
350
362
|
const header = [`Generated by c64-source from ${image.path}${space === DEFAULT_SPACE ? "" : ` in space ${space}`} and knowledge revision ${knowledge.revision}.`, "Change names, comments, regions and modules in knowledge, then generate again."];
|
|
351
363
|
const rendered = render(laid, asBytes, header);
|
|
352
364
|
const files = [...rendered.files];
|
|
353
365
|
let loader: LoaderPlan | undefined;
|
|
366
|
+
// An image without its entry has no program: the source is still written and checked, but the run fails (R2-02).
|
|
367
|
+
const missingEntry = entryOutsideImage(image.entry, image.segments);
|
|
354
368
|
// A loader that cannot be planned does not stop the source: the source is written and checked, and the result says why.
|
|
355
|
-
let loaderRefused: string | undefined;
|
|
356
|
-
if (state !== undefined) {
|
|
369
|
+
let loaderRefused: string | undefined = state === undefined ? undefined : missingEntry;
|
|
370
|
+
if (state !== undefined && missingEntry === undefined) {
|
|
357
371
|
const segments = image.segments.map((segment) => ({ ...segment, files: laid.pieces.filter((piece) => piece.start >= segment.start && piece.start <= segment.end).map((piece) => piece.file) }));
|
|
358
372
|
try {
|
|
359
373
|
loader = planLoader({ segments, byteAt: image.byteAt, state, entry: image.entry!, header: [`Loader generated by c64-source from ${image.path}.`, "It copies the segments into place, sets the entry state and jumps to the entry."] });
|
|
@@ -367,6 +381,12 @@ async function run(argv: string[]): Promise<{ output: Record<string, unknown>; f
|
|
|
367
381
|
for (const [index, file] of files.entries()) if (!file.text.startsWith(GENERATED_MARK)) files[index] = { ...file, text: `${GENERATED_MARK}. Do not edit: change the knowledge and generate again.\n${file.text}` };
|
|
368
382
|
removeOldFiles(values.out, new Set(files.map((file) => file.name)));
|
|
369
383
|
for (const file of files) writeProjectFile(`${values.out}/${file.name}`, Buffer.from(file.text, "utf8"), { what: "--out" });
|
|
384
|
+
const showRange = (range: Range) => ({ start: formatC64Address(range.start), end: formatC64Address(range.end) });
|
|
385
|
+
const inData = entriesInData(entries, knowledge.regions);
|
|
386
|
+
if (rendered.notExecuted !== undefined) {
|
|
387
|
+
const all = { image: image.path, revision: knowledge.revision, instructions: rendered.notExecuted.instructions, ranges: rendered.notExecuted.ranges.map(showRange) };
|
|
388
|
+
writeProjectFile(`${values.out}/${NOT_EXECUTED_FILE}`, Buffer.from(`${JSON.stringify(all, null, 2)}\n`, "utf8"), { what: "--out" });
|
|
389
|
+
}
|
|
370
390
|
|
|
371
391
|
const output: Record<string, unknown> = {
|
|
372
392
|
out: values.out,
|
|
@@ -375,7 +395,12 @@ async function run(argv: string[]): Promise<{ output: Record<string, unknown>; f
|
|
|
375
395
|
instructions: rendered.instructions,
|
|
376
396
|
dataBytes: rendered.dataBytes,
|
|
377
397
|
...(rendered.undecodedBytes === 0 ? {} : { undecodedBytes: rendered.undecodedBytes }),
|
|
398
|
+
...(missingEntry === undefined ? {} : { entryOutside: { entry: formatC64Address(image.entry!), reason: missingEntry } }),
|
|
378
399
|
labels: { named: laid.namedLabels, generated: laid.generatedLabels },
|
|
400
|
+
// An --entry in a data region decodes nothing: the result says so, and a code region in the knowledge changes it (F-27).
|
|
401
|
+
...(inData.length === 0
|
|
402
|
+
? {}
|
|
403
|
+
: { entriesInData: inData.map(({ entry, region }) => ({ entry: formatC64Address(entry), region: { ...showRange(region), type: region.type }, origin: region.origin })) }),
|
|
379
404
|
...(rendered.asBytes.length === 0
|
|
380
405
|
? {}
|
|
381
406
|
: { asBytes: { count: rendered.asBytes.length, listed: rendered.asBytes.slice(0, MAX_LISTED).map((item) => ({ address: formatC64Address(item.address), instruction: item.text })) } }),
|
|
@@ -385,7 +410,8 @@ async function run(argv: string[]): Promise<{ output: Record<string, unknown>; f
|
|
|
385
410
|
notExecuted: {
|
|
386
411
|
instructions: rendered.notExecuted.instructions,
|
|
387
412
|
ranges: rendered.notExecuted.ranges.length,
|
|
388
|
-
listed: rendered.notExecuted.ranges.slice(0, MAX_LISTED).map(
|
|
413
|
+
listed: rendered.notExecuted.ranges.slice(0, MAX_LISTED).map(showRange),
|
|
414
|
+
all: `${values.out}/${NOT_EXECUTED_FILE}`,
|
|
389
415
|
},
|
|
390
416
|
}),
|
|
391
417
|
...(laid.unusedModules.length === 0 ? {} : { unusedModules: laid.unusedModules.map((module) => ({ start: formatC64Address(module.start), file: module.file })) }),
|
|
@@ -400,7 +426,7 @@ async function run(argv: string[]): Promise<{ output: Record<string, unknown>; f
|
|
|
400
426
|
output.check = checked;
|
|
401
427
|
if (!checked.identical) return { output: { ...output, ...notBuilt("The source does not assemble to the image, and the loader includes it.") }, failed: true };
|
|
402
428
|
if (loaderRefused !== undefined) return { output: { ...output, loader: { verified: false, reason: loaderRefused } }, failed: true };
|
|
403
|
-
if (loader === undefined || state === undefined) return { output, failed:
|
|
429
|
+
if (loader === undefined || state === undefined) return { output, failed: missingEntry !== undefined };
|
|
404
430
|
|
|
405
431
|
const loaderBuilt = await assembleFiles(files, "loader.asm");
|
|
406
432
|
if (loaderBuilt.program === undefined) return { output: { ...output, loader: { verified: false, reason: "loader.asm does not assemble.", diagnostics: loaderBuilt.diagnostics.slice(0, MAX_LISTED) } }, failed: true };
|