Skip to content

[Experimental] Automatic installation of known winetricks fixes #3335

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 10 commits into from
Jan 2, 2024
6 changes: 4 additions & 2 deletions public/locales/en/gamepage.json
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,8 @@
"prerequisites": "Installing Prerequisites",
"saves": {
"syncing": "Syncing Saves"
}
},
"winetricks": "Installing Winetricks Packages"
},
"launch": {
"options": "Launch Options..."
Expand Down Expand Up @@ -229,7 +230,8 @@
"this-game-uses-third-party": "This game uses third party launcher and it is not supported yet",
"totalDownloaded": "Total Downloaded",
"uninstalling": "Uninstalling",
"updating": "Updating Game"
"updating": "Updating Game",
"winetricks": "Applying Winetricks fixes"
},
"submenu": {
"addShortcut": "Add shortcut",
Expand Down
1 change: 1 addition & 0 deletions public/locales/en/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -591,6 +591,7 @@
"esync": "Enable Esync",
"exit-to-tray": "Exit to System Tray",
"experimental_features": {
"automaticWinetricksFixes": "Apply known Winetricks fixes automatically",
"enableHelp": "Help component",
"enableNewDesign": "New design"
},
Expand Down
4 changes: 3 additions & 1 deletion src/backend/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ const defaultWinePrefixDir = join(userHome, 'Games', 'Heroic', 'Prefixes')
const defaultWinePrefix = join(defaultWinePrefixDir, 'default')
const anticheatDataPath = join(appFolder, 'areweanticheatyet.json')
const imagesCachePath = join(appFolder, 'images-cache')
const fixesPath = join(appFolder, 'fixes')

const {
currentLogFile,
Expand Down Expand Up @@ -276,5 +277,6 @@ export {
nileConfigPath,
nileInstalled,
nileLibrary,
nileUserData
nileUserData,
fixesPath
}
31 changes: 29 additions & 2 deletions src/backend/downloadmanager/utils.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,18 @@
import { gameManagerMap } from 'backend/storeManagers'
import { logError, LogPrefix, logWarning } from '../logger/logger'
import { isEpicServiceOffline, sendGameStatusUpdate } from '../utils'
import { DMStatus, InstallParams } from 'common/types'
import {
downloadFile,
isEpicServiceOffline,
sendGameStatusUpdate
} from '../utils'
import { DMStatus, InstallParams, Runner } from 'common/types'
import i18next from 'i18next'
import { notify, showDialogBoxModalAuto } from '../dialog/dialog'
import { isOnline } from '../online_monitor'
import { fixesPath } from 'backend/constants'
import path from 'path'
import { existsSync, mkdirSync } from 'graceful-fs'
import { platform } from 'os'

async function installQueueElement(params: InstallParams): Promise<{
status: DMStatus
Expand Down Expand Up @@ -64,6 +72,10 @@ async function installQueueElement(params: InstallParams): Promise<{
}

try {
if (platform() !== 'win32') {
downloadFixesFor(appName, runner)
}

const { status, error } = await gameManagerMap[runner].install(appName, {
path: path.replaceAll("'", ''),
installDlcs,
Expand Down Expand Up @@ -157,4 +169,19 @@ async function updateQueueElement(params: InstallParams): Promise<{
}
}

const runnerToStore = {
legendary: 'epic',
gog: 'gog',
nile: 'amazon'
}

async function downloadFixesFor(appName: string, runner: Runner) {
const url = `https://raw.githubusercontent.com/Heroic-Games-Launcher/known-fixes/main/${appName}-${runnerToStore[runner]}.json`
const dest = path.join(fixesPath, `${appName}-${runnerToStore[runner]}.json`)
if (!existsSync(fixesPath)) {
mkdirSync(fixesPath, { recursive: true })
}
downloadFile({ url, dest })
}

export { installQueueElement, updateQueueElement }
44 changes: 42 additions & 2 deletions src/backend/launcher.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import { join, normalize } from 'path'

import {
defaultWinePrefix,
fixesPath,
flatPakHome,
isLinux,
isMac,
Expand All @@ -40,7 +41,8 @@ import {
quoteIfNecessary,
errorHandler,
removeQuoteIfNecessary,
memoryLog
memoryLog,
sendGameStatusUpdate
} from './utils'
import {
logDebug,
Expand All @@ -52,7 +54,7 @@ import {
} from './logger/logger'
import { GlobalConfig } from './config'
import { GameConfig } from './game_config'
import { DXVK } from './tools'
import { DXVK, Winetricks } from './tools'
import setup from './storeManagers/gog/setup'
import nileSetup from './storeManagers/nile/setup'
import { spawn, spawnSync } from 'child_process'
Expand Down Expand Up @@ -343,6 +345,12 @@ async function prepareWineLaunch(
if (runner === 'legendary') {
await legendarySetup(appName)
}
if (
GlobalConfig.get().getSettings().experimentalFeatures
.automaticWinetricksFixes
) {
await installFixes(appName, runner)
}
}

// If DXVK/VKD3D installation is enabled, install it
Expand Down Expand Up @@ -381,6 +389,38 @@ async function prepareWineLaunch(
return { success: true, envVars: envVars }
}

const runnerToStore = {
legendary: 'epic',
gog: 'gog',
nile: 'amazon'
}

async function installFixes(appName: string, runner: Runner) {
const fixPath = join(fixesPath, `${appName}-${runnerToStore[runner]}.json`)

if (!existsSync(fixPath)) return

try {
const fixesContent = JSON.parse(readFileSync(fixPath).toString())

sendGameStatusUpdate({
appName,
runner: runner,
status: 'winetricks'
})

for (const winetricksPackage of fixesContent.winetricks) {
await Winetricks.install(runner, appName, winetricksPackage)
}
} catch (error) {
// if we fail to download the json file, it can be malformed causing
// JSON.parse to throw an exception
logWarning(
`Known winetricks fixes could not be applied, ignoring.\n${error}`
)
}
}

/**
* Maps general settings to environment variables
* @param gameSettings The GameSettings to get the environment variables for
Expand Down
6 changes: 6 additions & 0 deletions src/backend/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1079,6 +1079,12 @@ ipcMain.handle(
}
}

sendGameStatusUpdate({
appName,
runner,
status: 'launching'
})

const command = gameManagerMap[runner].launch(appName, launchArguments)

const launchResult = await command.catch((exception) => {
Expand Down
6 changes: 6 additions & 0 deletions src/backend/storeManagers/gog/games.ts
Original file line number Diff line number Diff line change
Expand Up @@ -523,6 +523,12 @@ export async function launch(

sendGameStatusUpdate({ appName, runner: 'gog', status: 'playing' })

sendGameStatusUpdate({
appName,
runner: 'gog',
status: 'playing'
})

const { error, abort } = await runGogdlCommand(commandParts, {
abortId: appName,
env: commandEnv,
Expand Down
6 changes: 6 additions & 0 deletions src/backend/storeManagers/legendary/games.ts
Original file line number Diff line number Diff line change
Expand Up @@ -859,6 +859,12 @@ export async function launch(

sendGameStatusUpdate({ appName, runner: 'legendary', status: 'playing' })

sendGameStatusUpdate({
appName,
runner: 'legendary',
status: 'playing'
})

const { error } = await runLegendaryCommand(command, {
abortId: appName,
env: commandEnv,
Expand Down
6 changes: 6 additions & 0 deletions src/backend/storeManagers/nile/games.ts
Original file line number Diff line number Diff line change
Expand Up @@ -404,6 +404,12 @@ export async function launch(

sendGameStatusUpdate({ appName, runner: 'nile', status: 'playing' })

sendGameStatusUpdate({
appName,
runner: 'nile',
status: 'playing'
})

const { error } = await runNileCommand(commandParts, {
abortId: appName,
env: commandEnv,
Expand Down
2 changes: 2 additions & 0 deletions src/common/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ export type Release = {
export type ExperimentalFeatures = {
enableNewDesign: boolean
enableHelp: boolean
automaticWinetricksFixes: boolean
}

export interface AppSettings extends GameSettings {
Expand Down Expand Up @@ -193,6 +194,7 @@ export type Status =
| 'installed'
| 'prerequisites'
| 'extracting'
| 'winetricks'

export interface GameStatus {
appName: string
Expand Down
1 change: 1 addition & 0 deletions src/frontend/hooks/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ export function getStatusLabel({
}`,
notInstalled: t('gamepage:status.notinstalled'),
launching: t('gamepage:status.launching', 'Launching'),
winetricks: t('gamepage:status.winetricks', 'Applying Winetricks fixes'),
prerequisites: t(
'gamepage:status.prerequisites',
'Installing Prerequisites'
Expand Down
1 change: 1 addition & 0 deletions src/frontend/screens/Game/GameContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ const initialContext: GameContextType = {
gameInstallInfo: null,
is: {
installing: false,
installingWinetricksPackages: false,
installingPrerequisites: false,
launching: false,
linux: false,
Expand Down
4 changes: 4 additions & 0 deletions src/frontend/screens/Game/GamePage/components/MainButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,9 @@ const MainButton = ({ gameInfo, handlePlay, handleInstall }: Props) => {
if (is.installingPrerequisites) {
return t('label.prerequisites', 'Installing Prerequisites')
}
if (is.installingWinetricksPackages) {
return t('label.winetricks', 'Installing Winetricks Packages')
}
if (is.launching) {
return t('label.launching', 'Launching')
}
Expand Down Expand Up @@ -136,6 +139,7 @@ const MainButton = ({ gameInfo, handlePlay, handleInstall }: Props) => {
is.uninstalling ||
is.syncing ||
is.launching ||
is.installingWinetricksPackages ||
is.installingPrerequisites
}
autoFocus={true}
Expand Down
2 changes: 2 additions & 0 deletions src/frontend/screens/Game/GamePage/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,7 @@ export default React.memo(function GamePage(): JSX.Element | null {
const isUninstalling = status === 'uninstalling'
const isSyncing = status === 'syncing-saves'
const isLaunching = status === 'launching'
const isInstallingWinetricksPackages = status === 'winetricks'
const isInstallingPrerequisites = status === 'prerequisites'
const notAvailable = !gameAvailable && gameInfo.is_installed
const notInstallable =
Expand Down Expand Up @@ -292,6 +293,7 @@ export default React.memo(function GamePage(): JSX.Element | null {
gameExtraInfo: extraInfo,
is: {
installing: isInstalling,
installingWinetricksPackages: isInstallingWinetricksPackages,
installingPrerequisites: isInstallingPrerequisites,
launching: isLaunching,
linux: isLinux,
Expand Down
3 changes: 3 additions & 0 deletions src/frontend/screens/Library/components/GameCard/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ export function getCardStatus(
const notSupportedGame = status === 'notSupportedGame'
const syncingSaves = status === 'syncing-saves'
const isLaunching = status === 'launching'
const isInstallingWinetricksPackages = status === 'winetricks'
const isInstallingPrerequisites = status === 'prerequisites'

const haveStatus =
Expand All @@ -44,6 +45,7 @@ export function getCardStatus(
isPlaying ||
syncingSaves ||
isLaunching ||
isInstallingWinetricksPackages ||
isInstallingPrerequisites ||
(isInstalled && layout !== 'grid')
return {
Expand All @@ -55,6 +57,7 @@ export function getCardStatus(
notAvailable,
isUpdating,
isLaunching,
isInstallingWinetricksPackages,
isInstallingPrerequisites,
haveStatus
}
Expand Down
4 changes: 3 additions & 1 deletion src/frontend/screens/Library/components/GameCard/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -227,7 +227,9 @@ const GameCard = ({
if (isInstalled) {
const disabled =
isLaunching ||
['syncing-saves', 'launching', 'prerequisites'].includes(status!)
['syncing-saves', 'launching', 'prerequisites', 'winetricks'].includes(
status!
)
return (
<SvgButton
className={!notAvailable ? 'playIcon' : 'notAvailableIcon'}
Expand Down
17 changes: 14 additions & 3 deletions src/frontend/screens/Settings/components/ExperimentalFeatures.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,23 @@ import useSetting from 'frontend/hooks/useSetting'
import { ToggleSwitch } from 'frontend/components/UI'
import ContextProvider from 'frontend/state/ContextProvider'

const FEATURES = ['enableNewDesign', 'enableHelp']

const ExperimentalFeatures = () => {
const { platform } = useContext(ContextProvider)

const FEATURES = ['enableNewDesign', 'enableHelp']

if (platform !== 'win32') {
FEATURES.push('automaticWinetricksFixes')
}

const { t } = useTranslation()
const [experimentalFeatures, setExperimentalFeatures] = useSetting(
'experimentalFeatures',
{ enableNewDesign: false, enableHelp: false }
{
enableNewDesign: false,
enableHelp: false,
automaticWinetricksFixes: false
}
)
const { handleExperimentalFeatures } = useContext(ContextProvider)

Expand All @@ -27,6 +37,7 @@ const ExperimentalFeatures = () => {
Translations:
t('setting.experimental_features.enableNewDesign', 'New design')
t('setting.experimental_features.enableHelp', 'Help component')
t('setting.experimental_features.automaticWinetricksFixes', 'Apply known Winetricks fixes automatically')
*/

return (
Expand Down
6 changes: 5 additions & 1 deletion src/frontend/state/ContextProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,11 @@ const initialContext: ContextType = {
addHelpItem: () => null,
removeHelpItem: () => null
},
experimentalFeatures: { enableNewDesign: false, enableHelp: false },
experimentalFeatures: {
enableNewDesign: false,
enableHelp: false,
automaticWinetricksFixes: false
},
handleExperimentalFeatures: () => null
}

Expand Down
4 changes: 3 additions & 1 deletion src/frontend/state/GlobalState.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -213,7 +213,8 @@ class GlobalState extends PureComponent<Props> {
helpItems: {},
experimentalFeatures: globalSettings?.experimentalFeatures || {
enableNewDesign: false,
enableHelp: false
enableHelp: false,
automaticWinetricksFixes: false
}
}

Expand Down Expand Up @@ -697,6 +698,7 @@ class GlobalState extends PureComponent<Props> {
'playing',
'extracting',
'launching',
'winetricks',
'prerequisites',
'queued'
].includes(status)
Expand Down
Loading