@henols/c64-re-tools 2.0.0-rc.9 → 2.0.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/README.md +97 -116
- package/package.json +1 -1
- package/skills/c64-assembler/SKILL.md +22 -6
- package/skills/c64-assembler/scripts/assemble.ts +42 -14
- package/skills/c64-assembler/scripts/lib/compare.ts +48 -0
- package/skills/c64-basic/SKILL.md +10 -1
- package/skills/c64-basic/scripts/basic.ts +3 -3
- package/skills/c64-disk/SKILL.md +13 -4
- package/skills/c64-disk/scripts/disk.ts +7 -7
- package/skills/c64-disk/scripts/{containers.ts → lib/containers.ts} +2 -2
- package/skills/c64-docs/SKILL.md +138 -0
- package/skills/c64-docs/references/page-types.md +141 -0
- package/skills/c64-docs/references/style.md +43 -0
- package/skills/c64-docs/scripts/generate.ts +109 -0
- package/skills/c64-docs/scripts/lib/links.ts +167 -0
- package/skills/c64-docs/scripts/lib/markdown.ts +79 -0
- package/skills/c64-docs/scripts/lib/reference.ts +406 -0
- package/skills/c64-docs/scripts/lib/source-map.ts +81 -0
- package/skills/c64-docs/scripts/link.ts +239 -0
- package/skills/c64-emulator/SKILL.md +16 -5
- package/skills/c64-knowledge/SKILL.md +140 -18
- package/skills/c64-knowledge/scripts/knowledge.ts +737 -112
- package/skills/c64-knowledge/scripts/lib/listing.ts +162 -0
- package/skills/c64-memory-map/SKILL.md +11 -2
- package/skills/c64-memory-map/scripts/memmap.ts +2 -2
- package/skills/c64-provenance/SKILL.md +66 -11
- package/skills/c64-provenance/scripts/{compare.ts → lib/compare.ts} +3 -3
- package/skills/c64-provenance/scripts/lib/library.ts +584 -0
- package/skills/c64-provenance/scripts/{runtime.ts → lib/runtime.ts} +2 -2
- package/skills/c64-provenance/scripts/{separate.ts → lib/separate.ts} +3 -3
- package/skills/c64-provenance/scripts/provenance.ts +242 -40
- package/skills/c64-reverse-engineering/SKILL.md +51 -26
- package/skills/c64-source/SKILL.md +76 -28
- package/skills/c64-source/scripts/generate.ts +425 -127
- package/skills/c64-source/scripts/{loader.ts → lib/loader.ts} +84 -25
- package/skills/c64-source/scripts/lib/source.ts +1121 -0
- package/skills/c64-static-analysis/SKILL.md +35 -18
- package/skills/c64-static-analysis/scripts/analyze.ts +184 -55
- package/skills/c64-static-analysis/scripts/{coverage.ts → lib/coverage.ts} +19 -25
- package/skills/c64-static-analysis/scripts/{findings.ts → lib/findings.ts} +6 -2
- package/skills/c64-static-analysis/scripts/{idioms.ts → lib/idioms.ts} +21 -12
- package/skills/c64-static-analysis/scripts/{instructions.ts → lib/instructions.ts} +1 -1
- package/skills/c64-static-analysis/scripts/lib/macros.ts +233 -0
- package/skills/c64-static-analysis/scripts/lib/pitfalls.ts +68 -0
- package/skills/c64-static-analysis/scripts/lib/pointers.ts +108 -0
- package/skills/c64-testing/SKILL.md +36 -7
- package/skills/c64-testing/references/scenario-format.md +12 -8
- package/skills/c64-testing/scripts/checklist.ts +3 -3
- package/skills/c64-testing/scripts/diff.ts +167 -0
- package/skills/c64-testing/scripts/lib/diff.ts +70 -0
- package/skills/c64-testing/scripts/{scenario.ts → lib/scenario.ts} +45 -34
- package/skills/c64-testing/scripts/run.ts +73 -21
- package/skills/c64-testing/scripts/test.ts +5 -5
- package/skills/c64-unpacker/SKILL.md +23 -9
- package/skills/c64-unpacker/scripts/{evidence.ts → lib/evidence.ts} +1 -1
- package/skills/c64-unpacker/scripts/{loads.ts → lib/loads.ts} +4 -4
- package/skills/c64-unpacker/scripts/{machine.ts → lib/machine.ts} +4 -4
- package/skills/c64-unpacker/scripts/{stages.ts → lib/stages.ts} +17 -5
- package/skills/c64-unpacker/scripts/unpack.ts +41 -24
- package/src/c64.ts +103 -5
- package/src/cli/cleanup.ts +1 -1
- package/src/cli/main.ts +7 -4
- package/src/cli/status.ts +70 -3
- package/src/graphics.ts +158 -0
- package/src/host/main.ts +18 -4
- package/src/host/server.ts +10 -3
- package/src/host/tools/petcat.ts +1 -1
- package/src/host/tools/status.ts +1 -1
- package/src/host/vice/adapter.ts +4 -3
- package/src/host/vice/process.ts +2 -2
- package/src/host/vice/screen.ts +1 -42
- package/src/host/vice/session.ts +196 -23
- package/src/host-client/autostart.ts +1 -1
- package/src/host-client/connect.ts +1 -1
- package/src/host-client/coverage.ts +2 -29
- package/src/host-client/image.ts +66 -0
- package/src/host-client/steps.ts +9 -4
- package/src/host-client/tools.ts +1 -0
- package/src/host-client/transfer.ts +39 -11
- package/src/host-client/vice-session.ts +33 -16
- package/src/knowledge/conflicts.ts +161 -0
- package/src/knowledge/database.ts +150 -38
- package/src/knowledge/expression.ts +209 -0
- package/src/knowledge/graph.ts +104 -0
- package/src/knowledge/history.ts +70 -22
- package/src/knowledge/import.ts +64 -55
- package/src/knowledge/join.ts +211 -0
- package/src/knowledge/program.ts +266 -0
- package/src/knowledge/read.ts +258 -27
- package/src/knowledge/schema.ts +199 -75
- package/src/knowledge/text.ts +415 -0
- package/src/knowledge/write.ts +740 -57
- package/src/mcp/main.ts +1 -1
- package/src/mcp/output.ts +15 -8
- package/src/mcp/server.ts +3 -1
- package/src/mcp/tools/debug.ts +97 -16
- package/src/mcp/tools/execution.ts +38 -5
- package/src/mcp/tools/machine.ts +11 -2
- package/src/mcp/tools/media.ts +18 -13
- package/src/mcp/tools/memory.ts +17 -8
- package/src/mcp/tools/video.ts +9 -9
- package/src/native/discover.ts +2 -2
- package/src/native/ghidra/analyze.ts +2 -2
- package/src/native/ghidra/index.ts +1 -1
- package/src/native/ghidra/language/c64rt_6510.slaspec +4 -1
- package/src/native/ghidra/scripts/C64Export.java +1 -1
- package/src/native/ghidra/scripts/C64Prepare.java +7 -1
- package/src/native/types.ts +14 -8
- package/{skills/c64-source/scripts → src}/opcodes.ts +75 -3
- package/src/platform.ts +70 -7
- package/src/project.ts +180 -3
- package/src/protocol/messages.ts +1 -1
- package/src/protocol/tools.ts +2 -16
- package/src/protocol/vice.ts +124 -15
- package/src/script.ts +46 -7
- package/skills/c64-assembler/scripts/compare.ts +0 -84
- package/skills/c64-source/scripts/source.ts +0 -595
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
// The program in the knowledge: its bytes, its entry with the machine
|
|
2
|
+
// state and the stages of its unpacker, and the coverage of the runs. A
|
|
3
|
+
// capture and the coverage maps are imported one time; every later step reads
|
|
4
|
+
// them here, so a project needs no image or coverage files to be made again.
|
|
5
|
+
|
|
6
|
+
import { createHash } from "node:crypto";
|
|
7
|
+
import type { DatabaseSync } from "node:sqlite";
|
|
8
|
+
|
|
9
|
+
import { COVERAGE } from "../c64.ts";
|
|
10
|
+
import { KnowledgeError } from "./database.ts";
|
|
11
|
+
import { COVERAGE_KINDS, DEFAULT_SPACE, MEMORY_ROW, type CoverageKind, type Space } from "./schema.ts";
|
|
12
|
+
import { checkSpace, hex, write, type AddressRange, type Evidence, type WriteContext } from "./write.ts";
|
|
13
|
+
|
|
14
|
+
/** The 64 KiB of one space: offset n is address n. */
|
|
15
|
+
const SIZE = 0x10000;
|
|
16
|
+
|
|
17
|
+
/** The coverage map flag of each kind; wx has its own map. */
|
|
18
|
+
const KIND_BITS: Record<Exclude<CoverageKind, "wx">, number> = {
|
|
19
|
+
r: COVERAGE.ramRead,
|
|
20
|
+
w: COVERAGE.ramWrite,
|
|
21
|
+
x: COVERAGE.ramExecute,
|
|
22
|
+
"rom-r": COVERAGE.romRead,
|
|
23
|
+
"rom-x": COVERAGE.romExecute,
|
|
24
|
+
"io-r": COVERAGE.ioRead,
|
|
25
|
+
"io-w": COVERAGE.ioWrite,
|
|
26
|
+
"io-x": COVERAGE.ioExecute,
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
/** The program of one space as the knowledge holds it. */
|
|
30
|
+
export interface ProgramImage {
|
|
31
|
+
space: Space;
|
|
32
|
+
/** The address ranges that hold bytes, in address order, without overlap. */
|
|
33
|
+
segments: AddressRange[];
|
|
34
|
+
/** 64 KiB; an address outside the segments holds 0. */
|
|
35
|
+
memory: Uint8Array;
|
|
36
|
+
/** The byte at an address inside a segment. */
|
|
37
|
+
byteAt: (address: number) => number;
|
|
38
|
+
entry?: number;
|
|
39
|
+
/** The machine state at the entry: the content of the entry_state.json of the c64-unpacker skill. */
|
|
40
|
+
state?: Record<string, unknown>;
|
|
41
|
+
/** The stages of the unpacker capture, in order: the last one is the program. */
|
|
42
|
+
stages?: Array<Record<string, unknown>>;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** What `setImage` writes: the bytes of the segments, and the entry when the program has one. */
|
|
46
|
+
export interface ImageRequest {
|
|
47
|
+
space?: Space;
|
|
48
|
+
segments: AddressRange[];
|
|
49
|
+
byteAt: (address: number) => number;
|
|
50
|
+
entry?: number;
|
|
51
|
+
state?: Record<string, unknown>;
|
|
52
|
+
stages?: Array<Record<string, unknown>>;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/** One coverage map to import, with the project path that names it as evidence. */
|
|
56
|
+
export interface CoverageMap {
|
|
57
|
+
name: string;
|
|
58
|
+
bytes: Uint8Array;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/** The address ranges where `has` is true, in address order. */
|
|
62
|
+
function rangesWhere(has: (address: number) => boolean): AddressRange[] {
|
|
63
|
+
const ranges: AddressRange[] = [];
|
|
64
|
+
for (let address = 0; address < SIZE; address++) {
|
|
65
|
+
if (!has(address)) continue;
|
|
66
|
+
const start = address;
|
|
67
|
+
while (address + 1 < SIZE && has(address + 1)) address++;
|
|
68
|
+
ranges.push({ start, end: address });
|
|
69
|
+
}
|
|
70
|
+
return ranges;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/** The memory rows of the segments: at most MEMORY_ROW bytes, never across a multiple of MEMORY_ROW. */
|
|
74
|
+
function memoryRows(segments: readonly AddressRange[], byteAt: (address: number) => number): Map<number, string> {
|
|
75
|
+
const rows = new Map<number, string>();
|
|
76
|
+
for (const segment of segments) {
|
|
77
|
+
for (let at = segment.start; at <= segment.end; ) {
|
|
78
|
+
const end = Math.min(segment.end, (at | (MEMORY_ROW - 1)));
|
|
79
|
+
rows.set(at, Buffer.from(Array.from({ length: end - at + 1 }, (_, index) => byteAt(at + index))).toString("hex"));
|
|
80
|
+
at = end + 1;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
return rows;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function checkSegments(segments: readonly AddressRange[]): void {
|
|
87
|
+
if (segments.length === 0) throw new KnowledgeError("invalid-input", "An image has at least one segment.");
|
|
88
|
+
const sorted = [...segments].sort((a, b) => a.start - b.start);
|
|
89
|
+
sorted.forEach((segment, index) => {
|
|
90
|
+
if (!Number.isInteger(segment.start) || !Number.isInteger(segment.end) || segment.start < 0 || segment.end > 0xffff || segment.end < segment.start) {
|
|
91
|
+
throw new KnowledgeError("invalid-input", `The segment ${hex(segment.start)}-${hex(segment.end)} is not a range from $0000 to $ffff.`);
|
|
92
|
+
}
|
|
93
|
+
if (index > 0 && segment.start <= sorted[index - 1]!.end) throw new KnowledgeError("invalid-input", `The segments at ${hex(sorted[index - 1]!.start)} and ${hex(segment.start)} overlap.`);
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Makes the program of a space equal to the image: closes the memory rows
|
|
99
|
+
* that changed or are gone, adds the new ones, and replaces the entry when it
|
|
100
|
+
* changed. An image that is already in the knowledge writes nothing.
|
|
101
|
+
*/
|
|
102
|
+
export function setImage(db: DatabaseSync, context: WriteContext, request: ImageRequest): { revision: string | null; current: { space: Space; bytes: number; rowsAdded: number; rowsClosed: number; entry: boolean } } {
|
|
103
|
+
checkSpace(request);
|
|
104
|
+
checkSegments(request.segments);
|
|
105
|
+
if (request.entry !== undefined && (!Number.isInteger(request.entry) || request.entry < 0 || request.entry > 0xffff)) throw new KnowledgeError("invalid-input", "The entry must be from $0000 to $ffff.");
|
|
106
|
+
if (request.entry === undefined && (request.state !== undefined || request.stages !== undefined)) throw new KnowledgeError("invalid-input", "A machine state or stages need an entry.");
|
|
107
|
+
const space = request.space ?? DEFAULT_SPACE;
|
|
108
|
+
const wanted = memoryRows(request.segments, request.byteAt);
|
|
109
|
+
return write(db, context, "image", (change) => {
|
|
110
|
+
const current = db.prepare("SELECT id, address, bytes FROM current_memory WHERE space = ?").all(space) as Array<{ id: string; address: number; bytes: string }>;
|
|
111
|
+
const kept = new Set<number>();
|
|
112
|
+
let rowsClosed = 0;
|
|
113
|
+
for (const row of current) {
|
|
114
|
+
if (wanted.get(row.address) === row.bytes) kept.add(row.address);
|
|
115
|
+
else {
|
|
116
|
+
change.close("memory", "id = ?", row.id);
|
|
117
|
+
rowsClosed++;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
let rowsAdded = 0;
|
|
121
|
+
for (const [address, bytes] of [...wanted].sort(([a], [b]) => a - b)) {
|
|
122
|
+
if (kept.has(address)) continue;
|
|
123
|
+
change.insertMemory({ space, address, bytes, origin: context.origin });
|
|
124
|
+
rowsAdded++;
|
|
125
|
+
}
|
|
126
|
+
const entryRow = db.prepare("SELECT id, address, state, stages FROM current_entries WHERE space = ?").get(space) as { id: string; address: number; state: string | null; stages: string | null } | undefined;
|
|
127
|
+
const state = request.state === undefined ? null : JSON.stringify(request.state);
|
|
128
|
+
const stages = request.stages === undefined ? null : JSON.stringify(request.stages);
|
|
129
|
+
const same = entryRow !== undefined && entryRow.address === request.entry && entryRow.state === state && entryRow.stages === stages;
|
|
130
|
+
const entryChanged = !same && (entryRow !== undefined || request.entry !== undefined);
|
|
131
|
+
if (entryChanged) {
|
|
132
|
+
if (entryRow !== undefined) change.close("entries", "id = ?", entryRow.id);
|
|
133
|
+
if (request.entry !== undefined) change.insertEntry({ space, address: request.entry, state, stages, origin: context.origin });
|
|
134
|
+
}
|
|
135
|
+
const bytes = request.segments.reduce((sum, segment) => sum + segment.end - segment.start + 1, 0);
|
|
136
|
+
return { space, bytes, rowsAdded, rowsClosed, entry: entryChanged };
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/** The coverage of one space as ranges of each kind. */
|
|
141
|
+
function currentCoverage(db: DatabaseSync, space: Space): Map<CoverageKind, Uint8Array> {
|
|
142
|
+
const maps = new Map<CoverageKind, Uint8Array>(COVERAGE_KINDS.map((kind) => [kind, new Uint8Array(SIZE)]));
|
|
143
|
+
for (const row of db.prepare("SELECT kind, start_address, end_address FROM current_coverage WHERE space = ?").all(space) as Array<{ kind: CoverageKind; start_address: number; end_address: number }>) {
|
|
144
|
+
maps.get(row.kind)!.fill(1, row.start_address, row.end_address + 1);
|
|
145
|
+
}
|
|
146
|
+
return maps;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/** One import of a coverage map: its project path, its SHA-256, the space it went into and the revision. */
|
|
150
|
+
export interface CoverageRun {
|
|
151
|
+
name: string;
|
|
152
|
+
sha256: string;
|
|
153
|
+
space: Space;
|
|
154
|
+
revision: string;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/** The imports of coverage maps, by name and then oldest first: the last import of a name is the latest. */
|
|
158
|
+
export function coverageRuns(db: DatabaseSync | undefined): CoverageRun[] {
|
|
159
|
+
if (db === undefined) return [];
|
|
160
|
+
return db
|
|
161
|
+
.prepare(`SELECT DISTINCT e.ref AS name, e.sha256 AS sha256, coalesce(e.space, '${DEFAULT_SPACE}') AS space, e.revision AS revision FROM evidence e JOIN revisions r ON r.id = e.revision WHERE r.operation = 'coverage' AND e.kind = 'coverage' ORDER BY e.ref, e.revision`)
|
|
162
|
+
.all() as unknown as CoverageRun[];
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Joins coverage maps into the coverage of a space: adds the ranges of
|
|
167
|
+
* the flags that no imported run set before, and as wx the addresses that one
|
|
168
|
+
* run both wrote and executed. Each new map is evidence of the revision, with
|
|
169
|
+
* its space. A map that the space has with the same name and bytes is not
|
|
170
|
+
* imported again.
|
|
171
|
+
*/
|
|
172
|
+
export function addCoverage(
|
|
173
|
+
db: DatabaseSync,
|
|
174
|
+
context: WriteContext,
|
|
175
|
+
request: { space?: Space; maps: CoverageMap[] },
|
|
176
|
+
): { revision: string | null; current: { space: Space; imported: string[]; known: string[]; ranges: number } } {
|
|
177
|
+
checkSpace(request);
|
|
178
|
+
const space = request.space ?? DEFAULT_SPACE;
|
|
179
|
+
for (const map of request.maps) {
|
|
180
|
+
if (map.bytes.length !== SIZE) throw new KnowledgeError("invalid-input", `${map.name} has ${map.bytes.length} bytes; a coverage map has 65536, one for each address.`);
|
|
181
|
+
}
|
|
182
|
+
const hashed = request.maps.map((map) => ({ ...map, sha256: createHash("sha256").update(map.bytes).digest("hex") }));
|
|
183
|
+
const known = new Set(coverageRuns(db).filter((run) => run.space === space).map((run) => `${run.name}\n${run.sha256}`));
|
|
184
|
+
const fresh = hashed.filter((map) => !known.has(`${map.name}\n${map.sha256}`));
|
|
185
|
+
const evidence: Evidence[] = [...(context.evidence ?? []), ...fresh.map((map): Evidence => ({ kind: "coverage", ref: map.name, sha256: map.sha256, ...(space === DEFAULT_SPACE ? {} : { space }) }))];
|
|
186
|
+
return write(db, { ...context, evidence }, "coverage", (change) => {
|
|
187
|
+
const result = { space, imported: fresh.map((map) => map.name), known: hashed.filter((map) => !fresh.includes(map)).map((map) => map.name), ranges: 0 };
|
|
188
|
+
if (fresh.length === 0) return result;
|
|
189
|
+
// A revision for each import, also when no flag is new: the maps are its evidence.
|
|
190
|
+
change.id();
|
|
191
|
+
const union = currentCoverage(db, space);
|
|
192
|
+
for (const map of fresh) {
|
|
193
|
+
for (const kind of COVERAGE_KINDS) {
|
|
194
|
+
const has = kind === "wx" ? (address: number) => (map.bytes[address]! & (COVERAGE.ramWrite | COVERAGE.ramExecute)) === (COVERAGE.ramWrite | COVERAGE.ramExecute) : (address: number) => (map.bytes[address]! & KIND_BITS[kind]) !== 0;
|
|
195
|
+
const known = union.get(kind)!;
|
|
196
|
+
for (const range of rangesWhere((address) => known[address] === 0 && has(address))) {
|
|
197
|
+
change.insertCoverage({ space, kind, start: range.start, end: range.end, origin: context.origin });
|
|
198
|
+
known.fill(1, range.start, range.end + 1);
|
|
199
|
+
result.ranges++;
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
return result;
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/** The program of a space, or undefined when the knowledge has no bytes for it. */
|
|
208
|
+
export function programImage(db: DatabaseSync | undefined, space: Space = DEFAULT_SPACE): ProgramImage | undefined {
|
|
209
|
+
if (db === undefined) return undefined;
|
|
210
|
+
const rows = db.prepare("SELECT address, bytes FROM current_memory WHERE space = ? ORDER BY address").all(space) as Array<{ address: number; bytes: string }>;
|
|
211
|
+
if (rows.length === 0) return undefined;
|
|
212
|
+
const memory = new Uint8Array(SIZE);
|
|
213
|
+
const segments: AddressRange[] = [];
|
|
214
|
+
for (const row of rows) {
|
|
215
|
+
const bytes = Buffer.from(row.bytes, "hex");
|
|
216
|
+
memory.set(bytes, row.address);
|
|
217
|
+
const end = row.address + bytes.length - 1;
|
|
218
|
+
const last = segments.at(-1);
|
|
219
|
+
if (last !== undefined && last.end + 1 === row.address) last.end = end;
|
|
220
|
+
else segments.push({ start: row.address, end });
|
|
221
|
+
}
|
|
222
|
+
const image: ProgramImage = { space, segments, memory, byteAt: (address) => memory[address]! };
|
|
223
|
+
const entry = db.prepare("SELECT address, state, stages FROM current_entries WHERE space = ?").get(space) as { address: number; state: string | null; stages: string | null } | undefined;
|
|
224
|
+
if (entry !== undefined) {
|
|
225
|
+
image.entry = entry.address;
|
|
226
|
+
if (entry.state !== null) image.state = JSON.parse(entry.state) as Record<string, unknown>;
|
|
227
|
+
if (entry.stages !== null) image.stages = JSON.parse(entry.stages) as Array<Record<string, unknown>>;
|
|
228
|
+
}
|
|
229
|
+
return image;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
/** The program of a space for a tool; refused with the way to put it in when the knowledge has none. */
|
|
233
|
+
export function requireProgram(db: DatabaseSync | undefined, space: Space = DEFAULT_SPACE): ProgramImage {
|
|
234
|
+
const image = programImage(db, space);
|
|
235
|
+
if (image !== undefined) return image;
|
|
236
|
+
const where = space === DEFAULT_SPACE ? "" : ` in ${space}`;
|
|
237
|
+
const option = space === DEFAULT_SPACE ? "" : ` --space ${space}`;
|
|
238
|
+
throw new KnowledgeError("not-found", `The knowledge has no program${where}. Put it in with knowledge.ts image <image.json|prg>${option} of the c64-knowledge skill.`);
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
/** The bytes for an analyzer: a PRG for one segment without an entry state, else the 64 KiB. */
|
|
242
|
+
export function analyzedBytes(image: ProgramImage): { bytes: Buffer; kind: "prg" | "flat64k" } {
|
|
243
|
+
if (image.segments.length === 1 && image.state === undefined) {
|
|
244
|
+
const [segment] = image.segments;
|
|
245
|
+
return { bytes: Buffer.concat([Buffer.from([segment!.start & 0xff, segment!.start >> 8]), Buffer.from(image.memory.subarray(segment!.start, segment!.end + 1))]), kind: "prg" };
|
|
246
|
+
}
|
|
247
|
+
return { bytes: Buffer.from(image.memory), kind: "flat64k" };
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
/**
|
|
251
|
+
* The coverage of a space as a joined coverage map (the bits of COVERAGE in
|
|
252
|
+
* c64.ts), and the addresses that one run both wrote and executed; undefined
|
|
253
|
+
* when the knowledge has no coverage for the space.
|
|
254
|
+
*/
|
|
255
|
+
export function programCoverage(db: DatabaseSync | undefined, space: Space = DEFAULT_SPACE): { map: Uint8Array; selfModified: Uint8Array } | undefined {
|
|
256
|
+
if (db === undefined) return undefined;
|
|
257
|
+
const rows = db.prepare("SELECT kind, start_address, end_address FROM current_coverage WHERE space = ?").all(space) as Array<{ kind: CoverageKind; start_address: number; end_address: number }>;
|
|
258
|
+
if (rows.length === 0) return undefined;
|
|
259
|
+
const map = new Uint8Array(SIZE);
|
|
260
|
+
const selfModified = new Uint8Array(SIZE);
|
|
261
|
+
for (const row of rows) {
|
|
262
|
+
if (row.kind === "wx") selfModified.fill(1, row.start_address, row.end_address + 1);
|
|
263
|
+
else for (let address = row.start_address; address <= row.end_address; address++) map[address]! |= KIND_BITS[row.kind];
|
|
264
|
+
}
|
|
265
|
+
return { map, selfModified };
|
|
266
|
+
}
|