Skip to content

[Feat] Auto Download Wine if no version was found in the system #2706

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
May 30, 2023
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "heroic",
"version": "2.7.1",
"version": "2.8.0",
"private": true,
"main": "build/electron/main.js",
"homepage": "./",
Expand Down
2 changes: 1 addition & 1 deletion public/locales/be/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -668,7 +668,7 @@
"manager": {
"link": "Менеджэр Wine",
"not-found": "Версіі Wine не знойдзены. Націсніце на значок абнаўлення, каб паўтарыць спробу.",
"title": "Wine Manager (Beta)",
"title": "Wine Manager",
"unzipping": "Распакоўка"
},
"release": "Release Date",
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 @@ -295,6 +295,7 @@
"warning": "Warning",
"wine-path": "Wine Path",
"wine-path-invalid": "Wine Path is invalid, please select another one.",
"wine-path-none-found": "No Wine version was found, download one from the Wine Manager",
"wine-prefix": {
"title": "Wine Prefix"
},
Expand Down
2 changes: 1 addition & 1 deletion public/locales/sk/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -668,7 +668,7 @@
"manager": {
"link": "Wine Manager",
"not-found": "No Wine versions found. Please click the refresh icon to try again.",
"title": "Wine Manager (Beta)",
"title": "Wine Manager",
"unzipping": "Unzipping"
},
"release": "Release Date",
Expand Down
22 changes: 9 additions & 13 deletions src/backend/game_config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,10 @@ import {
currentGameConfigVersion,
configPath,
gamesConfigPath,
defaultWinePrefix,
isLinux,
isMac,
isWindows,
userHome
userHome,
defaultWinePrefix
} from './constants'
import { logError, logInfo, LogPrefix } from './logger/logger'
import { join } from 'path'
Expand Down Expand Up @@ -270,16 +269,13 @@ class GameConfigV0 extends GameConfig {
// set specific keys depending on the platform
if (isMac) {
defaultSettings.wineCrossoverBottle = wineCrossoverBottle
} else if (isLinux) {
defaultSettings.winePrefix = winePrefix || defaultWinePrefix

// fix winePrefix if needed
if (gameSettings.winePrefix?.includes('~')) {
gameSettings.winePrefix = gameSettings.winePrefix.replace(
'~',
userHome
)
}
}

defaultSettings.winePrefix = winePrefix || defaultWinePrefix

// fix winePrefix if needed
if (gameSettings.winePrefix?.includes('~')) {
gameSettings.winePrefix = gameSettings.winePrefix.replace('~', userHome)
}
}

Expand Down
11 changes: 9 additions & 2 deletions src/backend/launcher.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,14 @@ import {
} from 'graceful-fs'
import { join } from 'path'

