5 Commits

12 changed files with 245 additions and 64 deletions

View File

@@ -1,4 +1,33 @@
<svg width="512" height="512" viewBox="0 0 512 512" fill="none" xmlns="http://www.w3.org/2000/svg"> <svg width="512" height="512" viewBox="0 0 512 512" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect width="512" height="512" rx="64" fill="#171A21"/> <defs>
<path d="M256 64C150.13 64 64 150.13 64 256C64 361.87 150.13 448 256 448C361.87 448 448 361.87 448 256C448 150.13 375.73 64 256 64ZM256 405.33C173.6 405.33 106.67 338.4 106.67 256C106.67 221.33 118.4 189.33 138.13 164.27L347.73 373.87C322.67 393.6 290.67 405.33 256 405.33ZM373.87 347.73L164.27 138.13C189.33 118.4 221.33 106.67 256 106.67C338.4 106.67 405.33 173.6 405.33 256C405.33 290.67 393.6 322.67 373.87 347.73Z" fill="#66C0F4"/> <linearGradient id="bg_grad" x1="0" y1="0" x2="512" y2="512" gradientUnits="userSpaceOnUse">
<stop offset="0%" stop-color="#1B2838"/>
<stop offset="100%" stop-color="#101419"/>
</linearGradient>
<linearGradient id="symbol_grad" x1="256" y1="100" x2="256" y2="412" gradientUnits="userSpaceOnUse">
<stop offset="0%" stop-color="#66C0F4"/>
<stop offset="100%" stop-color="#1A9FFF"/>
</linearGradient>
<filter id="glow" x="-20%" y="-20%" width="140%" height="140%">
<feGaussianBlur stdDeviation="15" result="blur"/>
<feComposite in="SourceGraphic" in2="blur" operator="over"/>
</filter>
</defs>
<!-- Outer Rounded Container -->
<rect width="512" height="512" rx="100" fill="url(#bg_grad)"/>
<rect x="2" y="2" width="508" height="508" rx="98" stroke="white" stroke-opacity="0.05" stroke-width="4"/>
<!-- Tracking Ring (Detailed) -->
<circle cx="256" cy="256" r="180" stroke="#66C0F4" stroke-width="8" stroke-dasharray="20 10" opacity="0.2"/>
<circle cx="256" cy="256" r="160" stroke="url(#symbol_grad)" stroke-width="20" stroke-linecap="round" stroke-dasharray="350 500" filter="url(#glow)"/>
<!-- Central Shield Symbol -->
<path d="M256 120L360 160V260C360 330 310 380 256 400C202 380 152 330 152 260V160L256 120Z" fill="url(#symbol_grad)" filter="url(#glow)"/>
<!-- "Ban" Intersect (Stylized Cross) -->
<path d="M210 220L302 312M302 220L210 312" stroke="#1B2838" stroke-width="24" stroke-linecap="round" opacity="0.8"/>
<!-- Glass Highlight -->
<path d="M100 100C150 60 362 60 412 100" stroke="white" stroke-opacity="0.1" stroke-width="20" stroke-linecap="round"/>
</svg> </svg>

Before

Width:  |  Height:  |  Size: 604 B

After

Width:  |  Height:  |  Size: 1.7 KiB

View File

