diff --git a/README.md b/README.md index acd76f1..fbeceff 100644 --- a/README.md +++ b/README.md @@ -169,11 +169,11 @@ This is our current roadmap goals. You can find older ones [here](ROADMAP.md) * Splash screen * Theming system * Game pre-installation -* Launcher auto-updates +* Default runner and DXVK auto-installation * Statistics window -* Chengelog window -* Default runner auto-installation * Ability to change the temp directory where the launcher should download some files +* Launcher auto-updates +* Changelog window ### Features diff --git a/public/runners.json b/public/runners.json index cf1093c..42ce4cc 100644 --- a/public/runners.json +++ b/public/runners.json @@ -1,4 +1,69 @@ [ + { + "title": "Wine-GE", + "runners": [ + { + "family": "Wine-GE", + "name": "lutris-ge-6.21-1-x86_64", + "title": "Wine-6.21-GE-1", + "uri": "https://github.com/GloriousEggroll/wine-ge-custom/releases/download/6.21-GE-1/wine-lutris-ge-6.21-1-x86_64.tar.xz", + "files": { + "wine": "bin/wine64", + "wineserver": "bin/wineserver", + "winecfg": "lib64/wine/x86_64-windows/winecfg.exe" + }, + "recommended": true + }, + { + "family": "Wine-GE", + "name": "lutris-ge-6.20-1-x86_64", + "title": "Wine-6.20-GE-1", + "uri": "https://github.com/GloriousEggroll/wine-ge-custom/releases/download/6.20-GE-1/wine-lutris-ge-6.20-1-x86_64.tar.xz", + "files": { + "wine": "bin/wine64", + "wineserver": "bin/wineserver", + "winecfg": "lib64/wine/x86_64-windows/winecfg.exe" + }, + "recommended": true + }, + { + "family": "Wine-GE", + "name": "lutris-ge-6.19-1-x86_64", + "title": "Wine-6.19-GE-1", + "uri": "https://github.com/GloriousEggroll/wine-ge-custom/releases/download/6.19-GE-1/wine-lutris-ge-6.19-1-x86_64.tar.xz", + "files": { + "wine": "bin/wine64", + "wineserver": "bin/wineserver", + "winecfg": "lib64/wine/x86_64-windows/winecfg.exe" + }, + "recommended": true + }, + { + "family": "Wine-GE", + "name": "lutris-ge-6.18-1-x86_64", + "title": "Wine-6.18-GE-1", + "uri": "https://github.com/GloriousEggroll/wine-ge-custom/releases/download/6.18-GE-1/wine-lutris-ge-6.18-1-x86_64.tar.xz", + "files": { + "wine": "bin/wine64", + "wineserver": "bin/wineserver", + "winecfg": "lib64/wine/x86_64-windows/winecfg.exe" + }, + "recommended": true + }, + { + "family": "Wine-GE", + "name": "lutris-ge-6.16-1-x86_64", + "title": "Wine-6.16-GE-1", + "uri": "https://github.com/GloriousEggroll/wine-ge-custom/releases/download/6.16-GE-1/lutris-ge-6.16-1-x86_64.tar.xz", + "files": { + "wine": "bin/wine64", + "wineserver": "bin/wineserver", + "winecfg": "lib64/wine/x86_64-windows/winecfg.exe" + }, + "recommended": true + } + ] + }, { "title": "Lutris", "runners": [ @@ -159,70 +224,5 @@ "recommended": false } ] - }, - { - "title": "Wine-GE", - "runners": [ - { - "family": "Wine-GE", - "name": "lutris-ge-6.21-1-x86_64", - "title": "Wine-6.21-GE-1", - "uri": "https://github.com/GloriousEggroll/wine-ge-custom/releases/download/6.21-GE-1/wine-lutris-ge-6.21-1-x86_64.tar.xz", - "files": { - "wine": "bin/wine64", - "wineserver": "bin/wineserver", - "winecfg": "lib64/wine/x86_64-windows/winecfg.exe" - }, - "recommended": true - }, - { - "family": "Wine-GE", - "name": "lutris-ge-6.20-1-x86_64", - "title": "Wine-6.20-GE-1", - "uri": "https://github.com/GloriousEggroll/wine-ge-custom/releases/download/6.20-GE-1/wine-lutris-ge-6.20-1-x86_64.tar.xz", - "files": { - "wine": "bin/wine64", - "wineserver": "bin/wineserver", - "winecfg": "lib64/wine/x86_64-windows/winecfg.exe" - }, - "recommended": true - }, - { - "family": "Wine-GE", - "name": "lutris-ge-6.19-1-x86_64", - "title": "Wine-6.19-GE-1", - "uri": "https://github.com/GloriousEggroll/wine-ge-custom/releases/download/6.19-GE-1/wine-lutris-ge-6.19-1-x86_64.tar.xz", - "files": { - "wine": "bin/wine64", - "wineserver": "bin/wineserver", - "winecfg": "lib64/wine/x86_64-windows/winecfg.exe" - }, - "recommended": false - }, - { - "family": "Wine-GE", - "name": "lutris-ge-6.18-1-x86_64", - "title": "Wine-6.18-GE-1", - "uri": "https://github.com/GloriousEggroll/wine-ge-custom/releases/download/6.18-GE-1/wine-lutris-ge-6.18-1-x86_64.tar.xz", - "files": { - "wine": "bin/wine64", - "wineserver": "bin/wineserver", - "winecfg": "lib64/wine/x86_64-windows/winecfg.exe" - }, - "recommended": false - }, - { - "family": "Wine-GE", - "name": "lutris-ge-6.16-1-x86_64", - "title": "Wine-6.16-GE-1", - "uri": "https://github.com/GloriousEggroll/wine-ge-custom/releases/download/6.16-GE-1/lutris-ge-6.16-1-x86_64.tar.xz", - "files": { - "wine": "bin/wine64", - "wineserver": "bin/wineserver", - "winecfg": "lib64/wine/x86_64-windows/winecfg.exe" - }, - "recommended": false - } - ] } ] diff --git a/src/components/RunnerSelectionList.svelte b/src/components/RunnerSelectionList.svelte index 8f5d4c1..5fbb15b 100644 --- a/src/components/RunnerSelectionList.svelte +++ b/src/components/RunnerSelectionList.svelte @@ -47,6 +47,10 @@ disabledRunners[runner.name] = false; progress[runner.name] = undefined; + + selectedVersion = runner.name; + + Runners.current(runner); }); }); }; diff --git a/src/sass/index.sass b/src/sass/index.sass index d2384ab..8c1f4c9 100644 --- a/src/sass/index.sass +++ b/src/sass/index.sass @@ -41,7 +41,7 @@ height: 64px right: 128px - bottom: 64px + bottom: 48px font-size: 22px @@ -53,7 +53,7 @@ height: 52px right: 386px - bottom: 70px + bottom: 54px border-radius: 32px @@ -98,7 +98,7 @@ position: absolute left: 48px - bottom: 116px + bottom: 100px color: white font-size: 18px @@ -119,7 +119,7 @@ padding: 0 left: 48px - bottom: 68px + bottom: 52px width: 720px height: 36px diff --git a/src/ts/core/AbstractInstaller.ts b/src/ts/core/AbstractInstaller.ts index fb096da..a973d2c 100644 --- a/src/ts/core/AbstractInstaller.ts +++ b/src/ts/core/AbstractInstaller.ts @@ -114,13 +114,7 @@ export default abstract class Installer if (this.onDownloadFinish) this.onDownloadFinish(); - Promise.resolve(unpackDir) - .then((unpackDir) => { - if (shouldResolve) - debugThread.log(`Resolved unpack dir: ${unpackDir}`); - - unpackArchive(); - }); + unpackArchive(); }); }); } diff --git a/src/ts/core/Prefix.ts b/src/ts/core/Prefix.ts index 312635e..2ec01ed 100644 --- a/src/ts/core/Prefix.ts +++ b/src/ts/core/Prefix.ts @@ -104,7 +104,7 @@ export default class Prefix this.getWinetricks().then(async (winetricks) => { let installationProgress = 0; - const process = await Process.run(`./'${Process.addSlashes(winetricks)}' corefonts usetakefocus=n`, { + const process = await Process.run(`'${Process.addSlashes(winetricks)}' corefonts usetakefocus=n`, { env: { WINE: `${await constants.paths.runnersDir}/${runner.name}/${runner.files.wine}`, WINESERVER: `${await constants.paths.runnersDir}/${runner.name}/${runner.files.wineserver}`, diff --git a/src/ts/launcher/State.ts b/src/ts/launcher/State.ts index 1a8bbe4..36cef7b 100644 --- a/src/ts/launcher/State.ts +++ b/src/ts/launcher/State.ts @@ -6,6 +6,9 @@ import Window from '../neutralino/Window'; import Game from '../Game'; import Patch from '../Patch'; import Voice from '../Voice'; +import Runners from '../core/Runners'; +import { DebugThread } from '../core/Debug'; +import DXVK from '../core/DXVK'; declare const Neutralino; @@ -15,15 +18,17 @@ export default class State public launchButton: HTMLElement; public predownloadButton: HTMLElement; + public settingsButton: HTMLElement; protected _state: LauncherState = 'game-launch-available'; protected events = { + 'runner-installation-required': import('./states/InstallWine'), + 'dxvk-installation-required': import('./states/InstallDXVK'), 'game-launch-available': import('./states/Launch'), 'game-installation-available': import('./states/Install'), 'game-update-available': import('./states/Install'), - 'game-voice-update-required': import('./states/InstallVoice'), 'test-patch-available': import('./states/ApplyPatch'), @@ -36,16 +41,19 @@ export default class State this.launchButton = document.getElementById('launch'); this.predownloadButton = document.getElementById('predownload'); + this.settingsButton = document.getElementById('settings'); this.launchButton.onclick = () => { if (this.events[this._state]) { this.launchButton.style['display'] = 'none'; + this.settingsButton.style['display'] = 'none'; this.events[this._state].then((event) => { event.default(this.launcher).then(() => { this.update().then(() => { this.launchButton.style['display'] = 'block'; + this.settingsButton.style['display'] = 'block'; }); }); }); @@ -55,6 +63,7 @@ export default class State this.predownloadButton.onclick = () => { this.launchButton.style['display'] = 'none'; this.predownloadButton.style['display'] = 'none'; + this.settingsButton.style['display'] = 'none'; const module = this._state === 'game-pre-installation-available' ? 'Predownload' : 'PredownloadVoice'; @@ -63,6 +72,7 @@ export default class State module.default(this.launcher).then(() => { this.update().then(() => { this.launchButton.style['display'] = 'block'; + this.settingsButton.style['display'] = 'block'; }); }); }); @@ -95,6 +105,16 @@ export default class State switch(state) { + case 'runner-installation-required': + this.launchButton.textContent = 'Install wine'; + + break; + + case 'dxvk-installation-required': + this.launchButton.textContent = 'Install DXVK'; + + break; + case 'game-launch-available': this.launchButton.textContent = 'Launch'; @@ -148,42 +168,120 @@ export default class State */ public update(): Promise { + const debugThread = new DebugThread('State.update', 'Updating launcher state'); + return new Promise(async (resolve) => { - let state: LauncherState; + let state: LauncherState|null = null; - const gameCurrent = await Game.current; - const gameLatest = await Game.getLatestData(); - const patch = await Patch.latest; - const voiceData = await Voice.current; + const runner = await Runners.current(); + const dxvk = await DXVK.current(); - if (gameCurrent === null) - state = 'game-installation-available'; - - else if (gameCurrent != gameLatest.game.latest.version) - state = 'game-update-available'; - - // TODO: update this thing if the user selected another voice language - else if (voiceData.installed.length === 0) - state = 'game-voice-update-required'; - - else if (!patch.applied) + // Check if the wine is installed + if (runner === null) { - state = patch.state == 'preparation' ? - 'patch-unavailable' : (patch.state == 'testing' ? - 'test-patch-available' : 'patch-available'); + debugThread.log('Runner is not specified'); + + state = 'runner-installation-required'; + + Runners.list().then((list) => { + for (const family of list) + for (const runner of family.runners) + if (runner.installed && runner.recommended) + { + debugThread.log(`Automatically selected runner ${runner.title} (${runner.name})`); + + state = null; + + Runners.current(runner).then(() => { + this.update().then(resolve); + }); + + return; + } + }); + + if (state !== null) + { + debugThread.log('No recommended runner installed'); + + this.set(state); + + resolve(state); + } } - else if (gameLatest.pre_download_game && !await Game.isUpdatePredownloaded()) - state = 'game-pre-installation-available'; + // Check if the DXVK is installed + else if (dxvk === null) + { + debugThread.log('DXVK is not specified'); - else if (gameLatest.pre_download_game && !await Voice.isUpdatePredownloaded(await Voice.selected)) - state = 'game-voice-pre-installation-available'; + state = 'dxvk-installation-required'; - else state = 'game-launch-available'; + DXVK.list().then((list) => { + for (const dxvk of list) + if (dxvk.installed && dxvk.recommended) + { + debugThread.log(`Automatically selected DXVK ${dxvk.version}`); - this.set(state); + state = null; - resolve(state); + DXVK.current(dxvk).then(() => { + this.update().then(resolve); + }); + + return; + } + }); + + if (state !== null) + { + debugThread.log('No recommended DXVK installed'); + + this.set(state); + + resolve(state); + } + } + + // Otherwise select some launcher state + else + { + const gameCurrent = await Game.current; + const gameLatest = await Game.getLatestData(); + const patch = await Patch.latest; + const voiceData = await Voice.current; + + if (gameCurrent === null) + state = 'game-installation-available'; + + else if (gameCurrent != gameLatest.game.latest.version) + state = 'game-update-available'; + + // TODO: update this thing if the user selected another voice language + else if (voiceData.installed.length === 0) + state = 'game-voice-update-required'; + + else if (!patch.applied) + { + state = patch.state == 'preparation' ? + 'patch-unavailable' : (patch.state == 'testing' ? + 'test-patch-available' : 'patch-available'); + } + + else if (gameLatest.pre_download_game && !await Game.isUpdatePredownloaded()) + state = 'game-pre-installation-available'; + + else if (gameLatest.pre_download_game && !await Voice.isUpdatePredownloaded(await Voice.selected)) + state = 'game-voice-pre-installation-available'; + + else state = 'game-launch-available'; + + debugThread.log(`Updated state: ${state}`); + + this.set(state); + + resolve(state); + } }); } }; diff --git a/src/ts/launcher/states/CreatePrefix.ts b/src/ts/launcher/states/CreatePrefix.ts index eec3f42..790e12c 100644 --- a/src/ts/launcher/states/CreatePrefix.ts +++ b/src/ts/launcher/states/CreatePrefix.ts @@ -7,7 +7,7 @@ export default (launcher: Launcher): Promise => { return new Promise(async (resolve) => { const prefixDir = await constants.paths.prefix.current; - Prefix.exists().then((exists) => { + Prefix.exists(prefixDir).then((exists) => { if (exists) resolve(); @@ -28,23 +28,12 @@ export default (launcher: Launcher): Promise => { Prefix.create(prefixDir, (output, current, total) => { progressLabel = output; - if (progressLabel.length > 70) - progressLabel = progressLabel.substring(0, 70) + '...'; + if (progressLabel.length > 80) + progressLabel = progressLabel.substring(0, 80) + '...'; launcher.progressBar!.update(current, total, 1); }) - .then((result) => { - if (result === true) - resolve(); - - else - { - // TODO - console.error('There\'s no wine version installed to use to create the prefix'); - - resolve(); - } - }); + .then(() => resolve()); } }); }); diff --git a/src/ts/launcher/states/InstallDXVK.ts b/src/ts/launcher/states/InstallDXVK.ts new file mode 100644 index 0000000..59adb0f --- /dev/null +++ b/src/ts/launcher/states/InstallDXVK.ts @@ -0,0 +1,56 @@ +import type Launcher from '../../Launcher'; + +import DXVK from '../../core/DXVK'; +import constants from '../../Constants'; + +export default (launcher: Launcher): Promise => { + return new Promise(async (resolve) => { + // Create prefix if it is not created + import('./CreatePrefix').then((module) => { + module.default(launcher).then(() => { + // And then download the DXVK + DXVK.download('1.9.2').then((stream) => { + launcher.progressBar?.init({ + label: 'Downloading DXVK 1.9.2...', + showSpeed: true, + showEta: true, + showPercents: true, + showTotals: true + }); + + stream?.downloadStart(() => launcher.progressBar?.show()); + + stream?.downloadProgress((current: number, total: number, difference: number) => { + launcher.progressBar?.update(current, total, difference); + }); + + let unpacking = true; + + stream?.unpackStart(() => { + launcher.progressBar?.init({ + label: () => unpacking ? 'Unpacking DXVK 1.9.2...' : 'Applying DXVK 1.9.2...', + showSpeed: true, + showEta: true, + showPercents: true, + showTotals: true + }); + }); + + stream?.unpackProgress((current: number, total: number, difference: number) => { + launcher.progressBar?.update(current, total, difference); + }); + + stream?.unpackFinish(async () => { + unpacking = true; + + DXVK.apply(await constants.paths.prefix.current, '1.9.2').then(() => { + launcher.progressBar?.hide(); + + resolve(); + }); + }); + }); + }); + }); + }); +}; diff --git a/src/ts/launcher/states/InstallWine.ts b/src/ts/launcher/states/InstallWine.ts new file mode 100644 index 0000000..36cb190 --- /dev/null +++ b/src/ts/launcher/states/InstallWine.ts @@ -0,0 +1,57 @@ +import type Launcher from '../../Launcher'; + +import Runners from '../../core/Runners'; +import DXVK from '../../core/DXVK'; + +export default (launcher: Launcher): Promise => { + return new Promise(async (resolve) => { + Runners.download('lutris-ge-6.21-1-x86_64').then((stream) => { + launcher.progressBar?.init({ + label: 'Downloading Wine-GE 6.21-1...', + showSpeed: true, + showEta: true, + showPercents: true, + showTotals: true + }); + + stream?.downloadStart(() => launcher.progressBar?.show()); + + stream?.downloadProgress((current: number, total: number, difference: number) => { + launcher.progressBar?.update(current, total, difference); + }); + + stream?.unpackStart(() => { + launcher.progressBar?.init({ + label: 'Unpacking Wine-GE 6.21-1...', + showSpeed: true, + showEta: true, + showPercents: true, + showTotals: true + }); + }); + + stream?.unpackProgress((current: number, total: number, difference: number) => { + launcher.progressBar?.update(current, total, difference); + }); + + stream?.unpackFinish(() => { + // Create prefix if it is not created + import('./CreatePrefix').then((module) => { + module.default(launcher).then(() => { + // Download DXVK if it wasn't downloaded + DXVK.current().then((dxvk) => { + if (dxvk === null) + { + import('./InstallDXVK').then((module) => { + module.default(launcher).then(() => resolve()); + }); + } + + else resolve(); + }); + }); + }); + }); + }); + }); +}; diff --git a/src/ts/launcher/states/Launch.ts b/src/ts/launcher/states/Launch.ts index 52bb7bd..1bebf85 100644 --- a/src/ts/launcher/states/Launch.ts +++ b/src/ts/launcher/states/Launch.ts @@ -144,7 +144,7 @@ export default (): Promise => { Window.current.show(); - // todo + // TODO resolve(); }); diff --git a/src/ts/types/Launcher.ts b/src/ts/types/Launcher.ts index 1597759..0409298 100644 --- a/src/ts/types/Launcher.ts +++ b/src/ts/types/Launcher.ts @@ -13,6 +13,8 @@ */ type LauncherState = + | 'runner-installation-required' + | 'dxvk-installation-required' | 'patch-unavailable' | 'test-patch-available' | 'patch-available'