From cd6bcdb9a8f209b2f68c8cc93230cf582b891f27 Mon Sep 17 00:00:00 2001 From: Cade Ayres Date: Wed, 11 Mar 2026 07:30:37 +0000 Subject: [PATCH] Added support for Baby Steps Server - Required a change to DirectGameRunner execution. We now use `spawn` instead of `exec`. - Tested with Baby Steps Server. - Tested using an existing game (Timberborn) to ensure the "Other" option still launches games correctly. --- src-electron/ipc/node-child-process-impl.ts | 21 +++++++++++--- src-electron/preload/node-child-process.ts | 6 +++- .../ChildProcessImplementation.ts | 1 + .../node/child_process/child_process.ts | 2 ++ .../runners/multiplatform/DirectGameRunner.ts | 28 +++++++++++-------- 5 files changed, 41 insertions(+), 17 deletions(-) diff --git a/src-electron/ipc/node-child-process-impl.ts b/src-electron/ipc/node-child-process-impl.ts index 8fed9b2b3..2e658f8a9 100644 --- a/src-electron/ipc/node-child-process-impl.ts +++ b/src-electron/ipc/node-child-process-impl.ts @@ -4,17 +4,30 @@ import ChildProcess from 'child_process'; export function hookChildProcessIpc(browserWindow: BrowserWindow) { ipcMain.on("node:child_process:execSync", (event, path) => { event.returnValue = ChildProcess.execSync(path).toString(); - }) + }); ipcMain.on("node:child_process:spawnSync", (event, path, args, options) => { event.returnValue = ChildProcess.spawnSync(path, args, options).stdout; - }) + }); ipcMain.handle("node:child_process:exec", (event, path, options) => { return new Promise(resolve => { ChildProcess.exec(path, options, err => { resolve(err); }); - }) - }) + }); + }); + + ipcMain.handle("node:child_process:spawn", (event, path, args, options) => { + return new Promise((resolve, reject) => { + try { + ChildProcess.spawn(path, args, options) + resolve(undefined); + } catch (e) { + reject(JSON.stringify(e)); + } + }); + }); + + } diff --git a/src-electron/preload/node-child-process.ts b/src-electron/preload/node-child-process.ts index 592e59e1c..c4fcee459 100644 --- a/src-electron/preload/node-child-process.ts +++ b/src-electron/preload/node-child-process.ts @@ -9,5 +9,9 @@ export function exec(path: string, options: any) { } export function spawnSync(path: string, args: string[], options: object) { - return ipcRenderer.sendSync('node:child_process:spawnSync', path, options); + return ipcRenderer.sendSync('node:child_process:spawnSync', path, args, options); +} + +export function spawn(path: string, args: string[], options: object) { + return ipcRenderer.invoke('node:child_process:spawn', path, args, options); } diff --git a/src/providers/node/child_process/ChildProcessImplementation.ts b/src/providers/node/child_process/ChildProcessImplementation.ts index 1ae91813c..d3c3c8466 100644 --- a/src/providers/node/child_process/ChildProcessImplementation.ts +++ b/src/providers/node/child_process/ChildProcessImplementation.ts @@ -13,4 +13,5 @@ export const NodeChildProcessImplementation: NodeChildProcessProvider = { }); }, spawnSync: (path, args, options) => window.node.child_process.spawnSync(path, args, options), + spawn: async (path, args, options) => window.node.child_process.spawn(path, args, options), } diff --git a/src/providers/node/child_process/child_process.ts b/src/providers/node/child_process/child_process.ts index dae941830..ca8fc7eb4 100644 --- a/src/providers/node/child_process/child_process.ts +++ b/src/providers/node/child_process/child_process.ts @@ -4,6 +4,7 @@ export type NodeChildProcessProvider = { execSync: (path: string, options?: any) => string; exec: (path: string, options?: any, callback?: (err: Error) => void) => Promise; spawnSync: (path: string, args?: string[], options?: object) => string; + spawn: (path: string, args?: string[], options?: object) => Promise; } let implementation: () => NodeChildProcessProvider; @@ -23,6 +24,7 @@ const childProcess: NodeChildProcessProvider = { execSync: path => getImplementation().execSync(path), exec: (path, options, callback) => getImplementation().exec(path, options, callback), spawnSync: (path, args, options) => getImplementation().spawnSync(path, args, options), + spawn: (path, args, options) => getImplementation().spawn(path, args, options), }; export default childProcess; diff --git a/src/r2mm/launching/runners/multiplatform/DirectGameRunner.ts b/src/r2mm/launching/runners/multiplatform/DirectGameRunner.ts index 4bd936b8e..ee602b674 100644 --- a/src/r2mm/launching/runners/multiplatform/DirectGameRunner.ts +++ b/src/r2mm/launching/runners/multiplatform/DirectGameRunner.ts @@ -9,6 +9,7 @@ import GameDirectoryResolverProvider from '../../../../providers/ror2/game/GameD import FsProvider from '../../../../providers/generic/file/FsProvider'; import LoggerProvider, { LogSeverity } from '../../../../providers/ror2/logging/LoggerProvider'; import ChildProcess from '../../../../providers/node/child_process/child_process'; +import { parse as parseShell } from "shell-quote"; export default class DirectGameRunner extends GameRunnerProvider { @@ -45,20 +46,23 @@ export default class DirectGameRunner extends GameRunnerProvider { const mappedArgs = args.map(value => `"${value}"`).join(' '); - LoggerProvider.instance.Log(LogSeverity.INFO, `Running command: ${gameDir}/${gameExecutable} ${mappedArgs} ${settings.getContext().gameSpecific.launchParameters}`); + const allArgs = [ + ...args, + ...parseShell(settings.getContext().gameSpecific.launchParameters) + ]; - const childProcess = ChildProcess.exec(`"${gameExecutable}" ${mappedArgs} ${settings.getContext().gameSpecific.launchParameters}`, { + LoggerProvider.instance.Log(LogSeverity.INFO, `Running command: ${gameDir}/${gameExecutable} ${allArgs.join(" ")}`); + + ChildProcess.spawn(`"${gameExecutable}"`, allArgs, { cwd: gameDir, - windowsHide: false, - }, (err => { - if (err !== null) { - LoggerProvider.instance.Log(LogSeverity.ACTION_STOPPED, 'Error was thrown whilst starting modded'); - LoggerProvider.instance.Log(LogSeverity.ERROR, err.message); - const r2err = new R2Error('Error starting the game', err.message, 'Ensure that the game folder has been set correctly in the settings'); - return reject(r2err); - } - return resolve(); - })); + shell: true, + detached: true, + }).catch(reason => { + LoggerProvider.instance.Log(LogSeverity.ACTION_STOPPED, 'Error was thrown whilst starting modded'); + LoggerProvider.instance.Log(LogSeverity.ERROR, reason); + const r2err = new R2Error('Error starting the game', reason, 'Ensure that the game folder has been set correctly in the settings'); + return reject(r2err); + }); }); } }