API improvements

- updated empathize to 1.4.0;
  due to it was removed `Launcher.isPackageAvailable()` method
  which now included in empathize as `Package.exists()`
- `Game.isTelemetryDisabled()` now rejects an Error object
  when `iputils` package (`ping` command) is not installed
- due to the change above was slightly rewritten `Launch.ts` script
This commit is contained in:
Observer KRypt0n_ 2022-01-29 13:27:33 +02:00
parent 1808cdf39f
commit fcc67af03a
No known key found for this signature in database
GPG key ID: DC5D4EC1303465DA
8 changed files with 251 additions and 280 deletions

View file

@ -11,7 +11,7 @@
"check": "svelte-check --tsconfig ./tsconfig.json" "check": "svelte-check --tsconfig ./tsconfig.json"
}, },
"dependencies": { "dependencies": {
"@empathize/framework": "^1.3.5", "@empathize/framework": "^1.4.0",
"js-md5": "^0.7.3", "js-md5": "^0.7.3",
"semver": "^7.3.5", "semver": "^7.3.5",
"svelte-i18n": "^3.3.13", "svelte-i18n": "^3.3.13",

View file

@ -1,10 +1,11 @@
<script lang="ts"> <script lang="ts">
import { _, locale } from 'svelte-i18n'; import { _, locale } from 'svelte-i18n';
import { Package } from '../empathize';
import type { Shader } from '../ts/types/Shaders'; import type { Shader } from '../ts/types/Shaders';
import Shaders from '../ts/launcher/Shaders'; import Shaders from '../ts/launcher/Shaders';
import Launcher from '../ts/Launcher';
import SelectionBox from './SelectionBox.svelte'; import SelectionBox from './SelectionBox.svelte';
@ -23,7 +24,7 @@
shadersOptions['custom'] = 'settings.shaders.items.shaders.items.custom'; shadersOptions['custom'] = 'settings.shaders.items.shaders.items.custom';
}); });
Launcher.isPackageAvailable('reshade').then((available) => reshadeInstalled = available); Package.exists('reshade').then((available) => reshadeInstalled = available);
</script> </script>
<div> <div>

View file

@ -9,7 +9,7 @@ import {
Windows, Windows,
// OS API // OS API
Process, Tray, IPC, Notification, Archive, Process, Tray, IPC, Notification, Archive, Package,
// Network API // Network API
fetch, Domain, Downloader, fetch, Domain, Downloader,
@ -42,7 +42,7 @@ export {
Windows, Windows,
// OS API // OS API
Process, Tray, IPC, Notification, Archive, Process, Tray, IPC, Notification, Archive, Package,
// Network API // Network API
fetch, Domain, Downloader, fetch, Domain, Downloader,

View file

@ -6,7 +6,7 @@
import { onMount } from 'svelte'; import { onMount } from 'svelte';
import { _, locale, locales } from 'svelte-i18n'; 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 constants from './ts/Constants';
import Launcher from './ts/Launcher'; import Launcher from './ts/Launcher';
@ -86,7 +86,7 @@
tooltip: 'settings.enhancements.items.gamemode.tooltip.enabled' tooltip: 'settings.enhancements.items.gamemode.tooltip.enabled'
}; };
Launcher.isPackageAvailable('gamemoderun').then((available) => { Package.exists('gamemoderun').then((available) => {
gamemode.disabled = !available; gamemode.disabled = !available;
if (gamemode.disabled) if (gamemode.disabled)

View file

@ -7,7 +7,7 @@ import type {
import type { Stream as DownloadingStream } from '@empathize/framework/dist/network/Downloader'; 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 { DebugThread } from '@empathize/framework/dist/meta/Debug';
import constants from './Constants'; import constants from './Constants';
@ -246,31 +246,45 @@ export default class Game
/** /**
* Check if the telemetry servers are disabled * Check if the telemetry servers are disabled
*
* @returns throws Error object when iputils package (ping command) is not available
*/ */
public static isTelemetryDisabled(): Promise<boolean> public static isTelemetryDisabled(): Promise<boolean>
{ {
const debugThread = new DebugThread('Game.isTelemetryDisabled', 'Checking if the telemetry servers are disabled'); const debugThread = new DebugThread('Game.isTelemetryDisabled', 'Checking if the telemetry servers are disabled');
return new Promise(async (resolve) => { return new Promise(async (resolve, reject) => {
const pipeline = promisify({ // If ping command is not available - throw an error
callbacks: await constants.uri.telemetry.map((domain) => { if (!await Package.exists('ping'))
return new Promise((resolve) => { {
Domain.getInfo(domain).then((info) => resolve(info.available)); debugThread.log('iputils package is not installed');
});
}),
callAtOnce: true,
interval: 500
});
pipeline.then((result) => { reject(new Error('iputils package is not installed'));
let disabled = false; }
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);
});
}
}); });
} }
} }

View file

@ -154,45 +154,4 @@ export default class Launcher
}); });
}); });
} }
/**
* Check if some binary file or package downloaded
*/
public static isPackageAvailable(name: string): Promise<boolean>
{
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);
});
}
}; };

