c64bridge 0.9.1 → 0.9.2
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 +34 -2
- package/data/assembly/assembly-spec.md +1 -1
- package/data/context/bootstrap.md +3 -3
- package/data/context/fast-paths.md +3 -1
- package/data/graphics/petscii-style-guide.md +1 -1
- package/data/graphics/sprite-charset-best-practices.md +14 -13
- package/data/sound/sid-file-structure.md +5 -5
- package/data/sound/sid-programming-best-practices.md +5 -5
- package/data/vice/vice-binary-monitor-spec.md +5 -6
- package/dist/.tsbuildinfo +1 -1
- package/dist/c64Client.js +33 -8
- package/dist/sidwaveCompiler.js +1 -1
- package/dist/tools/input.js +5 -7
- package/dist/tools/memory.js +11 -2
- package/dist/tools/programRunners.js +3 -3
- package/dist/tools/responses.js +14 -1
- package/dist/tools/{assember.js → translation/assembler.js} +56 -162
- package/dist/tools/{basicTokenizer.js → translation/basicTokenizer.js} +1 -1
- package/dist/tools/translation/disassembler.js +150 -0
- package/dist/tools/translation/opcodeInventory.js +126 -0
- package/doc/reviews/audit.md +143 -0
- package/mcp/schemas/c64_input.schema.json +231 -113
- package/mcp/server.json +1 -1
- package/mcp/tools.json +234 -115
- package/mcp.json +1 -1
- package/package.json +2 -2
- package/scripts/c64-cli.mjs +2 -2
- package/scripts/run-coverage.mjs +54 -3
- package/scripts/run-tests.mjs +1 -0
- package/scripts/run-tests.ts +1 -0
- package/server.json +2 -2
- package/dist/tools/disassembler.js +0 -478
- /package/dist/tools/{symbolRegistry.js → translation/symbolRegistry.js} +0 -0
package/dist/c64Client.js
CHANGED
|
@@ -8,8 +8,8 @@ See <https://www.gnu.org/licenses/> for details.
|
|
|
8
8
|
import { Buffer } from "node:buffer";
|
|
9
9
|
import { createSocket } from "node:dgram";
|
|
10
10
|
import axios from "axios";
|
|
11
|
-
import { basicToPrg } from "./tools/basicTokenizer.js";
|
|
12
|
-
import { assemblyToPrg } from "./tools/
|
|
11
|
+
import { basicToPrg } from "./tools/translation/basicTokenizer.js";
|
|
12
|
+
import { assemblyToPrg } from "./tools/translation/assembler.js";
|
|
13
13
|
import { screenCodesToAscii } from "./petscii.js";
|
|
14
14
|
import { resolveAddressSymbol } from "./knowledge.js";
|
|
15
15
|
import { createAllFacades, createFacade } from "./device.js";
|
|
@@ -28,6 +28,17 @@ const DEFAULT_TEXT_FOREGROUND = 1;
|
|
|
28
28
|
const DEFAULT_BORDER_COLOR = 6;
|
|
29
29
|
const DEFAULT_BACKGROUND_COLOR = 0;
|
|
30
30
|
const SPACE_SCREEN_CODE = 0x20;
|
|
31
|
+
function validateMemoryRange(address, length) {
|
|
32
|
+
if (!Number.isInteger(address) || address < 0 || address > 0xffff) {
|
|
33
|
+
throw new Error("Address must be within 0x0000 - 0xFFFF");
|
|
34
|
+
}
|
|
35
|
+
if (!Number.isInteger(length) || length <= 0) {
|
|
36
|
+
throw new Error("Length must be greater than zero");
|
|
37
|
+
}
|
|
38
|
+
if (address + length - 1 > 0xffff) {
|
|
39
|
+
throw new Error("Memory range must stay within 0x0000 - 0xFFFF");
|
|
40
|
+
}
|
|
41
|
+
}
|
|
31
42
|
export class C64Client {
|
|
32
43
|
constructor(baseUrl, options = {}) {
|
|
33
44
|
this.allFacades = new Map();
|
|
@@ -481,9 +492,7 @@ export class C64Client {
|
|
|
481
492
|
const resolved = resolveAddressSymbol(addressInput);
|
|
482
493
|
const address = resolved ?? this.parseNumeric(addressInput);
|
|
483
494
|
const length = this.parseNumeric(lengthInput);
|
|
484
|
-
|
|
485
|
-
throw new Error("Length must be greater than zero");
|
|
486
|
-
}
|
|
495
|
+
validateMemoryRange(address, length);
|
|
487
496
|
const rawBytes = await this.readMemoryRaw(address, length);
|
|
488
497
|
const bytes = rawBytes.slice(0, length);
|
|
489
498
|
return {
|
|
@@ -507,9 +516,7 @@ export class C64Client {
|
|
|
507
516
|
const resolved = resolveAddressSymbol(addressInput);
|
|
508
517
|
const address = resolved ?? this.parseNumeric(addressInput);
|
|
509
518
|
const dataBuffer = this.hexStringToBuffer(bytesInput);
|
|
510
|
-
|
|
511
|
-
throw new Error("No bytes provided");
|
|
512
|
-
}
|
|
519
|
+
validateMemoryRange(address, dataBuffer.length);
|
|
513
520
|
// Prefer PUT with hex data for up to 128 bytes; fall back to POST binary for larger writes
|
|
514
521
|
const addrStr = this.formatAddress(address);
|
|
515
522
|
try {
|
|
@@ -1467,6 +1474,7 @@ export class C64Client {
|
|
|
1467
1474
|
* Public to allow advanced polling and monitoring use cases.
|
|
1468
1475
|
*/
|
|
1469
1476
|
async readMemoryRaw(address, length) {
|
|
1477
|
+
validateMemoryRange(address, length);
|
|
1470
1478
|
try {
|
|
1471
1479
|
const facade = await this.facadePromise;
|
|
1472
1480
|
return await facade.readMemory(address, length);
|
|
@@ -1498,8 +1506,12 @@ export class C64Client {
|
|
|
1498
1506
|
* keyboard-buffer injection) can avoid backend-specific paths.
|
|
1499
1507
|
*/
|
|
1500
1508
|
async writeMemoryRaw(address, bytes) {
|
|
1509
|
+
if (!Number.isInteger(address) || address < 0 || address > 0xffff) {
|
|
1510
|
+
throw new Error("Address must be within 0x0000 - 0xFFFF");
|
|
1511
|
+
}
|
|
1501
1512
|
const facade = await this.facadePromise;
|
|
1502
1513
|
const buf = Buffer.isBuffer(bytes) ? bytes : Buffer.from(bytes);
|
|
1514
|
+
validateMemoryRange(address, buf.length);
|
|
1503
1515
|
await facade.writeMemory(address, buf);
|
|
1504
1516
|
}
|
|
1505
1517
|
/**
|
|
@@ -1695,17 +1707,27 @@ export class C64Client {
|
|
|
1695
1707
|
const lower = trimmed.toLowerCase();
|
|
1696
1708
|
let radix = 10;
|
|
1697
1709
|
let literal = lower;
|
|
1710
|
+
let pattern;
|
|
1698
1711
|
if (lower.startsWith("$")) {
|
|
1699
1712
|
radix = 16;
|
|
1700
1713
|
literal = lower.slice(1);
|
|
1714
|
+
pattern = /^[0-9a-f]+$/;
|
|
1701
1715
|
}
|
|
1702
1716
|
else if (lower.startsWith("0x")) {
|
|
1703
1717
|
radix = 16;
|
|
1704
1718
|
literal = lower.slice(2);
|
|
1719
|
+
pattern = /^[0-9a-f]+$/;
|
|
1705
1720
|
}
|
|
1706
1721
|
else if (lower.startsWith("%")) {
|
|
1707
1722
|
radix = 2;
|
|
1708
1723
|
literal = lower.slice(1);
|
|
1724
|
+
pattern = /^[01]+$/;
|
|
1725
|
+
}
|
|
1726
|
+
else {
|
|
1727
|
+
pattern = /^[0-9]+$/;
|
|
1728
|
+
}
|
|
1729
|
+
if (!pattern.test(literal)) {
|
|
1730
|
+
throw new Error(`Unable to parse numeric value "${value}"`);
|
|
1709
1731
|
}
|
|
1710
1732
|
const parsed = Number.parseInt(literal, radix);
|
|
1711
1733
|
if (!Number.isFinite(parsed) || Number.isNaN(parsed) || parsed < 0) {
|
|
@@ -1739,6 +1761,9 @@ export class C64Client {
|
|
|
1739
1761
|
if (cleaned.length % 2 !== 0) {
|
|
1740
1762
|
throw new Error("Hex string must contain an even number of characters");
|
|
1741
1763
|
}
|
|
1764
|
+
if (!/^[0-9a-f]+$/.test(cleaned)) {
|
|
1765
|
+
throw new Error("Hex string contains non-hexadecimal characters");
|
|
1766
|
+
}
|
|
1742
1767
|
return Buffer.from(cleaned, "hex");
|
|
1743
1768
|
}
|
|
1744
1769
|
hzToSidFrequency(hz, system = "PAL") {
|
package/dist/sidwaveCompiler.js
CHANGED
|
@@ -6,7 +6,7 @@ Licensed under the GNU General Public License v2.0 or later.
|
|
|
6
6
|
See <https://www.gnu.org/licenses/> for details.
|
|
7
7
|
*/
|
|
8
8
|
import { Buffer } from "node:buffer";
|
|
9
|
-
import { assemblyToPrg } from "./tools/
|
|
9
|
+
import { assemblyToPrg } from "./tools/translation/assembler.js";
|
|
10
10
|
import { midiToHz, noteNameToMidi } from "./sidwave.js";
|
|
11
11
|
/**
|
|
12
12
|
* Small SIDWAVE to PRG compiler: generates a 50/60Hz player that writes SID registers from a precomputed frame table.
|
package/dist/tools/input.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { createOperationDispatcher, defineToolModule, } from "./types.js";
|
|
1
|
+
import { createOperationDispatcher, defineToolModule, discriminatedUnionSchema, } from "./types.js";
|
|
2
2
|
import { arraySchema, literalSchema, numberSchema, objectSchema, optionalSchema, stringSchema, } from "./schema.js";
|
|
3
3
|
import { textResult } from "./responses.js";
|
|
4
4
|
import { ToolValidationError, toolErrorResult, unknownErrorResult, } from "./errors.js";
|
|
@@ -285,7 +285,7 @@ export const inputModule = defineToolModule({
|
|
|
285
285
|
domain: "input",
|
|
286
286
|
summary: "Cross-platform keyboard input plus VICE-only joystick simulation.",
|
|
287
287
|
supportedPlatforms: ["c64u", "vice"],
|
|
288
|
-
resources: ["c64://
|
|
288
|
+
resources: ["c64://guide/bootstrap", "c64://memory/map", "c64://io/cia/spec"],
|
|
289
289
|
prompts: [],
|
|
290
290
|
defaultTags: ["input"],
|
|
291
291
|
workflowHints: [
|
|
@@ -298,16 +298,14 @@ export const inputModule = defineToolModule({
|
|
|
298
298
|
name: "c64_input",
|
|
299
299
|
description: "Keyboard buffer injection (cross-platform) and joystick simulation (VICE only).",
|
|
300
300
|
summary: "Types text, taps keys, and simulates joystick movements.",
|
|
301
|
-
inputSchema: {
|
|
302
|
-
type: "object",
|
|
301
|
+
inputSchema: discriminatedUnionSchema({
|
|
303
302
|
description: "Input operations: write_text, key, joystick.",
|
|
304
|
-
|
|
303
|
+
variants: [
|
|
305
304
|
writeTextArgsSchema.jsonSchema,
|
|
306
305
|
keyArgsSchema.jsonSchema,
|
|
307
306
|
joystickArgsSchema.jsonSchema,
|
|
308
307
|
],
|
|
309
|
-
|
|
310
|
-
},
|
|
308
|
+
}),
|
|
311
309
|
operationPlatforms: { joystick: ["vice"] },
|
|
312
310
|
tags: ["input", "keyboard", "joystick"],
|
|
313
311
|
examples: [
|
package/dist/tools/memory.js
CHANGED
|
@@ -5,8 +5,8 @@ import { defineToolModule, OPERATION_DISCRIMINATOR, } from "./types.js";
|
|
|
5
5
|
import { booleanSchema, numberSchema, objectSchema, optionalSchema, stringSchema } from "./schema.js";
|
|
6
6
|
import { jsonResult, textResult } from "./responses.js";
|
|
7
7
|
import { ToolError, ToolExecutionError, ToolValidationError, toolErrorResult, unknownErrorResult, } from "./errors.js";
|
|
8
|
-
import { disassemble, formatDisassembly } from "./disassembler.js";
|
|
9
|
-
import { getViceSymbols } from "./symbolRegistry.js";
|
|
8
|
+
import { disassemble, formatDisassembly } from "./translation/disassembler.js";
|
|
9
|
+
import { getViceSymbols } from "./translation/symbolRegistry.js";
|
|
10
10
|
import { resolveAddressSymbol } from "../knowledge.js";
|
|
11
11
|
function toRecord(details) {
|
|
12
12
|
if (details && typeof details === "object") {
|
|
@@ -39,6 +39,12 @@ function parseHexInternal(value, path) {
|
|
|
39
39
|
if (cleaned.length === 0) {
|
|
40
40
|
return { bytes: new Uint8Array(), canonical: "$" };
|
|
41
41
|
}
|
|
42
|
+
if (!/^[0-9A-F]+$/.test(cleaned)) {
|
|
43
|
+
throw new ToolValidationError("Hex string contains non-hexadecimal characters", {
|
|
44
|
+
path,
|
|
45
|
+
details: { value },
|
|
46
|
+
});
|
|
47
|
+
}
|
|
42
48
|
const bytes = Uint8Array.from(Buffer.from(cleaned, "hex"));
|
|
43
49
|
return { bytes, canonical: `$${cleaned}` };
|
|
44
50
|
}
|
|
@@ -399,6 +405,7 @@ async function executeDisassemble(rawArgs, ctx) {
|
|
|
399
405
|
const length = parsed.length ?? 64;
|
|
400
406
|
ctx.logger.info("Disassembling VICE memory", { address: parsed.address, length });
|
|
401
407
|
const address = parseAddressArg(parsed.address, "$.address");
|
|
408
|
+
validateRangeWithinAddressSpace(address, length, "$.address");
|
|
402
409
|
const bytes = await ctx.client.readMemoryRaw(address, length);
|
|
403
410
|
const symbols = ctx.platform?.id === "vice" ? getViceSymbols() : undefined;
|
|
404
411
|
const lines = disassemble(bytes, address, parsed.instructionCount, symbols);
|
|
@@ -597,6 +604,8 @@ async function executeCompareMemory(rawArgs, ctx) {
|
|
|
597
604
|
const a2 = parseAddressArg(parsed.address2, "$.address2");
|
|
598
605
|
const len = parsed.length;
|
|
599
606
|
const maxDiffs = parsed.maxDiffs ?? 10;
|
|
607
|
+
validateRangeWithinAddressSpace(a1, len, "$.address1");
|
|
608
|
+
validateRangeWithinAddressSpace(a2, len, "$.address2");
|
|
600
609
|
ctx.logger.info("Comparing memory regions", { a1: fmtAddr(a1), a2: fmtAddr(a2), len });
|
|
601
610
|
const [r1, r2] = await Promise.all([
|
|
602
611
|
ctx.client.readMemoryRaw(a1, len),
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import fs from "node:fs";
|
|
2
|
-
import { assemblyToPrgDetailed, AssemblyError } from "./
|
|
3
|
-
import { basicToPrg } from "./basicTokenizer.js";
|
|
2
|
+
import { assemblyToPrgDetailed, AssemblyError } from "./translation/assembler.js";
|
|
3
|
+
import { basicToPrg } from "./translation/basicTokenizer.js";
|
|
4
4
|
import { defineToolModule, OPERATION_DISCRIMINATOR, } from "./types.js";
|
|
5
5
|
import { booleanSchema, objectSchema, optionalSchema, stringSchema } from "./schema.js";
|
|
6
6
|
import { textResult } from "./responses.js";
|
|
7
7
|
import { ToolExecutionError, ToolError, ToolValidationError, toolErrorResult, unknownErrorResult, } from "./errors.js";
|
|
8
8
|
import { pollForProgramOutcome } from "./pollValidator.js";
|
|
9
|
-
import { clearViceSymbols, setViceSymbols, parseViceSymbolFile } from "./symbolRegistry.js";
|
|
9
|
+
import { clearViceSymbols, setViceSymbols, parseViceSymbolFile } from "./translation/symbolRegistry.js";
|
|
10
10
|
function extractFailureDetails(details) {
|
|
11
11
|
if (details === undefined || details === null) {
|
|
12
12
|
return undefined;
|
package/dist/tools/responses.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export function textResult(text, metadata) {
|
|
2
|
-
|
|
2
|
+
const result = {
|
|
3
3
|
content: [
|
|
4
4
|
{
|
|
5
5
|
type: "text",
|
|
@@ -8,6 +8,19 @@ export function textResult(text, metadata) {
|
|
|
8
8
|
],
|
|
9
9
|
metadata,
|
|
10
10
|
};
|
|
11
|
+
if (metadata !== undefined) {
|
|
12
|
+
return {
|
|
13
|
+
...result,
|
|
14
|
+
structuredContent: {
|
|
15
|
+
type: "json",
|
|
16
|
+
data: {
|
|
17
|
+
message: text,
|
|
18
|
+
...metadata,
|
|
19
|
+
},
|
|
20
|
+
},
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
return result;
|
|
11
24
|
}
|
|
12
25
|
export function jsonResult(data, metadata) {
|
|
13
26
|
const text = typeof data === "string"
|
|
@@ -6,6 +6,7 @@ Licensed under the GNU General Public License v2.0 or later.
|
|
|
6
6
|
See <https://www.gnu.org/licenses/> for details.
|
|
7
7
|
*/
|
|
8
8
|
import { Buffer } from "node:buffer";
|
|
9
|
+
import { OPCODE_INVENTORY } from "./opcodeInventory.js";
|
|
9
10
|
export class AssemblyError extends Error {
|
|
10
11
|
constructor(message, location) {
|
|
11
12
|
super(`${location.file}:${location.line}: ${message}`);
|
|
@@ -20,6 +21,7 @@ var AddressingMode;
|
|
|
20
21
|
AddressingMode["ZeroPage"] = "zeroPage";
|
|
21
22
|
AddressingMode["ZeroPageX"] = "zeroPageX";
|
|
22
23
|
AddressingMode["ZeroPageY"] = "zeroPageY";
|
|
24
|
+
AddressingMode["ZeroPageS"] = "zeroPageS";
|
|
23
25
|
AddressingMode["Absolute"] = "absolute";
|
|
24
26
|
AddressingMode["AbsoluteX"] = "absoluteX";
|
|
25
27
|
AddressingMode["AbsoluteY"] = "absoluteY";
|
|
@@ -519,7 +521,19 @@ function parseOperand(stream) {
|
|
|
519
521
|
}
|
|
520
522
|
if (first.kind === "punct" && first.value === "(") {
|
|
521
523
|
stream.consumePunct("(");
|
|
522
|
-
const expr = parseExpression(stream, new Set([")"]));
|
|
524
|
+
const expr = parseExpression(stream, new Set([",", ")"]));
|
|
525
|
+
if (stream.peek()?.kind === "punct" && stream.peek()?.value === ",") {
|
|
526
|
+
stream.consumePunct(",");
|
|
527
|
+
const register = stream.consumeSymbol("Expected X before ')'").toUpperCase();
|
|
528
|
+
if (register !== "X") {
|
|
529
|
+
throw new Error("Only X is allowed inside pre-indexed indirect operands");
|
|
530
|
+
}
|
|
531
|
+
stream.consumePunct(")", "Expected ')' to close indirect operand");
|
|
532
|
+
if (!stream.isAtEnd()) {
|
|
533
|
+
throw new Error("Unexpected tokens after indirect operand");
|
|
534
|
+
}
|
|
535
|
+
return { kind: "indirect", expr, register: "X" };
|
|
536
|
+
}
|
|
523
537
|
stream.consumePunct(")", "Expected ')' to close indirect operand");
|
|
524
538
|
if (stream.isAtEnd()) {
|
|
525
539
|
return { kind: "indirect", expr };
|
|
@@ -545,9 +559,9 @@ function parseOperand(stream) {
|
|
|
545
559
|
const next = stream.peek();
|
|
546
560
|
if (next && next.kind === "punct" && next.value === ",") {
|
|
547
561
|
stream.consumePunct(",");
|
|
548
|
-
const register = stream.consumeSymbol("Expected X or
|
|
549
|
-
if (register !== "X" && register !== "Y") {
|
|
550
|
-
throw new Error("Only X or
|
|
562
|
+
const register = stream.consumeSymbol("Expected X, Y, or S after comma").toUpperCase();
|
|
563
|
+
if (register !== "X" && register !== "Y" && register !== "S") {
|
|
564
|
+
throw new Error("Only X, Y, or S are allowed after comma");
|
|
551
565
|
}
|
|
552
566
|
return { kind: "expression", expr, register };
|
|
553
567
|
}
|
|
@@ -830,6 +844,7 @@ class Assembler {
|
|
|
830
844
|
case AddressingMode.ZeroPage:
|
|
831
845
|
case AddressingMode.ZeroPageX:
|
|
832
846
|
case AddressingMode.ZeroPageY:
|
|
847
|
+
case AddressingMode.ZeroPageS:
|
|
833
848
|
case AddressingMode.IndirectX:
|
|
834
849
|
case AddressingMode.IndirectY:
|
|
835
850
|
this.writeByte(info.value ?? 0, location);
|
|
@@ -1040,6 +1055,11 @@ function chooseAddressingMode(mnemonic, operand, value, pass, location) {
|
|
|
1040
1055
|
return AddressingMode.AbsoluteY;
|
|
1041
1056
|
}
|
|
1042
1057
|
}
|
|
1058
|
+
else if (register === "S") {
|
|
1059
|
+
if (available.has(AddressingMode.ZeroPageS)) {
|
|
1060
|
+
return AddressingMode.ZeroPageS;
|
|
1061
|
+
}
|
|
1062
|
+
}
|
|
1043
1063
|
else {
|
|
1044
1064
|
if (available.has(AddressingMode.ZeroPage) && value !== undefined && fitsByte(value)) {
|
|
1045
1065
|
return AddressingMode.ZeroPage;
|
|
@@ -1066,6 +1086,7 @@ function instructionSize(mode) {
|
|
|
1066
1086
|
case AddressingMode.ZeroPage:
|
|
1067
1087
|
case AddressingMode.ZeroPageX:
|
|
1068
1088
|
case AddressingMode.ZeroPageY:
|
|
1089
|
+
case AddressingMode.ZeroPageS:
|
|
1069
1090
|
case AddressingMode.IndirectX:
|
|
1070
1091
|
case AddressingMode.IndirectY:
|
|
1071
1092
|
case AddressingMode.Relative:
|
|
@@ -1095,6 +1116,7 @@ function normalizeOperandValue(mode, value, locationCounterOrLocation, locationM
|
|
|
1095
1116
|
case AddressingMode.ZeroPage:
|
|
1096
1117
|
case AddressingMode.ZeroPageX:
|
|
1097
1118
|
case AddressingMode.ZeroPageY:
|
|
1119
|
+
case AddressingMode.ZeroPageS:
|
|
1098
1120
|
case AddressingMode.IndirectX:
|
|
1099
1121
|
case AddressingMode.IndirectY:
|
|
1100
1122
|
if (value === undefined)
|
|
@@ -1141,168 +1163,40 @@ function fitsByte(value) {
|
|
|
1141
1163
|
function formatHex(value) {
|
|
1142
1164
|
return `$${value.toString(16).toUpperCase().padStart(4, "0")}`;
|
|
1143
1165
|
}
|
|
1166
|
+
const OPCODE_MODE_TO_ADDRESSING_MODE = {
|
|
1167
|
+
implied: AddressingMode.Implied,
|
|
1168
|
+
accumulator: AddressingMode.Accumulator,
|
|
1169
|
+
immediate: AddressingMode.Immediate,
|
|
1170
|
+
zeroPage: AddressingMode.ZeroPage,
|
|
1171
|
+
zeroPageX: AddressingMode.ZeroPageX,
|
|
1172
|
+
zeroPageY: AddressingMode.ZeroPageY,
|
|
1173
|
+
zeroPageS: AddressingMode.ZeroPageS,
|
|
1174
|
+
absolute: AddressingMode.Absolute,
|
|
1175
|
+
absoluteX: AddressingMode.AbsoluteX,
|
|
1176
|
+
absoluteY: AddressingMode.AbsoluteY,
|
|
1177
|
+
indirect: AddressingMode.Indirect,
|
|
1178
|
+
indirectX: AddressingMode.IndirectX,
|
|
1179
|
+
indirectY: AddressingMode.IndirectY,
|
|
1180
|
+
relative: AddressingMode.Relative,
|
|
1181
|
+
};
|
|
1144
1182
|
function buildInstructionTable() {
|
|
1145
1183
|
const table = new Map();
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1184
|
+
const entryByMnemonicAndMode = new Map();
|
|
1185
|
+
for (const entry of OPCODE_INVENTORY) {
|
|
1186
|
+
const mode = OPCODE_MODE_TO_ADDRESSING_MODE[entry.mode];
|
|
1187
|
+
const key = `${entry.mnemonic}\u0000${mode}`;
|
|
1188
|
+
const existing = entryByMnemonicAndMode.get(key);
|
|
1189
|
+
// The inventory is sorted by opcode, but assembly should prefer official
|
|
1190
|
+
// encodings over undocumented aliases for duplicate mnemonic/mode pairs.
|
|
1191
|
+
if (existing && (!existing.illegal || entry.illegal)) {
|
|
1192
|
+
continue;
|
|
1193
|
+
}
|
|
1194
|
+
entryByMnemonicAndMode.set(key, { opcode: entry.opcode, illegal: entry.illegal });
|
|
1195
|
+
if (!table.has(entry.mnemonic)) {
|
|
1196
|
+
table.set(entry.mnemonic, new Map());
|
|
1150
1197
|
}
|
|
1151
|
-
table.get(mnemonic).set(
|
|
1198
|
+
table.get(entry.mnemonic).set(mode, entry.opcode);
|
|
1152
1199
|
}
|
|
1153
1200
|
return table;
|
|
1154
1201
|
}
|
|
1155
|
-
const INSTRUCTION_DATA = [
|
|
1156
|
-
{ mnemonic: "ADC", mode: AddressingMode.Immediate, opcode: 0x69 },
|
|
1157
|
-
{ mnemonic: "ADC", mode: AddressingMode.ZeroPage, opcode: 0x65 },
|
|
1158
|
-
{ mnemonic: "ADC", mode: AddressingMode.ZeroPageX, opcode: 0x75 },
|
|
1159
|
-
{ mnemonic: "ADC", mode: AddressingMode.Absolute, opcode: 0x6d },
|
|
1160
|
-
{ mnemonic: "ADC", mode: AddressingMode.AbsoluteX, opcode: 0x7d },
|
|
1161
|
-
{ mnemonic: "ADC", mode: AddressingMode.AbsoluteY, opcode: 0x79 },
|
|
1162
|
-
{ mnemonic: "ADC", mode: AddressingMode.IndirectX, opcode: 0x61 },
|
|
1163
|
-
{ mnemonic: "ADC", mode: AddressingMode.IndirectY, opcode: 0x71 },
|
|
1164
|
-
{ mnemonic: "AND", mode: AddressingMode.Immediate, opcode: 0x29 },
|
|
1165
|
-
{ mnemonic: "AND", mode: AddressingMode.ZeroPage, opcode: 0x25 },
|
|
1166
|
-
{ mnemonic: "AND", mode: AddressingMode.ZeroPageX, opcode: 0x35 },
|
|
1167
|
-
{ mnemonic: "AND", mode: AddressingMode.Absolute, opcode: 0x2d },
|
|
1168
|
-
{ mnemonic: "AND", mode: AddressingMode.AbsoluteX, opcode: 0x3d },
|
|
1169
|
-
{ mnemonic: "AND", mode: AddressingMode.AbsoluteY, opcode: 0x39 },
|
|
1170
|
-
{ mnemonic: "AND", mode: AddressingMode.IndirectX, opcode: 0x21 },
|
|
1171
|
-
{ mnemonic: "AND", mode: AddressingMode.IndirectY, opcode: 0x31 },
|
|
1172
|
-
{ mnemonic: "ASL", mode: AddressingMode.Accumulator, opcode: 0x0a },
|
|
1173
|
-
{ mnemonic: "ASL", mode: AddressingMode.ZeroPage, opcode: 0x06 },
|
|
1174
|
-
{ mnemonic: "ASL", mode: AddressingMode.ZeroPageX, opcode: 0x16 },
|
|
1175
|
-
{ mnemonic: "ASL", mode: AddressingMode.Absolute, opcode: 0x0e },
|
|
1176
|
-
{ mnemonic: "ASL", mode: AddressingMode.AbsoluteX, opcode: 0x1e },
|
|
1177
|
-
{ mnemonic: "BIT", mode: AddressingMode.ZeroPage, opcode: 0x24 },
|
|
1178
|
-
{ mnemonic: "BIT", mode: AddressingMode.Absolute, opcode: 0x2c },
|
|
1179
|
-
{ mnemonic: "BPL", mode: AddressingMode.Relative, opcode: 0x10 },
|
|
1180
|
-
{ mnemonic: "BMI", mode: AddressingMode.Relative, opcode: 0x30 },
|
|
1181
|
-
{ mnemonic: "BVC", mode: AddressingMode.Relative, opcode: 0x50 },
|
|
1182
|
-
{ mnemonic: "BVS", mode: AddressingMode.Relative, opcode: 0x70 },
|
|
1183
|
-
{ mnemonic: "BCC", mode: AddressingMode.Relative, opcode: 0x90 },
|
|
1184
|
-
{ mnemonic: "BCS", mode: AddressingMode.Relative, opcode: 0xb0 },
|
|
1185
|
-
{ mnemonic: "BNE", mode: AddressingMode.Relative, opcode: 0xd0 },
|
|
1186
|
-
{ mnemonic: "BEQ", mode: AddressingMode.Relative, opcode: 0xf0 },
|
|
1187
|
-
{ mnemonic: "BRK", mode: AddressingMode.Implied, opcode: 0x00 },
|
|
1188
|
-
{ mnemonic: "CLC", mode: AddressingMode.Implied, opcode: 0x18 },
|
|
1189
|
-
{ mnemonic: "CLD", mode: AddressingMode.Implied, opcode: 0xd8 },
|
|
1190
|
-
{ mnemonic: "CLI", mode: AddressingMode.Implied, opcode: 0x58 },
|
|
1191
|
-
{ mnemonic: "CLV", mode: AddressingMode.Implied, opcode: 0xb8 },
|
|
1192
|
-
{ mnemonic: "CMP", mode: AddressingMode.Immediate, opcode: 0xc9 },
|
|
1193
|
-
{ mnemonic: "CMP", mode: AddressingMode.ZeroPage, opcode: 0xc5 },
|
|
1194
|
-
{ mnemonic: "CMP", mode: AddressingMode.ZeroPageX, opcode: 0xd5 },
|
|
1195
|
-
{ mnemonic: "CMP", mode: AddressingMode.Absolute, opcode: 0xcd },
|
|
1196
|
-
{ mnemonic: "CMP", mode: AddressingMode.AbsoluteX, opcode: 0xdd },
|
|
1197
|
-
{ mnemonic: "CMP", mode: AddressingMode.AbsoluteY, opcode: 0xd9 },
|
|
1198
|
-
{ mnemonic: "CMP", mode: AddressingMode.IndirectX, opcode: 0xc1 },
|
|
1199
|
-
{ mnemonic: "CMP", mode: AddressingMode.IndirectY, opcode: 0xd1 },
|
|
1200
|
-
{ mnemonic: "CPX", mode: AddressingMode.Immediate, opcode: 0xe0 },
|
|
1201
|
-
{ mnemonic: "CPX", mode: AddressingMode.ZeroPage, opcode: 0xe4 },
|
|
1202
|
-
{ mnemonic: "CPX", mode: AddressingMode.Absolute, opcode: 0xec },
|
|
1203
|
-
{ mnemonic: "CPY", mode: AddressingMode.Immediate, opcode: 0xc0 },
|
|
1204
|
-
{ mnemonic: "CPY", mode: AddressingMode.ZeroPage, opcode: 0xc4 },
|
|
1205
|
-
{ mnemonic: "CPY", mode: AddressingMode.Absolute, opcode: 0xcc },
|
|
1206
|
-
{ mnemonic: "DEC", mode: AddressingMode.ZeroPage, opcode: 0xc6 },
|
|
1207
|
-
{ mnemonic: "DEC", mode: AddressingMode.ZeroPageX, opcode: 0xd6 },
|
|
1208
|
-
{ mnemonic: "DEC", mode: AddressingMode.Absolute, opcode: 0xce },
|
|
1209
|
-
{ mnemonic: "DEC", mode: AddressingMode.AbsoluteX, opcode: 0xde },
|
|
1210
|
-
{ mnemonic: "DEX", mode: AddressingMode.Implied, opcode: 0xca },
|
|
1211
|
-
{ mnemonic: "DEY", mode: AddressingMode.Implied, opcode: 0x88 },
|
|
1212
|
-
{ mnemonic: "EOR", mode: AddressingMode.Immediate, opcode: 0x49 },
|
|
1213
|
-
{ mnemonic: "EOR", mode: AddressingMode.ZeroPage, opcode: 0x45 },
|
|
1214
|
-
{ mnemonic: "EOR", mode: AddressingMode.ZeroPageX, opcode: 0x55 },
|
|
1215
|
-
{ mnemonic: "EOR", mode: AddressingMode.Absolute, opcode: 0x4d },
|
|
1216
|
-
{ mnemonic: "EOR", mode: AddressingMode.AbsoluteX, opcode: 0x5d },
|
|
1217
|
-
{ mnemonic: "EOR", mode: AddressingMode.AbsoluteY, opcode: 0x59 },
|
|
1218
|
-
{ mnemonic: "EOR", mode: AddressingMode.IndirectX, opcode: 0x41 },
|
|
1219
|
-
{ mnemonic: "EOR", mode: AddressingMode.IndirectY, opcode: 0x51 },
|
|
1220
|
-
{ mnemonic: "INC", mode: AddressingMode.ZeroPage, opcode: 0xe6 },
|
|
1221
|
-
{ mnemonic: "INC", mode: AddressingMode.ZeroPageX, opcode: 0xf6 },
|
|
1222
|
-
{ mnemonic: "INC", mode: AddressingMode.Absolute, opcode: 0xee },
|
|
1223
|
-
{ mnemonic: "INC", mode: AddressingMode.AbsoluteX, opcode: 0xfe },
|
|
1224
|
-
{ mnemonic: "INX", mode: AddressingMode.Implied, opcode: 0xe8 },
|
|
1225
|
-
{ mnemonic: "INY", mode: AddressingMode.Implied, opcode: 0xc8 },
|
|
1226
|
-
{ mnemonic: "JMP", mode: AddressingMode.Absolute, opcode: 0x4c },
|
|
1227
|
-
{ mnemonic: "JMP", mode: AddressingMode.Indirect, opcode: 0x6c },
|
|
1228
|
-
{ mnemonic: "JSR", mode: AddressingMode.Absolute, opcode: 0x20 },
|
|
1229
|
-
{ mnemonic: "LDA", mode: AddressingMode.Immediate, opcode: 0xa9 },
|
|
1230
|
-
{ mnemonic: "LDA", mode: AddressingMode.ZeroPage, opcode: 0xa5 },
|
|
1231
|
-
{ mnemonic: "LDA", mode: AddressingMode.ZeroPageX, opcode: 0xb5 },
|
|
1232
|
-
{ mnemonic: "LDA", mode: AddressingMode.Absolute, opcode: 0xad },
|
|
1233
|
-
{ mnemonic: "LDA", mode: AddressingMode.AbsoluteX, opcode: 0xbd },
|
|
1234
|
-
{ mnemonic: "LDA", mode: AddressingMode.AbsoluteY, opcode: 0xb9 },
|
|
1235
|
-
{ mnemonic: "LDA", mode: AddressingMode.IndirectX, opcode: 0xa1 },
|
|
1236
|
-
{ mnemonic: "LDA", mode: AddressingMode.IndirectY, opcode: 0xb1 },
|
|
1237
|
-
{ mnemonic: "LDX", mode: AddressingMode.Immediate, opcode: 0xa2 },
|
|
1238
|
-
{ mnemonic: "LDX", mode: AddressingMode.ZeroPage, opcode: 0xa6 },
|
|
1239
|
-
{ mnemonic: "LDX", mode: AddressingMode.ZeroPageY, opcode: 0xb6 },
|
|
1240
|
-
{ mnemonic: "LDX", mode: AddressingMode.Absolute, opcode: 0xae },
|
|
1241
|
-
{ mnemonic: "LDX", mode: AddressingMode.AbsoluteY, opcode: 0xbe },
|
|
1242
|
-
{ mnemonic: "LDY", mode: AddressingMode.Immediate, opcode: 0xa0 },
|
|
1243
|
-
{ mnemonic: "LDY", mode: AddressingMode.ZeroPage, opcode: 0xa4 },
|
|
1244
|
-
{ mnemonic: "LDY", mode: AddressingMode.ZeroPageX, opcode: 0xb4 },
|
|
1245
|
-
{ mnemonic: "LDY", mode: AddressingMode.Absolute, opcode: 0xac },
|
|
1246
|
-
{ mnemonic: "LDY", mode: AddressingMode.AbsoluteX, opcode: 0xbc },
|
|
1247
|
-
{ mnemonic: "LSR", mode: AddressingMode.Accumulator, opcode: 0x4a },
|
|
1248
|
-
{ mnemonic: "LSR", mode: AddressingMode.ZeroPage, opcode: 0x46 },
|
|
1249
|
-
{ mnemonic: "LSR", mode: AddressingMode.ZeroPageX, opcode: 0x56 },
|
|
1250
|
-
{ mnemonic: "LSR", mode: AddressingMode.Absolute, opcode: 0x4e },
|
|
1251
|
-
{ mnemonic: "LSR", mode: AddressingMode.AbsoluteX, opcode: 0x5e },
|
|
1252
|
-
{ mnemonic: "NOP", mode: AddressingMode.Implied, opcode: 0xea },
|
|
1253
|
-
{ mnemonic: "ORA", mode: AddressingMode.Immediate, opcode: 0x09 },
|
|
1254
|
-
{ mnemonic: "ORA", mode: AddressingMode.ZeroPage, opcode: 0x05 },
|
|
1255
|
-
{ mnemonic: "ORA", mode: AddressingMode.ZeroPageX, opcode: 0x15 },
|
|
1256
|
-
{ mnemonic: "ORA", mode: AddressingMode.Absolute, opcode: 0x0d },
|
|
1257
|
-
{ mnemonic: "ORA", mode: AddressingMode.AbsoluteX, opcode: 0x1d },
|
|
1258
|
-
{ mnemonic: "ORA", mode: AddressingMode.AbsoluteY, opcode: 0x19 },
|
|
1259
|
-
{ mnemonic: "ORA", mode: AddressingMode.IndirectX, opcode: 0x01 },
|
|
1260
|
-
{ mnemonic: "ORA", mode: AddressingMode.IndirectY, opcode: 0x11 },
|
|
1261
|
-
{ mnemonic: "PHA", mode: AddressingMode.Implied, opcode: 0x48 },
|
|
1262
|
-
{ mnemonic: "PHP", mode: AddressingMode.Implied, opcode: 0x08 },
|
|
1263
|
-
{ mnemonic: "PLA", mode: AddressingMode.Implied, opcode: 0x68 },
|
|
1264
|
-
{ mnemonic: "PLP", mode: AddressingMode.Implied, opcode: 0x28 },
|
|
1265
|
-
{ mnemonic: "ROL", mode: AddressingMode.Accumulator, opcode: 0x2a },
|
|
1266
|
-
{ mnemonic: "ROL", mode: AddressingMode.ZeroPage, opcode: 0x26 },
|
|
1267
|
-
{ mnemonic: "ROL", mode: AddressingMode.ZeroPageX, opcode: 0x36 },
|
|
1268
|
-
{ mnemonic: "ROL", mode: AddressingMode.Absolute, opcode: 0x2e },
|
|
1269
|
-
{ mnemonic: "ROL", mode: AddressingMode.AbsoluteX, opcode: 0x3e },
|
|
1270
|
-
{ mnemonic: "ROR", mode: AddressingMode.Accumulator, opcode: 0x6a },
|
|
1271
|
-
{ mnemonic: "ROR", mode: AddressingMode.ZeroPage, opcode: 0x66 },
|
|
1272
|
-
{ mnemonic: "ROR", mode: AddressingMode.ZeroPageX, opcode: 0x76 },
|
|
1273
|
-
{ mnemonic: "ROR", mode: AddressingMode.Absolute, opcode: 0x6e },
|
|
1274
|
-
{ mnemonic: "ROR", mode: AddressingMode.AbsoluteX, opcode: 0x7e },
|
|
1275
|
-
{ mnemonic: "RTI", mode: AddressingMode.Implied, opcode: 0x40 },
|
|
1276
|
-
{ mnemonic: "RTS", mode: AddressingMode.Implied, opcode: 0x60 },
|
|
1277
|
-
{ mnemonic: "SBC", mode: AddressingMode.Immediate, opcode: 0xe9 },
|
|
1278
|
-
{ mnemonic: "SBC", mode: AddressingMode.ZeroPage, opcode: 0xe5 },
|
|
1279
|
-
{ mnemonic: "SBC", mode: AddressingMode.ZeroPageX, opcode: 0xf5 },
|
|
1280
|
-
{ mnemonic: "SBC", mode: AddressingMode.Absolute, opcode: 0xed },
|
|
1281
|
-
{ mnemonic: "SBC", mode: AddressingMode.AbsoluteX, opcode: 0xfd },
|
|
1282
|
-
{ mnemonic: "SBC", mode: AddressingMode.AbsoluteY, opcode: 0xf9 },
|
|
1283
|
-
{ mnemonic: "SBC", mode: AddressingMode.IndirectX, opcode: 0xe1 },
|
|
1284
|
-
{ mnemonic: "SBC", mode: AddressingMode.IndirectY, opcode: 0xf1 },
|
|
1285
|
-
{ mnemonic: "SEC", mode: AddressingMode.Implied, opcode: 0x38 },
|
|
1286
|
-
{ mnemonic: "SED", mode: AddressingMode.Implied, opcode: 0xf8 },
|
|
1287
|
-
{ mnemonic: "SEI", mode: AddressingMode.Implied, opcode: 0x78 },
|
|
1288
|
-
{ mnemonic: "STA", mode: AddressingMode.ZeroPage, opcode: 0x85 },
|
|
1289
|
-
{ mnemonic: "STA", mode: AddressingMode.ZeroPageX, opcode: 0x95 },
|
|
1290
|
-
{ mnemonic: "STA", mode: AddressingMode.Absolute, opcode: 0x8d },
|
|
1291
|
-
{ mnemonic: "STA", mode: AddressingMode.AbsoluteX, opcode: 0x9d },
|
|
1292
|
-
{ mnemonic: "STA", mode: AddressingMode.AbsoluteY, opcode: 0x99 },
|
|
1293
|
-
{ mnemonic: "STA", mode: AddressingMode.IndirectX, opcode: 0x81 },
|
|
1294
|
-
{ mnemonic: "STA", mode: AddressingMode.IndirectY, opcode: 0x91 },
|
|
1295
|
-
{ mnemonic: "STX", mode: AddressingMode.ZeroPage, opcode: 0x86 },
|
|
1296
|
-
{ mnemonic: "STX", mode: AddressingMode.ZeroPageY, opcode: 0x96 },
|
|
1297
|
-
{ mnemonic: "STX", mode: AddressingMode.Absolute, opcode: 0x8e },
|
|
1298
|
-
{ mnemonic: "STY", mode: AddressingMode.ZeroPage, opcode: 0x84 },
|
|
1299
|
-
{ mnemonic: "STY", mode: AddressingMode.ZeroPageX, opcode: 0x94 },
|
|
1300
|
-
{ mnemonic: "STY", mode: AddressingMode.Absolute, opcode: 0x8c },
|
|
1301
|
-
{ mnemonic: "TAX", mode: AddressingMode.Implied, opcode: 0xaa },
|
|
1302
|
-
{ mnemonic: "TAY", mode: AddressingMode.Implied, opcode: 0xa8 },
|
|
1303
|
-
{ mnemonic: "TSX", mode: AddressingMode.Implied, opcode: 0xba },
|
|
1304
|
-
{ mnemonic: "TXA", mode: AddressingMode.Implied, opcode: 0x8a },
|
|
1305
|
-
{ mnemonic: "TXS", mode: AddressingMode.Implied, opcode: 0x9a },
|
|
1306
|
-
{ mnemonic: "TYA", mode: AddressingMode.Implied, opcode: 0x98 },
|
|
1307
|
-
];
|
|
1308
1202
|
const INSTRUCTION_TABLE = buildInstructionTable();
|
|
@@ -6,7 +6,7 @@ Licensed under the GNU General Public License v2.0 or later.
|
|
|
6
6
|
See <https://www.gnu.org/licenses/> for details.
|
|
7
7
|
*/
|
|
8
8
|
import { Buffer } from "node:buffer";
|
|
9
|
-
import { asciiToPetscii, encodeStringWithNames } from "
|
|
9
|
+
import { asciiToPetscii, encodeStringWithNames } from "../../petscii.js";
|
|
10
10
|
const DEFAULT_START_ADDRESS = 0x0801;
|
|
11
11
|
const REM_TOKEN = 0x8f;
|
|
12
12
|
const TOKEN_TABLE = buildTokenTable();
|