From 17f45bb2c28b0bfca3cc3e5d51212e2fa6adbeda Mon Sep 17 00:00:00 2001 From: Flavio F Lima Date: Thu, 8 Jun 2023 18:56:56 +0200 Subject: [PATCH 01/19] feat: search for apple gaming porting kit on macOS --- src/backend/config.ts | 48 ++++++++++++++++++++++++++++++++++++++++++- src/common/types.ts | 2 +- 2 files changed, 48 insertions(+), 2 deletions(-) diff --git a/src/backend/config.ts b/src/backend/config.ts index 00fa4f10a5..3eb0b1bf29 100644 --- a/src/backend/config.ts +++ b/src/backend/config.ts @@ -286,6 +286,46 @@ abstract class GlobalConfig { return crossover } + public async getGamingPortingToolkit(): Promise> { + const gamingPortingToolkit = new Set() + + if (!isMac) { + return gamingPortingToolkit + } + + await execAsync('mdfind gameportingtoolkit-no-hud').then( + async ({ stdout }) => { + const gptNoHud = stdout + .split('\n') + .filter((p) => p.includes('gameportingtoolkit'))[0] + + // get the directory and add the binaries 'gameportingtoolkit-no-hud' and 'gameportingtoolkit' and 'gameportingtoolkit-no-esync' to the set + const gptHud = join(dirname(gptNoHud), 'gameportingtoolkit') + const gptNoEsync = join( + dirname(gptNoHud), + 'gameportingtoolkit-no-esync' + ) + + gamingPortingToolkit.add({ + bin: gptNoHud, + name: `Gaming Porting Toolkit No Hud`, + type: 'toolkit' + }) + gamingPortingToolkit.add({ + bin: gptHud, + name: `Gaming Porting Toolkit With Hud`, + type: 'toolkit' + }) + gamingPortingToolkit.add({ + bin: gptNoEsync, + name: `Gaming Porting Toolkit No Esync`, + type: 'toolkit' + }) + } + ) + return gamingPortingToolkit + } + public async getMacOsWineSet(): Promise> { if (!isMac) { return new Set() @@ -294,7 +334,13 @@ abstract class GlobalConfig { const crossover = await this.getCrossover() const wineOnMac = await this.getWineOnMac() const wineskinWine = await this.getWineskinWine() - return new Set([...crossover, ...wineOnMac, ...wineskinWine]) + const gamingPortingToolkit = await this.getGamingPortingToolkit() + return new Set([ + ...gamingPortingToolkit, + ...crossover, + ...wineOnMac, + ...wineskinWine + ]) } /** diff --git a/src/common/types.ts b/src/common/types.ts index cc694de06d..3c5129e11a 100644 --- a/src/common/types.ts +++ b/src/common/types.ts @@ -226,7 +226,7 @@ export type UserInfo = { export interface WineInstallation { bin: string name: string - type: 'wine' | 'proton' | 'crossover' + type: 'wine' | 'proton' | 'crossover' | 'toolkit' lib?: string lib32?: string wineserver?: string From 5b513408dd669eb432088d030d79b11aaba3da3d Mon Sep 17 00:00:00 2001 From: Flavio F Lima Date: Thu, 8 Jun 2023 18:57:22 +0200 Subject: [PATCH 02/19] feat: add support for epic and gog games --- src/backend/launcher.ts | 18 +++++++++++++++++- src/backend/storeManagers/gog/games.ts | 14 +++++++++++--- src/backend/storeManagers/legendary/games.ts | 14 +++++++++++--- 3 files changed, 39 insertions(+), 7 deletions(-) diff --git a/src/backend/launcher.ts b/src/backend/launcher.ts index 2441e4cf46..030c27d803 100644 --- a/src/backend/launcher.ts +++ b/src/backend/launcher.ts @@ -164,6 +164,13 @@ async function prepareWineLaunch( GameConfig.get(appName).config || (await GameConfig.get(appName).getSettings()) + if (gameSettings.wineVersion.type === 'toolkit') { + return { + success: true, + envVars: {} + } + } + if (!(await validWine(gameSettings.wineVersion))) { const defaultWine = GlobalConfig.get().getSettings().wineVersion // now check if the default wine is valid as well @@ -457,6 +464,11 @@ export async function verifyWinePrefix( ): Promise<{ res: ExecResult; updated: boolean }> { const { winePrefix = defaultWinePrefix, wineVersion } = settings + // if type === 'toolkit', we don't need to verify the prefix + if (wineVersion.type === 'toolkit') { + return { res: { stdout: '', stderr: '' }, updated: false } + } + const isValidWine = await validWine(wineVersion) if (!isValidWine) { @@ -522,7 +534,11 @@ async function runWineCommand({ : GlobalConfig.get().getSettings() const { wineVersion, winePrefix } = settings - if (!skipPrefixCheckIKnowWhatImDoing && wineVersion.type !== 'crossover') { + if ( + !skipPrefixCheckIKnowWhatImDoing && + wineVersion.type !== 'crossover' && + wineVersion.type !== 'toolkit' + ) { let requiredPrefixFiles = [ 'dosdevices', 'drive_c', diff --git a/src/backend/storeManagers/gog/games.ts b/src/backend/storeManagers/gog/games.ts index 4d69b3a05d..37866caad5 100644 --- a/src/backend/storeManagers/gog/games.ts +++ b/src/backend/storeManagers/gog/games.ts @@ -471,10 +471,18 @@ export async function launch( ? wineExec.replaceAll("'", '') : wineExec + const wineFlagsObj = { + proton: ['--no-wine', '--wrapper', `'${wineBin}' run`], + wine: ['--wine', wineBin], + toolkit: [ + '--wrapper', + `${wineBin} ${gameSettings.winePrefix}`, + '--no-wine' + ] + } + wineFlag.push( - ...(wineType === 'proton' - ? ['--no-wine', '--wrapper', `'${wineBin}' run`] - : ['--wine', wineBin]) + ...(wineFlagsObj[wineType as keyof typeof wineFlagsObj] || []) ) } diff --git a/src/backend/storeManagers/legendary/games.ts b/src/backend/storeManagers/legendary/games.ts index 789aa79ccd..b159c99815 100644 --- a/src/backend/storeManagers/legendary/games.ts +++ b/src/backend/storeManagers/legendary/games.ts @@ -851,10 +851,18 @@ export async function launch( ? wineExec.replaceAll("'", '') : wineExec + const wineFlagsObj = { + proton: ['--no-wine', '--wrapper', `'${wineBin}' run`], + wine: ['--wine', wineBin], + toolkit: [ + '--wrapper', + `${wineBin} ${gameSettings.winePrefix}`, + '--no-wine' + ] + } + wineFlag.push( - ...(wineType === 'proton' - ? ['--no-wine', '--wrapper', `'${wineBin}' run`] - : ['--wine', wineBin]) + ...(wineFlagsObj[wineType as keyof typeof wineFlagsObj] || []) ) } From a9179d7d657c694ab7cabc891cbe41a2e70443fd Mon Sep 17 00:00:00 2001 From: Flavio F Lima Date: Thu, 8 Jun 2023 19:13:23 +0200 Subject: [PATCH 03/19] feat: add support for Sideloaded Games --- src/backend/launcher.ts | 22 ++++++++++++++++++- src/backend/main.ts | 6 ++++- .../storeManagers/storeManagerCommon/games.ts | 8 +++++++ 3 files changed, 34 insertions(+), 2 deletions(-) diff --git a/src/backend/launcher.ts b/src/backend/launcher.ts index 030c27d803..29729c125b 100644 --- a/src/backend/launcher.ts +++ b/src/backend/launcher.ts @@ -25,7 +25,8 @@ import { quoteIfNecessary, errorHandler, removeQuoteIfNecessary, - memoryLog + memoryLog, + spawnAsync } from './utils' import { logDebug, @@ -519,6 +520,25 @@ function launchCleanup(rpcClient?: RpcClient) { logInfo('Stopped Discord Rich Presence', LogPrefix.Backend) } } + +export async function runToolkitCommand( + gameSettings: GameSettings, + command: string +): Promise<{ stderr: string; stdout: string }> { + const { + winePrefix = defaultWinePrefix, + wineVersion: { bin: wineBin } + } = gameSettings + + logInfo( + `Running App using Apple's Gaming Toolkit: ${wineBin} ${winePrefix} ${command}`, + LogPrefix.Backend + ) + const { stderr, stdout } = await spawnAsync(wineBin, [winePrefix, command]) + + return { stderr, stdout } +} + async function runWineCommand({ gameSettings, commandParts, diff --git a/src/backend/main.ts b/src/backend/main.ts index 5c59314e4c..0c050d969d 100644 --- a/src/backend/main.ts +++ b/src/backend/main.ts @@ -111,7 +111,7 @@ import { } from './logger/logger' import { gameInfoStore } from 'backend/storeManagers/legendary/electronStores' import { getFonts } from 'font-list' -import { runWineCommand, verifyWinePrefix } from './launcher' +import { runToolkitCommand, runWineCommand, verifyWinePrefix } from './launcher' import shlex from 'shlex' import { initQueue } from './downloadmanager/downloadqueue' import { @@ -575,6 +575,10 @@ async function runWineCommandOnGame( const { folder_name } = gameManagerMap[runner].getGameInfo(appName) const gameSettings = await gameManagerMap[runner].getSettings(appName) + if (gameSettings.wineVersion.type === 'toolkit') { + return runToolkitCommand(gameSettings, commandParts[0]) + } + return runWineCommand({ gameSettings, installFolderName: folder_name, diff --git a/src/backend/storeManagers/storeManagerCommon/games.ts b/src/backend/storeManagers/storeManagerCommon/games.ts index 79b6c4144b..aae0fee03a 100644 --- a/src/backend/storeManagers/storeManagerCommon/games.ts +++ b/src/backend/storeManagers/storeManagerCommon/games.ts @@ -9,6 +9,7 @@ import { callRunner, launchCleanup, prepareLaunch, + runToolkitCommand, runWineCommand, setupEnvVars, setupWrappers @@ -173,6 +174,13 @@ export async function launchGame( LogPrefix.Backend ) + if (gameSettings.wineVersion.type === 'toolkit') { + logInfo('Using wine toolkit', LogPrefix.Backend) + await runToolkitCommand(gameSettings, executable) + launchCleanup(rpcClient) + return true + } + await runWineCommand({ commandParts: [executable, launcherArgs ?? ''], gameSettings, From fb71728437e1671b1d86ae2d8c2ff1129d7a1aae Mon Sep 17 00:00:00 2001 From: Flavio F Lima Date: Thu, 8 Jun 2023 19:20:52 +0200 Subject: [PATCH 04/19] fix: run installer first button on sideload dialog --- src/backend/launcher.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/backend/launcher.ts b/src/backend/launcher.ts index 29729c125b..43b89ce96b 100644 --- a/src/backend/launcher.ts +++ b/src/backend/launcher.ts @@ -554,11 +554,11 @@ async function runWineCommand({ : GlobalConfig.get().getSettings() const { wineVersion, winePrefix } = settings - if ( - !skipPrefixCheckIKnowWhatImDoing && - wineVersion.type !== 'crossover' && - wineVersion.type !== 'toolkit' - ) { + if (wineVersion.type === 'toolkit') { + return runToolkitCommand(settings, commandParts[0]) + } + + if (!skipPrefixCheckIKnowWhatImDoing && wineVersion.type !== 'crossover') { let requiredPrefixFiles = [ 'dosdevices', 'drive_c', From 6d2e9ee7ce4a85e69f9e346fad5d06c5ee81a71e Mon Sep 17 00:00:00 2001 From: Flavio F Lima Date: Thu, 8 Jun 2023 21:04:02 +0200 Subject: [PATCH 05/19] fix: check if path exists --- src/backend/config.ts | 50 +++++++++++++++++++++++++------------------ 1 file changed, 29 insertions(+), 21 deletions(-) diff --git a/src/backend/config.ts b/src/backend/config.ts index 3eb0b1bf29..48c2f29117 100644 --- a/src/backend/config.ts +++ b/src/backend/config.ts @@ -293,34 +293,42 @@ abstract class GlobalConfig { return gamingPortingToolkit } + logInfo('Searching for Gaming Porting Toolkit', LogPrefix.GlobalConfig) await execAsync('mdfind gameportingtoolkit-no-hud').then( async ({ stdout }) => { const gptNoHud = stdout .split('\n') .filter((p) => p.includes('gameportingtoolkit'))[0] - // get the directory and add the binaries 'gameportingtoolkit-no-hud' and 'gameportingtoolkit' and 'gameportingtoolkit-no-esync' to the set - const gptHud = join(dirname(gptNoHud), 'gameportingtoolkit') - const gptNoEsync = join( - dirname(gptNoHud), - 'gameportingtoolkit-no-esync' - ) + if (gptNoHud) { + logInfo( + `Found Gaming Porting Toolkit at ${dirname(gptNoHud)}`, + LogPrefix.GlobalConfig + ) - gamingPortingToolkit.add({ - bin: gptNoHud, - name: `Gaming Porting Toolkit No Hud`, - type: 'toolkit' - }) - gamingPortingToolkit.add({ - bin: gptHud, - name: `Gaming Porting Toolkit With Hud`, - type: 'toolkit' - }) - gamingPortingToolkit.add({ - bin: gptNoEsync, - name: `Gaming Porting Toolkit No Esync`, - type: 'toolkit' - }) + // get the directory and add the binaries 'gameportingtoolkit-no-hud' and 'gameportingtoolkit' and 'gameportingtoolkit-no-esync' to the set + const gptHud = join(dirname(gptNoHud), 'gameportingtoolkit') + const gptNoEsync = join( + dirname(gptNoHud), + 'gameportingtoolkit-no-esync' + ) + + gamingPortingToolkit.add({ + bin: gptNoHud, + name: `Gaming Porting Toolkit No Hud`, + type: 'toolkit' + }) + gamingPortingToolkit.add({ + bin: gptHud, + name: `Gaming Porting Toolkit With Hud`, + type: 'toolkit' + }) + gamingPortingToolkit.add({ + bin: gptNoEsync, + name: `Gaming Porting Toolkit No Esync`, + type: 'toolkit' + }) + } } ) return gamingPortingToolkit From 4bacefb5e7e3d02bda044f076e145a8f76c26cf8 Mon Sep 17 00:00:00 2001 From: Flavio F Lima Date: Fri, 9 Jun 2023 14:20:21 +0200 Subject: [PATCH 06/19] feat: search for wine64 from toolkit as well --- src/backend/config.ts | 36 +++++++++++++++++++ src/backend/launcher.ts | 2 +- src/backend/tools.ts | 9 +++++ .../screens/Settings/components/AutoDXVK.tsx | 4 ++- 4 files changed, 49 insertions(+), 2 deletions(-) diff --git a/src/backend/config.ts b/src/backend/config.ts index 48c2f29117..04b0c197c9 100644 --- a/src/backend/config.ts +++ b/src/backend/config.ts @@ -334,6 +334,39 @@ abstract class GlobalConfig { return gamingPortingToolkit } + public async getGamingPortingToolkitWine(): Promise> { + const gamingPortingToolkitWine = new Set() + if (!isMac) { + return gamingPortingToolkitWine + } + + logInfo('Searching for Gaming Porting Toolkit Wine', LogPrefix.GlobalConfig) + await execAsync('mdfind wine64').then(async ({ stdout }) => { + const wineBin = stdout + .split('\n') + .filter((p) => p.includes('game-porting-toolkit'))[0] + if (existsSync(wineBin)) { + try { + const { stdout: out } = await execAsync(`'${wineBin}' --version`) + const version = out.split('\n')[0] + gamingPortingToolkitWine.add({ + ...this.getWineExecs(wineBin), + name: `Game Porting Toolkit Wine - ${version}`, + type: 'wine', + bin: wineBin + }) + } catch (error) { + logError( + `Error getting wine version for ${wineBin}`, + LogPrefix.GlobalConfig + ) + } + } + }) + + return gamingPortingToolkitWine + } + public async getMacOsWineSet(): Promise> { if (!isMac) { return new Set() @@ -343,8 +376,11 @@ abstract class GlobalConfig { const wineOnMac = await this.getWineOnMac() const wineskinWine = await this.getWineskinWine() const gamingPortingToolkit = await this.getGamingPortingToolkit() + const gamingPortingToolkitWine = await this.getGamingPortingToolkitWine() + return new Set([ ...gamingPortingToolkit, + ...gamingPortingToolkitWine, ...crossover, ...wineOnMac, ...wineskinWine diff --git a/src/backend/launcher.ts b/src/backend/launcher.ts index 43b89ce96b..a1136ff94e 100644 --- a/src/backend/launcher.ts +++ b/src/backend/launcher.ts @@ -316,7 +316,7 @@ function setupWineEnvVars(gameSettings: GameSettings, gameId = '0') { } if (gameSettings.showFps) { - ret.DXVK_HUD = 'fps' + isMac ? (ret.MTL_HUD_ENABLED = '1') : (ret.DXVK_HUD = 'fps') } if (gameSettings.enableDXVKFpsLimit) { ret.DXVK_FRAME_RATE = gameSettings.DXVKFpsCap diff --git a/src/backend/tools.ts b/src/backend/tools.ts index 0c4f94001e..6e68829d8f 100644 --- a/src/backend/tools.ts +++ b/src/backend/tools.ts @@ -141,6 +141,15 @@ export const DXVK = { return true } + if (gameSettings.wineVersion.bin.includes('toolkit')) { + logWarning( + 'Skipping DXVK install on Game Porting Toolkit prefix!', + LogPrefix.DXVKInstaller + ) + // will return true anyway because otherwise the toggle will be stuck and the prefix might just not be crated yet. + return true + } + tool = isMac ? 'dxvk-macOS' : tool if (!existsSync(`${toolsPath}/${tool}/latest_${tool}`)) { diff --git a/src/frontend/screens/Settings/components/AutoDXVK.tsx b/src/frontend/screens/Settings/components/AutoDXVK.tsx index 757c5dd4c4..c9b91c2bcc 100644 --- a/src/frontend/screens/Settings/components/AutoDXVK.tsx +++ b/src/frontend/screens/Settings/components/AutoDXVK.tsx @@ -17,7 +17,9 @@ const AutoDXVK = () => { const { appName } = useContext(SettingsContext) const [installingDxvk, setInstallingDxvk] = React.useState(false) - if (wineVersion.type !== 'wine') { + console.log('wineVersion', wineVersion) + + if (wineVersion.type !== 'wine' || wineVersion.bin.includes('toolkit')) { return <> } From d47df31b847a0ffacfea7012faacd843d03711f6 Mon Sep 17 00:00:00 2001 From: Flavio F Lima Date: Fri, 9 Jun 2023 16:29:43 +0200 Subject: [PATCH 07/19] fix: filter --- src/backend/config.ts | 23 +++---------------- .../screens/Settings/components/AutoDXVK.tsx | 2 -- .../components/EnableDXVKFpsLimit.tsx | 9 +++++++- .../Settings/sections/GamesSettings/index.tsx | 7 +++--- 4 files changed, 15 insertions(+), 26 deletions(-) diff --git a/src/backend/config.ts b/src/backend/config.ts index 04b0c197c9..c908d98253 100644 --- a/src/backend/config.ts +++ b/src/backend/config.ts @@ -306,26 +306,9 @@ abstract class GlobalConfig { LogPrefix.GlobalConfig ) - // get the directory and add the binaries 'gameportingtoolkit-no-hud' and 'gameportingtoolkit' and 'gameportingtoolkit-no-esync' to the set - const gptHud = join(dirname(gptNoHud), 'gameportingtoolkit') - const gptNoEsync = join( - dirname(gptNoHud), - 'gameportingtoolkit-no-esync' - ) - gamingPortingToolkit.add({ bin: gptNoHud, - name: `Gaming Porting Toolkit No Hud`, - type: 'toolkit' - }) - gamingPortingToolkit.add({ - bin: gptHud, - name: `Gaming Porting Toolkit With Hud`, - type: 'toolkit' - }) - gamingPortingToolkit.add({ - bin: gptNoEsync, - name: `Gaming Porting Toolkit No Esync`, + name: `Gaming Toolkit Script`, type: 'toolkit' }) } @@ -344,14 +327,14 @@ abstract class GlobalConfig { await execAsync('mdfind wine64').then(async ({ stdout }) => { const wineBin = stdout .split('\n') - .filter((p) => p.includes('game-porting-toolkit'))[0] + .filter((p) => p.includes('bin/wine64'))[0] if (existsSync(wineBin)) { try { const { stdout: out } = await execAsync(`'${wineBin}' --version`) const version = out.split('\n')[0] gamingPortingToolkitWine.add({ ...this.getWineExecs(wineBin), - name: `Game Porting Toolkit Wine - ${version}`, + name: `Gaming Toolkit Standalone Wine - ${version}`, type: 'wine', bin: wineBin }) diff --git a/src/frontend/screens/Settings/components/AutoDXVK.tsx b/src/frontend/screens/Settings/components/AutoDXVK.tsx index c9b91c2bcc..39d9f97f23 100644 --- a/src/frontend/screens/Settings/components/AutoDXVK.tsx +++ b/src/frontend/screens/Settings/components/AutoDXVK.tsx @@ -17,8 +17,6 @@ const AutoDXVK = () => { const { appName } = useContext(SettingsContext) const [installingDxvk, setInstallingDxvk] = React.useState(false) - console.log('wineVersion', wineVersion) - if (wineVersion.type !== 'wine' || wineVersion.bin.includes('toolkit')) { return <> } diff --git a/src/frontend/screens/Settings/components/EnableDXVKFpsLimit.tsx b/src/frontend/screens/Settings/components/EnableDXVKFpsLimit.tsx index 1b14e4472a..eb8b1201e0 100644 --- a/src/frontend/screens/Settings/components/EnableDXVKFpsLimit.tsx +++ b/src/frontend/screens/Settings/components/EnableDXVKFpsLimit.tsx @@ -6,6 +6,7 @@ import ContextProvider from 'frontend/state/ContextProvider' import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' import { faCircleInfo } from '@fortawesome/free-solid-svg-icons' import SettingsContext from '../SettingsContext' +import { defaultWineVersion } from '..' const EnableDXVKFpsLimit = () => { const { t } = useTranslation() @@ -17,8 +18,14 @@ const EnableDXVKFpsLimit = () => { false ) const [DXVKFpsCap, setDXVKFpsCap] = useSetting('DXVKFpsCap', '') + const [wineVersion] = useSetting('wineVersion', defaultWineVersion) - if (isWin || isLinuxNative || isMacNative) { + if ( + isWin || + isLinuxNative || + isMacNative || + wineVersion.bin.includes('toolkit') + ) { return <> } diff --git a/src/frontend/screens/Settings/sections/GamesSettings/index.tsx b/src/frontend/screens/Settings/sections/GamesSettings/index.tsx index 21f352f279..00382ee4c3 100644 --- a/src/frontend/screens/Settings/sections/GamesSettings/index.tsx +++ b/src/frontend/screens/Settings/sections/GamesSettings/index.tsx @@ -51,6 +51,7 @@ export default function GamesSettings({ useDetails = true }: Props) { const [wineVersion] = useSetting('wineVersion', defaultWineVersion) const [nativeGame, setNativeGame] = useState(false) const isLinux = platform === 'linux' + const isWin = platform === 'win32' const isCrossover = wineVersion?.type === 'crossover' const hasCloudSaves = gameInfo?.cloud_save_enabled && gameInfo.install.platform !== 'linux' @@ -125,16 +126,16 @@ export default function GamesSettings({ useDetails = true }: Props) { {!nativeGame && } - {isLinux && !nativeGame && ( + {!isWin && !nativeGame && ( <> - - {isLinux && ( <> + + From ac6d6157dff505f77514aede8a1117c5ed7606c4 Mon Sep 17 00:00:00 2001 From: Flavio F Lima Date: Fri, 9 Jun 2023 16:45:53 +0200 Subject: [PATCH 08/19] fix: filter only toolkit --- src/backend/config.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/backend/config.ts b/src/backend/config.ts index c908d98253..a3141af67b 100644 --- a/src/backend/config.ts +++ b/src/backend/config.ts @@ -327,7 +327,7 @@ abstract class GlobalConfig { await execAsync('mdfind wine64').then(async ({ stdout }) => { const wineBin = stdout .split('\n') - .filter((p) => p.includes('bin/wine64'))[0] + .filter((p) => p.includes('game-porting-toolkit/1.0/bin/wine64'))[0] if (existsSync(wineBin)) { try { const { stdout: out } = await execAsync(`'${wineBin}' --version`) From 4b6f67ef255fc5fac32162c030d4fe4901ce129c Mon Sep 17 00:00:00 2001 From: Flavio F Lima Date: Mon, 12 Jun 2023 10:53:00 +0200 Subject: [PATCH 09/19] chore: ignore legendary error due to missing crossover --- src/backend/utils.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/backend/utils.ts b/src/backend/utils.ts index 6c013a1b77..a900f96b81 100644 --- a/src/backend/utils.ts +++ b/src/backend/utils.ts @@ -388,6 +388,8 @@ async function errorHandler({ const deletedFolderMsg = 'appears to be deleted' const expiredCredentials = 'No saved credentials' const legendaryRegex = /legendary.*\.py/ + // this message appears on macOS when no Crossover was found in the system but its a false alarm + const ignoreMessage = 'IndexError: list index out of range' if (logPath) { execAsync(`tail "${logPath}" | grep 'disk space'`) @@ -410,6 +412,9 @@ async function errorHandler({ }) } if (error) { + if (error.includes(ignoreMessage)) { + return + } if (error.includes(deletedFolderMsg) && appName) { const runner = r.toLocaleLowerCase() as Runner const { title } = gameManagerMap[runner].getGameInfo(appName) From ef4c893208e65799b732dc9047a6aecf3afb51f4 Mon Sep 17 00:00:00 2001 From: Flavio F Lima Date: Mon, 12 Jun 2023 11:00:12 +0200 Subject: [PATCH 10/19] fix: be even more specific on filter --- src/backend/config.ts | 45 ++++++++++++++++++++++++++++++++----------- 1 file changed, 34 insertions(+), 11 deletions(-) diff --git a/src/backend/config.ts b/src/backend/config.ts index a3141af67b..8e09eb1af7 100644 --- a/src/backend/config.ts +++ b/src/backend/config.ts @@ -286,6 +286,11 @@ abstract class GlobalConfig { return crossover } + /** + * Detects Gaming Porting Toolkit Wine installs on Mac + * @returns Promise> + * @memberof GlobalConfig + **/ public async getGamingPortingToolkit(): Promise> { const gamingPortingToolkit = new Set() @@ -294,20 +299,16 @@ abstract class GlobalConfig { } logInfo('Searching for Gaming Porting Toolkit', LogPrefix.GlobalConfig) - await execAsync('mdfind gameportingtoolkit-no-hud').then( + await execAsync('which gameportingtoolkit-no-hud').then( async ({ stdout }) => { - const gptNoHud = stdout - .split('\n') - .filter((p) => p.includes('gameportingtoolkit'))[0] - - if (gptNoHud) { + if (stdout) { logInfo( - `Found Gaming Porting Toolkit at ${dirname(gptNoHud)}`, + `Found Gaming Porting Toolkit at ${dirname(stdout)}`, LogPrefix.GlobalConfig ) gamingPortingToolkit.add({ - bin: gptNoHud, + bin: stdout, name: `Gaming Toolkit Script`, type: 'toolkit' }) @@ -317,6 +318,11 @@ abstract class GlobalConfig { return gamingPortingToolkit } + /** + * Detects Gaming Porting Toolkit Wine installs on Mac + * @returns Promise> + * @memberof GlobalConfig + **/ public async getGamingPortingToolkitWine(): Promise> { const gamingPortingToolkitWine = new Set() if (!isMac) { @@ -325,10 +331,20 @@ abstract class GlobalConfig { logInfo('Searching for Gaming Porting Toolkit Wine', LogPrefix.GlobalConfig) await execAsync('mdfind wine64').then(async ({ stdout }) => { - const wineBin = stdout - .split('\n') - .filter((p) => p.includes('game-porting-toolkit/1.0/bin/wine64'))[0] + const wineBin = stdout.split('\n').filter((p) => { + if (!p.includes('game-porting-toolkit')) { + return false + } + + const parts = p.split('/') + const lastPart = parts[parts.length - 1] + return lastPart === 'wine64' + })[0] if (existsSync(wineBin)) { + logInfo( + `Found Gaming Porting Toolkit Wine at ${dirname(wineBin)}`, + LogPrefix.GlobalConfig + ) try { const { stdout: out } = await execAsync(`'${wineBin}' --version`) const version = out.split('\n')[0] @@ -336,6 +352,8 @@ abstract class GlobalConfig { ...this.getWineExecs(wineBin), name: `Gaming Toolkit Standalone Wine - ${version}`, type: 'wine', + lib: `${dirname(wineBin)}/../lib`, + lib32: `${dirname(wineBin)}/../lib`, bin: wineBin }) } catch (error) { @@ -350,6 +368,11 @@ abstract class GlobalConfig { return gamingPortingToolkitWine } + /** + * Detects Wine on Mac + * @returns Promise> + * @memberof GlobalConfig + **/ public async getMacOsWineSet(): Promise> { if (!isMac) { return new Set() From 4993bf527dcbb4453be262c26ac789bb52c34f44 Mon Sep 17 00:00:00 2001 From: Flavio F Lima Date: Mon, 12 Jun 2023 11:00:33 +0200 Subject: [PATCH 11/19] feat: enable metal hud for native games --- src/backend/launcher.ts | 4 ++++ src/frontend/screens/Settings/components/ShowFPS.tsx | 11 ++++++++--- .../screens/Settings/sections/GamesSettings/index.tsx | 2 +- 3 files changed, 13 insertions(+), 4 deletions(-) diff --git a/src/backend/launcher.ts b/src/backend/launcher.ts index a1136ff94e..cb5a335e2e 100644 --- a/src/backend/launcher.ts +++ b/src/backend/launcher.ts @@ -259,6 +259,10 @@ function setupEnvVars(gameSettings: GameSettings) { ret.__GLX_VENDOR_LIBRARY_NAME = 'nvidia' } + if (isMac && gameSettings.showFps) { + ret.MTL_HUD_ENABLED = '1' + } + if (gameSettings.enviromentOptions) { gameSettings.enviromentOptions.forEach((envEntry: EnviromentVariable) => { ret[envEntry.key] = removeQuoteIfNecessary(envEntry.value) diff --git a/src/frontend/screens/Settings/components/ShowFPS.tsx b/src/frontend/screens/Settings/components/ShowFPS.tsx index 50ced24394..f930d21628 100644 --- a/src/frontend/screens/Settings/components/ShowFPS.tsx +++ b/src/frontend/screens/Settings/components/ShowFPS.tsx @@ -9,9 +9,10 @@ const ShowFPS = () => { const { t } = useTranslation() const [showFps, setShowFps] = useSetting('showFps', false) const { platform } = useContext(ContextProvider) - const { isMacNative, isLinuxNative } = useContext(SettingsContext) + const { isLinuxNative } = useContext(SettingsContext) const isWin = platform === 'win32' - const shouldRenderFpsOption = !isMacNative && !isWin && !isLinuxNative + const isLinux = platform === 'linux' + const shouldRenderFpsOption = !isWin || (isLinux && !isLinuxNative) if (!shouldRenderFpsOption) { return <> @@ -22,7 +23,11 @@ const ShowFPS = () => { htmlId="showFPS" value={showFps} handleChange={() => setShowFps(!showFps)} - title={t('setting.showfps')} + title={ + isLinux + ? t('setting.showfps') + : t('setting.showMetalOverlay', 'Show Stats Overlay') + } /> ) } diff --git a/src/frontend/screens/Settings/sections/GamesSettings/index.tsx b/src/frontend/screens/Settings/sections/GamesSettings/index.tsx index 00382ee4c3..9053238867 100644 --- a/src/frontend/screens/Settings/sections/GamesSettings/index.tsx +++ b/src/frontend/screens/Settings/sections/GamesSettings/index.tsx @@ -122,7 +122,7 @@ export default function GamesSettings({ useDetails = true }: Props) { > - {!nativeGame && } + {!nativeGame && } From 635b3e952c1b8fee97e35f7235d3c864f5b58de6 Mon Sep 17 00:00:00 2001 From: Flavio F Lima Date: Mon, 12 Jun 2023 11:02:17 +0200 Subject: [PATCH 12/19] i18n: updated keys --- public/locales/en/translation.json | 1 + 1 file changed, 1 insertion(+) diff --git a/public/locales/en/translation.json b/public/locales/en/translation.json index b34fe26dec..63f21107f8 100644 --- a/public/locales/en/translation.json +++ b/public/locales/en/translation.json @@ -563,6 +563,7 @@ }, "select_theme": "Select Theme", "showfps": "Show FPS (DX9, 10 and 11)", + "showMetalOverlay": "Show Stats Overlay", "start-in-tray": "Start Minimized", "steamruntime": "Use Steam Runtime", "winecrossoverbottle": "CrossOver Bottle", From 1c0dc205bf9d04208c6c1b11633cb2de90a9e627 Mon Sep 17 00:00:00 2001 From: Flavio F Lima Date: Mon, 12 Jun 2023 14:23:29 +0200 Subject: [PATCH 13/19] fix: pass env vars to launch command --- src/backend/config.ts | 2 +- src/backend/launcher.ts | 9 ++++++++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/src/backend/config.ts b/src/backend/config.ts index 8e09eb1af7..ce881dadb6 100644 --- a/src/backend/config.ts +++ b/src/backend/config.ts @@ -308,7 +308,7 @@ abstract class GlobalConfig { ) gamingPortingToolkit.add({ - bin: stdout, + bin: stdout.replace('\n', ''), name: `Gaming Toolkit Script`, type: 'toolkit' }) diff --git a/src/backend/launcher.ts b/src/backend/launcher.ts index cb5a335e2e..3cd596a37c 100644 --- a/src/backend/launcher.ts +++ b/src/backend/launcher.ts @@ -534,11 +534,18 @@ export async function runToolkitCommand( wineVersion: { bin: wineBin } } = gameSettings + const env_vars = { + ...process.env, + ...setupEnvVars(gameSettings) + } + logInfo( `Running App using Apple's Gaming Toolkit: ${wineBin} ${winePrefix} ${command}`, LogPrefix.Backend ) - const { stderr, stdout } = await spawnAsync(wineBin, [winePrefix, command]) + const { stderr, stdout } = await spawnAsync(wineBin, [winePrefix, command], { + env: env_vars + }) return { stderr, stdout } } From c1e515aed8b54e378bbcbd1879a1f9c646340100 Mon Sep 17 00:00:00 2001 From: Flavio F Lima Date: Wed, 14 Jun 2023 15:17:57 +0200 Subject: [PATCH 14/19] tech: refactor wine and proton methods to another file --- src/backend/config.ts | 429 +--------------------- src/backend/utils/compatibility_layers.ts | 410 +++++++++++++++++++++ 2 files changed, 430 insertions(+), 409 deletions(-) create mode 100644 src/backend/utils/compatibility_layers.ts diff --git a/src/backend/config.ts b/src/backend/config.ts index ce881dadb6..fe14f68218 100644 --- a/src/backend/config.ts +++ b/src/backend/config.ts @@ -1,12 +1,5 @@ -import { - existsSync, - mkdirSync, - readFileSync, - readdirSync, - writeFileSync -} from 'graceful-fs' -import { homedir, userInfo as user } from 'os' -import { parse as plistParse, PlistObject } from 'plist' +import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'graceful-fs' +import { userInfo as user } from 'os' import { AppSettings, @@ -20,19 +13,24 @@ import { defaultWinePrefix, gamesConfigPath, heroicInstallPath, - toolsPath, userHome, isFlatpak, isMac, isWindows, - getSteamLibraries, getSteamCompatFolder, configStore } from './constants' -import { execAsync } from './utils' -import { execSync } from 'child_process' + import { logError, logInfo, LogPrefix } from './logger/logger' -import { dirname, join } from 'path' +import { + getCrossover, + getDefaultWine, + getGamingPortingToolkit, + getGamingPortingToolkitWine, + getLinuxWineSet, + getWineOnMac, + getWineskinWine +} from './utils/compatibility_layers' /** * This class does config handling. @@ -126,248 +124,6 @@ abstract class GlobalConfig { } } - /** - * Loads the default wine installation path and version. - * - * @returns Promise - */ - public getDefaultWine(): WineInstallation { - const defaultWine: WineInstallation = { - bin: '', - name: 'Default Wine - Not Found', - type: 'wine' - } - - try { - let stdout = execSync(`which wine`).toString() - const wineBin = stdout.split('\n')[0] - defaultWine.bin = wineBin - - stdout = execSync(`wine --version`).toString() - const version = stdout.split('\n')[0] - defaultWine.name = `Wine Default - ${version}` - - return { - ...defaultWine, - ...this.getWineExecs(wineBin) - } - } catch { - return defaultWine - } - } - - /** - * Detects Wine installed on home application folder on Mac - * - * @returns Promise> - */ - public async getWineOnMac(): Promise> { - const wineSet = new Set() - if (!isMac) { - return wineSet - } - - const winePaths = new Set() - - // search for wine installed on $HOME/Library/Application Support/heroic/tools/wine - const wineToolsPath = `${toolsPath}/wine/` - if (existsSync(wineToolsPath)) { - readdirSync(wineToolsPath).forEach((path) => { - winePaths.add(join(wineToolsPath, path)) - }) - } - - // search for wine installed around the system - await execAsync('mdfind kMDItemCFBundleIdentifier = "*.wine"').then( - async ({ stdout }) => { - stdout.split('\n').forEach((winePath) => { - winePaths.add(winePath) - }) - } - ) - - winePaths.forEach((winePath) => { - const infoFilePath = join(winePath, 'Contents/Info.plist') - if (winePath && existsSync(infoFilePath)) { - const info = plistParse( - readFileSync(infoFilePath, 'utf-8') - ) as PlistObject - const version = info['CFBundleShortVersionString'] || '' - const name = info['CFBundleName'] || '' - const wineBin = join(winePath, '/Contents/Resources/wine/bin/wine64') - if (existsSync(wineBin)) { - wineSet.add({ - ...this.getWineExecs(wineBin), - lib: `${winePath}/Contents/Resources/wine/lib`, - lib32: `${winePath}/Contents/Resources/wine/lib`, - bin: wineBin, - name: `${name} - ${version}`, - type: 'wine', - ...this.getWineExecs(wineBin) - }) - } - } - }) - - return wineSet - } - - public async getWineskinWine(): Promise> { - const wineSet = new Set() - if (!isMac) { - return wineSet - } - const wineSkinPath = `${userHome}/Applications/Wineskin` - if (existsSync(wineSkinPath)) { - const apps = readdirSync(wineSkinPath) - for (const app of apps) { - if (app.includes('.app')) { - const wineBin = `${userHome}/Applications/Wineskin/${app}/Contents/SharedSupport/wine/bin/wine64` - if (existsSync(wineBin)) { - try { - const { stdout: out } = await execAsync(`'${wineBin}' --version`) - const version = out.split('\n')[0] - wineSet.add({ - ...this.getWineExecs(wineBin), - lib: `${userHome}/Applications/Wineskin/${app}/Contents/SharedSupport/wine/lib`, - lib32: `${userHome}/Applications/Wineskin/${app}/Contents/SharedSupport/wine/lib`, - name: `Wineskin - ${version}`, - type: 'wine', - bin: wineBin - }) - } catch (error) { - logError( - `Error getting wine version for ${wineBin}`, - LogPrefix.GlobalConfig - ) - } - } - } - } - } - return wineSet - } - - /** - * Detects CrossOver installs on Mac - * - * @returns Promise> - */ - public async getCrossover(): Promise> { - const crossover = new Set() - - if (!isMac) { - return crossover - } - - await execAsync( - 'mdfind kMDItemCFBundleIdentifier = "com.codeweavers.CrossOver"' - ).then(async ({ stdout }) => { - stdout.split('\n').forEach((crossoverMacPath) => { - const infoFilePath = join(crossoverMacPath, 'Contents/Info.plist') - if (crossoverMacPath && existsSync(infoFilePath)) { - const info = plistParse( - readFileSync(infoFilePath, 'utf-8') - ) as PlistObject - const version = info['CFBundleShortVersionString'] || '' - const crossoverWineBin = join( - crossoverMacPath, - 'Contents/SharedSupport/CrossOver/bin/wine' - ) - crossover.add({ - bin: crossoverWineBin, - name: `CrossOver - ${version}`, - type: 'crossover', - ...this.getWineExecs(crossoverWineBin) - }) - } - }) - }) - return crossover - } - - /** - * Detects Gaming Porting Toolkit Wine installs on Mac - * @returns Promise> - * @memberof GlobalConfig - **/ - public async getGamingPortingToolkit(): Promise> { - const gamingPortingToolkit = new Set() - - if (!isMac) { - return gamingPortingToolkit - } - - logInfo('Searching for Gaming Porting Toolkit', LogPrefix.GlobalConfig) - await execAsync('which gameportingtoolkit-no-hud').then( - async ({ stdout }) => { - if (stdout) { - logInfo( - `Found Gaming Porting Toolkit at ${dirname(stdout)}`, - LogPrefix.GlobalConfig - ) - - gamingPortingToolkit.add({ - bin: stdout.replace('\n', ''), - name: `Gaming Toolkit Script`, - type: 'toolkit' - }) - } - } - ) - return gamingPortingToolkit - } - - /** - * Detects Gaming Porting Toolkit Wine installs on Mac - * @returns Promise> - * @memberof GlobalConfig - **/ - public async getGamingPortingToolkitWine(): Promise> { - const gamingPortingToolkitWine = new Set() - if (!isMac) { - return gamingPortingToolkitWine - } - - logInfo('Searching for Gaming Porting Toolkit Wine', LogPrefix.GlobalConfig) - await execAsync('mdfind wine64').then(async ({ stdout }) => { - const wineBin = stdout.split('\n').filter((p) => { - if (!p.includes('game-porting-toolkit')) { - return false - } - - const parts = p.split('/') - const lastPart = parts[parts.length - 1] - return lastPart === 'wine64' - })[0] - if (existsSync(wineBin)) { - logInfo( - `Found Gaming Porting Toolkit Wine at ${dirname(wineBin)}`, - LogPrefix.GlobalConfig - ) - try { - const { stdout: out } = await execAsync(`'${wineBin}' --version`) - const version = out.split('\n')[0] - gamingPortingToolkitWine.add({ - ...this.getWineExecs(wineBin), - name: `Gaming Toolkit Standalone Wine - ${version}`, - type: 'wine', - lib: `${dirname(wineBin)}/../lib`, - lib32: `${dirname(wineBin)}/../lib`, - bin: wineBin - }) - } catch (error) { - logError( - `Error getting wine version for ${wineBin}`, - LogPrefix.GlobalConfig - ) - } - } - }) - - return gamingPortingToolkitWine - } - /** * Detects Wine on Mac * @returns Promise> @@ -378,11 +134,11 @@ abstract class GlobalConfig { return new Set() } - const crossover = await this.getCrossover() - const wineOnMac = await this.getWineOnMac() - const wineskinWine = await this.getWineskinWine() - const gamingPortingToolkit = await this.getGamingPortingToolkit() - const gamingPortingToolkitWine = await this.getGamingPortingToolkitWine() + const crossover = await getCrossover() + const wineOnMac = await getWineOnMac() + const wineskinWine = await getWineskinWine() + const gamingPortingToolkit = await getGamingPortingToolkit() + const gamingPortingToolkitWine = await getGamingPortingToolkitWine() return new Set([ ...gamingPortingToolkit, @@ -406,123 +162,9 @@ abstract class GlobalConfig { return [...macOsWineSet] } - if (!existsSync(`${toolsPath}/wine`)) { - mkdirSync(`${toolsPath}/wine`, { recursive: true }) - } - - if (!existsSync(`${toolsPath}/proton`)) { - mkdirSync(`${toolsPath}/proton`, { recursive: true }) - } + const linuxWineSet = await getLinuxWineSet(scanCustom) - const altWine = new Set() - - readdirSync(`${toolsPath}/wine/`).forEach((version) => { - const wineBin = join(toolsPath, 'wine', version, 'bin', 'wine') - altWine.add({ - bin: wineBin, - name: `Wine - ${version}`, - type: 'wine', - ...this.getWineLibs(wineBin), - ...this.getWineExecs(wineBin) - }) - }) - - const lutrisPath = `${homedir()}/.local/share/lutris` - const lutrisCompatPath = `${lutrisPath}/runners/wine/` - - if (existsSync(lutrisCompatPath)) { - readdirSync(lutrisCompatPath).forEach((version) => { - const wineBin = join(lutrisCompatPath, version, 'bin', 'wine') - altWine.add({ - bin: wineBin, - name: `Wine - ${version}`, - type: 'wine', - ...this.getWineLibs(wineBin), - ...this.getWineExecs(wineBin) - }) - }) - } - - const protonPaths = [`${toolsPath}/proton/`] - - await getSteamLibraries().then((libs) => { - libs.forEach((path) => { - protonPaths.push(`${path}/steam/steamapps/common`) - protonPaths.push(`${path}/steamapps/common`) - protonPaths.push(`${path}/root/compatibilitytools.d`) - protonPaths.push(`${path}/compatibilitytools.d`) - return - }) - }) - - const proton = new Set() - - protonPaths.forEach((path) => { - if (existsSync(path)) { - readdirSync(path).forEach((version) => { - const protonBin = join(path, version, 'proton') - // check if bin exists to avoid false positives - if (existsSync(protonBin)) { - proton.add({ - bin: protonBin, - name: `Proton - ${version}`, - type: 'proton' - // No need to run this.getWineExecs here since Proton ships neither Wineboot nor Wineserver - }) - } - }) - } - }) - - const defaultWineSet = new Set() - const defaultWine = await this.getDefaultWine() - if (!defaultWine.name.includes('Not Found')) { - defaultWineSet.add(defaultWine) - } - - let customWineSet = new Set() - if (scanCustom) { - customWineSet = this.getCustomWinePaths() - } - - return [...defaultWineSet, ...altWine, ...proton, ...customWineSet] - } - - /** - * Checks if a Wine version has the Wineserver executable and returns the path to it if it's present - * @param wineBin The unquoted path to the Wine binary ('wine') - * @returns The quoted path to wineserver, if present - */ - public getWineExecs(wineBin: string): { wineserver: string } { - const wineDir = dirname(wineBin) - const ret = { wineserver: '' } - const potWineserverPath = join(wineDir, 'wineserver') - if (existsSync(potWineserverPath)) { - ret.wineserver = potWineserverPath - } - return ret - } - - /** - * Checks if a Wine version has lib/lib32 folders and returns the path to those if they're present - * @param wineBin The unquoted path to the Wine binary ('wine') - * @returns The paths to lib and lib32, if present - */ - public getWineLibs(wineBin: string): { - lib: string - lib32: string - } { - const wineDir = dirname(wineBin) - const ret = { lib: '', lib32: '' } - const potLib32Path = join(wineDir, '../lib') - if (existsSync(potLib32Path)) { - ret.lib32 = potLib32Path - } - const potLibPath = join(wineDir, '../lib64') - if (existsSync(potLibPath)) { - ret.lib = potLibPath - } - return ret + return [...linuxWineSet] } /** @@ -544,13 +186,6 @@ abstract class GlobalConfig { */ public abstract upgrade(): boolean - /** - * Get custom Wine installations as defined in the config file. - * - * @returns Set of Wine installations. - */ - public abstract getCustomWinePaths(): Set - /** * Get default settings as if the user's config file doesn't exist. * Doesn't modify the parent object. @@ -650,34 +285,10 @@ class GlobalConfigV0 extends GlobalConfig { return settings } - public getCustomWinePaths(): Set { - const customPaths = new Set() - // skips this on new installations to avoid infinite loops - if (existsSync(configPath)) { - const { customWinePaths = [] } = this.getSettings() - customWinePaths.forEach((path: string) => { - if (path.endsWith('proton')) { - return customPaths.add({ - bin: path, - name: `Custom Proton - ${path}`, - type: 'proton' - }) - } - return customPaths.add({ - bin: path, - name: `Custom Wine - ${path}`, - type: 'wine', - ...this.getWineExecs(path) - }) - }) - } - return customPaths - } - public getFactoryDefaults(): AppSettings { const account_id = LegendaryUser.getUserInfo()?.account_id const userName = user().username - const defaultWine = isWindows ? {} : this.getDefaultWine() + const defaultWine = isWindows ? {} : getDefaultWine() // @ts-expect-error TODO: We need to settle on *one* place to define settings defaults return { diff --git a/src/backend/utils/compatibility_layers.ts b/src/backend/utils/compatibility_layers.ts new file mode 100644 index 0000000000..ee4ac11179 --- /dev/null +++ b/src/backend/utils/compatibility_layers.ts @@ -0,0 +1,410 @@ +import { GlobalConfig } from 'backend/config' +import { + configPath, + getSteamLibraries, + isMac, + toolsPath, + userHome +} from 'backend/constants' +import { logError, LogPrefix, logInfo } from 'backend/logger/logger' +import { execAsync } from 'backend/utils' +import { execSync } from 'child_process' +import { WineInstallation } from 'common/types' +import { existsSync, mkdirSync, readFileSync, readdirSync } from 'graceful-fs' +import { homedir } from 'os' +import { dirname, join } from 'path' +import { PlistObject, parse as plistParse } from 'plist' + +/** + * Loads the default wine installation path and version. + * + * @returns Promise + */ +export function getDefaultWine(): WineInstallation { + const defaultWine: WineInstallation = { + bin: '', + name: 'Default Wine - Not Found', + type: 'wine' + } + + try { + let stdout = execSync(`which wine`).toString() + const wineBin = stdout.split('\n')[0] + defaultWine.bin = wineBin + + stdout = execSync(`wine --version`).toString() + const version = stdout.split('\n')[0] + defaultWine.name = `Wine Default - ${version}` + + return { + ...defaultWine, + ...getWineExecs(wineBin) + } + } catch { + return defaultWine + } +} + +export function getCustomWinePaths(): Set { + const customPaths = new Set() + // skips this on new installations to avoid infinite loops + if (existsSync(configPath)) { + const { customWinePaths = [] } = GlobalConfig.get().getSettings() + customWinePaths.forEach((path: string) => { + if (path.endsWith('proton')) { + return customPaths.add({ + bin: path, + name: `Custom Proton - ${path}`, + type: 'proton' + }) + } + return customPaths.add({ + bin: path, + name: `Custom Wine - ${path}`, + type: 'wine', + ...getWineExecs(path) + }) + }) + } + return customPaths +} + +/** + * Checks if a Wine version has the Wineserver executable and returns the path to it if it's present + * @param wineBin The unquoted path to the Wine binary ('wine') + * @returns The quoted path to wineserver, if present + */ +export function getWineExecs(wineBin: string): { wineserver: string } { + const wineDir = dirname(wineBin) + const ret = { wineserver: '' } + const potWineserverPath = join(wineDir, 'wineserver') + if (existsSync(potWineserverPath)) { + ret.wineserver = potWineserverPath + } + return ret +} + +/** + * Checks if a Wine version has lib/lib32 folders and returns the path to those if they're present + * @param wineBin The unquoted path to the Wine binary ('wine') + * @returns The paths to lib and lib32, if present + */ +export function getWineLibs(wineBin: string): { + lib: string + lib32: string +} { + const wineDir = dirname(wineBin) + const ret = { lib: '', lib32: '' } + const potLib32Path = join(wineDir, '../lib') + if (existsSync(potLib32Path)) { + ret.lib32 = potLib32Path + } + const potLibPath = join(wineDir, '../lib64') + if (existsSync(potLibPath)) { + ret.lib = potLibPath + } + return ret +} + +export async function getLinuxWineSet( + scanCustom?: boolean +): Promise> { + if (!existsSync(`${toolsPath}/wine`)) { + mkdirSync(`${toolsPath}/wine`, { recursive: true }) + } + + if (!existsSync(`${toolsPath}/proton`)) { + mkdirSync(`${toolsPath}/proton`, { recursive: true }) + } + + const altWine = new Set() + + readdirSync(`${toolsPath}/wine/`).forEach((version) => { + const wineBin = join(toolsPath, 'wine', version, 'bin', 'wine') + altWine.add({ + bin: wineBin, + name: `Wine - ${version}`, + type: 'wine', + ...getWineLibs(wineBin), + ...getWineExecs(wineBin) + }) + }) + + const lutrisPath = `${homedir()}/.local/share/lutris` + const lutrisCompatPath = `${lutrisPath}/runners/wine/` + + if (existsSync(lutrisCompatPath)) { + readdirSync(lutrisCompatPath).forEach((version) => { + const wineBin = join(lutrisCompatPath, version, 'bin', 'wine') + altWine.add({ + bin: wineBin, + name: `Wine - ${version}`, + type: 'wine', + ...getWineLibs(wineBin), + ...getWineExecs(wineBin) + }) + }) + } + + const protonPaths = [`${toolsPath}/proton/`] + + await getSteamLibraries().then((libs) => { + libs.forEach((path) => { + protonPaths.push(`${path}/steam/steamapps/common`) + protonPaths.push(`${path}/steamapps/common`) + protonPaths.push(`${path}/root/compatibilitytools.d`) + protonPaths.push(`${path}/compatibilitytools.d`) + return + }) + }) + + const proton = new Set() + + protonPaths.forEach((path) => { + if (existsSync(path)) { + readdirSync(path).forEach((version) => { + const protonBin = join(path, version, 'proton') + // check if bin exists to avoid false positives + if (existsSync(protonBin)) { + proton.add({ + bin: protonBin, + name: `Proton - ${version}`, + type: 'proton' + // No need to run this.getWineExecs here since Proton ships neither Wineboot nor Wineserver + }) + } + }) + } + }) + + const defaultWineSet = new Set() + const defaultWine = await getDefaultWine() + if (!defaultWine.name.includes('Not Found')) { + defaultWineSet.add(defaultWine) + } + + let customWineSet = new Set() + if (scanCustom) { + customWineSet = getCustomWinePaths() + } + + return new Set([...defaultWineSet, ...altWine, ...proton, ...customWineSet]) +} + +/// --------------- MACOS ------------------ + +/** + * Detects Wine installed on home application folder on Mac + * + * @returns Promise> + */ +export async function getWineOnMac(): Promise> { + const wineSet = new Set() + if (!isMac) { + return wineSet + } + + const winePaths = new Set() + + // search for wine installed on $HOME/Library/Application Support/heroic/tools/wine + const wineToolsPath = `${toolsPath}/wine/` + if (existsSync(wineToolsPath)) { + readdirSync(wineToolsPath).forEach((path) => { + winePaths.add(join(wineToolsPath, path)) + }) + } + + // search for wine installed around the system + await execAsync('mdfind kMDItemCFBundleIdentifier = "*.wine"').then( + async ({ stdout }) => { + stdout.split('\n').forEach((winePath) => { + winePaths.add(winePath) + }) + } + ) + + winePaths.forEach((winePath) => { + const infoFilePath = join(winePath, 'Contents/Info.plist') + if (winePath && existsSync(infoFilePath)) { + const info = plistParse( + readFileSync(infoFilePath, 'utf-8') + ) as PlistObject + const version = info['CFBundleShortVersionString'] || '' + const name = info['CFBundleName'] || '' + const wineBin = join(winePath, '/Contents/Resources/wine/bin/wine64') + if (existsSync(wineBin)) { + wineSet.add({ + ...getWineExecs(wineBin), + lib: `${winePath}/Contents/Resources/wine/lib`, + lib32: `${winePath}/Contents/Resources/wine/lib`, + bin: wineBin, + name: `${name} - ${version}`, + type: 'wine', + ...getWineExecs(wineBin) + }) + } + } + }) + + return wineSet +} + +export async function getWineskinWine(): Promise> { + const wineSet = new Set() + if (!isMac) { + return wineSet + } + const wineSkinPath = `${userHome}/Applications/Wineskin` + if (existsSync(wineSkinPath)) { + const apps = readdirSync(wineSkinPath) + for (const app of apps) { + if (app.includes('.app')) { + const wineBin = `${userHome}/Applications/Wineskin/${app}/Contents/SharedSupport/wine/bin/wine64` + if (existsSync(wineBin)) { + try { + const { stdout: out } = await execAsync(`'${wineBin}' --version`) + const version = out.split('\n')[0] + wineSet.add({ + ...getWineExecs(wineBin), + lib: `${userHome}/Applications/Wineskin/${app}/Contents/SharedSupport/wine/lib`, + lib32: `${userHome}/Applications/Wineskin/${app}/Contents/SharedSupport/wine/lib`, + name: `Wineskin - ${version}`, + type: 'wine', + bin: wineBin + }) + } catch (error) { + logError( + `Error getting wine version for ${wineBin}`, + LogPrefix.GlobalConfig + ) + } + } + } + } + } + return wineSet +} + +/** + * Detects CrossOver installs on Mac + * + * @returns Promise> + */ +export async function getCrossover(): Promise> { + const crossover = new Set() + + if (!isMac) { + return crossover + } + + await execAsync( + 'mdfind kMDItemCFBundleIdentifier = "com.codeweavers.CrossOver"' + ).then(async ({ stdout }) => { + stdout.split('\n').forEach((crossoverMacPath) => { + const infoFilePath = join(crossoverMacPath, 'Contents/Info.plist') + if (crossoverMacPath && existsSync(infoFilePath)) { + const info = plistParse( + readFileSync(infoFilePath, 'utf-8') + ) as PlistObject + const version = info['CFBundleShortVersionString'] || '' + const crossoverWineBin = join( + crossoverMacPath, + 'Contents/SharedSupport/CrossOver/bin/wine' + ) + crossover.add({ + bin: crossoverWineBin, + name: `CrossOver - ${version}`, + type: 'crossover', + ...getWineExecs(crossoverWineBin) + }) + } + }) + }) + return crossover +} + +/** + * Detects Gaming Porting Toolkit Wine installs on Mac + * @returns Promise> + * @memberof GlobalConfig + **/ +export async function getGamingPortingToolkit(): Promise< + Set +> { + const gamingPortingToolkit = new Set() + + if (!isMac) { + return gamingPortingToolkit + } + + logInfo('Searching for Gaming Porting Toolkit', LogPrefix.GlobalConfig) + await execAsync('which gameportingtoolkit-no-hud').then( + async ({ stdout }) => { + if (stdout) { + logInfo( + `Found Gaming Porting Toolkit at ${dirname(stdout)}`, + LogPrefix.GlobalConfig + ) + + gamingPortingToolkit.add({ + bin: stdout.replace('\n', ''), + name: `Gaming Toolkit Script`, + type: 'toolkit' + }) + } + } + ) + return gamingPortingToolkit +} + +/** + * Detects Gaming Porting Toolkit Wine installs on Mac + * @returns Promise> + * @memberof GlobalConfig + **/ +export async function getGamingPortingToolkitWine(): Promise< + Set +> { + const gamingPortingToolkitWine = new Set() + if (!isMac) { + return gamingPortingToolkitWine + } + + logInfo('Searching for Gaming Porting Toolkit Wine', LogPrefix.GlobalConfig) + await execAsync('mdfind wine64').then(async ({ stdout }) => { + const wineBin = stdout.split('\n').filter((p) => { + if (!p.includes('game-porting-toolkit')) { + return false + } + + const parts = p.split('/') + const lastPart = parts[parts.length - 1] + return lastPart === 'wine64' + })[0] + if (existsSync(wineBin)) { + logInfo( + `Found Gaming Porting Toolkit Wine at ${dirname(wineBin)}`, + LogPrefix.GlobalConfig + ) + try { + const { stdout: out } = await execAsync(`'${wineBin}' --version`) + const version = out.split('\n')[0] + gamingPortingToolkitWine.add({ + ...getWineExecs(wineBin), + name: `Gaming Toolkit Standalone Wine - ${version}`, + type: 'wine', + lib: `${dirname(wineBin)}/../lib`, + lib32: `${dirname(wineBin)}/../lib`, + bin: wineBin + }) + } catch (error) { + logError( + `Error getting wine version for ${wineBin}`, + LogPrefix.GlobalConfig + ) + } + } + }) + + return gamingPortingToolkitWine +} From 006d73ae092742b4b8e21302a66670a7a328bc11 Mon Sep 17 00:00:00 2001 From: Flavio F Lima Date: Wed, 14 Jun 2023 15:18:24 +0200 Subject: [PATCH 15/19] tests: write tests for wine methods --- .../__tests__/compatibility_layers.test.ts | 104 ++++++++++++++++++ 1 file changed, 104 insertions(+) create mode 100644 src/backend/utils/__tests__/compatibility_layers.test.ts diff --git a/src/backend/utils/__tests__/compatibility_layers.test.ts b/src/backend/utils/__tests__/compatibility_layers.test.ts new file mode 100644 index 0000000000..197839137a --- /dev/null +++ b/src/backend/utils/__tests__/compatibility_layers.test.ts @@ -0,0 +1,104 @@ +import { describe } from 'node:test' +import { + getDefaultWine, + getWineExecs, + getWineLibs +} from '../compatibility_layers' +import { mkdirSync } from 'graceful-fs' +import { dirname, join } from 'path' +import { tmpdir } from 'os' + +jest.mock('../../logger/logfile') + +describe('getDefaultWine', () => { + test('return wine not found', () => { + const expected = { + bin: '', + name: 'Default Wine - Not Found', + type: 'wine' + } + const result = getDefaultWine() + expect(result).toEqual(expected) + }) + + test('return list with one default wine', () => { + const expected = { + bin: '/usr/bin/wine', + name: 'Wine Default - wine-6.0 (Staging)', + type: 'wine', + wineserver: '' + } + + // spy on the execSync calling which wine and returning /usr/bin/wine + const execSync = jest.spyOn(require('child_process'), 'execSync') + execSync.mockImplementation((command: any) => { + if (command === 'which wine') { + return '/usr/bin/wine\n' + } else if (command === 'wine --version') { + return 'wine-6.0 (Staging)\n' + } + throw new Error('Unexpected command') + }) + + const result = getDefaultWine() + expect(result).toEqual(expected) + }) +}) + +describe('getWineLibs', () => { + it('should return empty strings if lib and lib32 do not exist', () => { + const wineBin = '/path/to/wine' + const { lib, lib32 } = getWineLibs(wineBin) + expect(lib).toBe('') + expect(lib32).toBe('') + }) + + it('should return the path to lib32 if it exists', () => { + const wineBin = join(tmpdir(), 'wine_test') + const wineDir = join(wineBin, '..') + const lib32Path = join(wineDir, '../lib') + mkdirSync(lib32Path, { recursive: true }) + const { lib32 } = getWineLibs(wineBin) + expect(lib32).toBe(lib32Path) + }) + + it('should return the path to lib if it exists', () => { + const wineBin = join(tmpdir(), 'wine_test') + const wineDir = join(wineBin, '..') + const libPath = join(wineDir, '../lib64') + mkdirSync(libPath, { recursive: true }) + const { lib } = getWineLibs(wineBin) + expect(lib).toBe(libPath) + }) + + it('should return the paths to lib and lib32 if they both exist', () => { + const wineBin = join(tmpdir(), 'wine_test') + const wineDir = join(wineBin, '..') + const libPath = join(wineDir, '../lib64') + const lib32Path = join(wineDir, '../lib') + mkdirSync(libPath, { recursive: true }) + mkdirSync(lib32Path, { recursive: true }) + const { lib, lib32 } = getWineLibs(wineBin) + expect(lib).toBe(libPath) + expect(lib32).toBe(lib32Path) + }) +}) + +describe('getWineExes', () => { + describe('getWineExecs', () => { + it('should return the path to wineserver if it exists', () => { + const wineBin = join(tmpdir(), 'wine_test') + const wineDir = dirname(wineBin) + const wineServerPath = join(wineDir, 'wineserver') + mkdirSync(wineServerPath, { recursive: true }) + const execs = getWineExecs(wineBin) + expect(execs.wineserver).toBe(wineServerPath) + }) + + it('should return an empty string if wineserver does not exist', () => { + const wineBin = '/path/to/wine' + const execs = getWineExecs(wineBin) + expect(execs.wineserver).toBe('') + }) + }) +}) From b0e20a4d73edc091931d0558e2f261540c165f95 Mon Sep 17 00:00:00 2001 From: Flavio F Lima Date: Wed, 14 Jun 2023 15:26:56 +0200 Subject: [PATCH 16/19] fix: catch promises --- .../__tests__/compatibility_layers.test.ts | 1 + src/backend/utils/compatibility_layers.ts | 56 ++++++++++--------- 2 files changed, 31 insertions(+), 26 deletions(-) diff --git a/src/backend/utils/__tests__/compatibility_layers.test.ts b/src/backend/utils/__tests__/compatibility_layers.test.ts index 197839137a..0c3cda5dc3 100644 --- a/src/backend/utils/__tests__/compatibility_layers.test.ts +++ b/src/backend/utils/__tests__/compatibility_layers.test.ts @@ -30,6 +30,7 @@ describe('getDefaultWine', () => { } // spy on the execSync calling which wine and returning /usr/bin/wine + // eslint-disable-next-line @typescript-eslint/no-var-requires const execSync = jest.spyOn(require('child_process'), 'execSync') execSync.mockImplementation((command: any) => { if (command === 'which wine') { diff --git a/src/backend/utils/compatibility_layers.ts b/src/backend/utils/compatibility_layers.ts index ee4ac11179..7445d5b18b 100644 --- a/src/backend/utils/compatibility_layers.ts +++ b/src/backend/utils/compatibility_layers.ts @@ -299,34 +299,37 @@ export async function getCrossover(): Promise> { await execAsync( 'mdfind kMDItemCFBundleIdentifier = "com.codeweavers.CrossOver"' - ).then(async ({ stdout }) => { - stdout.split('\n').forEach((crossoverMacPath) => { - const infoFilePath = join(crossoverMacPath, 'Contents/Info.plist') - if (crossoverMacPath && existsSync(infoFilePath)) { - const info = plistParse( - readFileSync(infoFilePath, 'utf-8') - ) as PlistObject - const version = info['CFBundleShortVersionString'] || '' - const crossoverWineBin = join( - crossoverMacPath, - 'Contents/SharedSupport/CrossOver/bin/wine' - ) - crossover.add({ - bin: crossoverWineBin, - name: `CrossOver - ${version}`, - type: 'crossover', - ...getWineExecs(crossoverWineBin) - }) - } + ) + .then(async ({ stdout }) => { + stdout.split('\n').forEach((crossoverMacPath) => { + const infoFilePath = join(crossoverMacPath, 'Contents/Info.plist') + if (crossoverMacPath && existsSync(infoFilePath)) { + const info = plistParse( + readFileSync(infoFilePath, 'utf-8') + ) as PlistObject + const version = info['CFBundleShortVersionString'] || '' + const crossoverWineBin = join( + crossoverMacPath, + 'Contents/SharedSupport/CrossOver/bin/wine' + ) + crossover.add({ + bin: crossoverWineBin, + name: `CrossOver - ${version}`, + type: 'crossover', + ...getWineExecs(crossoverWineBin) + }) + } + }) + }) + .catch(() => { + logInfo('CrossOver not found', LogPrefix.GlobalConfig) }) - }) return crossover } /** * Detects Gaming Porting Toolkit Wine installs on Mac * @returns Promise> - * @memberof GlobalConfig **/ export async function getGamingPortingToolkit(): Promise< Set @@ -338,8 +341,8 @@ export async function getGamingPortingToolkit(): Promise< } logInfo('Searching for Gaming Porting Toolkit', LogPrefix.GlobalConfig) - await execAsync('which gameportingtoolkit-no-hud').then( - async ({ stdout }) => { + await execAsync('which gameportingtoolkit-no-hud') + .then(async ({ stdout }) => { if (stdout) { logInfo( `Found Gaming Porting Toolkit at ${dirname(stdout)}`, @@ -352,15 +355,16 @@ export async function getGamingPortingToolkit(): Promise< type: 'toolkit' }) } - } - ) + }) + .catch(() => { + logInfo('Gaming Porting Toolkit not found', LogPrefix.GlobalConfig) + }) return gamingPortingToolkit } /** * Detects Gaming Porting Toolkit Wine installs on Mac * @returns Promise> - * @memberof GlobalConfig **/ export async function getGamingPortingToolkitWine(): Promise< Set From cf52ef181247de4efa93b8c66db7751668471505 Mon Sep 17 00:00:00 2001 From: Flavio F Lima Date: Wed, 14 Jun 2023 15:34:52 +0200 Subject: [PATCH 17/19] fix_ lint --- src/backend/utils/compatibility_layers.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/backend/utils/compatibility_layers.ts b/src/backend/utils/compatibility_layers.ts index 7445d5b18b..79d7323f97 100644 --- a/src/backend/utils/compatibility_layers.ts +++ b/src/backend/utils/compatibility_layers.ts @@ -45,7 +45,7 @@ export function getDefaultWine(): WineInstallation { } } -export function getCustomWinePaths(): Set { +function getCustomWinePaths(): Set { const customPaths = new Set() // skips this on new installations to avoid infinite loops if (existsSync(configPath)) { From 8d681fe70d6b0ecd67ad8ebc83a300944f2b850a Mon Sep 17 00:00:00 2001 From: Flavio F Lima Date: Fri, 23 Jun 2023 16:08:44 +0200 Subject: [PATCH 18/19] chore: ignore scripts --- src/backend/config.ts | 3 -- src/backend/launcher.ts | 44 +------------------ src/backend/main.ts | 6 +-- .../storeManagers/storeManagerCommon/games.ts | 8 ---- src/backend/utils/compatibility_layers.ts | 39 +--------------- 5 files changed, 4 insertions(+), 96 deletions(-) diff --git a/src/backend/config.ts b/src/backend/config.ts index fe14f68218..af40f9c69e 100644 --- a/src/backend/config.ts +++ b/src/backend/config.ts @@ -25,7 +25,6 @@ import { logError, logInfo, LogPrefix } from './logger/logger' import { getCrossover, getDefaultWine, - getGamingPortingToolkit, getGamingPortingToolkitWine, getLinuxWineSet, getWineOnMac, @@ -137,11 +136,9 @@ abstract class GlobalConfig { const crossover = await getCrossover() const wineOnMac = await getWineOnMac() const wineskinWine = await getWineskinWine() - const gamingPortingToolkit = await getGamingPortingToolkit() const gamingPortingToolkitWine = await getGamingPortingToolkitWine() return new Set([ - ...gamingPortingToolkit, ...gamingPortingToolkitWine, ...crossover, ...wineOnMac, diff --git a/src/backend/launcher.ts b/src/backend/launcher.ts index 3cd596a37c..0174ed3d5f 100644 --- a/src/backend/launcher.ts +++ b/src/backend/launcher.ts @@ -25,8 +25,7 @@ import { quoteIfNecessary, errorHandler, removeQuoteIfNecessary, - memoryLog, - spawnAsync + memoryLog } from './utils' import { logDebug, @@ -165,13 +164,6 @@ async function prepareWineLaunch( GameConfig.get(appName).config || (await GameConfig.get(appName).getSettings()) - if (gameSettings.wineVersion.type === 'toolkit') { - return { - success: true, - envVars: {} - } - } - if (!(await validWine(gameSettings.wineVersion))) { const defaultWine = GlobalConfig.get().getSettings().wineVersion // now check if the default wine is valid as well @@ -469,11 +461,6 @@ export async function verifyWinePrefix( ): Promise<{ res: ExecResult; updated: boolean }> { const { winePrefix = defaultWinePrefix, wineVersion } = settings - // if type === 'toolkit', we don't need to verify the prefix - if (wineVersion.type === 'toolkit') { - return { res: { stdout: '', stderr: '' }, updated: false } - } - const isValidWine = await validWine(wineVersion) if (!isValidWine) { @@ -525,31 +512,6 @@ function launchCleanup(rpcClient?: RpcClient) { } } -export async function runToolkitCommand( - gameSettings: GameSettings, - command: string -): Promise<{ stderr: string; stdout: string }> { - const { - winePrefix = defaultWinePrefix, - wineVersion: { bin: wineBin } - } = gameSettings - - const env_vars = { - ...process.env, - ...setupEnvVars(gameSettings) - } - - logInfo( - `Running App using Apple's Gaming Toolkit: ${wineBin} ${winePrefix} ${command}`, - LogPrefix.Backend - ) - const { stderr, stdout } = await spawnAsync(wineBin, [winePrefix, command], { - env: env_vars - }) - - return { stderr, stdout } -} - async function runWineCommand({ gameSettings, commandParts, @@ -565,10 +527,6 @@ async function runWineCommand({ : GlobalConfig.get().getSettings() const { wineVersion, winePrefix } = settings - if (wineVersion.type === 'toolkit') { - return runToolkitCommand(settings, commandParts[0]) - } - if (!skipPrefixCheckIKnowWhatImDoing && wineVersion.type !== 'crossover') { let requiredPrefixFiles = [ 'dosdevices', diff --git a/src/backend/main.ts b/src/backend/main.ts index 0c050d969d..5c59314e4c 100644 --- a/src/backend/main.ts +++ b/src/backend/main.ts @@ -111,7 +111,7 @@ import { } from './logger/logger' import { gameInfoStore } from 'backend/storeManagers/legendary/electronStores' import { getFonts } from 'font-list' -import { runToolkitCommand, runWineCommand, verifyWinePrefix } from './launcher' +import { runWineCommand, verifyWinePrefix } from './launcher' import shlex from 'shlex' import { initQueue } from './downloadmanager/downloadqueue' import { @@ -575,10 +575,6 @@ async function runWineCommandOnGame( const { folder_name } = gameManagerMap[runner].getGameInfo(appName) const gameSettings = await gameManagerMap[runner].getSettings(appName) - if (gameSettings.wineVersion.type === 'toolkit') { - return runToolkitCommand(gameSettings, commandParts[0]) - } - return runWineCommand({ gameSettings, installFolderName: folder_name, diff --git a/src/backend/storeManagers/storeManagerCommon/games.ts b/src/backend/storeManagers/storeManagerCommon/games.ts index aae0fee03a..79b6c4144b 100644 --- a/src/backend/storeManagers/storeManagerCommon/games.ts +++ b/src/backend/storeManagers/storeManagerCommon/games.ts @@ -9,7 +9,6 @@ import { callRunner, launchCleanup, prepareLaunch, - runToolkitCommand, runWineCommand, setupEnvVars, setupWrappers @@ -174,13 +173,6 @@ export async function launchGame( LogPrefix.Backend ) - if (gameSettings.wineVersion.type === 'toolkit') { - logInfo('Using wine toolkit', LogPrefix.Backend) - await runToolkitCommand(gameSettings, executable) - launchCleanup(rpcClient) - return true - } - await runWineCommand({ commandParts: [executable, launcherArgs ?? ''], gameSettings, diff --git a/src/backend/utils/compatibility_layers.ts b/src/backend/utils/compatibility_layers.ts index 79d7323f97..0b01c487e1 100644 --- a/src/backend/utils/compatibility_layers.ts +++ b/src/backend/utils/compatibility_layers.ts @@ -327,41 +327,6 @@ export async function getCrossover(): Promise> { return crossover } -/** - * Detects Gaming Porting Toolkit Wine installs on Mac - * @returns Promise> - **/ -export async function getGamingPortingToolkit(): Promise< - Set -> { - const gamingPortingToolkit = new Set() - - if (!isMac) { - return gamingPortingToolkit - } - - logInfo('Searching for Gaming Porting Toolkit', LogPrefix.GlobalConfig) - await execAsync('which gameportingtoolkit-no-hud') - .then(async ({ stdout }) => { - if (stdout) { - logInfo( - `Found Gaming Porting Toolkit at ${dirname(stdout)}`, - LogPrefix.GlobalConfig - ) - - gamingPortingToolkit.add({ - bin: stdout.replace('\n', ''), - name: `Gaming Toolkit Script`, - type: 'toolkit' - }) - } - }) - .catch(() => { - logInfo('Gaming Porting Toolkit not found', LogPrefix.GlobalConfig) - }) - return gamingPortingToolkit -} - /** * Detects Gaming Porting Toolkit Wine installs on Mac * @returns Promise> @@ -395,8 +360,8 @@ export async function getGamingPortingToolkitWine(): Promise< const version = out.split('\n')[0] gamingPortingToolkitWine.add({ ...getWineExecs(wineBin), - name: `Gaming Toolkit Standalone Wine - ${version}`, - type: 'wine', + name: `Gaming Toolkit Wine - ${version}`, + type: 'toolkit', lib: `${dirname(wineBin)}/../lib`, lib32: `${dirname(wineBin)}/../lib`, bin: wineBin From 70c26823821775c569f0108b5f32ee7b0fe2a62e Mon Sep 17 00:00:00 2001 From: Flavio F Lima Date: Fri, 30 Jun 2023 12:03:32 +0200 Subject: [PATCH 19/19] fix: PR Comments --- src/backend/storeManagers/gog/games.ts | 17 +---- src/backend/storeManagers/legendary/games.ts | 17 +---- src/backend/tools.ts | 18 ++--- src/backend/utils.ts | 4 +- src/backend/utils/compatibility_layers.ts | 74 +++++++++++--------- 5 files changed, 59 insertions(+), 71 deletions(-) diff --git a/src/backend/storeManagers/gog/games.ts b/src/backend/storeManagers/gog/games.ts index f7898c6862..a9d87725f4 100644 --- a/src/backend/storeManagers/gog/games.ts +++ b/src/backend/storeManagers/gog/games.ts @@ -71,6 +71,7 @@ import { showDialogBoxModalAuto } from '../../dialog/dialog' import { sendFrontendMessage } from '../../main_window' import { RemoveArgs } from 'common/types/game_manager' import { logFileLocation } from 'backend/storeManagers/storeManagerCommon/games' +import { getWineFlags } from 'backend/utils/compatibility_layers' export async function getExtraInfo(appName: string): Promise { const gameInfo = getGameInfo(appName) @@ -445,7 +446,7 @@ export async function launch( let commandEnv = isWindows ? process.env : { ...process.env, ...setupEnvVars(gameSettings) } - const wineFlag: string[] = [] + let wineFlag: string[] = [] if (!isNative(appName)) { const { @@ -481,19 +482,7 @@ export async function launch( ? wineExec.replaceAll("'", '') : wineExec - const wineFlagsObj = { - proton: ['--no-wine', '--wrapper', `'${wineBin}' run`], - wine: ['--wine', wineBin], - toolkit: [ - '--wrapper', - `${wineBin} ${gameSettings.winePrefix}`, - '--no-wine' - ] - } - - wineFlag.push( - ...(wineFlagsObj[wineType as keyof typeof wineFlagsObj] || []) - ) + wineFlag = [...getWineFlags(wineBin, gameSettings, wineType)] } const commandParts = [ diff --git a/src/backend/storeManagers/legendary/games.ts b/src/backend/storeManagers/legendary/games.ts index 96695bd70c..98baf6ddb0 100644 --- a/src/backend/storeManagers/legendary/games.ts +++ b/src/backend/storeManagers/legendary/games.ts @@ -68,6 +68,7 @@ import { Catalog, Product } from 'common/types/epic-graphql' import { sendFrontendMessage } from '../../main_window' import { RemoveArgs } from 'common/types/game_manager' import { logFileLocation } from 'backend/storeManagers/storeManagerCommon/games' +import { getWineFlags } from 'backend/utils/compatibility_layers' /** * Alias for `LegendaryLibrary.listUpdateableGames` @@ -815,7 +816,7 @@ export async function launch( let commandEnv = isWindows ? process.env : { ...process.env, ...setupEnvVars(gameSettings) } - const wineFlag: string[] = [] + let wineFlag: string[] = [] if (!isNative(appName)) { // -> We're using Wine/Proton on Linux or CX on Mac const { @@ -851,19 +852,7 @@ export async function launch( ? wineExec.replaceAll("'", '') : wineExec - const wineFlagsObj = { - proton: ['--no-wine', '--wrapper', `'${wineBin}' run`], - wine: ['--wine', wineBin], - toolkit: [ - '--wrapper', - `${wineBin} ${gameSettings.winePrefix}`, - '--no-wine' - ] - } - - wineFlag.push( - ...(wineFlagsObj[wineType as keyof typeof wineFlagsObj] || []) - ) + wineFlag = [...getWineFlags(wineBin, gameSettings, wineType)] } // Log any launch information configured in Legendary's config.ini diff --git a/src/backend/tools.ts b/src/backend/tools.ts index 6e68829d8f..7badb431d0 100644 --- a/src/backend/tools.ts +++ b/src/backend/tools.ts @@ -128,22 +128,22 @@ export const DXVK = { tool: 'dxvk' | 'vkd3d' | 'dxvk-macOS', action: 'backup' | 'restore' ): Promise => { - const prefix = gameSettings.winePrefix - const winePrefix = prefix.replace('~', userHome) - const isValidPrefix = existsSync(`${winePrefix}/.update-timestamp`) - - if (!isValidPrefix) { + if (gameSettings.wineVersion.bin.includes('toolkit')) { + // we don't want to install dxvk on the toolkit prefix since it breaks Apple's implementation logWarning( - 'DXVK cannot be installed on a Proton or a invalid prefix!', + 'Skipping DXVK install on Game Porting Toolkit prefix!', LogPrefix.DXVKInstaller ) - // will return true anyway because otherwise the toggle will be stuck and the prefix might just not be crated yet. return true } - if (gameSettings.wineVersion.bin.includes('toolkit')) { + const prefix = gameSettings.winePrefix + const winePrefix = prefix.replace('~', userHome) + const isValidPrefix = existsSync(`${winePrefix}/.update-timestamp`) + + if (!isValidPrefix) { logWarning( - 'Skipping DXVK install on Game Porting Toolkit prefix!', + 'DXVK cannot be installed on a Proton or a invalid prefix!', LogPrefix.DXVKInstaller ) // will return true anyway because otherwise the toggle will be stuck and the prefix might just not be crated yet. diff --git a/src/backend/utils.ts b/src/backend/utils.ts index b7c6fe829b..18cdb55034 100644 --- a/src/backend/utils.ts +++ b/src/backend/utils.ts @@ -395,7 +395,7 @@ async function errorHandler({ const expiredCredentials = 'No saved credentials' const legendaryRegex = /legendary.*\.py/ // this message appears on macOS when no Crossover was found in the system but its a false alarm - const ignoreMessage = 'IndexError: list index out of range' + const ignoreCrossoverMessage = 'IndexError: list index out of range' if (logPath) { execAsync(`tail "${logPath}" | grep 'disk space'`) @@ -418,7 +418,7 @@ async function errorHandler({ }) } if (error) { - if (error.includes(ignoreMessage)) { + if (error.includes(ignoreCrossoverMessage)) { return } if (error.includes(deletedFolderMsg) && appName) { diff --git a/src/backend/utils/compatibility_layers.ts b/src/backend/utils/compatibility_layers.ts index 0b01c487e1..1b5e2e9466 100644 --- a/src/backend/utils/compatibility_layers.ts +++ b/src/backend/utils/compatibility_layers.ts @@ -9,7 +9,7 @@ import { import { logError, LogPrefix, logInfo } from 'backend/logger/logger' import { execAsync } from 'backend/utils' import { execSync } from 'child_process' -import { WineInstallation } from 'common/types' +import { GameSettings, WineInstallation } from 'common/types' import { existsSync, mkdirSync, readFileSync, readdirSync } from 'graceful-fs' import { homedir } from 'os' import { dirname, join } from 'path' @@ -340,40 +340,50 @@ export async function getGamingPortingToolkitWine(): Promise< } logInfo('Searching for Gaming Porting Toolkit Wine', LogPrefix.GlobalConfig) - await execAsync('mdfind wine64').then(async ({ stdout }) => { - const wineBin = stdout.split('\n').filter((p) => { - if (!p.includes('game-porting-toolkit')) { - return false - } - - const parts = p.split('/') - const lastPart = parts[parts.length - 1] - return lastPart === 'wine64' - })[0] - if (existsSync(wineBin)) { - logInfo( - `Found Gaming Porting Toolkit Wine at ${dirname(wineBin)}`, + const { stdout } = await execAsync('mdfind wine64') + const wineBin = stdout.split('\n').filter((p) => { + return p.match(/game-porting-toolkit.*\/wine64$/) + })[0] + + if (existsSync(wineBin)) { + logInfo( + `Found Gaming Porting Toolkit Wine at ${dirname(wineBin)}`, + LogPrefix.GlobalConfig + ) + try { + const { stdout: out } = await execAsync(`'${wineBin}' --version`) + const version = out.split('\n')[0] + gamingPortingToolkitWine.add({ + ...getWineExecs(wineBin), + name: `GPTK Wine (DX11/DX12 Only) - ${version}`, + type: 'toolkit', + lib: `${dirname(wineBin)}/../lib`, + lib32: `${dirname(wineBin)}/../lib`, + bin: wineBin + }) + } catch (error) { + logError( + `Error getting wine version for ${wineBin}`, LogPrefix.GlobalConfig ) - try { - const { stdout: out } = await execAsync(`'${wineBin}' --version`) - const version = out.split('\n')[0] - gamingPortingToolkitWine.add({ - ...getWineExecs(wineBin), - name: `Gaming Toolkit Wine - ${version}`, - type: 'toolkit', - lib: `${dirname(wineBin)}/../lib`, - lib32: `${dirname(wineBin)}/../lib`, - bin: wineBin - }) - } catch (error) { - logError( - `Error getting wine version for ${wineBin}`, - LogPrefix.GlobalConfig - ) - } } - }) + } return gamingPortingToolkitWine } + +export function getWineFlags( + wineBin: string, + gameSettings: GameSettings, + wineType: string +) { + const wineFlags = [] + const wineFlagsObj = { + proton: ['--no-wine', '--wrapper', `'${wineBin}' run`], + wine: ['--wine', wineBin], + toolkit: ['--wrapper', `${wineBin} ${gameSettings.winePrefix}`, '--no-wine'] + } + + wineFlags.push(...(wineFlagsObj[wineType as keyof typeof wineFlagsObj] || [])) + return wineFlags +}