View file

@ -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 Patch from '../../Patch';
import constants from '../../Constants'; import constants from '../../Constants';
import Locales from '../Locales'; import Locales from '../Locales';
@ -8,7 +9,7 @@ import Locales from '../Locales';
export default (launcher: Launcher): Promise<void> => { export default (launcher: Launcher): Promise<void> => {
return new Promise(async (resolve) => { return new Promise(async (resolve) => {
// Show an error notification if xdelta3 package is not installed // Show an error notification if xdelta3 package is not installed
if (!await Launcher.isPackageAvailable('xdelta3')) if (!await Package.exists('xdelta3'))
{ {
Notification.show({ Notification.show({
...(Locales.translate('notifications.xdelta3_package_required') as { title: string, body: string }), ...(Locales.translate('notifications.xdelta3_package_required') as { title: string, body: string }),

View file

@ -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 { DebugThread } from '@empathize/framework/dist/meta/Debug';
import Launcher from '../../Launcher'; import type Launcher from '../../Launcher';
import constants from '../../Constants'; import constants from '../../Constants';
import Runners from '../../core/Runners'; import Runners from '../../core/Runners';
import Game from '../../Game'; import Game from '../../Game';
@ -13,240 +14,235 @@ export default (launcher: Launcher): Promise<void> => {
return new Promise(async (resolve) => { return new Promise(async (resolve) => {
const debugThread = new DebugThread('State/Launch', 'Starting the game'); const debugThread = new DebugThread('State/Launch', 'Starting the game');
// Show an error notification if ping is not available // Check if telemetry servers are disabled
if (!await Launcher.isPackageAvailable('ping')) Game.isTelemetryDisabled()
{ .then(async (telemetryDisabled) => {
Notification.show({ // If telemetry servers are not disabled
...(Locales.translate('notifications.iputils_package_required') as { title: string, body: string }), if (!telemetryDisabled)
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')
{ {
const userShadersFile = `${constants.paths.shadersDir}/${shaders}/vkBasalt.conf`; Notification.show({
...(Locales.translate('notifications.telemetry_not_disabled') as { title: string, body: string }),
await Neutralino.filesystem.writeFile(launcherShadersFile, await Neutralino.filesystem.readFile(userShadersFile)); icon: `${constants.paths.appDir}/public/images/baal64-transparent.png`,
} importance: 'critical'
} });
/** debugThread.log('Telemetry is not disabled!');
* 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`); // 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');
/** launcher.tray.update([
* Gamemode integration { text: 'Starting the game...', disabled: true }
*/ ]);
if (await Configs.get('gamemode') && await Launcher.isPackageAvailable('gamemoderun'))
command = `gamemoderun ${command}`;
/** /**
* Starting the game * Selecting wine executable
*/ */
const startTime = Date.now(); let wineExeutable = 'wine';
const process = await Process.run(command, { const runner = await Runners.current();
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 if (runner !== null)
// so we just need to wait until AnimeGame.e process wineExeutable = `${await constants.paths.runnersDir}/${runner.name}/${runner.files.wine}`;
// will be closed
process.finish(() => {
const processName = `${constants.placeholders.uppercase.first + constants.placeholders.uppercase.second}.e`;
let closeGameCounter = 0; debugThread.log(`Wine executable path: ${wineExeutable}`);
const waiter = async () => { // Some special variables
const processes: string = (await Neutralino.os.execCommand('ps -A')).stdOut; 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); break;
let minutes: string|number = Math.floor((playtime - hours * 3600) / 60);
let seconds: string|number = playtime - hours * 3600 - minutes * 60;
if (hours < 10) case 'mangohud':
hours = `0${hours}`; env['MANGOHUD'] = 1;
if (minutes < 10) break;
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 * 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(); break;
// FIXME: Windows.current.center(1280, 700);
launcher.updateDiscordRPC('in-launcher'); case 'fsync':
launcher.tray.hide(); env['WINEESYNC'] = 1;
env['WINEFSYNC'] = 1;
// Purge game logs break;
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); /**
* 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'
});
}); });
}
}); });
}; };