snes-disassembler
Version:
A Super Nintendo (SNES) ROM disassembler for 65816 assembly
397 lines ⢠18.4 kB
JavaScript
;
/**
* SNES Disassembly Handler
*
* Handles the complete disassembly workflow with enhanced algorithms
* and MCP server insights integration.
*/
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.disassembleROM = disassembleROM;
const fs_1 = require("fs");
const path_1 = __importDefault(require("path"));
const disassembler_1 = require("./disassembler");
const output_formats_extended_1 = require("./output-formats-extended");
const symbol_manager_1 = require("./symbol-manager");
const asset_handler_1 = require("./asset-handler");
const enhanced_disassembly_engine_1 = require("./enhanced-disassembly-engine");
const quality_reporter_1 = require("./quality-reporter");
const error_handler_1 = require("./error-handler");
const brr_decoder_1 = require("./brr-decoder");
async function processBRRFile(brrFile, options) {
try {
// Read BRR file
const inputFileContent = await fs_1.promises.readFile(brrFile);
// Set up BRR decoder options
const decoderOptions = {
enableLooping: options.brrEnableLooping || false,
maxSamples: parseInt(options.brrMaxSamples || '1000000'),
outputSampleRate: parseInt(options.brrSampleRate || '32000')
};
// Decode BRR file
const brrData = new Uint8Array(inputFileContent);
const decodedResult = (0, brr_decoder_1.decodeBRRFile)(brrData, decoderOptions);
// Show detailed BRR info if requested
if (options.brrInfo) {
console.log('BRR File Information:');
decodedResult.blocks.forEach((block, index) => {
console.log(`Block ${index + 1}:`, block);
});
console.log('Decoding Statistics:', decodedResult.stats);
return;
}
// Convert decoded samples to WAV
const wavData = (0, brr_decoder_1.exportToWAV)(decodedResult.samples, decodedResult.sampleRate);
// Determine output file name
let outputFile = options.brrOutput;
if (!outputFile) {
const baseName = path_1.default.basename(brrFile, path_1.default.extname(brrFile));
outputFile = `${baseName}.wav`;
}
// Write WAV file
await fs_1.promises.writeFile(outputFile, wavData);
if (options.verbose) {
console.log(`BRR audio decoded successfully to: ${outputFile}`);
console.log(`Sample Rate: ${decodedResult.sampleRate} Hz`);
console.log('Looping:', decodedResult.loopStart !== undefined ? 'Enabled' : 'Disabled');
}
}
catch (error) {
console.error('Failed to process BRR file:', error instanceof Error ? error.message : error);
}
}
async function disassembleROM(romFile, options) {
const errorHandler = new error_handler_1.ErrorHandler();
// Handle BRR audio decoding if requested
if (options.decodeBrr) {
await processBRRFile(options.decodeBrr, options);
return;
}
try {
// Validate ROM file exists
await fs_1.promises.access(romFile);
}
catch {
throw new Error(`ROM file not found: ${romFile}`);
}
if (options.verbose) {
console.log('š® SNES Disassembler v2.0.0');
console.log('================================');
console.log(`š ROM File: ${romFile}`);
console.log(`š Output Format: ${options.format}`);
if (options.enhancedDisasm) {
console.log('ā” Enhanced disassembly algorithms: ENABLED');
}
if (options.bankAware) {
console.log('š¦ Bank-aware addressing: ENABLED');
}
console.log('');
}
// Initialize disassembler with enhanced options
const disassembler = options.enhancedDisasm
? new enhanced_disassembly_engine_1.EnhancedDisassemblyEngine(romFile, {
bankAware: options.bankAware || false,
detectFunctions: options.detectFunctions || false,
generateLabels: true,
extractVectors: true
})
: new disassembler_1.SNESDisassembler(romFile);
const romInfo = disassembler.getRomInfo();
if (options.verbose) {
console.log('š ROM Information:');
console.log(` Title: ${romInfo.header.title || 'Unknown'}`);
console.log(` Size: ${romInfo.data.length} bytes (${(romInfo.data.length / 1024).toFixed(1)} KB)`);
console.log(` Type: ${romInfo.cartridgeInfo.type}`);
console.log(` Has Battery: ${romInfo.cartridgeInfo.hasBattery ? 'Yes' : 'No'}`);
if (romInfo.cartridgeInfo.specialChip) {
console.log(` Special Chip: ${romInfo.cartridgeInfo.specialChip}`);
}
// Enhanced ROM analysis
if (options.enhancedDisasm && disassembler instanceof enhanced_disassembly_engine_1.EnhancedDisassemblyEngine) {
const analysis = disassembler.performROMAnalysis();
console.log(` Detected Vectors: ${analysis.vectors.length}`);
console.log(` Potential Functions: ${analysis.functions.length}`);
console.log(` Data Regions: ${analysis.dataRegions.length}`);
}
console.log('');
}
// Parse and validate address range
const { startAddress, endAddress } = parseAddressRange(options, errorHandler);
if (options.verbose && (startAddress !== undefined || endAddress !== undefined)) {
console.log('šÆ Address Range:');
console.log(` Start: $${(startAddress || 0x8000).toString(16).toUpperCase()}`);
console.log(` End: $${(endAddress || 0xFFFF).toString(16).toUpperCase()}`);
console.log('');
}
// Load symbols if specified
const symbolManager = await loadSymbols(options, errorHandler);
// Perform disassembly
if (options.verbose) {
console.log('š Disassembling...');
if (options.enhancedDisasm) {
console.log(' Using enhanced algorithms with MCP server insights');
}
}
const startTime = Date.now();
let disassemblyLines;
try {
if (options.analysis || options.enhancedDisasm) {
// Run full analysis including enhanced features
if (disassembler instanceof enhanced_disassembly_engine_1.EnhancedDisassemblyEngine) {
disassembler.setAnalysisOptions({
controlFlowAnalysis: true,
functionDetection: options.detectFunctions || false,
dataStructureRecognition: true,
crossReferenceGeneration: true,
gamePatternRecognition: !options.disableAI
});
}
disassembler.analyze();
disassemblyLines = disassembler.disassemble(startAddress, endAddress);
}
else {
// Basic disassembly only
disassemblyLines = disassembler.disassemble(startAddress, endAddress);
}
}
catch (error) {
throw new Error(`Disassembly failed: ${error instanceof Error ? error.message : error}`);
}
const disassemblyTime = Date.now() - startTime;
if (options.verbose) {
console.log(`ā
Disassembly completed in ${disassemblyTime}ms`);
console.log(`š Instructions: ${disassemblyLines.length}`);
if (options.enhancedDisasm && disassembler instanceof enhanced_disassembly_engine_1.EnhancedDisassemblyEngine) {
const stats = disassembler.getDisassemblyStats();
console.log(`š Functions detected: ${stats.functionsDetected}`);
console.log(`š Cross-references: ${stats.crossReferences}`);
console.log(`š·ļø Labels generated: ${stats.labelsGenerated}`);
}
console.log('');
}
// Generate output
const outputFile = await generateOutput(disassemblyLines, romFile, options, romInfo, symbolManager, errorHandler);
// Generate quality report if requested
if (options.quality) {
await generateQualityReport(outputFile, disassembler, options, errorHandler);
}
// Generate documentation if requested
if (options.generateDocs && disassembler instanceof enhanced_disassembly_engine_1.EnhancedDisassemblyEngine) {
await generateDocumentation(outputFile, disassembler, options, errorHandler);
}
// Extract assets if requested
if (options.extractAssets) {
await (0, asset_handler_1.extractAssets)(romFile, options, path_1.default.dirname(outputFile));
}
// Show analysis summary if enabled
if ((options.analysis || options.enhancedDisasm) && options.verbose) {
console.log('\nš¬ Analysis Summary:');
console.log(' - Control flow analysis: Enabled');
console.log(` - Function detection: ${options.detectFunctions ? 'Enabled' : 'Disabled'}`);
console.log(' - Data structure recognition: Enabled');
console.log(' - Cross-reference generation: Enabled');
console.log(` - Game pattern recognition: ${!options.disableAI ? 'Enabled' : 'Disabled'}`);
console.log(' - Code quality metrics: Enabled');
if (options.enhancedDisasm) {
console.log(' - Enhanced MCP algorithms: Enabled');
console.log(` - Bank-aware addressing: ${options.bankAware ? 'Enabled' : 'Disabled'}`);
}
}
if (!options.verbose) {
console.log(`Disassembly complete: ${outputFile}`);
}
}
function parseAddressRange(options, errorHandler) {
const startAddress = options.start ? parseInt(options.start, 16) : undefined;
const endAddress = options.end ? parseInt(options.end, 16) : undefined;
if (startAddress !== undefined && isNaN(startAddress)) {
throw new Error(`Invalid start address: ${options.start}`);
}
if (endAddress !== undefined && isNaN(endAddress)) {
throw new Error(`Invalid end address: ${options.end}`);
}
if (startAddress !== undefined && endAddress !== undefined && startAddress >= endAddress) {
throw new Error(`Start address ($${startAddress.toString(16)}) must be less than end address ($${endAddress.toString(16)})`);
}
return { startAddress, endAddress };
}
async function loadSymbols(options, errorHandler) {
if (!options.symbols) {
return undefined;
}
if (options.verbose) {
console.log(`š Loading symbols from: ${options.symbols}`);
}
try {
const symbolManager = new symbol_manager_1.SymbolManager();
// Note: loadFromFile method needs to be implemented in SymbolManager
// await symbolManager.loadFromFile(options.symbols);
if (options.verbose) {
const symbolCount = 0; // symbolManager.getAllSymbols()?.size || 0;
console.log(` ā
Loaded ${symbolCount} symbols`);
}
return symbolManager;
}
catch (error) {
throw new Error(`Failed to load symbols: ${error instanceof Error ? error.message : error}`);
}
}
async function generateOutput(disassemblyLines, romFile, options, romInfo, symbolManager, errorHandler) {
const outputOptions = {
includeBytes: true,
includeComments: true,
includeSymbols: true,
includeCrossReferences: options.analysis || options.enhancedDisasm || false,
includeHeader: true,
includeTiming: false,
lineNumbers: false,
uppercase: true,
tabWidth: 4
};
const formatter = output_formats_extended_1.ExtendedOutputFormatterFactory.create(options.format || 'ca65', romInfo, symbolManager?.getAllSymbols(), undefined, // crossRefs - would be generated by enhanced engine
outputOptions);
const output = formatter.format(disassemblyLines);
// Determine output file name and directory
let outputFile = options.output;
if (!outputFile) {
const baseName = path_1.default.basename(romFile, path_1.default.extname(romFile));
const extension = getFileExtension(options.format);
const fileName = `${baseName}.${extension}`;
if (options.outputDir) {
await fs_1.promises.mkdir(options.outputDir, { recursive: true });
outputFile = path_1.default.join(options.outputDir, fileName);
}
else {
outputFile = fileName;
}
}
else if (options.outputDir) {
await fs_1.promises.mkdir(options.outputDir, { recursive: true });
outputFile = path_1.default.join(options.outputDir, path_1.default.basename(outputFile));
}
try {
await fs_1.promises.writeFile(outputFile, output, 'utf8');
}
catch (error) {
throw new Error(`Failed to write output file: ${error instanceof Error ? error.message : error}`);
}
if (options.verbose) {
console.log(`š¾ Output written to: ${outputFile}`);
console.log(`š Output size: ${output.length} characters (${(output.length / 1024).toFixed(1)} KB)`);
}
return outputFile;
}
async function generateQualityReport(outputFile, disassembler, options, errorHandler) {
const outputDir = path_1.default.dirname(outputFile);
const baseName = path_1.default.basename(outputFile, path_1.default.extname(outputFile));
const qualityReportFile = path_1.default.join(outputDir, `${baseName}_quality.md`);
try {
const qualityReporter = new quality_reporter_1.QualityReporter();
const metrics = qualityReporter.analyzeQuality(disassembler);
const report = qualityReporter.generateReport();
await fs_1.promises.writeFile(qualityReportFile, report, 'utf8');
if (options.verbose) {
console.log(`š Quality report generated: ${qualityReportFile}`);
}
}
catch (error) {
console.warn(`Warning: Could not generate quality report: ${error instanceof Error ? error.message : error}`);
}
}
async function generateDocumentation(outputFile, disassembler, options, errorHandler) {
const outputDir = path_1.default.dirname(outputFile);
const baseName = path_1.default.basename(outputFile, path_1.default.extname(outputFile));
const docsDir = path_1.default.join(outputDir, `${baseName}_docs`);
try {
await fs_1.promises.mkdir(docsDir, { recursive: true });
const analysis = disassembler.performROMAnalysis();
// Generate function documentation
if (analysis.functions.length > 0) {
const functionsDoc = generateFunctionsDocumentation(analysis.functions);
await fs_1.promises.writeFile(path_1.default.join(docsDir, 'functions.md'), functionsDoc, 'utf8');
}
// Generate memory map documentation
const memoryMapDoc = generateMemoryMapDocumentation(analysis);
await fs_1.promises.writeFile(path_1.default.join(docsDir, 'memory-map.md'), memoryMapDoc, 'utf8');
// Generate overview documentation
const overviewDoc = generateOverviewDocumentation(analysis, disassembler.getRomInfo());
await fs_1.promises.writeFile(path_1.default.join(docsDir, 'README.md'), overviewDoc, 'utf8');
if (options.verbose) {
console.log(`š Documentation generated: ${docsDir}`);
}
}
catch (error) {
console.warn(`Warning: Could not generate documentation: ${error instanceof Error ? error.message : error}`);
}
}
function generateFunctionsDocumentation(functions) {
let doc = '# Detected Functions\n\n';
doc += 'This document lists all functions detected during disassembly analysis.\n\n';
functions.forEach((func, index) => {
doc += `## Function ${index + 1}: ${func.name || `func_${func.address.toString(16).toUpperCase()}`}\n\n`;
doc += `- **Address**: $${func.address.toString(16).toUpperCase()}\n`;
doc += `- **Size**: ${func.size} bytes\n`;
doc += `- **Type**: ${func.type || 'Unknown'}\n`;
if (func.description) {
doc += `- **Description**: ${func.description}\n`;
}
doc += '\n';
});
return doc;
}
function generateMemoryMapDocumentation(analysis) {
let doc = '# Memory Map\n\n';
doc += 'This document describes the memory layout discovered during analysis.\n\n';
if (analysis.dataRegions && analysis.dataRegions.length > 0) {
doc += '## Data Regions\n\n';
analysis.dataRegions.forEach((region, index) => {
doc += `### Region ${index + 1}\n\n`;
doc += `- **Start**: $${region.start.toString(16).toUpperCase()}\n`;
doc += `- **End**: $${region.end.toString(16).toUpperCase()}\n`;
doc += `- **Type**: ${region.type || 'Data'}\n`;
doc += `- **Size**: ${region.size} bytes\n\n`;
});
}
if (analysis.vectors && analysis.vectors.length > 0) {
doc += '## Interrupt Vectors\n\n';
analysis.vectors.forEach((vector) => {
doc += `- **${vector.name}**: $${vector.address.toString(16).toUpperCase()}\n`;
});
doc += '\n';
}
return doc;
}
function generateOverviewDocumentation(analysis, romInfo) {
let doc = `# ${romInfo.header.title || 'Unknown ROM'} - Disassembly Analysis\n\n`;
doc += 'This directory contains the complete disassembly analysis results.\n\n';
doc += '## ROM Information\n\n';
doc += `- **Title**: ${romInfo.header.title || 'Unknown'}\n`;
doc += `- **Size**: ${romInfo.data.length} bytes\n`;
doc += `- **Type**: ${romInfo.cartridgeInfo.type}\n`;
doc += `- **Mapping**: ${romInfo.cartridgeInfo.mapping || 'LoROM'}\n\n`;
doc += '## Analysis Summary\n\n';
doc += `- **Functions Detected**: ${analysis.functions?.length || 0}\n`;
doc += `- **Data Regions**: ${analysis.dataRegions?.length || 0}\n`;
doc += `- **Interrupt Vectors**: ${analysis.vectors?.length || 0}\n\n`;
doc += '## Files\n\n';
doc += '- `functions.md` - Detailed function documentation\n';
doc += '- `memory-map.md` - Memory layout and regions\n';
doc += '- `README.md` - This overview document\n\n';
return doc;
}
function getFileExtension(format) {
const extensions = {
'ca65': 'asm',
'wla-dx': 'asm',
'bass': 'asm',
'html': 'html',
'json': 'json',
'xml': 'xml',
'csv': 'csv',
'markdown': 'md'
};
return extensions[format] || 'asm';
}
//# sourceMappingURL=disassembly-handler.js.map