UNPKG

snes-disassembler

Version:

A Super Nintendo (SNES) ROM disassembler for 65816 assembly

397 lines • 18.4 kB
"use strict"; /** * 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