From 3a0fcd4b832186feee2241b032e10a4208ee02b8 Mon Sep 17 00:00:00 2001 From: Nat3z <66748576+Nat3z@users.noreply.github.com> Date: Wed, 25 Feb 2026 19:44:11 -0800 Subject: [PATCH 1/4] fix: possible fix for non-proton games launching --- .../src/electron/handlers/handler.library.ts | 30 ++++++++++++------- 1 file changed, 20 insertions(+), 10 deletions(-) diff --git a/application/src/electron/handlers/handler.library.ts b/application/src/electron/handlers/handler.library.ts index a33b1742..c6ad63bd 100644 --- a/application/src/electron/handlers/handler.library.ts +++ b/application/src/electron/handlers/handler.library.ts @@ -203,12 +203,12 @@ async function executeWrapperCommandForAppSteam( `[wrapper] Executing wrapper command for ${appInfo.name}: ${wrapperCommand}` ); - // Parse so paths with spaces aren't broken: split on the known verb first, - // parse only the prefix (which may contain quoted paths), and treat - // everything after the verb as a single path argument we replace with - // appInfo.launchExecutable. + // Parse so paths with spaces aren't broken. We only parse the launcher + // prefix and always replace the wrapped executable segment with the + // canonical appInfo.launchExecutable. const verb = 'waitforexitandrun'; const verbWithSpaces = ` ${verb} `; + const steamArgSeparator = ' -- '; const verbIndexInString = wrapperCommand.indexOf(verbWithSpaces); let parsed: ReturnType; if (verbIndexInString !== -1) { @@ -218,14 +218,22 @@ async function executeWrapperCommandForAppSteam( // Everything after " waitforexitandrun " is the exe path (may contain spaces); // we replace it with the canonical path, so we don't parse the suffix. } else { - parsed = shellQuoteParse(wrapperCommand); + // Non-Proton command variants usually end with " -- ". + // Parse only the prefix up to the last separator, then replace . + const lastSeparatorInString = wrapperCommand.lastIndexOf(steamArgSeparator); + if (lastSeparatorInString !== -1) { + const prefix = wrapperCommand.slice(0, lastSeparatorInString).trimEnd(); + parsed = shellQuoteParse(prefix); + parsed.push('--'); + } else { + parsed = shellQuoteParse(wrapperCommand); + } } - const verbIndex = parsed.findIndex((x) => x === verb); - const fixedArgs = - verbIndex === -1 - ? [...parsed, appInfo.launchExecutable] - : [...parsed.slice(0, verbIndex + 1), appInfo.launchExecutable]; + if (parsed.length === 0) { + return { success: false, error: 'Wrapper command could not be parsed' }; + } + const fixedArgs = [...parsed, appInfo.launchExecutable]; return await new Promise((resolve) => { const effectiveLaunchEnv = getEffectiveLaunchEnv(appInfo); @@ -245,10 +253,12 @@ async function executeWrapperCommandForAppSteam( ...(dllOverrideString ? { WINEDLLOVERRIDES: dllOverrideString } : {}), } : baseEnv; + const wrappedChild = spawn( parsed[0].toString(), fixedArgs.slice(1).map((x) => x.toString()), { + shell: true, cwd: appInfo.cwd, env, stdio: ['ignore', 'pipe', 'pipe'], From a88f107c79323fd05e555422a1f4036e5f5c5146 Mon Sep 17 00:00:00 2001 From: Nat3z <66748576+Nat3z@users.noreply.github.com> Date: Wed, 25 Feb 2026 20:34:08 -0800 Subject: [PATCH 2/4] fix: improve parsing of wrapper commands for better handling of executable paths --- .../src/electron/handlers/handler.library.ts | 37 ++++++++++++------- 1 file changed, 24 insertions(+), 13 deletions(-) diff --git a/application/src/electron/handlers/handler.library.ts b/application/src/electron/handlers/handler.library.ts index c6ad63bd..d2dcc16d 100644 --- a/application/src/electron/handlers/handler.library.ts +++ b/application/src/electron/handlers/handler.library.ts @@ -203,9 +203,10 @@ async function executeWrapperCommandForAppSteam( `[wrapper] Executing wrapper command for ${appInfo.name}: ${wrapperCommand}` ); - // Parse so paths with spaces aren't broken. We only parse the launcher - // prefix and always replace the wrapped executable segment with the - // canonical appInfo.launchExecutable. + // Parse so paths with spaces aren't broken: split on the known verb first, + // parse only the prefix (which may contain quoted paths), and treat + // everything after the verb as a single path argument we replace with + // appInfo.launchExecutable. const verb = 'waitforexitandrun'; const verbWithSpaces = ` ${verb} `; const steamArgSeparator = ' -- '; @@ -218,22 +219,32 @@ async function executeWrapperCommandForAppSteam( // Everything after " waitforexitandrun " is the exe path (may contain spaces); // we replace it with the canonical path, so we don't parse the suffix. } else { - // Non-Proton command variants usually end with " -- ". - // Parse only the prefix up to the last separator, then replace . - const lastSeparatorInString = wrapperCommand.lastIndexOf(steamArgSeparator); - if (lastSeparatorInString !== -1) { - const prefix = wrapperCommand.slice(0, lastSeparatorInString).trimEnd(); - parsed = shellQuoteParse(prefix); - parsed.push('--'); - } else { - parsed = shellQuoteParse(wrapperCommand); + parsed = shellQuoteParse(wrapperCommand); + + const firstToken = + parsed.length > 0 && typeof parsed[0] === 'string' ? parsed[0] : ''; + const looksLikeCollapsedLauncher = + firstToken.includes('steam-launch-wrapper') && + firstToken.includes(steamArgSeparator); + if (looksLikeCollapsedLauncher) { + const lastSeparatorInString = + wrapperCommand.lastIndexOf(steamArgSeparator); + if (lastSeparatorInString !== -1) { + const prefix = wrapperCommand.slice(0, lastSeparatorInString).trimEnd(); + parsed = shellQuoteParse(prefix); + parsed.push('--'); + } } } if (parsed.length === 0) { return { success: false, error: 'Wrapper command could not be parsed' }; } - const fixedArgs = [...parsed, appInfo.launchExecutable]; + const verbIndex = parsed.findIndex((x) => x === verb); + const fixedArgs = + verbIndex === -1 + ? [...parsed, appInfo.launchExecutable] + : [...parsed.slice(0, verbIndex + 1), appInfo.launchExecutable]; return await new Promise((resolve) => { const effectiveLaunchEnv = getEffectiveLaunchEnv(appInfo); From 0b8ba2a89a100cd7c9d48f3635d2c9fe4bca8234 Mon Sep 17 00:00:00 2001 From: Nat3z <66748576+Nat3z@users.noreply.github.com> Date: Wed, 25 Feb 2026 20:39:21 -0800 Subject: [PATCH 3/4] fix: enhance handling of split Proton executable paths in wrapper commands --- .../src/electron/handlers/handler.library.ts | 47 +++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/application/src/electron/handlers/handler.library.ts b/application/src/electron/handlers/handler.library.ts index d2dcc16d..1b529629 100644 --- a/application/src/electron/handlers/handler.library.ts +++ b/application/src/electron/handlers/handler.library.ts @@ -237,6 +237,53 @@ async function executeWrapperCommandForAppSteam( } } + // Some Steam wrapper payloads arrive with a Proton executable path split + // across tokens (for example: ".../common/Proton", "-", "Experimental/proton"). + // Recombine those segments so the wrapped launcher gets a valid executable path. + const normalizeSplitProtonExecutable = ( + tokens: ReturnType + ): ReturnType => { + const normalized: ReturnType = []; + for (let i = 0; i < tokens.length; i += 1) { + const token = tokens[i]; + if (typeof token !== 'string') { + normalized.push(token); + continue; + } + + const isSplitProtonStart = + token.includes('/steamapps/common/Proton') && + !token.includes('/proton') && + !token.endsWith('/proton'); + + if (!isSplitProtonStart) { + normalized.push(token); + continue; + } + + let merged = token; + let j = i + 1; + while (j < tokens.length) { + const next = tokens[j]; + if (typeof next !== 'string' || next === '--' || next === verb) { + break; + } + merged += ` ${next}`; + j += 1; + if (merged.includes('/proton') || merged.endsWith('/proton')) { + break; + } + } + + normalized.push(merged); + i = j - 1; + } + + return normalized; + }; + + parsed = normalizeSplitProtonExecutable(parsed); + if (parsed.length === 0) { return { success: false, error: 'Wrapper command could not be parsed' }; } From 8386250f47db426ca2f19c17d2ed774b8cf0e79b Mon Sep 17 00:00:00 2001 From: Nat3z <66748576+Nat3z@users.noreply.github.com> Date: Wed, 25 Feb 2026 20:44:28 -0800 Subject: [PATCH 4/4] fix: streamline command execution in wrapper by simplifying argument handling --- .../src/electron/handlers/handler.library.ts | 21 ++++++++++--------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/application/src/electron/handlers/handler.library.ts b/application/src/electron/handlers/handler.library.ts index 1b529629..9820f969 100644 --- a/application/src/electron/handlers/handler.library.ts +++ b/application/src/electron/handlers/handler.library.ts @@ -292,6 +292,12 @@ async function executeWrapperCommandForAppSteam( verbIndex === -1 ? [...parsed, appInfo.launchExecutable] : [...parsed.slice(0, verbIndex + 1), appInfo.launchExecutable]; + const wrappedCommand = parsed[0].toString(); + const wrappedArgv = fixedArgs.slice(1).map((x) => x.toString()); + + console.log( + `[wrapper] Resolved exec for ${appInfo.name}: command=${wrappedCommand} args=${JSON.stringify(wrappedArgv)}` + ); return await new Promise((resolve) => { const effectiveLaunchEnv = getEffectiveLaunchEnv(appInfo); @@ -312,16 +318,11 @@ async function executeWrapperCommandForAppSteam( } : baseEnv; - const wrappedChild = spawn( - parsed[0].toString(), - fixedArgs.slice(1).map((x) => x.toString()), - { - shell: true, - cwd: appInfo.cwd, - env, - stdio: ['ignore', 'pipe', 'pipe'], - } - ); + const wrappedChild = spawn(wrappedCommand, wrappedArgv, { + cwd: appInfo.cwd, + env, + stdio: ['ignore', 'pipe', 'pipe'], + }); wrappedChild.stdout?.on('data', (data) => { console.log(`[wrapper stdout] ${data}`);