diff --git a/package.json b/package.json index a40faf6..6f9fde7 100644 --- a/package.json +++ b/package.json @@ -11,7 +11,7 @@ "check": "svelte-check --tsconfig ./tsconfig.json" }, "dependencies": { - "@empathize/framework": "^1.3.5", + "@empathize/framework": "^1.4.0", "js-md5": "^0.7.3", "semver": "^7.3.5", "svelte-i18n": "^3.3.13", diff --git a/src/components/ShadersSelection.svelte b/src/components/ShadersSelection.svelte index 88e440c..289b9d8 100644 --- a/src/components/ShadersSelection.svelte +++ b/src/components/ShadersSelection.svelte @@ -1,10 +1,11 @@
diff --git a/src/empathize.ts b/src/empathize.ts index 237b973..1512f39 100644 --- a/src/empathize.ts +++ b/src/empathize.ts @@ -9,7 +9,7 @@ import { Windows, // OS API - Process, Tray, IPC, Notification, Archive, + Process, Tray, IPC, Notification, Archive, Package, // Network API fetch, Domain, Downloader, @@ -42,7 +42,7 @@ export { Windows, // OS API - Process, Tray, IPC, Notification, Archive, + Process, Tray, IPC, Notification, Archive, Package, // Network API fetch, Domain, Downloader, diff --git a/src/settings.svelte b/src/settings.svelte index 52a2c96..1a514b6 100644 --- a/src/settings.svelte +++ b/src/settings.svelte @@ -6,7 +6,7 @@ import { onMount } from 'svelte'; import { _, locale, locales } from 'svelte-i18n'; - import { Windows, Configs, Debug, IPC, Process, path } from './empathize'; + import { Windows, Configs, Debug, IPC, Process, path, Package } from './empathize'; import constants from './ts/Constants'; import Launcher from './ts/Launcher'; @@ -86,7 +86,7 @@ tooltip: 'settings.enhancements.items.gamemode.tooltip.enabled' }; - Launcher.isPackageAvailable('gamemoderun').then((available) => { + Package.exists('gamemoderun').then((available) => { gamemode.disabled = !available; if (gamemode.disabled) diff --git a/src/ts/Game.ts b/src/ts/Game.ts index 0554cdb..18c8578 100644 --- a/src/ts/Game.ts +++ b/src/ts/Game.ts @@ -7,7 +7,7 @@ import type { import type { Stream as DownloadingStream } from '@empathize/framework/dist/network/Downloader'; -import { fetch, Domain, promisify, Downloader, Cache, Debug } from '../empathize'; +import { fetch, Domain, promisify, Downloader, Cache, Debug, Package } from '../empathize'; import { DebugThread } from '@empathize/framework/dist/meta/Debug'; import constants from './Constants'; @@ -246,31 +246,45 @@ export default class Game /** * Check if the telemetry servers are disabled + * + * @returns throws Error object when iputils package (ping command) is not available */ public static isTelemetryDisabled(): Promise { const debugThread = new DebugThread('Game.isTelemetryDisabled', 'Checking if the telemetry servers are disabled'); - return new Promise(async (resolve) => { - const pipeline = promisify({ - callbacks: await constants.uri.telemetry.map((domain) => { - return new Promise((resolve) => { - Domain.getInfo(domain).then((info) => resolve(info.available)); - }); - }), - callAtOnce: true, - interval: 500 - }); + return new Promise(async (resolve, reject) => { + // If ping command is not available - throw an error + if (!await Package.exists('ping')) + { + debugThread.log('iputils package is not installed'); - pipeline.then((result) => { - let disabled = false; + reject(new Error('iputils package is not installed')); + } - Object.values(result).forEach((value) => disabled ||= value as boolean); + // Otherwise - check if telemetry is disabled + else + { + const pipeline = promisify({ + callbacks: await constants.uri.telemetry.map((domain) => { + return new Promise((resolve) => { + Domain.getInfo(domain).then((info) => resolve(info.available)); + }); + }), + callAtOnce: true, + interval: 500 + }); - debugThread.log(`Telemetry is ${disabled ? 'not ' : ''}disabled`); + pipeline.then((result) => { + let disabled = false; - resolve(disabled === false); - }); + Object.values(result).forEach((value) => disabled ||= value as boolean); + + debugThread.log(`Telemetry is ${disabled ? 'not ' : ''}disabled`); + + resolve(disabled === false); + }); + } }); } } diff --git a/src/ts/Launcher.ts b/src/ts/Launcher.ts index 953ad2e..13288ec 100644 --- a/src/ts/Launcher.ts +++ b/src/ts/Launcher.ts @@ -154,45 +154,4 @@ export default class Launcher }); }); } - - /** - * Check if some binary file or package downloaded - */ - public static isPackageAvailable(name: string): Promise - { - return new Promise(async (resolve) => { - let available = false; - - let paths: string[] = (await Neutralino.os.getEnv('PATH')).split(':'); - - // Add "/usr/share" if it is not included - // because we use these paths to check if some library exists in system - if (!paths.includes('/usr/share')) - paths.push('/usr/share'); - - // Sort them by length because obviously - // "/usr/bin" more important than some randomly generated - // yaml or npm folder for its globally downloaded packages - paths = paths.sort((a, b) => a.length - b.length); - - for (const path of paths) - { - // Becasue await Neutralino.filesystem.getStats will throw an erro - // if the specified path doesn't exist - try - { - if (await Neutralino.filesystem.getStats(`${path}/${name}`)) - { - available = true; - - break; - } - } - - catch {} - } - - resolve(available); - }); - } }; diff --git a/src/ts/launcher/states/ApplyPatch.ts b/src/ts/launcher/states/ApplyPatch.ts index 04aaee8..92a3a4d 100644 --- a/src/ts/launcher/states/ApplyPatch.ts +++ b/src/ts/launcher/states/ApplyPatch.ts @@ -1,6 +1,7 @@ -import { Notification } from '../../../empathize'; +import { Notification, Package } from '../../../empathize'; + +import type Launcher from '../../Launcher'; -import Launcher from '../../Launcher'; import Patch from '../../Patch'; import constants from '../../Constants'; import Locales from '../Locales'; @@ -8,7 +9,7 @@ import Locales from '../Locales'; export default (launcher: Launcher): Promise => { return new Promise(async (resolve) => { // Show an error notification if xdelta3 package is not installed - if (!await Launcher.isPackageAvailable('xdelta3')) + if (!await Package.exists('xdelta3')) { Notification.show({ ...(Locales.translate('notifications.xdelta3_package_required') as { title: string, body: string }), diff --git a/src/ts/launcher/states/Launch.ts b/src/ts/launcher/states/Launch.ts index 6ad22e0..9e663b9 100644 --- a/src/ts/launcher/states/Launch.ts +++ b/src/ts/launcher/states/Launch.ts @@ -1,7 +1,8 @@ -import { Process, Windows, Configs, Notification, path } from '../../../empathize'; +import { Process, Windows, Configs, Notification, path, Package } from '../../../empathize'; import { DebugThread } from '@empathize/framework/dist/meta/Debug'; -import Launcher from '../../Launcher'; +import type Launcher from '../../Launcher'; + import constants from '../../Constants'; import Runners from '../../core/Runners'; import Game from '../../Game'; @@ -13,240 +14,235 @@ export default (launcher: Launcher): Promise => { return new Promise(async (resolve) => { const debugThread = new DebugThread('State/Launch', 'Starting the game'); - // Show an error notification if ping is not available - if (!await Launcher.isPackageAvailable('ping')) - { - Notification.show({ - ...(Locales.translate('notifications.iputils_package_required') as { title: string, body: string }), - icon: `${constants.paths.appDir}/public/images/baal64-transparent.png`, - importance: 'critical' - }); - - debugThread.log('iputils package is not installed!'); - - resolve(); - } - - const telemetry = await Game.isTelemetryDisabled(); - - // If telemetry servers are not disabled - if (!telemetry) - { - Notification.show({ - ...(Locales.translate('notifications.telemetry_not_disabled') as { title: string, body: string }), - icon: `${constants.paths.appDir}/public/images/baal64-transparent.png`, - importance: 'critical' - }); - - debugThread.log('Telemetry is not disabled!'); - } - - // Otherwise run the game - else - { - Windows.current.hide(); - - launcher.updateDiscordRPC('in-game'); - - launcher.tray.update([ - { text: 'Starting the game...', disabled: true } - ]); - - /** - * Selecting wine executable - */ - let wineExeutable = 'wine'; - - const runner = await Runners.current(); - - if (runner !== null) - wineExeutable = `${await constants.paths.runnersDir}/${runner.name}/${runner.files.wine}`; - - debugThread.log(`Wine executable path: ${wineExeutable}`); - - // Some special variables - let env: any = {}; - - /** - * HUD - */ - switch (await Configs.get('hud')) - { - case 'dxvk': - env['DXVK_HUD'] = 'fps,frametimes,version,gpuload'; - - break; - - case 'mangohud': - env['MANGOHUD'] = 1; - - break; - } - - /** - * Wine synchronizations - * - * @link https://github.com/AdelKS/LinuxGamingGuide#wine-tkg - */ - switch (await Configs.get('winesync')) - { - case 'esync': - env['WINEESYNC'] = 1; - - break; - - case 'fsync': - env['WINEESYNC'] = 1; - env['WINEFSYNC'] = 1; - - break; - } - - /** - * AMD FSR - */ - if (await Configs.get('fsr')) - { - env['WINE_FULLSCREEN_FSR'] = 1; - env['WINE_FULLSCREEN_FSR_STRENGTH'] = 3; - } - - /** - * Shaders - */ - const shaders = await Configs.get('shaders'); - - if (shaders !== 'none' && await Launcher.isPackageAvailable('reshade')) - { - const launcherShadersFile = `${await constants.paths.launcherDir}/vkBasalt.conf`; - - env['ENABLE_VKBASALT'] = 1; - env['VKBASALT_CONFIG_FILE'] = launcherShadersFile; - - if (shaders !== 'custom') + // Check if telemetry servers are disabled + Game.isTelemetryDisabled() + .then(async (telemetryDisabled) => { + // If telemetry servers are not disabled + if (!telemetryDisabled) { - const userShadersFile = `${constants.paths.shadersDir}/${shaders}/vkBasalt.conf`; - - await Neutralino.filesystem.writeFile(launcherShadersFile, await Neutralino.filesystem.readFile(userShadersFile)); - } - } + Notification.show({ + ...(Locales.translate('notifications.telemetry_not_disabled') as { title: string, body: string }), + icon: `${constants.paths.appDir}/public/images/baal64-transparent.png`, + importance: 'critical' + }); - /** - * GPU selection - */ - // TODO - /*if (LauncherLib.getConfig('gpu') != 'default') - { - const gpu = await SwitcherooControl.getGpuByName(LauncherLib.getConfig('gpu')); - - if (gpu) - { - env = { - ...env, - ...SwitcherooControl.getEnvAsObject(gpu) - }; + debugThread.log('Telemetry is not disabled!'); } - else console.warn(`GPU ${LauncherLib.getConfig('gpu')} not found. Launching on the default GPU`); - }*/ + // Otherwise run the game + else + { + Windows.current.hide(); - let command = `"${path.addSlashes(wineExeutable)}" ${await Configs.get('fps_unlocker') ? 'unlockfps.bat' : 'launcher.bat'}`; + launcher.updateDiscordRPC('in-game'); - /** - * Gamemode integration - */ - if (await Configs.get('gamemode') && await Launcher.isPackageAvailable('gamemoderun')) - command = `gamemoderun ${command}`; + launcher.tray.update([ + { text: 'Starting the game...', disabled: true } + ]); - /** - * Starting the game - */ - const startTime = Date.now(); + /** + * Selecting wine executable + */ + let wineExeutable = 'wine'; - const process = await Process.run(command, { - env: { - WINEPREFIX: await constants.paths.prefix.current, - ...env, - ...((await Configs.get('env') as object|null) ?? {}) - }, - cwd: await constants.paths.gameDir - }); + const runner = await Runners.current(); - // Game was started by the launcher.bat file - // so we just need to wait until AnimeGame.e process - // will be closed - process.finish(() => { - const processName = `${constants.placeholders.uppercase.first + constants.placeholders.uppercase.second}.e`; + if (runner !== null) + wineExeutable = `${await constants.paths.runnersDir}/${runner.name}/${runner.files.wine}`; - let closeGameCounter = 0; + debugThread.log(`Wine executable path: ${wineExeutable}`); - const waiter = async () => { - const processes: string = (await Neutralino.os.execCommand('ps -A')).stdOut; + // Some special variables + let env: any = {}; - // Game is still running - if (processes.includes(processName)) + /** + * HUD + */ + switch (await Configs.get('hud')) { - const playtime = Math.round((Date.now() - startTime) / 1000); + case 'dxvk': + env['DXVK_HUD'] = 'fps,frametimes,version,gpuload'; - let hours: string|number = Math.floor(playtime / 3600); - let minutes: string|number = Math.floor((playtime - hours * 3600) / 60); - let seconds: string|number = playtime - hours * 3600 - minutes * 60; + break; - if (hours < 10) - hours = `0${hours}`; + case 'mangohud': + env['MANGOHUD'] = 1; - if (minutes < 10) - minutes = `0${minutes}`; - - if (seconds < 10) - seconds = `0${seconds}`; - - // FIXME: tray doesn't work in AppImage - launcher.tray.update([ - { text: `Playing for ${hours}:${minutes}:${seconds}`, disabled: true }, - { - text: `Close game${closeGameCounter > 0 ? ` (${closeGameCounter})` : ''}`, - - click: () => Neutralino.os.execCommand(`kill ${++closeGameCounter < 3 ? '-15' : '-9'} $(pidof ${processName})`) - } - ]); - - setTimeout(waiter, 3000); + break; } - // Game was closed - else + /** + * Wine synchronizations + * + * @link https://github.com/AdelKS/LinuxGamingGuide#wine-tkg + */ + switch (await Configs.get('winesync')) { - const stopTime = Date.now(); + case 'esync': + env['WINEESYNC'] = 1; - Windows.current.show(); - // FIXME: Windows.current.center(1280, 700); + break; - launcher.updateDiscordRPC('in-launcher'); - launcher.tray.hide(); + case 'fsync': + env['WINEESYNC'] = 1; + env['WINEFSYNC'] = 1; - // Purge game logs - Configs.get('purge_logs.game').then(async (purge_logs) => { - if (purge_logs) - { - const gameDir = path.addSlashes(await constants.paths.gameDir); - - // Delete .log files (e.g. "ZFGameBrowser_xxxx.log") - Neutralino.os.execCommand(`find "${gameDir}" -maxdepth 1 -type f -name "*.log" -delete`); - - // Delete .dmp files (e.g. "DumpFile-zfbrowser-xxxxxx.dmp") - Neutralino.os.execCommand(`find "${gameDir}" -maxdepth 1 -type f -name "*.dmp" -delete`); - } - }); - - // TODO - - resolve(); + break; } - }; - setTimeout(waiter, 5000); + /** + * AMD FSR + */ + if (await Configs.get('fsr')) + { + env['WINE_FULLSCREEN_FSR'] = 1; + env['WINE_FULLSCREEN_FSR_STRENGTH'] = 3; + } + + /** + * Shaders + */ + const shaders = await Configs.get('shaders'); + + if (shaders !== 'none' && await Package.exists('reshade')) + { + const launcherShadersFile = `${await constants.paths.launcherDir}/vkBasalt.conf`; + + env['ENABLE_VKBASALT'] = 1; + env['VKBASALT_CONFIG_FILE'] = launcherShadersFile; + + if (shaders !== 'custom') + { + const userShadersFile = `${constants.paths.shadersDir}/${shaders}/vkBasalt.conf`; + + await Neutralino.filesystem.writeFile(launcherShadersFile, await Neutralino.filesystem.readFile(userShadersFile)); + } + } + + /** + * GPU selection + */ + // TODO + /*if (LauncherLib.getConfig('gpu') != 'default') + { + const gpu = await SwitcherooControl.getGpuByName(LauncherLib.getConfig('gpu')); + + if (gpu) + { + env = { + ...env, + ...SwitcherooControl.getEnvAsObject(gpu) + }; + } + + else console.warn(`GPU ${LauncherLib.getConfig('gpu')} not found. Launching on the default GPU`); + }*/ + + let command = `"${path.addSlashes(wineExeutable)}" ${await Configs.get('fps_unlocker') ? 'unlockfps.bat' : 'launcher.bat'}`; + + /** + * Gamemode integration + */ + if (await Configs.get('gamemode') && await Package.exists('gamemoderun')) + command = `gamemoderun ${command}`; + + /** + * Starting the game + */ + const startTime = Date.now(); + + const process = await Process.run(command, { + env: { + WINEPREFIX: await constants.paths.prefix.current, + ...env, + ...((await Configs.get('env') as object|null) ?? {}) + }, + cwd: await constants.paths.gameDir + }); + + // Game was started by the launcher.bat file + // so we just need to wait until AnimeGame.e process + // will be closed + process.finish(() => { + const processName = `${constants.placeholders.uppercase.first + constants.placeholders.uppercase.second}.e`; + + let closeGameCounter = 0; + + const waiter = async () => { + const processes: string = (await Neutralino.os.execCommand('ps -A')).stdOut; + + // Game is still running + if (processes.includes(processName)) + { + const playtime = Math.round((Date.now() - startTime) / 1000); + + let hours: string|number = Math.floor(playtime / 3600); + let minutes: string|number = Math.floor((playtime - hours * 3600) / 60); + let seconds: string|number = playtime - hours * 3600 - minutes * 60; + + if (hours < 10) + hours = `0${hours}`; + + if (minutes < 10) + minutes = `0${minutes}`; + + if (seconds < 10) + seconds = `0${seconds}`; + + // FIXME: tray doesn't work in AppImage + launcher.tray.update([ + { text: `Playing for ${hours}:${minutes}:${seconds}`, disabled: true }, + { + text: `Close game${closeGameCounter > 0 ? ` (${closeGameCounter})` : ''}`, + + click: () => Neutralino.os.execCommand(`kill ${++closeGameCounter < 3 ? '-15' : '-9'} $(pidof ${processName})`) + } + ]); + + setTimeout(waiter, 3000); + } + + // Game was closed + else + { + const stopTime = Date.now(); + + Windows.current.show(); + // FIXME: Windows.current.center(1280, 700); + + launcher.updateDiscordRPC('in-launcher'); + launcher.tray.hide(); + + // Purge game logs + Configs.get('purge_logs.game').then(async (purge_logs) => { + if (purge_logs) + { + const gameDir = path.addSlashes(await constants.paths.gameDir); + + // Delete .log files (e.g. "ZFGameBrowser_xxxx.log") + Neutralino.os.execCommand(`find "${gameDir}" -maxdepth 1 -type f -name "*.log" -delete`); + + // Delete .dmp files (e.g. "DumpFile-zfbrowser-xxxxxx.dmp") + Neutralino.os.execCommand(`find "${gameDir}" -maxdepth 1 -type f -name "*.dmp" -delete`); + } + }); + + // TODO + + resolve(); + } + }; + + setTimeout(waiter, 5000); + }); + } + }) + .catch(() => { + Notification.show({ + ...(Locales.translate('notifications.iputils_package_required') as { title: string, body: string }), + icon: `${constants.paths.appDir}/public/images/baal64-transparent.png`, + importance: 'critical' + }); }); - } }); };