const { ipcRenderer } = require('electron'); const CryptoJS = require('crypto-js'); // Application state let currentSaveData = null; let currentFilePath = null; let isModified = false; // AES encryption constants const AES_KEY_WORDS = [2815074099, 1725469378, 4039046167, 874293617, 3063605751, 3133984764, 4097598161, 3620741625]; const AES_KEY = CryptoJS.lib.WordArray.create(AES_KEY_WORDS); const AES_IV = CryptoJS.enc.Hex.parse('7475383967656A693334307438397532'); // Initialize the application document.addEventListener('DOMContentLoaded', () => { setTimeout(() => { startupAnimation(); }, 100); }); function startupAnimation() { const overlay = document.getElementById('startup-overlay'); const progressFill = document.getElementById('startup-progress'); const progressPercent = document.getElementById('progress-percent'); const statusText = document.getElementById('startup-status'); if (!overlay || !progressFill || !progressPercent || !statusText) { initializeApp(); return; } // Initialize progressFill.style.width = '0%'; progressPercent.textContent = '0%'; const steps = [ { progress: 25, status: 'Loading Vault-Tec Database...', delay: 300 }, { progress: 50, status: 'Initializing Encryption...', delay: 300 }, { progress: 75, status: 'Connecting Systems...', delay: 300 }, { progress: 100, status: 'Welcome to Vault-Tec!', delay: 400 } ]; let currentStep = 0; function updateProgress() { if (currentStep < steps.length) { const step = steps[currentStep]; statusText.textContent = step.status; progressFill.style.width = step.progress + '%'; progressPercent.textContent = step.progress + '%'; currentStep++; setTimeout(updateProgress, step.delay); } else { setTimeout(() => { overlay.classList.add('fade-out'); setTimeout(() => { overlay.style.display = 'none'; initializeApp(); }, 800); }, 200); } } setTimeout(updateProgress, 500); } function initializeApp() { initializeEventListeners(); initializeTabSystem(); updateUI(); setStatus('Ready'); setTimeout(() => { showToast('Vault-Tec Save Editor ready!', 'success'); }, 200); } function initializeEventListeners() { // Navigation tabs document.querySelectorAll('.nav-tab').forEach(tab => { tab.addEventListener('click', () => switchTab(tab.dataset.tab)); }); // File operations const openFileBtn = document.getElementById('open-file-btn'); const getStartedBtn = document.getElementById('get-started-btn'); const saveBtn = document.getElementById('save-btn'); const backupBtn = document.getElementById('backup-btn'); if (openFileBtn) openFileBtn.addEventListener('click', openFile); if (getStartedBtn) getStartedBtn.addEventListener('click', openFile); if (saveBtn) saveBtn.addEventListener('click', saveFile); if (backupBtn) backupBtn.addEventListener('click', createBackup); // Resource operations const maxResourcesBtn = document.getElementById('max-resources-btn'); const resetResourcesBtn = document.getElementById('reset-resources-btn'); const applyResourcesBtn = document.getElementById('apply-resources-btn'); if (maxResourcesBtn) maxResourcesBtn.addEventListener('click', maxAllResources); if (resetResourcesBtn) resetResourcesBtn.addEventListener('click', resetResources); if (applyResourcesBtn) applyResourcesBtn.addEventListener('click', applyResourceChanges); // Vault operations const applyVaultBtn = document.getElementById('apply-vault-btn'); if (applyVaultBtn) applyVaultBtn.addEventListener('click', applyVaultChanges); // Raw data operations const formatJsonBtn = document.getElementById('format-json-btn'); const copyJsonBtn = document.getElementById('copy-json-btn'); if (formatJsonBtn) formatJsonBtn.addEventListener('click', formatJSON); if (copyJsonBtn) copyJsonBtn.addEventListener('click', copyJSON); // Dweller operations const maxAllDwellersBtn = document.getElementById('max-all-dwellers-btn'); const refreshDwellersBtn = document.getElementById('refresh-dwellers-btn'); if (maxAllDwellersBtn) maxAllDwellersBtn.addEventListener('click', maxAllDwellers); if (refreshDwellersBtn) refreshDwellersBtn.addEventListener('click', populateDwellers); // IPC listeners ipcRenderer.on('file-opened', handleFileOpened); ipcRenderer.on('menu-save', saveFile); } function initializeTabSystem() { switchTab('overview'); } function switchTab(tabName) { document.querySelectorAll('.nav-tab').forEach(tab => { tab.classList.toggle('active', tab.dataset.tab === tabName); }); document.querySelectorAll('.tab-content').forEach(content => { content.classList.toggle('active', content.id === `${tabName}-tab`); }); } function openFile() { ipcRenderer.send('open-file-dialog'); } async function handleFileOpened(event, fileData) { showLoading(true); setStatus('Loading save file...'); try { currentFilePath = fileData.path; document.getElementById('current-file').textContent = fileData.name; const decryptedData = decryptSaveData(fileData.data.trim()); currentSaveData = JSON.parse(decryptedData); populateResourceInputs(); populateVaultInfo(); populateDwellers(); updateRawData(); document.getElementById('save-btn').disabled = false; document.getElementById('backup-btn').disabled = false; switchTab('resources'); setStatus('Save file loaded successfully'); showToast('Save file loaded successfully', 'success'); setModified(false); } catch (error) { console.error('Error loading save file:', error); showToast(`Error loading save file: ${error.message}`, 'error'); setStatus('Error loading save file'); } finally { showLoading(false); } } function decryptSaveData(base64Data) { try { const ciphertext = CryptoJS.enc.Base64.parse(base64Data); const cipherParams = CryptoJS.lib.CipherParams.create({ ciphertext: ciphertext }); const decrypted = CryptoJS.AES.decrypt(cipherParams, AES_KEY, { iv: AES_IV, mode: CryptoJS.mode.CBC, padding: CryptoJS.pad.Pkcs7 }); const decryptedString = decrypted.toString(CryptoJS.enc.Utf8); if (!decryptedString) { throw new Error('Decryption failed - invalid key or corrupted data'); } return decryptedString; } catch (error) { throw new Error(`Decryption failed: ${error.message}`); } } function encryptSaveData(jsonString) { try { const encrypted = CryptoJS.AES.encrypt(jsonString, AES_KEY, { iv: AES_IV, mode: CryptoJS.mode.CBC, padding: CryptoJS.pad.Pkcs7 }); return encrypted.toString(); } catch (error) { throw new Error(`Encryption failed: ${error.message}`); } } function populateResourceInputs() { if (!currentSaveData || !currentSaveData.vault || !currentSaveData.vault.storage) { console.log('No vault storage data found'); return; } const resources = currentSaveData.vault.storage.resources; console.log('Available resources:', Object.keys(resources)); const resourceMap = { 'caps-input': 'Nuka', 'food-input': 'Food', 'water-input': 'Water', 'power-input': 'Energy', 'stimpaks-input': 'StimPack', 'radaway-input': 'RadAway', 'quantum-input': 'NukaColaQuantum', 'lunchbox-input': 'Lunchbox', 'mrhandy-input': 'MrHandy', 'petcarrier-input': 'PetCarrier' }; Object.entries(resourceMap).forEach(([inputId, resourceKey]) => { const input = document.getElementById(inputId); if (input && resources[resourceKey] !== undefined) { input.value = Math.floor(resources[resourceKey]); console.log(`Set ${inputId} to ${resources[resourceKey]}`); } else { console.log(`Could not find ${inputId} or ${resourceKey}`); } }); } function populateVaultInfo() { if (!currentSaveData || !currentSaveData.vault) { return; } const vault = currentSaveData.vault; const vaultNameInput = document.getElementById('vault-name-input'); if (vaultNameInput && vault.VaultName !== undefined) { vaultNameInput.value = vault.VaultName; } const vaultModeInput = document.getElementById('vault-mode-input'); if (vaultModeInput && vault.VaultMode !== undefined) { vaultModeInput.value = vault.VaultMode; } const vaultThemeInput = document.getElementById('vault-theme-input'); if (vaultThemeInput && vault.VaultTheme !== undefined) { vaultThemeInput.value = vault.VaultTheme; } } function applyResourceChanges() { if (!currentSaveData || !currentSaveData.vault || !currentSaveData.vault.storage) { showToast('No save data loaded', 'warning'); return; } console.log('Applying resource changes...'); const resources = currentSaveData.vault.storage.resources; console.log('Current resources before changes:', resources); const resourceMap = { 'caps-input': 'Nuka', 'food-input': 'Food', 'water-input': 'Water', 'power-input': 'Energy', 'stimpaks-input': 'StimPack', 'radaway-input': 'RadAway', 'quantum-input': 'NukaColaQuantum', 'lunchbox-input': 'Lunchbox', 'mrhandy-input': 'MrHandy', 'petcarrier-input': 'PetCarrier' }; let changesApplied = 0; Object.entries(resourceMap).forEach(([inputId, resourceKey]) => { const input = document.getElementById(inputId); if (input && input.value.trim()) { const value = parseFloat(input.value); if (!isNaN(value) && value >= 0) { console.log(`Changing ${resourceKey} from ${resources[resourceKey]} to ${value}`); resources[resourceKey] = value; changesApplied++; } } }); console.log('Resources after changes:', resources); console.log(`Applied ${changesApplied} changes`); if (changesApplied > 0) { setModified(true); updateRawData(); showToast(`Applied ${changesApplied} resource changes`, 'success'); } else { showToast('No valid changes to apply', 'warning'); } } function maxAllResources() { const maxValues = { 'caps-input': 999999, 'food-input': 999999, 'water-input': 999999, 'power-input': 999999, 'stimpaks-input': 999999, 'radaway-input': 999999, 'quantum-input': 999, 'lunchbox-input': 999, 'mrhandy-input': 99, 'petcarrier-input': 999 }; Object.entries(maxValues).forEach(([inputId, value]) => { const input = document.getElementById(inputId); if (input) { input.value = value; } }); applyResourceChanges(); } function resetResources() { populateResourceInputs(); showToast('Resources reset to saved values', 'info'); } function applyVaultChanges() { if (!currentSaveData || !currentSaveData.vault) { showToast('No save data loaded', 'warning'); return; } const vault = currentSaveData.vault; let changesApplied = 0; const vaultNameInput = document.getElementById('vault-name-input'); if (vaultNameInput && vaultNameInput.value.trim()) { vault.VaultName = vaultNameInput.value.trim(); changesApplied++; } const vaultModeInput = document.getElementById('vault-mode-input'); if (vaultModeInput && vaultModeInput.value) { vault.VaultMode = vaultModeInput.value; changesApplied++; } const vaultThemeInput = document.getElementById('vault-theme-input'); if (vaultThemeInput && vaultThemeInput.value.trim()) { const theme = parseInt(vaultThemeInput.value); if (!isNaN(theme) && theme >= 0) { vault.VaultTheme = theme; changesApplied++; } } if (changesApplied > 0) { setModified(true); updateRawData(); showToast(`Applied ${changesApplied} vault changes`, 'success'); } else { showToast('No valid changes to apply', 'warning'); } } function updateRawData() { const textarea = document.getElementById('raw-data-textarea'); if (currentSaveData && textarea) { textarea.value = JSON.stringify(currentSaveData, null, 2); } } function formatJSON() { const textarea = document.getElementById('raw-data-textarea'); if (!textarea) return; try { const parsed = JSON.parse(textarea.value); textarea.value = JSON.stringify(parsed, null, 2); showToast('JSON formatted successfully', 'success'); } catch (error) { showToast('Invalid JSON format', 'error'); } } function copyJSON() { const textarea = document.getElementById('raw-data-textarea'); if (!textarea) return; textarea.select(); document.execCommand('copy'); showToast('JSON copied to clipboard', 'success'); } async function saveFile() { if (!currentSaveData || !currentFilePath) { showToast('No file loaded to save', 'warning'); return; } console.log('Starting save process...'); console.log('Current file path:', currentFilePath); console.log('Save data exists:', !!currentSaveData); showLoading(true); setStatus('Saving file...'); try { console.log('Converting to JSON...'); const jsonString = JSON.stringify(currentSaveData); console.log('JSON string length:', jsonString.length); console.log('Encrypting data...'); const encryptedData = encryptSaveData(jsonString); console.log('Encrypted data length:', encryptedData.length); console.log('Sending to main process...'); const result = await ipcRenderer.invoke('save-file', currentFilePath, encryptedData); console.log('Save result:', result); if (result.success) { setModified(false); setStatus('File saved successfully'); showToast('File saved successfully', 'success'); console.log('Save completed successfully'); } else { throw new Error(result.error); } } catch (error) { console.error('Error saving file:', error); showToast(`Error saving file: ${error.message}`, 'error'); setStatus('Error saving file'); } finally { showLoading(false); } } async function createBackup() { if (!currentFilePath) { showToast('No file loaded to backup', 'warning'); return; } try { const result = await ipcRenderer.invoke('create-backup', currentFilePath); if (result.success) { showToast(`Backup created: ${require('path').basename(result.backupPath)}`, 'success'); } else { throw new Error(result.error); } } catch (error) { console.error('Error creating backup:', error); showToast(`Error creating backup: ${error.message}`, 'error'); } } function setStatus(message) { const statusText = document.getElementById('status-text'); if (statusText) { statusText.textContent = message; } } function setModified(modified) { isModified = modified; updateUI(); } function updateUI() { if (isModified) { document.title = 'Fallout Shelter Save Editor - Modified'; } else { document.title = 'Fallout Shelter Save Editor'; } } function showLoading(show) { const overlay = document.getElementById('loading-overlay'); if (overlay) { overlay.classList.toggle('show', show); } } function showToast(message, type = 'info') { const container = document.getElementById('toast-container'); if (!container) return; const toast = document.createElement('div'); toast.className = `toast ${type}`; toast.textContent = message; container.appendChild(toast); setTimeout(() => { toast.remove(); }, 4000); } // Dwellers Management function populateDwellers() { const dwellersList = document.getElementById('dwellers-list'); if (!currentSaveData || !currentSaveData.dwellers || !currentSaveData.dwellers.dwellers) { if (dwellersList) { dwellersList.innerHTML = `
No dwellers found in save file
No dwellers in vault