@gaialabs/core 0.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/dist/browser.d.mts +2 -0
- package/dist/browser.d.ts +2 -0
- package/dist/browser.js +4 -0
- package/dist/browser.js.map +1 -0
- package/dist/browser.mjs +3 -0
- package/dist/browser.mjs.map +1 -0
- package/dist/index.d.mts +1015 -0
- package/dist/index.d.ts +1015 -0
- package/dist/index.js +3603 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +3561 -0
- package/dist/index.mjs.map +1 -0
- package/dist/node.d.mts +2 -0
- package/dist/node.d.ts +2 -0
- package/dist/node.js +4 -0
- package/dist/node.js.map +1 -0
- package/dist/node.mjs +3 -0
- package/dist/node.mjs.map +1 -0
- package/dist/worker.d.mts +2 -0
- package/dist/worker.d.ts +2 -0
- package/dist/worker.js +4 -0
- package/dist/worker.js.map +1 -0
- package/dist/worker.mjs +3 -0
- package/dist/worker.mjs.map +1 -0
- package/package.json +69 -0
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,3561 @@
|
|
|
1
|
+
import { BitStream, RomProcessingConstants, BinType, AddressingMode, RegisterType, Address, DbBlockUtils, BlockReaderConstants, createTableEntry, LocationWrapper, createWord, createByte, StatusFlags, AddressSpace, AddressType, MemberType, readFileAsBinary, DbRootUtils, readJsonFile, getDirectory } from 'gaia-shared';
|
|
2
|
+
import { promises } from 'fs';
|
|
3
|
+
import { join } from 'path';
|
|
4
|
+
|
|
5
|
+
// src/api/RomProcessor.ts
|
|
6
|
+
|
|
7
|
+
// src/sprites/SpriteFrame.ts
|
|
8
|
+
var SpriteFrame = class {
|
|
9
|
+
constructor(duration = 0, groupIndex = 0, groupOffset = 0) {
|
|
10
|
+
this.duration = duration;
|
|
11
|
+
this.groupIndex = groupIndex;
|
|
12
|
+
this.groupOffset = groupOffset;
|
|
13
|
+
}
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
// src/sprites/SpritePart.ts
|
|
17
|
+
var SpritePart = class {
|
|
18
|
+
constructor() {
|
|
19
|
+
this.isLarge = false;
|
|
20
|
+
this.xOffset = 0;
|
|
21
|
+
this.xOffsetMirror = 0;
|
|
22
|
+
this.yOffset = 0;
|
|
23
|
+
this.yOffsetMirror = 0;
|
|
24
|
+
this.vMirror = false;
|
|
25
|
+
this.hMirror = false;
|
|
26
|
+
this.someOffset = 0;
|
|
27
|
+
this.paletteIndex = 0;
|
|
28
|
+
this.tileIndex = 0;
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
// src/sprites/SpriteGroup.ts
|
|
33
|
+
var SpriteGroup = class {
|
|
34
|
+
constructor() {
|
|
35
|
+
this.xOffset = 0;
|
|
36
|
+
this.xOffsetMirror = 0;
|
|
37
|
+
this.yOffset = 0;
|
|
38
|
+
this.yOffsetMirror = 0;
|
|
39
|
+
this.xRecoilHitboxOffset = 0;
|
|
40
|
+
this.yRecoilHitboxOffset = 0;
|
|
41
|
+
this.xRecoilHitboxTilesize = 0;
|
|
42
|
+
this.yRecoilHitboxTilesize = 0;
|
|
43
|
+
this.xHostileHitboxOffset = 0;
|
|
44
|
+
this.xHostileHitboxSize = 0;
|
|
45
|
+
this.yHostileHitboxOffset = 0;
|
|
46
|
+
this.yHostileHitboxSize = 0;
|
|
47
|
+
this.parts = [];
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
// src/sprites/SpriteMap.ts
|
|
52
|
+
var SpriteMap = class _SpriteMap {
|
|
53
|
+
constructor() {
|
|
54
|
+
this.frameSets = [];
|
|
55
|
+
this.groups = [];
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Create a SpriteMap from binary data
|
|
59
|
+
* @param data Binary data buffer
|
|
60
|
+
* @returns SpriteMap instance
|
|
61
|
+
*/
|
|
62
|
+
static fromBytes(data) {
|
|
63
|
+
let position = 0;
|
|
64
|
+
const getByte = () => {
|
|
65
|
+
if (position >= data.length) return 0;
|
|
66
|
+
return data[position++];
|
|
67
|
+
};
|
|
68
|
+
const getUshort = () => {
|
|
69
|
+
if (position + 1 >= data.length) return 0;
|
|
70
|
+
const low = data[position++];
|
|
71
|
+
const high = data[position++];
|
|
72
|
+
return low | high << 8;
|
|
73
|
+
};
|
|
74
|
+
const spriteMap = new _SpriteMap();
|
|
75
|
+
const setOffsets = /* @__PURE__ */ new Set();
|
|
76
|
+
const groupOffsets = /* @__PURE__ */ new Set();
|
|
77
|
+
position = 0;
|
|
78
|
+
while (!setOffsets.has(position)) {
|
|
79
|
+
const offset = getUshort() - 16384;
|
|
80
|
+
setOffsets.add(offset);
|
|
81
|
+
}
|
|
82
|
+
for (const offStart of setOffsets) {
|
|
83
|
+
position = offStart;
|
|
84
|
+
const frameList = [];
|
|
85
|
+
while (true) {
|
|
86
|
+
const duration = getUshort();
|
|
87
|
+
if (duration === 65535) {
|
|
88
|
+
break;
|
|
89
|
+
}
|
|
90
|
+
const groupOffset = getUshort() - 16384;
|
|
91
|
+
frameList.push(new SpriteFrame(duration, 0, groupOffset));
|
|
92
|
+
groupOffsets.add(groupOffset);
|
|
93
|
+
}
|
|
94
|
+
spriteMap.frameSets.push(frameList);
|
|
95
|
+
}
|
|
96
|
+
let grpIx = 0;
|
|
97
|
+
const sortedGroupOffsets = Array.from(groupOffsets).sort((a, b) => a - b);
|
|
98
|
+
for (const offStart of sortedGroupOffsets) {
|
|
99
|
+
position = offStart;
|
|
100
|
+
for (const set of spriteMap.frameSets) {
|
|
101
|
+
for (const frame of set) {
|
|
102
|
+
if (frame.groupOffset === offStart) {
|
|
103
|
+
frame.groupIndex = grpIx;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
const grp = new SpriteGroup();
|
|
108
|
+
grp.xOffset = getByte();
|
|
109
|
+
grp.xOffsetMirror = getByte();
|
|
110
|
+
grp.yOffset = getByte();
|
|
111
|
+
grp.yOffsetMirror = getByte();
|
|
112
|
+
grp.xRecoilHitboxOffset = getByte();
|
|
113
|
+
grp.yRecoilHitboxOffset = getByte();
|
|
114
|
+
grp.xRecoilHitboxTilesize = getByte();
|
|
115
|
+
grp.yRecoilHitboxTilesize = getByte();
|
|
116
|
+
grp.xHostileHitboxOffset = getByte();
|
|
117
|
+
grp.xHostileHitboxSize = getByte();
|
|
118
|
+
grp.yHostileHitboxOffset = getByte();
|
|
119
|
+
grp.yHostileHitboxSize = getByte();
|
|
120
|
+
let numParts = getByte();
|
|
121
|
+
while (numParts-- > 0) {
|
|
122
|
+
const part = new SpritePart();
|
|
123
|
+
part.isLarge = getByte() !== 0;
|
|
124
|
+
part.xOffset = getByte();
|
|
125
|
+
part.xOffsetMirror = getByte();
|
|
126
|
+
part.yOffset = getByte();
|
|
127
|
+
part.yOffsetMirror = getByte();
|
|
128
|
+
const props = getUshort();
|
|
129
|
+
part.vMirror = (props & 32768) !== 0;
|
|
130
|
+
part.hMirror = (props & 16384) !== 0;
|
|
131
|
+
part.someOffset = props >> 12 & 3;
|
|
132
|
+
part.paletteIndex = props >> 9 & 7;
|
|
133
|
+
part.tileIndex = props & 511;
|
|
134
|
+
grp.parts.push(part);
|
|
135
|
+
}
|
|
136
|
+
spriteMap.groups.push(grp);
|
|
137
|
+
grpIx++;
|
|
138
|
+
}
|
|
139
|
+
return spriteMap;
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Convert this SpriteMap to binary data
|
|
143
|
+
* @returns Binary data buffer
|
|
144
|
+
*/
|
|
145
|
+
toBytes() {
|
|
146
|
+
let size = this.frameSets.length * 2;
|
|
147
|
+
for (const set of this.frameSets) {
|
|
148
|
+
size += set.length * 4 + 2;
|
|
149
|
+
}
|
|
150
|
+
for (const grp of this.groups) {
|
|
151
|
+
size += grp.parts.length * 7 + 13;
|
|
152
|
+
}
|
|
153
|
+
const buffer = new Uint8Array(size);
|
|
154
|
+
let position = 0;
|
|
155
|
+
const writeShort = (val) => {
|
|
156
|
+
buffer[position++] = val & 255;
|
|
157
|
+
buffer[position++] = val >> 8 & 255;
|
|
158
|
+
};
|
|
159
|
+
const writeLoc = (val) => writeShort(val + 16384);
|
|
160
|
+
let pos = this.frameSets.length << 1;
|
|
161
|
+
const groupPos = new Array(this.groups.length);
|
|
162
|
+
for (const set of this.frameSets) {
|
|
163
|
+
writeLoc(pos);
|
|
164
|
+
pos += (set.length << 2) + 2;
|
|
165
|
+
}
|
|
166
|
+
for (let i = 0; i < this.groups.length; i++) {
|
|
167
|
+
groupPos[i] = pos;
|
|
168
|
+
pos += this.groups[i].parts.length * 7 + 13;
|
|
169
|
+
}
|
|
170
|
+
for (const set of this.frameSets) {
|
|
171
|
+
for (const frm of set) {
|
|
172
|
+
writeShort(frm.duration);
|
|
173
|
+
writeLoc(groupPos[frm.groupIndex]);
|
|
174
|
+
}
|
|
175
|
+
writeShort(65535);
|
|
176
|
+
}
|
|
177
|
+
for (const grp of this.groups) {
|
|
178
|
+
buffer[position++] = grp.xOffset;
|
|
179
|
+
buffer[position++] = grp.xOffsetMirror;
|
|
180
|
+
buffer[position++] = grp.yOffset;
|
|
181
|
+
buffer[position++] = grp.yOffsetMirror;
|
|
182
|
+
buffer[position++] = grp.xRecoilHitboxOffset;
|
|
183
|
+
buffer[position++] = grp.yRecoilHitboxOffset;
|
|
184
|
+
buffer[position++] = grp.xRecoilHitboxTilesize;
|
|
185
|
+
buffer[position++] = grp.yRecoilHitboxTilesize;
|
|
186
|
+
buffer[position++] = grp.xHostileHitboxOffset;
|
|
187
|
+
buffer[position++] = grp.xHostileHitboxSize;
|
|
188
|
+
buffer[position++] = grp.yHostileHitboxOffset;
|
|
189
|
+
buffer[position++] = grp.yHostileHitboxSize;
|
|
190
|
+
buffer[position++] = grp.parts.length;
|
|
191
|
+
for (const prt of grp.parts) {
|
|
192
|
+
buffer[position++] = prt.isLarge ? 1 : 0;
|
|
193
|
+
buffer[position++] = prt.xOffset;
|
|
194
|
+
buffer[position++] = prt.xOffsetMirror;
|
|
195
|
+
buffer[position++] = prt.yOffset;
|
|
196
|
+
buffer[position++] = prt.yOffsetMirror;
|
|
197
|
+
const accum = (prt.vMirror ? 32768 : 0) | (prt.hMirror ? 16384 : 0) | (prt.someOffset & 3) << 12 | (prt.paletteIndex & 7) << 9 | prt.tileIndex;
|
|
198
|
+
writeShort(accum);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
return buffer.slice(0, position);
|
|
202
|
+
}
|
|
203
|
+
};
|
|
204
|
+
|
|
205
|
+
// src/rom/state.ts
|
|
206
|
+
var RomState = class _RomState {
|
|
207
|
+
constructor() {
|
|
208
|
+
// Hardware memory buffers
|
|
209
|
+
this.cgram = new Uint8Array(512);
|
|
210
|
+
// Color palette RAM
|
|
211
|
+
this.vram = new Uint8Array(65536);
|
|
212
|
+
// Video RAM
|
|
213
|
+
// Tileset data
|
|
214
|
+
this.mainTileset = new Uint8Array(2048);
|
|
215
|
+
this.effectTileset = new Uint8Array(2048);
|
|
216
|
+
// Tilemap data
|
|
217
|
+
this.mainTilemap = new Uint8Array(8192);
|
|
218
|
+
this.mainTilemapW = 0;
|
|
219
|
+
this.mainTilemapH = 0;
|
|
220
|
+
this.effectTilemap = new Uint8Array(8192);
|
|
221
|
+
this.effectTilemapW = 0;
|
|
222
|
+
this.effectTilemapH = 0;
|
|
223
|
+
}
|
|
224
|
+
static {
|
|
225
|
+
// Sprite data
|
|
226
|
+
this.spriteMap = null;
|
|
227
|
+
}
|
|
228
|
+
static {
|
|
229
|
+
this.spriteMapPath = null;
|
|
230
|
+
}
|
|
231
|
+
/**
|
|
232
|
+
* Strip address space prefixes from names
|
|
233
|
+
*/
|
|
234
|
+
static stripName(name) {
|
|
235
|
+
const addressSpace = ["@", "&", "^", "#", "$", "%", "*"];
|
|
236
|
+
while (name.length > 0 && addressSpace.includes(name[0])) {
|
|
237
|
+
name = name.substring(1);
|
|
238
|
+
}
|
|
239
|
+
return name;
|
|
240
|
+
}
|
|
241
|
+
/**
|
|
242
|
+
* Create ROM state from scene metadata
|
|
243
|
+
* Simplified version of the C# implementation
|
|
244
|
+
*/
|
|
245
|
+
static async fromScene(baseDir, root, metaFile, id) {
|
|
246
|
+
const state = new _RomState();
|
|
247
|
+
return state;
|
|
248
|
+
}
|
|
249
|
+
/**
|
|
250
|
+
* Process scene command (simplified version)
|
|
251
|
+
* TODO: Implement full command processing
|
|
252
|
+
*/
|
|
253
|
+
static async processCommand(state, command, params, getResource) {
|
|
254
|
+
switch (command) {
|
|
255
|
+
case 2:
|
|
256
|
+
break;
|
|
257
|
+
case 3:
|
|
258
|
+
break;
|
|
259
|
+
case 4:
|
|
260
|
+
break;
|
|
261
|
+
case 5:
|
|
262
|
+
break;
|
|
263
|
+
case 6:
|
|
264
|
+
break;
|
|
265
|
+
case 16:
|
|
266
|
+
if (params.length > 0) {
|
|
267
|
+
const spritePath = getResource(params[0], BinType.Spritemap);
|
|
268
|
+
try {
|
|
269
|
+
const data = await readFileAsBinary(spritePath);
|
|
270
|
+
_RomState.spriteMap = SpriteMap.fromBytes(data);
|
|
271
|
+
_RomState.spriteMapPath = spritePath;
|
|
272
|
+
} catch (error) {
|
|
273
|
+
console.warn(`Failed to load sprite map: ${spritePath}`, error);
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
break;
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
};
|
|
280
|
+
var RomStateUtils = class {
|
|
281
|
+
/**
|
|
282
|
+
* Create empty ROM state
|
|
283
|
+
*/
|
|
284
|
+
static createEmpty() {
|
|
285
|
+
return new RomState();
|
|
286
|
+
}
|
|
287
|
+
/**
|
|
288
|
+
* Clear all data in ROM state
|
|
289
|
+
*/
|
|
290
|
+
static clear(state) {
|
|
291
|
+
state.cgram.fill(0);
|
|
292
|
+
state.vram.fill(0);
|
|
293
|
+
state.mainTileset.fill(0);
|
|
294
|
+
state.effectTileset.fill(0);
|
|
295
|
+
state.mainTilemap.fill(0);
|
|
296
|
+
state.effectTilemap.fill(0);
|
|
297
|
+
state.mainTilesetPath = void 0;
|
|
298
|
+
state.effectTilesetPath = void 0;
|
|
299
|
+
state.mainTilemapPath = void 0;
|
|
300
|
+
state.effectTilemapPath = void 0;
|
|
301
|
+
state.mainTilemapW = 0;
|
|
302
|
+
state.mainTilemapH = 0;
|
|
303
|
+
state.effectTilemapW = 0;
|
|
304
|
+
state.effectTilemapH = 0;
|
|
305
|
+
}
|
|
306
|
+
};
|
|
307
|
+
var QuintetLZ = class _QuintetLZ {
|
|
308
|
+
static {
|
|
309
|
+
this.DICTIONARY_SIZE = 256;
|
|
310
|
+
}
|
|
311
|
+
static {
|
|
312
|
+
this.DICTIONARY_INIT = 32;
|
|
313
|
+
}
|
|
314
|
+
static {
|
|
315
|
+
this.DICTIONARY_OFFSET = 239;
|
|
316
|
+
}
|
|
317
|
+
static {
|
|
318
|
+
this.DEFAULT_PAGE_SIZE = 32768;
|
|
319
|
+
}
|
|
320
|
+
/**
|
|
321
|
+
* Expand (decompress) data using QuintetLZ algorithm
|
|
322
|
+
* @param srcData Source data buffer
|
|
323
|
+
* @param srcPosition Starting position in source data
|
|
324
|
+
* @param srcLen Length of source data to process
|
|
325
|
+
* @returns Expanded data
|
|
326
|
+
*/
|
|
327
|
+
expand(srcData, srcPosition = 0, srcLen = _QuintetLZ.DEFAULT_PAGE_SIZE) {
|
|
328
|
+
const bitStream = new BitStream(srcData, srcPosition);
|
|
329
|
+
const srcStop = srcPosition + srcLen;
|
|
330
|
+
const dictionary = new Uint8Array(_QuintetLZ.DICTIONARY_SIZE);
|
|
331
|
+
dictionary.fill(_QuintetLZ.DICTIONARY_INIT);
|
|
332
|
+
let dictPosition = _QuintetLZ.DICTIONARY_OFFSET;
|
|
333
|
+
let outPosition = 0;
|
|
334
|
+
const dstLen = bitStream.readShort();
|
|
335
|
+
const outBuffer = new Uint8Array(dstLen);
|
|
336
|
+
while (bitStream.currentPosition < srcStop && outPosition < dstLen) {
|
|
337
|
+
if (bitStream.readBit()) {
|
|
338
|
+
const sample = bitStream.readByte();
|
|
339
|
+
if (outPosition < dstLen) {
|
|
340
|
+
outBuffer[outPosition++] = sample;
|
|
341
|
+
}
|
|
342
|
+
dictionary[dictPosition] = sample;
|
|
343
|
+
dictPosition = dictPosition + 1 & 255;
|
|
344
|
+
} else {
|
|
345
|
+
let wordIndex = bitStream.readByte();
|
|
346
|
+
let wordLength = bitStream.readNibble() + 2;
|
|
347
|
+
while (wordLength-- > 0) {
|
|
348
|
+
const sample = dictionary[wordIndex];
|
|
349
|
+
wordIndex = wordIndex + 1 & 255;
|
|
350
|
+
if (outPosition < dstLen) {
|
|
351
|
+
outBuffer[outPosition++] = sample;
|
|
352
|
+
}
|
|
353
|
+
dictionary[dictPosition] = sample;
|
|
354
|
+
dictPosition = dictPosition + 1 & 255;
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
if (outPosition < dstLen) {
|
|
359
|
+
return outBuffer.slice(0, outPosition);
|
|
360
|
+
}
|
|
361
|
+
return outBuffer;
|
|
362
|
+
}
|
|
363
|
+
/**
|
|
364
|
+
* Compact (compress) data using QuintetLZ algorithm
|
|
365
|
+
* @param srcData Source data to compress
|
|
366
|
+
* @returns Compressed data
|
|
367
|
+
*/
|
|
368
|
+
compact(srcData) {
|
|
369
|
+
const dictionary = new Uint8Array(_QuintetLZ.DICTIONARY_SIZE);
|
|
370
|
+
dictionary.fill(_QuintetLZ.DICTIONARY_INIT);
|
|
371
|
+
let offset = _QuintetLZ.DICTIONARY_OFFSET;
|
|
372
|
+
let srcIx = 0;
|
|
373
|
+
let dstIx = 0;
|
|
374
|
+
const srcLen = srcData.length;
|
|
375
|
+
const outputBuffer = new Uint8Array(srcLen * 2);
|
|
376
|
+
outputBuffer[dstIx++] = srcLen & 255;
|
|
377
|
+
outputBuffer[dstIx++] = srcLen >> 8 & 255;
|
|
378
|
+
const bitStream = new BitStream(outputBuffer, 2);
|
|
379
|
+
const getCommand = () => {
|
|
380
|
+
const maxLen = Math.min(srcLen - srcIx, 17);
|
|
381
|
+
if (maxLen < 2) {
|
|
382
|
+
return [0, 0];
|
|
383
|
+
}
|
|
384
|
+
let startByte = 0;
|
|
385
|
+
let bestLen = 0;
|
|
386
|
+
let bx = offset;
|
|
387
|
+
for (let i = 0; i < 256; i++, bx = bx + 1 & 255) {
|
|
388
|
+
let size = 0;
|
|
389
|
+
let bix = bx;
|
|
390
|
+
while (size < maxLen && dictionary[bix] === srcData[srcIx + size]) {
|
|
391
|
+
bix = bix + 1 & 255;
|
|
392
|
+
if (bix === offset) {
|
|
393
|
+
bix = bx;
|
|
394
|
+
}
|
|
395
|
+
size++;
|
|
396
|
+
}
|
|
397
|
+
if (size > bestLen) {
|
|
398
|
+
startByte = bx;
|
|
399
|
+
bestLen = size;
|
|
400
|
+
if (bestLen >= maxLen) {
|
|
401
|
+
break;
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
return [startByte, bestLen];
|
|
406
|
+
};
|
|
407
|
+
while (srcIx < srcLen) {
|
|
408
|
+
const [cmdStart, cmdLen] = getCommand();
|
|
409
|
+
if (cmdLen >= 2) {
|
|
410
|
+
bitStream.writeBit(false);
|
|
411
|
+
bitStream.writeByte(cmdStart);
|
|
412
|
+
bitStream.writeNibble(cmdLen - 2);
|
|
413
|
+
for (let i = 0; i < cmdLen; i++) {
|
|
414
|
+
dictionary[offset] = srcData[srcIx++];
|
|
415
|
+
offset = offset + 1 & 255;
|
|
416
|
+
}
|
|
417
|
+
} else {
|
|
418
|
+
bitStream.writeBit(true);
|
|
419
|
+
const val = srcData[srcIx++];
|
|
420
|
+
bitStream.writeByte(val);
|
|
421
|
+
dictionary[offset] = val;
|
|
422
|
+
offset = offset + 1 & 255;
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
bitStream.flush();
|
|
426
|
+
const finalSize = Math.max(dstIx, bitStream.currentPosition);
|
|
427
|
+
return outputBuffer.slice(0, finalSize);
|
|
428
|
+
}
|
|
429
|
+
};
|
|
430
|
+
|
|
431
|
+
// src/rom/extraction/processor.ts
|
|
432
|
+
var ProcessorStateManager = class {
|
|
433
|
+
constructor() {
|
|
434
|
+
this.accumulatorFlags = /* @__PURE__ */ new Map();
|
|
435
|
+
this.indexFlags = /* @__PURE__ */ new Map();
|
|
436
|
+
this.bankNotes = /* @__PURE__ */ new Map();
|
|
437
|
+
this.stackPositions = /* @__PURE__ */ new Map();
|
|
438
|
+
}
|
|
439
|
+
/**
|
|
440
|
+
* Hydrate processor registers with stored state
|
|
441
|
+
* Uses Registers from gaia-core/assembly for processor state management
|
|
442
|
+
*/
|
|
443
|
+
hydrateRegisters(position, reg) {
|
|
444
|
+
const acc = this.accumulatorFlags.get(position);
|
|
445
|
+
if (acc !== void 0) {
|
|
446
|
+
reg.accumulatorFlag = acc;
|
|
447
|
+
}
|
|
448
|
+
const ind = this.indexFlags.get(position);
|
|
449
|
+
if (ind !== void 0) {
|
|
450
|
+
reg.indexFlag = ind;
|
|
451
|
+
}
|
|
452
|
+
const bnk = this.bankNotes.get(position);
|
|
453
|
+
if (bnk !== void 0) {
|
|
454
|
+
reg.dataBank = bnk;
|
|
455
|
+
}
|
|
456
|
+
const stack = this.stackPositions.get(position);
|
|
457
|
+
if (stack !== void 0) {
|
|
458
|
+
reg.stack.location = stack;
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
// Accumulator flag methods
|
|
462
|
+
getAccumulatorFlag(location) {
|
|
463
|
+
return this.accumulatorFlags.get(location);
|
|
464
|
+
}
|
|
465
|
+
setAccumulatorFlag(location, value) {
|
|
466
|
+
this.accumulatorFlags.set(location, value);
|
|
467
|
+
}
|
|
468
|
+
tryAddAccumulatorFlag(location, value) {
|
|
469
|
+
if (this.accumulatorFlags.has(location)) {
|
|
470
|
+
return false;
|
|
471
|
+
}
|
|
472
|
+
this.accumulatorFlags.set(location, value);
|
|
473
|
+
return true;
|
|
474
|
+
}
|
|
475
|
+
// Index flag methods
|
|
476
|
+
getIndexFlag(location) {
|
|
477
|
+
return this.indexFlags.get(location);
|
|
478
|
+
}
|
|
479
|
+
setIndexFlag(location, value) {
|
|
480
|
+
this.indexFlags.set(location, value);
|
|
481
|
+
}
|
|
482
|
+
tryAddIndexFlag(location, value) {
|
|
483
|
+
if (this.indexFlags.has(location)) {
|
|
484
|
+
return false;
|
|
485
|
+
}
|
|
486
|
+
this.indexFlags.set(location, value);
|
|
487
|
+
return true;
|
|
488
|
+
}
|
|
489
|
+
// Bank note methods
|
|
490
|
+
getBankNote(location) {
|
|
491
|
+
return this.bankNotes.get(location);
|
|
492
|
+
}
|
|
493
|
+
setBankNote(location, value) {
|
|
494
|
+
this.bankNotes.set(location, value);
|
|
495
|
+
}
|
|
496
|
+
// Stack position methods
|
|
497
|
+
getStackPosition(location) {
|
|
498
|
+
return this.stackPositions.get(location);
|
|
499
|
+
}
|
|
500
|
+
setStackPosition(location, value) {
|
|
501
|
+
this.stackPositions.set(location, value);
|
|
502
|
+
}
|
|
503
|
+
tryAddStackPosition(location, value) {
|
|
504
|
+
if (this.stackPositions.has(location)) {
|
|
505
|
+
return false;
|
|
506
|
+
}
|
|
507
|
+
this.stackPositions.set(location, value);
|
|
508
|
+
return true;
|
|
509
|
+
}
|
|
510
|
+
};
|
|
511
|
+
|
|
512
|
+
// src/rom/extraction/reader.ts
|
|
513
|
+
var RomDataReader = class {
|
|
514
|
+
constructor(romData) {
|
|
515
|
+
if (!romData) {
|
|
516
|
+
throw new Error("romData cannot be null");
|
|
517
|
+
}
|
|
518
|
+
this.romData = romData;
|
|
519
|
+
this.position = 0;
|
|
520
|
+
}
|
|
521
|
+
readByte() {
|
|
522
|
+
return this.romData[this.position++];
|
|
523
|
+
}
|
|
524
|
+
readSByte() {
|
|
525
|
+
const value = this.readByte();
|
|
526
|
+
return value > 127 ? value - 256 : value;
|
|
527
|
+
}
|
|
528
|
+
readUShort() {
|
|
529
|
+
return this.readByte() | this.readByte() << 8;
|
|
530
|
+
}
|
|
531
|
+
readShort() {
|
|
532
|
+
const value = this.readUShort();
|
|
533
|
+
return value > 32767 ? value - 65536 : value;
|
|
534
|
+
}
|
|
535
|
+
readAddress() {
|
|
536
|
+
return this.readByte() | this.readByte() << 8 | this.readByte() << 16;
|
|
537
|
+
}
|
|
538
|
+
readInt() {
|
|
539
|
+
return this.readByte() | this.readByte() << 8 | this.readByte() << 16 | this.readByte() << 24;
|
|
540
|
+
}
|
|
541
|
+
peekByte() {
|
|
542
|
+
return this.romData[this.position];
|
|
543
|
+
}
|
|
544
|
+
peekShort() {
|
|
545
|
+
return this.romData[this.position] | this.romData[this.position + 1] << 8;
|
|
546
|
+
}
|
|
547
|
+
peekAddress() {
|
|
548
|
+
return this.romData[this.position] | this.romData[this.position + 1] << 8 | this.romData[this.position + 2] << 16;
|
|
549
|
+
}
|
|
550
|
+
};
|
|
551
|
+
var ReferenceManager = class {
|
|
552
|
+
constructor(root) {
|
|
553
|
+
this.structTable = /* @__PURE__ */ new Map();
|
|
554
|
+
this.markerTable = /* @__PURE__ */ new Map();
|
|
555
|
+
this.nameTable = /* @__PURE__ */ new Map();
|
|
556
|
+
if (!root) {
|
|
557
|
+
throw new Error("root cannot be null");
|
|
558
|
+
}
|
|
559
|
+
this.root = root;
|
|
560
|
+
}
|
|
561
|
+
// Struct management
|
|
562
|
+
tryGetStruct(location) {
|
|
563
|
+
const chunkType = this.structTable.get(location);
|
|
564
|
+
return { found: chunkType !== void 0, chunkType };
|
|
565
|
+
}
|
|
566
|
+
tryAddStruct(location, chunkType) {
|
|
567
|
+
if (this.structTable.has(location)) {
|
|
568
|
+
return false;
|
|
569
|
+
}
|
|
570
|
+
this.structTable.set(location, chunkType);
|
|
571
|
+
return true;
|
|
572
|
+
}
|
|
573
|
+
containsStruct(location) {
|
|
574
|
+
return this.structTable.has(location);
|
|
575
|
+
}
|
|
576
|
+
// Name management
|
|
577
|
+
tryGetName(location) {
|
|
578
|
+
const referenceName = this.nameTable.get(location);
|
|
579
|
+
return { found: referenceName !== void 0, referenceName };
|
|
580
|
+
}
|
|
581
|
+
tryAddName(location, referenceName) {
|
|
582
|
+
if (this.nameTable.has(location)) {
|
|
583
|
+
return false;
|
|
584
|
+
}
|
|
585
|
+
this.nameTable.set(location, referenceName);
|
|
586
|
+
return true;
|
|
587
|
+
}
|
|
588
|
+
// Marker management
|
|
589
|
+
tryGetMarker(location) {
|
|
590
|
+
const offset = this.markerTable.get(location);
|
|
591
|
+
return { found: offset !== void 0, offset };
|
|
592
|
+
}
|
|
593
|
+
setMarker(location, offset) {
|
|
594
|
+
this.markerTable.set(location, offset);
|
|
595
|
+
}
|
|
596
|
+
// Label creation
|
|
597
|
+
createBranchLabel(location) {
|
|
598
|
+
const name = `loc_${location.toString(16).toUpperCase().padStart(6, "0")}`;
|
|
599
|
+
this.nameTable.set(location, name);
|
|
600
|
+
return name;
|
|
601
|
+
}
|
|
602
|
+
createTypeName(type, location) {
|
|
603
|
+
let name = type.toLowerCase();
|
|
604
|
+
while (name.length > 0 && BlockReaderConstants.POINTER_CHARACTERS.includes(name[0])) {
|
|
605
|
+
name = name.substring(1) + "_list";
|
|
606
|
+
}
|
|
607
|
+
return `${name}_${location.toString(16).toUpperCase().padStart(6, "0")}`;
|
|
608
|
+
}
|
|
609
|
+
createFallbackName(location) {
|
|
610
|
+
const fileMatch = this.root.files.find(
|
|
611
|
+
(x) => x.start <= location && x.end > location
|
|
612
|
+
);
|
|
613
|
+
if (fileMatch) {
|
|
614
|
+
const offset = location - fileMatch.start;
|
|
615
|
+
return fileMatch.name + (offset !== 0 ? `+${offset.toString(16).toUpperCase()}` : "");
|
|
616
|
+
}
|
|
617
|
+
return location.toString(16).toUpperCase().padStart(6, "0");
|
|
618
|
+
}
|
|
619
|
+
/**
|
|
620
|
+
* Finds a reference location by its assigned name.
|
|
621
|
+
*/
|
|
622
|
+
findLocationByName(name) {
|
|
623
|
+
for (const [loc, refName] of this.nameTable) {
|
|
624
|
+
if (refName === name) {
|
|
625
|
+
return loc;
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
return void 0;
|
|
629
|
+
}
|
|
630
|
+
resolveName(location, type, isBranch) {
|
|
631
|
+
const prefix = Address.codeFromType(type);
|
|
632
|
+
let name;
|
|
633
|
+
let label = null;
|
|
634
|
+
let resolvedLocation = location;
|
|
635
|
+
const rewrite = this.root.rewrites[location];
|
|
636
|
+
if (rewrite !== void 0) {
|
|
637
|
+
const result = this.processRewrite(location, rewrite);
|
|
638
|
+
resolvedLocation = result.location;
|
|
639
|
+
label = result.label;
|
|
640
|
+
}
|
|
641
|
+
const existingName = this.nameTable.get(resolvedLocation);
|
|
642
|
+
if (existingName) {
|
|
643
|
+
name = existingName;
|
|
644
|
+
} else {
|
|
645
|
+
name = isBranch ? this.createBranchLabel(resolvedLocation) : this.findClosestReference(resolvedLocation) || this.createFallbackName(resolvedLocation);
|
|
646
|
+
}
|
|
647
|
+
return `${prefix || ""}${name}${label || ""}`;
|
|
648
|
+
}
|
|
649
|
+
findClosestReference(location) {
|
|
650
|
+
let closestDistance = BlockReaderConstants.REF_SEARCH_MAX_RANGE;
|
|
651
|
+
let closestName = null;
|
|
652
|
+
let closestLocation = null;
|
|
653
|
+
for (const [entryKey, entryValue] of this.nameTable) {
|
|
654
|
+
if (entryKey > location) {
|
|
655
|
+
continue;
|
|
656
|
+
}
|
|
657
|
+
const distance = location - entryKey;
|
|
658
|
+
if (distance >= closestDistance) {
|
|
659
|
+
continue;
|
|
660
|
+
}
|
|
661
|
+
closestDistance = distance;
|
|
662
|
+
closestName = entryValue;
|
|
663
|
+
closestLocation = entryKey;
|
|
664
|
+
if (closestDistance === 1) {
|
|
665
|
+
break;
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
return this.processClosestMatch(location, closestName, closestLocation, closestDistance);
|
|
669
|
+
}
|
|
670
|
+
processRewrite(location, rewrite) {
|
|
671
|
+
const offset = location - rewrite;
|
|
672
|
+
const cmd = offset < 0 ? "-" : "+";
|
|
673
|
+
const absOffset = Math.abs(offset);
|
|
674
|
+
let label = null;
|
|
675
|
+
const structType = this.structTable.get(rewrite);
|
|
676
|
+
if (structType === BlockReaderConstants.WIDE_STRING_TYPE) {
|
|
677
|
+
this.markerTable.set(rewrite, absOffset);
|
|
678
|
+
this.markerTable.set(location, absOffset);
|
|
679
|
+
label = cmd === "-" ? BlockReaderConstants.NEGATIVE_MARKER_FORMAT : BlockReaderConstants.MARKER_FORMAT;
|
|
680
|
+
} else {
|
|
681
|
+
const formatString = cmd === "-" ? BlockReaderConstants.NEGATIVE_OFFSET_FORMAT : BlockReaderConstants.OFFSET_FORMAT;
|
|
682
|
+
label = formatString.replace("{0:X}", absOffset.toString(16).toUpperCase());
|
|
683
|
+
}
|
|
684
|
+
return { location: rewrite, label };
|
|
685
|
+
}
|
|
686
|
+
processClosestMatch(location, closestName, closestLocation, closestDistance) {
|
|
687
|
+
if (!closestName || closestLocation === null) {
|
|
688
|
+
return null;
|
|
689
|
+
}
|
|
690
|
+
let result = closestName;
|
|
691
|
+
const structType = this.structTable.get(closestLocation);
|
|
692
|
+
if (structType === BlockReaderConstants.WIDE_STRING_TYPE) {
|
|
693
|
+
this.markerTable.set(closestLocation, closestDistance);
|
|
694
|
+
this.markerTable.set(location, closestDistance);
|
|
695
|
+
result += BlockReaderConstants.MARKER_FORMAT;
|
|
696
|
+
} else {
|
|
697
|
+
result += `+${closestDistance.toString(16).toUpperCase()}`;
|
|
698
|
+
}
|
|
699
|
+
return result;
|
|
700
|
+
}
|
|
701
|
+
};
|
|
702
|
+
|
|
703
|
+
// src/rom/extraction/stack.ts
|
|
704
|
+
var StackOperations = class {
|
|
705
|
+
constructor(registers, blockReader) {
|
|
706
|
+
this._registers = registers;
|
|
707
|
+
this._blockReader = blockReader;
|
|
708
|
+
}
|
|
709
|
+
handleStackOperation(mnemonic) {
|
|
710
|
+
switch (mnemonic) {
|
|
711
|
+
case "PHD":
|
|
712
|
+
this._registers.stack.push(this._registers.direct ?? 0);
|
|
713
|
+
break;
|
|
714
|
+
case "PLD":
|
|
715
|
+
this._registers.direct = this._registers.stack.popUInt16();
|
|
716
|
+
break;
|
|
717
|
+
case "PHK":
|
|
718
|
+
this._registers.stack.push(this._blockReader._romDataReader.position >> 16 | 128);
|
|
719
|
+
break;
|
|
720
|
+
case "PHB":
|
|
721
|
+
this._registers.stack.push(this._registers.dataBank ?? 129);
|
|
722
|
+
break;
|
|
723
|
+
case "PLB":
|
|
724
|
+
this._registers.dataBank = this._registers.stack.popByte();
|
|
725
|
+
break;
|
|
726
|
+
case "PHP":
|
|
727
|
+
this._registers.stack.push(this._registers.statusFlags);
|
|
728
|
+
break;
|
|
729
|
+
case "PLP":
|
|
730
|
+
this._registers.statusFlags = this._registers.stack.popByte();
|
|
731
|
+
break;
|
|
732
|
+
case "PHA":
|
|
733
|
+
this.handleAccumulatorPush();
|
|
734
|
+
break;
|
|
735
|
+
case "PLA":
|
|
736
|
+
this.handleAccumulatorPull();
|
|
737
|
+
break;
|
|
738
|
+
case "PHX":
|
|
739
|
+
this.handleXIndexPush();
|
|
740
|
+
break;
|
|
741
|
+
case "PLX":
|
|
742
|
+
this.handleXIndexPull();
|
|
743
|
+
break;
|
|
744
|
+
case "PHY":
|
|
745
|
+
this.handleYIndexPush();
|
|
746
|
+
break;
|
|
747
|
+
case "PLY":
|
|
748
|
+
this.handleYIndexPull();
|
|
749
|
+
break;
|
|
750
|
+
case "XBA":
|
|
751
|
+
this.handleExchangeBytes();
|
|
752
|
+
break;
|
|
753
|
+
}
|
|
754
|
+
}
|
|
755
|
+
handleAccumulatorPush() {
|
|
756
|
+
if (this._registers.accumulatorFlag === true) {
|
|
757
|
+
this._registers.stack.push((this._registers.accumulator ?? 0) & 255);
|
|
758
|
+
} else {
|
|
759
|
+
this._registers.stack.pushUInt16(this._registers.accumulator ?? 0);
|
|
760
|
+
}
|
|
761
|
+
}
|
|
762
|
+
handleAccumulatorPull() {
|
|
763
|
+
if (this._registers.accumulatorFlag === true) {
|
|
764
|
+
this._registers.accumulator = (this._registers.accumulator ?? 0) & 65280 | this._registers.stack.popByte();
|
|
765
|
+
} else {
|
|
766
|
+
this._registers.accumulator = this._registers.stack.popUInt16();
|
|
767
|
+
}
|
|
768
|
+
}
|
|
769
|
+
handleXIndexPush() {
|
|
770
|
+
if (this._registers.indexFlag === true) {
|
|
771
|
+
this._registers.stack.push((this._registers.xIndex ?? 0) & 255);
|
|
772
|
+
} else {
|
|
773
|
+
this._registers.stack.pushUInt16(this._registers.xIndex ?? 0);
|
|
774
|
+
}
|
|
775
|
+
}
|
|
776
|
+
handleXIndexPull() {
|
|
777
|
+
if (this._registers.indexFlag === true) {
|
|
778
|
+
this._registers.xIndex = (this._registers.xIndex ?? 0) & 65280 | this._registers.stack.popByte();
|
|
779
|
+
} else {
|
|
780
|
+
this._registers.xIndex = this._registers.stack.popUInt16();
|
|
781
|
+
}
|
|
782
|
+
}
|
|
783
|
+
handleYIndexPush() {
|
|
784
|
+
if (this._registers.indexFlag === true) {
|
|
785
|
+
this._registers.stack.push((this._registers.yIndex ?? 0) & 255);
|
|
786
|
+
} else {
|
|
787
|
+
this._registers.stack.pushUInt16(this._registers.yIndex ?? 0);
|
|
788
|
+
}
|
|
789
|
+
}
|
|
790
|
+
handleYIndexPull() {
|
|
791
|
+
if (this._registers.indexFlag === true) {
|
|
792
|
+
this._registers.yIndex = (this._registers.yIndex ?? 0) & 65280 | this._registers.stack.popByte();
|
|
793
|
+
} else {
|
|
794
|
+
this._registers.yIndex = this._registers.stack.popUInt16();
|
|
795
|
+
}
|
|
796
|
+
}
|
|
797
|
+
handleExchangeBytes() {
|
|
798
|
+
const acc = this._registers.accumulator ?? 0;
|
|
799
|
+
this._registers.accumulator = (acc >> 8 | acc << 8) & 65535;
|
|
800
|
+
}
|
|
801
|
+
};
|
|
802
|
+
var CopCommandProcessor = class {
|
|
803
|
+
constructor(blockReader) {
|
|
804
|
+
this._blockReader = blockReader;
|
|
805
|
+
this._romDataReader = blockReader._romDataReader;
|
|
806
|
+
}
|
|
807
|
+
/**
|
|
808
|
+
* Parses a COP command based on its definition
|
|
809
|
+
*/
|
|
810
|
+
parseCopCommand(copDef, operands) {
|
|
811
|
+
for (const partStr of copDef.parts) {
|
|
812
|
+
const addrType = Address.typeFromCode(partStr[0]);
|
|
813
|
+
const isPtr = addrType !== AddressType.Unknown;
|
|
814
|
+
const referenceType = isPtr ? partStr.substring(1) : this._blockReader._currentPart?.struct ?? "Binary";
|
|
815
|
+
const memberTypeName = isPtr ? addrType.toString() : partStr;
|
|
816
|
+
const memberType = this.tryParseMemberType(memberTypeName);
|
|
817
|
+
if (memberType === null) {
|
|
818
|
+
throw new Error("Cannot use structs in cop def");
|
|
819
|
+
}
|
|
820
|
+
const label = this._blockReader._root.labels[this._romDataReader.position];
|
|
821
|
+
if (label) {
|
|
822
|
+
this._romDataReader.position += this.getMemberTypeSize(memberType);
|
|
823
|
+
operands.push(label);
|
|
824
|
+
} else {
|
|
825
|
+
operands.push(this.readMemberTypeValue(memberType, partStr, isPtr, referenceType, addrType));
|
|
826
|
+
}
|
|
827
|
+
}
|
|
828
|
+
}
|
|
829
|
+
tryParseMemberType(memberTypeName) {
|
|
830
|
+
const upperName = memberTypeName.toUpperCase();
|
|
831
|
+
for (const [key, value] of Object.entries(MemberType)) {
|
|
832
|
+
if (key.toUpperCase() === upperName) {
|
|
833
|
+
return value;
|
|
834
|
+
}
|
|
835
|
+
}
|
|
836
|
+
return null;
|
|
837
|
+
}
|
|
838
|
+
getMemberTypeSize(memberType) {
|
|
839
|
+
switch (memberType) {
|
|
840
|
+
case MemberType.Byte:
|
|
841
|
+
return 1;
|
|
842
|
+
case MemberType.Word:
|
|
843
|
+
case MemberType.Offset:
|
|
844
|
+
return 2;
|
|
845
|
+
case MemberType.Address:
|
|
846
|
+
return 3;
|
|
847
|
+
default:
|
|
848
|
+
throw new Error("Unsupported COP member type");
|
|
849
|
+
}
|
|
850
|
+
}
|
|
851
|
+
readMemberTypeValue(memberType, partStr, isPtr, referenceType, addrType) {
|
|
852
|
+
switch (memberType) {
|
|
853
|
+
case MemberType.Byte:
|
|
854
|
+
return createByte(this._romDataReader.readByte());
|
|
855
|
+
case MemberType.Word:
|
|
856
|
+
return createWord(this._romDataReader.readUShort());
|
|
857
|
+
case MemberType.Offset:
|
|
858
|
+
return this.createCopLocation(this._romDataReader.readUShort(), null, partStr, isPtr, referenceType, addrType);
|
|
859
|
+
case MemberType.Address:
|
|
860
|
+
return this.createCopLocation(this._romDataReader.readUShort(), this._romDataReader.readByte(), partStr, isPtr, referenceType, addrType);
|
|
861
|
+
default:
|
|
862
|
+
throw new Error("Unsupported COP member type");
|
|
863
|
+
}
|
|
864
|
+
}
|
|
865
|
+
createCopLocation(offset, bank, partStr, isPtr, otherStr, type) {
|
|
866
|
+
if (bank === null && offset === 0) {
|
|
867
|
+
return createWord(offset);
|
|
868
|
+
}
|
|
869
|
+
const addr = new Address(bank ?? this._romDataReader.position >> 16, offset);
|
|
870
|
+
if (addr.space === AddressSpace.ROM) {
|
|
871
|
+
const location = addr.toInt();
|
|
872
|
+
if (partStr !== "Address" && isPtr && !this._blockReader._root.rewrites[location]) {
|
|
873
|
+
this._blockReader.noteType(location, otherStr, true);
|
|
874
|
+
}
|
|
875
|
+
if (type === AddressType.Unknown) {
|
|
876
|
+
type = this.tryParseAddressType(partStr) ?? AddressType.Unknown;
|
|
877
|
+
}
|
|
878
|
+
return new LocationWrapper(location, type);
|
|
879
|
+
}
|
|
880
|
+
return addr;
|
|
881
|
+
}
|
|
882
|
+
tryParseAddressType(addressTypeName) {
|
|
883
|
+
const upperName = addressTypeName.toUpperCase();
|
|
884
|
+
for (const [key, value] of Object.entries(AddressType)) {
|
|
885
|
+
if (key.toUpperCase() === upperName) {
|
|
886
|
+
return value;
|
|
887
|
+
}
|
|
888
|
+
}
|
|
889
|
+
return null;
|
|
890
|
+
}
|
|
891
|
+
};
|
|
892
|
+
|
|
893
|
+
// src/rom/extraction/addressing.ts
|
|
894
|
+
var OperationContext = class {
|
|
895
|
+
constructor() {
|
|
896
|
+
this.size = 0;
|
|
897
|
+
this.nextAddress = 0;
|
|
898
|
+
this.xForm1 = null;
|
|
899
|
+
this.xForm2 = null;
|
|
900
|
+
this.copDef = null;
|
|
901
|
+
}
|
|
902
|
+
};
|
|
903
|
+
var AddressingModeHandler = class {
|
|
904
|
+
constructor(blockReader, transformProcessor) {
|
|
905
|
+
this._blockReader = blockReader;
|
|
906
|
+
this._transformProcessor = transformProcessor;
|
|
907
|
+
this._copProcessor = new CopCommandProcessor(blockReader);
|
|
908
|
+
this._dataReader = blockReader._romDataReader;
|
|
909
|
+
}
|
|
910
|
+
processAddressingMode(code, context, reg) {
|
|
911
|
+
const operands = [];
|
|
912
|
+
switch (code.mode) {
|
|
913
|
+
case AddressingMode.Stack:
|
|
914
|
+
case AddressingMode.Implied:
|
|
915
|
+
this.handleStackOrImpliedMode(code.mnem, reg);
|
|
916
|
+
break;
|
|
917
|
+
case AddressingMode.Immediate:
|
|
918
|
+
this.handleImmediateMode(code.mnem, context.size, operands, reg);
|
|
919
|
+
break;
|
|
920
|
+
case AddressingMode.AbsoluteIndirect:
|
|
921
|
+
case AddressingMode.AbsoluteIndirectLong:
|
|
922
|
+
case AddressingMode.AbsoluteIndexedIndirect:
|
|
923
|
+
case AddressingMode.Absolute:
|
|
924
|
+
case AddressingMode.AbsoluteIndexedX:
|
|
925
|
+
case AddressingMode.AbsoluteIndexedY:
|
|
926
|
+
this.handleAbsoluteMode(code.mnem, void 0, context.nextAddress, reg.dataBank, operands, reg);
|
|
927
|
+
break;
|
|
928
|
+
case AddressingMode.AbsoluteLong:
|
|
929
|
+
case AddressingMode.AbsoluteLongIndexedX:
|
|
930
|
+
this.handleAbsoluteLongMode(code.mnem, operands, reg);
|
|
931
|
+
break;
|
|
932
|
+
case AddressingMode.BlockMove:
|
|
933
|
+
this.handleBlockMoveMode(operands, context);
|
|
934
|
+
break;
|
|
935
|
+
case AddressingMode.DirectPage:
|
|
936
|
+
case AddressingMode.DirectPageIndexedIndirectX:
|
|
937
|
+
case AddressingMode.DirectPageIndexedX:
|
|
938
|
+
case AddressingMode.DirectPageIndexedY:
|
|
939
|
+
case AddressingMode.DirectPageIndirect:
|
|
940
|
+
case AddressingMode.DirectPageIndirectIndexedY:
|
|
941
|
+
case AddressingMode.DirectPageIndirectLong:
|
|
942
|
+
case AddressingMode.DirectPageIndirectLongIndexedY:
|
|
943
|
+
this.handleDirectPageMode(operands);
|
|
944
|
+
break;
|
|
945
|
+
case AddressingMode.PCRelative:
|
|
946
|
+
this.handlePCRelativeMode(operands, context.nextAddress, reg, false);
|
|
947
|
+
break;
|
|
948
|
+
case AddressingMode.PCRelativeLong:
|
|
949
|
+
this.handlePCRelativeMode(operands, context.nextAddress, reg, true);
|
|
950
|
+
break;
|
|
951
|
+
case AddressingMode.StackRelative:
|
|
952
|
+
case AddressingMode.StackRelativeIndirectIndexedY:
|
|
953
|
+
this.handleStackRelativeMode(operands);
|
|
954
|
+
break;
|
|
955
|
+
case AddressingMode.StackInterrupt:
|
|
956
|
+
this.handleStackInterruptMode(code.mnem, operands, context);
|
|
957
|
+
break;
|
|
958
|
+
}
|
|
959
|
+
return operands;
|
|
960
|
+
}
|
|
961
|
+
handleImmediateMode(mnemonic, size, operands, reg) {
|
|
962
|
+
const operand = this.readImmediateOperand(size);
|
|
963
|
+
operands.push(operand);
|
|
964
|
+
this.updateRegisterForImmediateInstruction(mnemonic, operand, reg);
|
|
965
|
+
}
|
|
966
|
+
readImmediateOperand(size) {
|
|
967
|
+
return size === 3 ? createWord(this._dataReader.readUShort()) : createByte(this._dataReader.readByte());
|
|
968
|
+
}
|
|
969
|
+
updateRegisterForImmediateInstruction(mnemonic, operand, reg) {
|
|
970
|
+
const value = typeof operand === "number" ? operand : operand.value;
|
|
971
|
+
switch (mnemonic) {
|
|
972
|
+
case "LDA":
|
|
973
|
+
reg.accumulator = this.calculateRegisterValue(reg.accumulator, value);
|
|
974
|
+
break;
|
|
975
|
+
case "LDX":
|
|
976
|
+
reg.xIndex = this.calculateRegisterValue(reg.xIndex, value);
|
|
977
|
+
break;
|
|
978
|
+
case "LDY":
|
|
979
|
+
reg.yIndex = this.calculateRegisterValue(reg.yIndex, value);
|
|
980
|
+
break;
|
|
981
|
+
case "SEP":
|
|
982
|
+
case "REP":
|
|
983
|
+
this.updateStatusFlags(mnemonic, value);
|
|
984
|
+
break;
|
|
985
|
+
}
|
|
986
|
+
}
|
|
987
|
+
calculateRegisterValue(currentValue, operand) {
|
|
988
|
+
if (operand > 255) {
|
|
989
|
+
return operand;
|
|
990
|
+
} else {
|
|
991
|
+
return (currentValue ?? 0) & 65280 | operand;
|
|
992
|
+
}
|
|
993
|
+
}
|
|
994
|
+
updateStatusFlags(mnemonic, flagValue) {
|
|
995
|
+
const flag = flagValue;
|
|
996
|
+
const isSep = mnemonic === "SEP";
|
|
997
|
+
if (flag & StatusFlags.AccumulatorMode) {
|
|
998
|
+
this._blockReader.AccumulatorFlags.set(this._dataReader.position, isSep);
|
|
999
|
+
}
|
|
1000
|
+
if (flag & StatusFlags.IndexMode) {
|
|
1001
|
+
this._blockReader.IndexFlags.set(this._dataReader.position, isSep);
|
|
1002
|
+
}
|
|
1003
|
+
}
|
|
1004
|
+
handleAbsoluteLongMode(mnemonic, operands, reg) {
|
|
1005
|
+
const refLoc = this._dataReader.readUShort();
|
|
1006
|
+
const bank = this._dataReader.readByte();
|
|
1007
|
+
const address = new Address(bank, refLoc);
|
|
1008
|
+
if (address.space === AddressSpace.ROM) {
|
|
1009
|
+
const wrapper = new LocationWrapper(address.toInt(), AddressType.Address);
|
|
1010
|
+
if (this.isJumpInstruction(mnemonic)) {
|
|
1011
|
+
this._blockReader.noteType(wrapper.location, "Code", false, reg);
|
|
1012
|
+
}
|
|
1013
|
+
operands.push(wrapper);
|
|
1014
|
+
} else {
|
|
1015
|
+
operands.push(address);
|
|
1016
|
+
}
|
|
1017
|
+
}
|
|
1018
|
+
handleBlockMoveMode(operands, context) {
|
|
1019
|
+
operands.push(createByte(this._dataReader.readByte()));
|
|
1020
|
+
context.xForm2 = this._transformProcessor.getTransform();
|
|
1021
|
+
operands.push(createByte(this._dataReader.readByte()));
|
|
1022
|
+
}
|
|
1023
|
+
handleDirectPageMode(operands) {
|
|
1024
|
+
operands.push(createByte(this._dataReader.readByte()));
|
|
1025
|
+
}
|
|
1026
|
+
handlePCRelativeMode(operands, nextAddress, reg, isLong) {
|
|
1027
|
+
const relative = isLong ? nextAddress + this._dataReader.readShort() : nextAddress + this._dataReader.readSByte();
|
|
1028
|
+
this._blockReader.noteType(relative, "Code", void 0, reg);
|
|
1029
|
+
operands.push(new LocationWrapper(relative, AddressType.Relative));
|
|
1030
|
+
}
|
|
1031
|
+
handleStackRelativeMode(operands) {
|
|
1032
|
+
operands.push(createByte(this._dataReader.readByte()));
|
|
1033
|
+
}
|
|
1034
|
+
handleStackInterruptMode(mnemonic, operands, context) {
|
|
1035
|
+
const cmd = this._dataReader.readByte();
|
|
1036
|
+
operands.push(cmd);
|
|
1037
|
+
if (mnemonic === "COP") {
|
|
1038
|
+
const copDef = this._blockReader._root.copDef[cmd];
|
|
1039
|
+
if (!copDef) {
|
|
1040
|
+
throw new Error("Unknown COP command");
|
|
1041
|
+
}
|
|
1042
|
+
context.copDef = copDef;
|
|
1043
|
+
this._copProcessor.parseCopCommand(copDef, operands);
|
|
1044
|
+
}
|
|
1045
|
+
}
|
|
1046
|
+
handleStackOrImpliedMode(mnemonic, reg) {
|
|
1047
|
+
const stackOperations = new StackOperations(reg, this._blockReader);
|
|
1048
|
+
stackOperations.handleStackOperation(mnemonic);
|
|
1049
|
+
}
|
|
1050
|
+
handleAbsoluteMode(mnemonic, xBank1, next, dataBank, operands, registers) {
|
|
1051
|
+
let refLoc = this._dataReader.readUShort();
|
|
1052
|
+
const isPush = this.isPushInstruction(mnemonic);
|
|
1053
|
+
if (isPush) {
|
|
1054
|
+
refLoc++;
|
|
1055
|
+
}
|
|
1056
|
+
const isJump = isPush || this.isJumpInstruction(mnemonic);
|
|
1057
|
+
const bank = xBank1 ?? (isJump ? this._dataReader.position >> 16 : dataBank ?? 129);
|
|
1058
|
+
const addr = new Address(bank, refLoc);
|
|
1059
|
+
if (addr.isROM) {
|
|
1060
|
+
const wrapper = new LocationWrapper(addr.toInt(), AddressType.Offset);
|
|
1061
|
+
if (isJump) {
|
|
1062
|
+
const name = this._blockReader.noteType(wrapper.location, "Code", isPush, registers);
|
|
1063
|
+
if (isPush) {
|
|
1064
|
+
operands.push(`&${name}-1`);
|
|
1065
|
+
return;
|
|
1066
|
+
}
|
|
1067
|
+
}
|
|
1068
|
+
operands.push(wrapper);
|
|
1069
|
+
} else {
|
|
1070
|
+
operands.push(addr);
|
|
1071
|
+
}
|
|
1072
|
+
}
|
|
1073
|
+
isJumpInstruction(mnemonic) {
|
|
1074
|
+
return mnemonic[0] === "J";
|
|
1075
|
+
}
|
|
1076
|
+
isPushInstruction(mnemonic) {
|
|
1077
|
+
return mnemonic[0] === "P";
|
|
1078
|
+
}
|
|
1079
|
+
};
|
|
1080
|
+
var TransformProcessor = class _TransformProcessor {
|
|
1081
|
+
static {
|
|
1082
|
+
// Location regex pattern: _([A-Fa-f0-9]{6})
|
|
1083
|
+
this.LOCATION_REGEX = /_([A-Fa-f0-9]{6})/;
|
|
1084
|
+
}
|
|
1085
|
+
constructor(romReader) {
|
|
1086
|
+
this._blockReader = romReader;
|
|
1087
|
+
this._romDataReader = romReader._romDataReader;
|
|
1088
|
+
this._referenceManager = romReader._referenceManager;
|
|
1089
|
+
this._labelLookup = romReader._root.labels;
|
|
1090
|
+
}
|
|
1091
|
+
/**
|
|
1092
|
+
* Retrieves transform information for the current ROM position
|
|
1093
|
+
*/
|
|
1094
|
+
getTransform() {
|
|
1095
|
+
const transform = this._labelLookup[this._romDataReader.position];
|
|
1096
|
+
if (transform === "") {
|
|
1097
|
+
return transform;
|
|
1098
|
+
} else if (!transform) {
|
|
1099
|
+
return null;
|
|
1100
|
+
}
|
|
1101
|
+
const transformName = this.cleanTransformName(transform);
|
|
1102
|
+
const referenceLocation = this.resolveTransformReference(transformName);
|
|
1103
|
+
if (referenceLocation !== null) {
|
|
1104
|
+
this._blockReader.resolveInclude(referenceLocation, false);
|
|
1105
|
+
}
|
|
1106
|
+
return transform;
|
|
1107
|
+
}
|
|
1108
|
+
/**
|
|
1109
|
+
* Applies transforms to operands
|
|
1110
|
+
*/
|
|
1111
|
+
applyTransforms(op1Label, op2Label, operands) {
|
|
1112
|
+
this.applyTransform(op1Label, 0, operands);
|
|
1113
|
+
this.applyTransform(op2Label, 1, operands);
|
|
1114
|
+
}
|
|
1115
|
+
applyTransform(transform, operandIndex, operands) {
|
|
1116
|
+
if (transform === null || transform === void 0 || operandIndex >= operands.length) {
|
|
1117
|
+
return;
|
|
1118
|
+
}
|
|
1119
|
+
if (transform === "") {
|
|
1120
|
+
this.applyDefaultTransform(operandIndex, operands);
|
|
1121
|
+
} else {
|
|
1122
|
+
operands[operandIndex] = transform;
|
|
1123
|
+
}
|
|
1124
|
+
}
|
|
1125
|
+
applyDefaultTransform(operandIndex, operands) {
|
|
1126
|
+
let value = this._romDataReader.position & 16711680;
|
|
1127
|
+
const opnd = operands[operandIndex];
|
|
1128
|
+
if (opnd && "value" in opnd) {
|
|
1129
|
+
value |= opnd["value"];
|
|
1130
|
+
} else {
|
|
1131
|
+
value |= opnd;
|
|
1132
|
+
}
|
|
1133
|
+
const nameResult = this._referenceManager.tryGetName(value);
|
|
1134
|
+
let referenceName;
|
|
1135
|
+
if (!nameResult.found) {
|
|
1136
|
+
referenceName = `loc_${value.toString(16).toUpperCase().padStart(6, "0")}`;
|
|
1137
|
+
this._referenceManager.tryAddName(value, referenceName);
|
|
1138
|
+
} else {
|
|
1139
|
+
referenceName = nameResult.referenceName;
|
|
1140
|
+
}
|
|
1141
|
+
this._blockReader.resolveInclude(value, false);
|
|
1142
|
+
operands[operandIndex] = `&${referenceName}`;
|
|
1143
|
+
}
|
|
1144
|
+
cleanTransformName(transform) {
|
|
1145
|
+
let name = transform;
|
|
1146
|
+
while (name.length > 0 && RomProcessingConstants.ADDRESS_SPACE.includes(name[0])) {
|
|
1147
|
+
name = name.substring(1);
|
|
1148
|
+
}
|
|
1149
|
+
let mathIndex = -1;
|
|
1150
|
+
for (let i = 0; i < name.length; i++) {
|
|
1151
|
+
if (RomProcessingConstants.OPERATORS.includes(name[i])) {
|
|
1152
|
+
mathIndex = i;
|
|
1153
|
+
break;
|
|
1154
|
+
}
|
|
1155
|
+
}
|
|
1156
|
+
if (mathIndex > 0) {
|
|
1157
|
+
name = name.substring(0, mathIndex);
|
|
1158
|
+
}
|
|
1159
|
+
return name;
|
|
1160
|
+
}
|
|
1161
|
+
resolveTransformReference(transformName) {
|
|
1162
|
+
const location = this._referenceManager.findLocationByName(transformName);
|
|
1163
|
+
if (location !== void 0) {
|
|
1164
|
+
return location;
|
|
1165
|
+
}
|
|
1166
|
+
const match = _TransformProcessor.LOCATION_REGEX.exec(transformName);
|
|
1167
|
+
return match ? parseInt(match[1], 16) : null;
|
|
1168
|
+
}
|
|
1169
|
+
};
|
|
1170
|
+
var SfxReader = class {
|
|
1171
|
+
constructor(romData, dbRoot) {
|
|
1172
|
+
this._romData = romData;
|
|
1173
|
+
this._dbRoot = dbRoot;
|
|
1174
|
+
this._location = dbRoot.config.sfxLocation;
|
|
1175
|
+
this._count = dbRoot.config.sfxCount;
|
|
1176
|
+
}
|
|
1177
|
+
/**
|
|
1178
|
+
* Reads a byte from the rom data
|
|
1179
|
+
*/
|
|
1180
|
+
readByte() {
|
|
1181
|
+
if ((this._location & Address.UPPER_BANK) !== 0) {
|
|
1182
|
+
this._location += Address.UPPER_BANK;
|
|
1183
|
+
}
|
|
1184
|
+
return this._romData[this._location++];
|
|
1185
|
+
}
|
|
1186
|
+
/**
|
|
1187
|
+
* Reads a short from the rom data
|
|
1188
|
+
*/
|
|
1189
|
+
readShort() {
|
|
1190
|
+
return this.readByte() | this.readByte() << 8;
|
|
1191
|
+
}
|
|
1192
|
+
/**
|
|
1193
|
+
* Extracts the sfx from the rom data to the given output path
|
|
1194
|
+
*/
|
|
1195
|
+
async extract(outPath) {
|
|
1196
|
+
const res = this.getPath(BinType.Sound);
|
|
1197
|
+
if (res?.folder) {
|
|
1198
|
+
outPath = `${outPath}/${res.folder}`;
|
|
1199
|
+
}
|
|
1200
|
+
const isNode = typeof window === "undefined";
|
|
1201
|
+
const fs2 = isNode ? await import('fs') : null;
|
|
1202
|
+
if (isNode) {
|
|
1203
|
+
fs2.mkdirSync(outPath, { recursive: true });
|
|
1204
|
+
}
|
|
1205
|
+
for (let i = 0; i < this._count; i++) {
|
|
1206
|
+
const size = this.readShort();
|
|
1207
|
+
const filePath = `${outPath}/sfx${i.toString(16).padStart(2, "0").toUpperCase()}.${res?.extension || "bin"}`;
|
|
1208
|
+
if (isNode) {
|
|
1209
|
+
if (fs2.existsSync(filePath)) {
|
|
1210
|
+
this._location += size;
|
|
1211
|
+
} else {
|
|
1212
|
+
const sfxData = new Uint8Array(size);
|
|
1213
|
+
for (let x = 0; x < size; x++) {
|
|
1214
|
+
sfxData[x] = this.readByte();
|
|
1215
|
+
}
|
|
1216
|
+
fs2.writeFileSync(filePath, sfxData);
|
|
1217
|
+
}
|
|
1218
|
+
} else {
|
|
1219
|
+
this._location += size;
|
|
1220
|
+
}
|
|
1221
|
+
}
|
|
1222
|
+
}
|
|
1223
|
+
/**
|
|
1224
|
+
* Gets the path information for a binary type
|
|
1225
|
+
* TODO: This should be implemented in DbRoot
|
|
1226
|
+
*/
|
|
1227
|
+
getPath(binType) {
|
|
1228
|
+
switch (binType) {
|
|
1229
|
+
case BinType.Sound:
|
|
1230
|
+
return { folder: "sound", extension: "sfc" };
|
|
1231
|
+
default:
|
|
1232
|
+
return null;
|
|
1233
|
+
}
|
|
1234
|
+
}
|
|
1235
|
+
};
|
|
1236
|
+
var FileReader = class _FileReader {
|
|
1237
|
+
static {
|
|
1238
|
+
this.PALETTE_MIN_SIZE = 512;
|
|
1239
|
+
}
|
|
1240
|
+
constructor(romData, dbRoot, provider) {
|
|
1241
|
+
this._romData = romData;
|
|
1242
|
+
this._dbRoot = dbRoot;
|
|
1243
|
+
this._compression = provider;
|
|
1244
|
+
}
|
|
1245
|
+
/**
|
|
1246
|
+
* Extracts all files from the ROM to the given output path
|
|
1247
|
+
*/
|
|
1248
|
+
async extract(outPath) {
|
|
1249
|
+
const isNode = typeof window === "undefined";
|
|
1250
|
+
const fs2 = isNode ? await import('fs') : null;
|
|
1251
|
+
for (const file of this._dbRoot.files) {
|
|
1252
|
+
let start = file.start;
|
|
1253
|
+
const res = this.getPath(file.type);
|
|
1254
|
+
let filePath = outPath;
|
|
1255
|
+
if (res.folder) {
|
|
1256
|
+
filePath = `${outPath}/${res.folder}`;
|
|
1257
|
+
if (isNode) {
|
|
1258
|
+
fs2.mkdirSync(filePath, { recursive: true });
|
|
1259
|
+
}
|
|
1260
|
+
}
|
|
1261
|
+
filePath = `${filePath}/${file.name}.${res.extension}`;
|
|
1262
|
+
if (isNode && fs2.existsSync(filePath)) {
|
|
1263
|
+
continue;
|
|
1264
|
+
}
|
|
1265
|
+
let header = null;
|
|
1266
|
+
if (file.type === BinType.Tilemap) {
|
|
1267
|
+
header = new Uint8Array([this._romData[start++], this._romData[start++]]);
|
|
1268
|
+
} else if (file.type === BinType.Meta17) {
|
|
1269
|
+
header = new Uint8Array([
|
|
1270
|
+
this._romData[start++],
|
|
1271
|
+
this._romData[start++],
|
|
1272
|
+
this._romData[start++],
|
|
1273
|
+
this._romData[start++]
|
|
1274
|
+
]);
|
|
1275
|
+
}
|
|
1276
|
+
let length = file.end - start;
|
|
1277
|
+
let fileData;
|
|
1278
|
+
if (file.compressed === true) {
|
|
1279
|
+
const data = this._compression.expand(this._romData, start, length);
|
|
1280
|
+
fileData = this.processFileData(data, 0, data.length, header, file.type);
|
|
1281
|
+
} else {
|
|
1282
|
+
if (file.compressed !== null) {
|
|
1283
|
+
start += 2;
|
|
1284
|
+
length -= 2;
|
|
1285
|
+
}
|
|
1286
|
+
fileData = this.processFileData(this._romData, start, length, header, file.type);
|
|
1287
|
+
}
|
|
1288
|
+
if (isNode) {
|
|
1289
|
+
fs2.writeFileSync(filePath, fileData);
|
|
1290
|
+
}
|
|
1291
|
+
}
|
|
1292
|
+
}
|
|
1293
|
+
processFileData(data, position, length, header, type) {
|
|
1294
|
+
let totalLength = length;
|
|
1295
|
+
let headerLength = 0;
|
|
1296
|
+
if (header) {
|
|
1297
|
+
headerLength = header.length;
|
|
1298
|
+
totalLength += headerLength;
|
|
1299
|
+
}
|
|
1300
|
+
if (type === BinType.Palette) {
|
|
1301
|
+
const remain = _FileReader.PALETTE_MIN_SIZE - length;
|
|
1302
|
+
if (remain > 0) {
|
|
1303
|
+
totalLength += remain;
|
|
1304
|
+
}
|
|
1305
|
+
}
|
|
1306
|
+
const result = new Uint8Array(totalLength);
|
|
1307
|
+
let offset = 0;
|
|
1308
|
+
if (header) {
|
|
1309
|
+
result.set(header, offset);
|
|
1310
|
+
offset += headerLength;
|
|
1311
|
+
}
|
|
1312
|
+
result.set(data.slice(position, position + length), offset);
|
|
1313
|
+
offset += length;
|
|
1314
|
+
if (type === BinType.Palette) {
|
|
1315
|
+
const remain = _FileReader.PALETTE_MIN_SIZE - length;
|
|
1316
|
+
if (remain > 0) {
|
|
1317
|
+
result.fill(0, offset, offset + remain);
|
|
1318
|
+
}
|
|
1319
|
+
}
|
|
1320
|
+
return result;
|
|
1321
|
+
}
|
|
1322
|
+
/**
|
|
1323
|
+
* Gets the path information for a binary type from DbRoot configuration
|
|
1324
|
+
*/
|
|
1325
|
+
getPath(binType) {
|
|
1326
|
+
const pathConfig = this._dbRoot.paths[binType] || this._dbRoot.paths[BinType.Unknown];
|
|
1327
|
+
return {
|
|
1328
|
+
folder: pathConfig.folder,
|
|
1329
|
+
extension: pathConfig.extension
|
|
1330
|
+
};
|
|
1331
|
+
}
|
|
1332
|
+
};
|
|
1333
|
+
|
|
1334
|
+
// src/utils/index.ts
|
|
1335
|
+
function indexOfAny(str, chars, startIndex = 0) {
|
|
1336
|
+
for (let i = startIndex; i < str.length; i++) {
|
|
1337
|
+
if (chars.includes(str[i])) {
|
|
1338
|
+
return i;
|
|
1339
|
+
}
|
|
1340
|
+
}
|
|
1341
|
+
return -1;
|
|
1342
|
+
}
|
|
1343
|
+
|
|
1344
|
+
// src/rom/extraction/strings.ts
|
|
1345
|
+
var StringReader = class _StringReader {
|
|
1346
|
+
static {
|
|
1347
|
+
this.STRING_REFERENCE_CHARACTERS = ["~", "^"];
|
|
1348
|
+
}
|
|
1349
|
+
constructor(blockReader) {
|
|
1350
|
+
this._blockReader = blockReader;
|
|
1351
|
+
this._romDataReader = blockReader._romDataReader;
|
|
1352
|
+
}
|
|
1353
|
+
resolveCommand(cmd, builder) {
|
|
1354
|
+
if (cmd.types && cmd.types.length > 0) {
|
|
1355
|
+
builder.push(`[${cmd.value}`);
|
|
1356
|
+
let first = true;
|
|
1357
|
+
for (const t of cmd.types) {
|
|
1358
|
+
if (first) {
|
|
1359
|
+
builder.push(":");
|
|
1360
|
+
first = false;
|
|
1361
|
+
} else {
|
|
1362
|
+
builder.push(",");
|
|
1363
|
+
}
|
|
1364
|
+
switch (t) {
|
|
1365
|
+
case MemberType.Byte:
|
|
1366
|
+
builder.push(this._romDataReader.readByte().toString(16).toUpperCase());
|
|
1367
|
+
break;
|
|
1368
|
+
case MemberType.Word:
|
|
1369
|
+
builder.push(this._romDataReader.readUShort().toString(16).toUpperCase());
|
|
1370
|
+
break;
|
|
1371
|
+
case MemberType.Offset:
|
|
1372
|
+
const loc = this._romDataReader.readUShort() | this._romDataReader.position & 4128768;
|
|
1373
|
+
builder.push(`^${loc.toString(16).toUpperCase().padStart(6, "0")}`);
|
|
1374
|
+
break;
|
|
1375
|
+
case MemberType.Address:
|
|
1376
|
+
builder.push(`~${this._romDataReader.readAddress().toString(16).toUpperCase().padStart(6, "0")}`);
|
|
1377
|
+
break;
|
|
1378
|
+
case MemberType.Binary:
|
|
1379
|
+
let sfirst = true;
|
|
1380
|
+
do {
|
|
1381
|
+
const r = this._romDataReader.readByte();
|
|
1382
|
+
if (cmd.delimiter !== void 0 && r === cmd.delimiter) {
|
|
1383
|
+
break;
|
|
1384
|
+
}
|
|
1385
|
+
if (sfirst) {
|
|
1386
|
+
sfirst = false;
|
|
1387
|
+
} else {
|
|
1388
|
+
builder.push(",");
|
|
1389
|
+
}
|
|
1390
|
+
builder.push(r.toString(16).toUpperCase());
|
|
1391
|
+
} while (this._blockReader.partCanContinue());
|
|
1392
|
+
break;
|
|
1393
|
+
default:
|
|
1394
|
+
throw new Error("Unsupported member type");
|
|
1395
|
+
}
|
|
1396
|
+
}
|
|
1397
|
+
builder.push("]");
|
|
1398
|
+
} else {
|
|
1399
|
+
builder.push(`[${cmd.value}]`);
|
|
1400
|
+
}
|
|
1401
|
+
}
|
|
1402
|
+
parseString(stringType) {
|
|
1403
|
+
const dict = stringType.commands;
|
|
1404
|
+
const builder = [];
|
|
1405
|
+
const strLoc = this._romDataReader.position;
|
|
1406
|
+
const map = stringType.characterMap;
|
|
1407
|
+
const terminator = stringType.terminator;
|
|
1408
|
+
do {
|
|
1409
|
+
const c = this._romDataReader.readByte();
|
|
1410
|
+
if (c === terminator) {
|
|
1411
|
+
if (stringType.greedyTerminator) {
|
|
1412
|
+
while (this._romDataReader.peekByte() === terminator && this._blockReader.partCanContinue()) {
|
|
1413
|
+
this._romDataReader.position++;
|
|
1414
|
+
}
|
|
1415
|
+
}
|
|
1416
|
+
break;
|
|
1417
|
+
}
|
|
1418
|
+
const cmd = dict[c];
|
|
1419
|
+
if (cmd) {
|
|
1420
|
+
this.resolveCommand(cmd, builder);
|
|
1421
|
+
if (cmd.halt) {
|
|
1422
|
+
break;
|
|
1423
|
+
}
|
|
1424
|
+
} else {
|
|
1425
|
+
const index = this.shiftDown(c, stringType.shiftType);
|
|
1426
|
+
if (index >= 0 && index < map.length) {
|
|
1427
|
+
builder.push(map[index]);
|
|
1428
|
+
} else {
|
|
1429
|
+
builder.push(`[${c.toString(16).toUpperCase()}]`);
|
|
1430
|
+
}
|
|
1431
|
+
}
|
|
1432
|
+
} while (this._blockReader.partCanContinue());
|
|
1433
|
+
return {
|
|
1434
|
+
string: builder.join(""),
|
|
1435
|
+
type: stringType,
|
|
1436
|
+
marker: 0,
|
|
1437
|
+
location: strLoc
|
|
1438
|
+
};
|
|
1439
|
+
}
|
|
1440
|
+
/**
|
|
1441
|
+
* Handles character shifting based on string type
|
|
1442
|
+
* This is a simplified implementation of the shift logic
|
|
1443
|
+
*/
|
|
1444
|
+
shiftDown(c, shiftType) {
|
|
1445
|
+
if (!shiftType) {
|
|
1446
|
+
return c;
|
|
1447
|
+
}
|
|
1448
|
+
switch (shiftType) {
|
|
1449
|
+
case "wh2":
|
|
1450
|
+
return (c & 112) >> 1 | c & 7;
|
|
1451
|
+
case "h2":
|
|
1452
|
+
return (c & 224) >> 1 | c & 15;
|
|
1453
|
+
default:
|
|
1454
|
+
return c;
|
|
1455
|
+
}
|
|
1456
|
+
}
|
|
1457
|
+
resolveString(sw, isBranch) {
|
|
1458
|
+
let str = sw.string;
|
|
1459
|
+
let ix = indexOfAny(str, _StringReader.STRING_REFERENCE_CHARACTERS);
|
|
1460
|
+
while (ix >= 0) {
|
|
1461
|
+
if (ix + 6 < str.length) {
|
|
1462
|
+
const hexStr = str.substring(ix + 1, ix + 7);
|
|
1463
|
+
const sloc = parseInt(hexStr, 16);
|
|
1464
|
+
if (!isNaN(sloc)) {
|
|
1465
|
+
const addrs = new Address(sloc >> 16, sloc & 65535);
|
|
1466
|
+
if (addrs.space === AddressSpace.ROM) {
|
|
1467
|
+
this._blockReader.resolveInclude(sloc, false);
|
|
1468
|
+
const name = this._blockReader.resolveName(sloc, AddressType.Unknown, false);
|
|
1469
|
+
const opix = indexOfAny(name, RomProcessingConstants.OPERATORS);
|
|
1470
|
+
if (opix > 0) {
|
|
1471
|
+
const offsetStr = name.substring(opix + 1);
|
|
1472
|
+
let offset;
|
|
1473
|
+
if (offsetStr === "M") {
|
|
1474
|
+
offset = this._blockReader._referenceManager.markerTable.get(sloc) || 0;
|
|
1475
|
+
} else {
|
|
1476
|
+
offset = parseInt(offsetStr, 16) || 0;
|
|
1477
|
+
}
|
|
1478
|
+
if (name[opix] === "-") {
|
|
1479
|
+
offset = -offset;
|
|
1480
|
+
}
|
|
1481
|
+
name.substring(0, opix);
|
|
1482
|
+
const target = sloc - offset;
|
|
1483
|
+
const [isOutside, prt] = DbBlockUtils.isOutsideWithPart(this._blockReader._currentBlock, sloc);
|
|
1484
|
+
if (prt != null) {
|
|
1485
|
+
const root = prt.objectRoot;
|
|
1486
|
+
const entry = root?.find((x) => x.Location === target);
|
|
1487
|
+
if (entry && entry.Object) {
|
|
1488
|
+
entry.Object.marker = offset;
|
|
1489
|
+
}
|
|
1490
|
+
}
|
|
1491
|
+
}
|
|
1492
|
+
}
|
|
1493
|
+
}
|
|
1494
|
+
}
|
|
1495
|
+
ix = indexOfAny(str, _StringReader.STRING_REFERENCE_CHARACTERS, ix + 7);
|
|
1496
|
+
}
|
|
1497
|
+
}
|
|
1498
|
+
};
|
|
1499
|
+
var TypeParser = class {
|
|
1500
|
+
constructor(blockReader) {
|
|
1501
|
+
this._blockReader = blockReader;
|
|
1502
|
+
this._referenceManager = blockReader._referenceManager;
|
|
1503
|
+
this._romDataReader = blockReader._romDataReader;
|
|
1504
|
+
this._stringReader = blockReader._stringReader;
|
|
1505
|
+
this._stringTypes = blockReader._root.stringTypes;
|
|
1506
|
+
}
|
|
1507
|
+
parseType(typeName, reg, depth, bank) {
|
|
1508
|
+
if (typeName[0] === "&") {
|
|
1509
|
+
return this.parseLocation(this._romDataReader.readUShort(), bank, typeName.substring(1), AddressType.Offset);
|
|
1510
|
+
}
|
|
1511
|
+
if (typeName[0] === "@") {
|
|
1512
|
+
return this.parseLocation(this._romDataReader.readUShort(), this._romDataReader.readByte(), typeName.substring(1), AddressType.Address);
|
|
1513
|
+
}
|
|
1514
|
+
const stringType = this._stringTypes[typeName];
|
|
1515
|
+
if (stringType) {
|
|
1516
|
+
return this._stringReader.parseString(stringType);
|
|
1517
|
+
}
|
|
1518
|
+
const mType = this.tryParseMemberType(typeName);
|
|
1519
|
+
if (mType !== null) {
|
|
1520
|
+
switch (mType) {
|
|
1521
|
+
case MemberType.Byte:
|
|
1522
|
+
return createByte(this._romDataReader.readByte());
|
|
1523
|
+
case MemberType.Word:
|
|
1524
|
+
return createWord(this.parseWordSafe());
|
|
1525
|
+
case MemberType.Offset:
|
|
1526
|
+
return this.parseLocation(this._romDataReader.readUShort(), bank, null, AddressType.Offset);
|
|
1527
|
+
case MemberType.Address:
|
|
1528
|
+
return this.parseLocation(this._romDataReader.readUShort(), this._romDataReader.readByte(), null, AddressType.Address);
|
|
1529
|
+
case MemberType.Binary:
|
|
1530
|
+
return this.parseBinary();
|
|
1531
|
+
case MemberType.Code:
|
|
1532
|
+
return this.parseCode(reg);
|
|
1533
|
+
default:
|
|
1534
|
+
throw new Error("Invalid member type");
|
|
1535
|
+
}
|
|
1536
|
+
}
|
|
1537
|
+
const parentType = this._blockReader._root.structs[typeName];
|
|
1538
|
+
if (!parentType) {
|
|
1539
|
+
throw new Error(`Unknown type: ${typeName}`);
|
|
1540
|
+
}
|
|
1541
|
+
const delimiter = parentType.delimiter;
|
|
1542
|
+
const discOffset = parentType.discriminator;
|
|
1543
|
+
const objects = [];
|
|
1544
|
+
let delReached;
|
|
1545
|
+
while (!(delReached = this._blockReader.delimiterReached(delimiter))) {
|
|
1546
|
+
const startPosition = this._romDataReader.position;
|
|
1547
|
+
let targetType = parentType;
|
|
1548
|
+
if (discOffset !== void 0) {
|
|
1549
|
+
const discPosition = this._romDataReader.position + discOffset;
|
|
1550
|
+
const desc = this._romDataReader.romData[discPosition];
|
|
1551
|
+
if (discOffset === 0) {
|
|
1552
|
+
this._romDataReader.position++;
|
|
1553
|
+
}
|
|
1554
|
+
const matchedStruct = Object.values(this._blockReader._root.structs).find(
|
|
1555
|
+
(x) => x.parent === typeName && x.discriminator === desc
|
|
1556
|
+
);
|
|
1557
|
+
targetType = matchedStruct || parentType;
|
|
1558
|
+
}
|
|
1559
|
+
const types = targetType.types;
|
|
1560
|
+
if (types && types.length > 0) {
|
|
1561
|
+
const memberCount = types.length;
|
|
1562
|
+
const prevPosition = this._romDataReader.position;
|
|
1563
|
+
const parts = new Array(memberCount);
|
|
1564
|
+
const def = { name: targetType.name, parts };
|
|
1565
|
+
for (let i = 0; i < memberCount; i++) {
|
|
1566
|
+
parts[i] = this.parseType(types[i], null, depth + 1);
|
|
1567
|
+
}
|
|
1568
|
+
if (discOffset !== void 0 && discOffset === this._romDataReader.position - prevPosition) {
|
|
1569
|
+
this._romDataReader.position++;
|
|
1570
|
+
}
|
|
1571
|
+
objects.push(def);
|
|
1572
|
+
}
|
|
1573
|
+
let checkPosition = startPosition;
|
|
1574
|
+
while (++checkPosition < this._romDataReader.position) {
|
|
1575
|
+
if (this._referenceManager.containsStruct(checkPosition)) {
|
|
1576
|
+
this._romDataReader.position = checkPosition;
|
|
1577
|
+
break;
|
|
1578
|
+
}
|
|
1579
|
+
}
|
|
1580
|
+
if (!this._blockReader.partCanContinue()) {
|
|
1581
|
+
break;
|
|
1582
|
+
}
|
|
1583
|
+
}
|
|
1584
|
+
if (delReached && depth === 0) {
|
|
1585
|
+
this._referenceManager.tryAddStruct(this._romDataReader.position, typeName);
|
|
1586
|
+
}
|
|
1587
|
+
return objects;
|
|
1588
|
+
}
|
|
1589
|
+
tryParseMemberType(memberTypeName) {
|
|
1590
|
+
const upperName = memberTypeName.toUpperCase();
|
|
1591
|
+
for (const [key, value] of Object.entries(MemberType)) {
|
|
1592
|
+
if (key.toUpperCase() === upperName) {
|
|
1593
|
+
return value;
|
|
1594
|
+
}
|
|
1595
|
+
}
|
|
1596
|
+
return null;
|
|
1597
|
+
}
|
|
1598
|
+
parseWordSafe() {
|
|
1599
|
+
return this._referenceManager.containsStruct(this._romDataReader.position + 1) ? this._romDataReader.readByte() : this._romDataReader.readUShort();
|
|
1600
|
+
}
|
|
1601
|
+
parseBinary() {
|
|
1602
|
+
const startPosition = this._romDataReader.position;
|
|
1603
|
+
do {
|
|
1604
|
+
this._romDataReader.position++;
|
|
1605
|
+
} while (this._blockReader.partCanContinue());
|
|
1606
|
+
const len = this._romDataReader.position - startPosition;
|
|
1607
|
+
const outBuffer = new Uint8Array(len);
|
|
1608
|
+
for (let i = 0; i < len; i++) {
|
|
1609
|
+
outBuffer[i] = this._romDataReader.romData[startPosition + i];
|
|
1610
|
+
}
|
|
1611
|
+
return outBuffer;
|
|
1612
|
+
}
|
|
1613
|
+
parseLocation(offset, bank, typeName, addrType) {
|
|
1614
|
+
if (bank === void 0 && offset === 0) {
|
|
1615
|
+
return createWord(offset);
|
|
1616
|
+
}
|
|
1617
|
+
const resolvedBank = bank ?? this._romDataReader.position >> 16;
|
|
1618
|
+
const adrs = new Address(resolvedBank, offset);
|
|
1619
|
+
if (adrs.space !== AddressSpace.ROM) {
|
|
1620
|
+
return adrs;
|
|
1621
|
+
}
|
|
1622
|
+
const loc = adrs.toInt();
|
|
1623
|
+
if (this._blockReader._currentBlock && DbBlockUtils.isInside(this._blockReader._currentBlock, loc) && !this._blockReader._root.rewrites[loc]) {
|
|
1624
|
+
const resolvedTypeName = typeName ?? this._blockReader._currentPart?.struct ?? "Binary";
|
|
1625
|
+
this._referenceManager.tryAddStruct(loc, resolvedTypeName);
|
|
1626
|
+
const referenceName = `${resolvedTypeName.toLowerCase()}_${loc.toString(16).toUpperCase().padStart(6, "0")}`;
|
|
1627
|
+
this._referenceManager.tryAddName(loc, referenceName);
|
|
1628
|
+
}
|
|
1629
|
+
return new LocationWrapper(loc, addrType);
|
|
1630
|
+
}
|
|
1631
|
+
parseCode(reg) {
|
|
1632
|
+
const opList = [];
|
|
1633
|
+
let first = true;
|
|
1634
|
+
while (this._romDataReader.position < this._blockReader._partEnd) {
|
|
1635
|
+
if (first) {
|
|
1636
|
+
first = false;
|
|
1637
|
+
} else if (this._referenceManager.containsStruct(this._romDataReader.position)) {
|
|
1638
|
+
break;
|
|
1639
|
+
}
|
|
1640
|
+
if (reg) {
|
|
1641
|
+
this._blockReader.HydrateRegisters(reg);
|
|
1642
|
+
}
|
|
1643
|
+
const op = this._blockReader._asmReader.parseAsm(reg);
|
|
1644
|
+
opList.push(op);
|
|
1645
|
+
}
|
|
1646
|
+
return opList;
|
|
1647
|
+
}
|
|
1648
|
+
};
|
|
1649
|
+
|
|
1650
|
+
// src/assembly/Stack.ts
|
|
1651
|
+
var Stack = class {
|
|
1652
|
+
constructor() {
|
|
1653
|
+
this.bytes = new Uint8Array(70);
|
|
1654
|
+
this.location = 10;
|
|
1655
|
+
}
|
|
1656
|
+
/**
|
|
1657
|
+
* Push a byte onto the stack
|
|
1658
|
+
*/
|
|
1659
|
+
push(value) {
|
|
1660
|
+
this.bytes[this.location++] = value & 255;
|
|
1661
|
+
}
|
|
1662
|
+
/**
|
|
1663
|
+
* Push a 16-bit value onto the stack (little-endian)
|
|
1664
|
+
*/
|
|
1665
|
+
pushUInt16(value) {
|
|
1666
|
+
this.bytes[this.location++] = value & 255;
|
|
1667
|
+
this.bytes[this.location++] = value >> 8 & 255;
|
|
1668
|
+
}
|
|
1669
|
+
/**
|
|
1670
|
+
* Pop a byte from the stack
|
|
1671
|
+
*/
|
|
1672
|
+
popByte() {
|
|
1673
|
+
return this.bytes[--this.location];
|
|
1674
|
+
}
|
|
1675
|
+
/**
|
|
1676
|
+
* Pop a 16-bit value from the stack (little-endian)
|
|
1677
|
+
*/
|
|
1678
|
+
popUInt16() {
|
|
1679
|
+
const high = this.bytes[--this.location];
|
|
1680
|
+
const low = this.bytes[--this.location];
|
|
1681
|
+
return high << 8 | low;
|
|
1682
|
+
}
|
|
1683
|
+
/**
|
|
1684
|
+
* Reset the stack to initial state
|
|
1685
|
+
*/
|
|
1686
|
+
reset() {
|
|
1687
|
+
this.bytes.fill(0);
|
|
1688
|
+
this.location = 10;
|
|
1689
|
+
}
|
|
1690
|
+
};
|
|
1691
|
+
var Registers = class {
|
|
1692
|
+
constructor() {
|
|
1693
|
+
this.stack = new Stack();
|
|
1694
|
+
}
|
|
1695
|
+
/**
|
|
1696
|
+
* Get the current status flags
|
|
1697
|
+
*/
|
|
1698
|
+
get statusFlags() {
|
|
1699
|
+
let flags = 0;
|
|
1700
|
+
if (this.accumulatorFlag ?? false) {
|
|
1701
|
+
flags |= StatusFlags.AccumulatorMode;
|
|
1702
|
+
}
|
|
1703
|
+
if (this.indexFlag ?? false) {
|
|
1704
|
+
flags |= StatusFlags.IndexMode;
|
|
1705
|
+
}
|
|
1706
|
+
return flags;
|
|
1707
|
+
}
|
|
1708
|
+
/**
|
|
1709
|
+
* Set the status flags
|
|
1710
|
+
*/
|
|
1711
|
+
set statusFlags(value) {
|
|
1712
|
+
this.accumulatorFlag = (value & StatusFlags.AccumulatorMode) !== 0;
|
|
1713
|
+
this.indexFlag = (value & StatusFlags.IndexMode) !== 0;
|
|
1714
|
+
}
|
|
1715
|
+
/**
|
|
1716
|
+
* Reset all registers to initial state
|
|
1717
|
+
*/
|
|
1718
|
+
reset() {
|
|
1719
|
+
this.accumulatorFlag = void 0;
|
|
1720
|
+
this.indexFlag = void 0;
|
|
1721
|
+
this.direct = void 0;
|
|
1722
|
+
this.dataBank = void 0;
|
|
1723
|
+
this.accumulator = void 0;
|
|
1724
|
+
this.xIndex = void 0;
|
|
1725
|
+
this.yIndex = void 0;
|
|
1726
|
+
this.stack.reset();
|
|
1727
|
+
}
|
|
1728
|
+
};
|
|
1729
|
+
|
|
1730
|
+
// src/assembly/Op.ts
|
|
1731
|
+
var Op = class {
|
|
1732
|
+
constructor(code, location = 0, operands = [], size = 1) {
|
|
1733
|
+
this.code = code;
|
|
1734
|
+
this.location = location;
|
|
1735
|
+
this.operands = operands;
|
|
1736
|
+
this.size = size;
|
|
1737
|
+
}
|
|
1738
|
+
/**
|
|
1739
|
+
* Get the formatted string representation of this operation
|
|
1740
|
+
*/
|
|
1741
|
+
toString() {
|
|
1742
|
+
const mnem = this.code.mnem;
|
|
1743
|
+
const op = this.operands;
|
|
1744
|
+
if (!op || op.length === 0) {
|
|
1745
|
+
return mnem;
|
|
1746
|
+
}
|
|
1747
|
+
const operandStrings = op.map((operand) => String(operand));
|
|
1748
|
+
return `${mnem} ${operandStrings.join(", ")}`;
|
|
1749
|
+
}
|
|
1750
|
+
};
|
|
1751
|
+
var OpCode = class {
|
|
1752
|
+
constructor(code, mnem, mode, size) {
|
|
1753
|
+
this.code = code;
|
|
1754
|
+
this.mnem = mnem;
|
|
1755
|
+
this.mode = mode;
|
|
1756
|
+
this.size = size;
|
|
1757
|
+
}
|
|
1758
|
+
};
|
|
1759
|
+
var ALL_OPCODES = {
|
|
1760
|
+
// ADC - Add with Carry
|
|
1761
|
+
105: new OpCode(105, "ADC", AddressingMode.Immediate, -2),
|
|
1762
|
+
109: new OpCode(109, "ADC", AddressingMode.Absolute, 3),
|
|
1763
|
+
111: new OpCode(111, "ADC", AddressingMode.AbsoluteLong, 4),
|
|
1764
|
+
101: new OpCode(101, "ADC", AddressingMode.DirectPage, 2),
|
|
1765
|
+
114: new OpCode(114, "ADC", AddressingMode.DirectPageIndirect, 2),
|
|
1766
|
+
103: new OpCode(103, "ADC", AddressingMode.DirectPageIndirectLong, 2),
|
|
1767
|
+
125: new OpCode(125, "ADC", AddressingMode.AbsoluteIndexedX, 3),
|
|
1768
|
+
127: new OpCode(127, "ADC", AddressingMode.AbsoluteLongIndexedX, 4),
|
|
1769
|
+
121: new OpCode(121, "ADC", AddressingMode.AbsoluteIndexedY, 3),
|
|
1770
|
+
117: new OpCode(117, "ADC", AddressingMode.DirectPageIndexedX, 2),
|
|
1771
|
+
97: new OpCode(97, "ADC", AddressingMode.DirectPageIndexedIndirectX, 2),
|
|
1772
|
+
113: new OpCode(113, "ADC", AddressingMode.DirectPageIndirectIndexedY, 2),
|
|
1773
|
+
119: new OpCode(119, "ADC", AddressingMode.DirectPageIndirectLongIndexedY, 2),
|
|
1774
|
+
99: new OpCode(99, "ADC", AddressingMode.StackRelative, 2),
|
|
1775
|
+
115: new OpCode(115, "ADC", AddressingMode.StackRelativeIndirectIndexedY, 2),
|
|
1776
|
+
// AND - Logical AND
|
|
1777
|
+
41: new OpCode(41, "AND", AddressingMode.Immediate, -2),
|
|
1778
|
+
45: new OpCode(45, "AND", AddressingMode.Absolute, 3),
|
|
1779
|
+
47: new OpCode(47, "AND", AddressingMode.AbsoluteLong, 4),
|
|
1780
|
+
37: new OpCode(37, "AND", AddressingMode.DirectPage, 2),
|
|
1781
|
+
50: new OpCode(50, "AND", AddressingMode.DirectPageIndirect, 2),
|
|
1782
|
+
39: new OpCode(39, "AND", AddressingMode.DirectPageIndirectLong, 2),
|
|
1783
|
+
61: new OpCode(61, "AND", AddressingMode.AbsoluteIndexedX, 3),
|
|
1784
|
+
63: new OpCode(63, "AND", AddressingMode.AbsoluteLongIndexedX, 4),
|
|
1785
|
+
57: new OpCode(57, "AND", AddressingMode.AbsoluteIndexedY, 3),
|
|
1786
|
+
53: new OpCode(53, "AND", AddressingMode.DirectPageIndexedX, 2),
|
|
1787
|
+
33: new OpCode(33, "AND", AddressingMode.DirectPageIndexedIndirectX, 2),
|
|
1788
|
+
49: new OpCode(49, "AND", AddressingMode.DirectPageIndirectIndexedY, 2),
|
|
1789
|
+
55: new OpCode(55, "AND", AddressingMode.DirectPageIndirectLongIndexedY, 2),
|
|
1790
|
+
35: new OpCode(35, "AND", AddressingMode.StackRelative, 2),
|
|
1791
|
+
51: new OpCode(51, "AND", AddressingMode.StackRelativeIndirectIndexedY, 2),
|
|
1792
|
+
// ASL - Arithmetic Shift Left
|
|
1793
|
+
10: new OpCode(10, "ASL", AddressingMode.Accumulator, 1),
|
|
1794
|
+
14: new OpCode(14, "ASL", AddressingMode.Absolute, 3),
|
|
1795
|
+
6: new OpCode(6, "ASL", AddressingMode.DirectPage, 2),
|
|
1796
|
+
30: new OpCode(30, "ASL", AddressingMode.AbsoluteIndexedX, 3),
|
|
1797
|
+
22: new OpCode(22, "ASL", AddressingMode.DirectPageIndexedX, 2),
|
|
1798
|
+
// Branch instructions
|
|
1799
|
+
16: new OpCode(16, "BPL", AddressingMode.PCRelative, 2),
|
|
1800
|
+
48: new OpCode(48, "BMI", AddressingMode.PCRelative, 2),
|
|
1801
|
+
128: new OpCode(128, "BRA", AddressingMode.PCRelative, 2),
|
|
1802
|
+
130: new OpCode(130, "BRL", AddressingMode.PCRelativeLong, 3),
|
|
1803
|
+
144: new OpCode(144, "BCC", AddressingMode.PCRelative, 2),
|
|
1804
|
+
176: new OpCode(176, "BCS", AddressingMode.PCRelative, 2),
|
|
1805
|
+
208: new OpCode(208, "BNE", AddressingMode.PCRelative, 2),
|
|
1806
|
+
240: new OpCode(240, "BEQ", AddressingMode.PCRelative, 2),
|
|
1807
|
+
80: new OpCode(80, "BVC", AddressingMode.PCRelative, 2),
|
|
1808
|
+
112: new OpCode(112, "BVS", AddressingMode.PCRelative, 2),
|
|
1809
|
+
// BIT - Bit Test
|
|
1810
|
+
137: new OpCode(137, "BIT", AddressingMode.Immediate, -2),
|
|
1811
|
+
44: new OpCode(44, "BIT", AddressingMode.Absolute, 3),
|
|
1812
|
+
36: new OpCode(36, "BIT", AddressingMode.DirectPage, 2),
|
|
1813
|
+
60: new OpCode(60, "BIT", AddressingMode.AbsoluteIndexedX, 3),
|
|
1814
|
+
52: new OpCode(52, "BIT", AddressingMode.DirectPageIndexedX, 2),
|
|
1815
|
+
// BRK - Break
|
|
1816
|
+
0: new OpCode(0, "BRK", AddressingMode.StackInterrupt, 2),
|
|
1817
|
+
// Clear flag instructions
|
|
1818
|
+
24: new OpCode(24, "CLC", AddressingMode.Implied, 1),
|
|
1819
|
+
216: new OpCode(216, "CLD", AddressingMode.Implied, 1),
|
|
1820
|
+
88: new OpCode(88, "CLI", AddressingMode.Implied, 1),
|
|
1821
|
+
184: new OpCode(184, "CLV", AddressingMode.Implied, 1),
|
|
1822
|
+
// CMP - Compare Accumulator
|
|
1823
|
+
201: new OpCode(201, "CMP", AddressingMode.Immediate, -2),
|
|
1824
|
+
205: new OpCode(205, "CMP", AddressingMode.Absolute, 3),
|
|
1825
|
+
207: new OpCode(207, "CMP", AddressingMode.AbsoluteLong, 4),
|
|
1826
|
+
197: new OpCode(197, "CMP", AddressingMode.DirectPage, 2),
|
|
1827
|
+
210: new OpCode(210, "CMP", AddressingMode.DirectPageIndirect, 2),
|
|
1828
|
+
199: new OpCode(199, "CMP", AddressingMode.DirectPageIndirectLong, 2),
|
|
1829
|
+
221: new OpCode(221, "CMP", AddressingMode.AbsoluteIndexedX, 3),
|
|
1830
|
+
223: new OpCode(223, "CMP", AddressingMode.AbsoluteLongIndexedX, 4),
|
|
1831
|
+
217: new OpCode(217, "CMP", AddressingMode.AbsoluteIndexedY, 3),
|
|
1832
|
+
213: new OpCode(213, "CMP", AddressingMode.DirectPageIndexedX, 2),
|
|
1833
|
+
193: new OpCode(193, "CMP", AddressingMode.DirectPageIndexedIndirectX, 2),
|
|
1834
|
+
209: new OpCode(209, "CMP", AddressingMode.DirectPageIndirectIndexedY, 2),
|
|
1835
|
+
215: new OpCode(215, "CMP", AddressingMode.DirectPageIndirectLongIndexedY, 2),
|
|
1836
|
+
195: new OpCode(195, "CMP", AddressingMode.StackRelative, 2),
|
|
1837
|
+
211: new OpCode(211, "CMP", AddressingMode.StackRelativeIndirectIndexedY, 2),
|
|
1838
|
+
// COP - Coprocessor Instruction
|
|
1839
|
+
2: new OpCode(2, "COP", AddressingMode.StackInterrupt, 2),
|
|
1840
|
+
// CPX - Compare X Register
|
|
1841
|
+
224: new OpCode(224, "CPX", AddressingMode.Immediate, -2),
|
|
1842
|
+
236: new OpCode(236, "CPX", AddressingMode.Absolute, 3),
|
|
1843
|
+
228: new OpCode(228, "CPX", AddressingMode.DirectPage, 2),
|
|
1844
|
+
// CPY - Compare Y Register
|
|
1845
|
+
192: new OpCode(192, "CPY", AddressingMode.Immediate, -2),
|
|
1846
|
+
204: new OpCode(204, "CPY", AddressingMode.Absolute, 3),
|
|
1847
|
+
196: new OpCode(196, "CPY", AddressingMode.DirectPage, 2),
|
|
1848
|
+
// DEC - Decrement
|
|
1849
|
+
58: new OpCode(58, "DEC", AddressingMode.Accumulator, 1),
|
|
1850
|
+
206: new OpCode(206, "DEC", AddressingMode.Absolute, 3),
|
|
1851
|
+
198: new OpCode(198, "DEC", AddressingMode.DirectPage, 2),
|
|
1852
|
+
222: new OpCode(222, "DEC", AddressingMode.AbsoluteIndexedX, 3),
|
|
1853
|
+
214: new OpCode(214, "DEC", AddressingMode.DirectPageIndexedX, 2),
|
|
1854
|
+
// DEX/DEY - Decrement Index Registers
|
|
1855
|
+
202: new OpCode(202, "DEX", AddressingMode.Implied, 1),
|
|
1856
|
+
136: new OpCode(136, "DEY", AddressingMode.Implied, 1),
|
|
1857
|
+
// EOR - Exclusive OR
|
|
1858
|
+
73: new OpCode(73, "EOR", AddressingMode.Immediate, -2),
|
|
1859
|
+
77: new OpCode(77, "EOR", AddressingMode.Absolute, 3),
|
|
1860
|
+
79: new OpCode(79, "EOR", AddressingMode.AbsoluteLong, 4),
|
|
1861
|
+
69: new OpCode(69, "EOR", AddressingMode.DirectPage, 2),
|
|
1862
|
+
82: new OpCode(82, "EOR", AddressingMode.DirectPageIndirect, 2),
|
|
1863
|
+
71: new OpCode(71, "EOR", AddressingMode.DirectPageIndirectLong, 2),
|
|
1864
|
+
93: new OpCode(93, "EOR", AddressingMode.AbsoluteIndexedX, 3),
|
|
1865
|
+
95: new OpCode(95, "EOR", AddressingMode.AbsoluteLongIndexedX, 4),
|
|
1866
|
+
89: new OpCode(89, "EOR", AddressingMode.AbsoluteIndexedY, 3),
|
|
1867
|
+
85: new OpCode(85, "EOR", AddressingMode.DirectPageIndexedX, 2),
|
|
1868
|
+
65: new OpCode(65, "EOR", AddressingMode.DirectPageIndexedIndirectX, 2),
|
|
1869
|
+
81: new OpCode(81, "EOR", AddressingMode.DirectPageIndirectIndexedY, 2),
|
|
1870
|
+
87: new OpCode(87, "EOR", AddressingMode.DirectPageIndirectLongIndexedY, 2),
|
|
1871
|
+
67: new OpCode(67, "EOR", AddressingMode.StackRelative, 2),
|
|
1872
|
+
83: new OpCode(83, "EOR", AddressingMode.StackRelativeIndirectIndexedY, 2),
|
|
1873
|
+
// INC - Increment
|
|
1874
|
+
26: new OpCode(26, "INC", AddressingMode.Accumulator, 1),
|
|
1875
|
+
238: new OpCode(238, "INC", AddressingMode.Absolute, 3),
|
|
1876
|
+
230: new OpCode(230, "INC", AddressingMode.DirectPage, 2),
|
|
1877
|
+
254: new OpCode(254, "INC", AddressingMode.AbsoluteIndexedX, 3),
|
|
1878
|
+
246: new OpCode(246, "INC", AddressingMode.DirectPageIndexedX, 2),
|
|
1879
|
+
// INX/INY - Increment Index Registers
|
|
1880
|
+
232: new OpCode(232, "INX", AddressingMode.Implied, 1),
|
|
1881
|
+
200: new OpCode(200, "INY", AddressingMode.Implied, 1),
|
|
1882
|
+
// JMP/JML - Jump
|
|
1883
|
+
76: new OpCode(76, "JMP", AddressingMode.Absolute, 3),
|
|
1884
|
+
108: new OpCode(108, "JMP", AddressingMode.AbsoluteIndirect, 3),
|
|
1885
|
+
124: new OpCode(124, "JMP", AddressingMode.AbsoluteIndexedIndirect, 3),
|
|
1886
|
+
92: new OpCode(92, "JML", AddressingMode.AbsoluteLong, 4),
|
|
1887
|
+
220: new OpCode(220, "JML", AddressingMode.AbsoluteIndirectLong, 3),
|
|
1888
|
+
// JSR/JSL - Jump to Subroutine
|
|
1889
|
+
32: new OpCode(32, "JSR", AddressingMode.Absolute, 3),
|
|
1890
|
+
252: new OpCode(252, "JSR", AddressingMode.AbsoluteIndexedIndirect, 3),
|
|
1891
|
+
34: new OpCode(34, "JSL", AddressingMode.AbsoluteLong, 4),
|
|
1892
|
+
// LDA - Load Accumulator
|
|
1893
|
+
169: new OpCode(169, "LDA", AddressingMode.Immediate, -2),
|
|
1894
|
+
173: new OpCode(173, "LDA", AddressingMode.Absolute, 3),
|
|
1895
|
+
175: new OpCode(175, "LDA", AddressingMode.AbsoluteLong, 4),
|
|
1896
|
+
165: new OpCode(165, "LDA", AddressingMode.DirectPage, 2),
|
|
1897
|
+
178: new OpCode(178, "LDA", AddressingMode.DirectPageIndirect, 2),
|
|
1898
|
+
167: new OpCode(167, "LDA", AddressingMode.DirectPageIndirectLong, 2),
|
|
1899
|
+
189: new OpCode(189, "LDA", AddressingMode.AbsoluteIndexedX, 3),
|
|
1900
|
+
191: new OpCode(191, "LDA", AddressingMode.AbsoluteLongIndexedX, 4),
|
|
1901
|
+
185: new OpCode(185, "LDA", AddressingMode.AbsoluteIndexedY, 3),
|
|
1902
|
+
181: new OpCode(181, "LDA", AddressingMode.DirectPageIndexedX, 2),
|
|
1903
|
+
161: new OpCode(161, "LDA", AddressingMode.DirectPageIndexedIndirectX, 2),
|
|
1904
|
+
177: new OpCode(177, "LDA", AddressingMode.DirectPageIndirectIndexedY, 2),
|
|
1905
|
+
183: new OpCode(183, "LDA", AddressingMode.DirectPageIndirectLongIndexedY, 2),
|
|
1906
|
+
163: new OpCode(163, "LDA", AddressingMode.StackRelative, 2),
|
|
1907
|
+
179: new OpCode(179, "LDA", AddressingMode.StackRelativeIndirectIndexedY, 2),
|
|
1908
|
+
// LDX - Load X Register
|
|
1909
|
+
162: new OpCode(162, "LDX", AddressingMode.Immediate, -2),
|
|
1910
|
+
174: new OpCode(174, "LDX", AddressingMode.Absolute, 3),
|
|
1911
|
+
166: new OpCode(166, "LDX", AddressingMode.DirectPage, 2),
|
|
1912
|
+
190: new OpCode(190, "LDX", AddressingMode.AbsoluteIndexedY, 3),
|
|
1913
|
+
182: new OpCode(182, "LDX", AddressingMode.DirectPageIndexedY, 2),
|
|
1914
|
+
// LDY - Load Y Register
|
|
1915
|
+
160: new OpCode(160, "LDY", AddressingMode.Immediate, -2),
|
|
1916
|
+
172: new OpCode(172, "LDY", AddressingMode.Absolute, 3),
|
|
1917
|
+
164: new OpCode(164, "LDY", AddressingMode.DirectPage, 2),
|
|
1918
|
+
188: new OpCode(188, "LDY", AddressingMode.AbsoluteIndexedX, 3),
|
|
1919
|
+
180: new OpCode(180, "LDY", AddressingMode.DirectPageIndexedX, 2),
|
|
1920
|
+
// LSR - Logical Shift Right
|
|
1921
|
+
74: new OpCode(74, "LSR", AddressingMode.Accumulator, 1),
|
|
1922
|
+
78: new OpCode(78, "LSR", AddressingMode.Absolute, 3),
|
|
1923
|
+
70: new OpCode(70, "LSR", AddressingMode.DirectPage, 2),
|
|
1924
|
+
94: new OpCode(94, "LSR", AddressingMode.AbsoluteIndexedX, 3),
|
|
1925
|
+
86: new OpCode(86, "LSR", AddressingMode.DirectPageIndexedX, 2),
|
|
1926
|
+
// MVN/MVP - Block Move
|
|
1927
|
+
84: new OpCode(84, "MVN", AddressingMode.BlockMove, 3),
|
|
1928
|
+
68: new OpCode(68, "MVP", AddressingMode.BlockMove, 3),
|
|
1929
|
+
// NOP - No Operation
|
|
1930
|
+
234: new OpCode(234, "NOP", AddressingMode.Implied, 1),
|
|
1931
|
+
// ORA - Logical OR
|
|
1932
|
+
9: new OpCode(9, "ORA", AddressingMode.Immediate, -2),
|
|
1933
|
+
13: new OpCode(13, "ORA", AddressingMode.Absolute, 3),
|
|
1934
|
+
15: new OpCode(15, "ORA", AddressingMode.AbsoluteLong, 4),
|
|
1935
|
+
5: new OpCode(5, "ORA", AddressingMode.DirectPage, 2),
|
|
1936
|
+
18: new OpCode(18, "ORA", AddressingMode.DirectPageIndirect, 2),
|
|
1937
|
+
7: new OpCode(7, "ORA", AddressingMode.DirectPageIndirectLong, 2),
|
|
1938
|
+
29: new OpCode(29, "ORA", AddressingMode.AbsoluteIndexedX, 3),
|
|
1939
|
+
31: new OpCode(31, "ORA", AddressingMode.AbsoluteLongIndexedX, 4),
|
|
1940
|
+
25: new OpCode(25, "ORA", AddressingMode.AbsoluteIndexedY, 3),
|
|
1941
|
+
21: new OpCode(21, "ORA", AddressingMode.DirectPageIndexedX, 2),
|
|
1942
|
+
1: new OpCode(1, "ORA", AddressingMode.DirectPageIndexedIndirectX, 2),
|
|
1943
|
+
17: new OpCode(17, "ORA", AddressingMode.DirectPageIndirectIndexedY, 2),
|
|
1944
|
+
23: new OpCode(23, "ORA", AddressingMode.DirectPageIndirectLongIndexedY, 2),
|
|
1945
|
+
3: new OpCode(3, "ORA", AddressingMode.StackRelative, 2),
|
|
1946
|
+
19: new OpCode(19, "ORA", AddressingMode.StackRelativeIndirectIndexedY, 2),
|
|
1947
|
+
// Push instructions
|
|
1948
|
+
244: new OpCode(244, "PEA", AddressingMode.Absolute, 3),
|
|
1949
|
+
212: new OpCode(212, "PEI", AddressingMode.DirectPageIndirect, 2),
|
|
1950
|
+
98: new OpCode(98, "PER", AddressingMode.PCRelativeLong, 3),
|
|
1951
|
+
72: new OpCode(72, "PHA", AddressingMode.Stack, 1),
|
|
1952
|
+
139: new OpCode(139, "PHB", AddressingMode.Stack, 1),
|
|
1953
|
+
11: new OpCode(11, "PHD", AddressingMode.Stack, 1),
|
|
1954
|
+
75: new OpCode(75, "PHK", AddressingMode.Stack, 1),
|
|
1955
|
+
8: new OpCode(8, "PHP", AddressingMode.Stack, 1),
|
|
1956
|
+
218: new OpCode(218, "PHX", AddressingMode.Stack, 1),
|
|
1957
|
+
90: new OpCode(90, "PHY", AddressingMode.Stack, 1),
|
|
1958
|
+
// Pull instructions
|
|
1959
|
+
104: new OpCode(104, "PLA", AddressingMode.Stack, 1),
|
|
1960
|
+
171: new OpCode(171, "PLB", AddressingMode.Stack, 1),
|
|
1961
|
+
43: new OpCode(43, "PLD", AddressingMode.Stack, 1),
|
|
1962
|
+
40: new OpCode(40, "PLP", AddressingMode.Stack, 1),
|
|
1963
|
+
250: new OpCode(250, "PLX", AddressingMode.Stack, 1),
|
|
1964
|
+
122: new OpCode(122, "PLY", AddressingMode.Stack, 1),
|
|
1965
|
+
// REP - Reset Status Bits
|
|
1966
|
+
194: new OpCode(194, "REP", AddressingMode.Immediate, 2),
|
|
1967
|
+
// ROL - Rotate Left
|
|
1968
|
+
42: new OpCode(42, "ROL", AddressingMode.Accumulator, 1),
|
|
1969
|
+
46: new OpCode(46, "ROL", AddressingMode.Absolute, 3),
|
|
1970
|
+
38: new OpCode(38, "ROL", AddressingMode.DirectPage, 2),
|
|
1971
|
+
62: new OpCode(62, "ROL", AddressingMode.AbsoluteIndexedX, 3),
|
|
1972
|
+
54: new OpCode(54, "ROL", AddressingMode.DirectPageIndexedX, 2),
|
|
1973
|
+
// ROR - Rotate Right
|
|
1974
|
+
106: new OpCode(106, "ROR", AddressingMode.Accumulator, 1),
|
|
1975
|
+
110: new OpCode(110, "ROR", AddressingMode.Absolute, 3),
|
|
1976
|
+
102: new OpCode(102, "ROR", AddressingMode.DirectPage, 2),
|
|
1977
|
+
126: new OpCode(126, "ROR", AddressingMode.AbsoluteIndexedX, 3),
|
|
1978
|
+
118: new OpCode(118, "ROR", AddressingMode.DirectPageIndexedX, 2),
|
|
1979
|
+
// Return instructions
|
|
1980
|
+
64: new OpCode(64, "RTI", AddressingMode.Stack, 1),
|
|
1981
|
+
107: new OpCode(107, "RTL", AddressingMode.Stack, 1),
|
|
1982
|
+
96: new OpCode(96, "RTS", AddressingMode.Stack, 1),
|
|
1983
|
+
// SBC - Subtract with Carry
|
|
1984
|
+
233: new OpCode(233, "SBC", AddressingMode.Immediate, -2),
|
|
1985
|
+
237: new OpCode(237, "SBC", AddressingMode.Absolute, 3),
|
|
1986
|
+
239: new OpCode(239, "SBC", AddressingMode.AbsoluteLong, 4),
|
|
1987
|
+
229: new OpCode(229, "SBC", AddressingMode.DirectPage, 2),
|
|
1988
|
+
242: new OpCode(242, "SBC", AddressingMode.DirectPageIndirect, 2),
|
|
1989
|
+
231: new OpCode(231, "SBC", AddressingMode.DirectPageIndirectLong, 2),
|
|
1990
|
+
253: new OpCode(253, "SBC", AddressingMode.AbsoluteIndexedX, 3),
|
|
1991
|
+
255: new OpCode(255, "SBC", AddressingMode.AbsoluteLongIndexedX, 4),
|
|
1992
|
+
249: new OpCode(249, "SBC", AddressingMode.AbsoluteIndexedY, 3),
|
|
1993
|
+
245: new OpCode(245, "SBC", AddressingMode.DirectPageIndexedX, 2),
|
|
1994
|
+
225: new OpCode(225, "SBC", AddressingMode.DirectPageIndexedIndirectX, 2),
|
|
1995
|
+
241: new OpCode(241, "SBC", AddressingMode.DirectPageIndirectIndexedY, 2),
|
|
1996
|
+
247: new OpCode(247, "SBC", AddressingMode.DirectPageIndirectLongIndexedY, 2),
|
|
1997
|
+
227: new OpCode(227, "SBC", AddressingMode.StackRelative, 2),
|
|
1998
|
+
243: new OpCode(243, "SBC", AddressingMode.StackRelativeIndirectIndexedY, 2),
|
|
1999
|
+
// Set flag instructions
|
|
2000
|
+
56: new OpCode(56, "SEC", AddressingMode.Implied, 1),
|
|
2001
|
+
248: new OpCode(248, "SED", AddressingMode.Implied, 1),
|
|
2002
|
+
120: new OpCode(120, "SEI", AddressingMode.Implied, 1),
|
|
2003
|
+
226: new OpCode(226, "SEP", AddressingMode.Immediate, 2),
|
|
2004
|
+
// STA - Store Accumulator
|
|
2005
|
+
141: new OpCode(141, "STA", AddressingMode.Absolute, 3),
|
|
2006
|
+
143: new OpCode(143, "STA", AddressingMode.AbsoluteLong, 4),
|
|
2007
|
+
133: new OpCode(133, "STA", AddressingMode.DirectPage, 2),
|
|
2008
|
+
146: new OpCode(146, "STA", AddressingMode.DirectPageIndirect, 2),
|
|
2009
|
+
135: new OpCode(135, "STA", AddressingMode.DirectPageIndirectLong, 2),
|
|
2010
|
+
157: new OpCode(157, "STA", AddressingMode.AbsoluteIndexedX, 3),
|
|
2011
|
+
159: new OpCode(159, "STA", AddressingMode.AbsoluteLongIndexedX, 4),
|
|
2012
|
+
153: new OpCode(153, "STA", AddressingMode.AbsoluteIndexedY, 3),
|
|
2013
|
+
149: new OpCode(149, "STA", AddressingMode.DirectPageIndexedX, 2),
|
|
2014
|
+
129: new OpCode(129, "STA", AddressingMode.DirectPageIndexedIndirectX, 2),
|
|
2015
|
+
145: new OpCode(145, "STA", AddressingMode.DirectPageIndirectIndexedY, 2),
|
|
2016
|
+
151: new OpCode(151, "STA", AddressingMode.DirectPageIndirectLongIndexedY, 2),
|
|
2017
|
+
131: new OpCode(131, "STA", AddressingMode.StackRelative, 2),
|
|
2018
|
+
147: new OpCode(147, "STA", AddressingMode.StackRelativeIndirectIndexedY, 2),
|
|
2019
|
+
// STP - Stop
|
|
2020
|
+
219: new OpCode(219, "STP", AddressingMode.Implied, 1),
|
|
2021
|
+
// STX - Store X Register
|
|
2022
|
+
142: new OpCode(142, "STX", AddressingMode.Absolute, 3),
|
|
2023
|
+
134: new OpCode(134, "STX", AddressingMode.DirectPage, 2),
|
|
2024
|
+
150: new OpCode(150, "STX", AddressingMode.DirectPageIndexedY, 2),
|
|
2025
|
+
// STY - Store Y Register
|
|
2026
|
+
140: new OpCode(140, "STY", AddressingMode.Absolute, 3),
|
|
2027
|
+
132: new OpCode(132, "STY", AddressingMode.DirectPage, 2),
|
|
2028
|
+
148: new OpCode(148, "STY", AddressingMode.DirectPageIndexedX, 2),
|
|
2029
|
+
// STZ - Store Zero
|
|
2030
|
+
156: new OpCode(156, "STZ", AddressingMode.Absolute, 3),
|
|
2031
|
+
100: new OpCode(100, "STZ", AddressingMode.DirectPage, 2),
|
|
2032
|
+
158: new OpCode(158, "STZ", AddressingMode.AbsoluteIndexedX, 3),
|
|
2033
|
+
116: new OpCode(116, "STZ", AddressingMode.DirectPageIndexedX, 2),
|
|
2034
|
+
// Transfer instructions
|
|
2035
|
+
170: new OpCode(170, "TAX", AddressingMode.Implied, 1),
|
|
2036
|
+
168: new OpCode(168, "TAY", AddressingMode.Implied, 1),
|
|
2037
|
+
91: new OpCode(91, "TCD", AddressingMode.Implied, 1),
|
|
2038
|
+
27: new OpCode(27, "TCS", AddressingMode.Implied, 1),
|
|
2039
|
+
123: new OpCode(123, "TDC", AddressingMode.Implied, 1),
|
|
2040
|
+
28: new OpCode(28, "TRB", AddressingMode.Absolute, 3),
|
|
2041
|
+
20: new OpCode(20, "TRB", AddressingMode.DirectPage, 2),
|
|
2042
|
+
12: new OpCode(12, "TSB", AddressingMode.Absolute, 3),
|
|
2043
|
+
4: new OpCode(4, "TSB", AddressingMode.DirectPage, 2),
|
|
2044
|
+
59: new OpCode(59, "TSC", AddressingMode.Implied, 1),
|
|
2045
|
+
186: new OpCode(186, "TSX", AddressingMode.Implied, 1),
|
|
2046
|
+
138: new OpCode(138, "TXA", AddressingMode.Implied, 1),
|
|
2047
|
+
154: new OpCode(154, "TXS", AddressingMode.Implied, 1),
|
|
2048
|
+
155: new OpCode(155, "TXY", AddressingMode.Implied, 1),
|
|
2049
|
+
152: new OpCode(152, "TYA", AddressingMode.Implied, 1),
|
|
2050
|
+
187: new OpCode(187, "TYX", AddressingMode.Implied, 1),
|
|
2051
|
+
// Miscellaneous
|
|
2052
|
+
203: new OpCode(203, "WAI", AddressingMode.Implied, 1),
|
|
2053
|
+
66: new OpCode(66, "WDM", AddressingMode.Implied, 1),
|
|
2054
|
+
235: new OpCode(235, "XBA", AddressingMode.Implied, 1),
|
|
2055
|
+
251: new OpCode(251, "XCE", AddressingMode.Implied, 1)
|
|
2056
|
+
};
|
|
2057
|
+
var GROUPED_OPCODES = {};
|
|
2058
|
+
for (const opcode of Object.values(ALL_OPCODES)) {
|
|
2059
|
+
if (!GROUPED_OPCODES[opcode.mnem]) {
|
|
2060
|
+
GROUPED_OPCODES[opcode.mnem] = [];
|
|
2061
|
+
}
|
|
2062
|
+
GROUPED_OPCODES[opcode.mnem].push(opcode);
|
|
2063
|
+
}
|
|
2064
|
+
var ADDRESSING_REGEX = {
|
|
2065
|
+
[AddressingMode.DirectPageIndexedIndirectX]: /^\(\$([A-Fa-f0-9]{2}),\s?[Xx]\)$/,
|
|
2066
|
+
[AddressingMode.StackRelative]: /^\$([A-Fa-f0-9]{2}),\s?[Ss]$/,
|
|
2067
|
+
[AddressingMode.StackInterrupt]: /^#\$([A-Fa-f0-9]{2})$/,
|
|
2068
|
+
[AddressingMode.DirectPage]: /^\$([A-Fa-f0-9]{2})$/,
|
|
2069
|
+
[AddressingMode.DirectPageIndirectLong]: /^\[\$([A-Fa-f0-9]{2})\]$/,
|
|
2070
|
+
[AddressingMode.Immediate]: /^#(\$[A-Fa-f0-9]{2,4}|\$?[&^*][A-Za-z0-9-+_]+)$/,
|
|
2071
|
+
[AddressingMode.Absolute]: /^\$([A-Fa-f0-9]{4}|&[A-Za-z0-9-+_]+)$/,
|
|
2072
|
+
[AddressingMode.AbsoluteLong]: /^\$([A-Fa-f0-9]{6}|\@[A-Za-z0-9-+_]+)$/,
|
|
2073
|
+
[AddressingMode.DirectPageIndirectIndexedY]: /^\(\$([A-Fa-f0-9]{2})\),\s?[Yy]$/,
|
|
2074
|
+
[AddressingMode.DirectPageIndirect]: /^\(\$([A-Fa-f0-9]{2})\)$/,
|
|
2075
|
+
[AddressingMode.StackRelativeIndirectIndexedY]: /^\(\$([A-Fa-f0-9]{2}),\s?[Ss]\),\s?[Yy]$/,
|
|
2076
|
+
[AddressingMode.DirectPageIndexedX]: /^\$([A-Fa-f0-9]{2}),\s?[Xx]$/,
|
|
2077
|
+
[AddressingMode.DirectPageIndirectLongIndexedY]: /^\[\$([A-Fa-f0-9]{2})\],\s?[Yy]$/,
|
|
2078
|
+
[AddressingMode.AbsoluteIndexedY]: /^(\$[A-Fa-f0-9]{4}|\$?&[A-Za-z0-9-+_]+),\s?[Yy]$/,
|
|
2079
|
+
[AddressingMode.AbsoluteIndexedX]: /^(\$[A-Fa-f0-9]{4}|\$?&[A-Za-z0-9-+_]+),\s?[Xx]$/,
|
|
2080
|
+
[AddressingMode.AbsoluteLongIndexedX]: /^(\$[A-Fa-f0-9]{6}|\$?@[A-Za-z0-9-+_]+),\s?[Xx]$/,
|
|
2081
|
+
[AddressingMode.AbsoluteIndexedIndirect]: /^\((\$[A-Fa-f0-9]{4}|\$?&[A-Za-z0-9-+_]+),\s*[Xx]\)$/,
|
|
2082
|
+
[AddressingMode.BlockMove]: /^#\$([A-Fa-f0-9]{2}|\^[A-Za-z0-9-+_]+),\s?#\$([A-Fa-f0-9]{2}|\^[A-Za-z0-9-+_]+)$/,
|
|
2083
|
+
// Default entries for other addressing modes
|
|
2084
|
+
[AddressingMode.Implied]: /^$/,
|
|
2085
|
+
[AddressingMode.Accumulator]: /^[Aa]$/,
|
|
2086
|
+
[AddressingMode.PCRelative]: /^/,
|
|
2087
|
+
[AddressingMode.PCRelativeLong]: /^/,
|
|
2088
|
+
[AddressingMode.AbsoluteIndirect]: /^/,
|
|
2089
|
+
[AddressingMode.AbsoluteIndirectLong]: /^/,
|
|
2090
|
+
[AddressingMode.DirectPageIndexedY]: /^/,
|
|
2091
|
+
[AddressingMode.Stack]: /^/
|
|
2092
|
+
};
|
|
2093
|
+
var HEX_REGEX = /[^A-Fa-f0-9]/g;
|
|
2094
|
+
var OpCodeUtils = class {
|
|
2095
|
+
/**
|
|
2096
|
+
* Get all available opcodes
|
|
2097
|
+
*/
|
|
2098
|
+
static getAllOpcodes() {
|
|
2099
|
+
return Object.values(ALL_OPCODES);
|
|
2100
|
+
}
|
|
2101
|
+
/**
|
|
2102
|
+
* Get opcodes by mnemonic
|
|
2103
|
+
*/
|
|
2104
|
+
static getByMnemonic(mnemonic) {
|
|
2105
|
+
return GROUPED_OPCODES[mnemonic] || [];
|
|
2106
|
+
}
|
|
2107
|
+
/**
|
|
2108
|
+
* Find opcode by hex value
|
|
2109
|
+
*/
|
|
2110
|
+
static findByCode(code) {
|
|
2111
|
+
return ALL_OPCODES[code];
|
|
2112
|
+
}
|
|
2113
|
+
};
|
|
2114
|
+
|
|
2115
|
+
// src/rom/extraction/asm.ts
|
|
2116
|
+
var AsmReader = class _AsmReader {
|
|
2117
|
+
static {
|
|
2118
|
+
// Constants
|
|
2119
|
+
this.ACCUMULATOR_OP_MASK = 15;
|
|
2120
|
+
}
|
|
2121
|
+
static {
|
|
2122
|
+
this.ACCUMULATOR_OP_VALUE = 9;
|
|
2123
|
+
}
|
|
2124
|
+
static {
|
|
2125
|
+
this.VARIABLE_SIZE_INDICATOR = -2;
|
|
2126
|
+
}
|
|
2127
|
+
static {
|
|
2128
|
+
this.TWO_BYTES_SIZE = 2;
|
|
2129
|
+
}
|
|
2130
|
+
static {
|
|
2131
|
+
this.THREE_BYTES_SIZE = 3;
|
|
2132
|
+
}
|
|
2133
|
+
constructor(blockReader) {
|
|
2134
|
+
this._blockReader = blockReader;
|
|
2135
|
+
this._transformProcessor = new TransformProcessor(blockReader);
|
|
2136
|
+
this._addressingModeHandler = new AddressingModeHandler(blockReader, this._transformProcessor);
|
|
2137
|
+
this._romDataReader = blockReader._romDataReader;
|
|
2138
|
+
}
|
|
2139
|
+
parseAsm(reg) {
|
|
2140
|
+
const opStart = this._romDataReader.position;
|
|
2141
|
+
const opCode = this._romDataReader.readByte();
|
|
2142
|
+
const code = this._blockReader._root.opCodes[opCode];
|
|
2143
|
+
if (!code) {
|
|
2144
|
+
throw new Error("Unknown OpCode");
|
|
2145
|
+
}
|
|
2146
|
+
const operationContext = this.initializeOperation(code, reg, opStart);
|
|
2147
|
+
const operands = this._addressingModeHandler.processAddressingMode(code, operationContext, reg);
|
|
2148
|
+
this._transformProcessor.applyTransforms(operationContext.xForm1, operationContext.xForm2, operands);
|
|
2149
|
+
const op = new Op(
|
|
2150
|
+
code,
|
|
2151
|
+
opStart,
|
|
2152
|
+
operands,
|
|
2153
|
+
this._romDataReader.position - opStart
|
|
2154
|
+
);
|
|
2155
|
+
if (operationContext.copDef) {
|
|
2156
|
+
op.copDef = operationContext.copDef;
|
|
2157
|
+
}
|
|
2158
|
+
return op;
|
|
2159
|
+
}
|
|
2160
|
+
clearDestinationRegister(code, reg) {
|
|
2161
|
+
switch (code.mnem) {
|
|
2162
|
+
case "LDA":
|
|
2163
|
+
reg.accumulator = void 0;
|
|
2164
|
+
break;
|
|
2165
|
+
case "LDX":
|
|
2166
|
+
reg.xIndex = void 0;
|
|
2167
|
+
break;
|
|
2168
|
+
case "LDY":
|
|
2169
|
+
reg.yIndex = void 0;
|
|
2170
|
+
break;
|
|
2171
|
+
}
|
|
2172
|
+
}
|
|
2173
|
+
initializeOperation(code, reg, loc) {
|
|
2174
|
+
const size = this.calculateInstructionSize(code, reg);
|
|
2175
|
+
const next = loc + size;
|
|
2176
|
+
this.clearDestinationRegister(code, reg);
|
|
2177
|
+
const context = new OperationContext();
|
|
2178
|
+
context.size = size;
|
|
2179
|
+
context.nextAddress = next;
|
|
2180
|
+
context.xForm1 = this._transformProcessor.getTransform();
|
|
2181
|
+
context.xForm2 = null;
|
|
2182
|
+
context.copDef = null;
|
|
2183
|
+
return context;
|
|
2184
|
+
}
|
|
2185
|
+
calculateInstructionSize(code, reg) {
|
|
2186
|
+
let size = code.size;
|
|
2187
|
+
if (size === _AsmReader.VARIABLE_SIZE_INDICATOR) {
|
|
2188
|
+
if ((code.code & _AsmReader.ACCUMULATOR_OP_MASK) === _AsmReader.ACCUMULATOR_OP_VALUE) {
|
|
2189
|
+
size = reg.accumulatorFlag ?? false ? _AsmReader.TWO_BYTES_SIZE : _AsmReader.THREE_BYTES_SIZE;
|
|
2190
|
+
} else {
|
|
2191
|
+
size = reg.indexFlag ?? false ? _AsmReader.TWO_BYTES_SIZE : _AsmReader.THREE_BYTES_SIZE;
|
|
2192
|
+
}
|
|
2193
|
+
}
|
|
2194
|
+
return size;
|
|
2195
|
+
}
|
|
2196
|
+
};
|
|
2197
|
+
var BlockReader = class {
|
|
2198
|
+
constructor(romData, root) {
|
|
2199
|
+
this._currentPart = null;
|
|
2200
|
+
this._partEnd = 0;
|
|
2201
|
+
this._romDataReader = new RomDataReader(romData);
|
|
2202
|
+
this._stateManager = new ProcessorStateManager();
|
|
2203
|
+
this._referenceManager = new ReferenceManager(root);
|
|
2204
|
+
this._root = root;
|
|
2205
|
+
this._stringReader = new StringReader(this);
|
|
2206
|
+
this._asmReader = new AsmReader(this);
|
|
2207
|
+
this._typeParser = new TypeParser(this);
|
|
2208
|
+
this.initializeOverrides();
|
|
2209
|
+
this.initializeFileReferences();
|
|
2210
|
+
}
|
|
2211
|
+
static {
|
|
2212
|
+
// Constants
|
|
2213
|
+
this.REF_SEARCH_MAX_RANGE = 416;
|
|
2214
|
+
}
|
|
2215
|
+
static {
|
|
2216
|
+
this.BANK_MASK_CHECK = 64;
|
|
2217
|
+
}
|
|
2218
|
+
static {
|
|
2219
|
+
this.BYTE_DELIMITER_THRESHOLD = 256;
|
|
2220
|
+
}
|
|
2221
|
+
static {
|
|
2222
|
+
this.BANK_HIGH_MEMORY_1 = 126;
|
|
2223
|
+
}
|
|
2224
|
+
static {
|
|
2225
|
+
this.BANK_HIGH_MEMORY_2 = 127;
|
|
2226
|
+
}
|
|
2227
|
+
static {
|
|
2228
|
+
this.POINTER_CHARACTERS = ["&", "@"];
|
|
2229
|
+
}
|
|
2230
|
+
static {
|
|
2231
|
+
// Location regex pattern: _([A-Fa-f0-9]{6})
|
|
2232
|
+
this.LOCATION_REGEX = /_([A-Fa-f0-9]{6})/;
|
|
2233
|
+
}
|
|
2234
|
+
// Backward Compatibility Properties
|
|
2235
|
+
get AccumulatorFlags() {
|
|
2236
|
+
return this._stateManager.accumulatorFlags;
|
|
2237
|
+
}
|
|
2238
|
+
get IndexFlags() {
|
|
2239
|
+
return this._stateManager.indexFlags;
|
|
2240
|
+
}
|
|
2241
|
+
get BankNotes() {
|
|
2242
|
+
return this._stateManager.bankNotes;
|
|
2243
|
+
}
|
|
2244
|
+
get StackPosition() {
|
|
2245
|
+
return this._stateManager.stackPositions;
|
|
2246
|
+
}
|
|
2247
|
+
// Direct access to reference management collections
|
|
2248
|
+
get _structTable() {
|
|
2249
|
+
return this._referenceManager.structTable;
|
|
2250
|
+
}
|
|
2251
|
+
get _markerTable() {
|
|
2252
|
+
return this._referenceManager.markerTable;
|
|
2253
|
+
}
|
|
2254
|
+
get _nameTable() {
|
|
2255
|
+
return this._referenceManager.nameTable;
|
|
2256
|
+
}
|
|
2257
|
+
/**
|
|
2258
|
+
* Processes predefined overrides for registers and bank notes
|
|
2259
|
+
*/
|
|
2260
|
+
initializeOverrides() {
|
|
2261
|
+
for (const over of Object.values(this._root.overrides)) {
|
|
2262
|
+
switch (over.register) {
|
|
2263
|
+
case RegisterType.M:
|
|
2264
|
+
this._stateManager.setAccumulatorFlag(over.location, over.value === 1);
|
|
2265
|
+
break;
|
|
2266
|
+
case RegisterType.X:
|
|
2267
|
+
this._stateManager.setIndexFlag(over.location, over.value === 1);
|
|
2268
|
+
break;
|
|
2269
|
+
case RegisterType.B:
|
|
2270
|
+
this._stateManager.setBankNote(over.location, over.value);
|
|
2271
|
+
break;
|
|
2272
|
+
}
|
|
2273
|
+
}
|
|
2274
|
+
}
|
|
2275
|
+
/**
|
|
2276
|
+
* Processes predefined file references
|
|
2277
|
+
*/
|
|
2278
|
+
initializeFileReferences() {
|
|
2279
|
+
for (const file of this._root.files) {
|
|
2280
|
+
this._referenceManager.tryAddName(file.start, file.name);
|
|
2281
|
+
}
|
|
2282
|
+
}
|
|
2283
|
+
/**
|
|
2284
|
+
* Resolves mnemonic for a given address
|
|
2285
|
+
*/
|
|
2286
|
+
resolveMnemonic(addr) {
|
|
2287
|
+
if ((addr.bank & Address.DATA_BANK_FLAG) !== 0) {
|
|
2288
|
+
return;
|
|
2289
|
+
}
|
|
2290
|
+
let offset = addr.offset;
|
|
2291
|
+
if (offset === 1750) {
|
|
2292
|
+
console.log(this._root.mnemonics[offset]);
|
|
2293
|
+
}
|
|
2294
|
+
const label = this._root.mnemonics[offset];
|
|
2295
|
+
if (!label) {
|
|
2296
|
+
return;
|
|
2297
|
+
}
|
|
2298
|
+
const ix = indexOfAny(label, RomProcessingConstants.OPERATORS);
|
|
2299
|
+
if (ix >= 0) {
|
|
2300
|
+
let opnd = parseInt(label.substring(ix + 1), 16);
|
|
2301
|
+
const op = label[ix];
|
|
2302
|
+
if (op === "-")
|
|
2303
|
+
opnd = -opnd;
|
|
2304
|
+
offset -= opnd;
|
|
2305
|
+
}
|
|
2306
|
+
if (this._currentBlock.mnemonics) {
|
|
2307
|
+
this._currentBlock.mnemonics[offset] = label.substring(0, ix >= 0 ? ix : label.length);
|
|
2308
|
+
}
|
|
2309
|
+
}
|
|
2310
|
+
/**
|
|
2311
|
+
* Resolves name for a location (delegated to ReferenceManager)
|
|
2312
|
+
*/
|
|
2313
|
+
resolveName(location, type, isBranch) {
|
|
2314
|
+
return this._referenceManager.resolveName(location, type, isBranch);
|
|
2315
|
+
}
|
|
2316
|
+
/**
|
|
2317
|
+
* Resolves include for a location
|
|
2318
|
+
*/
|
|
2319
|
+
resolveInclude(loc, isBranch) {
|
|
2320
|
+
if (DbBlockUtils.isOutside(this._currentBlock, loc) && this._currentPart) {
|
|
2321
|
+
let foundPart = null;
|
|
2322
|
+
for (const block of this._root.blocks) {
|
|
2323
|
+
for (const part of block.parts) {
|
|
2324
|
+
if (loc >= part.start && loc < part.end) {
|
|
2325
|
+
foundPart = part;
|
|
2326
|
+
break;
|
|
2327
|
+
}
|
|
2328
|
+
}
|
|
2329
|
+
if (foundPart) break;
|
|
2330
|
+
}
|
|
2331
|
+
if (foundPart) {
|
|
2332
|
+
this._currentPart.includes = this._currentPart.includes || /* @__PURE__ */ new Set();
|
|
2333
|
+
this._currentPart.includes.add(foundPart);
|
|
2334
|
+
}
|
|
2335
|
+
} else if (isBranch && !this._referenceManager.tryGetName(loc).found) {
|
|
2336
|
+
const name = `loc_${loc.toString(16).toUpperCase().padStart(6, "0")}`;
|
|
2337
|
+
this._referenceManager.tryAddName(loc, name);
|
|
2338
|
+
}
|
|
2339
|
+
}
|
|
2340
|
+
/**
|
|
2341
|
+
* Notes a type at a location and manages chunk references
|
|
2342
|
+
*/
|
|
2343
|
+
noteType(loc, type, silent = false, reg) {
|
|
2344
|
+
this._referenceManager.tryAddStruct(loc, type);
|
|
2345
|
+
const nameResult = this._referenceManager.tryGetName(loc);
|
|
2346
|
+
let name;
|
|
2347
|
+
if (!nameResult.found) {
|
|
2348
|
+
name = this._referenceManager.createTypeName(type, loc);
|
|
2349
|
+
this._referenceManager.tryAddName(loc, name);
|
|
2350
|
+
} else {
|
|
2351
|
+
name = nameResult.referenceName;
|
|
2352
|
+
}
|
|
2353
|
+
if (!silent && type === BlockReaderConstants.CODE_TYPE && reg) {
|
|
2354
|
+
this.updateRegisterState(loc, reg);
|
|
2355
|
+
}
|
|
2356
|
+
return name;
|
|
2357
|
+
}
|
|
2358
|
+
updateRegisterState(loc, reg) {
|
|
2359
|
+
if (reg.accumulatorFlag !== void 0) {
|
|
2360
|
+
this._stateManager.tryAddAccumulatorFlag(loc, reg.accumulatorFlag);
|
|
2361
|
+
}
|
|
2362
|
+
if (reg.indexFlag !== void 0) {
|
|
2363
|
+
this._stateManager.tryAddIndexFlag(loc, reg.indexFlag);
|
|
2364
|
+
}
|
|
2365
|
+
if (reg.stack.location > 0) {
|
|
2366
|
+
this._stateManager.tryAddStackPosition(loc, reg.stack.location);
|
|
2367
|
+
}
|
|
2368
|
+
}
|
|
2369
|
+
/**
|
|
2370
|
+
* Checks if a delimiter has been reached
|
|
2371
|
+
*/
|
|
2372
|
+
delimiterReached(delimiter) {
|
|
2373
|
+
if (delimiter === void 0) {
|
|
2374
|
+
return false;
|
|
2375
|
+
}
|
|
2376
|
+
if (delimiter >= BlockReaderConstants.BYTE_DELIMITER_THRESHOLD) {
|
|
2377
|
+
if (this._romDataReader.peekShort() === delimiter) {
|
|
2378
|
+
this._romDataReader.position += 2;
|
|
2379
|
+
return true;
|
|
2380
|
+
}
|
|
2381
|
+
} else if (this._romDataReader.peekByte() === delimiter) {
|
|
2382
|
+
this._romDataReader.position++;
|
|
2383
|
+
return true;
|
|
2384
|
+
}
|
|
2385
|
+
return false;
|
|
2386
|
+
}
|
|
2387
|
+
/**
|
|
2388
|
+
* Checks if processing of the current part can continue
|
|
2389
|
+
*/
|
|
2390
|
+
partCanContinue() {
|
|
2391
|
+
return this._romDataReader.position < this._partEnd && !this._referenceManager.containsStruct(this._romDataReader.position);
|
|
2392
|
+
}
|
|
2393
|
+
/**
|
|
2394
|
+
* Main analysis entry point
|
|
2395
|
+
*/
|
|
2396
|
+
analyzeAndResolve() {
|
|
2397
|
+
this.analyzeBlocks();
|
|
2398
|
+
this.resolveReferences();
|
|
2399
|
+
}
|
|
2400
|
+
/**
|
|
2401
|
+
* Analyzes all blocks in the ROM
|
|
2402
|
+
*/
|
|
2403
|
+
analyzeBlocks() {
|
|
2404
|
+
this.initializeBlocksAndParts();
|
|
2405
|
+
for (const block of this._root.blocks) {
|
|
2406
|
+
this._currentBlock = block;
|
|
2407
|
+
for (const part of block.parts) {
|
|
2408
|
+
this.processPart(part);
|
|
2409
|
+
}
|
|
2410
|
+
}
|
|
2411
|
+
}
|
|
2412
|
+
/**
|
|
2413
|
+
* Initializes blocks and parts with base references
|
|
2414
|
+
*/
|
|
2415
|
+
initializeBlocksAndParts() {
|
|
2416
|
+
for (const block of this._root.blocks) {
|
|
2417
|
+
for (const part of block.parts) {
|
|
2418
|
+
part.includes = /* @__PURE__ */ new Set();
|
|
2419
|
+
this._referenceManager.tryAddStruct(part.start, part.struct);
|
|
2420
|
+
this._referenceManager.tryAddName(part.start, part.name);
|
|
2421
|
+
}
|
|
2422
|
+
}
|
|
2423
|
+
}
|
|
2424
|
+
/**
|
|
2425
|
+
* Processes a single part
|
|
2426
|
+
*/
|
|
2427
|
+
processPart(part) {
|
|
2428
|
+
this._currentPart = part;
|
|
2429
|
+
this._romDataReader.position = part.start;
|
|
2430
|
+
this._partEnd = part.end;
|
|
2431
|
+
let current = part.struct || BlockReaderConstants.BINARY_TYPE;
|
|
2432
|
+
const chunks = [];
|
|
2433
|
+
const reg = new Registers();
|
|
2434
|
+
const bank = part.bank;
|
|
2435
|
+
let last = null;
|
|
2436
|
+
while (this._romDataReader.position < this._partEnd) {
|
|
2437
|
+
const structResult = this._referenceManager.tryGetStruct(this._romDataReader.position);
|
|
2438
|
+
if (structResult.found) {
|
|
2439
|
+
current = structResult.chunkType;
|
|
2440
|
+
} else if (last !== null) {
|
|
2441
|
+
this.processContinuousEntry(current, reg, bank, last);
|
|
2442
|
+
continue;
|
|
2443
|
+
}
|
|
2444
|
+
last = createTableEntry(this._romDataReader.position);
|
|
2445
|
+
chunks.push(last);
|
|
2446
|
+
this.processNewEntry(current, reg, bank, last);
|
|
2447
|
+
}
|
|
2448
|
+
part.objectRoot = chunks;
|
|
2449
|
+
}
|
|
2450
|
+
/**
|
|
2451
|
+
* Processes a continuous entry (same type as previous)
|
|
2452
|
+
*/
|
|
2453
|
+
processContinuousEntry(current, reg, bank, last) {
|
|
2454
|
+
const obj = this._typeParser.parseType(current, reg, 0, bank);
|
|
2455
|
+
if (!Array.isArray(last.object)) {
|
|
2456
|
+
last.object = [last.object];
|
|
2457
|
+
}
|
|
2458
|
+
last.object.push(obj);
|
|
2459
|
+
}
|
|
2460
|
+
/**
|
|
2461
|
+
* Processes a new entry
|
|
2462
|
+
*/
|
|
2463
|
+
processNewEntry(current, reg, bank, last) {
|
|
2464
|
+
let res = this._typeParser.parseType(current, reg, 0, bank);
|
|
2465
|
+
if (BlockReaderConstants.POINTER_CHARACTERS.includes(current[0]) && !Array.isArray(res)) {
|
|
2466
|
+
res = [res];
|
|
2467
|
+
}
|
|
2468
|
+
last.object = res;
|
|
2469
|
+
}
|
|
2470
|
+
/**
|
|
2471
|
+
* Resolves all references after analysis
|
|
2472
|
+
*/
|
|
2473
|
+
resolveReferences() {
|
|
2474
|
+
for (const block of this._root.blocks) {
|
|
2475
|
+
this._currentBlock = block;
|
|
2476
|
+
for (const part of block.parts) {
|
|
2477
|
+
this._currentPart = part;
|
|
2478
|
+
this.resolveObject(part.objectRoot, false);
|
|
2479
|
+
}
|
|
2480
|
+
}
|
|
2481
|
+
}
|
|
2482
|
+
/**
|
|
2483
|
+
* Resolves a single object and its references
|
|
2484
|
+
*/
|
|
2485
|
+
resolveObject(obj, isBranch) {
|
|
2486
|
+
if (typeof obj === "string") {
|
|
2487
|
+
return;
|
|
2488
|
+
}
|
|
2489
|
+
if (Array.isArray(obj)) {
|
|
2490
|
+
for (const o of obj) {
|
|
2491
|
+
this.resolveObject(o, isBranch);
|
|
2492
|
+
}
|
|
2493
|
+
return;
|
|
2494
|
+
}
|
|
2495
|
+
if (obj instanceof Address) {
|
|
2496
|
+
this.resolveMnemonic(obj);
|
|
2497
|
+
return;
|
|
2498
|
+
}
|
|
2499
|
+
if (typeof obj === "number") {
|
|
2500
|
+
this.resolveInclude(obj, isBranch);
|
|
2501
|
+
return;
|
|
2502
|
+
}
|
|
2503
|
+
if (obj instanceof LocationWrapper) {
|
|
2504
|
+
this.resolveInclude(obj.location, isBranch);
|
|
2505
|
+
return;
|
|
2506
|
+
}
|
|
2507
|
+
if (obj && typeof obj === "object") {
|
|
2508
|
+
if ("string" in obj && "type" in obj) {
|
|
2509
|
+
this._stringReader.resolveString(obj, isBranch);
|
|
2510
|
+
return;
|
|
2511
|
+
}
|
|
2512
|
+
if ("name" in obj && "parts" in obj) {
|
|
2513
|
+
this.resolveObject(obj.parts, isBranch);
|
|
2514
|
+
return;
|
|
2515
|
+
}
|
|
2516
|
+
if ("location" in obj && "object" in obj) {
|
|
2517
|
+
this.resolveObject(obj.object, isBranch);
|
|
2518
|
+
return;
|
|
2519
|
+
}
|
|
2520
|
+
if ("code" in obj && "operands" in obj) {
|
|
2521
|
+
this.resolveOperationObject(obj);
|
|
2522
|
+
return;
|
|
2523
|
+
}
|
|
2524
|
+
}
|
|
2525
|
+
}
|
|
2526
|
+
/**
|
|
2527
|
+
* Resolves references in an operation object
|
|
2528
|
+
*/
|
|
2529
|
+
resolveOperationObject(op) {
|
|
2530
|
+
const branch = this.isBranchOperation(op);
|
|
2531
|
+
for (const opnd of op.operands) {
|
|
2532
|
+
this.resolveObject(opnd, branch);
|
|
2533
|
+
}
|
|
2534
|
+
}
|
|
2535
|
+
/**
|
|
2536
|
+
* Checks if an operation is a branch operation
|
|
2537
|
+
*/
|
|
2538
|
+
isBranchOperation(op) {
|
|
2539
|
+
return op.code.mode === AddressingMode.PCRelative || op.code.mode === AddressingMode.PCRelativeLong || op.code.mnem[0] === "J";
|
|
2540
|
+
}
|
|
2541
|
+
/**
|
|
2542
|
+
* Hydrates registers with stored state
|
|
2543
|
+
*/
|
|
2544
|
+
hydrateRegisters(reg) {
|
|
2545
|
+
this._stateManager.hydrateRegisters(this._romDataReader.position, reg);
|
|
2546
|
+
}
|
|
2547
|
+
/**
|
|
2548
|
+
* PascalCase wrapper for hydrateRegisters (for C# compatibility)
|
|
2549
|
+
*/
|
|
2550
|
+
HydrateRegisters(reg) {
|
|
2551
|
+
this.hydrateRegisters(reg);
|
|
2552
|
+
}
|
|
2553
|
+
};
|
|
2554
|
+
var PostProcessor = class {
|
|
2555
|
+
constructor(reader) {
|
|
2556
|
+
this._referenceManager = reader._referenceManager;
|
|
2557
|
+
}
|
|
2558
|
+
/**
|
|
2559
|
+
* Execute post process directive on a block if present.
|
|
2560
|
+
*/
|
|
2561
|
+
process(block) {
|
|
2562
|
+
if (!block.postProcess || block.postProcess.trim() === "") {
|
|
2563
|
+
return;
|
|
2564
|
+
}
|
|
2565
|
+
let signature = block.postProcess;
|
|
2566
|
+
const parts = [];
|
|
2567
|
+
const index = signature.indexOf("(");
|
|
2568
|
+
if (index > 0) {
|
|
2569
|
+
const endIx = signature.indexOf(")", index);
|
|
2570
|
+
const params = signature.substring(
|
|
2571
|
+
index + 1,
|
|
2572
|
+
endIx >= 0 ? endIx : signature.length
|
|
2573
|
+
);
|
|
2574
|
+
if (params.length > 0) {
|
|
2575
|
+
for (const p of params.split(",")) {
|
|
2576
|
+
parts.push(p.trim());
|
|
2577
|
+
}
|
|
2578
|
+
}
|
|
2579
|
+
signature = signature.substring(0, index);
|
|
2580
|
+
}
|
|
2581
|
+
const fn = this[signature];
|
|
2582
|
+
if (typeof fn !== "function") {
|
|
2583
|
+
throw new Error(`Unable to locate postprocess function ${signature}`);
|
|
2584
|
+
}
|
|
2585
|
+
fn.apply(this, [block, ...parts]);
|
|
2586
|
+
}
|
|
2587
|
+
/**
|
|
2588
|
+
* Builds a lookup table from struct entries.
|
|
2589
|
+
* Equivalent to PostProcessor.Lookup in C# implementation.
|
|
2590
|
+
*/
|
|
2591
|
+
Lookup(block, keyIx, valueIx) {
|
|
2592
|
+
const kix = parseInt(keyIx.trim());
|
|
2593
|
+
const vix = parseInt(valueIx.trim());
|
|
2594
|
+
const table = block.parts[0].objectRoot;
|
|
2595
|
+
const tableEntry = table && table[0];
|
|
2596
|
+
const entries = tableEntry?.object;
|
|
2597
|
+
if (!tableEntry || !entries) {
|
|
2598
|
+
throw new Error("Invalid table structure for Lookup post process");
|
|
2599
|
+
}
|
|
2600
|
+
const newParts = [];
|
|
2601
|
+
const newList = [];
|
|
2602
|
+
newParts.push({ location: tableEntry.location, object: newList });
|
|
2603
|
+
let eIx = 1;
|
|
2604
|
+
for (const entry of entries) {
|
|
2605
|
+
if (!entry || !Array.isArray(entry.parts)) {
|
|
2606
|
+
continue;
|
|
2607
|
+
}
|
|
2608
|
+
const struct = entry;
|
|
2609
|
+
let cIx = 0;
|
|
2610
|
+
let key = null;
|
|
2611
|
+
let value = null;
|
|
2612
|
+
for (const obj of struct.parts) {
|
|
2613
|
+
if (cIx === kix) {
|
|
2614
|
+
if (obj && typeof obj === "object" && "value" in obj) {
|
|
2615
|
+
key = obj.value;
|
|
2616
|
+
} else {
|
|
2617
|
+
key = obj;
|
|
2618
|
+
}
|
|
2619
|
+
} else if (cIx === vix) {
|
|
2620
|
+
value = obj;
|
|
2621
|
+
}
|
|
2622
|
+
cIx++;
|
|
2623
|
+
}
|
|
2624
|
+
if (key === null || value === null) {
|
|
2625
|
+
throw new Error("Could not locate key or value for transform");
|
|
2626
|
+
}
|
|
2627
|
+
const name = `entry_${key.toString(16).toUpperCase().padStart(2, "0")}`;
|
|
2628
|
+
const loc = tableEntry.location + eIx;
|
|
2629
|
+
newParts.push({ location: loc, object: value });
|
|
2630
|
+
this._referenceManager.nameTable.set(loc, name);
|
|
2631
|
+
while (newList.length <= key) {
|
|
2632
|
+
newList.push(createWord(0));
|
|
2633
|
+
}
|
|
2634
|
+
newList[key] = `&${name}`;
|
|
2635
|
+
eIx++;
|
|
2636
|
+
}
|
|
2637
|
+
block.parts[0].objectRoot = newParts;
|
|
2638
|
+
}
|
|
2639
|
+
};
|
|
2640
|
+
|
|
2641
|
+
// src/rom/extraction/writer.ts
|
|
2642
|
+
var ObjectType = /* @__PURE__ */ ((ObjectType2) => {
|
|
2643
|
+
ObjectType2["TableEntryArray"] = "TableEntryArray";
|
|
2644
|
+
ObjectType2["StructDef"] = "StructDef";
|
|
2645
|
+
ObjectType2["OpArray"] = "OpArray";
|
|
2646
|
+
ObjectType2["LocationWrapper"] = "LocationWrapper";
|
|
2647
|
+
ObjectType2["Address"] = "Address";
|
|
2648
|
+
ObjectType2["StringWrapper"] = "StringWrapper";
|
|
2649
|
+
ObjectType2["ByteArray"] = "ByteArray";
|
|
2650
|
+
ObjectType2["Array"] = "Array";
|
|
2651
|
+
ObjectType2["String"] = "String";
|
|
2652
|
+
ObjectType2["Number"] = "Number";
|
|
2653
|
+
ObjectType2["TypedNumber"] = "TypedNumber";
|
|
2654
|
+
return ObjectType2;
|
|
2655
|
+
})(ObjectType || {});
|
|
2656
|
+
var BlockWriter = class {
|
|
2657
|
+
constructor(reader) {
|
|
2658
|
+
this._isInline = false;
|
|
2659
|
+
this._currentPart = null;
|
|
2660
|
+
this._blockReader = reader;
|
|
2661
|
+
this._root = reader._root;
|
|
2662
|
+
this._referenceManager = reader._referenceManager;
|
|
2663
|
+
this._postProcessor = new PostProcessor(reader);
|
|
2664
|
+
}
|
|
2665
|
+
async writeBlocks(outPath) {
|
|
2666
|
+
const res = DbRootUtils.getPath(this._root, BinType.Assembly);
|
|
2667
|
+
const folderPath = join(outPath, res.folder);
|
|
2668
|
+
for (const block of this._root.blocks) {
|
|
2669
|
+
const groupedFolderPath = block.group ? join(folderPath, block.group) : folderPath;
|
|
2670
|
+
await promises.mkdir(groupedFolderPath, { recursive: true });
|
|
2671
|
+
const outFile = join(groupedFolderPath, `${block.name}.${res.extension}`);
|
|
2672
|
+
try {
|
|
2673
|
+
await promises.access(outFile);
|
|
2674
|
+
continue;
|
|
2675
|
+
} catch {
|
|
2676
|
+
}
|
|
2677
|
+
const content = this.generateAsm(block);
|
|
2678
|
+
await promises.writeFile(outFile, content);
|
|
2679
|
+
}
|
|
2680
|
+
}
|
|
2681
|
+
generateAsm(block) {
|
|
2682
|
+
const lines = [];
|
|
2683
|
+
if (!block.movable && block.parts.length > 0) {
|
|
2684
|
+
lines.push(`?BANK ${(block.parts[0].start >> 16).toString(16).toUpperCase().padStart(2, "0")}`);
|
|
2685
|
+
}
|
|
2686
|
+
const includes = DbBlockUtils.getIncludes(block);
|
|
2687
|
+
if (includes && includes.length > 0) {
|
|
2688
|
+
lines.push("");
|
|
2689
|
+
for (const inc of includes) {
|
|
2690
|
+
lines.push(`?INCLUDE '${inc.name}'`);
|
|
2691
|
+
}
|
|
2692
|
+
}
|
|
2693
|
+
const mnemonics = this.getMnemonicsForBlock(block);
|
|
2694
|
+
if (mnemonics && mnemonics.length > 0) {
|
|
2695
|
+
lines.push("");
|
|
2696
|
+
for (const [name, address] of mnemonics) {
|
|
2697
|
+
const paddedName = name.padEnd(30, " ");
|
|
2698
|
+
lines.push(`!${paddedName} ${address.toString(16).toUpperCase().padStart(4, "0")}`);
|
|
2699
|
+
}
|
|
2700
|
+
}
|
|
2701
|
+
this._postProcessor.process(block);
|
|
2702
|
+
for (const part of block.parts) {
|
|
2703
|
+
this._currentPart = part;
|
|
2704
|
+
this._isInline = true;
|
|
2705
|
+
lines.push("");
|
|
2706
|
+
lines.push("---------------------------------------------");
|
|
2707
|
+
const objectLines = this.writeObject(part.objectRoot, -1);
|
|
2708
|
+
lines.push(...objectLines);
|
|
2709
|
+
}
|
|
2710
|
+
let content = lines.join("\r\n");
|
|
2711
|
+
if (block.transforms) {
|
|
2712
|
+
for (const x of block.transforms) {
|
|
2713
|
+
if (x.key && x.value) {
|
|
2714
|
+
const regex = new RegExp(x.key, "g");
|
|
2715
|
+
content = content.replace(regex, x.value);
|
|
2716
|
+
}
|
|
2717
|
+
}
|
|
2718
|
+
}
|
|
2719
|
+
return content;
|
|
2720
|
+
}
|
|
2721
|
+
getMnemonicsForBlock(block) {
|
|
2722
|
+
if (!block.mnemonics) {
|
|
2723
|
+
return [];
|
|
2724
|
+
}
|
|
2725
|
+
return Object.entries(block.mnemonics).map(([k, v]) => [v, parseInt(k, 10)]).sort((a, b) => a[1] - b[1]);
|
|
2726
|
+
}
|
|
2727
|
+
resolveOperand(op, obj, isBranch = false) {
|
|
2728
|
+
this.getObjectType(obj);
|
|
2729
|
+
if (typeof obj === "number") {
|
|
2730
|
+
if (op.size === 3) ;
|
|
2731
|
+
if (op.code.mode === AddressingMode.Immediate) {
|
|
2732
|
+
return obj;
|
|
2733
|
+
}
|
|
2734
|
+
return this._blockReader.resolveName(obj, AddressType.Address, isBranch);
|
|
2735
|
+
}
|
|
2736
|
+
if (this.getObjectType(obj) === "LocationWrapper" /* LocationWrapper */) {
|
|
2737
|
+
const lw = obj;
|
|
2738
|
+
return this._blockReader.resolveName(lw.location, lw.type, isBranch);
|
|
2739
|
+
}
|
|
2740
|
+
if (this.getObjectType(obj) === "Address" /* Address */) {
|
|
2741
|
+
const addr = obj;
|
|
2742
|
+
if (op.size === 4) {
|
|
2743
|
+
return addr;
|
|
2744
|
+
}
|
|
2745
|
+
if (addr.isCodeBank && addr.offset < Address.UPPER_BANK) {
|
|
2746
|
+
const label = this._root.mnemonics[addr.offset];
|
|
2747
|
+
if (label) {
|
|
2748
|
+
return label;
|
|
2749
|
+
}
|
|
2750
|
+
}
|
|
2751
|
+
return addr.offset;
|
|
2752
|
+
}
|
|
2753
|
+
if (obj && typeof obj === "object" && obj._tag && "value" in obj) {
|
|
2754
|
+
const typed = obj;
|
|
2755
|
+
if (typed._tag === "Byte" || typed._tag === "Word" || typed._tag === "TypedNumber") {
|
|
2756
|
+
return obj;
|
|
2757
|
+
}
|
|
2758
|
+
if (typeof typed.value === "number" && op.code.mode !== AddressingMode.Immediate) {
|
|
2759
|
+
const resolved = this._blockReader.resolveName(typed.value, AddressType.Address, isBranch);
|
|
2760
|
+
return { ...typed, value: resolved };
|
|
2761
|
+
}
|
|
2762
|
+
return obj;
|
|
2763
|
+
}
|
|
2764
|
+
return obj;
|
|
2765
|
+
}
|
|
2766
|
+
getObjectType(obj) {
|
|
2767
|
+
if (obj === null || obj === void 0) {
|
|
2768
|
+
return "String" /* String */;
|
|
2769
|
+
}
|
|
2770
|
+
if (obj._tag) {
|
|
2771
|
+
if (obj._tag === "Byte" || obj._tag === "Word") {
|
|
2772
|
+
return "TypedNumber" /* TypedNumber */;
|
|
2773
|
+
}
|
|
2774
|
+
return obj._tag;
|
|
2775
|
+
}
|
|
2776
|
+
if (Array.isArray(obj)) {
|
|
2777
|
+
if (obj.length > 0) {
|
|
2778
|
+
if (obj[0] && typeof obj[0] === "object" && "location" in obj[0] && "object" in obj[0]) {
|
|
2779
|
+
return "TableEntryArray" /* TableEntryArray */;
|
|
2780
|
+
}
|
|
2781
|
+
if (obj[0] instanceof Op) {
|
|
2782
|
+
return "OpArray" /* OpArray */;
|
|
2783
|
+
}
|
|
2784
|
+
}
|
|
2785
|
+
return "Array" /* Array */;
|
|
2786
|
+
}
|
|
2787
|
+
if (obj instanceof Op) {
|
|
2788
|
+
return "OpArray" /* OpArray */;
|
|
2789
|
+
}
|
|
2790
|
+
if (obj instanceof LocationWrapper) {
|
|
2791
|
+
return "LocationWrapper" /* LocationWrapper */;
|
|
2792
|
+
}
|
|
2793
|
+
if (obj instanceof Address) {
|
|
2794
|
+
return "Address" /* Address */;
|
|
2795
|
+
}
|
|
2796
|
+
if (obj instanceof Uint8Array) {
|
|
2797
|
+
return "ByteArray" /* ByteArray */;
|
|
2798
|
+
}
|
|
2799
|
+
if (obj && typeof obj === "object") {
|
|
2800
|
+
if ("string" in obj && "type" in obj && "marker" in obj && "location" in obj) {
|
|
2801
|
+
return "StringWrapper" /* StringWrapper */;
|
|
2802
|
+
}
|
|
2803
|
+
if ("name" in obj && "parts" in obj) {
|
|
2804
|
+
return "StructDef" /* StructDef */;
|
|
2805
|
+
}
|
|
2806
|
+
}
|
|
2807
|
+
if (typeof obj === "string") {
|
|
2808
|
+
return "String" /* String */;
|
|
2809
|
+
}
|
|
2810
|
+
if (typeof obj === "number") {
|
|
2811
|
+
return "Number" /* Number */;
|
|
2812
|
+
}
|
|
2813
|
+
return "String" /* String */;
|
|
2814
|
+
}
|
|
2815
|
+
writeObject(obj, depth, isBranch = false) {
|
|
2816
|
+
const lines = [];
|
|
2817
|
+
const objType = this.getObjectType(obj);
|
|
2818
|
+
let objLines;
|
|
2819
|
+
switch (objType) {
|
|
2820
|
+
case "TableEntryArray" /* TableEntryArray */:
|
|
2821
|
+
objLines = this.writeTableEntryArray(obj, depth);
|
|
2822
|
+
break;
|
|
2823
|
+
case "StructDef" /* StructDef */:
|
|
2824
|
+
objLines = this.writeStructDef(obj, depth);
|
|
2825
|
+
break;
|
|
2826
|
+
case "OpArray" /* OpArray */:
|
|
2827
|
+
objLines = this.writeOpArray(obj, depth);
|
|
2828
|
+
break;
|
|
2829
|
+
case "LocationWrapper" /* LocationWrapper */:
|
|
2830
|
+
objLines = [
|
|
2831
|
+
this._blockReader.resolveName(
|
|
2832
|
+
obj.location,
|
|
2833
|
+
obj.type,
|
|
2834
|
+
isBranch
|
|
2835
|
+
)
|
|
2836
|
+
];
|
|
2837
|
+
break;
|
|
2838
|
+
case "Address" /* Address */:
|
|
2839
|
+
objLines = [`$${obj.toString()}`];
|
|
2840
|
+
break;
|
|
2841
|
+
case "ByteArray" /* ByteArray */:
|
|
2842
|
+
objLines = [
|
|
2843
|
+
`#${Array.from(obj).map((b) => b.toString(16).toUpperCase().padStart(2, "0")).join("")}`
|
|
2844
|
+
];
|
|
2845
|
+
break;
|
|
2846
|
+
case "StringWrapper" /* StringWrapper */:
|
|
2847
|
+
objLines = this.writeStringWrapper(obj);
|
|
2848
|
+
break;
|
|
2849
|
+
case "Array" /* Array */:
|
|
2850
|
+
objLines = this.writeArray(obj, depth);
|
|
2851
|
+
break;
|
|
2852
|
+
case "Number" /* Number */:
|
|
2853
|
+
objLines = this.writeNumber(obj);
|
|
2854
|
+
break;
|
|
2855
|
+
case "TypedNumber" /* TypedNumber */:
|
|
2856
|
+
objLines = this.writeTypedNumber(obj);
|
|
2857
|
+
break;
|
|
2858
|
+
case "String" /* String */:
|
|
2859
|
+
objLines = [String(obj)];
|
|
2860
|
+
break;
|
|
2861
|
+
default:
|
|
2862
|
+
objLines = [String(obj)];
|
|
2863
|
+
break;
|
|
2864
|
+
}
|
|
2865
|
+
lines.push(...objLines);
|
|
2866
|
+
return lines;
|
|
2867
|
+
}
|
|
2868
|
+
writeTableEntryArray(tGroup, depth) {
|
|
2869
|
+
const lines = [];
|
|
2870
|
+
const isInline = this._isInline;
|
|
2871
|
+
for (const t of tGroup) {
|
|
2872
|
+
const nameResult = this._referenceManager.tryGetName(t.location);
|
|
2873
|
+
const name = nameResult.found ? nameResult.referenceName : `loc_${t.location.toString(16).toUpperCase().padStart(6, "0")}`;
|
|
2874
|
+
const objectLines = this.writeObject(t.object, depth + 1);
|
|
2875
|
+
lines.push("");
|
|
2876
|
+
lines.push(`${name} ${objectLines[0]}`);
|
|
2877
|
+
if (objectLines.length > 1) {
|
|
2878
|
+
lines.push(...objectLines.slice(1));
|
|
2879
|
+
}
|
|
2880
|
+
}
|
|
2881
|
+
this._isInline = isInline;
|
|
2882
|
+
return lines;
|
|
2883
|
+
}
|
|
2884
|
+
writeStructDef(structObj, depth) {
|
|
2885
|
+
const parts = [];
|
|
2886
|
+
const isInline = this._isInline;
|
|
2887
|
+
this._isInline = true;
|
|
2888
|
+
for (const part of structObj.parts) {
|
|
2889
|
+
const partLines = this.writeObject(part, depth);
|
|
2890
|
+
parts.push(partLines.join("\r\n"));
|
|
2891
|
+
}
|
|
2892
|
+
const line = `${structObj.name} < ${parts.join(", ")} >`;
|
|
2893
|
+
this._isInline = isInline;
|
|
2894
|
+
return [line];
|
|
2895
|
+
}
|
|
2896
|
+
writeOpArray(opList, depth) {
|
|
2897
|
+
const lines = [];
|
|
2898
|
+
lines.push("{");
|
|
2899
|
+
const isInline = this._isInline;
|
|
2900
|
+
this._isInline = true;
|
|
2901
|
+
let first = true;
|
|
2902
|
+
for (const op of opList) {
|
|
2903
|
+
if (first) {
|
|
2904
|
+
first = false;
|
|
2905
|
+
} else {
|
|
2906
|
+
const labelResult = this._referenceManager.tryGetName(op.location);
|
|
2907
|
+
if (labelResult.found) {
|
|
2908
|
+
lines.push("");
|
|
2909
|
+
lines.push(` ${labelResult.referenceName}:`);
|
|
2910
|
+
}
|
|
2911
|
+
}
|
|
2912
|
+
let opLine = ` ${op.code.mnem} `;
|
|
2913
|
+
if (op.copDef) {
|
|
2914
|
+
opLine += `[${op.copDef.mnem}]`;
|
|
2915
|
+
if (op.operands && op.operands.length > 1) {
|
|
2916
|
+
const operandStrings = [];
|
|
2917
|
+
for (let i = 1; i < op.operands.length; i++) {
|
|
2918
|
+
const operandLines = this.writeObject(op.operands[i], depth + 1, false);
|
|
2919
|
+
operandStrings.push(operandLines[0]);
|
|
2920
|
+
}
|
|
2921
|
+
opLine += ` ( ${operandStrings.join(", ")} )`;
|
|
2922
|
+
}
|
|
2923
|
+
} else if (op.operands && op.operands.length > 0) {
|
|
2924
|
+
if (op.code.mnem === "COP") {
|
|
2925
|
+
const operand = op.operands[0];
|
|
2926
|
+
if (typeof operand === "number") {
|
|
2927
|
+
opLine += `[${operand.toString(16).toUpperCase().padStart(2, "0")}]`;
|
|
2928
|
+
} else {
|
|
2929
|
+
opLine += `[${operand}]`;
|
|
2930
|
+
}
|
|
2931
|
+
} else {
|
|
2932
|
+
const isBr = op.code.mnem[0] === "J" || op.code.mode === AddressingMode.PCRelative || op.code.mode === AddressingMode.PCRelativeLong;
|
|
2933
|
+
const resolvedOperand = this.resolveOperand(op, op.operands[0], isBr);
|
|
2934
|
+
const format = this._root.config.asmFormats?.[op.code.mode];
|
|
2935
|
+
if (format) {
|
|
2936
|
+
let actualFormat = format;
|
|
2937
|
+
if (op.code.mode === AddressingMode.Immediate && op.size === 3) {
|
|
2938
|
+
actualFormat = format.replace("X2", "X4");
|
|
2939
|
+
}
|
|
2940
|
+
opLine += this.formatOperand(actualFormat, [resolvedOperand, ...op.operands.slice(1)]);
|
|
2941
|
+
} else {
|
|
2942
|
+
opLine += this.formatDefaultOperand(resolvedOperand, op.size);
|
|
2943
|
+
}
|
|
2944
|
+
}
|
|
2945
|
+
}
|
|
2946
|
+
lines.push(opLine);
|
|
2947
|
+
}
|
|
2948
|
+
lines.push("}");
|
|
2949
|
+
this._isInline = isInline;
|
|
2950
|
+
return lines;
|
|
2951
|
+
}
|
|
2952
|
+
formatDefaultOperand(operand, size) {
|
|
2953
|
+
if (typeof operand === "string") {
|
|
2954
|
+
return operand;
|
|
2955
|
+
}
|
|
2956
|
+
if (operand && typeof operand === "object" && operand._tag && "value" in operand) {
|
|
2957
|
+
return this.formatTypedNumber(operand);
|
|
2958
|
+
}
|
|
2959
|
+
if (typeof operand === "number") {
|
|
2960
|
+
const hexSize = (size - 1) * 2;
|
|
2961
|
+
const hex = operand.toString(16).toUpperCase().padStart(hexSize, "0");
|
|
2962
|
+
return `$${hex}`;
|
|
2963
|
+
}
|
|
2964
|
+
return String(operand);
|
|
2965
|
+
}
|
|
2966
|
+
writeStringWrapper(stringObj) {
|
|
2967
|
+
let str = stringObj.string;
|
|
2968
|
+
const stringReferenceChars = ["~", "^"];
|
|
2969
|
+
for (const char of stringReferenceChars) {
|
|
2970
|
+
let ix = str.indexOf(char);
|
|
2971
|
+
while (ix >= 0) {
|
|
2972
|
+
const hexStr = str.substring(ix + 1, ix + 7);
|
|
2973
|
+
const rawAddr = parseInt(hexStr, 16);
|
|
2974
|
+
const adrs = new Address(rawAddr >> 16 & 255, rawAddr & 65535);
|
|
2975
|
+
if (adrs.space === AddressSpace.ROM) {
|
|
2976
|
+
const addressType = char === "^" ? AddressType.Offset : AddressType.Address;
|
|
2977
|
+
const location = adrs.toInt();
|
|
2978
|
+
const name = this._blockReader.resolveName(location, addressType, false);
|
|
2979
|
+
str = str.replace(str.substring(ix, ix + 7), name);
|
|
2980
|
+
} else {
|
|
2981
|
+
throw new Error("Unsupported address space");
|
|
2982
|
+
}
|
|
2983
|
+
ix = str.indexOf(char, ix + 7);
|
|
2984
|
+
}
|
|
2985
|
+
}
|
|
2986
|
+
const refChar = stringObj.type.delimiter;
|
|
2987
|
+
let marker = stringObj.marker;
|
|
2988
|
+
if (marker <= 0) {
|
|
2989
|
+
const markerResult = this._referenceManager.tryGetMarker(stringObj.location);
|
|
2990
|
+
marker = markerResult.found ? markerResult.offset || 0 : 0;
|
|
2991
|
+
}
|
|
2992
|
+
if (marker > 0) {
|
|
2993
|
+
let six = 0;
|
|
2994
|
+
let mix = 0;
|
|
2995
|
+
while (mix < marker) {
|
|
2996
|
+
if (str[six] === "[") {
|
|
2997
|
+
const eix = str.indexOf("]", ++six);
|
|
2998
|
+
const parts = str.substring(six, eix).split(/[,: ]/);
|
|
2999
|
+
const cmd = Object.values(stringObj.type.commands).find((x) => x.value === parts[0]);
|
|
3000
|
+
if (cmd && cmd.types) {
|
|
3001
|
+
for (const t of cmd.types) {
|
|
3002
|
+
switch (t) {
|
|
3003
|
+
case MemberType.Byte:
|
|
3004
|
+
mix += 1;
|
|
3005
|
+
break;
|
|
3006
|
+
case MemberType.Word:
|
|
3007
|
+
case MemberType.Offset:
|
|
3008
|
+
mix += 2;
|
|
3009
|
+
break;
|
|
3010
|
+
case MemberType.Address:
|
|
3011
|
+
mix += 3;
|
|
3012
|
+
break;
|
|
3013
|
+
case MemberType.Binary:
|
|
3014
|
+
mix += parts.length - 1;
|
|
3015
|
+
break;
|
|
3016
|
+
default:
|
|
3017
|
+
throw new Error("Unsupported member type");
|
|
3018
|
+
}
|
|
3019
|
+
}
|
|
3020
|
+
}
|
|
3021
|
+
six = eix + 1;
|
|
3022
|
+
mix++;
|
|
3023
|
+
} else {
|
|
3024
|
+
six++;
|
|
3025
|
+
mix++;
|
|
3026
|
+
}
|
|
3027
|
+
}
|
|
3028
|
+
str = str.substring(0, six) + "[::]" + str.substring(six);
|
|
3029
|
+
}
|
|
3030
|
+
return [`${refChar}${str}${refChar}`];
|
|
3031
|
+
}
|
|
3032
|
+
writeArray(arr, depth) {
|
|
3033
|
+
const lines = [];
|
|
3034
|
+
const indent = " ".repeat(depth);
|
|
3035
|
+
const isInline = this._isInline;
|
|
3036
|
+
lines.push("[");
|
|
3037
|
+
this._isInline = false;
|
|
3038
|
+
for (let i = 0; i < arr.length; i++) {
|
|
3039
|
+
const objLines = this.writeObject(arr[i], depth + 1);
|
|
3040
|
+
for (const line of objLines) {
|
|
3041
|
+
lines.push(indent + " " + line);
|
|
3042
|
+
}
|
|
3043
|
+
lines[lines.length - 1] += ` ;${i.toString(16).toUpperCase().padStart(2, "0")}`;
|
|
3044
|
+
}
|
|
3045
|
+
lines.push(indent + "]");
|
|
3046
|
+
this._isInline = isInline;
|
|
3047
|
+
return lines;
|
|
3048
|
+
}
|
|
3049
|
+
writeNumber(num) {
|
|
3050
|
+
if (num <= 255) {
|
|
3051
|
+
return [`#${num.toString(16).toUpperCase().padStart(2, "0")}`];
|
|
3052
|
+
}
|
|
3053
|
+
if (num <= 65535) {
|
|
3054
|
+
return [`#$${num.toString(16).toUpperCase().padStart(4, "0")}`];
|
|
3055
|
+
}
|
|
3056
|
+
return [`#$${num.toString(16).toUpperCase().padStart(6, "0")}`];
|
|
3057
|
+
}
|
|
3058
|
+
formatTypedNumber(num) {
|
|
3059
|
+
let size = 1;
|
|
3060
|
+
if ("size" in num) {
|
|
3061
|
+
size = num.size;
|
|
3062
|
+
} else if (num._tag === "Word") {
|
|
3063
|
+
size = 2;
|
|
3064
|
+
}
|
|
3065
|
+
if (typeof num.value === "number") {
|
|
3066
|
+
const width = size * 2;
|
|
3067
|
+
const hex = num.value.toString(16).toUpperCase().padStart(width, "0");
|
|
3068
|
+
return size === 1 ? `#${hex}` : `#$${hex}`;
|
|
3069
|
+
}
|
|
3070
|
+
return `#${num.value}`;
|
|
3071
|
+
}
|
|
3072
|
+
writeTypedNumber(num) {
|
|
3073
|
+
return [this.formatTypedNumber(num)];
|
|
3074
|
+
}
|
|
3075
|
+
formatOperand(format, operands) {
|
|
3076
|
+
return format.replace(/\{(\d+)(?::([^}]+))?\}/g, (match, index, formatSpec) => {
|
|
3077
|
+
const operand = operands[parseInt(index)];
|
|
3078
|
+
if (operand && typeof operand === "object" && operand._tag && "value" in operand) {
|
|
3079
|
+
const value = operand.value;
|
|
3080
|
+
if (formatSpec && typeof value === "number" && formatSpec.startsWith("X")) {
|
|
3081
|
+
const width = parseInt(formatSpec.substring(1)) || 2;
|
|
3082
|
+
return value.toString(16).toUpperCase().padStart(width, "0");
|
|
3083
|
+
}
|
|
3084
|
+
return String(value);
|
|
3085
|
+
}
|
|
3086
|
+
if (formatSpec && typeof operand === "number") {
|
|
3087
|
+
if (formatSpec.startsWith("X")) {
|
|
3088
|
+
const width = parseInt(formatSpec.substring(1)) || 2;
|
|
3089
|
+
return operand.toString(16).toUpperCase().padStart(width, "0");
|
|
3090
|
+
}
|
|
3091
|
+
}
|
|
3092
|
+
return String(operand);
|
|
3093
|
+
});
|
|
3094
|
+
}
|
|
3095
|
+
};
|
|
3096
|
+
|
|
3097
|
+
// src/rom/project.ts
|
|
3098
|
+
var ProjectRoot = class _ProjectRoot {
|
|
3099
|
+
constructor(config) {
|
|
3100
|
+
this.config = config;
|
|
3101
|
+
this.name = config.name;
|
|
3102
|
+
this.romPath = config.romPath;
|
|
3103
|
+
this.baseDir = config.baseDir;
|
|
3104
|
+
this.system = config.system;
|
|
3105
|
+
this.database = config.database;
|
|
3106
|
+
this.flipsPath = config.flipsPath;
|
|
3107
|
+
this.resources = config.resources;
|
|
3108
|
+
this.databasePath = config.databasePath;
|
|
3109
|
+
this.systemPath = config.systemPath;
|
|
3110
|
+
this.compression = config.compression;
|
|
3111
|
+
}
|
|
3112
|
+
/**
|
|
3113
|
+
* Get compression provider
|
|
3114
|
+
*/
|
|
3115
|
+
getCompression() {
|
|
3116
|
+
const compressionType = this.compression || "QuintetLZ";
|
|
3117
|
+
switch (compressionType) {
|
|
3118
|
+
case "QuintetLZ":
|
|
3119
|
+
default:
|
|
3120
|
+
return new QuintetLZ();
|
|
3121
|
+
}
|
|
3122
|
+
}
|
|
3123
|
+
/**
|
|
3124
|
+
* Load project from file or directory
|
|
3125
|
+
*/
|
|
3126
|
+
static async load(path) {
|
|
3127
|
+
path = path || "./project.json";
|
|
3128
|
+
try {
|
|
3129
|
+
const config = await readJsonFile(path);
|
|
3130
|
+
if (!config.baseDir) {
|
|
3131
|
+
config.baseDir = getDirectory(path);
|
|
3132
|
+
}
|
|
3133
|
+
return new _ProjectRoot(config);
|
|
3134
|
+
} catch (error) {
|
|
3135
|
+
console.warn(`Failed to load project file: ${error}. Using directory-based configuration.`);
|
|
3136
|
+
}
|
|
3137
|
+
const defaultConfig = {
|
|
3138
|
+
name: "GaiaLabs",
|
|
3139
|
+
baseDir: path,
|
|
3140
|
+
flipsPath: process?.env?.flips_path,
|
|
3141
|
+
romPath: process?.env?.rom_path || "",
|
|
3142
|
+
database: "us",
|
|
3143
|
+
databasePath: `${path}/db/us`,
|
|
3144
|
+
systemPath: `${path}/db/snes`,
|
|
3145
|
+
resources: {}
|
|
3146
|
+
};
|
|
3147
|
+
return new _ProjectRoot(defaultConfig);
|
|
3148
|
+
}
|
|
3149
|
+
/**
|
|
3150
|
+
* Build the ROM (simplified)
|
|
3151
|
+
*/
|
|
3152
|
+
async build() {
|
|
3153
|
+
throw new Error("ROM building not yet implemented");
|
|
3154
|
+
}
|
|
3155
|
+
/**
|
|
3156
|
+
* Dump database and extract ROM data
|
|
3157
|
+
*/
|
|
3158
|
+
async dumpDatabase() {
|
|
3159
|
+
const root = await DbRootUtils.fromFolder(this.databasePath, this.systemPath);
|
|
3160
|
+
const data = await readFileAsBinary(this.romPath);
|
|
3161
|
+
root.paths = this.resources;
|
|
3162
|
+
const fileReader = new FileReader(data, root, this.getCompression());
|
|
3163
|
+
await fileReader.extract(this.baseDir);
|
|
3164
|
+
const sfxReader = new SfxReader(data, root);
|
|
3165
|
+
await sfxReader.extract(this.baseDir);
|
|
3166
|
+
const blockReader = new BlockReader(data, root);
|
|
3167
|
+
blockReader.analyzeAndResolve();
|
|
3168
|
+
const blockWriter = new BlockWriter(blockReader);
|
|
3169
|
+
await blockWriter.writeBlocks(this.baseDir);
|
|
3170
|
+
return root;
|
|
3171
|
+
}
|
|
3172
|
+
};
|
|
3173
|
+
|
|
3174
|
+
// src/api/RomProcessor.ts
|
|
3175
|
+
var RomProcessor = class _RomProcessor {
|
|
3176
|
+
constructor(projectConfig) {
|
|
3177
|
+
this.dbRoot = null;
|
|
3178
|
+
this.romData = null;
|
|
3179
|
+
this.romState = null;
|
|
3180
|
+
this.projectRoot = new ProjectRoot(projectConfig);
|
|
3181
|
+
}
|
|
3182
|
+
/**
|
|
3183
|
+
* Create ROM processor from project file or directory
|
|
3184
|
+
*/
|
|
3185
|
+
static async fromProject(path) {
|
|
3186
|
+
const projectRoot = await ProjectRoot.load(path);
|
|
3187
|
+
return new _RomProcessor(projectRoot.config);
|
|
3188
|
+
}
|
|
3189
|
+
/**
|
|
3190
|
+
* Load ROM data from file or URL
|
|
3191
|
+
*/
|
|
3192
|
+
async loadRom(romPath) {
|
|
3193
|
+
const path = romPath || this.projectRoot.romPath;
|
|
3194
|
+
if (!path) {
|
|
3195
|
+
throw new Error("ROM path not specified");
|
|
3196
|
+
}
|
|
3197
|
+
try {
|
|
3198
|
+
this.romData = await readFileAsBinary(path);
|
|
3199
|
+
} catch (error) {
|
|
3200
|
+
throw new Error(`Failed to load ROM from ${path}: ${error}`);
|
|
3201
|
+
}
|
|
3202
|
+
}
|
|
3203
|
+
/**
|
|
3204
|
+
* Initialize database and load ROM metadata
|
|
3205
|
+
*/
|
|
3206
|
+
async initialize() {
|
|
3207
|
+
if (!this.romData) {
|
|
3208
|
+
throw new Error("ROM data not loaded. Call loadRom() first.");
|
|
3209
|
+
}
|
|
3210
|
+
this.dbRoot = await this.projectRoot.dumpDatabase();
|
|
3211
|
+
this.romState = new RomState();
|
|
3212
|
+
}
|
|
3213
|
+
/**
|
|
3214
|
+
* Analyze ROM structure and extract metadata
|
|
3215
|
+
*/
|
|
3216
|
+
async analyze() {
|
|
3217
|
+
if (!this.dbRoot || !this.romData) {
|
|
3218
|
+
throw new Error("ROM processor not initialized. Call initialize() first.");
|
|
3219
|
+
}
|
|
3220
|
+
const result = {
|
|
3221
|
+
romSize: this.romData.length,
|
|
3222
|
+
headerInfo: this.analyzeHeader(),
|
|
3223
|
+
entryPoints: this.dbRoot.entryPoints,
|
|
3224
|
+
fileCount: this.dbRoot.files.length,
|
|
3225
|
+
blockCount: this.dbRoot.blocks.length,
|
|
3226
|
+
compressionUsed: this.detectCompression(),
|
|
3227
|
+
spriteMapFound: RomState.spriteMap !== null,
|
|
3228
|
+
opcodeStats: this.analyzeOpcodes()
|
|
3229
|
+
};
|
|
3230
|
+
return result;
|
|
3231
|
+
}
|
|
3232
|
+
/**
|
|
3233
|
+
* Extract files from ROM data
|
|
3234
|
+
*/
|
|
3235
|
+
async extract(outputDir) {
|
|
3236
|
+
if (!this.dbRoot || !this.romData) {
|
|
3237
|
+
throw new Error("ROM processor not initialized. Call initialize() first.");
|
|
3238
|
+
}
|
|
3239
|
+
const result = {
|
|
3240
|
+
extractedFiles: [],
|
|
3241
|
+
errors: [],
|
|
3242
|
+
totalSize: 0
|
|
3243
|
+
};
|
|
3244
|
+
console.warn("File extraction not yet implemented - awaiting FileReader conversion");
|
|
3245
|
+
return result;
|
|
3246
|
+
}
|
|
3247
|
+
/**
|
|
3248
|
+
* Process scene data for a specific scene ID
|
|
3249
|
+
*/
|
|
3250
|
+
async processScene(sceneId, metaFile) {
|
|
3251
|
+
if (!this.dbRoot) {
|
|
3252
|
+
throw new Error("ROM processor not initialized. Call initialize() first.");
|
|
3253
|
+
}
|
|
3254
|
+
const state = await RomState.fromScene(
|
|
3255
|
+
this.projectRoot.baseDir,
|
|
3256
|
+
this.dbRoot,
|
|
3257
|
+
metaFile,
|
|
3258
|
+
sceneId
|
|
3259
|
+
);
|
|
3260
|
+
this.romState = state;
|
|
3261
|
+
return state;
|
|
3262
|
+
}
|
|
3263
|
+
/**
|
|
3264
|
+
* Build ROM from extracted files
|
|
3265
|
+
*/
|
|
3266
|
+
async build(outputPath) {
|
|
3267
|
+
if (!this.dbRoot) {
|
|
3268
|
+
throw new Error("ROM processor not initialized. Call initialize() first.");
|
|
3269
|
+
}
|
|
3270
|
+
const result = {
|
|
3271
|
+
success: false,
|
|
3272
|
+
outputPath,
|
|
3273
|
+
romSize: 0,
|
|
3274
|
+
errors: []
|
|
3275
|
+
};
|
|
3276
|
+
console.warn("ROM building not yet implemented - awaiting Assembler conversion");
|
|
3277
|
+
return result;
|
|
3278
|
+
}
|
|
3279
|
+
/**
|
|
3280
|
+
* Get compression provider for the project
|
|
3281
|
+
*/
|
|
3282
|
+
getCompression() {
|
|
3283
|
+
return this.projectRoot.getCompression();
|
|
3284
|
+
}
|
|
3285
|
+
/**
|
|
3286
|
+
* Get current ROM state
|
|
3287
|
+
*/
|
|
3288
|
+
getRomState() {
|
|
3289
|
+
return this.romState;
|
|
3290
|
+
}
|
|
3291
|
+
/**
|
|
3292
|
+
* Get database root
|
|
3293
|
+
*/
|
|
3294
|
+
getDatabase() {
|
|
3295
|
+
return this.dbRoot;
|
|
3296
|
+
}
|
|
3297
|
+
/**
|
|
3298
|
+
* Get project configuration
|
|
3299
|
+
*/
|
|
3300
|
+
getProject() {
|
|
3301
|
+
return this.projectRoot;
|
|
3302
|
+
}
|
|
3303
|
+
// Private helper methods
|
|
3304
|
+
analyzeHeader() {
|
|
3305
|
+
if (!this.romData) {
|
|
3306
|
+
throw new Error("ROM data not loaded");
|
|
3307
|
+
}
|
|
3308
|
+
const headerOffset = 32704;
|
|
3309
|
+
const title = new TextDecoder().decode(this.romData.slice(headerOffset, headerOffset + 21)).replace(/\0/g, "");
|
|
3310
|
+
const mapMode = this.romData[headerOffset + 21];
|
|
3311
|
+
const cartridgeType = this.romData[headerOffset + 22];
|
|
3312
|
+
const romSize = this.romData[headerOffset + 23];
|
|
3313
|
+
const ramSize = this.romData[headerOffset + 24];
|
|
3314
|
+
return {
|
|
3315
|
+
title,
|
|
3316
|
+
mapMode,
|
|
3317
|
+
cartridgeType,
|
|
3318
|
+
romSize,
|
|
3319
|
+
ramSize,
|
|
3320
|
+
isValid: title.length > 0 && mapMode !== 255
|
|
3321
|
+
};
|
|
3322
|
+
}
|
|
3323
|
+
detectCompression() {
|
|
3324
|
+
return this.projectRoot.getCompression() instanceof QuintetLZ;
|
|
3325
|
+
}
|
|
3326
|
+
analyzeOpcodes() {
|
|
3327
|
+
if (!this.dbRoot) {
|
|
3328
|
+
return { totalOpcodes: 0, uniqueOpcodes: 0, coverage: 0 };
|
|
3329
|
+
}
|
|
3330
|
+
const totalOpcodes = Object.keys(this.dbRoot.opCodes).length;
|
|
3331
|
+
const uniqueOpcodes = new Set(Object.values(this.dbRoot.opCodes)).size;
|
|
3332
|
+
const coverage = totalOpcodes > 0 ? uniqueOpcodes / OpCodeUtils.getAllOpcodes().length * 100 : 0;
|
|
3333
|
+
return {
|
|
3334
|
+
totalOpcodes,
|
|
3335
|
+
uniqueOpcodes,
|
|
3336
|
+
coverage
|
|
3337
|
+
};
|
|
3338
|
+
}
|
|
3339
|
+
};
|
|
3340
|
+
|
|
3341
|
+
// src/api/ProjectManager.ts
|
|
3342
|
+
var ProjectManager = class _ProjectManager {
|
|
3343
|
+
constructor(events) {
|
|
3344
|
+
this.processor = null;
|
|
3345
|
+
this.status = {
|
|
3346
|
+
isLoaded: false,
|
|
3347
|
+
isAnalyzed: false,
|
|
3348
|
+
isExtracted: false,
|
|
3349
|
+
canBuild: false,
|
|
3350
|
+
progress: 0
|
|
3351
|
+
};
|
|
3352
|
+
this.events = {};
|
|
3353
|
+
this.events = events || {};
|
|
3354
|
+
}
|
|
3355
|
+
/**
|
|
3356
|
+
* Create project manager from existing project configuration
|
|
3357
|
+
*/
|
|
3358
|
+
static async fromConfig(config, events) {
|
|
3359
|
+
const manager = new _ProjectManager(events);
|
|
3360
|
+
await manager.loadProject(config);
|
|
3361
|
+
return manager;
|
|
3362
|
+
}
|
|
3363
|
+
/**
|
|
3364
|
+
* Create project manager from project file or directory
|
|
3365
|
+
*/
|
|
3366
|
+
static async fromPath(path, events) {
|
|
3367
|
+
const manager = new _ProjectManager(events);
|
|
3368
|
+
await manager.loadProjectFromPath(path);
|
|
3369
|
+
return manager;
|
|
3370
|
+
}
|
|
3371
|
+
/**
|
|
3372
|
+
* Load project from configuration
|
|
3373
|
+
*/
|
|
3374
|
+
async loadProject(config) {
|
|
3375
|
+
try {
|
|
3376
|
+
this.updateStatus({ currentTask: "Loading project configuration", progress: 10 });
|
|
3377
|
+
this.events.onLoadStart?.();
|
|
3378
|
+
this.processor = new RomProcessor(config);
|
|
3379
|
+
this.updateStatus({ currentTask: "Loading ROM data", progress: 30 });
|
|
3380
|
+
await this.processor.loadRom();
|
|
3381
|
+
this.updateStatus({ currentTask: "Initializing database", progress: 60 });
|
|
3382
|
+
await this.processor.initialize();
|
|
3383
|
+
this.updateStatus({
|
|
3384
|
+
isLoaded: true,
|
|
3385
|
+
currentTask: "Project loaded successfully",
|
|
3386
|
+
progress: 100
|
|
3387
|
+
});
|
|
3388
|
+
this.events.onLoadComplete?.(this.processor);
|
|
3389
|
+
} catch (error) {
|
|
3390
|
+
const err = error;
|
|
3391
|
+
this.updateStatus({ lastError: err, currentTask: "Load failed", progress: 0 });
|
|
3392
|
+
this.events.onLoadError?.(err);
|
|
3393
|
+
throw err;
|
|
3394
|
+
}
|
|
3395
|
+
}
|
|
3396
|
+
/**
|
|
3397
|
+
* Load project from file or directory path
|
|
3398
|
+
*/
|
|
3399
|
+
async loadProjectFromPath(path) {
|
|
3400
|
+
const processor = await RomProcessor.fromProject(path);
|
|
3401
|
+
await this.loadProject(processor.getProject().config);
|
|
3402
|
+
}
|
|
3403
|
+
/**
|
|
3404
|
+
* Analyze ROM structure and extract metadata
|
|
3405
|
+
*/
|
|
3406
|
+
async analyzeRom() {
|
|
3407
|
+
if (!this.processor) {
|
|
3408
|
+
throw new Error("Project not loaded. Call loadProject() first.");
|
|
3409
|
+
}
|
|
3410
|
+
try {
|
|
3411
|
+
this.updateStatus({ currentTask: "Analyzing ROM structure", progress: 20 });
|
|
3412
|
+
this.events.onAnalysisStart?.();
|
|
3413
|
+
const results = await this.processor.analyze();
|
|
3414
|
+
this.updateStatus({
|
|
3415
|
+
isAnalyzed: true,
|
|
3416
|
+
currentTask: "ROM analysis complete",
|
|
3417
|
+
progress: 100
|
|
3418
|
+
});
|
|
3419
|
+
this.events.onAnalysisComplete?.(results);
|
|
3420
|
+
return results;
|
|
3421
|
+
} catch (error) {
|
|
3422
|
+
const err = error;
|
|
3423
|
+
this.updateStatus({ lastError: err, currentTask: "Analysis failed" });
|
|
3424
|
+
throw err;
|
|
3425
|
+
}
|
|
3426
|
+
}
|
|
3427
|
+
/**
|
|
3428
|
+
* Extract files from ROM
|
|
3429
|
+
*/
|
|
3430
|
+
async extractFiles(outputDir) {
|
|
3431
|
+
if (!this.processor) {
|
|
3432
|
+
throw new Error("Project not loaded. Call loadProject() first.");
|
|
3433
|
+
}
|
|
3434
|
+
try {
|
|
3435
|
+
this.updateStatus({ currentTask: "Extracting files from ROM", progress: 30 });
|
|
3436
|
+
this.events.onExtractionStart?.();
|
|
3437
|
+
const results = await this.processor.extract(outputDir);
|
|
3438
|
+
this.updateStatus({
|
|
3439
|
+
isExtracted: true,
|
|
3440
|
+
canBuild: true,
|
|
3441
|
+
currentTask: "File extraction complete",
|
|
3442
|
+
progress: 100
|
|
3443
|
+
});
|
|
3444
|
+
this.events.onExtractionComplete?.(results);
|
|
3445
|
+
return results;
|
|
3446
|
+
} catch (error) {
|
|
3447
|
+
const err = error;
|
|
3448
|
+
this.updateStatus({ lastError: err, currentTask: "Extraction failed" });
|
|
3449
|
+
throw err;
|
|
3450
|
+
}
|
|
3451
|
+
}
|
|
3452
|
+
/**
|
|
3453
|
+
* Build ROM from extracted files
|
|
3454
|
+
*/
|
|
3455
|
+
async buildRom(outputPath) {
|
|
3456
|
+
if (!this.processor) {
|
|
3457
|
+
throw new Error("Project not loaded. Call loadProject() first.");
|
|
3458
|
+
}
|
|
3459
|
+
if (!this.status.canBuild) {
|
|
3460
|
+
throw new Error("Cannot build ROM. Extract files first.");
|
|
3461
|
+
}
|
|
3462
|
+
try {
|
|
3463
|
+
this.updateStatus({ currentTask: "Building ROM", progress: 40 });
|
|
3464
|
+
this.events.onBuildStart?.();
|
|
3465
|
+
const results = await this.processor.build(outputPath);
|
|
3466
|
+
this.updateStatus({
|
|
3467
|
+
currentTask: "ROM build complete",
|
|
3468
|
+
progress: 100
|
|
3469
|
+
});
|
|
3470
|
+
this.events.onBuildComplete?.(results);
|
|
3471
|
+
return results;
|
|
3472
|
+
} catch (error) {
|
|
3473
|
+
const err = error;
|
|
3474
|
+
this.updateStatus({ lastError: err, currentTask: "Build failed" });
|
|
3475
|
+
throw err;
|
|
3476
|
+
}
|
|
3477
|
+
}
|
|
3478
|
+
/**
|
|
3479
|
+
* Process specific scene
|
|
3480
|
+
*/
|
|
3481
|
+
async processScene(sceneId, metaFile) {
|
|
3482
|
+
if (!this.processor) {
|
|
3483
|
+
throw new Error("Project not loaded. Call loadProject() first.");
|
|
3484
|
+
}
|
|
3485
|
+
return await this.processor.processScene(sceneId, metaFile);
|
|
3486
|
+
}
|
|
3487
|
+
/**
|
|
3488
|
+
* Get current project status
|
|
3489
|
+
*/
|
|
3490
|
+
getStatus() {
|
|
3491
|
+
return { ...this.status };
|
|
3492
|
+
}
|
|
3493
|
+
/**
|
|
3494
|
+
* Get the ROM processor instance
|
|
3495
|
+
*/
|
|
3496
|
+
getProcessor() {
|
|
3497
|
+
return this.processor;
|
|
3498
|
+
}
|
|
3499
|
+
/**
|
|
3500
|
+
* Get project configuration
|
|
3501
|
+
*/
|
|
3502
|
+
getProjectConfig() {
|
|
3503
|
+
return this.processor?.getProject().config || null;
|
|
3504
|
+
}
|
|
3505
|
+
/**
|
|
3506
|
+
* Get database root
|
|
3507
|
+
*/
|
|
3508
|
+
getDatabase() {
|
|
3509
|
+
return this.processor?.getDatabase() || null;
|
|
3510
|
+
}
|
|
3511
|
+
/**
|
|
3512
|
+
* Get ROM state
|
|
3513
|
+
*/
|
|
3514
|
+
getRomState() {
|
|
3515
|
+
return this.processor?.getRomState() || null;
|
|
3516
|
+
}
|
|
3517
|
+
/**
|
|
3518
|
+
* Reset project to initial state
|
|
3519
|
+
*/
|
|
3520
|
+
reset() {
|
|
3521
|
+
this.processor = null;
|
|
3522
|
+
this.status = {
|
|
3523
|
+
isLoaded: false,
|
|
3524
|
+
isAnalyzed: false,
|
|
3525
|
+
isExtracted: false,
|
|
3526
|
+
canBuild: false,
|
|
3527
|
+
progress: 0
|
|
3528
|
+
};
|
|
3529
|
+
}
|
|
3530
|
+
/**
|
|
3531
|
+
* Complete workflow: load, analyze, extract, and build
|
|
3532
|
+
*/
|
|
3533
|
+
async completeWorkflow(projectPath, outputDir, outputRomPath) {
|
|
3534
|
+
await this.loadProjectFromPath(projectPath);
|
|
3535
|
+
const analysisResults = await this.analyzeRom();
|
|
3536
|
+
const extractionResults = await this.extractFiles(outputDir);
|
|
3537
|
+
const buildResults = await this.buildRom(outputRomPath);
|
|
3538
|
+
return {
|
|
3539
|
+
analysis: analysisResults,
|
|
3540
|
+
extraction: extractionResults,
|
|
3541
|
+
build: buildResults
|
|
3542
|
+
};
|
|
3543
|
+
}
|
|
3544
|
+
// Private helper methods
|
|
3545
|
+
updateStatus(update) {
|
|
3546
|
+
this.status = { ...this.status, ...update };
|
|
3547
|
+
}
|
|
3548
|
+
};
|
|
3549
|
+
|
|
3550
|
+
// src/rom/rebuild/index.ts
|
|
3551
|
+
var ROM_REBUILD_MODULE = "gaia-core/rom/rebuild";
|
|
3552
|
+
|
|
3553
|
+
// src/index.ts
|
|
3554
|
+
var GAIA_CORE_VERSION = "0.1.0";
|
|
3555
|
+
var isPlatformBrowser = typeof window !== "undefined";
|
|
3556
|
+
var isPlatformNode = typeof process !== "undefined" && process.versions?.node;
|
|
3557
|
+
var isPlatformWebWorker = typeof importScripts !== "undefined";
|
|
3558
|
+
|
|
3559
|
+
export { ADDRESSING_REGEX, ALL_OPCODES, AddressingModeHandler, AsmReader, BlockReader, BlockWriter, CopCommandProcessor, FileReader, GAIA_CORE_VERSION, GROUPED_OPCODES, HEX_REGEX, ObjectType, Op, OpCode, OpCodeUtils, OperationContext, PostProcessor, ProcessorStateManager, ProjectManager, ProjectRoot, QuintetLZ, ROM_REBUILD_MODULE, ReferenceManager, Registers, RomDataReader, RomProcessor, RomState, RomStateUtils, SfxReader, SpriteFrame, SpriteGroup, SpriteMap, SpritePart, Stack, StackOperations, StringReader, TransformProcessor, TypeParser, isPlatformBrowser, isPlatformNode, isPlatformWebWorker };
|
|
3560
|
+
//# sourceMappingURL=index.mjs.map
|
|
3561
|
+
//# sourceMappingURL=index.mjs.map
|