diff --git a/src/backend/launcher.ts b/src/backend/launcher.ts index 9a2694b4de..a8ec3e0e42 100644 --- a/src/backend/launcher.ts +++ b/src/backend/launcher.ts @@ -26,6 +26,7 @@ import { flatPakHome, isLinux, isMac, + isWindows, isSteamDeckGameMode, runtimePath, userHome @@ -945,6 +946,8 @@ interface RunnerProps { const commandsRunning = {} +let shouldUsePowerShell: boolean | null = null + function appNameFromCommandParts(commandParts: string[], runner: Runner) { let appNameIndex = -1 let idx = -1 @@ -993,13 +996,34 @@ 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, provided PowerShell is available + if (shouldUsePowerShell === null) + shouldUsePowerShell = + isWindows && !!(await searchForExecutableOnPath('powershell')) + + if (shouldUsePowerShell) { + const argsAsString = commandParts.map((part) => `"\`"${part}\`""`).join(',') + commandParts = [ + 'Start-Process', + `"\`"${fullRunnerPath}\`""`, + '-Wait', + '-ArgumentList', + argsAsString, + '-NoNewWindow' + ] + bin = fullRunnerPath = 'powershell' + } + const safeCommand = getRunnerCallWithoutCredentials( [...commandParts], options?.env, @@ -1032,8 +1056,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 +1220,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[] = []