First build
This commit is contained in:
parent
511556737e
commit
fd773f5832
954 changed files with 305115 additions and 2 deletions
151
src/runner.js
Normal file
151
src/runner.js
Normal file
|
|
@ -0,0 +1,151 @@
|
|||
'use strict'
|
||||
|
||||
const path = require('path')
|
||||
const fs = require('fs')
|
||||
const { spawn } = require('child_process')
|
||||
const { DIRS } = require('./paths')
|
||||
const { prefixPath } = require('./prefixes')
|
||||
|
||||
function ensureFakeSteam() {
|
||||
const fake = DIRS.FAKE_STEAM
|
||||
fs.mkdirSync(path.join(fake, 'steamapps'), { recursive: true })
|
||||
return fake
|
||||
}
|
||||
|
||||
function linkPrefix(compatData, prefixDir) {
|
||||
try {
|
||||
const st = fs.lstatSync(compatData)
|
||||
if (st.isSymbolicLink()) {
|
||||
if (fs.readlinkSync(compatData) === prefixDir) return
|
||||
fs.unlinkSync(compatData)
|
||||
} else {
|
||||
return
|
||||
}
|
||||
} catch {}
|
||||
try {
|
||||
fs.symlinkSync(prefixDir, compatData, 'dir')
|
||||
} catch {}
|
||||
}
|
||||
|
||||
function buildEnv({ game, wine, workdir, systemWine }) {
|
||||
const env = { ...process.env }
|
||||
const isProton = wine && wine.type === 'proton'
|
||||
let prefixDir = game.prefix ? prefixPath(game.prefix) : null
|
||||
if (!prefixDir) {
|
||||
const defs = require('./prefixes').listPrefixes()
|
||||
prefixDir = prefixPath(defs.default || 'default')
|
||||
}
|
||||
|
||||
if (isProton) {
|
||||
const fakeSteam = ensureFakeSteam()
|
||||
const buildRoot = path.resolve(wine.path, '..')
|
||||
env.STEAM_COMPAT_CLIENT_INSTALL_PATH = fakeSteam
|
||||
env.STEAM_COMPAT_DATA_PATH = path.join(DIRS.DATA, 'compat', game.id)
|
||||
env.STEAM_COMPAT_APP_ID = '0'
|
||||
env.SteamAppId = '0'
|
||||
env.SteamGameId = '0'
|
||||
env.WINEPREFIX = path.join(env.STEAM_COMPAT_DATA_PATH, 'pfx')
|
||||
env.PROTON_LOG_DIR = path.join(DIRS.DATA, 'logs')
|
||||
fs.mkdirSync(path.join(env.PROTON_LOG_DIR), { recursive: true })
|
||||
linkPrefix(env.STEAM_COMPAT_DATA_PATH, prefixDir)
|
||||
if (!fs.existsSync(env.STEAM_COMPAT_DATA_PATH)) {
|
||||
fs.mkdirSync(env.STEAM_COMPAT_DATA_PATH, { recursive: true })
|
||||
}
|
||||
} else if (!systemWine) {
|
||||
env.WINEPREFIX = prefixDir
|
||||
} else {
|
||||
env.WINEPREFIX = prefixDir
|
||||
}
|
||||
|
||||
env.WINEDLLOVERRIDES = (game.dllOverrides || [])
|
||||
.filter(o => o && o.dll && o.setting)
|
||||
.map(o => `${o.dll}=${o.setting}`)
|
||||
.join(';')
|
||||
if (!env.WINEDLLOVERRIDES) delete env.WINEDLLOVERRIDES
|
||||
|
||||
for (const [k, v] of Object.entries(game.env || {})) env[k] = v
|
||||
return { env, prefixDir }
|
||||
}
|
||||
|
||||
function parseArgs(args) {
|
||||
const out = []
|
||||
const re = /"([^"]*)"|'([^']*)'|(\S+)/g
|
||||
let m
|
||||
while ((m = re.exec(String(args || '')))) out.push(m[1] ?? m[2] ?? m[3])
|
||||
return out
|
||||
}
|
||||
|
||||
function runGame({ game, wine, systemWine, onExit }) {
|
||||
const isProton = wine && wine.type === 'proton'
|
||||
const { env } = buildEnv({ game, wine })
|
||||
const exe = game.exePath
|
||||
const workdir = game.workdir || path.dirname(exe)
|
||||
const argList = parseArgs(game.args)
|
||||
|
||||
let cmd
|
||||
let base
|
||||
if (isProton) {
|
||||
cmd = wine.wineBin || path.join(wine.path, 'proton')
|
||||
base = ['run', exe, ...argList]
|
||||
} else {
|
||||
cmd = systemWine ? 'wine' : wine.wineBin
|
||||
base = [exe, ...argList]
|
||||
}
|
||||
|
||||
fs.mkdirSync(DIRS.DATA, { recursive: true })
|
||||
return new Promise((resolve, reject) => {
|
||||
let child
|
||||
try {
|
||||
child = spawn(cmd, base, {
|
||||
cwd: workdir,
|
||||
env,
|
||||
stdio: ['ignore', 'pipe', 'pipe'],
|
||||
detached: true
|
||||
})
|
||||
} catch (err) {
|
||||
return reject(err)
|
||||
}
|
||||
const logFile = path.join(DIRS.DATA, 'logs', `${game.id}-${Date.now()}.log`)
|
||||
fs.mkdirSync(path.dirname(logFile), { recursive: true })
|
||||
const ws = fs.createWriteStream(logFile)
|
||||
child.stdout.pipe(ws)
|
||||
child.stderr.pipe(ws)
|
||||
child.on('error', reject)
|
||||
child.unref()
|
||||
child.on('exit', (code, signal) => {
|
||||
ws.end()
|
||||
require('./library').updateGame(game.id, { lastPlayed: Date.now() })
|
||||
if (onExit) onExit(code, signal)
|
||||
})
|
||||
resolve({ pid: child.pid, logFile })
|
||||
})
|
||||
}
|
||||
|
||||
function runTool({ game, wine, tool, args, systemWine }) {
|
||||
const isProton = wine && wine.type === 'proton'
|
||||
const { env } = buildEnv({ game, wine })
|
||||
const exe = game.exePath
|
||||
const workdir = game.workdir || path.dirname(exe)
|
||||
const argList = parseArgs(args)
|
||||
|
||||
let cmd
|
||||
let base
|
||||
if (isProton) {
|
||||
cmd = wine.wineBin || path.join(wine.path, 'proton')
|
||||
base = ['run', tool, ...argList]
|
||||
} else if (systemWine) {
|
||||
cmd = tool
|
||||
base = argList
|
||||
} else {
|
||||
cmd = wine.wineBin
|
||||
base = [tool, ...argList]
|
||||
}
|
||||
return new Promise((resolve, reject) => {
|
||||
const child = spawn(cmd, base, { cwd: workdir, env, stdio: 'ignore', detached: true })
|
||||
child.on('error', reject)
|
||||
child.unref()
|
||||
resolve({ pid: child.pid })
|
||||
})
|
||||
}
|
||||
|
||||
module.exports = { buildEnv, runGame, runTool, parseArgs }
|
||||
Loading…
Add table
Add a link
Reference in a new issue