UNPKG

snes-disassembler

Version:

A Super Nintendo (SNES) ROM disassembler for 65816 assembly

367 lines 13.4 kB
"use strict"; /** * ROM Analysis Cache System * * Reduces redundant ROM analysis calls by implementing intelligent caching * and streamlined validation steps while maintaining analysis integrity. */ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); Object.defineProperty(exports, "__esModule", { value: true }); exports.globalROMCache = exports.ROMAnalysisCache = void 0; const logger_1 = require("./utils/logger"); const crypto = __importStar(require("crypto")); /** * Centralized cache for ROM analysis results to eliminate redundant calculations */ class ROMAnalysisCache { constructor(config = {}) { this.memoryCache = new Map(); this.stats = { hits: 0, misses: 0, evictions: 0, cacheSize: 0 }; // Recursion guard: Track keys currently being processed this.currentlyProcessing = new Set(); this.recursionDepth = new Map(); this.MAX_RECURSION_DEPTH = 5; this.config = { maxCacheAge: 1000 * 60 * 30, // 30 minutes maxCacheSize: 100, enableMemoryCache: true, enablePersistentCache: false, cacheValidation: true, ...config }; this.logger = (0, logger_1.createLogger)('ROMAnalysisCache'); } /** * Generate cache key based on ROM content and analysis parameters */ generateCacheKey(rom, analysisType, params) { const romHash = this.calculateROMHash(rom.data); const paramsHash = params ? crypto.createHash('md5').update(JSON.stringify(params)).digest('hex').substring(0, 8) : ''; return `${analysisType}_${romHash}_${paramsHash}`; } /** * Calculate fast hash of ROM data for cache key generation */ calculateROMHash(romData) { // Use a subset of ROM data for faster hashing while maintaining uniqueness const sampleSize = Math.min(0x8000, romData.length); // 32KB sample const sample = Buffer.concat([ romData.slice(0, Math.min(0x200, romData.length)), // Header area romData.slice(Math.max(0, romData.length - 0x200)), // Vector area romData.slice(0x8000, 0x8000 + Math.min(0x1000, romData.length - 0x8000)) // Code area sample ]); return crypto.createHash('md5').update(sample).digest('hex').substring(0, 16); } /** * Check if cache entry is valid and not expired */ isValidCacheEntry(entry) { const now = Date.now(); const isExpired = (now - entry.timestamp) > this.config.maxCacheAge; return !isExpired; } /** * Check if a key is currently being processed (recursion guard) */ isCurrentlyProcessing(key) { return this.currentlyProcessing.has(key); } /** * Mark a key as currently being processed and track recursion depth */ markAsProcessing(key) { const currentDepth = this.recursionDepth.get(key) || 0; if (currentDepth >= this.MAX_RECURSION_DEPTH) { this.logger.warn(`Maximum recursion depth (${this.MAX_RECURSION_DEPTH}) exceeded for cache key`, { key: key.substring(0, 16) + '...', depth: currentDepth }); return false; } if (this.currentlyProcessing.has(key)) { this.logger.warn(`Recursion detected for cache key`, { key: key.substring(0, 16) + '...', depth: currentDepth }); return false; } this.currentlyProcessing.add(key); this.recursionDepth.set(key, currentDepth + 1); this.logger.debug(`Marking key as processing`, { key: key.substring(0, 16) + '...', depth: currentDepth + 1 }); return true; } /** * Unmark a key as currently being processed */ unmarkAsProcessing(key) { this.currentlyProcessing.delete(key); const currentDepth = this.recursionDepth.get(key) || 0; if (currentDepth <= 1) { this.recursionDepth.delete(key); } else { this.recursionDepth.set(key, currentDepth - 1); } this.logger.debug(`Unmarking key as processing`, { key: key.substring(0, 16) + '...', depth: Math.max(0, currentDepth - 1) }); } /** * Get cached analysis result if available and valid */ get(rom, analysisType, params) { if (!this.config.enableMemoryCache) return null; const key = this.generateCacheKey(rom, analysisType, params); // Check for recursion if (this.isCurrentlyProcessing(key)) { this.logger.warn(`Recursion attempt detected for cache get operation`, { analysisType, key: key.substring(0, 16) + '...', currentDepth: this.recursionDepth.get(key) || 0 }); return null; } const entry = this.memoryCache.get(key); if (entry && this.isValidCacheEntry(entry)) { this.stats.hits++; this.logger.debug(`Cache HIT for ${analysisType}`, { key: key.substring(0, 16) + '...' }); return entry.data; } this.stats.misses++; this.logger.debug(`Cache MISS for ${analysisType}`, { key: key.substring(0, 16) + '...' }); return null; } /** * Store analysis result in cache */ set(rom, analysisType, data, params) { if (!this.config.enableMemoryCache) return; const key = this.generateCacheKey(rom, analysisType, params); // Check for recursion if (this.isCurrentlyProcessing(key)) { this.logger.warn(`Recursion attempt detected for cache set operation`, { analysisType, key: key.substring(0, 16) + '...', currentDepth: this.recursionDepth.get(key) || 0 }); return; } // Implement LRU eviction if cache is full if (this.memoryCache.size >= this.config.maxCacheSize) { this.evictOldestEntry(); } const entry = { hash: key, timestamp: Date.now(), data, metadata: { romSize: rom.data.length, cartridgeType: rom.cartridgeInfo.type, version: '1.0' } }; this.memoryCache.set(key, entry); this.stats.cacheSize = this.memoryCache.size; this.logger.debug(`Cached ${analysisType}`, { key: key.substring(0, 16) + '...', size: this.memoryCache.size }); } /** * Evict oldest cache entry (LRU) */ evictOldestEntry() { let oldestKey = ''; let oldestTimestamp = Date.now(); for (const [key, entry] of this.memoryCache.entries()) { if (entry.timestamp < oldestTimestamp) { oldestTimestamp = entry.timestamp; oldestKey = key; } } if (oldestKey) { this.memoryCache.delete(oldestKey); this.stats.evictions++; this.logger.debug(`Evicted cache entry`, { key: oldestKey.substring(0, 16) + '...' }); } } /** * Invalidate all cache entries for a specific ROM */ invalidateROM(rom) { const romHash = this.calculateROMHash(rom.data); const keysToDelete = []; for (const key of this.memoryCache.keys()) { if (key.includes(romHash)) { keysToDelete.push(key); } } keysToDelete.forEach(key => this.memoryCache.delete(key)); this.stats.cacheSize = this.memoryCache.size; this.logger.info(`Invalidated ${keysToDelete.length} cache entries for ROM`); } /** * Clear all cache entries */ clear() { const size = this.memoryCache.size; this.memoryCache.clear(); this.stats.cacheSize = 0; this.logger.info(`Cleared ${size} cache entries`); } /** * Get cache statistics */ getStats() { const total = this.stats.hits + this.stats.misses; const hitRate = total > 0 ? (this.stats.hits / total) * 100 : 0; return { ...this.stats, hitRate: Math.round(hitRate * 100) / 100 }; } /** * Begin processing for a specific analysis operation (public API) * Returns false if recursion is detected or max depth exceeded */ beginProcessing(rom, analysisType, params) { const key = this.generateCacheKey(rom, analysisType, params); return this.markAsProcessing(key); } /** * End processing for a specific analysis operation (public API) */ endProcessing(rom, analysisType, params) { const key = this.generateCacheKey(rom, analysisType, params); this.unmarkAsProcessing(key); } /** * Check if a specific analysis operation is currently being processed */ isProcessing(rom, analysisType, params) { const key = this.generateCacheKey(rom, analysisType, params); return this.isCurrentlyProcessing(key); } /** * Get current recursion depth for a specific operation */ getRecursionDepth(rom, analysisType, params) { const key = this.generateCacheKey(rom, analysisType, params); return this.recursionDepth.get(key) || 0; } /** * Clear all processing state (useful for cleanup after errors) */ clearProcessingState() { const processingCount = this.currentlyProcessing.size; const depthCount = this.recursionDepth.size; this.currentlyProcessing.clear(); this.recursionDepth.clear(); if (processingCount > 0 || depthCount > 0) { this.logger.warn(`Cleared orphaned processing state`, { processingKeys: processingCount, depthEntries: depthCount }); } } /** * Convenience methods for specific analysis types */ getROMInfo(rom) { return this.get(rom, ROMAnalysisCache.CACHE_KEYS.ROM_INFO); } setROMInfo(rom) { this.set(rom, ROMAnalysisCache.CACHE_KEYS.ROM_INFO, rom); } getVectors(rom) { return this.get(rom, ROMAnalysisCache.CACHE_KEYS.VECTORS); } setVectors(rom, vectors) { this.set(rom, ROMAnalysisCache.CACHE_KEYS.VECTORS, vectors); } getBankLayout(rom) { return this.get(rom, ROMAnalysisCache.CACHE_KEYS.BANK_LAYOUT); } setBankLayout(rom, bankLayout) { this.set(rom, ROMAnalysisCache.CACHE_KEYS.BANK_LAYOUT, bankLayout); } getFunctions(rom, params) { return this.get(rom, ROMAnalysisCache.CACHE_KEYS.FUNCTIONS, params); } setFunctions(rom, functions, params) { this.set(rom, ROMAnalysisCache.CACHE_KEYS.FUNCTIONS, functions, params); } getValidationResult(rom, params) { return this.get(rom, ROMAnalysisCache.CACHE_KEYS.VALIDATION, params); } setValidationResult(rom, result, params) { this.set(rom, ROMAnalysisCache.CACHE_KEYS.VALIDATION, result, params); } getAudioState(rom) { return this.get(rom, ROMAnalysisCache.CACHE_KEYS.AUDIO_STATE); } setAudioState(rom, audioState) { this.set(rom, ROMAnalysisCache.CACHE_KEYS.AUDIO_STATE, audioState); } getDisassembly(rom, params) { return this.get(rom, ROMAnalysisCache.CACHE_KEYS.DISASSEMBLY, params); } setDisassembly(rom, lines, params) { this.set(rom, ROMAnalysisCache.CACHE_KEYS.DISASSEMBLY, lines, params); } } exports.ROMAnalysisCache = ROMAnalysisCache; // Cache keys for different analysis types ROMAnalysisCache.CACHE_KEYS = { ROM_INFO: 'rom_info', VECTORS: 'vectors', BANK_LAYOUT: 'bank_layout', FUNCTIONS: 'functions', DATA_STRUCTURES: 'data_structures', VALIDATION: 'validation', AUDIO_STATE: 'audio_state', DISASSEMBLY: 'disassembly' }; /** * Singleton cache instance for global use */ exports.globalROMCache = new ROMAnalysisCache(); //# sourceMappingURL=analysis-cache.js.map