c64bridge 0.9.1 → 0.9.3
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/AGENTS.md +5 -0
- package/README.md +115 -15
- package/data/assembly/assembly-spec.md +1 -1
- package/data/basic/rom-routines.md +269 -0
- package/data/context/bootstrap.md +4 -4
- 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/io/cia-spec.md +2 -2
- package/data/{memory/kernal-memory-map.md → kernal/rom-routines.md} +3 -2
- package/data/memory/low-memory-map.md +1 -0
- package/data/memory/memory-map.md +13 -0
- 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 -15
- package/dist/config.js +22 -0
- package/dist/mcp-server.js +17 -5
- package/dist/rag/knowledgeIndex.js +54 -9
- package/dist/rag/resourceUris.js +2 -0
- 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/developer.md +3 -2
- package/doc/reviews/audit.md +143 -0
- package/mcp/resources.json +60 -7
- package/mcp/schemas/c64_input.schema.json +231 -113
- package/mcp/server.json +1 -1
- package/mcp/tools.json +234 -115
- package/mcp.json +8 -4
- package/package.json +12 -8
- 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/scripts/update-readme.ts +1 -0
- package/server.json +3 -3
- package/dist/tools/disassembler.js +0 -478
- /package/dist/tools/{symbolRegistry.js → translation/symbolRegistry.js} +0 -0
|
@@ -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();
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 6502/6510 disassembler covering the full 256-opcode space, including
|
|
3
|
+
* undocumented (illegal) opcodes. Mnemonic naming follows the canonical
|
|
4
|
+
* convention used by the 1541U monitor (HLT->JAM, ASO->SLO, LSE->SRE,
|
|
5
|
+
* DCM->DCP, INS->ISC).
|
|
6
|
+
*/
|
|
7
|
+
import { OpcodeAddressingMode as Mode, OPCODES_BY_BYTE as OPCODES, opcodeSize as modeSize, } from "./opcodeInventory.js";
|
|
8
|
+
function hex2(v) {
|
|
9
|
+
return v.toString(16).toUpperCase().padStart(2, "0");
|
|
10
|
+
}
|
|
11
|
+
function hex4(v) {
|
|
12
|
+
return v.toString(16).toUpperCase().padStart(4, "0");
|
|
13
|
+
}
|
|
14
|
+
function labelOrHex4(addr, symbols) {
|
|
15
|
+
const label = symbols?.get(addr);
|
|
16
|
+
return label ? `${label} ($${hex4(addr)})` : `$${hex4(addr)}`;
|
|
17
|
+
}
|
|
18
|
+
function formatOperand(mode, bytes, offset, instrAddr, symbols) {
|
|
19
|
+
switch (mode) {
|
|
20
|
+
case Mode.Imp:
|
|
21
|
+
return "";
|
|
22
|
+
case Mode.Acc:
|
|
23
|
+
return "A";
|
|
24
|
+
case Mode.Imm:
|
|
25
|
+
return `#$${hex2(bytes[offset + 1] ?? 0)}`;
|
|
26
|
+
case Mode.Zp:
|
|
27
|
+
return `$${hex2(bytes[offset + 1] ?? 0)}`;
|
|
28
|
+
case Mode.ZpX:
|
|
29
|
+
return `$${hex2(bytes[offset + 1] ?? 0)},X`;
|
|
30
|
+
case Mode.ZpY:
|
|
31
|
+
return `$${hex2(bytes[offset + 1] ?? 0)},Y`;
|
|
32
|
+
case Mode.ZpS:
|
|
33
|
+
return `$${hex2(bytes[offset + 1] ?? 0)},S`;
|
|
34
|
+
case Mode.Rel: {
|
|
35
|
+
const rel = bytes[offset + 1] ?? 0;
|
|
36
|
+
const signed = rel >= 0x80 ? rel - 0x100 : rel;
|
|
37
|
+
const target = (instrAddr + 2 + signed) & 0xffff;
|
|
38
|
+
return labelOrHex4(target, symbols);
|
|
39
|
+
}
|
|
40
|
+
case Mode.Abs: {
|
|
41
|
+
const lo = bytes[offset + 1] ?? 0;
|
|
42
|
+
const hi = bytes[offset + 2] ?? 0;
|
|
43
|
+
const addr = lo | (hi << 8);
|
|
44
|
+
return labelOrHex4(addr, symbols);
|
|
45
|
+
}
|
|
46
|
+
case Mode.AbsX: {
|
|
47
|
+
const lo = bytes[offset + 1] ?? 0;
|
|
48
|
+
const hi = bytes[offset + 2] ?? 0;
|
|
49
|
+
const addr = lo | (hi << 8);
|
|
50
|
+
return `${labelOrHex4(addr, symbols)},X`;
|
|
51
|
+
}
|
|
52
|
+
case Mode.AbsY: {
|
|
53
|
+
const lo = bytes[offset + 1] ?? 0;
|
|
54
|
+
const hi = bytes[offset + 2] ?? 0;
|
|
55
|
+
const addr = lo | (hi << 8);
|
|
56
|
+
return `${labelOrHex4(addr, symbols)},Y`;
|
|
57
|
+
}
|
|
58
|
+
case Mode.Ind: {
|
|
59
|
+
const lo = bytes[offset + 1] ?? 0;
|
|
60
|
+
const hi = bytes[offset + 2] ?? 0;
|
|
61
|
+
const addr = lo | (hi << 8);
|
|
62
|
+
return `($${hex4(addr)})`;
|
|
63
|
+
}
|
|
64
|
+
case Mode.IndX:
|
|
65
|
+
return `($${hex2(bytes[offset + 1] ?? 0)},X)`;
|
|
66
|
+
case Mode.IndY:
|
|
67
|
+
return `($${hex2(bytes[offset + 1] ?? 0)}),Y`;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Disassemble a block of 6502/6510 bytes.
|
|
72
|
+
*
|
|
73
|
+
* @param bytes Raw memory bytes (may be longer than needed)
|
|
74
|
+
* @param baseAddress The C64 memory address of `bytes[0]`
|
|
75
|
+
* @param countOrOptions Either a max-instruction count (legacy form) or an options object
|
|
76
|
+
* @param symbols Optional address->label map (legacy positional form)
|
|
77
|
+
*/
|
|
78
|
+
export function disassemble(bytes, baseAddress, countOrOptions, symbols) {
|
|
79
|
+
const options = typeof countOrOptions === "object" && countOrOptions !== null
|
|
80
|
+
? countOrOptions
|
|
81
|
+
: { count: countOrOptions, symbols };
|
|
82
|
+
const limit = options.count ?? Infinity;
|
|
83
|
+
const symMap = options.symbols ?? symbols;
|
|
84
|
+
const allowUndocumented = options.allowUndocumented !== false;
|
|
85
|
+
const lines = [];
|
|
86
|
+
let offset = 0;
|
|
87
|
+
while (offset < bytes.length && lines.length < limit) {
|
|
88
|
+
const instrAddr = (baseAddress + offset) & 0xffff;
|
|
89
|
+
const opcode = bytes[offset] ?? 0;
|
|
90
|
+
const op = OPCODES[opcode];
|
|
91
|
+
const label = symMap?.get(instrAddr);
|
|
92
|
+
if (op.illegal && !allowUndocumented) {
|
|
93
|
+
lines.push({
|
|
94
|
+
address: instrAddr,
|
|
95
|
+
bytes: [opcode],
|
|
96
|
+
label,
|
|
97
|
+
mnemonic: "???",
|
|
98
|
+
operand: `$${hex2(opcode)}`,
|
|
99
|
+
undocumented: true,
|
|
100
|
+
});
|
|
101
|
+
offset += 1;
|
|
102
|
+
continue;
|
|
103
|
+
}
|
|
104
|
+
const size = modeSize(op.mode);
|
|
105
|
+
const instrBytes = [];
|
|
106
|
+
for (let i = 0; i < size && offset + i < bytes.length; i += 1) {
|
|
107
|
+
instrBytes.push(bytes[offset + i] ?? 0);
|
|
108
|
+
}
|
|
109
|
+
const operand = formatOperand(op.mode, bytes, offset, instrAddr, symMap);
|
|
110
|
+
lines.push({
|
|
111
|
+
address: instrAddr,
|
|
112
|
+
bytes: instrBytes,
|
|
113
|
+
label,
|
|
114
|
+
mnemonic: op.mnemonic,
|
|
115
|
+
operand,
|
|
116
|
+
undocumented: op.illegal,
|
|
117
|
+
});
|
|
118
|
+
offset += size;
|
|
119
|
+
}
|
|
120
|
+
return lines;
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Render disassembly lines as a human-readable string.
|
|
124
|
+
* Format: [label:\n] $ADDR XX XX XX MNEM operand ; *undocumented*
|
|
125
|
+
*/
|
|
126
|
+
export function formatDisassembly(lines) {
|
|
127
|
+
const parts = [];
|
|
128
|
+
for (const line of lines) {
|
|
129
|
+
if (line.label) {
|
|
130
|
+
parts.push(`${line.label}:`);
|
|
131
|
+
}
|
|
132
|
+
const addrStr = `$${hex4(line.address)}`;
|
|
133
|
+
const byteStr = line.bytes.map((b) => hex2(b)).join(" ").padEnd(8);
|
|
134
|
+
const mnemonic = line.undocumented ? `${line.mnemonic}*` : line.mnemonic;
|
|
135
|
+
const instrStr = line.operand
|
|
136
|
+
? `${mnemonic} ${line.operand}`
|
|
137
|
+
: mnemonic;
|
|
138
|
+
parts.push(` ${addrStr} ${byteStr} ${instrStr}`);
|
|
139
|
+
}
|
|
140
|
+
return parts.join("\n");
|
|
141
|
+
}
|
|
142
|
+
/** Returns true when the opcode is undocumented per the 1541U reference. */
|
|
143
|
+
export function isUndocumentedOpcode(opcode) {
|
|
144
|
+
return OPCODES[opcode & 0xff].illegal;
|
|
145
|
+
}
|
|
146
|
+
/** Lookup helper used by tests to verify table coverage. */
|
|
147
|
+
export function opcodeMetadata(opcode) {
|
|
148
|
+
const entry = OPCODES[opcode & 0xff];
|
|
149
|
+
return { mnemonic: entry.mnemonic, undocumented: entry.illegal, size: modeSize(entry.mode) };
|
|
150
|
+
}
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared 6502/6510 opcode inventory.
|
|
3
|
+
*
|
|
4
|
+
* Entries are sorted by increasing binary opcode and cover the complete
|
|
5
|
+
* 256-byte opcode space, including undocumented opcodes. Mnemonic naming
|
|
6
|
+
* follows the canonical 1541U monitor convention:
|
|
7
|
+
* HLT -> JAM, ASO -> SLO, LSE -> SRE, DCM -> DCP, INS -> ISC.
|
|
8
|
+
*/
|
|
9
|
+
export const OpcodeAddressingMode = {
|
|
10
|
+
Imp: "implied",
|
|
11
|
+
Acc: "accumulator",
|
|
12
|
+
Imm: "immediate",
|
|
13
|
+
Zp: "zeroPage",
|
|
14
|
+
ZpX: "zeroPageX",
|
|
15
|
+
ZpY: "zeroPageY",
|
|
16
|
+
ZpS: "zeroPageS",
|
|
17
|
+
Rel: "relative",
|
|
18
|
+
Abs: "absolute",
|
|
19
|
+
AbsX: "absoluteX",
|
|
20
|
+
AbsY: "absoluteY",
|
|
21
|
+
Ind: "indirect",
|
|
22
|
+
IndX: "indirectX",
|
|
23
|
+
IndY: "indirectY",
|
|
24
|
+
};
|
|
25
|
+
const M = OpcodeAddressingMode;
|
|
26
|
+
const OPCODE_ROWS = [
|
|
27
|
+
// 0x00-0x0F
|
|
28
|
+
[["BRK", M.Imp], ["ORA", M.IndX], ["HLT", M.Imp], ["ASO", M.IndX], ["NOP", M.Zp], ["ORA", M.Zp], ["ASL", M.Zp], ["ASO", M.Zp], ["PHP", M.Imp], ["ORA", M.Imm], ["ASL", M.Acc], ["ANC", M.Imm], ["NOP", M.Abs], ["ORA", M.Abs], ["ASL", M.Abs], ["ASO", M.Abs]],
|
|
29
|
+
// 0x10-0x1F
|
|
30
|
+
[["BPL", M.Rel], ["ORA", M.IndY], ["HLT", M.Imp], ["ASO", M.IndY], ["NOP", M.ZpX], ["ORA", M.ZpX], ["ASL", M.ZpX], ["ASO", M.ZpX], ["CLC", M.Imp], ["ORA", M.AbsY], ["NOP", M.Imp], ["ASO", M.AbsY], ["NOP", M.AbsX], ["ORA", M.AbsX], ["ASL", M.AbsX], ["ASO", M.AbsX]],
|
|
31
|
+
// 0x20-0x2F
|
|
32
|
+
[["JSR", M.Abs], ["AND", M.IndX], ["HLT", M.Imp], ["RLA", M.IndX], ["BIT", M.Zp], ["AND", M.Zp], ["ROL", M.Zp], ["RLA", M.Zp], ["PLP", M.Imp], ["AND", M.Imm], ["ROL", M.Acc], ["ANC", M.Imm], ["BIT", M.Abs], ["AND", M.Abs], ["ROL", M.Abs], ["RLA", M.Abs]],
|
|
33
|
+
// 0x30-0x3F
|
|
34
|
+
[["BMI", M.Rel], ["AND", M.IndY], ["HLT", M.Imp], ["RLA", M.IndY], ["NOP", M.ZpX], ["AND", M.ZpX], ["ROL", M.ZpX], ["RLA", M.ZpX], ["SEC", M.Imp], ["AND", M.AbsY], ["NOP", M.Imp], ["RLA", M.AbsY], ["NOP", M.AbsX], ["AND", M.AbsX], ["ROL", M.AbsX], ["RLA", M.AbsX]],
|
|
35
|
+
// 0x40-0x4F
|
|
36
|
+
[["RTI", M.Imp], ["EOR", M.IndX], ["HLT", M.Imp], ["LSE", M.IndX], ["NOP", M.Zp], ["EOR", M.Zp], ["LSR", M.Zp], ["LSE", M.Zp], ["PHA", M.Imp], ["EOR", M.Imm], ["LSR", M.Acc], ["ALR", M.Imm], ["JMP", M.Abs], ["EOR", M.Abs], ["LSR", M.Abs], ["LSE", M.Abs]],
|
|
37
|
+
// 0x50-0x5F
|
|
38
|
+
[["BVC", M.Rel], ["EOR", M.IndY], ["HLT", M.Imp], ["LSE", M.IndY], ["NOP", M.ZpX], ["EOR", M.ZpX], ["LSR", M.ZpX], ["LSE", M.ZpX], ["CLI", M.Imp], ["EOR", M.AbsY], ["NOP", M.Imp], ["LSE", M.AbsY], ["NOP", M.AbsX], ["EOR", M.AbsX], ["LSR", M.AbsX], ["LSE", M.AbsX]],
|
|
39
|
+
// 0x60-0x6F
|
|
40
|
+
[["RTS", M.Imp], ["ADC", M.IndX], ["HLT", M.Imp], ["RRA", M.IndX], ["NOP", M.Zp], ["ADC", M.Zp], ["ROR", M.Zp], ["RRA", M.Zp], ["PLA", M.Imp], ["ADC", M.Imm], ["ROR", M.Acc], ["ARR", M.Imm], ["JMP", M.Ind], ["ADC", M.Abs], ["ROR", M.Abs], ["RRA", M.Abs]],
|
|
41
|
+
// 0x70-0x7F
|
|
42
|
+
[["BVS", M.Rel], ["ADC", M.IndY], ["HLT", M.Imp], ["RRA", M.IndY], ["NOP", M.ZpX], ["ADC", M.ZpX], ["ROR", M.ZpX], ["RRA", M.ZpX], ["SEI", M.Imp], ["ADC", M.AbsY], ["NOP", M.Imp], ["RRA", M.AbsY], ["NOP", M.AbsX], ["ADC", M.AbsX], ["ROR", M.AbsX], ["RRA", M.AbsX]],
|
|
43
|
+
// 0x80-0x8F
|
|
44
|
+
[["NOP", M.Imm], ["STA", M.IndX], ["NOP", M.Imm], ["SAX", M.IndX], ["STY", M.Zp], ["STA", M.Zp], ["STX", M.Zp], ["SAX", M.Zp], ["DEY", M.Imp], ["NOP", M.Imm], ["TXA", M.Imp], ["XAA", M.Imm], ["STY", M.Abs], ["STA", M.Abs], ["STX", M.Abs], ["SAX", M.Abs]],
|
|
45
|
+
// 0x90-0x9F
|
|
46
|
+
[["BCC", M.Rel], ["STA", M.IndY], ["HLT", M.Imp], ["AHX", M.IndY], ["STY", M.ZpX], ["STA", M.ZpX], ["STX", M.ZpY], ["SAX", M.ZpY], ["TYA", M.Imp], ["STA", M.AbsY], ["TXS", M.Imp], ["TAS", M.AbsY], ["SHY", M.AbsX], ["STA", M.AbsX], ["SHX", M.AbsY], ["AHX", M.AbsY]],
|
|
47
|
+
// 0xA0-0xAF
|
|
48
|
+
[["LDY", M.Imm], ["LDA", M.IndX], ["LDX", M.Imm], ["LAX", M.IndX], ["LDY", M.Zp], ["LDA", M.Zp], ["LDX", M.Zp], ["LAX", M.Zp], ["TAY", M.Imp], ["LDA", M.Imm], ["TAX", M.Imp], ["LAX", M.Imm], ["LDY", M.Abs], ["LDA", M.Abs], ["LDX", M.Abs], ["LAX", M.Abs]],
|
|
49
|
+
// 0xB0-0xBF
|
|
50
|
+
[["BCS", M.Rel], ["LDA", M.IndY], ["HLT", M.Imp], ["LAX", M.IndY], ["LDY", M.ZpX], ["LDA", M.ZpX], ["LDX", M.ZpY], ["LAX", M.ZpY], ["CLV", M.Imp], ["LDA", M.AbsY], ["TSX", M.Imp], ["LAS", M.AbsY], ["LDY", M.AbsX], ["LDA", M.AbsX], ["LDX", M.AbsY], ["LAX", M.AbsY]],
|
|
51
|
+
// 0xC0-0xCF
|
|
52
|
+
[["CPY", M.Imm], ["CMP", M.IndX], ["NOP", M.Imm], ["DCM", M.IndX], ["CPY", M.Zp], ["CMP", M.Zp], ["DEC", M.Zp], ["DCM", M.Zp], ["INY", M.Imp], ["CMP", M.Imm], ["DEX", M.Imp], ["AXS", M.Imm], ["CPY", M.Abs], ["CMP", M.Abs], ["DEC", M.Abs], ["DCM", M.Abs]],
|
|
53
|
+
// 0xD0-0xDF
|
|
54
|
+
[["BNE", M.Rel], ["CMP", M.IndY], ["HLT", M.Imp], ["DCM", M.IndY], ["NOP", M.ZpX], ["CMP", M.ZpX], ["DEC", M.ZpX], ["DCM", M.ZpX], ["CLD", M.Imp], ["CMP", M.AbsY], ["NOP", M.Imp], ["DCM", M.AbsY], ["NOP", M.AbsX], ["CMP", M.AbsX], ["DEC", M.AbsX], ["DCM", M.AbsX]],
|
|
55
|
+
// 0xE0-0xEF
|
|
56
|
+
[["CPX", M.Imm], ["SBC", M.IndX], ["NOP", M.Imm], ["INS", M.IndX], ["CPX", M.Zp], ["SBC", M.Zp], ["INC", M.Zp], ["INS", M.Zp], ["INX", M.Imp], ["SBC", M.Imm], ["NOP", M.Imp], ["SBC", M.Imm], ["CPX", M.Abs], ["SBC", M.Abs], ["INC", M.Abs], ["INS", M.Abs]],
|
|
57
|
+
// 0xF0-0xFF
|
|
58
|
+
[["BEQ", M.Rel], ["SBC", M.IndY], ["HLT", M.Imp], ["INS", M.IndY], ["NOP", M.ZpS], ["SBC", M.ZpX], ["INC", M.ZpX], ["INS", M.ZpX], ["SED", M.Imp], ["SBC", M.AbsY], ["NOP", M.Imp], ["INS", M.AbsY], ["NOP", M.AbsX], ["SBC", M.AbsX], ["INC", M.AbsX], ["INS", M.AbsX]],
|
|
59
|
+
];
|
|
60
|
+
const ILLEGAL_MAP = [
|
|
61
|
+
0x989C, 0x9C9C, 0x888C, 0x9C9C,
|
|
62
|
+
0x889C, 0x9C9C, 0x889C, 0x9C9C,
|
|
63
|
+
0x8A8D, 0xD88C, 0x8888, 0x888C,
|
|
64
|
+
0x888C, 0x9C9C, 0x888C, 0x9C9C,
|
|
65
|
+
];
|
|
66
|
+
function isIllegal(opcode) {
|
|
67
|
+
const row = ILLEGAL_MAP[opcode >> 4];
|
|
68
|
+
return ((row >> (opcode & 0x0F)) & 1) !== 0;
|
|
69
|
+
}
|
|
70
|
+
function canonical(mnemonic) {
|
|
71
|
+
switch (mnemonic) {
|
|
72
|
+
case "HLT": return "JAM";
|
|
73
|
+
case "ASO": return "SLO";
|
|
74
|
+
case "LSE": return "SRE";
|
|
75
|
+
case "DCM": return "DCP";
|
|
76
|
+
case "INS": return "ISC";
|
|
77
|
+
default: return mnemonic;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
function buildOpcodeInventory() {
|
|
81
|
+
const entries = OPCODE_ROWS.flatMap((row, rowIndex) => row.map(([mnemonic, mode], columnIndex) => {
|
|
82
|
+
const opcode = (rowIndex << 4) | columnIndex;
|
|
83
|
+
return {
|
|
84
|
+
opcode,
|
|
85
|
+
mnemonic: canonical(mnemonic),
|
|
86
|
+
mode,
|
|
87
|
+
illegal: isIllegal(opcode),
|
|
88
|
+
};
|
|
89
|
+
}));
|
|
90
|
+
validateOpcodeInventory(entries);
|
|
91
|
+
return entries;
|
|
92
|
+
}
|
|
93
|
+
function validateOpcodeInventory(entries) {
|
|
94
|
+
if (entries.length !== 256) {
|
|
95
|
+
throw new Error(`Opcode inventory must contain 256 entries, found ${entries.length}`);
|
|
96
|
+
}
|
|
97
|
+
for (let opcode = 0; opcode < 256; opcode += 1) {
|
|
98
|
+
const entry = entries[opcode];
|
|
99
|
+
if (!entry || entry.opcode !== opcode) {
|
|
100
|
+
throw new Error(`Opcode inventory is not sorted at $${opcode.toString(16).padStart(2, "0")}`);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
export function opcodeSize(mode) {
|
|
105
|
+
switch (mode) {
|
|
106
|
+
case M.Imp:
|
|
107
|
+
case M.Acc:
|
|
108
|
+
return 1;
|
|
109
|
+
case M.Imm:
|
|
110
|
+
case M.Zp:
|
|
111
|
+
case M.ZpX:
|
|
112
|
+
case M.ZpY:
|
|
113
|
+
case M.ZpS:
|
|
114
|
+
case M.Rel:
|
|
115
|
+
case M.IndX:
|
|
116
|
+
case M.IndY:
|
|
117
|
+
return 2;
|
|
118
|
+
case M.Abs:
|
|
119
|
+
case M.AbsX:
|
|
120
|
+
case M.AbsY:
|
|
121
|
+
case M.Ind:
|
|
122
|
+
return 3;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
export const OPCODE_INVENTORY = Object.freeze(buildOpcodeInventory());
|
|
126
|
+
export const OPCODES_BY_BYTE = OPCODE_INVENTORY;
|
package/doc/developer.md
CHANGED
|
@@ -124,9 +124,10 @@ Environment knobs: `RAG_EMBEDDINGS_DIR`, `RAG_BUILD_ON_START`, `RAG_REINDEX_INTE
|
|
|
124
124
|
|
|
125
125
|
Tagging a release through the GitHub release UI triggers [`.github/workflows/release.yaml`](../.github/workflows/release.yaml), which publishes the npm package and then publishes the same version to the MCP Registry.
|
|
126
126
|
|
|
127
|
-
-
|
|
127
|
+
- npm publication now uses trusted publishing over GitHub OIDC rather than an `NPM_TOKEN` secret
|
|
128
|
+
- One-time npm setup is required in the package settings for `c64bridge`: provider `GitHub Actions`, repository `chrisgleissner/c64bridge`, workflow `.github/workflows/release.yaml`, and allowed action `npm publish`
|
|
128
129
|
- No dedicated MCP Registry secret is required for the GitHub-driven flow; the workflow uses `mcp-publisher login github-oidc`
|
|
129
|
-
- The release workflow must grant `id-token: write` so GitHub Actions can mint the short-lived OIDC
|
|
130
|
+
- The release workflow must grant `id-token: write` so GitHub Actions can mint the short-lived OIDC tokens used by both npm trusted publishing and `mcp-publisher`
|
|
130
131
|
- Keep [`package.json`](../package.json) `mcpName` aligned with [`server.json`](../server.json) `name`
|
|
131
132
|
- The root [`server.json`](../server.json) is the MCP Registry manifest; [`mcp/server.json`](../mcp/server.json) remains generated MCP interface output
|
|
132
133
|
|