From 56b39922ed8e2401fccb285d660d15e4d47ea672 Mon Sep 17 00:00:00 2001 From: Flavio Lima Date: Fri, 19 Feb 2021 18:39:24 +0100 Subject: [PATCH 1/5] fix: dxvk and launch issues --- electron/main.ts | 23 +++++---- electron/utils.ts | 120 ++++++++++++++++++++++++++++++++++++++++------ 2 files changed, 119 insertions(+), 24 deletions(-) diff --git a/electron/main.ts b/electron/main.ts index c44a647369..365627d3aa 100644 --- a/electron/main.ts +++ b/electron/main.ts @@ -21,6 +21,7 @@ import { iconDark, getSettings, iconLight, + getLatestDxvk, } from './utils' import { spawn, exec } from 'child_process' @@ -69,6 +70,10 @@ function createWindow(): BrowserWindow { }, }) + setTimeout(() => { + getLatestDxvk() + }, 2500) + //load the index.html from a url if (isDev) { //@ts-ignore @@ -156,13 +161,6 @@ if (!gotTheLock) { mainWindow.show() }, }, - { - label: i18next.t('tray.reload', 'Reload'), - click: function () { - mainWindow.reload() - }, - accelerator: 'ctrl + R', - }, { label: i18next.t('tray.about', 'About'), click: function () { @@ -181,6 +179,13 @@ if (!gotTheLock) { exec(`xdg-open ${kofiURL}`) }, }, + { + label: i18next.t('tray.reload', 'Reload'), + click: function () { + mainWindow.reload() + }, + accelerator: 'ctrl + R', + }, { label: i18next.t('tray.quit', 'Quit'), click: function () { @@ -193,8 +198,8 @@ if (!gotTheLock) { appIcon.setToolTip('Heroic') ipcMain.on('changeLanguage', async (event, language: string) => { await i18next.changeLanguage(language) - appIcon.setContextMenu(contextMenu) }) + return }) } @@ -387,7 +392,7 @@ interface Tools { ipcMain.on('callTool', async (event, { tool, wine, prefix, exe }: Tools) => { const wineBin = wine.replace("/proton'", "/dist/bin/wine'") - let winePrefix: string = prefix + let winePrefix: string = prefix.replace('~', home) if (wine.includes('proton')) { const protonPrefix = winePrefix.replaceAll("'", '') diff --git a/electron/utils.ts b/electron/utils.ts index 2a8527d279..376171983c 100644 --- a/electron/utils.ts +++ b/electron/utils.ts @@ -156,8 +156,7 @@ const updateGame = (game: string) => { const launchGame = async (appName: string) => { let envVars = '' - let dxvkPrefix = '' - let gameMode + let gameMode: string const { winePrefix, @@ -171,13 +170,12 @@ const launchGame = async (appName: string) => { autoInstallDxvk, } = await getSettings(appName) - const wineTricksCommand = `WINE=${wineVersion.bin} WINEPREFIX=${dxvkPrefix} winetricks` - let wine = `--wine ${wineVersion.bin}` + const fixedWinePrefix = winePrefix.replace('~', home) + const wineTricksCommand = `WINE=${wineVersion.bin} WINEPREFIX=${fixedWinePrefix} winetricks` + let wineCommand = `--wine ${wineVersion.bin}` // We need to keep replacing the ' to keep compatibility with old configs - let prefix = `--wine-prefix '${winePrefix - .replaceAll("'", '') - .replace('~', home)}'` + let prefix = `--wine-prefix '${fixedWinePrefix.replaceAll("'", '')}'` const isProton = wineVersion.name.startsWith('Proton') || @@ -206,26 +204,35 @@ const launchGame = async (appName: string) => { } // start the new prefix if it doesn't exists - if (!existsSync(`'${winePrefix}'`)) { + if (!existsSync(fixedWinePrefix)) { + // Create a sandbox wine prefix by default + // TODO: Add an option to disable that let command = `${wineTricksCommand} sandbox` if (isProton) { - command = `mkdir '${winePrefix}' -p` + command = `mkdir '${fixedWinePrefix}' -p` await execAsync(command) - } + } else { + // Start a new prefix with wine to avoid breaking the dxvk installation + const wineBoot = wineVersion.bin + .replace('wine', 'wineboot') + .replace('wine64', 'wineboot') - await execAsync(command) + await execAsync(`WINEPREFIX=${fixedWinePrefix} ${wineBoot}`) + await execAsync(command) + } } // Install DXVK for non Proton Prefixes if (!isProton && autoInstallDxvk) { - dxvkPrefix = winePrefix - await execAsync(`${wineTricksCommand} dxvk`) + await installDxvk(winePrefix) } if (wineVersion.name !== 'Wine Default') { const { bin } = wineVersion - wine = isProton ? `--no-wine --wrapper "${bin} run"` : `--wine ${bin}` + wineCommand = isProton + ? `--no-wine --wrapper "${bin} run"` + : `--wine ${bin}` } // check if Gamemode is installed @@ -235,7 +242,7 @@ const launchGame = async (appName: string) => { const runWithGameMode = useGameMode && gameMode ? gameMode : '' - const command = `${envVars} ${runWithGameMode} ${legendaryBin} launch ${appName} ${wine} ${prefix} ${launcherArgs}` + const command = `${envVars} ${runWithGameMode} ${legendaryBin} launch ${appName} ${wineCommand} ${prefix} ${launcherArgs}` console.log('\n Launch Command:', command) return execAsync(command) @@ -265,6 +272,87 @@ const launchGame = async (appName: string) => { }) } +async function getLatestDxvk() { + const { + data: { assets }, + } = await axios.default.get( + 'https://api.github.com/repos/lutris/dxvk/releases/latest' + ) + const current = assets[0] + const pkg = current.name + const name = pkg.replace('.tar.gz', '') + const downloadUrl = current.browser_download_url + + const dxvkLatest = `${heroicToolsPath}/DXVK/${pkg}` + const pastVersionCheck = `${heroicToolsPath}/DXVK/latest_dxvk` + let pastVersion = '' + + if (existsSync(pastVersionCheck)) { + pastVersion = readFileSync(pastVersionCheck).toString().split('\n')[0] + } + + if (pastVersion === name) { + return + } + + const downloadCommand = `curl -L ${downloadUrl} -o ${dxvkLatest} --create-dirs` + const extractCommand = `tar -zxf ${dxvkLatest} -C ${heroicToolsPath}/DXVK/` + const echoCommand = `echo ${name} > ${heroicToolsPath}/DXVK/latest_dxvk` + const cleanCommand = `rm ${dxvkLatest}` + + console.log('Updating DXVK to:', name) + + return execAsync(downloadCommand) + .then(async () => { + console.log('downloaded DXVK') + console.log('extracting DXVK') + exec(echoCommand) + await execAsync(extractCommand) + console.log('DXVK updated!') + exec(cleanCommand) + }) + .catch(() => console.log('Error when downloading DXVK')) +} + +async function installDxvk(prefix: string) { + if (!prefix) { + return + } + const winePrefix = prefix.replace('~', home) + + if (!existsSync(`${heroicToolsPath}/DXVK/latest_dxvk`)) { + console.log('dxvk not found!') + await getLatestDxvk() + } + + const globalVersion = readFileSync(`${heroicToolsPath}/DXVK/latest_dxvk`) + .toString() + .split('\n')[0] + const dxvkPath = `${heroicToolsPath}/DXVK/${globalVersion}/` + const currentVersionCheck = `${winePrefix}/current_dxvk` + let currentVersion = '' + + if (existsSync(currentVersionCheck)) { + currentVersion = readFileSync(currentVersionCheck).toString().split('\n')[0] + } + + if (currentVersion === globalVersion) { + return + } + + const installCommand = `WINEPREFIX=${winePrefix} bash ${dxvkPath}setup_dxvk.sh install` + const echoCommand = `echo '${globalVersion}' > ${currentVersionCheck}` + console.log(`installing DXVK on ${winePrefix}`, installCommand) + await execAsync(`WINEPREFIX=${winePrefix} wineboot`) + await execAsync(installCommand, { shell: '/bin/bash' }) + .then(() => exec(echoCommand)) + .catch(() => + console.log( + 'error when installing DXVK, please try launching the game again' + ) + ) +} + const writeDefaultconfig = async () => { const { account_id } = getUserInfo() const userName = user().username @@ -285,6 +373,7 @@ const writeDefaultconfig = async () => { }, }, } + if (!existsSync(heroicConfigPath)) { writeFileSync(heroicConfigPath, JSON.stringify(config, null, 2)) } @@ -376,6 +465,7 @@ export { getSettings, isLoggedIn, launchGame, + getLatestDxvk, writeDefaultconfig, writeGameconfig, checkForUpdates, From 4fd70ef04a72699a3d678814eef698471a98d8e8 Mon Sep 17 00:00:00 2001 From: Flavio Lima Date: Fri, 19 Feb 2021 18:40:13 +0100 Subject: [PATCH 2/5] fix: header on settings --- src/components/Settings/index.tsx | 26 +++++++++++++++++--------- src/components/UI/Header.tsx | 11 +++++++---- 2 files changed, 24 insertions(+), 13 deletions(-) diff --git a/src/components/Settings/index.tsx b/src/components/Settings/index.tsx index 71c73b09ae..bc10a8ba9f 100644 --- a/src/components/Settings/index.tsx +++ b/src/components/Settings/index.tsx @@ -12,6 +12,7 @@ import SyncSaves from './SyncSaves' import Tools from './Tools' import WineSettings from './WineSettings' import { IpcRenderer } from 'electron' +import Update from '../UI/Update' interface ElectronProps { ipcRenderer: IpcRenderer @@ -27,7 +28,7 @@ interface RouteParams { // TODO: add feedback when launching winecfg and winetricks function Settings() { - const { t } = useTranslation() + const { t, i18n } = useTranslation() const { state } = useLocation() as { state: any } const [wineVersion, setWineversion] = useState({ @@ -39,6 +40,7 @@ function Settings() { const [otherOptions, setOtherOptions] = useState('') const [launcherArgs, setLauncherArgs] = useState('') const [egsLinkedPath, setEgsLinkedPath] = useState('') + const [title, setTitle] = useState('') const [maxWorkers, setMaxWorkers] = useState(2) const [egsPath, setEgsPath] = useState(egsLinkedPath) const [language, setLanguage] = useState( @@ -116,16 +118,22 @@ function Settings() { setMaxWorkers(config.maxWorkers || 2) if (!isDefault) { - const { cloudSaveEnabled, saveFolder } = await getGameInfo(appName) - setHaveCloudSaving({ cloudSaveEnabled, saveFolder }) + const { + cloudSaveEnabled, + saveFolder, + title: gameTitle, + } = await getGameInfo(appName) + setTitle(gameTitle) + return setHaveCloudSaving({ cloudSaveEnabled, saveFolder }) } + return setTitle(t('globalSettings', 'Global Settings')) } getSettings() return () => { ipcRenderer.removeAllListeners('requestSettings') } - }, [appName, type, isDefault]) + }, [appName, type, isDefault, i18n.language]) const GlobalSettings = { defaultSettings: { @@ -169,17 +177,17 @@ function Settings() { returnPath = null } - const headerTitle = isDefault - ? t('globalSettings', 'Global Settings') - : `${state ? state.title : ''}` - useEffect(() => { writeConfig([appName, settingsToSave]) }, [GlobalSettings, GameSettings, appName]) + if (!title) { + return + } + return ( <> -
+
{isDefault && ( diff --git a/src/components/UI/Header.tsx b/src/components/UI/Header.tsx index 5955cbeb32..eafb9339ef 100644 --- a/src/components/UI/Header.tsx +++ b/src/components/UI/Header.tsx @@ -1,6 +1,6 @@ import React, { useContext } from 'react' import { useTranslation } from 'react-i18next' -import { Link, useHistory } from 'react-router-dom' +import { Link, useHistory, useLocation } from 'react-router-dom' import cx from 'classnames' import ContextProvider from '../../state/ContextProvider' @@ -26,6 +26,7 @@ export default function Header({ const haveDownloads = libraryStatus.filter( (game) => game.status === 'installing' || game.status === 'updating' ).length + const { pathname } = useLocation() const history = useHistory() const link = goTo ? goTo : '' @@ -74,9 +75,11 @@ export default function Header({ {t('Total Games')}: {numberOfGames} - ) : -
{t('nogames')}
- } + ) : ( + !pathname.startsWith('/settings') && ( +
{t('nogames')}
+ ) + )} {title &&
{title}
} {handleLayout && (
From 3856f4c5d0fec83db9c3ad4d848b135fd44cbdae Mon Sep 17 00:00:00 2001 From: Flavio Lima Date: Fri, 19 Feb 2021 18:41:13 +0100 Subject: [PATCH 3/5] fix: err message when null --- src/components/GamePage/GamePage.tsx | 5 ++++- src/components/GamePage/GamesSubmenu.tsx | 2 +- src/components/UI/GameCard.tsx | 10 +++++++--- 3 files changed, 12 insertions(+), 5 deletions(-) diff --git a/src/components/GamePage/GamePage.tsx b/src/components/GamePage/GamePage.tsx index f887da6da8..db039d3c6b 100644 --- a/src/components/GamePage/GamePage.tsx +++ b/src/components/GamePage/GamePage.tsx @@ -363,7 +363,10 @@ export default function GamePage() { if (!err) { return } - if (err.includes('ERROR: Game is out of date')) { + if ( + typeof err === 'string' && + err.includes('ERROR: Game is out of date') + ) { const { response } = await showMessageBox({ title: t('box.update.title'), message: t('box.update.message'), diff --git a/src/components/GamePage/GamesSubmenu.tsx b/src/components/GamePage/GamesSubmenu.tsx index fe50e26083..7ec51423c8 100644 --- a/src/components/GamePage/GamesSubmenu.tsx +++ b/src/components/GamePage/GamesSubmenu.tsx @@ -81,7 +81,7 @@ export default function GamesSubmenu({ className="hidden link" to={{ pathname: `/settings/${appName}/wine`, - state: { fromGameCard: false, title }, + state: { fromGameCard: false }, }} > {t('submenu.settings')} diff --git a/src/components/UI/GameCard.tsx b/src/components/UI/GameCard.tsx index 4f201ba89f..be1012dc44 100644 --- a/src/components/UI/GameCard.tsx +++ b/src/components/UI/GameCard.tsx @@ -155,7 +155,7 @@ const GameCard = ({ @@ -175,7 +175,7 @@ const GameCard = ({ @@ -210,7 +210,11 @@ const GameCard = ({ if (!err) { return } - if (err.includes('ERROR: Game is out of date')) { + + if ( + typeof err === 'string' && + err.includes('ERROR: Game is out of date') + ) { const { response } = await showMessageBox({ title: t('box.update.title'), message: t('box.update.message'), From 200a7f234fc3fe8a64ab6a8fc8eb53cd2945a350 Mon Sep 17 00:00:00 2001 From: Flavio Lima Date: Fri, 19 Feb 2021 19:39:50 +0100 Subject: [PATCH 4/5] fix: small fixes --- src/App.css | 9 ++++++--- src/components/Library.tsx | 7 ++----- src/components/UI/Header.tsx | 12 +++++------- 3 files changed, 13 insertions(+), 15 deletions(-) diff --git a/src/App.css b/src/App.css index c8258aa9fc..3eb1541f70 100644 --- a/src/App.css +++ b/src/App.css @@ -1,5 +1,5 @@ -@import "~@fontsource/rubik/index.css"; -@import "~@fontsource/cabin/index.css"; +@import '~@fontsource/rubik/index.css'; +@import '~@fontsource/cabin/index.css'; * { box-sizing: border-box; @@ -389,6 +389,7 @@ display: grid; place-self: center; place-items: flex-start; + width: 569px; } .settingsTools { @@ -987,7 +988,7 @@ color: var(--primary); } .more .hidden { - opacity: 0; + display: none; cursor: default; } @@ -998,6 +999,8 @@ .more.clicked > .hidden { opacity: 1; + display: block; + cursor: pointer; } diff --git a/src/components/Library.tsx b/src/components/Library.tsx index 6cfddd2ca1..bb8bddad21 100644 --- a/src/components/Library.tsx +++ b/src/components/Library.tsx @@ -1,5 +1,4 @@ import React, { lazy, useContext } from 'react' -import { useTranslation } from 'react-i18next' import cx from 'classnames' import ContextProvider from '../state/ContextProvider' @@ -18,7 +17,6 @@ window.onscroll = () => { } export const Library = ({ library }: Props) => { - const { t } = useTranslation() const { layout } = useContext(ContextProvider) const backToTop = () => { const anchor = document.getElementById('top') @@ -36,7 +34,7 @@ export const Library = ({ library }: Props) => { gameList: layout === 'grid', })} > - {!!library.length && ( + {!!library.length && library.map( ({ title, @@ -66,8 +64,7 @@ export const Library = ({ library }: Props) => { /> ) } - ) - )} + )}