snes-disassembler
Version:
A Super Nintendo (SNES) ROM disassembler for 65816 assembly
1,156 lines • 101 kB
JavaScript
"use strict";
/**
* Advanced Analysis Engine for SNES Disassembler
* Based on research from SNES MCP servers, modern binary analysis tools, and ML approaches
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.AnalysisEngine = void 0;
const types_1 = require("./types");
const snes_reference_tables_1 = require("./snes-reference-tables");
class AnalysisEngine {
constructor() {
// Variable usage tracking storage
this.variableUsage = new Map();
// Symbol dependency tracking storage
this.symbolDependencies = new Map();
this.cfg = {
blocks: new Map(),
entryPoints: new Set(),
functions: new Map()
};
this.symbols = new Map();
this.crossReferences = new Map();
this.dataStructures = new Map();
this.hardwareRegisters = this.initializeHardwareRegisters();
this.jumpTables = new Map();
this.pointerTables = new Map();
this.spriteData = new Map();
this.registerUsage = new Map();
}
/**
* Perform comprehensive analysis on disassembled code
*/
analyze(lines, cartridgeInfo, vectorAddresses) {
// Phase 1: Basic block detection
const blocks = this.detectBasicBlocks(lines);
// Phase 2: Control flow analysis
this.buildControlFlowGraph(blocks, lines);
// Phase 3: Function boundary detection
this.detectFunctions(lines, cartridgeInfo, vectorAddresses);
// Phase 4: Data structure recognition
this.analyzeDataStructures(lines);
// Phase 5: Cross-reference building
this.buildCrossReferences(lines);
// Phase 6: Symbol generation
this.generateSymbols(lines);
// Phase 7: Hardware register analysis
this.analyzeHardwareRegisterUsage(lines);
// Phase 8: Advanced control flow analysis
this.performRecursiveDescentAnalysis(lines);
// Phase 9: String and audio data detection
this.detectStringData(lines);
this.detectAudioData(lines);
// Phase 10: Data flow and symbol analysis
this.performDataFlowAnalysis(lines);
this.performSymbolDependencyAnalysis();
// Phase 11: Macro and inline function detection
this.detectMacrosAndInlineFunctions(lines);
// Phase 12: Game-specific pattern recognition
this.detectGameSpecificPatterns(lines, cartridgeInfo);
// Phase 13: Code quality metrics calculation
this.calculateCodeQualityMetrics(lines);
}
/**
* Detect basic blocks using control flow analysis
* Based on research from SMDA and other modern disassemblers
*/
detectBasicBlocks(lines) {
const blocks = [];
const blockStarts = new Set();
const blockEnds = new Set();
// Find basic block boundaries
for (let i = 0; i < lines.length; i++) {
const line = lines[i];
const instruction = line.instruction;
// Mark block start at function entry points
if (i === 0 || this.isEntryPoint(line.address)) {
blockStarts.add(line.address);
}
// Mark block end and next block start for control flow instructions
if (this.isControlFlowInstruction(instruction.mnemonic)) {
blockEnds.add(line.address);
// Add target as block start for branches/jumps
if (line.operand !== undefined && this.isBranchOrJump(instruction.mnemonic)) {
blockStarts.add(line.operand);
// For conditional branches, next instruction is also a block start
if (this.isConditionalBranch(instruction.mnemonic) && i + 1 < lines.length) {
blockStarts.add(lines[i + 1].address);
}
}
}
// Mark block start for jump/call targets
if (line.operand !== undefined && this.isJumpTarget(line.operand, lines)) {
blockStarts.add(line.operand);
}
}
// Create basic blocks
const sortedStarts = Array.from(blockStarts).sort((a, b) => a - b);
for (let i = 0; i < sortedStarts.length; i++) {
const startAddr = sortedStarts[i];
const endAddr = i + 1 < sortedStarts.length ? sortedStarts[i + 1] - 1 :
lines[lines.length - 1].address;
const blockInstructions = lines.filter(line => line.address >= startAddr && line.address <= endAddr);
if (blockInstructions.length > 0) {
const block = {
id: `block_${startAddr.toString(16)}`,
startAddress: startAddr,
endAddress: blockInstructions[blockInstructions.length - 1].address,
instructions: blockInstructions,
predecessors: new Set(),
successors: new Set(),
isFunction: this.isLikelyFunctionStart(blockInstructions[0], lines),
isFunctionEnd: this.isFunctionEnd(blockInstructions[blockInstructions.length - 1])
};
blocks.push(block);
}
}
return blocks;
}
/**
* Build control flow graph from basic blocks
*/
buildControlFlowGraph(blocks, lines) {
this.cfg.blocks.clear();
// Add blocks to CFG
for (const block of blocks) {
this.cfg.blocks.set(block.id, block);
if (block.isFunction) {
this.cfg.entryPoints.add(block.id);
}
}
// Build edges between blocks
for (const block of blocks) {
const lastInstruction = block.instructions[block.instructions.length - 1];
if (this.isControlFlowInstruction(lastInstruction.instruction.mnemonic)) {
this.addControlFlowEdges(block, lastInstruction, blocks);
}
else {
// Sequential execution to next block
this.addSequentialEdge(block, blocks);
}
}
}
/**
* Detect function boundaries using multiple heuristics
* Based on Zelda3 analysis patterns and modern techniques
*/
detectFunctions(lines, cartridgeInfo, vectorAddresses) {
const functions = new Map();
// Heuristic 1: Reset/interrupt vectors
this.detectVectorFunctions(cartridgeInfo, functions, vectorAddresses);
// Heuristic 2: JSR targets
this.detectJSRTargets(lines, functions);
// Heuristic 3: Common function prologue patterns
this.detectProloguePatterns(lines, functions);
// Heuristic 4: Dead code analysis for function boundaries
this.detectDeadCodeBoundaries(lines, functions);
this.cfg.functions = functions;
}
/**
* Analyze data structures and patterns
* Based on research from SNES MCP servers and binary analysis tools
*/
analyzeDataStructures(lines) {
// Detect pointer tables
this.detectPointerTables(lines);
// Detect jump tables
this.detectJumpTables(lines);
// Detect graphics data patterns
this.detectGraphicsData(lines);
// Detect sprite data structures (based on Zelda3 research)
this.detectSpriteData(lines);
// Detect tile data patterns
this.detectTileData(lines);
// Detect music/audio data
this.detectMusicData(lines);
// Detect text/string data
this.detectStringData(lines);
// Detect level/map data structures
this.detectLevelData(lines);
// Detect palette data
this.detectPaletteData(lines);
// Analyze hardware register usage patterns
this.analyzeHardwareRegisterUsage(lines);
}
/**
* Build comprehensive cross-reference database
*/
buildCrossReferences(lines) {
this.crossReferences.clear();
for (const line of lines) {
if (line.operand !== undefined) {
const refType = this.getCrossReferenceType(line.instruction.mnemonic, line.instruction.addressingMode);
const xref = {
address: line.operand,
type: refType,
fromAddress: line.address,
instruction: `${line.instruction.mnemonic} ${this.formatOperand(line)}`
};
if (!this.crossReferences.has(line.operand)) {
this.crossReferences.set(line.operand, []);
}
this.crossReferences.get(line.operand).push(xref);
}
}
}
/**
* Generate smart labels based on usage patterns
*/
generateSymbols(lines) {
this.symbols.clear();
// Generate function labels
for (const [address, func] of this.cfg.functions) {
const symbol = {
address,
name: this.generateFunctionName(address, func),
type: 'FUNCTION',
references: this.crossReferences.get(address) || [],
confidence: func.confidence
};
this.symbols.set(address, symbol);
}
// Generate data labels
for (const [address, dataStruct] of this.dataStructures) {
const symbol = {
address,
name: this.generateDataName(address, dataStruct),
type: 'DATA',
size: dataStruct.size,
references: this.crossReferences.get(address) || [],
confidence: 0.8
};
this.symbols.set(address, symbol);
}
// Generate hardware register symbols using reference data
for (const line of lines) {
if (line.operand !== undefined && this.hardwareRegisters.has(line.operand)) {
if (!this.symbols.has(line.operand)) {
// Get enhanced register information from reference tables
const registerInfo = (0, snes_reference_tables_1.getRegisterInfo)(line.operand);
const registerName = registerInfo.name || this.hardwareRegisters.get(line.operand);
const symbol = {
address: line.operand,
name: registerName,
type: 'CONSTANT',
references: this.crossReferences.get(line.operand) || [],
confidence: 1.0,
description: registerInfo.description
};
this.symbols.set(line.operand, symbol);
}
}
}
// Generate instruction-based symbols using reference data
for (const line of lines) {
if (line.instruction && line.instruction.opcode) {
const reference = snes_reference_tables_1.INSTRUCTION_REFERENCE[line.instruction.opcode];
if (reference && line.operand !== undefined) {
// Enhanced symbol generation based on instruction context
this.enhanceSymbolWithInstructionContext(line, reference);
}
}
}
}
// Utility methods for instruction classification
isControlFlowInstruction(mnemonic) {
const controlFlowMnemonics = [
'JMP', 'JSR', 'RTS', 'RTI', 'RTL',
'BRA', 'BCC', 'BCS', 'BEQ', 'BNE', 'BMI', 'BPL', 'BVC', 'BVS',
'BRL', 'BRK', 'COP', 'WAI', 'STP'
];
return controlFlowMnemonics.includes(mnemonic);
}
isBranchOrJump(mnemonic) {
return mnemonic.startsWith('B') || mnemonic.startsWith('J');
}
isConditionalBranch(mnemonic) {
const conditionalBranches = ['BCC', 'BCS', 'BEQ', 'BNE', 'BMI', 'BPL', 'BVC', 'BVS'];
return conditionalBranches.includes(mnemonic);
}
isFunctionEnd(line) {
const returnInstructions = ['RTS', 'RTI', 'RTL'];
return returnInstructions.includes(line.instruction.mnemonic);
}
isLikelyFunctionStart(line, allLines) {
// Check if this address is a JSR target
return allLines.some(l => l.instruction.mnemonic === 'JSR' && l.operand === line.address);
}
isEntryPoint(address) {
// This would be populated from cartridge vector analysis
return false; // Simplified for now
}
isJumpTarget(address, lines) {
return lines.some(line => this.isBranchOrJump(line.instruction.mnemonic) && line.operand === address);
}
// Control flow graph edge creation
addControlFlowEdges(block, lastInstruction, blocks) {
const mnemonic = lastInstruction.instruction.mnemonic;
const operand = lastInstruction.operand;
// Handle unconditional jumps
if (mnemonic === 'JMP' || mnemonic === 'BRA' || mnemonic === 'BRL') {
if (operand !== undefined) {
const targetBlock = this.findBlockByAddress(operand, blocks);
if (targetBlock) {
block.successors.add(targetBlock.id);
targetBlock.predecessors.add(block.id);
}
}
}
// Handle conditional branches
else if (this.isConditionalBranch(mnemonic)) {
// Add edge to branch target
if (operand !== undefined) {
const targetBlock = this.findBlockByAddress(operand, blocks);
if (targetBlock) {
block.successors.add(targetBlock.id);
targetBlock.predecessors.add(block.id);
}
}
// Add edge to fall-through (next instruction)
this.addSequentialEdge(block, blocks);
}
// Handle subroutine calls
else if (mnemonic === 'JSR') {
if (operand !== undefined) {
const targetBlock = this.findBlockByAddress(operand, blocks);
if (targetBlock) {
// Mark as function call relationship
targetBlock.isFunction = true;
}
}
// JSR continues to next instruction after return
this.addSequentialEdge(block, blocks);
}
// Return instructions have no successors
else if (['RTS', 'RTI', 'RTL'].includes(mnemonic)) {
// No successors for return instructions
}
}
addSequentialEdge(block, blocks) {
const nextAddress = block.endAddress + 1;
const nextBlock = this.findBlockByAddress(nextAddress, blocks);
if (nextBlock) {
block.successors.add(nextBlock.id);
nextBlock.predecessors.add(block.id);
}
}
findBlockByAddress(address, blocks) {
return blocks.find(block => address >= block.startAddress && address <= block.endAddress);
}
detectVectorFunctions(cartridgeInfo, functions, vectorAddresses) {
if (!vectorAddresses)
return;
// Mark vector target addresses as high-confidence functions
for (const vectorAddr of vectorAddresses) {
if (vectorAddr > 0) { // Valid address
const func = {
startAddress: vectorAddr,
callers: new Set(),
callees: new Set(),
basicBlocks: new Set(),
isInterrupt: true,
confidence: 1.0
};
functions.set(vectorAddr, func);
}
}
}
detectJSRTargets(lines, functions) {
for (const line of lines) {
if (line.instruction.mnemonic === 'JSR' && line.operand !== undefined) {
const targetAddr = line.operand;
if (!functions.has(targetAddr)) {
const func = {
startAddress: targetAddr,
callers: new Set(),
callees: new Set(),
basicBlocks: new Set(),
isInterrupt: false,
confidence: 0.9 // High confidence for JSR targets
};
functions.set(targetAddr, func);
}
// Update caller/callee relationships
const func = functions.get(targetAddr);
func.callers.add(line.address);
// Find caller function to add callee relationship
for (const [addr, callerFunc] of functions) {
if (line.address >= addr && (callerFunc.endAddress === undefined || line.address <= callerFunc.endAddress)) {
callerFunc.callees.add(targetAddr);
break;
}
}
}
}
}
detectProloguePatterns(lines, functions) {
// Common SNES function prologue patterns
const prologuePatterns = [
['PHB', 'PHK', 'PLB'], // Bank switching prologue
['REP', 'SEP'], // Processor flag setup
['PHA', 'PHX', 'PHY'], // Register preservation
['PHP'] // Processor status preservation
];
for (let i = 0; i < lines.length - 2; i++) {
const sequence = [
lines[i]?.instruction.mnemonic,
lines[i + 1]?.instruction.mnemonic,
lines[i + 2]?.instruction.mnemonic
];
// Check for prologue patterns
for (const pattern of prologuePatterns) {
if (this.matchesPattern(sequence, pattern)) {
const addr = lines[i].address;
if (!functions.has(addr)) {
const func = {
startAddress: addr,
callers: new Set(),
callees: new Set(),
basicBlocks: new Set(),
isInterrupt: false,
confidence: 0.7 // Medium confidence for prologue patterns
};
functions.set(addr, func);
}
}
}
}
}
matchesPattern(sequence, pattern) {
if (sequence.length < pattern.length)
return false;
for (let i = 0; i < pattern.length; i++) {
if (sequence[i] !== pattern[i])
return false;
}
return true;
}
detectDeadCodeBoundaries(lines, functions) {
for (let i = 0; i < lines.length - 1; i++) {
const currentLine = lines[i];
const nextLine = lines[i + 1];
// Look for unconditional control flow followed by unreachable code
if (['JMP', 'BRA', 'BRL', 'RTS', 'RTI', 'RTL'].includes(currentLine.instruction.mnemonic)) {
// Check if next instruction is not a known target
const nextAddr = nextLine.address;
const isTarget = lines.some(line => line.operand === nextAddr && this.isBranchOrJump(line.instruction.mnemonic));
// If next instruction is not a target, it might start a new function
if (!isTarget && !functions.has(nextAddr)) {
const func = {
startAddress: nextAddr,
callers: new Set(),
callees: new Set(),
basicBlocks: new Set(),
isInterrupt: false,
confidence: 0.6 // Lower confidence for dead code boundary detection
};
functions.set(nextAddr, func);
}
}
}
}
detectPointerTables(lines) {
// Look for sequences of LDA/STA with pointer-like operands
for (let i = 0; i < lines.length - 3; i++) {
const sequence = lines.slice(i, i + 4);
// Pattern: LDA table,X / STA pointer / LDA table+1,X / STA pointer+1
if (this.isPointerTablePattern(sequence)) {
const tableAddr = sequence[0].operand;
if (tableAddr !== undefined) {
const dataStruct = {
address: tableAddr,
type: 'POINTER_TABLE',
size: this.estimateTableSize(lines, tableAddr, 'POINTER'),
entries: 0, // Will be calculated
description: `Pointer table at $${tableAddr.toString(16).toUpperCase()}`,
confidence: 0.7
};
dataStruct.entries = Math.floor(dataStruct.size / 2); // 2 bytes per pointer
this.dataStructures.set(tableAddr, dataStruct);
}
}
}
}
isPointerTablePattern(sequence) {
if (sequence.length < 4)
return false;
const [lda1, sta1, lda2, sta2] = sequence;
return (lda1.instruction.mnemonic === 'LDA' &&
sta1.instruction.mnemonic === 'STA' &&
lda2.instruction.mnemonic === 'LDA' &&
sta2.instruction.mnemonic === 'STA' &&
lda1.instruction.addressingMode === types_1.AddressingMode.AbsoluteX &&
lda2.instruction.addressingMode === types_1.AddressingMode.AbsoluteX &&
lda1.operand !== undefined &&
lda2.operand !== undefined &&
(lda2.operand === lda1.operand + 1) // Sequential addresses
);
}
detectJumpTables(lines) {
// Look for indirect jumps preceded by table loading
for (let i = 0; i < lines.length - 2; i++) {
const line = lines[i];
// Pattern: JMP (abs) or JMP (abs,X)
if (line.instruction.mnemonic === 'JMP' &&
(line.instruction.addressingMode === types_1.AddressingMode.AbsoluteIndirect ||
line.instruction.addressingMode === types_1.AddressingMode.AbsoluteIndexedIndirect)) {
const tableAddr = line.operand;
if (tableAddr !== undefined) {
const dataStruct = {
address: tableAddr,
type: 'JUMP_TABLE',
size: this.estimateTableSize(lines, tableAddr, 'JUMP'),
entries: 0,
description: `Jump table at $${tableAddr.toString(16).toUpperCase()}`,
confidence: 0.7
};
dataStruct.entries = Math.floor(dataStruct.size / 2); // 2 bytes per jump address
this.dataStructures.set(tableAddr, dataStruct);
}
}
}
}
detectGraphicsData(lines) {
// Look for patterns indicating graphics data access
for (let i = 0; i < lines.length - 1; i++) {
const line = lines[i];
// Pattern: STA to PPU graphics registers
if (line.instruction.mnemonic === 'STA' && line.operand !== undefined) {
const graphicsRegisters = [
0x2118, // VMDATAL
0x2119, // VMDATAH
0x2122, // CGDATA
0x2104 // OAMDATA
];
if (graphicsRegisters.includes(line.operand)) {
// Look backwards for data source
for (let j = i - 1; j >= Math.max(0, i - 5); j--) {
const prevLine = lines[j];
if (prevLine.instruction.mnemonic === 'LDA' &&
prevLine.instruction.addressingMode === types_1.AddressingMode.AbsoluteX &&
prevLine.operand !== undefined) {
const dataAddr = prevLine.operand;
const dataStruct = {
address: dataAddr,
type: 'GRAPHICS_DATA',
size: this.estimateTableSize(lines, dataAddr, 'GRAPHICS'),
entries: 0,
description: `Graphics data at $${dataAddr.toString(16).toUpperCase()}`,
confidence: 0.6
};
this.dataStructures.set(dataAddr, dataStruct);
break;
}
}
}
}
}
}
detectMusicData(lines) {
// Look for APU I/O register access patterns
const apuRegisters = [0x2140, 0x2141, 0x2142, 0x2143];
for (let i = 0; i < lines.length - 1; i++) {
const line = lines[i];
if (line.instruction.mnemonic === 'STA' &&
line.operand !== undefined &&
apuRegisters.includes(line.operand)) {
// Look for data source
for (let j = i - 1; j >= Math.max(0, i - 3); j--) {
const prevLine = lines[j];
if (prevLine.instruction.mnemonic === 'LDA' &&
prevLine.operand !== undefined &&
!apuRegisters.includes(prevLine.operand)) {
const dataAddr = prevLine.operand;
if (!this.dataStructures.has(dataAddr)) {
const dataStruct = {
address: dataAddr,
type: 'MUSIC_DATA',
size: this.estimateTableSize(lines, dataAddr, 'MUSIC'),
entries: 0,
description: `Music/Audio data at $${dataAddr.toString(16).toUpperCase()}`,
confidence: 0.5
};
this.dataStructures.set(dataAddr, dataStruct);
}
break;
}
}
}
}
}
estimateTableSize(lines, tableAddr, type) {
// Conservative estimate based on usage patterns
let usageCount = 0;
let maxOffset = 0;
for (const line of lines) {
if (line.operand === tableAddr ||
(line.operand !== undefined && Math.abs(line.operand - tableAddr) < 256)) {
usageCount++;
if (line.operand !== undefined && line.operand > tableAddr) {
maxOffset = Math.max(maxOffset, line.operand - tableAddr);
}
}
}
// Estimate size based on maximum offset seen + buffer
return Math.max(16, Math.min(256, maxOffset + 16));
}
getCrossReferenceType(mnemonic, mode) {
if (mnemonic === 'JSR')
return 'CALL';
if (this.isBranchOrJump(mnemonic))
return 'JUMP';
if (['LDA', 'LDX', 'LDY', 'CMP', 'CPX', 'CPY', 'BIT'].includes(mnemonic))
return 'READ';
if (['STA', 'STX', 'STY', 'STZ'].includes(mnemonic))
return 'WRITE';
return 'EXECUTE';
}
generateFunctionName(address, func) {
if (func.isInterrupt) {
return `interrupt_${address.toString(16).toUpperCase()}`;
}
return `function_${address.toString(16).toUpperCase()}`;
}
generateDataName(address, dataStruct) {
const typePrefix = dataStruct.type.toLowerCase().replace('_', '');
return `${typePrefix}_${address.toString(16).toUpperCase()}`;
}
formatOperand(line) {
if (line.operand === undefined)
return '';
// Check if operand is a known symbol
const symbol = this.symbols.get(line.operand);
if (symbol) {
return symbol.name;
}
// Format as hex address
const addr = line.operand;
if (addr <= 0xFF) {
return `$${addr.toString(16).toUpperCase().padStart(2, '0')}`;
}
else if (addr <= 0xFFFF) {
return `$${addr.toString(16).toUpperCase().padStart(4, '0')}`;
}
else {
return `$${addr.toString(16).toUpperCase().padStart(6, '0')}`;
}
}
/**
* Initialize hardware register mappings from SNES MCP server documentation
*/
initializeHardwareRegisters() {
const registers = new Map();
// Load from authoritative SNES reference tables
for (const [address, registerInfo] of Object.entries(snes_reference_tables_1.REGISTER_REFERENCE)) {
const addr = parseInt(address);
registers.set(addr, registerInfo.name);
}
// PPU Registers (keeping existing for backwards compatibility)
registers.set(0x2100, 'INIDISP'); // Screen Display Register
registers.set(0x2101, 'OBSEL'); // Object Size and Character Address
registers.set(0x2102, 'OAMADDL'); // OAM Address Register (Low)
registers.set(0x2103, 'OAMADDH'); // OAM Address Register (High)
registers.set(0x2104, 'OAMDATA'); // OAM Data Write Register
registers.set(0x2105, 'BGMODE'); // BG Mode and Character Size
registers.set(0x2106, 'MOSAIC'); // Mosaic Filter Enable and Size
registers.set(0x2107, 'BG1SC'); // BG1 Tilemap Address and Size
registers.set(0x2108, 'BG2SC'); // BG2 Tilemap Address and Size
registers.set(0x2109, 'BG3SC'); // BG3 Tilemap Address and Size
registers.set(0x210A, 'BG4SC'); // BG4 Tilemap Address and Size
registers.set(0x210B, 'BG12NBA'); // BG1/2 Character Address
registers.set(0x210C, 'BG34NBA'); // BG3/4 Character Address
registers.set(0x210D, 'BG1HOFS'); // BG1 Horizontal Scroll Offset
registers.set(0x210E, 'BG1VOFS'); // BG1 Vertical Scroll Offset
registers.set(0x210F, 'BG2HOFS'); // BG2 Horizontal Scroll Offset
registers.set(0x2110, 'BG2VOFS'); // BG2 Vertical Scroll Offset
registers.set(0x2111, 'BG3HOFS'); // BG3 Horizontal Scroll Offset
registers.set(0x2112, 'BG3VOFS'); // BG3 Vertical Scroll Offset
registers.set(0x2113, 'BG4HOFS'); // BG4 Horizontal Scroll Offset
registers.set(0x2114, 'BG4VOFS'); // BG4 Vertical Scroll Offset
registers.set(0x2115, 'VMAIN'); // Video Port Control
registers.set(0x2116, 'VMADDL'); // VRAM Address Register (Low)
registers.set(0x2117, 'VMADDH'); // VRAM Address Register (High)
registers.set(0x2118, 'VMDATAL'); // VRAM Data Write Register (Low)
registers.set(0x2119, 'VMDATAH'); // VRAM Data Write Register (High)
registers.set(0x211A, 'M7SEL'); // Mode 7 Settings
registers.set(0x211B, 'M7A'); // Mode 7 Matrix Parameter A
registers.set(0x211C, 'M7B'); // Mode 7 Matrix Parameter B
registers.set(0x211D, 'M7C'); // Mode 7 Matrix Parameter C
registers.set(0x211E, 'M7D'); // Mode 7 Matrix Parameter D
registers.set(0x211F, 'M7X'); // Mode 7 Center X
registers.set(0x2120, 'M7Y'); // Mode 7 Center Y
registers.set(0x2121, 'CGADD'); // CGRAM Address Register
registers.set(0x2122, 'CGDATA'); // CGRAM Data Write Register
registers.set(0x2123, 'W12SEL'); // Window Mask Settings BG1/2
registers.set(0x2124, 'W34SEL'); // Window Mask Settings BG3/4
registers.set(0x2125, 'WOBJSEL'); // Window Mask Settings OBJ/Color
registers.set(0x2126, 'WH0'); // Window Position 1 Left
registers.set(0x2127, 'WH1'); // Window Position 1 Right
registers.set(0x2128, 'WH2'); // Window Position 2 Left
registers.set(0x2129, 'WH3'); // Window Position 2 Right
registers.set(0x212A, 'WBGLOG'); // Window BG Logic Operation
registers.set(0x212B, 'WOBJLOG'); // Window OBJ/Color Logic Operation
registers.set(0x212C, 'TM'); // Main Screen Designation
registers.set(0x212D, 'TS'); // Sub Screen Designation
registers.set(0x212E, 'TMW'); // Window Mask Main Screen
registers.set(0x212F, 'TSW'); // Window Mask Sub Screen
registers.set(0x2130, 'CGWSEL'); // Color Addition Select
registers.set(0x2131, 'CGADSUB'); // Color Math Control
registers.set(0x2132, 'COLDATA'); // Fixed Color Data
registers.set(0x2133, 'SETINI'); // Screen Mode/Video Select
// APU Registers
registers.set(0x2140, 'APUIO0'); // APU I/O Register 0
registers.set(0x2141, 'APUIO1'); // APU I/O Register 1
registers.set(0x2142, 'APUIO2'); // APU I/O Register 2
registers.set(0x2143, 'APUIO3'); // APU I/O Register 3
// CPU/System Registers
registers.set(0x4200, 'NMITIMEN'); // Interrupt Enable and Joypad Request
registers.set(0x4201, 'WRIO'); // Joypad Programmable I/O Port
registers.set(0x4202, 'WRMPYA'); // Multiplicand Register A
registers.set(0x4203, 'WRMPYB'); // Multiplicand Register B
registers.set(0x4204, 'WRDIVL'); // Dividend Register (Low)
registers.set(0x4205, 'WRDIVH'); // Dividend Register (High)
registers.set(0x4206, 'WRDIVB'); // Divisor Register
registers.set(0x4207, 'HTIMEL'); // Horizontal Timer Register (Low)
registers.set(0x4208, 'HTIMEH'); // Horizontal Timer Register (High)
registers.set(0x4209, 'VTIMEL'); // Vertical Timer Register (Low)
registers.set(0x420A, 'VTIMEH'); // Vertical Timer Register (High)
registers.set(0x420B, 'MDMAEN'); // DMA Enable Register
registers.set(0x420C, 'HDMAEN'); // HDMA Enable Register
return registers;
}
/**
* Detect sprite data structures based on Zelda3 research patterns
*/
detectSpriteData(lines) {
// Look for sprite data patterns based on Zelda3 sprite structure research
for (let i = 0; i < lines.length - 10; i++) {
const line = lines[i];
// Pattern 1: Sprite position tables (x_lo, x_hi, y_lo, y_hi arrays)
if (this.isSpritePositionTable(lines, i)) {
const spriteInfo = {
address: line.address,
hitboxes: this.extractSpriteHitboxes(lines, i),
animationFrames: this.countAnimationFrames(lines, i),
tileReferences: this.extractTileReferences(lines, i)
};
this.spriteData.set(line.address, spriteInfo);
this.dataStructures.set(line.address, {
address: line.address,
type: 'SPRITE_DATA',
size: spriteInfo.hitboxes.length * 8, // Estimated size
entries: spriteInfo.hitboxes.length,
description: `Sprite data with ${spriteInfo.animationFrames} animation frames`,
confidence: 0.8,
format: 'SNES sprite structure'
});
}
}
}
/**
* Detect tile data patterns
*/
detectTileData(lines) {
for (let i = 0; i < lines.length - 5; i++) {
const line = lines[i];
// Look for tile data patterns (CHR data)
if (this.isTileData(lines, i)) {
const tileCount = this.estimateTileCount(lines, i);
this.dataStructures.set(line.address, {
address: line.address,
type: 'TILE_DATA',
size: tileCount * 32, // 4bpp tiles are 32 bytes each
entries: tileCount,
description: `Tile graphics data (${tileCount} tiles)`,
confidence: 0.7,
format: '4bpp CHR data'
});
}
}
}
/**
* Detect level/map data structures
*/
detectLevelData(lines) {
for (let i = 0; i < lines.length - 8; i++) {
const line = lines[i];
// Look for level data patterns (compressed or uncompressed)
if (this.isLevelData(lines, i)) {
const mapSize = this.estimateMapSize(lines, i);
this.dataStructures.set(line.address, {
address: line.address,
type: 'LEVEL_DATA',
size: mapSize,
entries: 1,
description: 'Level/map layout data',
confidence: 0.6,
format: 'Tilemap data'
});
}
}
}
/**
* Detect palette data
*/
detectPaletteData(lines) {
for (let i = 0; i < lines.length - 4; i++) {
const line = lines[i];
// Look for palette data patterns (16 colors * 2 bytes each)
if (this.isPaletteData(lines, i)) {
const colorCount = this.estimateColorCount(lines, i);
this.dataStructures.set(line.address, {
address: line.address,
type: 'PALETTE_DATA',
size: colorCount * 2, // 16-bit colors
entries: colorCount,
description: `Color palette data (${colorCount} colors)`,
confidence: 0.8,
format: '15-bit BGR color data'
});
}
}
}
/**
* Analyze hardware register usage patterns
*/
analyzeHardwareRegisterUsage(lines) {
this.registerUsage.clear();
for (const line of lines) {
if (line.operand !== undefined) {
const registerName = this.hardwareRegisters.get(line.operand);
if (registerName) {
let usage = this.registerUsage.get(line.operand);
if (!usage) {
usage = {
register: registerName,
address: line.operand,
reads: 0,
writes: 0,
accessPoints: [],
description: this.getRegisterDescription(line.operand)
};
this.registerUsage.set(line.operand, usage);
}
// Determine if this is a read or write operation
const isWrite = this.isWriteOperation(line.instruction.mnemonic);
if (isWrite) {
usage.writes++;
}
else {
usage.reads++;
}
usage.accessPoints.push(line.address);
}
}
}
// Log significant register usage for debugging
for (const [address, usage] of this.registerUsage) {
if (usage.reads + usage.writes > 0) {
console.log(`Register ${usage.register} ($${address.toString(16).toUpperCase()}): ${usage.reads} reads, ${usage.writes} writes`);
}
}
}
// Helper methods for data structure detection
isSpritePositionTable(lines, startIndex) {
// Look for patterns like sprite_x_lo, sprite_x_hi arrays
const pattern = lines.slice(startIndex, startIndex + 4);
return pattern.some(line => line.bytes && line.bytes.length >= 4);
}
extractSpriteHitboxes(lines, startIndex) {
// Extract hitbox data from sprite structure
const hitboxes = [];
for (let i = 0; i < 8 && startIndex + i < lines.length; i++) {
const line = lines[startIndex + i];
if (line.bytes && line.bytes.length >= 4) {
hitboxes.push({
x: line.bytes[0],
y: line.bytes[1],
width: line.bytes[2],
height: line.bytes[3]
});
}
}
return hitboxes;
}
countAnimationFrames(lines, startIndex) {
// Estimate animation frames based on data patterns
return Math.min(8, Math.floor((lines.length - startIndex) / 4));
}
extractTileReferences(lines, startIndex) {
// Extract tile reference numbers
const tiles = [];
for (let i = 0; i < 16 && startIndex + i < lines.length; i++) {
const line = lines[startIndex + i];
if (line.bytes) {
tiles.push(...line.bytes);
}
}
return tiles;
}
isTileData(lines, startIndex) {
// Look for 4bpp tile data patterns (32 bytes per tile)
const line = lines[startIndex];
return line.bytes && line.bytes.length >= 32;
}
estimateTileCount(lines, startIndex) {
let tileCount = 0;
for (let i = startIndex; i < lines.length && i < startIndex + 64; i++) {
const line = lines[i];
if (line.bytes && line.bytes.length >= 32) {
tileCount++;
}
}
return tileCount;
}
isLevelData(lines, startIndex) {
// Look for level data patterns (tilemap entries)
const pattern = lines.slice(startIndex, startIndex + 8);
return pattern.every(line => line.bytes && line.bytes.length <= 2);
}
estimateMapSize(lines, startIndex) {
let size = 0;
for (let i = startIndex; i < lines.length && i < startIndex + 256; i++) {
const line = lines[i];
if (line.bytes) {
size += line.bytes.length;
}
}
return size;
}
isPaletteData(lines, startIndex) {
// Look for 16-bit color data patterns
const line = lines[startIndex];
return line.bytes && line.bytes.length % 2 === 0 && line.bytes.length >= 32;
}
estimateColorCount(lines, startIndex) {
const line = lines[startIndex];
if (line.bytes) {
return Math.min(256, line.bytes.length / 2);
}
return 16; // Default palette size
}
getRegisterDescription(address) {
const descriptions = {
0x2100: 'Screen Display Register',
0x2101: 'Object Size and Character Address',
0x2105: 'BG Mode and Character Size',
0x2121: 'CGRAM Address Register',
0x2122: 'CGRAM Data Write Register',
0x4200: 'Interrupt Enable Register',
0x420B: 'DMA Enable Register',
0x420C: 'HDMA Enable Register'
};
return descriptions[address] || 'Hardware Register';
}
isWriteOperation(mnemonic) {
const writeOps = ['STA', 'STX', 'STY', 'STZ'];
return writeOps.includes(mnemonic);
}
extractPointers(lines, startIndex, tableAddr) {
// Extract pointer values from pointer table
const pointers = [];
for (let i = startIndex; i < lines.length && i < startIndex + 16; i++) {
const line = lines[i];
if (line.bytes && line.bytes.length >= 2) {
// Assume little-endian 16-bit pointers
const pointer = line.bytes[0] | (line.bytes[1] << 8);
pointers.push(pointer);
}
}
return pointers;
}
resolvePointerTargets(pointers) {
// Convert pointers to actual target addresses
return pointers.filter(p => p > 0x8000 && p < 0x10000); // Valid ROM addresses
}
// Enhanced public getter methods for analysis results
getControlFlowGraph() {
return this.cfg;
}
getSymbols() {
return this.symbols;
}
getCrossReferences() {
return this.crossReferences;
}
getDataStructures() {
return this.dataStructures;
}
getFunctions() {
return this.cfg.functions;
}
getJumpTables() {
return this.jumpTables;
}
getPointerTables() {
return this.pointerTables;
}
getSpriteData() {
return this.spriteData;
}
getHardwareRegisterUsage() {
return this.registerUsage;
}
/**
* Get comprehensive analysis summary
*/
getAnalysisSummary() {
return {
functions: this.cfg.functions.size,
basicBlocks: this.cfg.blocks.size,
dataStructures: this.dataStructures.size,
crossReferences: this.crossReferences.size,
jumpTables: this.jumpTables.size,
spriteStructures: this.spriteData.size,
registerUsage: this.registerUsage.size
};
}
// ====================================================================
// ENHANCED DISASSEMBLY FEATURES - Phase 3 Implementation
// ====================================================================
/**
* Enhanced disassembly features - Apply intelligent analysis to improve output
*/
getEnhancedDisassembly(lines) {
// Add inline data detection, better labels, and smart comments
const enhanced = [...lines];
// Apply all enhancement features
this.detectInlineData(enhanced);
this.generateBranchTargetLabels(enhanced);
this.addIntelligentComments(enhanced);
this.detectCompilerPatterns(enhanced);
this.analyzeInterruptVectors(enhanced);
this.documentHardwareRegisterUsageInCode(enhanced);
return enhanced;
}
detectInlineData(lines) {
// Detect data embedded within code segments
for (let i = 0; i < lines.length; i++) {
const line = lines[i];
// Look for patterns indicating inline data
if (this.isInlineDataPattern(line, lines, i)) {
// Mark as inline data and add appropriate comment
line.comment = line.comment
? `${line.comment} ; INLINE DATA: ${this.identifyDataType(line)}`
: `; INLINE DATA: ${this.identifyDataType(line)}`;
}
}
}
isInlineDataPattern(line, lines, index) {
// Check if this looks like data rather than executable code
const opcode = line.instruction.opcode;
// Invalid opcodes (often used for data)
if (opcode === 0x42 || opcode === 0x44 || opcode === 0x54 || opcode === 0x64 ||
opcode === 0xD4 || opcode === 0xF4) {
return true;
}
// Suspicious patterns: repeated bytes that look like data
if (index > 0 && index < lines.length - 1) {
const prev = lines[index - 1];
const next = lines[index + 1];
// Pattern: same opcode repeated (likely data table)
if (prev.instruction.opcode === opcode && next.instruction.opcode === opcode) {
return true;
}
}
// BRK instructions in the middle of code (often padding or data)
if (line.instruction.mnemonic === 'BRK' && index > 0 && index < lines.length - 1) {
return true;
}
return false;
}
identifyDataType(line) {
const opcode = line.instruction.opcode;
// Analyze the byte pattern to guess data type
if (opcode === 0x00)
return 'NULL/PADDING';
if (opcode === 0xFF)
return 'FILL BYTE';
if ((opcode & 0xF0) === 0x20 || (opcode & 0xF0) === 0x30)
return 'ASCII TEXT';
if (opcode < 0x20)
return 'CONTROL DATA';
if ((opcode & 0x0F) === (opcode >> 4))
return 'PATTERN DATA';
return 'BINARY DATA';
}
generateBranchTargetLabels(lines) {
const targets = new Map();
// First pass: identify all branch/jump targets
for (const line of lines) {
if (this.isBranchInstruction(line)) {
const target = this.calculateBranchTarget(line);
if (target !== null) {
if (!targets.has(target)) {
targets.set(target, this.generateLabelName(line, target));
}
}
}
}
// Second pass: apply labels to target lines
for (const line of lines) {
const label = targets.get(line.address);
if (label) {
line.label = label;
}
}
}
isBranchInstruction(line) {
const branchOpcodes = [
'BCC', 'BCS', 'BEQ', 'BMI', 'BNE', 'BPL', 'BVC', 'BVS', // Conditional branches
'BRA', 'BRL', 'JMP', 'JSR', 'JSL' // Unconditional jumps
];
return branchOpcodes.includes(line.instruction.mnemonic);
}
calculateBranchTarget(line) {
const mnemonic = line.instruction.mnemonic;
const operand = line.operand;
if (operand === undefined)
return null;
// Relative branches
if (mnemonic === 'BRA' || mnemonic.startsWith('B')) {
// For relative branches, operand is signed offset
const offset = operand > 127 ? operand - 256 : operand;
return line.address + line.instruction.bytes + offset;
}
// Absolute jumps
if (mnemonic === 'JMP' || mnemonic === 'JSR' || mnemonic === 'JSL') {
return operand;
}
return null;
}
generateLabelName(line, target) {
const mnemonic = line.instruction.mnemonic;
if (mnemonic === 'JSR' || mnemonic === 'JSL') {
return `sub_${target.toString(16).toUpperCase()}`;
}
else if (mnemonic === 'JMP') {
return `loc_${target.toString(16).toUpperCase()}`;
}
else {
return `branch_${target.toString(16).toUpperCase()}`;
}
}
addIntelligentComments(lines) {
for (let i = 0; i < lines.length; i++) {
const line = lines[i];
const comment = this.generateIntelligentComment(line, lines, i);
if (comment) {
line.comment = line.comment ? `${line.comment} ; ${comment}` : `; ${comment}`;
}
}
}
generateIntelligentComment(line, lines, index) {
const mnemonic = line.instruction.mnemonic;
const operand = line.operand;
// REP/SE