From 13e289d6042a2a33b59a1cb00fe6e0c024f6f7b7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fl=C3=A1vio=20Fearn?= <26871415+flavioislima@users.noreply.github.com> Date: Mon, 16 Sep 2024 19:37:20 +0100 Subject: [PATCH] [FIX] Improve default Compatibility Layer setup (#1045) * [FIX] Improve default Compatibility Layer setup * tech: refactor compatbility layer initialization and move things around * chore: comments and logs * tech: enforce to use downloaded CLs instead of system ones * fix: imports * chore: revert yarn.lock --------- Co-authored-by: Flavio F Lima --- src/backend/config.ts | 19 +- src/backend/main.ts | 33 +- src/backend/storeManagers/gog/setup.ts | 3 +- src/backend/utils.ts | 265 +-------------- src/backend/utils/compatibility_layers.ts | 373 +++++++++++++++++++++- yarn.lock | 2 +- 6 files changed, 380 insertions(+), 315 deletions(-) diff --git a/src/backend/config.ts b/src/backend/config.ts index 084cb84ea..fa6d09e41 100644 --- a/src/backend/config.ts +++ b/src/backend/config.ts @@ -27,12 +27,9 @@ import { logError, logInfo, LogPrefix } from './logger/logger' import { getCrossover, getDefaultWine, - getSystemGamingPortingToolkitWine, getGamingPortingToolkitWine, getLinuxWineSet, - getWhisky, - getWineOnMac, - getWineskinWine + getWineOnMac } from './utils/compatibility_layers' import { backendEvents } from './backend_events' @@ -140,20 +137,10 @@ abstract class GlobalConfig { } const getGPTKWine = await getGamingPortingToolkitWine() - const getSystemGPTK = await getSystemGamingPortingToolkitWine() const crossover = await getCrossover() const wineOnMac = await getWineOnMac() - const wineskinWine = await getWineskinWine() - const whiskyWine = await getWhisky() - - return new Set([ - ...getGPTKWine, - ...getSystemGPTK, - ...crossover, - ...wineOnMac, - ...wineskinWine, - ...whiskyWine - ]) + + return new Set([...getGPTKWine, ...crossover, ...wineOnMac]) } /** diff --git a/src/backend/main.ts b/src/backend/main.ts index 13cceeea2..dd655e92a 100644 --- a/src/backend/main.ts +++ b/src/backend/main.ts @@ -51,16 +51,12 @@ import { getStoreName, isEpicServiceOffline, handleExit, - checkRosettaInstall, openUrlOrFile, resetApp, - setGPTKDefaultOnMacOS, showAboutWindow, showItemInFolder, wait, getShellPath, - checkWineBeforeLaunch, - downloadDefaultWine, writeConfig } from './utils' import { @@ -81,7 +77,6 @@ import { isCLIFullscreen, isCLINoGui, isFlatpak, - isMac, isSteamDeckGameMode, onboardLocalStore, publicDir, @@ -150,6 +145,13 @@ import * as Sentry from '@sentry/electron' import { DEV_PORTAL_URL, devSentryDsn, prodSentryDsn } from 'common/constants' import { getHpOverlay, initOverlay } from './overlay' +import { initExtension } from './extension/importer' +import { hpApi } from './utils/hyperplay_api' +import { + initializeCompatibilityLayer, + checkWineBeforeLaunch +} from './utils/compatibility_layers' + /* * INSERT OTHER IPC HANDLERS HERE */ @@ -633,21 +635,10 @@ if (!gotTheLock) { }, 10000) } - // Will download Wine if none was found - const availableWine = (await GlobalConfig.get().getAlternativeWine()) || [] - const toolkitDownloaded = availableWine.some( - (wine) => wine.type === 'toolkit' - ) - const shouldDownloadWine = - !availableWine.length || (isMac && !toolkitDownloaded) - - Promise.all([ - DXVK.getLatest(), - Winetricks.download(), - shouldDownloadWine ? downloadDefaultWine() : null, - isMac && checkRosettaInstall(), - isMac && !shouldDownloadWine && setGPTKDefaultOnMacOS() - ]) + // Setup the compatibility layer if not on Windows + if (!isWindows) { + initializeCompatibilityLayer() + } // set initial zoom level after a moment, if set in sync the value stays as 1 setTimeout(() => { @@ -2071,5 +2062,3 @@ ipcMain.handle('getHyperPlayListings', async () => { */ import './storeManagers/legendary/eos_overlay/ipc_handler' -import { initExtension } from './extension/importer' -import { hpApi } from './utils/hyperplay_api' diff --git a/src/backend/storeManagers/gog/setup.ts b/src/backend/storeManagers/gog/setup.ts index d05d055da..294bab908 100644 --- a/src/backend/storeManagers/gog/setup.ts +++ b/src/backend/storeManagers/gog/setup.ts @@ -9,7 +9,7 @@ import { import { copySync } from 'fs-extra' import path from 'node:path' import { GameInfo, InstalledInfo } from 'common/types' -import { checkWineBeforeLaunch, getShellPath, spawnAsync } from '../../utils' +import { getShellPath, spawnAsync } from '../../utils' import { GameConfig } from '../../game_config' import { logError, logInfo, LogPrefix, logWarning } from '../../logger/logger' import { isWindows } from '../../constants' @@ -18,6 +18,7 @@ import { isOnline } from '../../online_monitor' import { getWinePath, runWineCommand, verifyWinePrefix } from '../../launcher' import { logFileLocation } from 'backend/storeManagers/storeManagerCommon/games' import { getGameInfo as getGogLibraryGameInfo } from 'backend/storeManagers/gog/library' +import { checkWineBeforeLaunch } from 'backend/utils/compatibility_layers' const nonNativePathSeparator = path.sep === '/' ? '\\' : '/' /** diff --git a/src/backend/utils.ts b/src/backend/utils.ts index 2f6d8822e..5b8101098 100644 --- a/src/backend/utils.ts +++ b/src/backend/utils.ts @@ -10,8 +10,6 @@ import { SteamRuntime, GameInfo, GameSettings, - State, - ProgressInfo, AppSettings } from 'common/types' import axios from 'axios' @@ -34,7 +32,7 @@ import { SpawnOptions, spawnSync } from 'child_process' -import { appendFileSync, existsSync, rmSync } from 'graceful-fs' +import { existsSync, rmSync } from 'graceful-fs' import { promisify } from 'util' import i18next, { t } from 'i18next' @@ -47,15 +45,13 @@ import { isWindows, publicDir, isMac, - configStore, - isLinux + configStore } from './constants' import { logChangedSetting, logError, logInfo, LogPrefix, - logsDisabled, logWarning } from './logger/logger' import { basename, dirname, join, normalize } from 'path' @@ -73,17 +69,11 @@ import { import * as fileSize from 'filesize' import makeClient from 'discord-rich-presence-typescript' -import { notify, showDialogBoxModalAuto } from './dialog/dialog' +import { showDialogBoxModalAuto } from './dialog/dialog' import { getMainWindow, sendFrontendMessage } from './main_window' import { GlobalConfig } from './config' -import { GameConfig } from './game_config' -import { runWineCommand, validWine } from './launcher' +import { runWineCommand } from './launcher' import { gameManagerMap } from 'backend/storeManagers' -import { - installWineVersion, - updateWineVersionInfos, - wineDownloaderInfoStore -} from './wine/manager/utils' import * as si from 'systeminformation' import { @@ -91,6 +81,7 @@ import { vendorNameCache } from './utils/systeminfo/gpu/pci_ids' import { copyFile, mkdir, readdir, stat } from 'fs/promises' +import { GameConfig } from './game_config' const execAsync = promisify(exec) @@ -759,47 +750,6 @@ export const spawnAsync = async ( }) } -async function ContinueWithFoundWine( - selectedWine: string, - foundWine: string -): Promise<{ response: number }> { - const isGPTK = selectedWine.toLowerCase().includes('toolkit') - const isGPTKCompatible = await isMacSonomaOrHigher() - - if (isMac && isGPTK && !isGPTKCompatible) { - const { response } = await dialog.showMessageBox({ - title: i18next.t( - 'box.warning.wine-change.title-gptk', - 'Game Porting Toolkit Not Compatible ' - ), - message: i18next.t('box.warning.wine-change.message-gptk', { - defaultValue: - 'To be able to run games using the Apple Gaming porting toolkit you need to upgrade your macOS to 14 (Sonoma) or higher. {{newline}} We found Wine on your system, do you want to continue launching using {{foundWine}} ?', - newline: '\n', - foundWine: foundWine - }), - buttons: [i18next.t('box.yes'), i18next.t('box.no')], - icon: icon - }) - return { response } - } - - const { response } = await dialog.showMessageBox({ - title: i18next.t('box.warning.wine-change.title', 'Wine not found!'), - message: i18next.t('box.warning.wine-change.message', { - defaultValue: - 'We could not find the selected wine version to launch this title ({{selectedWine}}). {{newline}} We found another one, do you want to continue launching using {{foundWine}} ?', - newline: '\n', - selectedWine: selectedWine, - foundWine: foundWine - }), - buttons: [i18next.t('box.yes'), i18next.t('box.no')], - icon: icon - }) - - return { response } -} - export async function checkRosettaInstall() { if (!isMac) { return @@ -863,211 +813,6 @@ export async function isMacSonomaOrHigher() { return isMacSonomaOrHigher } -export async function downloadDefaultWine() { - if (isWindows) { - return - } - // refresh wine list - await updateWineVersionInfos(false) - // get list of wines on wineDownloaderInfoStore - const availableWine = wineDownloaderInfoStore.get('wine-releases', []) - - // use Wine-GE type if on Linux and GPTK or Wine-Crossover if on Mac - const isGPTKCompatible = isMac ? await isMacSonomaOrHigher() : false - const results = await Promise.all( - availableWine.map(async (version) => { - if (isLinux) { - return ( - version.type === 'Wine-GE' && - version.version.includes('Wine-GE-Proton') - ) - } - - if (isMac) { - return isGPTKCompatible - ? version.type === 'Game-Porting-Toolkit' - : version.type === 'Wine-Crossover' - } - return false - }) - ) - - const release = availableWine.filter((_, index) => results[index])[0] - - if (!release) { - logError('Could not find default wine version', LogPrefix.Backend) - return null - } - - // download the latest version - const onProgress = (state: State, progress?: ProgressInfo) => { - sendFrontendMessage('progressOfWineManager' + release.version, { - state, - progress - }) - } - - notify({ - title: i18next.t('notification.wine-download.title', 'Compatibility Layer'), - body: i18next.t( - 'notification.wine-download.message', - 'Setting up the default compatibility layer' - ) - }) - - const result = await installWineVersion( - release, - onProgress, - createAbortController(release.version).signal - ) - deleteAbortController(release.version) - if (result === 'success') { - let downloadedWine = null - try { - const wineList = await GlobalConfig.get().getAlternativeWine() - // update the game config to use that wine - downloadedWine = wineList[0] - logInfo(`Changing wine version to ${downloadedWine.name}`) - GlobalConfig.get().setSetting('wineVersion', downloadedWine) - } catch (error) { - logError( - ['Error when changing wine version to default', error], - LogPrefix.Backend - ) - } - return downloadedWine - } - return null -} - -export async function setGPTKDefaultOnMacOS() { - const isGPTKCompatible = await isMacSonomaOrHigher() - if (!isGPTKCompatible) { - return - } - - const { wineVersion: defaultWine } = GlobalConfig.get().getSettings() - - const ignoreList = ['crossover', 'toolkit'] - - if ( - ignoreList.includes(defaultWine.type.toLowerCase()) || - defaultWine.name.includes('Toolkit') - ) { - return - } - - const wineList = await GlobalConfig.get().getAlternativeWine() - const gptk = wineList.find((wine) => wine.type === 'toolkit') - - if (gptk && existsSync(gptk.bin)) { - logInfo(`Changing wine version to ${gptk.name}`) - GlobalConfig.get().setSetting('wineVersion', gptk) - // update prefix to use the new one as well - const installPath = GlobalConfig.get().getSettings().defaultInstallPath - const newPrefix = join(installPath, 'Prefixes', 'GPTK') - GlobalConfig.get().setSetting('winePrefix', newPrefix) - } - return -} - -export async function checkWineBeforeLaunch( - appName: string, - gameSettings: GameSettings, - logFileLocation: string -): Promise { - const wineIsValid = await validWine(gameSettings.wineVersion) - - const isToolkit = gameSettings.wineVersion.type === 'toolkit' - const isGPTKCompatible = await isMacSonomaOrHigher() - - const isValidOnLinux = isLinux && wineIsValid - const isValidtoolkitOnMac = - isMac && isToolkit && isGPTKCompatible && wineIsValid - const isValidWineOnMac = isMac && !isToolkit && wineIsValid - const isValidOnMac = isValidtoolkitOnMac || isValidWineOnMac - - if (isValidOnMac || isValidOnLinux) { - return true - } else { - if (!logsDisabled) { - logError( - `Wine version ${gameSettings.wineVersion.name} is not valid, trying another one.`, - LogPrefix.Backend - ) - - appendFileSync( - logFileLocation, - `Wine version ${gameSettings.wineVersion.name} is not valid, trying another one.` - ) - } - - // check if the default wine is valid now - const { wineVersion: defaultwine } = GlobalConfig.get().getSettings() - const defaultWineIsValid = await validWine(defaultwine) - if (defaultWineIsValid) { - const { response } = await ContinueWithFoundWine( - gameSettings.wineVersion.name, - defaultwine.name - ) - - if (response === 0) { - logInfo(`Changing wine version to ${defaultwine.name}`) - gameSettings.wineVersion = defaultwine - GameConfig.get(appName).setSetting('wineVersion', defaultwine) - return true - } else { - logInfo('User canceled the launch', LogPrefix.Backend) - return false - } - } else { - const wineList = await GlobalConfig.get().getAlternativeWine() - - // if Linux get the first element, if macOS and isGPTKCompatible is true get one with type 'toolkit', otherwise get the one with type 'wine' - const firstFoundWine = wineList.find((wine) => { - if (isLinux) { - return wine.type === 'wine' - } else if (isMac) { - return isGPTKCompatible - ? wine.type === 'toolkit' - : wine.type === 'wine' - } - return undefined - }) - - const isValidWine = await validWine(firstFoundWine) - - if (!wineList.length || !firstFoundWine || !isValidWine) { - const firstFoundWine = await downloadDefaultWine() - if (firstFoundWine) { - logInfo(`Changing wine version to ${firstFoundWine.name}`) - gameSettings.wineVersion = firstFoundWine - GameConfig.get(appName).setSetting('wineVersion', firstFoundWine) - return true - } - } - - if (firstFoundWine && isValidWine) { - const { response } = await ContinueWithFoundWine( - gameSettings.wineVersion.name, - firstFoundWine.name - ) - - if (response === 0) { - logInfo(`Changing wine version to ${firstFoundWine.name}`) - gameSettings.wineVersion = firstFoundWine - GameConfig.get(appName).setSetting('wineVersion', firstFoundWine) - return true - } else { - logInfo('User canceled the launch', LogPrefix.Backend) - return false - } - } - } - } - return false -} - export async function moveOnWindows( newInstallPath: string, gameInfo: GameInfo diff --git a/src/backend/utils/compatibility_layers.ts b/src/backend/utils/compatibility_layers.ts index f3655028b..864a80d13 100644 --- a/src/backend/utils/compatibility_layers.ts +++ b/src/backend/utils/compatibility_layers.ts @@ -2,20 +2,59 @@ import { GlobalConfig } from 'backend/config' import { configPath, getSteamLibraries, + icon, + isLinux, isMac, + isWindows, toolsPath, userHome } from 'backend/constants' -import { logError, LogPrefix, logInfo } from 'backend/logger/logger' -import { execAsync } from 'backend/utils' +import { + logError, + LogPrefix, + logInfo, + logsDisabled +} from 'backend/logger/logger' +import { + checkRosettaInstall, + execAsync, + isMacSonomaOrHigher +} from 'backend/utils' import { execSync } from 'child_process' -import { WineInstallation } from 'common/types' -import { existsSync, mkdirSync, readFileSync, readdirSync } from 'graceful-fs' +import { + GameSettings, + ProgressInfo, + State, + WineInstallation +} from 'common/types' +import { + appendFileSync, + existsSync, + mkdirSync, + readFileSync, + readdirSync +} from 'graceful-fs' import { homedir } from 'os' import { dirname, join } from 'path' import { PlistObject, parse as plistParse } from 'plist' import LaunchCommand from '../storeManagers/legendary/commands/launch' import { NonEmptyString, Path } from '../storeManagers/legendary/commands/base' +import { GameConfig } from 'backend/game_config' +import { validWine } from 'backend/launcher' +import { sendFrontendMessage } from 'backend/main_window' +import { DXVK, Winetricks } from 'backend/tools' +import { + installWineVersion, + updateWineVersionInfos, + wineDownloaderInfoStore +} from 'backend/wine/manager/utils' +import { dialog } from 'electron' +import i18next from 'i18next' +import { + createAbortController, + deleteAbortController +} from './aborthandler/aborthandler' +import { notify } from 'backend/dialog/dialog' /** * Loads the default wine installation path and version. @@ -200,7 +239,9 @@ export async function getLinuxWineSet( * * @returns Promise> */ -export async function getWineOnMac(): Promise> { +export async function getWineOnMac( + searchSystem = false +): Promise> { const wineSet = new Set() if (!isMac) { return wineSet @@ -217,16 +258,18 @@ export async function getWineOnMac(): Promise> { } // search for wine installed around the system - await execAsync('mdfind kMDItemCFBundleIdentifier = "*.wine"').then( - async ({ stdout }) => { - stdout.split('\n').forEach((winePath) => { - // avoid duplicating toolkit wine - if (!winePath.includes('game-porting-toolkit')) { - winePaths.add(winePath) - } - }) - } - ) + if (searchSystem) { + await execAsync('mdfind kMDItemCFBundleIdentifier = "*.wine"').then( + async ({ stdout }) => { + stdout.split('\n').forEach((winePath) => { + // avoid duplicating toolkit wine + if (!winePath.includes('game-porting-toolkit')) { + winePaths.add(winePath) + } + }) + } + ) + } winePaths.forEach((winePath) => { const infoFilePath = join(winePath, 'Contents/Info.plist') @@ -526,3 +569,303 @@ export function getWineFlagsArray( } return commandArray } + +export async function initializeCompatibilityLayer() { + // Fetch available Wine versions on the system + const availableWine = (await GlobalConfig.get().getAlternativeWine()) || [] + + // Determine if the toolkit has been downloaded on macOS + const toolkitDownloaded = availableWine.some( + (wine) => wine.type === 'toolkit' + ) + + // Determine if we need to download a new Wine version + const shouldDownloadWine = + !availableWine.length || (isMac && !toolkitDownloaded) + + // Build an array of promises for initialization tasks + const initializationTasks: Array> = [ + DXVK.getLatest(), + Winetricks.download() + ] + + if (shouldDownloadWine) { + initializationTasks.push(downloadDefaultWine()) + } + + if (isMac) { + initializationTasks.push(checkRosettaInstall()) + initializationTasks.push(setGPTKDefaultOnMacOS()) + } + + try { + await Promise.all(initializationTasks) + } catch (error) { + logError( + ['Error during compatibility layer initialization', error], + LogPrefix.Backend + ) + } +} + +export async function downloadDefaultWine() { + if (isWindows) return null + + try { + // Refresh wine list + await updateWineVersionInfos(true) + + // Get list of available wine versions + const availableWine = wineDownloaderInfoStore.get('wine-releases', []) + + // use Wine-GE type if on Linux and GPTK or Wine-Crossover if on Mac + const isGPTKCompatible = isMac ? await isMacSonomaOrHigher() : false + const results = await Promise.all( + availableWine.map(async (version) => { + if (isLinux) { + return ( + version.type === 'Wine-GE' && + version.version.includes('Wine-GE-Proton') + ) + } + + if (isMac) { + return isGPTKCompatible + ? version.type === 'Game-Porting-Toolkit' + : version.type === 'Wine-Crossover' + } + return false + }) + ) + + const release = availableWine.filter((_, index) => results[index])[0] + + if (!release) { + logError('Could not find any wine from list', LogPrefix.Backend) + return null + } + + // Notify user and start download + notify({ + title: i18next.t( + 'notification.wine-download.title', + 'Compatibility Layer' + ), + body: i18next.t( + 'notification.wine-download.message', + 'Setting up the default compatibility layer' + ) + }) + + const onProgress = (state: State, progress?: ProgressInfo) => { + sendFrontendMessage(`progressOfWineManager${release.version}`, { + state, + progress + }) + } + + const abortController = createAbortController(release.version) + const result = await installWineVersion( + release, + onProgress, + abortController.signal + ) + + deleteAbortController(release.version) + + if (result !== 'success') { + return null + } + + // Update the game config to use the downloaded wine + const wineList = await GlobalConfig.get().getAlternativeWine() + const downloadedWine = wineList[0] + + if (downloadedWine) { + logInfo(`Changing wine version to ${downloadedWine.name}`) + GlobalConfig.get().setSetting('wineVersion', downloadedWine) + } + + return downloadedWine + } catch (error) { + logError(['Error during wine download process', error], LogPrefix.Backend) + notify({ + title: i18next.t('notification.wine-download-failed.title', 'Error'), + body: i18next.t( + 'notification.wine-download-failed.message', + 'Failed to setup the default compatibility layer' + ) + }) + + return null + } +} + +export async function setGPTKDefaultOnMacOS() { + const isGPTKCompatible = await isMacSonomaOrHigher() + if (!isGPTKCompatible) { + return + } + + const { wineVersion: defaultWine } = GlobalConfig.get().getSettings() + + const ignoreList = ['crossover', 'toolkit'] + + if ( + ignoreList.includes(defaultWine.type.toLowerCase()) || + defaultWine.name.includes('Toolkit') + ) { + return + } + + const wineList = await GlobalConfig.get().getAlternativeWine() + const gptk = wineList.find((wine) => wine.type === 'toolkit') + + if (gptk && existsSync(gptk.bin)) { + logInfo(`Changing wine version to ${gptk.name}`) + GlobalConfig.get().setSetting('wineVersion', gptk) + // update prefix to use the new one as well + const installPath = GlobalConfig.get().getSettings().defaultInstallPath + const newPrefix = join(installPath, 'Prefixes', 'GPTK') + GlobalConfig.get().setSetting('winePrefix', newPrefix) + } + return +} + +export async function checkWineBeforeLaunch( + appName: string, + gameSettings: GameSettings, + logFileLocation: string +): Promise { + const wineIsValid = await validWine(gameSettings.wineVersion) + + const isToolkit = gameSettings.wineVersion.type === 'toolkit' + const isGPTKCompatible = await isMacSonomaOrHigher() + + const isValidOnLinux = isLinux && wineIsValid + const isValidtoolkitOnMac = + isMac && isToolkit && isGPTKCompatible && wineIsValid + const isValidWineOnMac = isMac && !isToolkit && wineIsValid + const isValidOnMac = isValidtoolkitOnMac || isValidWineOnMac + + if (isValidOnMac || isValidOnLinux) { + return true + } else { + if (!logsDisabled) { + logError( + `Wine version ${gameSettings.wineVersion.name} is not valid, trying another one.`, + LogPrefix.Backend + ) + + appendFileSync( + logFileLocation, + `Wine version ${gameSettings.wineVersion.name} is not valid, trying another one.` + ) + } + + // check if the default wine is valid now + const { wineVersion: defaultwine } = GlobalConfig.get().getSettings() + const defaultWineIsValid = await validWine(defaultwine) + if (defaultWineIsValid) { + const { response } = await ContinueWithFoundWine( + gameSettings.wineVersion.name, + defaultwine.name + ) + + if (response === 0) { + logInfo(`Changing wine version to ${defaultwine.name}`) + gameSettings.wineVersion = defaultwine + GameConfig.get(appName).setSetting('wineVersion', defaultwine) + return true + } else { + logInfo('User canceled the launch', LogPrefix.Backend) + return false + } + } else { + const wineList = await GlobalConfig.get().getAlternativeWine() + + // if Linux get the first element, if macOS and isGPTKCompatible is true get one with type 'toolkit', otherwise get the one with type 'wine' + const firstFoundWine = wineList.find((wine) => { + if (isLinux) { + return wine.type === 'wine' + } else if (isMac) { + return isGPTKCompatible + ? wine.type === 'toolkit' + : wine.type === 'wine' + } + return undefined + }) + + const isValidWine = await validWine(firstFoundWine) + + if (!wineList.length || !firstFoundWine || !isValidWine) { + const firstFoundWine = await downloadDefaultWine() + if (firstFoundWine) { + logInfo(`Changing wine version to ${firstFoundWine.name}`) + gameSettings.wineVersion = firstFoundWine + GameConfig.get(appName).setSetting('wineVersion', firstFoundWine) + return true + } + } + + if (firstFoundWine && isValidWine) { + const { response } = await ContinueWithFoundWine( + gameSettings.wineVersion.name, + firstFoundWine.name + ) + + if (response === 0) { + logInfo(`Changing wine version to ${firstFoundWine.name}`) + gameSettings.wineVersion = firstFoundWine + GameConfig.get(appName).setSetting('wineVersion', firstFoundWine) + return true + } else { + logInfo('User canceled the launch', LogPrefix.Backend) + return false + } + } + } + } + return false +} + +async function ContinueWithFoundWine( + selectedWine: string, + foundWine: string +): Promise<{ response: number }> { + const isGPTK = selectedWine.toLowerCase().includes('toolkit') + const isGPTKCompatible = await isMacSonomaOrHigher() + + if (isMac && isGPTK && !isGPTKCompatible) { + const { response } = await dialog.showMessageBox({ + title: i18next.t( + 'box.warning.wine-change.title-gptk', + 'Game Porting Toolkit Not Compatible ' + ), + message: i18next.t('box.warning.wine-change.message-gptk', { + defaultValue: + 'To be able to run games using the Apple Gaming porting toolkit you need to upgrade your macOS to 14 (Sonoma) or higher. {{newline}} We found Wine on your system, do you want to continue launching using {{foundWine}} ?', + newline: '\n', + foundWine: foundWine + }), + buttons: [i18next.t('box.yes'), i18next.t('box.no')], + icon: icon + }) + return { response } + } + + const { response } = await dialog.showMessageBox({ + title: i18next.t('box.warning.wine-change.title', 'Wine not found!'), + message: i18next.t('box.warning.wine-change.message', { + defaultValue: + 'We could not find the selected wine version to launch this title ({{selectedWine}}). {{newline}} We found another one, do you want to continue launching using {{foundWine}} ?', + newline: '\n', + selectedWine: selectedWine, + foundWine: foundWine + }), + buttons: [i18next.t('box.yes'), i18next.t('box.no')], + icon: icon + }) + + return { response } +} diff --git a/yarn.lock b/yarn.lock index c993ca749..390f0c10a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -14128,4 +14128,4 @@ zustand@4.4.1: zwitch@^2.0.0: version "2.0.4" resolved "https://registry.yarnpkg.com/zwitch/-/zwitch-2.0.4.tgz#c827d4b0acb76fc3e685a4c6ec2902d51070e9d7" - integrity sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A== + integrity sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A== \ No newline at end of file