From 8155cb6deba5ccc6796a861b079185f442a4acc0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mathis=20Dr=C3=B6ge?= Date: Wed, 23 Aug 2023 14:57:47 +0200 Subject: [PATCH 1/4] callRunner: Use PowerShell to run commands on Windows PowerShell's `Start-Process` cmdlet & its `-Wait` parameter are used to wait for a process tree to exit. This means we can now accurately track the "Playing" status of games on Windows. --- src/backend/launcher.ts | 46 ++++++++++++++++++++++++++++++++++------- 1 file changed, 39 insertions(+), 7 deletions(-) diff --git a/src/backend/launcher.ts b/src/backend/launcher.ts index 9a2694b4de..0ab21694c5 100644 --- a/src/backend/launcher.ts +++ b/src/backend/launcher.ts @@ -26,6 +26,7 @@ import { flatPakHome, isLinux, isMac, + isWindows, isSteamDeckGameMode, runtimePath, userHome @@ -993,13 +994,32 @@ async function callRunner( runner: RunnerProps, options?: CallRunnerOptions ): Promise { - const fullRunnerPath = join(runner.dir, runner.bin) const appName = appNameFromCommandParts(commandParts, runner.name) // Necessary to get rid of possible undefined or null entries, else // TypeError is triggered commandParts = commandParts.filter(Boolean) + let bin = runner.bin + let fullRunnerPath = join(runner.dir, bin) + + // On Windows: Use PowerShell's `Start-Process` to wait for the process and + // its children to exit + if (isWindows) { + const argsAsString = commandParts + .map((part) => (part.includes(' ') ? `"\`"${part}\`""` : `"${part}"`)) + .join(',') + commandParts = [ + 'Start-Process', + `${fullRunnerPath}`, + '-Wait', + '-ArgumentList', + argsAsString, + '-NoNewWindow' + ] + bin = fullRunnerPath = 'powershell' + } + const safeCommand = getRunnerCallWithoutCredentials( [...commandParts], options?.env, @@ -1032,8 +1052,6 @@ async function callRunner( } } - const bin = runner.bin - // check if the same command is currently running // if so, return the same promise instead of running it again const key = [runner.name, commandParts].join(' ') @@ -1198,11 +1216,25 @@ function getRunnerCallWithoutCredentials( const modifiedCommand = [...command] // Redact sensitive arguments (Authorization Code for Legendary, token for GOGDL) for (const sensitiveArg of ['--code', '--token']) { - const sensitiveArgIndex = modifiedCommand.indexOf(sensitiveArg) - if (sensitiveArgIndex === -1) { - continue + // PowerShell's argument formatting is quite different, instead of having + // arguments as members of `command`, they're all in one specific member + // (the one after "-ArgumentList") + if (runnerPath === 'powershell') { + const argumentListIndex = modifiedCommand.indexOf('-ArgumentList') + 1 + if (!argumentListIndex) continue + modifiedCommand[argumentListIndex] = modifiedCommand[ + argumentListIndex + ].replace( + new RegExp(`"${sensitiveArg}","(.*?)"`), + `"${sensitiveArg}",""` + ) + } else { + const sensitiveArgIndex = modifiedCommand.indexOf(sensitiveArg) + if (sensitiveArgIndex === -1) { + continue + } + modifiedCommand[sensitiveArgIndex + 1] = '' } - modifiedCommand[sensitiveArgIndex + 1] = '' } const formattedEnvVars: string[] = [] From d9aa0d0bbfabc82e22a33147b29dad8deadab3a8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mathis=20Dr=C3=B6ge?= Date: Wed, 29 Nov 2023 14:08:02 +0100 Subject: [PATCH 2/4] Properly quote runner path --- src/backend/launcher.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/backend/launcher.ts b/src/backend/launcher.ts index 0ab21694c5..9468f908d8 100644 --- a/src/backend/launcher.ts +++ b/src/backend/launcher.ts @@ -1011,7 +1011,7 @@ async function callRunner( .join(',') commandParts = [ 'Start-Process', - `${fullRunnerPath}`, + `"\`"${fullRunnerPath}\`""`, '-Wait', '-ArgumentList', argsAsString, From ecb6061721a56d6a8fc55d9875149ffe18aeae48 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mathis=20Dr=C3=B6ge?= Date: Wed, 29 Nov 2023 14:09:54 +0100 Subject: [PATCH 3/4] Simplify argument quoting a bit There's no need to do different things depending on whether the argument contains a space, we can just always use the "longer" triple-quote form --- src/backend/launcher.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/backend/launcher.ts b/src/backend/launcher.ts index 9468f908d8..fa12a8a8b2 100644 --- a/src/backend/launcher.ts +++ b/src/backend/launcher.ts @@ -1006,9 +1006,7 @@ async function callRunner( // On Windows: Use PowerShell's `Start-Process` to wait for the process and // its children to exit if (isWindows) { - const argsAsString = commandParts - .map((part) => (part.includes(' ') ? `"\`"${part}\`""` : `"${part}"`)) - .join(',') + const argsAsString = commandParts.map((part) => `"\`"${part}\`""`).join(',') commandParts = [ 'Start-Process', `"\`"${fullRunnerPath}\`""`, From d3d39a1b635898029c5f9d4712bca3c5a670f57b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mathis=20Dr=C3=B6ge?= Date: Wed, 29 Nov 2023 14:18:10 +0100 Subject: [PATCH 4/4] Only try to run PowerShell if it's available --- src/backend/launcher.ts | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/backend/launcher.ts b/src/backend/launcher.ts index fa12a8a8b2..a8ec3e0e42 100644 --- a/src/backend/launcher.ts +++ b/src/backend/launcher.ts @@ -946,6 +946,8 @@ interface RunnerProps { const commandsRunning = {} +let shouldUsePowerShell: boolean | null = null + function appNameFromCommandParts(commandParts: string[], runner: Runner) { let appNameIndex = -1 let idx = -1 @@ -1004,8 +1006,12 @@ async function callRunner( let fullRunnerPath = join(runner.dir, bin) // On Windows: Use PowerShell's `Start-Process` to wait for the process and - // its children to exit - if (isWindows) { + // its children to exit, provided PowerShell is available + if (shouldUsePowerShell === null) + shouldUsePowerShell = + isWindows && !!(await searchForExecutableOnPath('powershell')) + + if (shouldUsePowerShell) { const argsAsString = commandParts.map((part) => `"\`"${part}\`""`).join(',') commandParts = [ 'Start-Process',