diff --git a/public/locales/en/translation.json b/public/locales/en/translation.json index 1fb4479797..bdcac18ef2 100644 --- a/public/locales/en/translation.json +++ b/public/locales/en/translation.json @@ -605,6 +605,7 @@ "disable_controller": "Disable Heroic navigation using controller", "disable_logs": "Disable Logs", "disablePlaytimeSync": "Disable playtime synchronization", + "disableUMU": "Disable UMU", "discordRPC": "Enable Discord Rich Presence", "download-no-https": "Download games without HTTPS (useful for CDNs e.g. LanCache)", "dxvkfpslimit": "Limit FPS (DX9, 10 and 11)", @@ -635,8 +636,7 @@ "experimental_features": { "cometSupport": "Comet support", "enableHelp": "Help component", - "enableNewDesign": "New design", - "umuSupport": "Use UMU as Proton runtime" + "enableNewDesign": "New design" }, "frameless-window": { "confirmation": { diff --git a/src/backend/config.ts b/src/backend/config.ts index ee3bcb1e36..d1bc6926d9 100644 --- a/src/backend/config.ts +++ b/src/backend/config.ts @@ -321,7 +321,8 @@ class GlobalConfigV0 extends GlobalConfig { battlEyeRuntime: isLinux, framelessWindow: false, beforeLaunchScriptPath: '', - afterLaunchScriptPath: '' + afterLaunchScriptPath: '', + disableUMU: false } // @ts-expect-error TODO: We need to settle on *one* place to define settings defaults return settings diff --git a/src/backend/launcher.ts b/src/backend/launcher.ts index b275e102cd..1ab625f32e 100644 --- a/src/backend/launcher.ts +++ b/src/backend/launcher.ts @@ -233,7 +233,7 @@ async function prepareLaunch( } if ( - (await isUmuSupported(gameSettings.wineVersion.type, false)) && + (await isUmuSupported(gameSettings, false)) && isOnline() && !(await isInstalled('umu')) && (await getUmuPath()) === defaultUmuPath @@ -246,7 +246,7 @@ async function prepareLaunch( const shouldUseRuntime = gameSettings.useSteamRuntime && (isNative || - (!(await isUmuSupported(gameSettings.wineVersion.type)) && + (!(await isUmuSupported(gameSettings)) && gameSettings.wineVersion.type === 'proton')) if (shouldUseRuntime) { @@ -819,7 +819,7 @@ export async function verifyWinePrefix( return { res: { stdout: '', stderr: '' } } } - if (!existsSync(winePrefix) && !(await isUmuSupported(wineVersion.type))) { + if (!existsSync(winePrefix) && !(await isUmuSupported(settings))) { mkdirSync(winePrefix, { recursive: true }) } @@ -831,7 +831,7 @@ export async function verifyWinePrefix( const haveToWait = !existsSync(systemRegPath) const command = runWineCommand({ - commandParts: (await isUmuSupported(wineVersion.type)) + commandParts: (await isUmuSupported(settings)) ? ['createprefix'] : ['wineboot', '--init'], wait: haveToWait, @@ -926,7 +926,7 @@ async function runWineCommand({ } const wineBin = wineVersion.bin.replaceAll("'", '') - const umuSupported = await isUmuSupported(wineVersion.type) + const umuSupported = await isUmuSupported(settings) const runnerBin = umuSupported ? await getUmuPath() : wineBin if (wineVersion.type === 'proton' && !umuSupported) { diff --git a/src/backend/storeManagers/gog/games.ts b/src/backend/storeManagers/gog/games.ts index caa7bd4f61..1b7cb698ab 100644 --- a/src/backend/storeManagers/gog/games.ts +++ b/src/backend/storeManagers/gog/games.ts @@ -576,22 +576,14 @@ export async function launch( ...wineEnvVars } - const { bin: wineExec, type: wineType } = gameSettings.wineVersion - - if (await isUmuSupported(wineType)) { + if (await isUmuSupported(gameSettings)) { const umuId = await getUmuId(gameInfo.app_name, gameInfo.runner) if (umuId) { commandEnv['GAMEID'] = umuId } } - // Fix for people with old config - const wineBin = - wineExec.startsWith("'") && wineExec.endsWith("'") - ? wineExec.replaceAll("'", '') - : wineExec - - wineFlag = await getWineFlagsArray(wineBin, wineType, shlex.join(wrappers)) + wineFlag = await getWineFlagsArray(gameSettings, shlex.join(wrappers)) } const commandParts = [ diff --git a/src/backend/storeManagers/legendary/games.ts b/src/backend/storeManagers/legendary/games.ts index 198a266401..e879d197ba 100644 --- a/src/backend/storeManagers/legendary/games.ts +++ b/src/backend/storeManagers/legendary/games.ts @@ -912,21 +912,14 @@ export async function launch( ...wineEnvVars } - const { bin: wineExec, type: wineType } = gameSettings.wineVersion - - if (await isUmuSupported(wineType)) { + if (await isUmuSupported(gameSettings)) { const umuId = await getUmuId(gameInfo.app_name, gameInfo.runner) if (umuId) { commandEnv['GAMEID'] = umuId } } - // Fix for people with old config - const wineBin = - wineExec.startsWith("'") && wineExec.endsWith("'") - ? wineExec.replaceAll("'", '') - : wineExec - wineFlags = await getWineFlags(wineBin, wineType, shlex.join(wrappers)) + wineFlags = await getWineFlags(gameSettings, shlex.join(wrappers)) } const appNameToLaunch = diff --git a/src/backend/storeManagers/nile/games.ts b/src/backend/storeManagers/nile/games.ts index 9481109983..d21fd28b5e 100644 --- a/src/backend/storeManagers/nile/games.ts +++ b/src/backend/storeManagers/nile/games.ts @@ -385,23 +385,15 @@ export async function launch( ...wineEnvVars } - const { bin: wineExec, type: wineType } = gameSettings.wineVersion - - if (await isUmuSupported(wineType)) { + if (await isUmuSupported(gameSettings)) { const umuId = await getUmuId(gameInfo.app_name, gameInfo.runner) if (umuId) { commandEnv['GAMEID'] = umuId } } - // Fix for people with old config - const wineBin = - wineExec.startsWith("'") && wineExec.endsWith("'") - ? wineExec.replaceAll("'", '') - : wineExec - wineFlag = [ - ...(await getWineFlagsArray(wineBin, wineType, shlex.join(wrappers))), + ...(await getWineFlagsArray(gameSettings, shlex.join(wrappers))), '--wine-prefix', gameSettings.winePrefix ] diff --git a/src/backend/tools/index.ts b/src/backend/tools/index.ts index a9813bcca7..ea5ef61a64 100644 --- a/src/backend/tools/index.ts +++ b/src/backend/tools/index.ts @@ -520,7 +520,6 @@ export const Winetricks = { const gameSettings = await gameManagerMap[runner].getSettings(appName) const { wineVersion } = gameSettings - const baseWinePrefix = gameSettings.winePrefix if (!(await validWine(wineVersion))) { return @@ -533,7 +532,7 @@ export const Winetricks = { await Winetricks.download() } - if (await isUmuSupported(wineVersion.type)) { + if (await isUmuSupported(gameSettings)) { winetricks = await getUmuPath() if (args.includes('-q')) { @@ -549,7 +548,7 @@ export const Winetricks = { } const { winePrefix, wineVersion: alwaysWine_wineVersion } = - await getWineFromProton(wineVersion, baseWinePrefix) + await getWineFromProton(gameSettings) return new Promise((resolve) => { const wineBin = alwaysWine_wineVersion.bin // We have to run Winetricks with an actual `wine` binary, meaning we @@ -719,10 +718,7 @@ export const Winetricks = { }, listInstalled: async (runner: Runner, appName: string) => { const gameSettings = await gameManagerMap[runner].getSettings(appName) - const { winePrefix } = await getWineFromProton( - gameSettings.wineVersion, - gameSettings.winePrefix - ) + const { winePrefix } = await getWineFromProton(gameSettings) const winetricksLogPath = join(winePrefix, 'winetricks.log') try { const winetricksLog = await readFile(winetricksLogPath, 'utf8') diff --git a/src/backend/utils.ts b/src/backend/utils.ts index e6c5194cbb..753f43fac0 100644 --- a/src/backend/utils.ts +++ b/src/backend/utils.ts @@ -141,13 +141,12 @@ function semverGt(target: string, base: string) { const getFileSize = fileSize.partial({ base: 2 }) as (arg: unknown) => string async function getWineFromProton( - wineVersion: WineInstallation, - winePrefix: string + gameSettings: GameSettings ): Promise<{ winePrefix: string; wineVersion: WineInstallation }> { - if ( - wineVersion.type !== 'proton' || - (await isUmuSupported(wineVersion.type)) - ) { + const wineVersion = gameSettings.wineVersion + let winePrefix = gameSettings.winePrefix + + if (wineVersion.type !== 'proton' || (await isUmuSupported(gameSettings))) { return { winePrefix, wineVersion } } diff --git a/src/backend/utils/compatibility_layers.ts b/src/backend/utils/compatibility_layers.ts index c73b6ce01c..d128bccc8e 100644 --- a/src/backend/utils/compatibility_layers.ts +++ b/src/backend/utils/compatibility_layers.ts @@ -3,6 +3,7 @@ import { configPath, defaultUmuPath, getSteamLibraries, + isLinux, isMac, toolsPath, userHome @@ -10,7 +11,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' @@ -489,12 +490,18 @@ export type AllowedWineFlags = Pick< * @param wrapper Any wrappers to be used, may be `''` */ export async function getWineFlags( - wineBin: string, - wineType: WineInstallation['type'], + gameSettings: GameSettings, wrapper: string ): Promise { let partialCommand: AllowedWineFlags = {} - const umuSupported = await isUmuSupported(wineType) + const { type: wineType, bin: wineExec } = gameSettings.wineVersion + + // Fix for people with old config + const wineBin = + wineExec.startsWith("'") && wineExec.endsWith("'") + ? wineExec.replaceAll("'", '') + : wineExec + switch (wineType) { case 'wine': case 'toolkit': @@ -508,7 +515,7 @@ export async function getWineFlags( `${wrapper} "${wineBin}" waitforexitandrun` ) } - if (umuSupported) { + if (await isUmuSupported(gameSettings)) { partialCommand['--wrapper'] = NonEmptyString.parse( (wrapper ? `${wrapper} ` : '') + `"${await getUmuPath()}"` ) @@ -530,11 +537,10 @@ export async function getWineFlags( * Like {@link getWineFlags}, but returns a `string[]` with the flags instead */ export async function getWineFlagsArray( - wineBin: string, - wineType: WineInstallation['type'], + gameSettings: GameSettings, wrapper: string ): Promise { - const partialCommand = await getWineFlags(wineBin, wineType, wrapper) + const partialCommand = await getWineFlags(gameSettings, wrapper) const commandArray: string[] = [] for (const [key, value] of Object.entries(partialCommand)) { @@ -548,13 +554,14 @@ export const getUmuPath = async () => searchForExecutableOnPath('umu-run').then((path) => path ?? defaultUmuPath) export async function isUmuSupported( - wineType: WineInstallation['type'], + gameSettings: GameSettings, checkUmuInstalled = true ): Promise { - const umuEnabled = - GlobalConfig.get().getSettings().experimentalFeatures?.umuSupport === true - const wineVersionSupported = wineType === 'proton' - const umuInstalled = checkUmuInstalled ? existsSync(await getUmuPath()) : true + if (!isLinux) return false + if (gameSettings.wineVersion.type !== 'proton') return false + if (gameSettings.disableUMU) return false + if (!checkUmuInstalled) return true + if (!existsSync(await getUmuPath())) return false - return umuEnabled && wineVersionSupported && umuInstalled + return true } diff --git a/src/common/types.ts b/src/common/types.ts index 91fc096411..36bcb0f216 100644 --- a/src/common/types.ts +++ b/src/common/types.ts @@ -65,7 +65,6 @@ export type ExperimentalFeatures = { enableNewDesign: boolean enableHelp: boolean cometSupport: boolean - umuSupport: boolean } export interface AppSettings extends GameSettings { @@ -102,6 +101,7 @@ export interface AppSettings extends GameSettings { minimizeOnLaunch: boolean startInTray: boolean allowInstallationBrokenAnticheat: boolean + disableUMU: boolean } export type LibraryTopSectionOptions = @@ -202,6 +202,7 @@ export interface GameSettings { gogSaves?: GOGCloudSavesLocation[] beforeLaunchScriptPath: string afterLaunchScriptPath: string + disableUMU: boolean } export type Status = diff --git a/src/frontend/screens/Settings/components/DisableUMU.tsx b/src/frontend/screens/Settings/components/DisableUMU.tsx new file mode 100644 index 0000000000..c26ce03178 --- /dev/null +++ b/src/frontend/screens/Settings/components/DisableUMU.tsx @@ -0,0 +1,31 @@ +import React, { useContext } from 'react' +import { useTranslation } from 'react-i18next' +import { ToggleSwitch } from 'frontend/components/UI' +import useSetting from 'frontend/hooks/useSetting' +import SettingsContext from '../SettingsContext' +import ContextProvider from 'frontend/state/ContextProvider' +import { defaultWineVersion } from '..' + +const DisableUMU = () => { + const { t } = useTranslation() + const { isDefault } = useContext(SettingsContext) + const { platform } = useContext(ContextProvider) + + const [disableUMU, setDisableUMU] = useSetting('disableUMU', false) + const [wineVersion] = useSetting('wineVersion', defaultWineVersion) + + if (isDefault || platform !== 'linux' || wineVersion.type !== 'proton') { + return <> + } + + return ( + setDisableUMU(!disableUMU)} + title={t('setting.disableUMU', 'Disable UMU')} + /> + ) +} + +export default DisableUMU diff --git a/src/frontend/screens/Settings/components/ExperimentalFeatures.tsx b/src/frontend/screens/Settings/components/ExperimentalFeatures.tsx index a9696cfc25..19fdf9fa08 100644 --- a/src/frontend/screens/Settings/components/ExperimentalFeatures.tsx +++ b/src/frontend/screens/Settings/components/ExperimentalFeatures.tsx @@ -5,22 +5,15 @@ import { ToggleSwitch } from 'frontend/components/UI' import ContextProvider from 'frontend/state/ContextProvider' const ExperimentalFeatures = () => { - const { platform } = useContext(ContextProvider) - const FEATURES = ['enableNewDesign', 'enableHelp', 'cometSupport'] - if (platform === 'linux') { - FEATURES.push('umuSupport') - } - const { t } = useTranslation() const [experimentalFeatures, setExperimentalFeatures] = useSetting( 'experimentalFeatures', { enableNewDesign: false, enableHelp: false, - cometSupport: true, - umuSupport: false + cometSupport: true } ) const { handleExperimentalFeatures } = useContext(ContextProvider) @@ -39,7 +32,6 @@ const ExperimentalFeatures = () => { t('setting.experimental_features.enableNewDesign', 'New design') t('setting.experimental_features.enableHelp', 'Help component') t('setting.experimental_features.cometSupport', 'Comet support') - t('setting.experimental_features.umuSupport', 'Use UMU as Proton runtime') */ return ( diff --git a/src/frontend/screens/Settings/sections/GamesSettings/index.tsx b/src/frontend/screens/Settings/sections/GamesSettings/index.tsx index f86ab083b3..41c3660df2 100644 --- a/src/frontend/screens/Settings/sections/GamesSettings/index.tsx +++ b/src/frontend/screens/Settings/sections/GamesSettings/index.tsx @@ -46,6 +46,7 @@ import SyncSaves from '../SyncSaves' import FooterInfo from '../FooterInfo' import { Tabs, Tab } from '@mui/material' import { GameInfo } from 'common/types' +import DisableUMU from '../../components/DisableUMU' const windowsPlatforms = ['Win32', 'Windows', 'windows'] function getStartingTab(platform: string, gameInfo?: GameInfo | null): string { @@ -214,6 +215,7 @@ export default function GamesSettings() { )} +
diff --git a/src/frontend/state/ContextProvider.tsx b/src/frontend/state/ContextProvider.tsx index 3b1472df69..01846ce44b 100644 --- a/src/frontend/state/ContextProvider.tsx +++ b/src/frontend/state/ContextProvider.tsx @@ -97,8 +97,7 @@ const initialContext: ContextType = { experimentalFeatures: { enableNewDesign: false, enableHelp: false, - cometSupport: true, - umuSupport: false + cometSupport: true }, handleExperimentalFeatures: () => null, disableDialogBackdropClose: false, diff --git a/src/frontend/state/GlobalState.tsx b/src/frontend/state/GlobalState.tsx index 9d5bee0065..aa93cd0d1d 100644 --- a/src/frontend/state/GlobalState.tsx +++ b/src/frontend/state/GlobalState.tsx @@ -212,7 +212,6 @@ class GlobalState extends PureComponent { enableNewDesign: false, enableHelp: false, cometSupport: true, - umuSupport: false, ...(globalSettings?.experimentalFeatures || {}) }, disableDialogBackdropClose: configStore.get(