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' ]; 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') ]; 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'); } 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 }).on('change', () => { this.readLocalAccounts(); }); } } private readLocalAccounts() { const filePath = this.getLoginUsersPath(); if (!filePath || !fs.existsSync(filePath)) return; try { const content = fs.readFileSync(filePath, 'utf-8'); 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; accounts.push({ steamId: steamId64, accountName: user.AccountName, personaName: user.PersonaName, timestamp: parseInt(user.Timestamp) || 0 }); } if (this.onAccountsChanged) this.onAccountsChanged(accounts); } catch (error) { console.error('[SteamClient] Error parsing loginusers.vdf:', 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; if (accounts && accounts[accountName]) { return accounts[accountName]; } } catch (e) { console.error('[SteamClient] Failed to extract config.vdf data'); } return null; } public injectAccountConfig(accountName: string, accountData: any) { const configPath = this.getConfigVdfPath(); if (!configPath) return; // Create directory if it doesn't exist const configDir = path.dirname(configPath); if (!fs.existsSync(configDir)) fs.mkdirSync(configDir, { recursive: true }); let data: any = { InstallConfigStore: { Software: { Valve: { Steam: { Accounts: {} } } } } }; if (fs.existsSync(configPath)) { try { const content = fs.readFileSync(configPath, 'utf-8'); data = parse(content) as any; } catch (e) { } } // Ensure structure exists if (!data.InstallConfigStore) data.InstallConfigStore = {}; if (!data.InstallConfigStore.Software) data.InstallConfigStore.Software = {}; if (!data.InstallConfigStore.Software.Valve) data.InstallConfigStore.Software.Valve = {}; if (!data.InstallConfigStore.Software.Valve.Steam) data.InstallConfigStore.Software.Valve.Steam = {}; if (!data.InstallConfigStore.Software.Valve.Steam.Accounts) data.InstallConfigStore.Software.Valve.Steam.Accounts = {}; data.InstallConfigStore.Software.Valve.Steam.Accounts[accountName] = accountData; try { fs.writeFileSync(configPath, stringify(data)); console.log(`[SteamClient] Injected login config for ${accountName} into config.vdf`); } catch (e) { console.error('[SteamClient] Failed to write config.vdf'); } } public async setAutoLoginUser(accountName: string, accountConfig?: any, steamId?: string): Promise { const platform = os.platform(); const loginUsersPath = this.getLoginUsersPath(); if (loginUsersPath) { const configDir = path.dirname(loginUsersPath); if (!fs.existsSync(configDir)) fs.mkdirSync(configDir, { recursive: true }); let data: any = { users: {} }; if (fs.existsSync(loginUsersPath)) { try { const content = fs.readFileSync(loginUsersPath, 'utf-8'); data = parse(content) as any; } 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) { console.log(`[SteamClient] Provisioning user ${accountName} into loginusers.vdf`); 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 { fs.writeFileSync(loginUsersPath, stringify(data)); } catch (e) { console.error('[SteamClient] Failed to write loginusers.vdf'); } } if (accountConfig) { this.injectAccountConfig(accountName, accountConfig); } 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) { let regData: any = { Registry: { HKCU: { Software: { Valve: { Steam: {} } } } } }; if (fs.existsSync(regPath)) { try { const content = fs.readFileSync(regPath, 'utf-8'); regData = parse(content) as any; } catch (e) { } } else { const regDir = path.dirname(regPath); if (!fs.existsSync(regDir)) fs.mkdirSync(regDir, { recursive: true }); } const setPath = (obj: any, keys: string[], val: string) => { let curr = obj; for (let i = 0; i < keys.length - 1; i++) { if (!curr[keys[i]!]) curr[keys[i]!] = {}; curr = curr[keys[i]!]; } curr[keys[keys.length - 1]!] = val; }; const steamReg = ['Registry', 'HKCU', 'Software', 'Valve', 'Steam']; setPath(regData, [...steamReg, 'AutoLoginUser'], accountName); setPath(regData, [...steamReg, 'RememberPassword'], "1"); setPath(regData, [...steamReg, 'AlreadyLoggedIn'], "1"); setPath(regData, [...steamReg, 'WantsOfflineMode'], "0"); try { fs.writeFileSync(regPath, stringify(regData)); console.log(`[SteamClient] Registry updated: ${regPath}`); } catch (e) { } } } return true; } } export const steamClient = new SteamClientService();