@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
|
@@ -7,10 +7,11 @@
|
|
|
7
7
|
import { mkdirSync, readdirSync, readFileSync, renameSync, statSync, writeFileSync } from "node:fs";
|
|
8
8
|
import { join, resolve } from "node:path";
|
|
9
9
|
|
|
10
|
+
import { Refusal } from "#src/errors.ts";
|
|
10
11
|
import { formatC64Address, instructionLength } from "#src/c64.ts";
|
|
11
|
-
import { evaluate,
|
|
12
|
+
import { evaluate, identifiers, operandText } from "#src/knowledge/expression.ts";
|
|
12
13
|
import type { CommentRow, OperandRow, ReferenceRow, RegionRow, SymbolRow } from "#src/knowledge/read.ts";
|
|
13
|
-
import { COMMENT_PLACEMENTS,
|
|
14
|
+
import { COMMENT_PLACEMENTS, type CommentPlacement, isSemantic, OPERAND_SELECTS, type OperandSelect, REFERENCE_KINDS, type ReferenceKind, REGION_TYPES, type RegionType, SYMBOL_KINDS, type SymbolKind } from "#src/knowledge/schema.ts";
|
|
14
15
|
import { PROJECT_DIRECTORY } from "#src/project.ts";
|
|
15
16
|
|
|
16
17
|
/** A routine shorter than this is not saved and not searched: its bytes occur by chance. */
|
|
@@ -22,17 +23,6 @@ const ROUTINE_NAME = /^[A-Za-z_][A-Za-z0-9_]{0,63}$/;
|
|
|
22
23
|
/** Bytes on each line of a routine file, so that git shows a small diff. */
|
|
23
24
|
const BYTES_PER_LINE = 16;
|
|
24
25
|
|
|
25
|
-
/** A refusal of the library rules, with a stable code. */
|
|
26
|
-
export class LibraryError extends Error {
|
|
27
|
-
override name = "LibraryError";
|
|
28
|
-
readonly code: "invalid-input" | "not-found" | "conflict";
|
|
29
|
-
|
|
30
|
-
constructor(code: "invalid-input" | "not-found" | "conflict", message: string) {
|
|
31
|
-
super(message);
|
|
32
|
-
this.code = code;
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
|
|
36
26
|
/**
|
|
37
27
|
* Bytes that move with the routine: a word, or the low or high byte, of the
|
|
38
28
|
* address at `value` (an offset from the routine start; -1 for the RTS entry
|
|
@@ -110,9 +100,9 @@ export interface LeftOut {
|
|
|
110
100
|
export function routineFrom(source: RoutineSource): { routine: Routine; leftOut: LeftOut[] } {
|
|
111
101
|
const { start, end } = source;
|
|
112
102
|
const where = (address: number) => formatC64Address(address);
|
|
113
|
-
if (!ROUTINE_NAME.test(source.name)) throw new
|
|
103
|
+
if (!ROUTINE_NAME.test(source.name)) throw new Refusal("invalid-input", `A routine name is a letter or underscore, then letters, digits or underscores (at most 64), not ${source.name}.`);
|
|
114
104
|
const length = end - start + 1;
|
|
115
|
-
if (length < MIN_ROUTINE) throw new
|
|
105
|
+
if (length < MIN_ROUTINE) throw new Refusal("invalid-input", `${where(start)}-${where(end)} has ${length} bytes. A routine has at least ${MIN_ROUTINE}, because shorter byte sequences occur by chance.`);
|
|
116
106
|
const inside = (address: number) => address >= start && address <= end;
|
|
117
107
|
const bytes = Uint8Array.from({ length }, (_, index) => source.byteAt(start + index));
|
|
118
108
|
const leftOut: LeftOut[] = [];
|
|
@@ -125,11 +115,11 @@ export function routineFrom(source: RoutineSource): { routine: Routine; leftOut:
|
|
|
125
115
|
for (const region of codeRegions) {
|
|
126
116
|
for (let at = Math.max(region.start, next); at <= Math.min(region.end, end); at = next) {
|
|
127
117
|
next = at + instructionLength(bytes[at - start]!);
|
|
128
|
-
if (next - 1 > end) throw new
|
|
118
|
+
if (next - 1 > end) throw new Refusal("invalid-input", `The instruction at ${where(at)} runs past the end ${where(end)} of the routine. Give an end after it.`);
|
|
129
119
|
instructions.push(at - start);
|
|
130
120
|
}
|
|
131
121
|
}
|
|
132
|
-
if (instructions.length === 0) throw new
|
|
122
|
+
if (instructions.length === 0) throw new Refusal("invalid-input", `The knowledge has no code region in ${where(start)}-${where(end)}. Run c64-static-analysis first, or classify the code.`);
|
|
133
123
|
|
|
134
124
|
// The operands: an address inside the routine moves with it; one outside may differ.
|
|
135
125
|
const relocations = new Map<number, Relocation>();
|
|
@@ -246,7 +236,7 @@ export function routineText(routine: Routine): string {
|
|
|
246
236
|
/** Reads the text of a routine file. Throws LibraryError for a file that is no routine. */
|
|
247
237
|
export function parseRoutine(text: string, file: string): Routine {
|
|
248
238
|
const fail = (what: string): never => {
|
|
249
|
-
throw new
|
|
239
|
+
throw new Refusal("invalid-input", `${file} is not a routine file of the library: ${what}.`);
|
|
250
240
|
};
|
|
251
241
|
let value: Record<string, unknown>;
|
|
252
242
|
try {
|
|
@@ -305,15 +295,15 @@ export function parseRoutine(text: string, file: string): Routine {
|
|
|
305
295
|
* `create`, a missing folder is made.
|
|
306
296
|
*/
|
|
307
297
|
export function libraryFolder(folder: string, options: { create: boolean; cwd?: string }): string {
|
|
308
|
-
if (folder.length === 0) throw new
|
|
298
|
+
if (folder.length === 0) throw new Refusal("invalid-input", "The library folder must not be empty.");
|
|
309
299
|
const path = resolve(options.cwd ?? process.cwd(), folder);
|
|
310
300
|
const stat = statSync(path, { throwIfNoEntry: false });
|
|
311
301
|
if (stat === undefined) {
|
|
312
|
-
if (!options.create) throw new
|
|
302
|
+
if (!options.create) throw new Refusal("not-found", `There is no library folder ${folder}. Save a routine to it first with routine.`);
|
|
313
303
|
mkdirSync(path, { recursive: true });
|
|
314
|
-
} else if (!stat.isDirectory()) throw new
|
|
304
|
+
} else if (!stat.isDirectory()) throw new Refusal("invalid-input", `${folder} is not a folder.`);
|
|
315
305
|
if (statSync(join(path, PROJECT_DIRECTORY), { throwIfNoEntry: false }) !== undefined) {
|
|
316
|
-
throw new
|
|
306
|
+
throw new Refusal("invalid-input", `${folder} is a project folder. A library is a plain folder beside the projects, for example library/ at the workspace root.`);
|
|
317
307
|
}
|
|
318
308
|
return path;
|
|
319
309
|
}
|
|
@@ -331,7 +321,7 @@ export function readLibrary(folder: string): Routine[] {
|
|
|
331
321
|
export function saveRoutine(folder: string, routine: Routine, replace: boolean): string {
|
|
332
322
|
const file = join(folder, `${routine.name}.json`);
|
|
333
323
|
if (!replace && statSync(file, { throwIfNoEntry: false }) !== undefined) {
|
|
334
|
-
throw new
|
|
324
|
+
throw new Refusal("conflict", `The library already has the routine ${routine.name}. Give another --name, or give --replace to save over it.`);
|
|
335
325
|
}
|
|
336
326
|
const temporary = `${file}.${process.pid}.tmp`;
|
|
337
327
|
writeFileSync(temporary, routineText(routine));
|
|
@@ -449,7 +439,7 @@ export interface Suggestions {
|
|
|
449
439
|
leftOut: Skipped[];
|
|
450
440
|
}
|
|
451
441
|
|
|
452
|
-
const semantic = (row: { origin: string }) => row.origin
|
|
442
|
+
const semantic = (row: { origin: string }) => isSemantic(row.origin);
|
|
453
443
|
|
|
454
444
|
/**
|
|
455
445
|
* The names, regions, comments, references and operand expressions of each
|
|
@@ -573,7 +563,7 @@ function operandProblem(address: number, text: string, image: Searched, resolveN
|
|
|
573
563
|
try {
|
|
574
564
|
value = evaluate(text, resolveName);
|
|
575
565
|
} catch (error) {
|
|
576
|
-
if (error instanceof
|
|
566
|
+
if (error instanceof Refusal) return error.message;
|
|
577
567
|
throw error;
|
|
578
568
|
}
|
|
579
569
|
const range = size === 2 ? 0x100 : 0x10000;
|
|
@@ -4,14 +4,15 @@
|
|
|
4
4
|
// Watchpoints and range breakpoints stop the run at an access; the coverage
|
|
5
5
|
// map then tells which range and which access it was.
|
|
6
6
|
|
|
7
|
-
import { COVERAGE, formatC64Address, instructionLength } from "#src/c64.ts";
|
|
7
|
+
import { type AddressRange, COVERAGE, formatC64Address, instructionLength } from "#src/c64.ts";
|
|
8
8
|
import { runProgram } from "#src/host-client/autostart.ts";
|
|
9
9
|
import { readCoverage } from "#src/host-client/coverage.ts";
|
|
10
|
+
import { startAtEntry, type EntryState } from "#src/host-client/entry-state.ts";
|
|
10
11
|
import { runActionStep, type ActionStep } from "#src/host-client/steps.ts";
|
|
11
|
-
import {
|
|
12
|
+
import { knowledgeAt } from "#src/knowledge/names.ts";
|
|
13
|
+
import { MAX_MEMORY_READ } from "#src/host-client/tools.ts";
|
|
12
14
|
import { readProjectFile } from "#src/host-client/transfer.ts";
|
|
13
15
|
import type { ViceSessionClient } from "#src/host-client/vice-session.ts";
|
|
14
|
-
import type { Range } from "./compare.ts";
|
|
15
16
|
|
|
16
17
|
export type FirstAccess = "read" | "write" | "execute" | "none";
|
|
17
18
|
|
|
@@ -21,26 +22,12 @@ export interface RangeAccess {
|
|
|
21
22
|
changed: boolean;
|
|
22
23
|
}
|
|
23
24
|
|
|
24
|
-
/** How a release starts in the emulator. */
|
|
25
|
+
/** How a release starts in the emulator: autostart of a PRG, or the captured program at its entry. */
|
|
25
26
|
export type Start =
|
|
26
27
|
| { kind: "program"; path: string }
|
|
27
|
-
| { kind: "image"; segments:
|
|
28
|
-
|
|
29
|
-
export interface ImageState {
|
|
30
|
-
pc: number;
|
|
31
|
-
a: number;
|
|
32
|
-
x: number;
|
|
33
|
-
y: number;
|
|
34
|
-
sp: number;
|
|
35
|
-
flags: { n: boolean; v: boolean; d: boolean; i: boolean; z: boolean; c: boolean };
|
|
36
|
-
port: [number, number];
|
|
37
|
-
/** $0000-$03ff at the entry. */
|
|
38
|
-
pages: Uint8Array;
|
|
39
|
-
}
|
|
28
|
+
| { kind: "image"; segments: AddressRange[]; byteAt: (address: number) => number; state: EntryState };
|
|
40
29
|
|
|
41
30
|
const MAX_WATCH = 256;
|
|
42
|
-
/** Frames until the KERNAL is at the READY prompt, before an image is written. */
|
|
43
|
-
const READY_FRAMES = 150;
|
|
44
31
|
|
|
45
32
|
const READ = COVERAGE.ramRead | COVERAGE.romRead | COVERAGE.ioRead;
|
|
46
33
|
const WRITE = COVERAGE.ramWrite | COVERAGE.ioWrite;
|
|
@@ -62,20 +49,7 @@ async function start(client: ViceSessionClient, how: Start): Promise<string | un
|
|
|
62
49
|
if (failed === "not-started") return "The program did not start after RUN; it needs a BASIC start line.";
|
|
63
50
|
return undefined;
|
|
64
51
|
}
|
|
65
|
-
await client
|
|
66
|
-
await client.execution({ action: "advance-frames", count: READY_FRAMES, space: "c64" });
|
|
67
|
-
for (const segment of how.segments) {
|
|
68
|
-
for (let at = segment.start; at <= segment.end; at += MAX_MEMORY_WRITE) {
|
|
69
|
-
const last = Math.min(segment.end, at + MAX_MEMORY_WRITE - 1);
|
|
70
|
-
const bytes = Buffer.from(Array.from({ length: last - at + 1 }, (_, offset) => how.byteAt(at + offset)));
|
|
71
|
-
await client.memoryWrite({ address: at, data: bytes.toString("hex"), space: "c64", view: "ram" });
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
const { state } = how;
|
|
75
|
-
// Pages 0 to 3 from $0002, then the CPU port: the port bits switch what the CPU sees.
|
|
76
|
-
await client.memoryWrite({ address: 0x0002, data: Buffer.from(state.pages.subarray(2)).toString("hex"), space: "c64", view: "ram" });
|
|
77
|
-
await client.memoryWrite({ address: 0x0000, data: Buffer.from(state.port).toString("hex"), space: "c64", view: "cpu" });
|
|
78
|
-
await client.registersSet("c64", { pc: state.pc, a: state.a, x: state.x, y: state.y, sp: state.sp, flags: state.flags });
|
|
52
|
+
await startAtEntry(client, how, how.state);
|
|
79
53
|
return undefined;
|
|
80
54
|
}
|
|
81
55
|
|
|
@@ -86,7 +60,7 @@ async function start(client: ViceSessionClient, how: Start): Promise<string | un
|
|
|
86
60
|
export async function watchRelease(
|
|
87
61
|
client: ViceSessionClient,
|
|
88
62
|
how: Start,
|
|
89
|
-
ranges:
|
|
63
|
+
ranges: AddressRange[],
|
|
90
64
|
release: (address: number) => number,
|
|
91
65
|
run: { frames: number; steps: ActionStep[] },
|
|
92
66
|
): Promise<{ started: true; accesses: RangeAccess[] } | { started: false; reason: string }> {
|
|
@@ -151,6 +125,7 @@ export async function watchRelease(
|
|
|
151
125
|
if (left > 0 && !(await settle())) left--;
|
|
152
126
|
}
|
|
153
127
|
};
|
|
128
|
+
const at = knowledgeAt();
|
|
154
129
|
for (const step of run.steps) {
|
|
155
130
|
if ("frames" in step) {
|
|
156
131
|
await runFrames(step.frames);
|
|
@@ -159,13 +134,13 @@ export async function watchRelease(
|
|
|
159
134
|
if ("runUntil" in step) {
|
|
160
135
|
// A stop at a point is not the target: settle and run to the target again.
|
|
161
136
|
while (guard-- > 0) {
|
|
162
|
-
const reached = await runActionStep(client, step,
|
|
137
|
+
const reached = await runActionStep(client, step, at);
|
|
163
138
|
if (reached === undefined || reached.stopReason !== "watchpoint" && reached.stopReason !== "breakpoint") break;
|
|
164
139
|
await settle();
|
|
165
140
|
}
|
|
166
141
|
continue;
|
|
167
142
|
}
|
|
168
|
-
await runActionStep(client, step,
|
|
143
|
+
await runActionStep(client, step, at);
|
|
169
144
|
}
|
|
170
145
|
await runFrames(run.frames);
|
|
171
146
|
if (points.size > 0) await settle();
|
|
@@ -188,4 +163,4 @@ export async function watchRelease(
|
|
|
188
163
|
return { started: true, accesses };
|
|
189
164
|
}
|
|
190
165
|
|
|
191
|
-
export const showRange = (range:
|
|
166
|
+
export const showRange = (range: AddressRange) => `${formatC64Address(range.start)}-${formatC64Address(range.end)}`;
|
|
@@ -4,31 +4,30 @@
|
|
|
4
4
|
// entry; and what the program reads or executes outside the image. Evidence
|
|
5
5
|
// only: nothing here decides what is original.
|
|
6
6
|
|
|
7
|
-
import { COVERAGE, executedRamCode } from "#src/c64.ts";
|
|
8
|
-
import type { Range } from "./compare.ts";
|
|
7
|
+
import { type AddressRange, COVERAGE, executedRamCode } from "#src/c64.ts";
|
|
9
8
|
|
|
10
9
|
export type Access = "executed" | "read" | "written" | "read-written" | "untouched";
|
|
11
10
|
|
|
12
11
|
export interface SeparationInput {
|
|
13
|
-
segments:
|
|
12
|
+
segments: AddressRange[];
|
|
14
13
|
/** 64 KiB with the image bytes at their addresses. */
|
|
15
14
|
memory: Uint8Array;
|
|
16
15
|
entry?: number;
|
|
17
16
|
/** The stages of an unpacker capture, in order; the last one is the program. */
|
|
18
|
-
stages?: Array<{ entry: number; wrote:
|
|
17
|
+
stages?: Array<{ entry: number; wrote: AddressRange[] }>;
|
|
19
18
|
/** Joined coverage files. */
|
|
20
19
|
coverage?: Uint8Array;
|
|
21
|
-
codeRegions: Array<
|
|
20
|
+
codeRegions: Array<AddressRange & { type: string }>;
|
|
22
21
|
references: Array<{ from: number; to: number; kind: string }>;
|
|
23
22
|
}
|
|
24
23
|
|
|
25
|
-
export interface EvidenceRange extends
|
|
24
|
+
export interface EvidenceRange extends AddressRange {
|
|
26
25
|
/** The last stage that wrote the range; absent when no stage wrote it. */
|
|
27
26
|
stage?: number;
|
|
28
27
|
access?: Access;
|
|
29
28
|
}
|
|
30
29
|
|
|
31
|
-
export interface CodeRegionEvidence extends
|
|
30
|
+
export interface CodeRegionEvidence extends AddressRange {
|
|
32
31
|
type: string;
|
|
33
32
|
/** Absent when the image has no entry. */
|
|
34
33
|
reachable?: boolean;
|
|
@@ -45,14 +44,14 @@ export interface Separation {
|
|
|
45
44
|
* those bytes there), and `generated` it wrote and then executed, in
|
|
46
45
|
* pieces of code with gaps of up to two operand bytes.
|
|
47
46
|
*/
|
|
48
|
-
outside: { read:
|
|
47
|
+
outside: { read: AddressRange[]; executed: AddressRange[]; generated: AddressRange[] };
|
|
49
48
|
}
|
|
50
49
|
|
|
51
50
|
const SYSTEM_PAGES_END = 0x03ff;
|
|
52
51
|
|
|
53
52
|
/** 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):
|
|
55
|
-
const ranges:
|
|
53
|
+
function merge(addresses: number[], gap = 0, joins: (from: number, to: number) => boolean = () => true): AddressRange[] {
|
|
54
|
+
const ranges: AddressRange[] = [];
|
|
56
55
|
for (const address of addresses) {
|
|
57
56
|
const last = ranges.at(-1);
|
|
58
57
|
if (last !== undefined && address - last.end - 1 <= gap && joins(last.end, address)) last.end = address;
|