import { exec } from 'node:child_process' import { existsSync, readFileSync } from 'node:fs' import { join } from 'node:path' import { promisify } from 'node:util' import { parseVdf, type VdfNode } from './vdf' const execAsync = promisify(exec) async function regQuery( key: string, valueName: string ): Promise { try { const { stdout } = await execAsync(`reg query "${key}" /v ${valueName}`, { windowsHide: true }) const m = stdout.match(/REG_SZ\s+(.+)/) return m?.[1]?.trim() } catch { return undefined } } export async function getSteamPath(): Promise { const fromUser = await regQuery('HKCU\\Software\\Valve\\Steam', 'SteamPath') if (fromUser && existsSync(fromUser)) return fromUser.replace(/\//g, '\\') const fromMachine = await regQuery( 'HKLM\\SOFTWARE\\WOW6432Node\\Valve\\Steam', 'InstallPath' ) if (fromMachine && existsSync(fromMachine)) return fromMachine // Common fallbacks for (const p of [ 'C:\\Program Files (x86)\\Steam', 'C:\\Program Files\\Steam' ]) { if (existsSync(p)) return p } return undefined } export type SteamLibrary = { path: string apps: Set } export async function getSteamLibraries(): Promise { const steamPath = await getSteamPath() if (!steamPath) return [] const libsFile = join(steamPath, 'config', 'libraryfolders.vdf') if (!existsSync(libsFile)) { // Bare minimum: the install itself is a library. return [{ path: steamPath, apps: new Set() }] } const raw = readFileSync(libsFile, 'utf-8') const parsed = parseVdf(raw) const libs: SteamLibrary[] = [] const root = (parsed['libraryfolders'] as VdfNode) ?? parsed for (const key of Object.keys(root)) { const entry = root[key] if (typeof entry !== 'object') continue const path = (entry['path'] as string) ?? '' if (!path) continue const apps = new Set() const appsNode = entry['apps'] if (typeof appsNode === 'object') { for (const appId of Object.keys(appsNode)) apps.add(appId) } libs.push({ path: path.replace(/\\\\/g, '\\'), apps }) } return libs } export async function findGameInstall( appId: string, installDirName: string ): Promise { const libs = await getSteamLibraries() for (const lib of libs) { // Prefer libraries that claim this app, but also probe by directory. const candidate = join(lib.path, 'steamapps', 'common', installDirName) if (existsSync(candidate)) return candidate } // Fallback: scan all libs for matching appmanifest. for (const lib of libs) { const manifest = join(lib.path, 'steamapps', `appmanifest_${appId}.acf`) if (!existsSync(manifest)) continue try { const acf = parseVdf(readFileSync(manifest, 'utf-8')) const appState = acf['AppState'] as VdfNode | undefined const installdir = appState?.['installdir'] as string | undefined if (installdir) { const candidate = join(lib.path, 'steamapps', 'common', installdir) if (existsSync(candidate)) return candidate } } catch { // ignore } } return undefined }