import { flatPakHome, isLinux, isMac, runtimePath, userHome } from './constants'
import {
defaultWinePrefix,
flatPakHome,
isLinux,
isMac,
runtimePath,
userHome
} from './constants'
import {
constructAndUpdateRPC,
getSteamRuntime,
Expand Down Expand Up @@ -448,7 +455,7 @@ export async function validWine(
export async function verifyWinePrefix(
settings: GameSettings
): Promise<{ res: ExecResult; updated: boolean }> {
const { winePrefix, wineVersion } = settings
const { winePrefix = defaultWinePrefix, wineVersion } = settings

const isValidWine = await validWine(wineVersion)

Expand Down
16 changes: 13 additions & 3 deletions src/backend/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,8 @@ import {
getShellPath,
getCurrentChangelog,
checkWineBeforeLaunch,
removeFolder
removeFolder,
downloadDefaultWine
} from './utils'
import {
configStore,
Expand Down Expand Up @@ -163,15 +164,19 @@ async function initializeWindow(): Promise<BrowserWindow> {
mainWindow.setFullScreen(true)
}

setTimeout(() => {
setTimeout(async () => {
// Will download Wine if none was found
const availableWine = await GlobalConfig.get().getAlternativeWine()
DXVK.getLatest()
Winetricks.download()
if (!availableWine.length) {
downloadDefaultWine()
}
}, 2500)

GlobalConfig.get()

mainWindow.setIcon(icon)
app.setAppUserModelId('Heroic')
app.commandLine.appendSwitch('enable-spatial-navigation')

mainWindow.on('close', async (e) => {
Expand Down Expand Up @@ -260,6 +265,11 @@ if (!gotTheLock) {
initStoreManagers()
initOnlineMonitor()

// try to fix notification app name on windows
if (isWindows) {
app.setAppUserModelId('Heroic Games Launcher')
}

getSystemInfo().then((systemInfo) => {
if (systemInfo === '') return
logInfo(`\n\n${systemInfo}\n`, LogPrefix.Backend)
Expand Down
78 changes: 75 additions & 3 deletions src/backend/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,9 @@ import {
SteamRuntime,
Release,
GameInfo,
GameSettings
GameSettings,
State,
ProgressInfo
} from 'common/types'
import * as axios from 'axios'
import { app, dialog, shell, Notification, BrowserWindow } from 'electron'
Expand All @@ -36,7 +38,8 @@ import {
publicDir,
GITHUB_API,
isMac,
configStore
configStore,
isLinux
} from './constants'
import { logError, logInfo, LogPrefix, logWarning } from './logger/logger'
import { basename, dirname, join, normalize } from 'path'
Expand All @@ -60,6 +63,11 @@ import { GlobalConfig } from './config'
import { GameConfig } from './game_config'
import { validWine, runWineCommand } from './launcher'
import { gameManagerMap } from 'backend/storeManagers'
import {
installWineVersion,
updateWineVersionInfos,
wineDownloaderInfoStore
} from './wine/manager/utils'

const execAsync = promisify(exec)

Expand Down Expand Up @@ -225,8 +233,9 @@ const getGogdlVersion = async () => {

const getHeroicVersion = () => {
const VERSION_NUMBER = app.getVersion()
// One Piece reference
const BETA_VERSION_NAME = 'Caesar Clown'
const STABLE_VERSION_NAME = 'Eustass Kid'
const STABLE_VERSION_NAME = 'Nico Robin'
const isBetaorAlpha =
VERSION_NUMBER.includes('alpha') || VERSION_NUMBER.includes('beta')
const VERSION_NAME = isBetaorAlpha ? BETA_VERSION_NAME : STABLE_VERSION_NAME
Expand Down Expand Up @@ -942,6 +951,58 @@ async function ContinueWithFoundWine(
return { response }
}

export async function downloadDefaultWine() {
// refresh wine list
await updateWineVersionInfos(true)
// get list of wines on wineDownloaderInfoStore
const availableWine = wineDownloaderInfoStore.get('wine-releases', [])
// use Wine-GE type if on Linux and Wine-Crossover if on Mac
const release = availableWine.filter((version) => {
if (isLinux) {
return version.version.includes('Wine-GE-Proton')
} else if (isMac) {
return version.version.includes('Wine-Crossover')
}
return false
})[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
})
}
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 checkWineBeforeLaunch(
appName: string,
gameSettings: GameSettings,
Expand Down Expand Up @@ -985,6 +1046,17 @@ export async function checkWineBeforeLaunch(
const firstFoundWine = wineList[0]

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,
Expand Down
2 changes: 1 addition & 1 deletion src/backend/wine/manager/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import { toolsPath, isMac } from '../../constants'
import { sendFrontendMessage } from '../../main_window'
import { TypeCheckedStoreBackend } from 'backend/electron_store'

const wineDownloaderInfoStore = new TypeCheckedStoreBackend(
export const wineDownloaderInfoStore = new TypeCheckedStoreBackend(
'wineDownloaderInfoStore',
{
cwd: 'store',
Expand Down
14 changes: 13 additions & 1 deletion src/frontend/screens/Settings/components/WineVersionSelector.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import ContextProvider from 'frontend/state/ContextProvider'
import { WineInstallation } from 'common/types'
import useSetting from 'frontend/hooks/useSetting'
import { defaultWineVersion } from '..'
import { Link } from 'react-router-dom'

export default function WineVersionSelector() {
const { t } = useTranslation()
Expand All @@ -17,15 +18,18 @@ export default function WineVersionSelector() {
)
const [altWine, setAltWine] = useState<WineInstallation[]>([])
const [validWine, setValidWine] = useState(true)
const [refreshing, setRefreshing] = useState(true)

useEffect(() => {
const getAltWine = async () => {
setRefreshing(true)
const wineList: WineInstallation[] = await window.api.getAlternativeWine()
setAltWine(wineList)
// Avoids not updating wine config when having one wine install only
if (wineList && wineList.length === 1) {
setWineVersion(wineList[0])
}
setRefreshing(false)
}
getAltWine()
}, [])
Expand Down Expand Up @@ -57,7 +61,15 @@ export default function WineVersionSelector() {
value={wineVersion.name}
afterSelect={
<>
{!validWine && (
{!refreshing && !altWine.length && (
<Link to={'/wine-manager'} className="smallInputInfo danger">
{t(
'infobox.wine-path-none-found',
'No Wine version was found, download one from the Wine Manager'
)}
</Link>
)}
{!!altWine.length && !validWine && (
<span className="smallInputInfo danger">
{t(
'infobox.wine-path-invalid',
Expand Down
2 changes: 1 addition & 1 deletion src/frontend/screens/WineManager/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ export default React.memo(function WineManager(): JSX.Element | null {
return (
<>
<h4 style={{ paddingTop: 'var(--space-md)' }}>
{t('wine.manager.title', 'Wine Manager (Beta)')}
{t('wine.manager.title', 'Wine Manager')}
</h4>
<div className="wineManager">
<span className="tabsWrapper">
Expand Down