Initial commit

This commit is contained in:
AnRil
2026-05-16 13:43:29 +07:00
commit 688a86b611
208 changed files with 44350 additions and 0 deletions

102
src/main/games/steam.ts Normal file
View File

@@ -0,0 +1,102 @@
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<string | undefined> {
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<string | undefined> {
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<string>
}
export async function getSteamLibraries(): Promise<SteamLibrary[]> {
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<string>()
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<string | undefined> {
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
}