@@ -0,0 +1,21 @@
<svg width="512" height="512" viewBox="0 0 512 512" fill="none" xmlns="http://www.w3.org/2000/svg">
<defs>
<linearGradient id="bg_grad" x1="0" y1="0" x2="512" y2="512" gradientUnits="userSpaceOnUse">
<stop offset="0%" stop-color="#EFF1F5"/>
<stop offset="100%" stop-color="#DCE0E8"/>
</linearGradient>
<linearGradient id="symbol_grad" x1="256" y1="100" x2="256" y2="412" gradientUnits="userSpaceOnUse">
<stop offset="0%" stop-color="#1E66F5"/>
<stop offset="100%" stop-color="#179299"/>
</linearGradient>
<filter id="glow" x="-20%" y="-20%" width="140%" height="140%">
<feGaussianBlur stdDeviation="15" result="blur"/>
<feComposite in="SourceGraphic" in2="blur" operator="over"/>
</filter>
</defs>
<rect width="512" height="512" rx="100" fill="url(#bg_grad)"/>
<circle cx="256" cy="256" r="180" stroke="#1E66F5" stroke-width="8" stroke-dasharray="20 10" opacity="0.2"/>
<circle cx="256" cy="256" r="160" stroke="url(#symbol_grad)" stroke-width="20" stroke-linecap="round" stroke-dasharray="350 500" filter="url(#glow)"/>
<path d="M256 120L360 160V260C360 330 310 380 256 400C202 380 152 330 152 260V160L256 120Z" fill="url(#symbol_grad)" filter="url(#glow)"/>
<path d="M210 220L302 312M302 220L210 312" stroke="#EFF1F5" stroke-width="24" stroke-linecap="round" opacity="0.8"/>
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@@ -0,0 +1,21 @@
<svg width="512" height="512" viewBox="0 0 512 512" fill="none" xmlns="http://www.w3.org/2000/svg">
<defs>
<linearGradient id="bg_grad" x1="0" y1="0" x2="512" y2="512" gradientUnits="userSpaceOnUse">
<stop offset="0%" stop-color="#1E1E2E"/>
<stop offset="100%" stop-color="#11111B"/>
</linearGradient>
<linearGradient id="symbol_grad" x1="256" y1="100" x2="256" y2="412" gradientUnits="userSpaceOnUse">
<stop offset="0%" stop-color="#B4BEFE"/>
<stop offset="100%" stop-color="#89B4FA"/>
</linearGradient>
<filter id="glow" x="-20%" y="-20%" width="140%" height="140%">
<feGaussianBlur stdDeviation="15" result="blur"/>
<feComposite in="SourceGraphic" in2="blur" operator="over"/>
</filter>
</defs>
<rect width="512" height="512" rx="100" fill="url(#bg_grad)"/>
<circle cx="256" cy="256" r="180" stroke="#B4BEFE" stroke-width="8" stroke-dasharray="20 10" opacity="0.2"/>
<circle cx="256" cy="256" r="160" stroke="url(#symbol_grad)" stroke-width="20" stroke-linecap="round" stroke-dasharray="350 500" filter="url(#glow)"/>
<path d="M256 120L360 160V260C360 330 310 380 256 400C202 380 152 330 152 260V160L256 120Z" fill="url(#symbol_grad)" filter="url(#glow)"/>
<path d="M210 220L302 312M302 220L210 312" stroke="#1E1E2E" stroke-width="24" stroke-linecap="round" opacity="0.8"/>
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@@ -0,0 +1,21 @@
<svg width="512" height="512" viewBox="0 0 512 512" fill="none" xmlns="http://www.w3.org/2000/svg">
<defs>
<linearGradient id="bg_grad" x1="0" y1="0" x2="512" y2="512" gradientUnits="userSpaceOnUse">
<stop offset="0%" stop-color="#3B4252"/>
<stop offset="100%" stop-color="#2E3440"/>
</linearGradient>
<linearGradient id="symbol_grad" x1="256" y1="100" x2="256" y2="412" gradientUnits="userSpaceOnUse">
<stop offset="0%" stop-color="#88C0D0"/>
<stop offset="100%" stop-color="#81A1C1"/>
</linearGradient>
<filter id="glow" x="-20%" y="-20%" width="140%" height="140%">
<feGaussianBlur stdDeviation="15" result="blur"/>
<feComposite in="SourceGraphic" in2="blur" operator="over"/>
</filter>
</defs>
<rect width="512" height="512" rx="100" fill="url(#bg_grad)"/>
<circle cx="256" cy="256" r="180" stroke="#88C0D0" stroke-width="8" stroke-dasharray="20 10" opacity="0.2"/>
<circle cx="256" cy="256" r="160" stroke="url(#symbol_grad)" stroke-width="20" stroke-linecap="round" stroke-dasharray="350 500" filter="url(#glow)"/>
<path d="M256 120L360 160V260C360 330 310 380 256 400C202 380 152 330 152 260V160L256 120Z" fill="url(#symbol_grad)" filter="url(#glow)"/>
<path d="M210 220L302 312M302 220L210 312" stroke="#3B4252" stroke-width="24" stroke-linecap="round" opacity="0.8"/>
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@@ -0,0 +1,21 @@
<svg width="512" height="512" viewBox="0 0 512 512" fill="none" xmlns="http://www.w3.org/2000/svg">
<defs>
<linearGradient id="bg_grad" x1="0" y1="0" x2="512" y2="512" gradientUnits="userSpaceOnUse">
<stop offset="0%" stop-color="#1B2838"/>
<stop offset="100%" stop-color="#101419"/>
</linearGradient>
<linearGradient id="symbol_grad" x1="256" y1="100" x2="256" y2="412" gradientUnits="userSpaceOnUse">
<stop offset="0%" stop-color="#66C0F4"/>
<stop offset="100%" stop-color="#1A9FFF"/>
</linearGradient>
<filter id="glow" x="-20%" y="-20%" width="140%" height="140%">
<feGaussianBlur stdDeviation="15" result="blur"/>
<feComposite in="SourceGraphic" in2="blur" operator="over"/>
</filter>
</defs>
<rect width="512" height="512" rx="100" fill="url(#bg_grad)"/>
<circle cx="256" cy="256" r="180" stroke="#66C0F4" stroke-width="8" stroke-dasharray="20 10" opacity="0.2"/>
<circle cx="256" cy="256" r="160" stroke="url(#symbol_grad)" stroke-width="20" stroke-linecap="round" stroke-dasharray="350 500" filter="url(#glow)"/>
<path d="M256 120L360 160V260C360 330 310 380 256 400C202 380 152 330 152 260V160L256 120Z" fill="url(#symbol_grad)" filter="url(#glow)"/>
<path d="M210 220L302 312M302 220L210 312" stroke="#1B2838" stroke-width="24" stroke-linecap="round" opacity="0.8"/>
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@@ -0,0 +1,21 @@
<svg width="512" height="512" viewBox="0 0 512 512" fill="none" xmlns="http://www.w3.org/2000/svg">
<defs>
<linearGradient id="bg_grad" x1="0" y1="0" x2="512" y2="512" gradientUnits="userSpaceOnUse">
<stop offset="0%" stop-color="#1A1B26"/>
<stop offset="100%" stop-color="#10101A"/>
</linearGradient>
<linearGradient id="symbol_grad" x1="256" y1="100" x2="256" y2="412" gradientUnits="userSpaceOnUse">
<stop offset="0%" stop-color="#7AA2F7"/>
<stop offset="100%" stop-color="#3D59A1"/>
</linearGradient>
<filter id="glow" x="-20%" y="-20%" width="140%" height="140%">
<feGaussianBlur stdDeviation="15" result="blur"/>
<feComposite in="SourceGraphic" in2="blur" operator="over"/>
</filter>
</defs>
<rect width="512" height="512" rx="100" fill="url(#bg_grad)"/>
<circle cx="256" cy="256" r="180" stroke="#7AA2F7" stroke-width="8" stroke-dasharray="20 10" opacity="0.2"/>
<circle cx="256" cy="256" r="160" stroke="url(#symbol_grad)" stroke-width="20" stroke-linecap="round" stroke-dasharray="350 500" filter="url(#glow)"/>
<path d="M256 120L360 160V260C360 330 310 380 256 400C202 380 152 330 152 260V160L256 120Z" fill="url(#symbol_grad)" filter="url(#glow)"/>
<path d="M210 220L302 312M302 220L210 312" stroke="#1A1B26" stroke-width="24" stroke-linecap="round" opacity="0.8"/>
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@@ -99,7 +99,14 @@ const createTray = () => {
mainWindow.show(); mainWindow.show();
mainWindow.focus(); mainWindow.focus();
} }); } });
updateTrayMenu(); // Load initial themed icon
const config = store.get('serverConfig');
if (config?.theme) {
setAppIcon(config.theme);
}
else {
updateTrayMenu(); // Fallback to refresh menu
}
} }
catch (e) { } catch (e) { }
}; };
@@ -127,6 +134,21 @@ const updateTrayMenu = () => {
]); ]);
tray.setContextMenu(contextMenu); tray.setContextMenu(contextMenu);
}; };
const setAppIcon = (themeName = 'steam') => {
const assetsDir = path_1.default.join(__dirname, '..', 'assets-build', 'icons');
const iconPath = path_1.default.join(assetsDir, `${themeName}.svg`);
if (!fs_1.default.existsSync(iconPath))
return;
const icon = electron_1.nativeImage.createFromPath(iconPath);
// Update Tray
if (tray) {
tray.setImage(icon.resize({ width: 16, height: 16 }));
}
// Update Main Window
if (mainWindow) {
mainWindow.setIcon(icon);
}
};
// --- Steam Logic --- // --- Steam Logic ---
const killSteam = async () => { const killSteam = async () => {
return new Promise((resolve) => { return new Promise((resolve) => {
@@ -588,6 +610,11 @@ electron_1.ipcMain.handle('admin-delete-user', async (event, userId) => { initBa
electron_1.ipcMain.handle('admin-get-accounts', async () => { initBackend(); return backend ? await backend.getAdminAccounts() : []; }); electron_1.ipcMain.handle('admin-get-accounts', async () => { initBackend(); return backend ? await backend.getAdminAccounts() : []; });
electron_1.ipcMain.handle('admin-remove-account', async (event, steamId) => { initBackend(); if (backend) electron_1.ipcMain.handle('admin-remove-account', async (event, steamId) => { initBackend(); if (backend)
await backend.forceRemoveAccount(steamId); return true; }); await backend.forceRemoveAccount(steamId); return true; });
electron_1.ipcMain.handle('force-sync', async () => { await syncAccounts(true); return true; });
electron_1.ipcMain.handle('update-app-icon', (event, themeName) => {
setAppIcon(themeName);
return true;
});
electron_1.ipcMain.handle('switch-account', async (event, loginName) => { electron_1.ipcMain.handle('switch-account', async (event, loginName) => {
if (!loginName) if (!loginName)
return false; return false;

View File

@@ -11,6 +11,7 @@ electron_1.contextBridge.exposeInMainWorld('electronAPI', {
revokeAccountAccess: (steamId, targetSteamId) => electron_1.ipcRenderer.invoke('revoke-account-access', steamId, targetSteamId), revokeAccountAccess: (steamId, targetSteamId) => electron_1.ipcRenderer.invoke('revoke-account-access', steamId, targetSteamId),
revokeAllAccountAccess: (steamId) => electron_1.ipcRenderer.invoke('revoke-all-account-access', steamId), revokeAllAccountAccess: (steamId) => electron_1.ipcRenderer.invoke('revoke-all-account-access', steamId),
openExternal: (url) => electron_1.ipcRenderer.invoke('open-external', url), openExternal: (url) => electron_1.ipcRenderer.invoke('open-external', url),
updateAppIcon: (theme) => electron_1.ipcRenderer.invoke('update-app-icon', theme),
openSteamAppLogin: () => electron_1.ipcRenderer.invoke('open-steam-app-login'), openSteamAppLogin: () => electron_1.ipcRenderer.invoke('open-steam-app-login'),
openSteamLogin: (steamId) => electron_1.ipcRenderer.invoke('open-steam-login', steamId), openSteamLogin: (steamId) => electron_1.ipcRenderer.invoke('open-steam-login', steamId),
// Server Config & Auth // Server Config & Auth

View File

@@ -135,7 +135,14 @@ const createTray = () => {
tray = new Tray(icon); tray = new Tray(icon);
tray.setToolTip('Ultimate Ban Tracker'); tray.setToolTip('Ultimate Ban Tracker');
tray.on('click', () => { if (mainWindow) { mainWindow.show(); mainWindow.focus(); } }); tray.on('click', () => { if (mainWindow) { mainWindow.show(); mainWindow.focus(); } });
updateTrayMenu();
// Load initial themed icon
const config = store.get('serverConfig');
if (config?.theme) {
setAppIcon(config.theme);
} else {
updateTrayMenu(); // Fallback to refresh menu
}
} catch (e) { } } catch (e) { }
}; };
@@ -162,6 +169,25 @@ const updateTrayMenu = () => {
tray.setContextMenu(contextMenu); tray.setContextMenu(contextMenu);
}; };
const setAppIcon = (themeName: string = 'steam') => {
const assetsDir = path.join(__dirname, '..', 'assets-build', 'icons');
const iconPath = path.join(assetsDir, `${themeName}.svg`);
if (!fs.existsSync(iconPath)) return;
const icon = nativeImage.createFromPath(iconPath);
// Update Tray
if (tray) {
tray.setImage(icon.resize({ width: 16, height: 16 }));
}
// Update Main Window
if (mainWindow) {
mainWindow.setIcon(icon);
}
};
// --- Steam Logic --- // --- Steam Logic ---
const killSteam = async () => { const killSteam = async () => {
return new Promise<void>((resolve) => { return new Promise<void>((resolve) => {
@@ -571,6 +597,13 @@ ipcMain.handle('admin-delete-user', async (event, userId: string) => { initBacke
ipcMain.handle('admin-get-accounts', async () => { initBackend(); return backend ? await backend.getAdminAccounts() : []; }); ipcMain.handle('admin-get-accounts', async () => { initBackend(); return backend ? await backend.getAdminAccounts() : []; });
ipcMain.handle('admin-remove-account', async (event, steamId: string) => { initBackend(); if (backend) await backend.forceRemoveAccount(steamId); return true; }); ipcMain.handle('admin-remove-account', async (event, steamId: string) => { initBackend(); if (backend) await backend.forceRemoveAccount(steamId); return true; });
ipcMain.handle('force-sync', async () => { await syncAccounts(true); return true; });
ipcMain.handle('update-app-icon', (event, themeName: string) => {
setAppIcon(themeName);
return true;
});
ipcMain.handle('switch-account', async (event, loginName: string) => { ipcMain.handle('switch-account', async (event, loginName: string) => {
if (!loginName) return false; if (!loginName) return false;
try { try {

View File

@@ -10,6 +10,7 @@ contextBridge.exposeInMainWorld('electronAPI', {
revokeAccountAccess: (steamId: string, targetSteamId: string) => ipcRenderer.invoke('revoke-account-access', steamId, targetSteamId), revokeAccountAccess: (steamId: string, targetSteamId: string) => ipcRenderer.invoke('revoke-account-access', steamId, targetSteamId),
revokeAllAccountAccess: (steamId: string) => ipcRenderer.invoke('revoke-all-account-access', steamId), revokeAllAccountAccess: (steamId: string) => ipcRenderer.invoke('revoke-all-account-access', steamId),
openExternal: (url: string) => ipcRenderer.invoke('open-external', url), openExternal: (url: string) => ipcRenderer.invoke('open-external', url),
updateAppIcon: (theme: string) => ipcRenderer.invoke('update-app-icon', theme),
openSteamAppLogin: () => ipcRenderer.invoke('open-steam-app-login'), openSteamAppLogin: () => ipcRenderer.invoke('open-steam-app-login'),
openSteamLogin: (steamId: string) => ipcRenderer.invoke('open-steam-login', steamId), openSteamLogin: (steamId: string) => ipcRenderer.invoke('open-steam-login', steamId),

View File

@@ -55,20 +55,13 @@ class SteamClientService {
return path.join(this.steamPath, 'config', 'config.vdf'); return path.join(this.steamPath, 'config', 'config.vdf');
} }
/**
* Safe Atomic Write: Writes to a temp file and renames it.
* This prevents file corruption if the app crashes during write.
*/
private safeWriteVdf(filePath: string, data: any) { private safeWriteVdf(filePath: string, data: any) {
const tempPath = `${filePath}.tmp_${Date.now()}`; const tempPath = `${filePath}.tmp_${Date.now()}`;
const dir = path.dirname(filePath); const dir = path.dirname(filePath);
try { try {
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true }); if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
const vdfContent = stringify(data); const vdfContent = stringify(data);
fs.writeFileSync(tempPath, vdfContent, 'utf-8'); fs.writeFileSync(tempPath, vdfContent, 'utf-8');
// Atomic rename
fs.renameSync(tempPath, filePath); fs.renameSync(tempPath, filePath);
} catch (e: any) { } catch (e: any) {
console.error(`[SteamClient] Atomic write failed for ${filePath}: ${e.message}`); console.error(`[SteamClient] Atomic write failed for ${filePath}: ${e.message}`);
@@ -80,11 +73,9 @@ class SteamClientService {
public startWatching(callback: (accounts: LocalSteamAccount[]) => void) { public startWatching(callback: (accounts: LocalSteamAccount[]) => void) {
this.onAccountsChanged = callback; this.onAccountsChanged = callback;
const loginUsersPath = this.getLoginUsersPath(); const loginUsersPath = this.getLoginUsersPath();
if (loginUsersPath && fs.existsSync(loginUsersPath)) { if (loginUsersPath && fs.existsSync(loginUsersPath)) {
this.readLocalAccounts(); this.readLocalAccounts();
chokidar.watch(loginUsersPath, { persistent: true, ignoreInitial: true }).on('change', () => { chokidar.watch(loginUsersPath, { persistent: true, ignoreInitial: true }).on('change', () => {
console.log(`[SteamClient] loginusers.vdf changed, re-scanning...`);
this.readLocalAccounts(); this.readLocalAccounts();
}); });
} }
@@ -93,64 +84,41 @@ class SteamClientService {
private readLocalAccounts() { private readLocalAccounts() {
const filePath = this.getLoginUsersPath(); const filePath = this.getLoginUsersPath();
if (!filePath || !fs.existsSync(filePath)) return; if (!filePath || !fs.existsSync(filePath)) return;
try { try {
const content = fs.readFileSync(filePath, 'utf-8'); const content = fs.readFileSync(filePath, 'utf-8');
if (!content.trim()) return; // Empty file if (!content.trim()) return;
const data = parse(content) as any; const data = parse(content) as any;
if (!data || !data.users) return; if (!data || !data.users) return;
const accounts: LocalSteamAccount[] = []; const accounts: LocalSteamAccount[] = [];
for (const [steamId64, userData] of Object.entries(data.users)) { for (const [steamId64, userData] of Object.entries(data.users)) {
const user = userData as any; const user = userData as any;
if (!user || !user.AccountName) continue; if (!user || !user.AccountName) continue;
accounts.push({ accounts.push({
steamId: steamId64, steamId: steamId64, accountName: user.AccountName,
accountName: user.AccountName,
personaName: user.PersonaName || user.AccountName, personaName: user.PersonaName || user.AccountName,
timestamp: parseInt(user.Timestamp) || 0 timestamp: parseInt(user.Timestamp) || 0
}); });
} }
if (this.onAccountsChanged) this.onAccountsChanged(accounts); if (this.onAccountsChanged) this.onAccountsChanged(accounts);
} catch (error) { } catch (error) { }
console.error('[SteamClient] Error parsing loginusers.vdf:', error);
}
} }
public extractAccountConfig(accountName: string): any | null { public extractAccountConfig(accountName: string): any | null {
const configPath = this.getConfigVdfPath(); const configPath = this.getConfigVdfPath();
if (!configPath || !fs.existsSync(configPath)) return null; if (!configPath || !fs.existsSync(configPath)) return null;
try { try {
const content = fs.readFileSync(configPath, 'utf-8'); const content = fs.readFileSync(configPath, 'utf-8');
const data = parse(content) as any; const data = parse(content) as any;
const accounts = data?.InstallConfigStore?.Software?.Valve?.Steam?.Accounts; const accounts = data?.InstallConfigStore?.Software?.Valve?.Steam?.Accounts;
return (accounts && accounts[accountName]) ? accounts[accountName] : null; return (accounts && accounts[accountName]) ? accounts[accountName] : null;
} catch (e) { } catch (e) { return null; }
console.error('[SteamClient] Failed to extract config.vdf data');
return null;
}
} }
public injectAccountConfig(accountName: string, accountData: any) { public injectAccountConfig(accountName: string, accountData: any) {
const configPath = this.getConfigVdfPath(); const configPath = this.getConfigVdfPath();
if (!configPath) return; if (!configPath) return;
let data: any = { let data: any = { InstallConfigStore: { Software: { Valve: { Steam: { Accounts: {} } } } } };
InstallConfigStore: {
Software: {
Valve: {
Steam: {
Accounts: {}
}
}
}
}
};
if (fs.existsSync(configPath)) { if (fs.existsSync(configPath)) {
try { try {
const content = fs.readFileSync(configPath, 'utf-8'); const content = fs.readFileSync(configPath, 'utf-8');
@@ -159,14 +127,24 @@ class SteamClientService {
} catch (e) { } } catch (e) { }
} }
// Ensure safe nesting const ensurePath = (obj: any, keys: string[]) => {
if (!data.InstallConfigStore) data.InstallConfigStore = {}; let curr = obj;
if (!data.InstallConfigStore.Software) data.InstallConfigStore.Software = {}; for (const key of keys) {
if (!data.InstallConfigStore.Software.Valve) data.InstallConfigStore.Software.Valve = {}; if (!curr[key] || typeof curr[key] !== 'object') curr[key] = {};
if (!data.InstallConfigStore.Software.Valve.Steam) data.InstallConfigStore.Software.Valve.Steam = {}; curr = curr[key];
if (!data.InstallConfigStore.Software.Valve.Steam.Accounts) data.InstallConfigStore.Software.Valve.Steam.Accounts = {}; }
return curr;
};
data.InstallConfigStore.Software.Valve.Steam.Accounts[accountName] = accountData; const steamAccounts = ensurePath(data, ['InstallConfigStore', 'Software', 'Valve', 'Steam', 'Accounts']);
// FAILPROOF: Force crucial flags that Steam uses to decide session validity
steamAccounts[accountName] = {
...accountData,
RememberPassword: "1",
AllowAutoLogin: "1",
Timestamp: Math.floor(Date.now() / 1000).toString()
};
try { try {
this.safeWriteVdf(configPath, data); this.safeWriteVdf(configPath, data);
@@ -226,6 +204,7 @@ class SteamClientService {
} catch (e) { } } catch (e) { }
} }
// Injection of the actual authentication blob
if (accountConfig && accountName) { if (accountConfig && accountName) {
this.injectAccountConfig(accountName, accountConfig); this.injectAccountConfig(accountName, accountConfig);
} }
@@ -239,13 +218,7 @@ class SteamClientService {
for (const regPath of regLocations) { for (const regPath of regLocations) {
if (!fs.existsSync(path.dirname(regPath))) continue; if (!fs.existsSync(path.dirname(regPath))) continue;
let regData: any = { Registry: { HKCU: { Software: { Valve: { Steam: { AutoLoginUser: "", RememberPassword: "1", AlreadyLoggedIn: "1" } } } } } };
let regData: any = { Registry: { HKCU: { Software: { Valve: { Steam: {
AutoLoginUser: "",
RememberPassword: "1",
AlreadyLoggedIn: "1"
} } } } } };
if (fs.existsSync(regPath)) { if (fs.existsSync(regPath)) {
try { try {
const content = fs.readFileSync(regPath, 'utf-8'); const content = fs.readFileSync(regPath, 'utf-8');
@@ -254,13 +227,9 @@ class SteamClientService {
} catch (e) { } } catch (e) { }
} }
// Deep merge helper
const ensurePath = (obj: any, keys: string[]) => { const ensurePath = (obj: any, keys: string[]) => {
let curr = obj; let curr = obj;
for (const key of keys) { for (const key of keys) { if (!curr[key] || typeof curr[key] !== 'object') curr[key] = {}; curr = curr[key]; }
if (!curr[key] || typeof curr[key] !== 'object') curr[key] = {};
curr = curr[key];
}
return curr; return curr;
}; };
@@ -269,10 +238,7 @@ class SteamClientService {
steamKey.RememberPassword = "1"; steamKey.RememberPassword = "1";
steamKey.AlreadyLoggedIn = "1"; steamKey.AlreadyLoggedIn = "1";
steamKey.WantsOfflineMode = "0"; steamKey.WantsOfflineMode = "0";
try { this.safeWriteVdf(regPath, regData); } catch (e) { }
try {
this.safeWriteVdf(regPath, regData);
} catch (e) { }
} }
} }

View File

@@ -31,8 +31,27 @@ export const AppThemeProvider: React.FC<{ children: React.ReactNode }> = ({ chil
if (api?.updateServerConfig) { if (api?.updateServerConfig) {
await api.updateServerConfig({ theme }); await api.updateServerConfig({ theme });
} }
if (api?.updateAppIcon) {
try {
await api.updateAppIcon(theme);
} catch (e) { }
}
}; };
useEffect(() => {
const updateIcon = async () => {
const api = (window as any).electronAPI;
if (api?.updateAppIcon && currentTheme) {
try {
await api.updateAppIcon(currentTheme);
} catch (e) {
console.warn("[ThemeContext] updateAppIcon failed (likely not registered yet)");
}
}
};
updateIcon();
}, [currentTheme]);
const theme = useMemo(() => getTheme(currentTheme), [currentTheme]); const theme = useMemo(() => getTheme(currentTheme), [currentTheme]);
return ( return (