import fs from 'fs'; import path from 'path'; import os from 'os'; import { parse, stringify } from 'simple-vdf'; import chokidar from 'chokidar'; export interface LocalSteamAccount { steamId: string; accountName: string; personaName: string; timestamp: number; } class SteamClientService { private steamPath: string | null = null; private onAccountsChanged: ((accounts: LocalSteamAccount[]) => void) | null = null; constructor() { this.detectSteamPath(); } private detectSteamPath() { const platform = os.platform(); const home = os.homedir(); if (platform === 'win32') { const possiblePaths = [ 'C:\\Program Files (x86)\\Steam', 'C:\\Program Files\\Steam', path.join(process.env.APPDATA || '', 'Steam'), ]; this.steamPath = possiblePaths.find(p => fs.existsSync(p)) || null; } else if (platform === 'linux') { const possiblePaths = [ path.join(home, '.steam/steam'), path.join(home, '.local/share/Steam'), path.join(home, '.var/app/com.valvesoftware.Steam/.steam/steam'), // Flatpak path.join(home, 'snap/steam/common/.steam/steam'), // Snap ]; this.steamPath = possiblePaths.find(p => fs.existsSync(p)) || null; } if (this.steamPath) { console.log(`[SteamClient] Detected Steam path: ${this.steamPath}`); } } public getLoginUsersPath(): string | null { if (!this.steamPath) return null; return path.join(this.steamPath, 'config', 'loginusers.vdf'); } public getConfigVdfPath(): string | null { if (!this.steamPath) return null; return path.join(this.steamPath, 'config', 'config.vdf'); } private safeWriteVdf(filePath: string, data: any) { const tempPath = `${filePath}.tmp_${Date.now()}`; const dir = path.dirname(filePath); try { if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true }); const vdfContent = stringify(data); fs.writeFileSync(tempPath, vdfContent, 'utf-8'); fs.renameSync(tempPath, filePath); } catch (e: any) { console.error(`[SteamClient] Atomic write failed for ${filePath}: ${e.message}`); if (fs.existsSync(tempPath)) fs.unlinkSync(tempPath); throw e; } } public startWatching(callback: (accounts: LocalSteamAccount[]) => void) { this.onAccountsChanged = callback; const loginUsersPath = this.getLoginUsersPath(); if (loginUsersPath && fs.existsSync(loginUsersPath)) { this.readLocalAccounts(); chokidar.watch(loginUsersPath, { persistent: true, ignoreInitial: true }).on('change', () => { this.readLocalAccounts(); }); } } private readLocalAccounts() { const filePath = this.getLoginUsersPath(); if (!filePath || !fs.existsSync(filePath)) return; try { const content = fs.readFileSync(filePath, 'utf-8'); if (!content.trim()) return; const data = parse(content) as any; if (!data || !data.users) return; const accounts: LocalSteamAccount[] = []; for (const [steamId64, userData] of Object.entries(data.users)) { const user = userData as any; if (!user || !user.AccountName) continue; accounts.push({ steamId: steamId64, accountName: user.AccountName, personaName: user.PersonaName || user.AccountName, timestamp: parseInt(user.Timestamp) || 0 }); } if (this.onAccountsChanged) this.onAccountsChanged(accounts); } catch (error) { } } public extractAccountConfig(accountName: string): any | null { const configPath = this.getConfigVdfPath(); if (!configPath || !fs.existsSync(configPath)) return null; try { const content = fs.readFileSync(configPath, 'utf-8'); const data = parse(content) as any; const accounts = data?.InstallConfigStore?.Software?.Valve?.Steam?.Accounts; return (accounts && accounts[accountName]) ? accounts[accountName] : null; } catch (e) { return null; } } public injectAccountConfig(accountName: string, accountData: any) { const configPath = this.getConfigVdfPath(); if (!configPath) return; let data: any = { InstallConfigStore: { Software: { Valve: { Steam: { Accounts: {} } } } } }; if (fs.existsSync(configPath)) { try { const content = fs.readFileSync(configPath, 'utf-8'); const parsed = parse(content) as any; if (parsed && typeof parsed === 'object') data = parsed; } catch (e) { } } const ensurePath = (obj: any, keys: string[]) => { let curr = obj; for (const key of keys) { if (!curr[key] || typeof curr[key] !== 'object') curr[key] = {}; curr = curr[key]; } return curr; }; 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 { this.safeWriteVdf(configPath, data); console.log(`[SteamClient] Safely injected session for ${accountName}`); } catch (e) { } } public async setAutoLoginUser(accountName: string, accountConfig?: any, steamId?: string): Promise { const platform = os.platform(); const loginUsersPath = this.getLoginUsersPath(); if (loginUsersPath) { let data: any = { users: {} }; if (fs.existsSync(loginUsersPath)) { try { const content = fs.readFileSync(loginUsersPath, 'utf-8'); const parsed = parse(content) as any; if (parsed && parsed.users) data = parsed; } catch (e) { } } if (!data.users) data.users = {}; let found = false; for (const [id, user] of Object.entries(data.users)) { const u = user as any; if (u.AccountName?.toLowerCase() === accountName.toLowerCase()) { u.mostrecent = "1"; u.RememberPassword = "1"; u.AllowAutoLogin = "1"; u.WantsOfflineMode = "0"; u.SkipOfflineModeWarning = "1"; u.WasNonInteractive = "0"; found = true; } else { u.mostrecent = "0"; } } if (!found && steamId && accountName) { console.log(`[SteamClient] Provisioning new user profile for ${accountName}`); data.users[steamId] = { AccountName: accountName, PersonaName: accountName, RememberPassword: "1", mostrecent: "1", AllowAutoLogin: "1", WantsOfflineMode: "0", SkipOfflineModeWarning: "1", WasNonInteractive: "0", Timestamp: Math.floor(Date.now() / 1000).toString() }; } try { this.safeWriteVdf(loginUsersPath, data); } catch (e) { } } // Injection of the actual authentication blob if (accountConfig && accountName) { this.injectAccountConfig(accountName, accountConfig); } // --- Linux Registry / Registry.vdf Hardening --- if (platform === 'linux') { const regLocations = [ path.join(os.homedir(), '.steam', 'registry.vdf'), path.join(os.homedir(), '.steam', 'steam', 'registry.vdf') ]; for (const regPath of regLocations) { if (!fs.existsSync(path.dirname(regPath))) continue; let regData: any = { Registry: { HKCU: { Software: { Valve: { Steam: { AutoLoginUser: "", RememberPassword: "1", AlreadyLoggedIn: "1" } } } } } }; if (fs.existsSync(regPath)) { try { const content = fs.readFileSync(regPath, 'utf-8'); const parsed = parse(content) as any; if (parsed && typeof parsed === 'object') regData = parsed; } catch (e) { } } const ensurePath = (obj: any, keys: string[]) => { let curr = obj; for (const key of keys) { if (!curr[key] || typeof curr[key] !== 'object') curr[key] = {}; curr = curr[key]; } return curr; }; const steamKey = ensurePath(regData, ['Registry', 'HKCU', 'Software', 'Valve', 'Steam']); steamKey.AutoLoginUser = accountName; steamKey.RememberPassword = "1"; steamKey.AlreadyLoggedIn = "1"; steamKey.WantsOfflineMode = "0"; try { this.safeWriteVdf(regPath, regData); } catch (e) { } } } return true; } } export const steamClient = new SteamClientService();