snes-disassembler
Version:
A Super Nintendo (SNES) ROM disassembler for 65816 assembly
554 lines • 20.9 kB
JavaScript
"use strict";
/**
* CLI Session Manager
*
* Handles session state persistence, recent files, and user preferences
*/
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.sessionManager = exports.SessionManager = void 0;
const fs = __importStar(require("fs/promises"));
const path = __importStar(require("path"));
const os = __importStar(require("os"));
class SessionManager {
constructor() {
const configDir = path.join(os.homedir(), '.snes-disassembler');
this.sessionFile = path.join(configDir, 'session.json');
this.session = this.getDefaultSession();
}
getDefaultSession() {
return {
recentFiles: [],
preferences: {
defaultOutputDir: './output',
globalOutputDir: undefined, // No global output directory set by default
defaultFormat: 'ca65',
showHelp: true,
confirmActions: true,
maxRecentFiles: 10
},
projects: []
};
}
async load() {
try {
// Ensure config directory exists
await fs.mkdir(path.dirname(this.sessionFile), { recursive: true });
// Try to load existing session
const data = await fs.readFile(this.sessionFile, 'utf-8');
this.session = { ...this.getDefaultSession(), ...JSON.parse(data) };
}
catch (error) {
// File doesn't exist or is corrupted, use defaults
this.session = this.getDefaultSession();
}
return this.session;
}
async save() {
try {
await fs.mkdir(path.dirname(this.sessionFile), { recursive: true });
await fs.writeFile(this.sessionFile, JSON.stringify(this.session, null, 2));
}
catch (error) {
console.warn('Failed to save session:', error);
}
}
async addRecentFile(filePath, romInfo) {
const stats = await fs.stat(filePath);
const recentFile = {
path: filePath,
name: path.basename(filePath),
size: this.formatFileSize(stats.size),
lastUsed: new Date().toISOString(),
romInfo
};
// Remove existing entry if present
this.session.recentFiles = this.session.recentFiles.filter(f => f.path !== filePath);
// Add to beginning
this.session.recentFiles.unshift(recentFile);
// Limit to max recent files
const maxFiles = this.session.preferences.maxRecentFiles ?? 10;
this.session.recentFiles = this.session.recentFiles.slice(0, maxFiles);
await this.save();
}
getRecentFiles() {
return this.session.recentFiles;
}
async addProject(project) {
// Remove existing project with same name
this.session.projects = this.session.projects.filter(p => p.name !== project.name);
// Add new project
this.session.projects.unshift(project);
// Limit to reasonable number of projects
this.session.projects = this.session.projects.slice(0, 20);
await this.save();
}
getProjects() {
return this.session.projects;
}
updatePreferences(preferences) {
this.session.preferences = { ...this.session.preferences, ...preferences };
}
getPreferences() {
return this.session.preferences;
}
setCurrentROM(romFile) {
this.session.currentROM = romFile;
}
getCurrentROM() {
return this.session.currentROM;
}
formatFileSize(bytes) {
const units = ['B', 'KB', 'MB', 'GB'];
let size = bytes;
let unitIndex = 0;
while (size >= 1024 && unitIndex < units.length - 1) {
size /= 1024;
unitIndex++;
}
return `${size.toFixed(1)} ${units[unitIndex]}`;
}
async clearRecentFiles() {
this.session.recentFiles = [];
await this.save();
}
async removeRecentFile(filePath) {
this.session.recentFiles = this.session.recentFiles.filter(f => f.path !== filePath);
await this.save();
}
// Clean up recent files that no longer exist
async cleanupRecentFiles() {
const validFiles = [];
for (const file of this.session.recentFiles) {
try {
await fs.access(file.path);
validFiles.push(file);
}
catch {
// File no longer exists, skip it
}
}
if (validFiles.length !== this.session.recentFiles.length) {
this.session.recentFiles = validFiles;
await this.save();
}
}
// Global output directory management
/**
* Set the global default output directory for the current session
* @param outputDir - The directory path to set as global default
*/
async setGlobalOutputDir(outputDir) {
// Validate the directory path
try {
// Try to create the directory if it doesn't exist
await fs.mkdir(outputDir, { recursive: true });
// Verify we can access it
await fs.access(outputDir);
}
catch (error) {
throw new Error(`Cannot access or create output directory: ${outputDir}`);
}
this.session.preferences.globalOutputDir = path.resolve(outputDir);
await this.save();
}
/**
* Get the global default output directory for the current session
* @returns The global output directory path, or undefined if not set
*/
getGlobalOutputDir() {
return this.session.preferences.globalOutputDir;
}
/**
* Clear the global default output directory setting
*/
async clearGlobalOutputDir() {
this.session.preferences.globalOutputDir = undefined;
await this.save();
}
/**
* Get the effective output directory - global if set, otherwise default
* @param specificDir - Optional specific directory that takes precedence
* @returns The output directory to use
*/
getEffectiveOutputDir(specificDir) {
if (specificDir) {
return specificDir;
}
return this.session.preferences.globalOutputDir ??
this.session.preferences.defaultOutputDir ??
'./output';
}
/**
* Check if a global output directory is currently set
* @returns True if global output directory is set, false otherwise
*/
hasGlobalOutputDir() {
return !!this.session.preferences.globalOutputDir;
}
// Advanced Preferences Management
/**
* Update advanced analysis options preferences
* @param options - Advanced options to update
*/
async updateAdvancedOptions(options) {
if (!this.session.preferences.advancedOptions) {
this.session.preferences.advancedOptions = {};
}
this.session.preferences.advancedOptions = {
...this.session.preferences.advancedOptions,
...options
};
await this.save();
}
/**
* Get advanced analysis options preferences
* @returns Current advanced options preferences
*/
getAdvancedOptions() {
return this.session.preferences.advancedOptions ?? {};
}
/**
* Update asset extraction preferences
* @param preferences - Asset preferences to update
*/
async updateAssetPreferences(preferences) {
if (!this.session.preferences.assetPreferences) {
this.session.preferences.assetPreferences = {};
}
this.session.preferences.assetPreferences = {
...this.session.preferences.assetPreferences,
...preferences
};
await this.save();
}
/**
* Get asset extraction preferences
* @returns Current asset preferences
*/
getAssetPreferences() {
return this.session.preferences.assetPreferences ?? {};
}
/**
* Update BRR audio preferences
* @param preferences - BRR preferences to update
*/
async updateBRRPreferences(preferences) {
if (!this.session.preferences.brrPreferences) {
this.session.preferences.brrPreferences = {};
}
this.session.preferences.brrPreferences = {
...this.session.preferences.brrPreferences,
...preferences
};
await this.save();
}
/**
* Get BRR audio preferences
* @returns Current BRR preferences
*/
getBRRPreferences() {
return this.session.preferences.brrPreferences ?? {};
}
/**
* Update analysis preferences
* @param preferences - Analysis preferences to update
*/
async updateAnalysisPreferences(preferences) {
if (!this.session.preferences.analysisPreferences) {
this.session.preferences.analysisPreferences = {};
}
this.session.preferences.analysisPreferences = {
...this.session.preferences.analysisPreferences,
...preferences
};
await this.save();
}
/**
* Get analysis preferences
* @returns Current analysis preferences
*/
getAnalysisPreferences() {
return this.session.preferences.analysisPreferences ?? {};
}
/**
* Update output format preferences
* @param preferences - Format preferences to update
*/
async updateFormatPreferences(preferences) {
if (!this.session.preferences.formatPreferences) {
this.session.preferences.formatPreferences = {};
}
this.session.preferences.formatPreferences = {
...this.session.preferences.formatPreferences,
...preferences
};
await this.save();
}
/**
* Get output format preferences
* @returns Current format preferences
*/
getFormatPreferences() {
return this.session.preferences.formatPreferences ?? {};
}
/**
* Update UI/UX preferences
* @param preferences - UI preferences to update
*/
async updateUIPreferences(preferences) {
if (!this.session.preferences.uiPreferences) {
this.session.preferences.uiPreferences = {};
}
this.session.preferences.uiPreferences = {
...this.session.preferences.uiPreferences,
...preferences
};
await this.save();
}
/**
* Get UI/UX preferences
* @returns Current UI preferences
*/
getUIPreferences() {
return this.session.preferences.uiPreferences ?? {};
}
/**
* Update AI feature preferences
* @param preferences - AI preferences to update
*/
async updateAIPreferences(preferences) {
if (!this.session.preferences.aiPreferences) {
this.session.preferences.aiPreferences = {};
}
this.session.preferences.aiPreferences = {
...this.session.preferences.aiPreferences,
...preferences
};
await this.save();
}
/**
* Get AI feature preferences
* @returns Current AI preferences
*/
getAIPreferences() {
return this.session.preferences.aiPreferences ?? {};
}
/**
* Apply saved preferences to CLI options
* @param baseOptions - Base CLI options to extend
* @returns CLI options with preferences applied
*/
applyPreferencesToOptions(baseOptions = {}) {
const prefs = this.session.preferences;
const advanced = prefs.advancedOptions ?? {};
const asset = prefs.assetPreferences ?? {};
const brr = prefs.brrPreferences ?? {};
// const analysis = prefs.analysisPreferences ?? {}; // unused
// const format = prefs.formatPreferences ?? {}; // unused
const ui = prefs.uiPreferences ?? {};
// const ai = prefs.aiPreferences ?? {}; // unused
return {
...baseOptions,
// Apply basic preferences
format: baseOptions.format ?? prefs.defaultFormat ?? 'ca65',
outputDir: baseOptions.outputDir ?? this.getEffectiveOutputDir(),
verbose: baseOptions.verbose ?? (ui.showProgressBars !== false),
// Apply advanced analysis options
analysis: baseOptions.analysis ?? advanced.analysis,
enhancedDisasm: baseOptions.enhancedDisasm ?? advanced.enhancedDisasm,
bankAware: baseOptions.bankAware ?? advanced.bankAware,
detectFunctions: baseOptions.detectFunctions ?? advanced.detectFunctions,
generateDocs: baseOptions.generateDocs ?? advanced.generateDocs,
extractAssets: baseOptions.extractAssets ?? advanced.extractAssets,
quality: baseOptions.quality ?? advanced.quality,
disableAI: baseOptions.disableAI ?? advanced.disableAI,
// Apply asset extraction preferences
assetTypes: baseOptions.assetTypes ?? asset.defaultAssetTypes?.join(','),
assetFormats: baseOptions.assetFormats ?? asset.defaultAssetFormats?.join(','),
// Apply BRR audio preferences
brrSampleRate: baseOptions.brrSampleRate ?? brr.defaultSampleRate?.toString(),
brrEnableLooping: baseOptions.brrEnableLooping ?? brr.enableLooping,
brrMaxSamples: baseOptions.brrMaxSamples ?? brr.maxSamples?.toString()
};
}
/**
* Get preferences summary for display
* @returns Human-readable summary of current preferences
*/
getPreferencesSummary() {
const prefs = this.session.preferences;
const lines = [];
lines.push('Current User Preferences:');
lines.push('=' + '='.repeat(50));
// Basic preferences
lines.push('');
lines.push('Basic Settings:');
lines.push(` Default Format: ${prefs.defaultFormat ?? 'ca65'}`);
lines.push(` Default Output Dir: ${prefs.defaultOutputDir ?? './output'}`);
lines.push(` Global Output Dir: ${prefs.globalOutputDir ?? 'Not set'}`);
lines.push(` Max Recent Files: ${prefs.maxRecentFiles ?? 10}`);
lines.push(` Show Help: ${prefs.showHelp !== false ? 'Yes' : 'No'}`);
lines.push(` Confirm Actions: ${prefs.confirmActions !== false ? 'Yes' : 'No'}`);
// Advanced options
if (prefs.advancedOptions) {
lines.push('');
lines.push('Advanced Analysis Options:');
const advanced = prefs.advancedOptions;
Object.entries(advanced).forEach(([key, value]) => {
if (value !== undefined) {
lines.push(` ${key}: ${value ? 'Enabled' : 'Disabled'}`);
}
});
}
// Asset preferences
if (prefs.assetPreferences) {
lines.push('');
lines.push('Asset Extraction Preferences:');
const asset = prefs.assetPreferences;
if (asset.defaultAssetTypes) {
lines.push(` Default Asset Types: ${asset.defaultAssetTypes.join(', ')}`);
}
if (asset.defaultAssetFormats) {
lines.push(` Default Asset Formats: ${asset.defaultAssetFormats.join(', ')}`);
}
if (asset.defaultAssetOutputDir) {
lines.push(` Default Asset Output Dir: ${asset.defaultAssetOutputDir}`);
}
}
// BRR preferences
if (prefs.brrPreferences) {
lines.push('');
lines.push('BRR Audio Preferences:');
const brr = prefs.brrPreferences;
if (brr.defaultSampleRate) {
lines.push(` Default Sample Rate: ${brr.defaultSampleRate} Hz`);
}
if (brr.enableLooping !== undefined) {
lines.push(` Enable Looping: ${brr.enableLooping ? 'Yes' : 'No'}`);
}
if (brr.maxSamples) {
lines.push(` Max Samples: ${brr.maxSamples}`);
}
if (brr.defaultOutputFormat) {
lines.push(` Default Output Format: ${brr.defaultOutputFormat}`);
}
}
// Analysis preferences
if (prefs.analysisPreferences) {
lines.push('');
lines.push('Analysis Preferences:');
const analysis = prefs.analysisPreferences;
if (analysis.defaultAnalysisTypes) {
lines.push(` Default Analysis Types: ${analysis.defaultAnalysisTypes.join(', ')}`);
}
if (analysis.defaultAnalysisOutputDir) {
lines.push(` Default Analysis Output Dir: ${analysis.defaultAnalysisOutputDir}`);
}
if (analysis.generateHtmlReports !== undefined) {
lines.push(` Generate HTML Reports: ${analysis.generateHtmlReports ? 'Yes' : 'No'}`);
}
if (analysis.includeQualityMetrics !== undefined) {
lines.push(` Include Quality Metrics: ${analysis.includeQualityMetrics ? 'Yes' : 'No'}`);
}
}
// AI preferences
if (prefs.aiPreferences) {
lines.push('');
lines.push('AI Feature Preferences:');
const ai = prefs.aiPreferences;
if (ai.enableAIFeatures !== undefined) {
lines.push(` Enable AI Features: ${ai.enableAIFeatures ? 'Yes' : 'No'}`);
}
if (ai.aiConfidenceThreshold) {
lines.push(` AI Confidence Threshold: ${ai.aiConfidenceThreshold}`);
}
if (ai.useContextualNaming !== undefined) {
lines.push(` Use Contextual Naming: ${ai.useContextualNaming ? 'Yes' : 'No'}`);
}
if (ai.generateAIDocumentation !== undefined) {
lines.push(` Generate AI Documentation: ${ai.generateAIDocumentation ? 'Yes' : 'No'}`);
}
}
return lines.join('\n');
}
/**
* Reset all preferences to defaults
*/
async resetPreferences() {
this.session.preferences = this.getDefaultSession().preferences;
await this.save();
}
/**
* Export preferences to a JSON file
* @param filePath - Path to export preferences to
*/
async exportPreferences(filePath) {
const prefsData = {
exportedAt: new Date().toISOString(),
version: '1.0.0',
preferences: this.session.preferences
};
await fs.writeFile(filePath, JSON.stringify(prefsData, null, 2));
}
/**
* Import preferences from a JSON file
* @param filePath - Path to import preferences from
*/
async importPreferences(filePath) {
try {
const data = await fs.readFile(filePath, 'utf-8');
const prefsData = JSON.parse(data);
if (prefsData.preferences) {
// Merge with current preferences, preserving structure
this.session.preferences = {
...this.session.preferences,
...prefsData.preferences
};
await this.save();
}
else {
throw new Error('Invalid preferences file format');
}
}
catch (error) {
throw new Error(`Failed to import preferences: ${error instanceof Error ? error.message : error}`);
}
}
}
exports.SessionManager = SessionManager;
// Singleton instance
exports.sessionManager = new SessionManager();
//# sourceMappingURL=session-manager.js.map