Skip to content

Commit

Permalink
[Experimental] Automatic installation of known winetricks fixes (#3335)
Browse files Browse the repository at this point in the history
* Initial basic implementation of automatic winetricks fixes

* Fix i18next

* AutoWinetricks: ignore on windows, experimental feature flag, create fixes folder

* Use repo from heroic org

* Catch error so launch doesn't fail if wenetricks installs fails

* Hide winetricks experimental flag on windows

* Update i18next

* Remove unnecessary import
  • Loading branch information
arielj authored Jan 2, 2024
1 parent 1226add commit 9bf746e
Show file tree
Hide file tree
Showing 20 changed files with 142 additions and 13 deletions.
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

0 comments on commit 9bf746e

Please sign in to comment.