From 049032a771b04957bf941208563100cce23622f6 Mon Sep 17 00:00:00 2001 From: Observer KRypt0n_ <suimin.tu.mu.ga.mi@gmail.com> Date: Fri, 28 Jan 2022 19:37:44 +0200 Subject: [PATCH] Moved launcher on empathize --- package.json | 5 +- src/components/Checkbox.svelte | 4 +- src/components/DiscordSettings.svelte | 2 +- src/components/DropdownCheckboxes.svelte | 2 +- src/components/EnvironmentManager.svelte | 57 ++-- src/components/SelectionBox.svelte | 2 +- src/defaultSettings.ts | 4 +- src/empathize.ts | 55 ++++ src/index.svelte | 16 +- src/settings.svelte | 130 +++----- src/splash.svelte | 11 +- src/ts/Configs.ts | 110 ------- src/ts/Constants.ts | 2 +- src/ts/FPSUnlock.ts | 6 +- src/ts/Game.ts | 11 +- src/ts/Launcher.ts | 18 +- src/ts/Patch.ts | 22 +- src/ts/Voice.ts | 16 +- src/ts/core/AbstractInstaller.ts | 10 +- src/ts/core/Archive.ts | 364 --------------------- src/ts/core/Cache.ts | 128 -------- src/ts/core/DXVK.ts | 9 +- src/ts/core/Debug.ts | 133 -------- src/ts/core/DiscordRPC.ts | 6 +- src/ts/core/Domain.ts | 48 --- src/ts/core/Downloader.ts | 230 ------------- src/ts/core/Fetch.ts | 79 ----- src/ts/core/Git.ts | 4 +- src/ts/core/IPC.ts | 101 ------ src/ts/core/Notifications.ts | 32 -- src/ts/core/Prefix.ts | 16 +- src/ts/core/Runners.ts | 8 +- src/ts/core/promisify.ts | 83 ----- src/ts/launcher/Background.ts | 3 +- src/ts/launcher/Locales.ts | 4 +- src/ts/launcher/Shaders.ts | 4 +- src/ts/launcher/State.ts | 18 +- src/ts/launcher/states/ApplyPatch.ts | 9 +- src/ts/launcher/states/Install.ts | 3 +- src/ts/launcher/states/InstallVoice.ts | 3 +- src/ts/launcher/states/Launch.ts | 19 +- src/ts/launcher/states/PredownloadVoice.ts | 3 +- src/ts/neutralino/Process.ts | 267 --------------- src/ts/neutralino/Tray.ts | 120 ------- src/ts/neutralino/Window.ts | 82 ----- src/ts/types/Archive.d.ts | 27 -- src/ts/types/Debug.d.ts | 23 -- src/ts/types/Domain.d.ts | 8 - src/ts/types/Notifications.d.ts | 24 -- tsconfig.json | 2 +- 50 files changed, 246 insertions(+), 2097 deletions(-) create mode 100644 src/empathize.ts delete mode 100644 src/ts/Configs.ts delete mode 100644 src/ts/core/Archive.ts delete mode 100644 src/ts/core/Cache.ts delete mode 100644 src/ts/core/Debug.ts delete mode 100644 src/ts/core/Domain.ts delete mode 100644 src/ts/core/Downloader.ts delete mode 100644 src/ts/core/Fetch.ts delete mode 100644 src/ts/core/IPC.ts delete mode 100644 src/ts/core/Notifications.ts delete mode 100644 src/ts/core/promisify.ts delete mode 100644 src/ts/neutralino/Process.ts delete mode 100644 src/ts/neutralino/Tray.ts delete mode 100644 src/ts/neutralino/Window.ts delete mode 100644 src/ts/types/Archive.d.ts delete mode 100644 src/ts/types/Debug.d.ts delete mode 100644 src/ts/types/Domain.d.ts delete mode 100644 src/ts/types/Notifications.d.ts diff --git a/package.json b/package.json index 0bb1ea3..d2b8f7d 100644 --- a/package.json +++ b/package.json @@ -11,6 +11,7 @@ "check": "svelte-check --tsconfig ./tsconfig.json" }, "dependencies": { + "@empathize/framework": "^1.3.5", "js-md5": "^0.7.3", "semver": "^7.3.5", "svelte-i18n": "^3.3.13", @@ -18,12 +19,12 @@ }, "devDependencies": { "@neutralinojs/neu": "^9.1.1", - "@sveltejs/vite-plugin-svelte": "^1.0.0-next.35", + "@sveltejs/vite-plugin-svelte": "^1.0.0-next.36", "@tsconfig/svelte": "^3.0.0", "@types/js-md5": "^0.4.3", "neutralino-appimage-bundler": "^1.3.2", "sass": "^1.49.0", - "svelte": "^3.46.2", + "svelte": "^3.46.3", "svelte-check": "^2.3.0", "svelte-preprocess": "^4.10.2", "tslib": "^2.3.1", diff --git a/src/components/Checkbox.svelte b/src/components/Checkbox.svelte index cab820d..62ab6fe 100644 --- a/src/components/Checkbox.svelte +++ b/src/components/Checkbox.svelte @@ -1,6 +1,8 @@ <script lang="ts"> import { _ } from 'svelte-i18n'; + import { Configs } from '../empathize'; + export let active: boolean = false; export let disabled: boolean = false; @@ -12,8 +14,6 @@ import Checkmark from '../assets/svgs/checkmark.svg'; - import Configs from '../ts/Configs'; - Configs.get(prop).then((value) => active = value as boolean); function updateCheckbox() diff --git a/src/components/DiscordSettings.svelte b/src/components/DiscordSettings.svelte index 99b4c31..a4be027 100644 --- a/src/components/DiscordSettings.svelte +++ b/src/components/DiscordSettings.svelte @@ -1,7 +1,7 @@ <script lang="ts"> import { _ } from 'svelte-i18n'; - import Configs from '../ts/Configs'; + import { Configs } from '../empathize'; export let visible: boolean = false; diff --git a/src/components/DropdownCheckboxes.svelte b/src/components/DropdownCheckboxes.svelte index a65e9d9..042fa74 100644 --- a/src/components/DropdownCheckboxes.svelte +++ b/src/components/DropdownCheckboxes.svelte @@ -1,7 +1,7 @@ <script lang="ts"> import { _ } from 'svelte-i18n'; - import Configs from '../ts/Configs'; + import { Configs } from '../empathize'; export let prop: string = ''; export let lang: string = ''; diff --git a/src/components/EnvironmentManager.svelte b/src/components/EnvironmentManager.svelte index 2f400f7..baac135 100644 --- a/src/components/EnvironmentManager.svelte +++ b/src/components/EnvironmentManager.svelte @@ -1,19 +1,22 @@ <script lang="ts"> import { _ } from 'svelte-i18n'; - import Configs from '../ts/Configs'; + import { Configs } from '../empathize'; import Button from './Button.svelte'; let last_id = 0, variables = {}, selected; Configs.get('env').then((env) => { - for (const key of Object.keys(env as object)) + if (env) { - variables[last_id++] = { - key: key, - value: env![key] - }; + for (const key of Object.keys(env as object)) + { + variables[last_id++] = { + key: key, + value: env![key] + }; + } } }); @@ -29,26 +32,30 @@ </script> <div> - <table class="table properties-table" style="margin-top: 16px"> - <tr> - <th>{$_('settings.environment.items.table.name')}</th> - <th>{$_('settings.environment.items.table.value')}</th> - </tr> - - {#each Object.keys(variables) as key} - <tr on:click={() => selected = key} class:selected={selected === key}> - <td> - <span>{variables[key].key}</span> - <input bind:value={variables[key].key} on:change={updateEnv} /> - </td> - - <td> - <span>{variables[key].value}</span> - <input bind:value={variables[key].value} on:change={updateEnv} /> - </td> + {#if Object.keys(variables).length > 0} + <table class="table properties-table" style="margin-top: 16px"> + <tr> + <th>{$_('settings.environment.items.table.name')}</th> + <th>{$_('settings.environment.items.table.value')}</th> </tr> - {/each} - </table> + + {#each Object.keys(variables) as key} + <tr on:click={() => selected = key} class:selected={selected === key}> + <td> + <span>{variables[key].key}</span> + <input bind:value={variables[key].key} on:change={updateEnv} /> + </td> + + <td> + <span>{variables[key].value}</span> + <input bind:value={variables[key].value} on:change={updateEnv} /> + </td> + </tr> + {/each} + </table> + {:else} + <p>There're no variables here</p> + {/if} <div style="margin-top: 16px"> <Button lang="settings.environment.items.buttons.add" click={() => variables[last_id++] = { key: '', value: '' }} /> diff --git a/src/components/SelectionBox.svelte b/src/components/SelectionBox.svelte index 4ea2e4c..a7a9a68 100644 --- a/src/components/SelectionBox.svelte +++ b/src/components/SelectionBox.svelte @@ -1,7 +1,7 @@ <script lang="ts"> import { _ } from 'svelte-i18n'; - import Configs from '../ts/Configs'; + import { Configs } from '../empathize'; export let prop: string = ''; export let lang: string = ''; diff --git a/src/defaultSettings.ts b/src/defaultSettings.ts index 0d5fb73..ba9a0fa 100644 --- a/src/defaultSettings.ts +++ b/src/defaultSettings.ts @@ -1,6 +1,6 @@ -import Configs from './ts/Configs'; +import { Configs, promisify } from './empathize'; + import constants from './ts/Constants'; -import promisify from './ts/core/promisify'; promisify(async () => { Configs.defaults({ diff --git a/src/empathize.ts b/src/empathize.ts new file mode 100644 index 0000000..237b973 --- /dev/null +++ b/src/empathize.ts @@ -0,0 +1,55 @@ +import { + // Paths API + path, dir, + + // Filesystem API + fs, + + // Windows API + Windows, + + // OS API + Process, Tray, IPC, Notification, Archive, + + // Network API + fetch, Domain, Downloader, + + // Async API + promisify, + + // Meta classes + Cache, Configs, Debug +} from '@empathize/framework'; + +import YAML from 'yaml'; + +import constants from './ts/Constants'; + +Configs.file = constants.paths.config; +Cache.file = constants.paths.cache; + +Configs.serialize = YAML.stringify; +Configs.unserialize = YAML.parse; + +export { + // Paths API + path, dir, + + // Filesystem API + fs, + + // Windows API + Windows, + + // OS API + Process, Tray, IPC, Notification, Archive, + + // Network API + fetch, Domain, Downloader, + + // Async API + promisify, + + // Meta classes + Cache, Configs, Debug +}; diff --git a/src/index.svelte b/src/index.svelte index c037d1e..24d0006 100644 --- a/src/index.svelte +++ b/src/index.svelte @@ -6,18 +6,12 @@ import { onMount } from 'svelte'; import { _, locale } from 'svelte-i18n'; - import Window from './ts/neutralino/Window'; - import Process from './ts/neutralino/Process'; + import { Windows, path, Archive, Debug, Downloader, IPC, Configs } from './empathize'; import Launcher from './ts/Launcher'; import constants from './ts/Constants'; import Game from './ts/Game'; import Background from './ts/launcher/Background'; - import Archive from './ts/core/Archive'; - import Debug from './ts/core/Debug'; - import Downloader from './ts/core/Downloader'; - import IPC from './ts/core/IPC'; - import Configs from './ts/Configs'; import Gear from './assets/images/gear.png'; import GearActive from './assets/images/gear-active.png'; @@ -36,7 +30,7 @@ const launcher = new Launcher(onMount); Neutralino.events.on('ready', () => { - Window.open('splash', { + Windows.open('splash', { title: 'Splash', width: 300, height: 400, @@ -60,13 +54,13 @@ await launcher.rpc.stop(true); // Remove .tmp files from the temp folder - await Neutralino.os.execCommand(`find "${Process.addSlashes(tempDir)}" -maxdepth 1 -type f -name "*.tmp" -delete`); + await Neutralino.os.execCommand(`find "${path.addSlashes(tempDir)}" -maxdepth 1 -type f -name "*.tmp" -delete`); // Remove old launcher's log files const purge_logs = await Configs.get('purge_logs.launcher') as string|null; if (purge_logs !== null && purge_logs[purge_logs.length - 1] == 'd') - await Neutralino.os.execCommand(`find "${Process.addSlashes(launcherDir)}/logs" -maxdepth 1 -mtime ${purge_logs.substring(0, purge_logs.length - 1)} -delete`); + await Neutralino.os.execCommand(`find "${path.addSlashes(launcherDir)}/logs" -maxdepth 1 -mtime ${purge_logs.substring(0, purge_logs.length - 1)} -delete`); // Save logs const log = Debug.get().join('\r\n'); @@ -103,7 +97,7 @@ * Update launcher's title */ Game.latest.then((game) => { - Window.current.setTitle(`${constants.placeholders.uppercase.full} Linux Launcher - ${game.version}`); + Windows.current.setTitle(`${constants.placeholders.uppercase.full} Linux Launcher - ${game.version}`); }); /** diff --git a/src/settings.svelte b/src/settings.svelte index 6dd9a36..52a2c96 100644 --- a/src/settings.svelte +++ b/src/settings.svelte @@ -6,16 +6,11 @@ import { onMount } from 'svelte'; import { _, locale, locales } from 'svelte-i18n'; - import Window from './ts/neutralino/Window'; - import Process from './ts/neutralino/Process'; + import { Windows, Configs, Debug, IPC, Process, path } from './empathize'; import constants from './ts/Constants'; - import Configs from './ts/Configs'; import Launcher from './ts/Launcher'; import FPSUnlock from './ts/FPSUnlock'; - - import Debug from './ts/core/Debug'; - import IPC from './ts/core/IPC'; import Runners from './ts/core/Runners'; import Button from './components/Button.svelte'; @@ -41,72 +36,6 @@ launcherLocales = launcherLocales; - /** - * Game voice packs languages - */ - - const voiceLocales = { - 'en-us': 'settings.general.items.lang.voice.items.en-us', - 'ja-jp': 'settings.general.items.lang.voice.items.ja-jp', - 'ko-kr': 'settings.general.items.lang.voice.items.ko-kr', - 'zn-cn': 'settings.general.items.lang.voice.items.zn-cn' - }; - - /** - * Themes - */ - - const themes = { - 'system': 'settings.general.items.theme.items.system', - 'light': 'settings.general.items.theme.items.light', - 'dark': 'settings.general.items.theme.items.dark' - }; - - /** - * HUD options - */ - - const huds = { - 'none': 'settings.enhancements.items.hud.items.none', - 'dxvk': 'settings.enhancements.items.hud.items.dxvk', - 'mangohud': 'settings.enhancements.items.hud.items.mangohud' - }; - - /** - * Wine synchronizations - */ - - const winesyncs = { - 'none': 'settings.enhancements.items.winesync.items.none', - 'esync': 'settings.enhancements.items.winesync.items.esync', - 'fsync': 'settings.enhancements.items.winesync.items.fsync' - }; - - /** - * Delete launcher logs options - */ - - const purgeLauncherLogs = { - '1d': 'settings.enhancements.items.purge_logs.launcher.items.1d', - '3d': 'settings.enhancements.items.purge_logs.launcher.items.3d', - '5d': 'settings.enhancements.items.purge_logs.launcher.items.5d', - '7d': 'settings.enhancements.items.purge_logs.launcher.items.7d', - '14d': 'settings.enhancements.items.purge_logs.launcher.items.14d', - 'never': 'settings.enhancements.items.purge_logs.launcher.items.never' - }; - - /** - * Menu items - */ - const menuItems = [ - 'general', - 'enhancements', - 'runners', - 'dxvks', - 'shaders', - 'environment' - ]; - /** * Some components stuff */ @@ -203,8 +132,8 @@ // Do some stuff when all the content will be loaded onMount(async () => { - await Window.current.show(); - await Window.current.center(900, 600); + await Windows.current.show(); + // FIXME: await Windows.current.center(900, 600); // This thing will fix window resizing // in several cases (wayland + gnome + custom theme) @@ -214,7 +143,7 @@ else { - Window.current.setSize({ + Windows.current.setSize({ width: 900 + (900 - window.innerWidth), height: 600 + (600 - window.innerHeight), resizable: false @@ -241,8 +170,13 @@ {#if typeof $locale === 'string'} <main> <div class="menu"> - {#each menuItems as item} - <div class="menu-item" on:click={changeItem} class:menu-item-active={selectedItem === item} data-anchor={item}>{ $_(`settings.${item}.title`) }</div> + {#each ['general', 'enhancements', 'runners', 'dxvks', 'shaders', 'environment'] as item} + <div + class="menu-item" + class:menu-item-active={selectedItem === item} + data-anchor={item} + on:click={changeItem} + >{$_(`settings.${item}.title`)}</div> {/each} </div> @@ -269,14 +203,23 @@ tooltip="settings.general.items.lang.voice.tooltip" prop="lang.voice" selected={undefined} - items={voiceLocales} + items={{ + 'en-us': 'settings.general.items.lang.voice.items.en-us', + 'ja-jp': 'settings.general.items.lang.voice.items.ja-jp', + 'ko-kr': 'settings.general.items.lang.voice.items.ko-kr', + 'zn-cn': 'settings.general.items.lang.voice.items.zn-cn' + }} selectionUpdated={() => voiceUpdateRequired = true} /> <SelectionBox lang="settings.general.items.theme.title" prop="theme" - items={themes} + items={{ + 'system': 'settings.general.items.theme.items.system', + 'light': 'settings.general.items.theme.items.light', + 'dark': 'settings.general.items.theme.items.dark' + }} valueChanged={switchTheme} /> @@ -294,7 +237,7 @@ const runnersDir = await constants.paths.runnersDir; - Process.run(`"${Process.addSlashes(await constants.paths.launcherDir)}/winetricks.sh"`, { + Process.run(`"${path.addSlashes(await constants.paths.launcherDir)}/winetricks.sh"`, { env: { WINE: runner ? `${runnersDir}/${runner.name}/${runner.files.wine}` : 'wine', WINESERVER: runner ? `${runnersDir}/${runner.name}/${runner.files.wineserver}` : 'wineserver', @@ -308,7 +251,7 @@ const runnerDir = runner ? `${await constants.paths.runnersDir}/${runner.name}` : ''; - Process.run(runner ? `"${Process.addSlashes(`${runnerDir}/${runner.files.wine}`)}" "${Process.addSlashes(`${runnerDir}/${runner.files.winecfg}`)}"` : 'winecfg', { + Process.run(runner ? `"${path.addSlashes(`${runnerDir}/${runner.files.wine}`)}" "${path.addSlashes(`${runnerDir}/${runner.files.winecfg}`)}"` : 'winecfg', { env: { WINE: runner ? `${runnerDir}/${runner.files.wine}` : 'wine', WINESERVER: runner ? `${runnerDir}/${runner.files.wineserver}` : 'wineserver', @@ -319,14 +262,14 @@ <!-- svelte-ignore missing-declaration --> <Button lang="settings.general.items.buttons.launcher" click={async () => { - Neutralino.os.execCommand(`xdg-open "${Process.addSlashes(await constants.paths.launcherDir)}"`, { + Neutralino.os.execCommand(`xdg-open "${path.addSlashes(await constants.paths.launcherDir)}"`, { background: true }); }} /> <!-- svelte-ignore missing-declaration --> <Button lang="settings.general.items.buttons.game" click={async () => { - Neutralino.os.execCommand(`xdg-open "${Process.addSlashes(await constants.paths.gameDir)}"`, { + Neutralino.os.execCommand(`xdg-open "${path.addSlashes(await constants.paths.gameDir)}"`, { background: true }); }} /> @@ -339,7 +282,11 @@ <SelectionBox lang="settings.enhancements.items.hud.title" prop="hud" - items={huds} + items={{ + 'none': 'settings.enhancements.items.hud.items.none', + 'dxvk': 'settings.enhancements.items.hud.items.dxvk', + 'mangohud': 'settings.enhancements.items.hud.items.mangohud' + }} /> <SelectionBox @@ -347,7 +294,11 @@ prop="winesync" tooltip="settings.enhancements.items.winesync.tooltip" tooltip_size="large" - items={winesyncs} + items={{ + 'none': 'settings.enhancements.items.winesync.items.none', + 'esync': 'settings.enhancements.items.winesync.items.esync', + 'fsync': 'settings.enhancements.items.winesync.items.fsync' + }} /> <Checkbox @@ -388,7 +339,14 @@ lang="settings.enhancements.items.purge_logs.launcher.title" tooltip="settings.enhancements.items.purge_logs.launcher.tooltip" prop="purge_logs.launcher" - items={purgeLauncherLogs} + items={{ + '1d': 'settings.enhancements.items.purge_logs.launcher.items.1d', + '3d': 'settings.enhancements.items.purge_logs.launcher.items.3d', + '5d': 'settings.enhancements.items.purge_logs.launcher.items.5d', + '7d': 'settings.enhancements.items.purge_logs.launcher.items.7d', + '14d': 'settings.enhancements.items.purge_logs.launcher.items.14d', + 'never': 'settings.enhancements.items.purge_logs.launcher.items.never' + }} /> </div> diff --git a/src/splash.svelte b/src/splash.svelte index 5263a19..d292723 100644 --- a/src/splash.svelte +++ b/src/splash.svelte @@ -6,10 +6,7 @@ import { onMount } from 'svelte'; import { _, locale } from 'svelte-i18n'; - import Configs from './ts/Configs'; - import IPC from './ts/core/IPC'; - - import Window from './ts/neutralino/Window'; + import { Configs, IPC, Windows } from './empathize'; import Splash from './assets/gifs/running-qiqi.gif'; import SplashSecret from './assets/gifs/loading-marie-please.gif'; @@ -19,8 +16,8 @@ let phrase = Math.round(Math.random() * 8); onMount(() => { - Window.current.show(); - Window.current.center(300, 400); + Windows.current.show(); + // FIXME: Windows.current.center(300, 400); }); const isLauncherLoaded = () => { @@ -32,7 +29,7 @@ for (const record of launcherLoaded) await record.pop(); - Window.current.hide(); + Windows.current.hide(); Neutralino.app.exit(); } diff --git a/src/ts/Configs.ts b/src/ts/Configs.ts deleted file mode 100644 index 40d79e8..0000000 --- a/src/ts/Configs.ts +++ /dev/null @@ -1,110 +0,0 @@ -import YAML from 'yaml'; - -import constants from './Constants'; - -declare const Neutralino; - -// Ok yea, null, object and boolean aren't scalars -// but I don't care -type scalar = null | string | number | boolean | object; - -export default class Configs -{ - /** - * Get config value - * - * @param name config name, e.g. "lang.launcher" - * - * @returns undefined if config doesn't exist. Otherwise - config value - */ - public static get(name: string = ''): Promise<undefined|scalar|scalar[]> - { - return new Promise(async (resolve) => { - Neutralino.filesystem.readFile(await constants.paths.config).then((config) => { - config = YAML.parse(config); - - if (name !== '') - { - name.split('.').forEach((value) => { - config = config[value]; - }); - } - - resolve(config); - }).catch(() => { - setTimeout(() => resolve(this.get(name)), 100); - }); - }); - } - - /** - * Set config value - * - * @param name config name, e.g. "lang.launcher" - * @param value config value, e.g. "en-us" - * - * @returns Promise<void> indicates if the settings were updated - */ - public static set(name: string, value: scalar|scalar[]|Promise<scalar|scalar[]>): Promise<void> - { - const getUpdatedArray = (path: string[], array: scalar|scalar[], value: scalar|scalar[]): scalar|scalar[] => { - array![path[0]] = path.length > 1 ? - getUpdatedArray(path.slice(1), array![path[0]] ?? {}, value) : value; - - return array; - }; - - return new Promise(async (resolve) => { - value = await Promise.resolve(value); - - Neutralino.filesystem.readFile(await constants.paths.config).then(async (config) => { - config = YAML.stringify(getUpdatedArray(name.split('.'), YAML.parse(config), value)); - - Neutralino.filesystem.writeFile(await constants.paths.config, config) - .then(() => resolve()); - }).catch(async () => { - let config = YAML.stringify(getUpdatedArray(name.split('.'), {}, value)); - - Neutralino.filesystem.writeFile(await constants.paths.config, config) - .then(() => resolve()); - }); - }); - } - - /** - * Set default values - * - * @param configs object of default values - * - * @returns Promise<void> indicates if the default settings were applied - */ - public static defaults(configs: object): Promise<void> - { - return new Promise(async (resolve) => { - const setDefaults = async (current: object) => { - const updateDefaults = (current: object, defaults: object) => { - Object.keys(defaults).forEach((key) => { - // If the field exists in defaults and doesn't exist in current - if (current[key] === undefined) - current[key] = defaults[key]; - - // If both default and current are objects - // and we also should check if they're not nulls - // because JS thinks that [typeof null === 'object'] - else if (typeof current[key] == 'object' && typeof defaults[key] == 'object' && current[key] !== null && defaults[key] !== null) - current[key] = updateDefaults(current[key], defaults![key]); - }); - - return current; - }; - - Neutralino.filesystem.writeFile(await constants.paths.config, YAML.stringify(updateDefaults(current, configs))) - .then(() => resolve()); - }; - - Neutralino.filesystem.readFile(await constants.paths.config) - .then((config) => setDefaults(YAML.parse(config))) - .catch(() => setDefaults({})); - }); - } -} diff --git a/src/ts/Constants.ts b/src/ts/Constants.ts index 8f0a4d8..74bc700 100644 --- a/src/ts/Constants.ts +++ b/src/ts/Constants.ts @@ -1,4 +1,4 @@ -import Configs from './Configs'; +import { Configs } from '../empathize'; declare const Neutralino; declare const NL_CWD; diff --git a/src/ts/FPSUnlock.ts b/src/ts/FPSUnlock.ts index 7d7a1c5..ead6880 100644 --- a/src/ts/FPSUnlock.ts +++ b/src/ts/FPSUnlock.ts @@ -1,6 +1,6 @@ +import { Downloader, path } from '../empathize'; + import constants from './Constants'; -import Downloader from './core/Downloader'; -import Process from './neutralino/Process'; declare const Neutralino; @@ -35,7 +35,7 @@ export default class FPSUnlock Downloader.download(constants.uri.fpsunlock.bat, fpsunlockBat).then((stream) => { stream.finish(async () => { // sed -i 's/start ..\/GI_FPSUnlocker\/unlockfps.exe \%\*/start ..\/fpsunlock\/unlockfps.exe \%\*/g' unlockfps.bat - Neutralino.os.execCommand(`sed -i 's/start ..\\/GI_FPSUnlocker\\/unlockfps.exe \\%\\*/start ..\\/fpsunlock\\/unlockfps.exe \\%\\*/g' "${Process.addSlashes(fpsunlockBat)}"`) + Neutralino.os.execCommand(`sed -i 's/start ..\\/GI_FPSUnlocker\\/unlockfps.exe \\%\\*/start ..\\/fpsunlock\\/unlockfps.exe \\%\\*/g' "${path.addSlashes(fpsunlockBat)}"`) .then(() => resolve()); }); }); diff --git a/src/ts/Game.ts b/src/ts/Game.ts index c032754..0554cdb 100644 --- a/src/ts/Game.ts +++ b/src/ts/Game.ts @@ -5,14 +5,13 @@ import type { Diff } from './types/GameData'; +import type { Stream as DownloadingStream } from '@empathize/framework/dist/network/Downloader'; + +import { fetch, Domain, promisify, Downloader, Cache, Debug } from '../empathize'; +import { DebugThread } from '@empathize/framework/dist/meta/Debug'; + import constants from './Constants'; -import fetch from './core/Fetch'; import AbstractInstaller from './core/AbstractInstaller'; -import Domain from './core/Domain'; -import promisify from './core/promisify'; -import Debug, { DebugThread } from './core/Debug'; -import Downloader, { Stream as DownloadingStream } from './core/Downloader'; -import Cache from './core/Cache'; declare const Neutralino; diff --git a/src/ts/Launcher.ts b/src/ts/Launcher.ts index 3e2df3b..953ad2e 100644 --- a/src/ts/Launcher.ts +++ b/src/ts/Launcher.ts @@ -1,13 +1,11 @@ import { locale } from 'svelte-i18n'; -import Window from './neutralino/Window'; -import Process from './neutralino/Process'; -import Tray from './neutralino/Tray'; +import { + Windows, Process, Tray, + Configs, Debug, IPC +} from '../empathize'; import constants from './Constants'; -import Configs from './Configs'; -import Debug from './core/Debug'; -import IPC from './core/IPC'; import DiscordRPC from './core/DiscordRPC'; import Locales from './launcher/Locales'; @@ -56,7 +54,7 @@ export default class Launcher { this.settingsMenu = undefined; - const window = await Window.open('settings', { + const window = await Windows.open('settings', { title: 'Settings', width: 900, height: 600, @@ -96,11 +94,11 @@ export default class Launcher }); }); - Window.current.show(); - Window.current.center(1280, 700); + Windows.current.show(); + // TODO: Windows.current.center(1280, 700); }) - Window.current.hide(); + Windows.current.hide(); } resolve(window.status); diff --git a/src/ts/Patch.ts b/src/ts/Patch.ts index 5fc4477..e781c76 100644 --- a/src/ts/Patch.ts +++ b/src/ts/Patch.ts @@ -2,14 +2,12 @@ import type { PatchInfo } from './types/Patch'; import md5 from 'js-md5'; +import { fetch, promisify, Debug, Cache, path } from '../empathize'; +import { DebugThread } from '@empathize/framework/dist/meta/Debug'; + import constants from './Constants'; import Game from './Game'; -import fetch from './core/Fetch'; import AbstractInstaller from './core/AbstractInstaller'; -import promisify from './core/promisify'; -import Process from './neutralino/Process'; -import Debug, { DebugThread } from './core/Debug'; -import Cache from './core/Cache'; declare const Neutralino; @@ -50,37 +48,37 @@ class Stream extends AbstractInstaller /** * Remove test version restrictions from the main patch */ - () => Neutralino.os.execCommand(`cd "${Process.addSlashes(patchDir)}" && sed -i '/^echo "If you would like to test this patch, modify this script and remove the line below this one."/,+5d' patch.sh`), + () => Neutralino.os.execCommand(`cd "${path.addSlashes(patchDir)}" && sed -i '/^echo "If you would like to test this patch, modify this script and remove the line below this one."/,+5d' patch.sh`), /** * Remove /etc/hosts editing due to sudo permissions */ - () => Neutralino.os.execCommand(`cd "${Process.addSlashes(patchDir)}" && sed -i '/^# ===========================================================/,+68d' patch.sh`), + () => Neutralino.os.execCommand(`cd "${path.addSlashes(patchDir)}" && sed -i '/^# ===========================================================/,+68d' patch.sh`), /** * Remove test version restrictions from the anti-login crash patch */ - () => Neutralino.os.execCommand(`cd "${Process.addSlashes(patchDir)}" && sed -i '/^echo " necessary afterwards (Friday?). If that's the case, comment the line below."/,+2d' patch_anti_logincrash.sh`), + () => Neutralino.os.execCommand(`cd "${path.addSlashes(patchDir)}" && sed -i '/^echo " necessary afterwards (Friday?). If that's the case, comment the line below."/,+2d' patch_anti_logincrash.sh`), /** * Make the main patch executable */ - () => Neutralino.os.execCommand(`chmod +x "${Process.addSlashes(patchDir)}/patch.sh"`), + () => Neutralino.os.execCommand(`chmod +x "${path.addSlashes(patchDir)}/patch.sh"`), /** * Make the anti-login crash patch executable */ - () => Neutralino.os.execCommand(`chmod +x "${Process.addSlashes(patchDir)}/patch_anti_logincrash.sh"`), + () => Neutralino.os.execCommand(`chmod +x "${path.addSlashes(patchDir)}/patch_anti_logincrash.sh"`), /** * Execute the main patch installation script */ - () => Neutralino.os.execCommand(`cd "${Process.addSlashes(gameDir)}" && yes yes | bash "${Process.addSlashes(patchDir)}/patch.sh"`), + () => Neutralino.os.execCommand(`cd "${path.addSlashes(gameDir)}" && yes yes | bash "${path.addSlashes(patchDir)}/patch.sh"`), /** * Execute the anti-login crash patch installation script */ - () => Neutralino.os.execCommand(`cd "${Process.addSlashes(gameDir)}" && yes | bash "${Process.addSlashes(patchDir)}/patch_anti_logincrash.sh"`) + () => Neutralino.os.execCommand(`cd "${path.addSlashes(gameDir)}" && yes | bash "${path.addSlashes(patchDir)}/patch_anti_logincrash.sh"`) ] }); diff --git a/src/ts/Voice.ts b/src/ts/Voice.ts index 00bd15e..8fca9c6 100644 --- a/src/ts/Voice.ts +++ b/src/ts/Voice.ts @@ -1,14 +1,14 @@ import type { VoicePack } from './types/GameData'; import type { VoiceLang, InstalledVoice } from './types/Voice'; +import type { Stream as DownloadingStream } from '@empathize/framework/dist/network/Downloader'; + +import { Configs, Debug, Downloader, promisify, path } from '../empathize'; +import { DebugThread } from '@empathize/framework/dist/meta/Debug'; + import constants from './Constants'; import Game from './Game'; import AbstractInstaller from './core/AbstractInstaller'; -import Configs from './Configs'; -import Debug, { DebugThread } from './core/Debug'; -import Downloader, { Stream as DownloadingStream } from './core/Downloader'; -import Process from './neutralino/Process'; -import promisify from './core/promisify'; declare const Neutralino; @@ -170,15 +170,15 @@ export default class Voice const pipeline = promisify({ callbacks: [ - () => Neutralino.os.execCommand(`rm -rf "${Process.addSlashes(`${voiceDir}/${this.langs[lang]}`)}"`), + () => Neutralino.os.execCommand(`rm -rf "${path.addSlashes(`${voiceDir}/${this.langs[lang]}`)}"`), (): Promise<void> => new Promise(async (resolve) => { - Neutralino.os.execCommand(`rm -f "${Process.addSlashes(`${await constants.paths.gameDir}/Audio_${this.langs[lang]}_pkg_version`)}"`) + Neutralino.os.execCommand(`rm -f "${path.addSlashes(`${await constants.paths.gameDir}/Audio_${this.langs[lang]}_pkg_version`)}"`) .then(() => resolve()); }), (): Promise<void> => new Promise(async (resolve) => { - Neutralino.os.execCommand(`sed -i '/${this.langs[lang]}/d' "${Process.addSlashes(`${await constants.paths.gameDataDir}/Persistent/audio_lang_14`)}"`) + Neutralino.os.execCommand(`sed -i '/${this.langs[lang]}/d' "${path.addSlashes(`${await constants.paths.gameDataDir}/Persistent/audio_lang_14`)}"`) .then(() => resolve()); }) ], diff --git a/src/ts/core/AbstractInstaller.ts b/src/ts/core/AbstractInstaller.ts index 001e059..7a82c6f 100644 --- a/src/ts/core/AbstractInstaller.ts +++ b/src/ts/core/AbstractInstaller.ts @@ -1,7 +1,9 @@ +import type { Stream as DownloadStream } from '@empathize/framework/dist/network/Downloader'; + +import { Downloader, Archive } from '../../empathize'; +import { DebugThread } from '@empathize/framework/dist/meta/Debug'; + import constants from '../Constants'; -import Downloader, { Stream as DownloadStream } from './Downloader'; -import Archive from './Archive'; -import { DebugThread } from './Debug'; declare const Neutralino; @@ -68,7 +70,7 @@ export default abstract class Installer if (shouldResolve) debugThread.log(`Resolved unpack dir: ${unpackDir}`); - Archive.unpack(archivePath, unpackDir).then((stream) => { + Archive.extract(archivePath, unpackDir).then((stream) => { stream.progressInterval = this.unpackProgressInterval; stream.start(() => { diff --git a/src/ts/core/Archive.ts b/src/ts/core/Archive.ts deleted file mode 100644 index dbeeb2b..0000000 --- a/src/ts/core/Archive.ts +++ /dev/null @@ -1,364 +0,0 @@ -import type { - ArchiveType, - Size, - File, - ArchiveInfo -} from '../types/Archive'; - -import { DebugThread } from './Debug'; -import promisify from './promisify'; -import Process from '../neutralino/Process'; - -declare const Neutralino; -declare const NL_CWD; - -class Stream -{ - protected _id: number = -1; - - /** - * ID of the archive unpacker process - */ - public get id(): number - { - return this._id; - } - - /** - * The interval in ms between progress event calls - */ - public progressInterval: number = 500; - - protected path: string; - protected unpackDir: string|null; - protected unpacked: number = 0; - - protected archive?: ArchiveInfo; - - protected onStart?: () => void; - protected onProgress?: (current: number, total: number, difference: number) => void; - protected onFinish?: () => void; - protected onError?: () => void; - - protected started: boolean = false; - protected finished: boolean = false; - protected throwedError: boolean = false; - - /** - * @param path path to archive - * @param unpackDir directory to extract the files to - */ - public constructor(path: string, unpackDir: string|null = null) - { - this.path = path; - this.unpackDir = unpackDir; - this.started = true; - - const debugThread = new DebugThread('Archive/Stream', { - message: { - 'path': path, - 'unpack dir': unpackDir - } - }); - - if (this.onStart) - this.onStart(); - - Archive.getInfo(path).then((info) => { - if (info === null) - { - this.throwedError = true; - - if (this.onError) - this.onError(); - } - - else - { - this.archive = info; - - let command = { - tar: `tar -xvf "${Process.addSlashes(path)}"${unpackDir ? ` -C "${Process.addSlashes(unpackDir)}"` : ''}`, - zip: `unzip -o "${Process.addSlashes(path)}"${unpackDir ? ` -d "${Process.addSlashes(unpackDir)}"` : ''}` - }[this.archive.type!]; - - if (unpackDir) - command = `mkdir -p "${Process.addSlashes(unpackDir)}" && ${command}`; - - let remainedFiles = this.archive.files; - - const baseDir = unpackDir ?? NL_CWD; - - Neutralino.os.execCommand(command, { - background: true - }).then((result) => { - this._id = result.pid; - }); - - debugThread.log(`Unpacking started with command: ${command}`); - - const updateProgress = async () => { - let difference: number = 0; - let pool: any[] = []; - - remainedFiles.forEach((file) => { - if (file.path != '#unpacked#') - { - pool.push((): Promise<void> => { - return new Promise((resolve) => { - Neutralino.filesystem.getStats(`${baseDir}/${file.path}`) - .then(() => { - this.unpacked += file.size.uncompressed!; - difference += file.size.uncompressed!; - - file.path = '#unpacked#'; - - resolve(); - }) - .catch(() => resolve()) - }); - }); - } - }); - - await promisify({ - callbacks: pool, - callAtOnce: true, - interval: 200 - }); - - remainedFiles = remainedFiles.filter((file) => file.path != '#unpacked#'); - - if (this.onProgress) - this.onProgress(this.unpacked, this.archive!.size.uncompressed!, difference); - - if (this.unpacked >= this.archive!.size.uncompressed!) - { - this.finished = true; - - debugThread.log('Unpacking finished'); - - if (this.onFinish) - this.onFinish(); - } - - if (!this.finished) - setTimeout(updateProgress, this.progressInterval); - }; - - setTimeout(updateProgress, this.progressInterval); - } - }); - } - - /** - * Specify event that will be called when the extraction has started - * - * @param callback - */ - public start(callback: () => void) - { - this.onStart = callback; - - if (this.started) - callback(); - } - - /** - * Specify event that will be called every [this.progressInterval] ms while extracting the archive - * - * @param callback - */ - public progress(callback: (current: number, total: number, difference: number) => void) - { - this.onProgress = callback; - } - - /** - * Specify event that will be called after the archive has been extracted - * - * @param callback - */ - public finish(callback: () => void) - { - this.onFinish = callback; - - if (this.finished) - callback(); - } - - /** - * Specify event that will be called if archive can't be extracted - * - * @param callback - */ - public error(callback: () => void) - { - this.onError = callback; - - if (this.throwedError) - callback(); - } - - /** - * Close unpacking stream - */ - public close(forced: boolean = false) - { - Neutralino.os.execCommand(`kill ${forced ? '-9' : '-15'} ${this._id}`); - } -} - -export default class Archive -{ - protected static streams: Stream[] = []; - - /** - * Get type of archive - * - * @param path path to archive - * @returns supported archive type or null - */ - public static getType(path: string): ArchiveType|null - { - if (path.substring(path.length - 4) == '.zip') - return 'zip'; - - else if (path.substring(path.length - 7, path.length - 2) == '.tar.') - return 'tar'; - - else return null; - } - - /** - * Get archive info - * - * @param path path to archive - * @returns null if the archive has unsupported type. Otherwise - archive info - */ - public static getInfo(path: string): Promise<ArchiveInfo|null> - { - const debugThread = new DebugThread('Archive.getInfo', `Getting info about archive: ${path}`); - - return new Promise(async (resolve) => { - let archive: ArchiveInfo = { - size: { - compressed: null, - uncompressed: null - }, - type: this.getType(path), - files: [] - }; - - switch (archive.type) - { - case 'tar': - const tarOutput = await Neutralino.os.execCommand(`tar -tvf "${path}"`); - - for (const match of tarOutput.stdOut.matchAll(/^[dwxr\-]+ [\w/]+[ ]+(\d+) [0-9\-]+ [0-9\:]+ (.+)/gm)) - { - let fileSize = parseInt(match[1]); - - archive.size.uncompressed! += fileSize; - - archive.files.push({ - path: match[2], - size: { - compressed: null, - uncompressed: fileSize - } - }); - } - - debugThread.log({ - message: { - 'type': archive.type, - 'compressed size': archive.size.compressed, - 'uncompressed size': archive.size.uncompressed, - 'files amount': archive.files.length - } - }); - - resolve(archive); - - break; - - case 'zip': - const zipOutput = await Neutralino.os.execCommand(`unzip -v "${path}"`); - - for (const match of zipOutput.stdOut.matchAll(/^(\d+) [a-zA-Z\:]+[ ]+(\d+)[ ]+[0-9\-]+% [0-9\-]+ [0-9\:]+ [a-f0-9]{8} (.+)/gm)) - { - let uncompressedSize = parseInt(match[1]), - compressedSize = parseInt(match[2]); - - archive.size.compressed! += compressedSize; - archive.size.uncompressed! += uncompressedSize; - - archive.files.push({ - path: match[3], - size: { - compressed: compressedSize, - uncompressed: uncompressedSize - } - }); - } - - debugThread.log({ - message: { - 'type': archive.type, - 'compressed size': archive.size.compressed, - 'uncompressed size': archive.size.uncompressed, - 'files amount': archive.files.length - } - }); - - resolve(archive); - - break; - - default: - debugThread.log(`Unsupported archive type: ${archive.type}`); - - resolve(null); - - break; - } - }); - } - - /** - * Extract Archive - * - * @param path path to archive - * @param unpackDir directory to extract the files to - */ - public static unpack(path: string, unpackDir: string|null = null): Promise<Stream> - { - return new Promise((resolve) => { - const stream = new Stream(path, unpackDir); - - this.streams.push(stream); - - resolve(stream); - }); - } - - /** - * Close every open archive extracting stream - */ - public static closeStreams(forced: boolean = false) - { - this.streams.forEach((stream) => { - stream.close(forced); - }); - } -}; - -export { Stream }; - -export type { - ArchiveType, - File, - Size, - ArchiveInfo -}; diff --git a/src/ts/core/Cache.ts b/src/ts/core/Cache.ts deleted file mode 100644 index 42cb7d7..0000000 --- a/src/ts/core/Cache.ts +++ /dev/null @@ -1,128 +0,0 @@ -import constants from '../Constants'; -import Debug from './Debug'; - -type Record = { - expired: boolean; - value: object|object[]; -}; - -declare const Neutralino; - -export default class Cache -{ - // Locally stored cache to not to access - // cache.json file every time we want to find something - protected static cache: object|null = null; - - /** - * Get cached value - * - * @returns null if this value is not cached - */ - public static get(name: string): Promise<Record|null> - { - return new Promise(async (resolve) => { - if (this.cache !== null && this.cache[name] !== undefined) - { - const expired = this.cache[name].ttl !== null ? Date.now() > this.cache[name].ttl * 1000 : false; - - Debug.log({ - function: 'Cache.get', - message: [ - `Resolved ${expired ? 'expired' : 'unexpired'} hot cache record`, - `[name] ${name}`, - `[value]: ${JSON.stringify(this.cache[name].value)}` - ] - }); - - resolve({ - expired: expired, - value: this.cache[name].value - }); - } - - else Neutralino.filesystem.readFile(await constants.paths.cache) - .then((cache) => { - this.cache = JSON.parse(cache); - - if (this.cache![name] === undefined) - resolve(null); - - else - { - const output = { - expired: this.cache![name].ttl !== null ? Date.now() > this.cache![name].ttl * 1000 : false, - value: this.cache![name].value - }; - - Debug.log({ - function: 'Cache.get', - message: [ - `Resolved ${output.expired ? 'expired' : 'unexpired'} cache`, - `[name] ${name}`, - `[value]: ${JSON.stringify(output.value)}` - ] - }); - - resolve(output); - } - }) - .catch(() => resolve(null)); - }); - } - - /** - * Cache value - * - * @param name name of the value to cache - * @param value value to cache - * @param ttl number of seconds to cache - * - * @returns promise that indicates when the value will be cached - */ - public static set(name: string, value: object|object[], ttl: number|null = null): Promise<void> - { - return new Promise((resolve) => { - constants.paths.cache.then((cacheFile) => { - const writeCache = () => { - Debug.log({ - function: 'Cache.set', - message: [ - 'Caching data:', - `[ttl] ${ttl}`, - `[value] ${JSON.stringify(value)}` - ] - }); - - this.cache![name] = { - ttl: ttl !== null ? Math.round(Date.now() / 1000) + ttl : null, - value: value - }; - - Neutralino.filesystem.writeFile(cacheFile, JSON.stringify(this.cache)) - .then(() => resolve()); - }; - - if (this.cache === null) - { - Neutralino.filesystem.readFile(cacheFile) - .then((cacheRaw) => - { - this.cache = JSON.parse(cacheRaw); - - writeCache(); - }) - .catch(() => { - this.cache = {}; - - writeCache(); - }); - } - - else writeCache(); - }); - }); - } -}; - -export type { Record }; diff --git a/src/ts/core/DXVK.ts b/src/ts/core/DXVK.ts index 4f76002..757dfcc 100644 --- a/src/ts/core/DXVK.ts +++ b/src/ts/core/DXVK.ts @@ -1,12 +1,11 @@ import type { DXVK as TDXVK } from '../types/DXVK'; +import { Configs, Process, promisify, path } from '../../empathize'; +import { DebugThread } from '@empathize/framework/dist/meta/Debug'; + import constants from '../Constants'; -import Configs from '../Configs'; import AbstractInstaller from './AbstractInstaller'; -import Process from '../neutralino/Process'; -import promisify from './promisify'; import Runners from './Runners'; -import { DebugThread } from './Debug'; declare const Neutralino; @@ -143,7 +142,7 @@ export default class DXVK const version = typeof dxvk !== 'string' ? dxvk.version : dxvk; - await Neutralino.os.execCommand(`rm -rf "${Process.addSlashes(await constants.paths.dxvksDir)}/dxvk-${version}"`); + await Neutralino.os.execCommand(`rm -rf "${path.addSlashes(await constants.paths.dxvksDir)}/dxvk-${version}"`); debugThread.log('Deletion completed'); diff --git a/src/ts/core/Debug.ts b/src/ts/core/Debug.ts deleted file mode 100644 index 4747077..0000000 --- a/src/ts/core/Debug.ts +++ /dev/null @@ -1,133 +0,0 @@ -import type { DebugOptions, LogRecord } from '../types/Debug'; - -class DebugThread -{ - protected thread: number; - protected funcName: string|null; - - public constructor(funcName: string|null = null, options: DebugOptions|string|null = null) - { - // Generate some random thread id - this.thread = 1000 + Math.round(Math.random() * 8999); - - this.funcName = funcName; - - if (options !== null) - this.log(options); - } - - public log(options: DebugOptions|string) - { - Debug.log({ - thread: this.thread, - function: this.funcName ?? '', - - ...(typeof options === 'string' ? { message: options } : options) - }); - } -} - -class Debug -{ - public static readonly startedAt = new Date; - - protected static logOutput: LogRecord[] = []; - - protected static onLogHandler?: (record: LogRecord) => void; - - protected static formatTime(time: number): string - { - const prefixTime = (time: number): string => { - return time < 10 ? `0${time}` : time.toString(); - }; - - const date = new Date(time); - - return `${prefixTime(date.getHours())}:${prefixTime(date.getMinutes())}:${prefixTime(date.getSeconds())}.${date.getMilliseconds()}`; - } - - public static log(options: DebugOptions|string) - { - const time = Date.now(); - - let output: LogRecord = { - time: time, - log: [ - `[${this.formatTime(time)}]` - ] - }; - - if (typeof options === 'string') - output.log[0] += ` ${options}`; - - else - { - // Add thread id - if (options.thread) - output.log[0] += `[thread: ${options.thread}]`; - - // Add function name - if (options.function) - output.log[0] += `[${options.function}]`; - - // Add log message if it is a single line - if (typeof options.message === 'string') - output.log[0] += ` ${options.message}`; - - // message: [a, b, c, d] - else if (Array.isArray(options.message)) - options.message.forEach((line) => { - if (line !== '') - output.log.push(` - ${line}`); - }); - - // message: { a: b, c: d } - else Object.keys(options.message).forEach((key) => { - output.log.push(` - [${key}] ${options.message[key]}`); - }); - } - - console.log(output.log.join('\r\n')); - - this.logOutput.push(output); - - if (this.onLogHandler) - this.onLogHandler(output); - } - - public static merge(records: LogRecord[]) - { - this.logOutput.unshift(...records); - this.logOutput.sort((a, b) => a.time - b.time); - } - - public static getRecords(): LogRecord[] - { - return this.logOutput; - } - - public static get(): string[] - { - let output: string[] = []; - - this.logOutput.forEach((record) => { - record.log.forEach((line) => output.push(line)); - }); - - return output; - } - - public static handler(handler: (record: LogRecord) => void) - { - this.onLogHandler = handler; - } -} - -export default Debug; - -export { DebugThread }; - -export type { - DebugOptions, - LogRecord -}; diff --git a/src/ts/core/DiscordRPC.ts b/src/ts/core/DiscordRPC.ts index a5b1654..4c79996 100644 --- a/src/ts/core/DiscordRPC.ts +++ b/src/ts/core/DiscordRPC.ts @@ -1,6 +1,6 @@ import type { Params } from '../types/DiscordRPC'; -import Process from '../neutralino/Process'; +import { Process, path } from '../../empathize'; declare const NL_CWD; @@ -19,10 +19,10 @@ export default class DiscordRPC ]; if (params.details) - exec = [...exec, `-d "${Process.addSlashes(params.details)}"`]; + exec = [...exec, `-d "${path.addSlashes(params.details)}"`]; if (params.state) - exec = [...exec, `-s "${Process.addSlashes(params.state)}"`]; + exec = [...exec, `-s "${path.addSlashes(params.state)}"`]; if (params.icon) { diff --git a/src/ts/core/Domain.ts b/src/ts/core/Domain.ts deleted file mode 100644 index c25aee7..0000000 --- a/src/ts/core/Domain.ts +++ /dev/null @@ -1,48 +0,0 @@ -import type { DomainInfo } from '../types/Domain'; - -import Process from '../neutralino/Process'; -import { DebugThread } from './Debug'; - -declare const Neutralino; - -export default class Domain -{ - public static getInfo(uri: string): Promise<DomainInfo> - { - const debugThread = new DebugThread('Domain.getInfo', `Getting info about uri: ${uri}`); - - return new Promise(async (resolve) => { - const process = await Neutralino.os.execCommand(`ping -n -4 -w 1 -B "${Process.addSlashes(uri)}"`); - const output = process.stdOut || process.stdErr; - - const resolveInfo = (info: DomainInfo) => { - debugThread.log({ message: info }); - - resolve(info); - }; - - if (output.includes('Name or service not known')) - { - resolveInfo({ - uri: uri, - available: false - }); - } - - else - { - const regex = /PING (.*) \(([0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3})\) .* ([0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}) : [\d]+\([\d]+\)/gm.exec(output); - - if (regex !== null) - { - resolveInfo({ - uri: regex[1], - remoteIp: regex[2], - localIp: regex[3], - available: regex[2] !== regex[3] - }); - } - } - }); - } -}; diff --git a/src/ts/core/Downloader.ts b/src/ts/core/Downloader.ts deleted file mode 100644 index 4a4457b..0000000 --- a/src/ts/core/Downloader.ts +++ /dev/null @@ -1,230 +0,0 @@ -import Process from '../neutralino/Process'; -import { DebugThread } from './Debug'; -import fetch from './Fetch'; - -declare const Neutralino; - -class Stream -{ - protected _id: number = -1; - - /** - * ID of the curl process - */ - public get id(): number - { - return this._id; - } - - /** - * The interval in ms between progress event calls - */ - public progressInterval: number = 200; - - /** - * The interval in ms between checking was downloading resumed after pausing - */ - public pauseInterval: number = 500; - - protected uri: string; - protected output: string; - protected total: number; - protected previous: number = 0; - - protected onStart?: () => void; - protected onProgress?: (current: number, total: number, difference: number) => void; - protected onFinish?: () => void; - - protected started: boolean = false; - protected paused: boolean = true; // true because we call .resume() method at start - protected finished: boolean = false; - - protected debugThread: DebugThread; - - public constructor(uri: string, output: string, total: number) - { - this.uri = uri; - this.output = output; - this.total = total; - this.started = true; - - this.debugThread = new DebugThread('Downloader/Stream', { - message: { - 'uri': uri, - 'output file': output, - 'total size': total - } - }); - - if (this.onStart) - this.onStart(); - - this.resume(); - - const updateProgress = () => { - if (!this.paused) - { - Neutralino.filesystem.getStats(output).then((stats) => { - if (this.onProgress) - this.onProgress(stats.size, this.total, stats.size - this.previous); - - this.previous = stats.size; - - if (stats.size >= this.total) - { - this.finished = true; - - this.debugThread.log('Downloading finished'); - - if (this.onFinish) - this.onFinish(); - } - - if (!this.finished) - setTimeout(updateProgress, this.progressInterval); - }).catch(() => { - if (!this.finished) - setTimeout(updateProgress, this.progressInterval); - }); - } - - else setTimeout(updateProgress, this.pauseInterval); - }; - - setTimeout(updateProgress, this.progressInterval); - } - - /** - * Specify event that will be called when the download gets started - * - * @param callback - */ - public start(callback: () => void) - { - this.onStart = callback; - - if (this.started) - callback(); - } - - /** - * Specify event that will be called every [this.progressInterval] ms while the file is downloading - * - * @param callback - */ - public progress(callback: (current: number, total: number, difference: number) => void) - { - this.onProgress = callback; - } - - /** - * Specify event that will be called after the file is downloaded - * - * @param callback - */ - public finish(callback: () => void) - { - this.onFinish = callback; - - if (this.finished) - callback(); - } - - /** - * Pause downloading - */ - public pause() - { - if (!this.paused) - { - this.debugThread.log('Downloading paused'); - - this.close(true); - - this.paused = true; - } - } - - /** - * Resume downloading - */ - public resume() - { - if (this.paused) - { - const command = `curl -s -L -N -C - -o "${Process.addSlashes(this.output)}" "${this.uri}"`; - - this.debugThread.log(`Downloading started with command: ${command}`); - - Neutralino.os.execCommand(command, { - background: true - }).then((result) => { - this._id = result.pid; - }); - - this.paused = false; - } - } - - /** - * Close downloading stream - */ - public close(forced: boolean = false) - { - Neutralino.os.execCommand(`kill ${forced ? '-9' : '-15'} ${this._id}`); - } -} - -export default class Downloader -{ - protected static streams: Stream[] = []; - - /** - * Download file - * - * @param uri file's uri to download - * @param output relative or absolute path to the file to save it as - * - * @returns downloading stream - */ - public static async download(uri: string, output: string|null = null): Promise<Stream> - { - return new Promise(async (resolve) => { - fetch(uri).then((response) => { - const stream = new Stream(uri, output ?? this.fileFromUri(uri), response.length!); - - this.streams.push(stream); - - resolve(stream); - }); - }); - } - - /** - * Close every open downloading stream - */ - public static closeStreams(forced: boolean = false) - { - this.streams.forEach((stream) => { - stream.close(forced); - }); - } - - /** - * Get a file name from the URI - */ - public static fileFromUri(uri: string): string - { - const file = uri.split('/').pop()!.split('#')[0].split('?')[0]; - - if (file === '') - return 'index.html'; - - else if (`https://${file}` != uri && `http://${file}` != uri) - return file; - - else return 'index.html'; - } -}; - -export { Stream }; diff --git a/src/ts/core/Fetch.ts b/src/ts/core/Fetch.ts deleted file mode 100644 index c4115d7..0000000 --- a/src/ts/core/Fetch.ts +++ /dev/null @@ -1,79 +0,0 @@ -declare const Neutralino; - -class Response -{ - /** - * Requested url - */ - public readonly url: string; - - /** - * HTTP status code - */ - public readonly status: number|null; - - /** - * Content length - */ - public readonly length: number|null; - - /** - * Represents whether the response was successful (status in the range 200-299) or not - */ - public readonly ok: boolean; - - public constructor(url: string, status: number|null, length: number|null) - { - this.url = url; - this.status = status; - this.length = length; - - // https://developer.mozilla.org/en-US/docs/Web/API/Response/ok - this.ok = status! >= 200 && status! <= 299; - } - - /** - * Get request's body - * - * @param delay maximal request delay in milliseconds - */ - public body(delay: number|null = null): Promise<string> - { - return new Promise((resolve) => { - Neutralino.os.execCommand(`curl -s -L ${delay !== null ? `-m ${delay / 1000}` : ''} "${this.url}"`) - .then((output) => resolve(output.stdOut)); - }); - } -} - -/** - * Fetch data from the URL - * - * @param delay maximal request delay in milliseconds - */ -export default function fetch(url: string, delay: number|null = null): Promise<Response> -{ - return new Promise(async (resolve) => { - let header = await Neutralino.os.execCommand(`curl -s -I -L ${delay !== null ? `-m ${delay / 1000}` : ''} "${url}"`); - - if (header.stdOut == '') - header = header.stdErr; - - else header = header.stdOut; - - header = header.split(/^HTTP\/[\d]+ /mi).pop(); - - let status: any = /^([\d]+)[\s]+$/m.exec(header); - let length: any = /^content-length: ([\d]+)/mi.exec(header); - - if (status !== null) - status = parseInt(status[1]); - - if (length !== null) - length = parseInt(length[1]); - - resolve(new Response(url, status, length)); - }); -}; - -export { Response }; diff --git a/src/ts/core/Git.ts b/src/ts/core/Git.ts index 6c9a59c..24b2cfe 100644 --- a/src/ts/core/Git.ts +++ b/src/ts/core/Git.ts @@ -1,4 +1,4 @@ -import Process from '../neutralino/Process'; +import { path } from '../../empathize'; type Tag = { tag: string, @@ -17,7 +17,7 @@ export default class Git public static getTags(repository: string): Promise<Tag[]> { return new Promise(async (resolve) => { - const output = await Neutralino.os.execCommand(`git ls-remote --tags "${Process.addSlashes(repository)}"`); + const output = await Neutralino.os.execCommand(`git ls-remote --tags "${path.addSlashes(repository)}"`); let tags: Tag[] = []; diff --git a/src/ts/core/IPC.ts b/src/ts/core/IPC.ts deleted file mode 100644 index 3173824..0000000 --- a/src/ts/core/IPC.ts +++ /dev/null @@ -1,101 +0,0 @@ -import constants from '../Constants'; - -declare const Neutralino; - -class IPCRecord -{ - public readonly id: number; - public readonly time: number; - public readonly data: any; - - public constructor(id: number, time: number, data: any) - { - this.id = id; - this.time = time; - this.data = data; - } - - /** - * Remove the record from the storage - */ - public pop(): IPCRecord - { - IPC.remove(this); - - return this; - } - - public get(): { id: number; time: number; data: any} - { - return { - id: this.id, - time: this.time, - data: this.data - }; - } -} - -export default class IPC -{ - /** - * Read records from the "shared inter-process storage" - */ - public static read(): Promise<IPCRecord[]> - { - return new Promise(async (resolve) => { - Neutralino.filesystem.readFile(`${await constants.paths.launcherDir}/.ipc.json`) - .then((data) => resolve(JSON.parse(data).map((record) => new IPCRecord(record.id, record.time, record.data)))) - .catch(() => resolve([])); - }); - } - - /** - * Write some data to the "shared inter-process storage" - */ - public static write(data: any): Promise<void> - { - return new Promise(async (resolve) => { - const records = await this.read(); - - records.push({ - id: Math.round(Math.random() * 100000), - time: Date.now(), - data: data - } as IPCRecord); - - await Neutralino.filesystem.writeFile(`${await constants.paths.launcherDir}/.ipc.json`, JSON.stringify(records)); - - resolve(); - }); - } - - /** - * Remove record from the "shared inter-process storage" - */ - public static remove(record: IPCRecord): Promise<void> - { - return new Promise(async (resolve) => { - let records = await this.read(); - - records = records.filter((item) => item.id !== record.id || item.time !== record.time); - - await Neutralino.filesystem.writeFile(`${await constants.paths.launcherDir}/.ipc.json`, JSON.stringify(records)); - - resolve(); - }); - } - - /** - * Remove all the record from the "shared inter-process storage" - */ - public static purge(): Promise<void> - { - return new Promise(async (resolve) => { - Neutralino.filesystem.removeFile(`${await constants.paths.launcherDir}/.ipc.json`) - .then(() => resolve()) - .catch(() => resolve()); - }); - } -}; - -export { IPCRecord }; diff --git a/src/ts/core/Notifications.ts b/src/ts/core/Notifications.ts deleted file mode 100644 index d979f8d..0000000 --- a/src/ts/core/Notifications.ts +++ /dev/null @@ -1,32 +0,0 @@ -import type { NotificationsOptions } from '../types/Notifications'; - -import Process from '../neutralino/Process'; - -declare const Neutralino; - -export default class Notifications -{ - /** - * Show notification - */ - public static show(options: NotificationsOptions) - { - let command = `notify-send "${Process.addSlashes(options.title)}" "${Process.addSlashes(options.body)}"`; - - // Specify notification icon - if (options.icon) - command += ` -i "${Process.addSlashes(options.icon)}"`; - - // Specify notification duration - if (options.duration) - command += ` -d ${options.duration}`; - - // Specify notification importance - if (options.importance) - command += ` -u ${options.importance}`; - - Neutralino.os.execCommand(command, { - background: true - }); - } -}; diff --git a/src/ts/core/Prefix.ts b/src/ts/core/Prefix.ts index edf2876..d480ac6 100644 --- a/src/ts/core/Prefix.ts +++ b/src/ts/core/Prefix.ts @@ -1,7 +1,7 @@ +import { Process, Downloader, Debug, path } from '../../empathize'; +import { DebugThread } from '@empathize/framework/dist/meta/Debug'; + import constants from '../Constants'; -import Process from '../neutralino/Process'; -import Debug, { DebugThread } from './Debug'; -import Downloader from './Downloader'; import Runners from './Runners'; declare const Neutralino; @@ -52,7 +52,7 @@ export default class Prefix .catch(() => { Downloader.download(constants.uri.winetricks, winetricksPath).then((stream) => { stream.finish(async () => { - await Neutralino.os.execCommand(`chmod +x "${Process.addSlashes(winetricksPath)}"`); + await Neutralino.os.execCommand(`chmod +x "${path.addSlashes(winetricksPath)}"`); resolve(winetricksPath); }); @@ -64,12 +64,12 @@ export default class Prefix /** * Create wine prefix using the current selected wine * - * @param path folder to create prefix in + * @param folder folder to create prefix in * @param progress function that will be called with every creation step * * @returns false if there's no selected wine version. Otherwise true */ - public static create(path: string, progress?: (output: string, current: number, total: number) => void): Promise<boolean> + public static create(folder: string, progress?: (output: string, current: number, total: number) => void): Promise<boolean> { const debugThread = new DebugThread('Prefix.create', 'Creating wine prefix'); @@ -108,11 +108,11 @@ 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(`"${path.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}`, - WINEPREFIX: path + WINEPREFIX: folder } }); diff --git a/src/ts/core/Runners.ts b/src/ts/core/Runners.ts index 1a475d1..6a6ef63 100644 --- a/src/ts/core/Runners.ts +++ b/src/ts/core/Runners.ts @@ -3,11 +3,11 @@ import type { RunnerFamily } from '../types/Runners'; +import { Configs, Process, path } from '../../empathize'; +import { DebugThread } from '@empathize/framework/dist/meta/Debug'; + import constants from '../Constants'; -import Configs from '../Configs'; import AbstractInstaller from './AbstractInstaller'; -import Process from '../neutralino/Process'; -import { DebugThread } from './Debug'; declare const Neutralino; @@ -149,7 +149,7 @@ class Runners const name = typeof runner !== 'string' ? runner.name : runner; - Process.run(`rm -rf "${Process.addSlashes(await constants.paths.runnersDir + '/' + name)}"`) + Process.run(`rm -rf "${path.addSlashes(await constants.paths.runnersDir + '/' + name)}"`) .then((process) => { process.finish(() => { debugThread.log('Runner deleted'); diff --git a/src/ts/core/promisify.ts b/src/ts/core/promisify.ts deleted file mode 100644 index b60e499..0000000 --- a/src/ts/core/promisify.ts +++ /dev/null @@ -1,83 +0,0 @@ -type callback = () => any; - -type PromiseOptions = { - callbacks: callback[]|Promise<any>[]; - - /** - * If true, then all the callbacks will be called - * at the same time and promisify will be resolved - * when all of them have finished - * - * Otherwise, callbacks will be called one after the other - * and promisify will be resolved with the last one - */ - callAtOnce?: boolean; - - /** - * [callAtOnce: true] updates interval in ms - * - * @default 100 - */ - interval?: number; -}; - -/** - * Make a promise from the provided function(s) and run it(them) - */ -export default function promisify(callback: callback|Promise<any>|PromiseOptions): Promise<any> -{ - return new Promise(async (resolve) => { - // promisify(() => { ... }) - if (typeof callback === 'function') - resolve(await Promise.resolve(callback())); - - // promisify(new Promise(...)) - else if (typeof callback['then'] === 'function') - resolve(await callback); - - // promisify({ callbacks: [ ... ] }) - else - { - let outputs = {}; - - // @ts-expect-error - if (callback.callAtOnce) - { - // @ts-expect-error - let remained = callback.callbacks.length; - - // @ts-expect-error - for (let i = 0; i < callback.callbacks.length; ++i) // @ts-expect-error - promisify(callback.callbacks[i]).then((output) => { - outputs[i] = output; - - --remained; - }); - - const updater = () => { - if (remained > 0) // @ts-expect-error - setTimeout(updater, callback.interval ?? 100); - - else resolve(outputs); - }; - - // @ts-expect-error - setTimeout(updater, callback.interval ?? 100); - } - - else - { - // @ts-expect-error - for (let i = 0; i < callback.callbacks.length; ++i) // @ts-expect-error - outputs[i] = await promisify(callback.callbacks[i]); - - resolve(outputs); - } - } - }); -}; - -export type { - PromiseOptions, - callback -}; diff --git a/src/ts/launcher/Background.ts b/src/ts/launcher/Background.ts index 24eb7e9..1f2a0d1 100644 --- a/src/ts/launcher/Background.ts +++ b/src/ts/launcher/Background.ts @@ -1,5 +1,6 @@ +import { fetch } from '../../empathize'; + import constants from '../Constants'; -import fetch from '../core/Fetch'; import Locales from './Locales'; export default class Background diff --git a/src/ts/launcher/Locales.ts b/src/ts/launcher/Locales.ts index fd3b3c7..e1674ad 100644 --- a/src/ts/launcher/Locales.ts +++ b/src/ts/launcher/Locales.ts @@ -2,9 +2,9 @@ import { dictionary, locale } from 'svelte-i18n'; import YAML from 'yaml'; +import { promisify, Configs } from '../../empathize'; + import constants from '../Constants'; -import promisify from '../core/promisify'; -import Configs from '../Configs'; type AvailableLocales = | 'en-us' diff --git a/src/ts/launcher/Shaders.ts b/src/ts/launcher/Shaders.ts index ef070de..1cc2bfc 100644 --- a/src/ts/launcher/Shaders.ts +++ b/src/ts/launcher/Shaders.ts @@ -2,9 +2,9 @@ import YAML from 'yaml'; import type { Shader } from '../types/Shaders'; -import Configs from '../Configs'; +import { Configs, promisify } from '../../empathize'; + import constants from '../Constants'; -import promisify from '../core/promisify'; declare const Neutralino; diff --git a/src/ts/launcher/State.ts b/src/ts/launcher/State.ts index d8bfbb6..db83153 100644 --- a/src/ts/launcher/State.ts +++ b/src/ts/launcher/State.ts @@ -3,22 +3,20 @@ import { dictionary, locale } from 'svelte-i18n'; import semver from 'semver'; -import type { LauncherState } from '../types/Launcher'; +import { Windows, Debug, IPC, Notification } from '../../empathize'; +import { DebugThread } from '@empathize/framework/dist/meta/Debug'; -import Window from '../neutralino/Window'; +import type { LauncherState } from '../types/Launcher'; import Launcher from '../Launcher'; import Game from '../Game'; import Patch from '../Patch'; import Voice from '../Voice'; import Runners from '../core/Runners'; -import Debug, { DebugThread } from '../core/Debug'; import DXVK from '../core/DXVK'; -import IPC from '../core/IPC'; import Locales from './Locales'; import Git from '../core/Git'; import constants from '../Constants'; -import Notifications from '../core/Notifications'; export default class State { @@ -97,8 +95,8 @@ export default class State this.update().then(async () => { IPC.write('launcher-loaded'); - await Window.current.show(); - await Window.current.center(1280, 700); + await Windows.current.show(); + // FIXME: await Windows.current.center(1280, 700); // Check for new versions of the launcher Git.getTags(constants.uri.launcher).then((tags) => { @@ -110,7 +108,7 @@ export default class State const locales = (currentDictionary[currentLocale ?? 'en-us'] ?? currentDictionary['en-us'])['launcher']!['update'] as object; - Notifications.show({ + Notification.show({ title: locales['title'].replace('{from}', Launcher.version).replace('{to}', tag.tag), body: locales['body'].replace('{repository}', constants.uri.launcher), icon: `${constants.paths.appDir}/public/images/baal64-transparent.png` @@ -128,7 +126,7 @@ export default class State else { - Window.current.setSize({ + Windows.current.setSize({ width: 1280 + (1280 - window.innerWidth), height: 700 + (700 - window.innerHeight), resizable: false @@ -400,7 +398,7 @@ export default class State { state = 'game-launch-available'; - Notifications.show({ + Notification.show({ title: 'An Anime Game Launcher', body: 'All the patch repositories are not available. You\'ll be able to run the game, but launcher can\'t be sure is it patched properly', icon: `${constants.paths.appDir}/public/images/baal64-transparent.png`, diff --git a/src/ts/launcher/states/ApplyPatch.ts b/src/ts/launcher/states/ApplyPatch.ts index 8875d23..80f7d19 100644 --- a/src/ts/launcher/states/ApplyPatch.ts +++ b/src/ts/launcher/states/ApplyPatch.ts @@ -1,6 +1,7 @@ +import { Notification } from '../../../empathize'; + import Launcher from '../../Launcher'; import Patch from '../../Patch'; -import Notifications from '../../core/Notifications'; import constants from '../../Constants'; export default (launcher: Launcher): Promise<void> => { @@ -8,7 +9,7 @@ export default (launcher: Launcher): Promise<void> => { // Show an error notification if xdelta3 package is not installed if (!await Launcher.isPackageAvailable('xdelta3')) { - Notifications.show({ + Notification.show({ title: 'An Anime Game Launcher', body: 'You must download xdelta3 package to apply the patch', icon: `${constants.paths.appDir}/public/images/baal64-transparent.png`, @@ -76,7 +77,7 @@ export default (launcher: Launcher): Promise<void> => { // If for some reasons patch wasn't applied successfully if (!result) { - Notifications.show({ + Notification.show({ title: 'An Anime Game Launcher', body: 'Patch wasn\'t applied successfully. Please, check your log file to find a reason of it, or ask someone in our discord server', icon: `${constants.paths.appDir}/public/images/baal64-transparent.png` @@ -89,7 +90,7 @@ export default (launcher: Launcher): Promise<void> => { }); } }).catch(() => { - Notifications.show({ + Notification.show({ title: 'An Anime Game Launcher', body: 'All the patch repositories are not available. You\'ll be able to run the game, but launcher can\'t be sure is it patched properly', icon: `${constants.paths.appDir}/public/images/baal64-transparent.png`, diff --git a/src/ts/launcher/states/Install.ts b/src/ts/launcher/states/Install.ts index 40b80db..b38bb6c 100644 --- a/src/ts/launcher/states/Install.ts +++ b/src/ts/launcher/states/Install.ts @@ -3,10 +3,11 @@ import { _ } from 'svelte-i18n'; import type Launcher from '../../Launcher'; +import { Debug } from '../../../empathize'; + import Game from '../../Game'; import Prefix from '../../core/Prefix'; import constants from '../../Constants'; -import Debug from '../../core/Debug'; declare const Neutralino; diff --git a/src/ts/launcher/states/InstallVoice.ts b/src/ts/launcher/states/InstallVoice.ts index 0d8182c..8e801e7 100644 --- a/src/ts/launcher/states/InstallVoice.ts +++ b/src/ts/launcher/states/InstallVoice.ts @@ -4,8 +4,9 @@ import { _ } from 'svelte-i18n'; import type Launcher from '../../Launcher'; import type { VoiceLang } from '../../types/Voice'; +import { promisify } from '../../../empathize'; + import Voice from '../../Voice'; -import promisify from '../../core/promisify'; import Game from '../../Game'; export default (launcher: Launcher): Promise<void> => { diff --git a/src/ts/launcher/states/Launch.ts b/src/ts/launcher/states/Launch.ts index e3c5f42..965e910 100644 --- a/src/ts/launcher/states/Launch.ts +++ b/src/ts/launcher/states/Launch.ts @@ -1,11 +1,8 @@ -import Process from '../../neutralino/Process'; -import Window from '../../neutralino/Window'; +import { Process, Windows, Configs, Notification, path } from '../../../empathize'; +import { DebugThread } from '@empathize/framework/dist/meta/Debug'; import Launcher from '../../Launcher'; -import Configs from '../../Configs'; import constants from '../../Constants'; -import { DebugThread } from '../../core/Debug'; -import Notifications from '../../core/Notifications'; import Runners from '../../core/Runners'; import Game from '../../Game'; @@ -20,7 +17,7 @@ export default (launcher: Launcher): Promise<void> => { // If telemetry servers are not disabled if (!telemetry) { - Notifications.show({ + Notification.show({ title: 'An Anime Game Launcher', body: 'Telemetry servers are not disabled', icon: `${constants.paths.appDir}/public/images/baal64-transparent.png`, @@ -33,7 +30,7 @@ export default (launcher: Launcher): Promise<void> => { // Otherwise run the game else { - Window.current.hide(); + Windows.current.hide(); launcher.updateDiscordRPC('in-game'); @@ -139,7 +136,7 @@ export default (launcher: Launcher): Promise<void> => { else console.warn(`GPU ${LauncherLib.getConfig('gpu')} not found. Launching on the default GPU`); }*/ - let command = `"${Process.addSlashes(wineExeutable)}" ${await Configs.get('fps_unlocker') ? 'unlockfps.bat' : 'launcher.bat'}`; + let command = `"${path.addSlashes(wineExeutable)}" ${await Configs.get('fps_unlocker') ? 'unlockfps.bat' : 'launcher.bat'}`; /** * Gamemode integration @@ -208,8 +205,8 @@ export default (launcher: Launcher): Promise<void> => { { const stopTime = Date.now(); - Window.current.show(); - Window.current.center(1280, 700); + Windows.current.show(); + // FIXME: Windows.current.center(1280, 700); launcher.updateDiscordRPC('in-launcher'); launcher.tray.hide(); @@ -218,7 +215,7 @@ export default (launcher: Launcher): Promise<void> => { Configs.get('purge_logs.game').then(async (purge_logs) => { if (purge_logs) { - const gameDir = Process.addSlashes(await constants.paths.gameDir); + 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`); diff --git a/src/ts/launcher/states/PredownloadVoice.ts b/src/ts/launcher/states/PredownloadVoice.ts index 9aaa3c2..4c441f4 100644 --- a/src/ts/launcher/states/PredownloadVoice.ts +++ b/src/ts/launcher/states/PredownloadVoice.ts @@ -1,8 +1,9 @@ import type Launcher from '../../Launcher'; import type { VoiceLang } from '../../types/Voice'; +import { promisify } from '../../../empathize'; + import Voice from '../../Voice'; -import promisify from '../../core/promisify'; export default (launcher: Launcher): Promise<void> => { return new Promise(async (resolve) => { diff --git a/src/ts/neutralino/Process.ts b/src/ts/neutralino/Process.ts deleted file mode 100644 index 1f827ec..0000000 --- a/src/ts/neutralino/Process.ts +++ /dev/null @@ -1,267 +0,0 @@ -import constants from '../Constants'; -import Debug, { DebugThread } from "../core/Debug"; - -declare const Neutralino; -declare const NL_CWD; - -type ProcessOptions = { - /** - * Environment variables - */ - env?: object; - - /** - * Current working directory for the running process - */ - cwd?: string; - - /** - * Interval between tries to find started process id - * - * @default 50 - */ - childInterval?: number; -}; - -class Process -{ - /** - * Process ID - */ - public readonly id: number; - - /** - * Interval in ms between process status update - * - * null if you don't want to update process status - * - * @default 200 - */ - public runningInterval: number|null = 200; - - /** - * Interval in ms between process output update - * - * null if you don't want to update process output - * - * @default 500 - */ - public outputInterval: number|null = 500; - - protected outputFile: string|null; - protected outputOffset: number = 0; - - protected _finished: boolean = false; - - /** - * Whether the process was finished - */ - public get finished(): boolean - { - return this._finished; - }; - - protected onOutput?: (output: string, process: Process) => void; - protected onFinish?: (process: Process) => void; - - public constructor(pid: number, outputFile: string|null = null) - { - const debugThread = new DebugThread('Process/Stream', `Opened process ${pid} stream`); - - this.id = pid; - this.outputFile = outputFile; - - const updateStatus = () => { - this.running().then((running) => { - // The process is still running - if (running) - { - if (this.runningInterval) - setTimeout(updateStatus, this.runningInterval); - } - - // Otherwise the process was stopped - else - { - this._finished = true; - - debugThread.log('Process stopped'); - - if (this.onFinish) - this.onFinish(this); - } - }); - }; - - if (this.runningInterval) - setTimeout(updateStatus, this.runningInterval); - - if (this.outputFile) - { - const updateOutput = () => { - Neutralino.filesystem.readFile(this.outputFile) - .then((output: string) => { - if (this.onOutput) - this.onOutput(output.substring(this.outputOffset), this); - - this.outputOffset = output.length; - - if (this._finished) - { - if (output !== '') - { - debugThread.log({ - message: [ - 'Process output:', - ...output.split(/\r\n|\r|\n/) - ] - }); - } - - Neutralino.filesystem.removeFile(this.outputFile); - } - - else if (this.outputInterval) - setTimeout(updateOutput, this.outputInterval); - }) - .catch(() => { - if (this.outputInterval && !this._finished) - setTimeout(updateOutput, this.outputInterval); - }); - }; - - if (this.outputInterval) - setTimeout(updateOutput, this.outputInterval); - } - } - - /** - * Specify callback to run when the process will be finished - */ - public finish(callback: (process: Process) => void) - { - this.onFinish = callback; - - if (this._finished) - callback(this); - - // If user stopped process status auto-checking - // then we should check it manually when this method was called - else if (this.runningInterval === null) - { - this.running().then((running) => { - if (!running) - { - this._finished = true; - - callback(this); - } - }); - } - } - - public output(callback: (output: string, process: Process) => void) - { - this.onOutput = callback; - } - - /** - * Kill process - */ - public kill(forced: boolean = false): Promise<void> - { - Neutralino.filesystem.removeFile(this.outputFile); - - return Process.kill(this.id, forced); - } - - /** - * Returns whether the process is running - * - * This method doesn't call onFinish event - */ - public running(): Promise<boolean> - { - return new Promise((resolve) => { - Neutralino.os.execCommand(`ps -p ${this.id} -S`).then((output) => { - resolve(output.stdOut.includes(this.id) && !output.stdOut.includes('Z ')); - }); - }); - } - - /** - * Run shell command - */ - public static run(command: string, options: ProcessOptions = {}): Promise<Process> - { - return new Promise(async (resolve) => { - const tmpFile = `${await constants.paths.tempDir}/${10000 + Math.round(Math.random() * 89999)}.tmp`; - - // Set env variables - if (options.env) - for (const key of Object.keys(options.env)) - command = `${key}="${this.addSlashes(options.env![key].toString())}" ${command}`; - - // Set output redirection to the temp file - command = `${command} > "${this.addSlashes(tmpFile)}" 2>&1`; - - // Set current working directory - if (options.cwd) - command = `cd "${this.addSlashes(options.cwd)}" && ${command}`; - - // And run the command - const process = await Neutralino.os.execCommand(command, { - background: true - }); - - const childFinder = async () => { - const childProcess = await Neutralino.os.execCommand(`pgrep -P ${process.pid}`); - - // Child wasn't found - if (childProcess.stdOut == '') - setTimeout(childFinder, options.childInterval ?? 50); - - // Otherwise return its id - else - { - const processId = parseInt(childProcess.stdOut.substring(0, childProcess.stdOut.length - 1)); - - Debug.log({ - function: 'Process.run', - message: { - 'running command': command, - 'cwd': options.cwd, - 'initial process id': process.pid, - 'real process id': processId, - ...options.env - } - }); - - resolve(new Process(processId, tmpFile)); - } - }; - - setTimeout(childFinder, options.childInterval ?? 50); - }); - } - - public static kill(id: number, forced: boolean = false): Promise<void> - { - return new Promise((resolve) => { - Neutralino.os.execCommand(`kill ${forced ? '-9' : '-15'} ${id}`).then(() => resolve()); - }); - } - - /** - * Replace '\a\b' to '\\a\\b' - * And replace ''' to '\'' - */ - public static addSlashes(str: string): string - { - return str.replaceAll('\\', '\\\\').replaceAll('"', '\\"'); - } -} - -export type { ProcessOptions }; - -export default Process; diff --git a/src/ts/neutralino/Tray.ts b/src/ts/neutralino/Tray.ts deleted file mode 100644 index c02b4d0..0000000 --- a/src/ts/neutralino/Tray.ts +++ /dev/null @@ -1,120 +0,0 @@ -declare const Neutralino; - -type Item = { - /** - * Item text - */ - text: string; - - /** - * Item id - */ - id?: string; - - /** - * Whether the item disabled or not - * - * If yes, then it will be a string - */ - disabled?: boolean; - - /** - * Is this item a checkbox or not - */ - checked?: boolean; - - /** - * Event on click - * - * If specified, then will generate random - * item id if it is not specified - */ - click?: (item: Item) => void; -}; - -Neutralino.events.on('trayMenuItemClicked', (item) => { - for (const tray of Tray.trays) - for (const trayItem of tray.items) - if (trayItem.id === item.detail.id) - { - if (trayItem.click) - { - trayItem.click({ - id: item.detail.id, - text: item.detail.text, - disabled: item.detail['isDisabled'], - checked: item.detail['isChecked'], - click: trayItem.click - }); - } - - return; - } -}); - -export default class Tray -{ - public static trays: Tray[] = []; - - public icon: string; - - protected _items: Item[] = []; - - public get items(): Item[] - { - return this._items.map((item) => { - return { - id: item.id, - text: item.text, - disabled: item['isDisabled'], - checked: item['isChecked'], - click: item.click - }; - }); - } - - public set items(items: Item[]) - { - this._items = items.map((item) => { - if (item.id === undefined && item.click !== undefined) - item.id = 'click:' + Math.random().toString().substring(2); - - return { - id: item.id, - text: item.text, - isDisabled: item.disabled, - isChecked: item.checked, - click: item.click - }; - }); - } - - public constructor(icon: string, items: Item[] = []) - { - this.icon = icon; - this.items = items; - - Tray.trays.push(this); - } - - public update(items: Item[]|null = null): Promise<void> - { - if (items !== null) - this.items = items; - - return Neutralino.os.setTray({ - icon: this.icon, - menuItems: this._items - }); - } - - public hide(): Promise<void> - { - return Neutralino.os.setTray({ - icon: this.icon, - menuItems: [] - }); - } -}; - -export type { Item }; diff --git a/src/ts/neutralino/Window.ts b/src/ts/neutralino/Window.ts deleted file mode 100644 index 440b650..0000000 --- a/src/ts/neutralino/Window.ts +++ /dev/null @@ -1,82 +0,0 @@ -type WindowSize = { - width?: number; - height?: number; - minWidth?: number; - minHeight?: number; - maxWidth?: number; - maxHeight?: number; - resizable?: boolean; -}; - -type WindowOptions = WindowSize & { - title?: string; - icon?: string; - fullScreen?: boolean; - alwaysOnTop?: boolean; - enableInspector?: boolean; - borderless?: boolean; - maximize?: boolean; - hidden?: boolean; - maximizable?: boolean; - exitProcessOnClose?: boolean; - processArgs?: string; -}; - -type WindowOpenResult = { - status: boolean; - data?: { - pid: number; - stdOut: string; - stdErr: string; - exitCode: number; - }; -}; - -declare const Neutralino; - -class Window -{ - public static get current(): any - { - return { - ...Neutralino.window, - - center(windowWidth: number, windowHeight: number) - { - Neutralino.window.move(Math.round((window.screen.width - windowWidth) / 2), Math.round((window.screen.height - windowHeight) / 2)); - } - }; - } - - public static open(name: string, options: WindowOptions = {}): Promise<WindowOpenResult> - { - return new Promise(async (resolve) => { - const status = await Neutralino.window.create(`/${name}.html`, { - width: 600, - height: 400, - enableInspector: false, - exitProcessOnClose: true, - - ...options, - - // So basically you should display the window manually - // with Window.current.show() when your app will load - // all its content there - hidden: true - }); - - resolve({ - status: status !== undefined, - data: status - }); - }); - } -} - -export type { - WindowSize, - WindowOptions, - WindowOpenResult -}; - -export default Window; diff --git a/src/ts/types/Archive.d.ts b/src/ts/types/Archive.d.ts deleted file mode 100644 index 414f045..0000000 --- a/src/ts/types/Archive.d.ts +++ /dev/null @@ -1,27 +0,0 @@ -type ArchiveType = - | 'tar' - | 'zip' - | null; - -type Size = { - compressed?: number | null; - uncompressed?: number | null; -}; - -type File = { - path: string; - size: Size; -}; - -type ArchiveInfo = { - size: Size; - type: ArchiveType; - files: File[]; -}; - -export type { - ArchiveType, - Size, - File, - ArchiveInfo -}; diff --git a/src/ts/types/Debug.d.ts b/src/ts/types/Debug.d.ts deleted file mode 100644 index f8715bd..0000000 --- a/src/ts/types/Debug.d.ts +++ /dev/null @@ -1,23 +0,0 @@ -type DebugOptions = { - /** - * Some random-generated thread id - */ - thread?: number; - - /** - * Some function name - */ - function?: string; - - /** - * Some log message - */ - message: string|string[]|object; -}; - -type LogRecord = { - time: number; - log: string[]; -}; - -export type { DebugOptions, LogRecord }; diff --git a/src/ts/types/Domain.d.ts b/src/ts/types/Domain.d.ts deleted file mode 100644 index a9ef794..0000000 --- a/src/ts/types/Domain.d.ts +++ /dev/null @@ -1,8 +0,0 @@ -type DomainInfo = { - uri: string; - remoteIp?: string; - localIp?: string; - available: boolean; -}; - -export type { DomainInfo }; diff --git a/src/ts/types/Notifications.d.ts b/src/ts/types/Notifications.d.ts deleted file mode 100644 index b3e1e4c..0000000 --- a/src/ts/types/Notifications.d.ts +++ /dev/null @@ -1,24 +0,0 @@ -type NotificationsOptions = { - title: string; - body: string; - - /** - * Icon name or path - */ - icon?: string; - - /** - * Number of seconds this notification - * will be visible - */ - duration?: number; - - /** - * Importance of the notification - * - * @default "normal" - */ - importance?: 'low' | 'normal' | 'critical'; -}; - -export type { NotificationsOptions }; diff --git a/tsconfig.json b/tsconfig.json index 4b17e3a..292bfda 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -2,12 +2,12 @@ "extends": "@tsconfig/svelte/tsconfig.json", "compilerOptions": { "target": "esnext", - "useDefineForClassFields": true, "module": "esnext", "strict": true, "noImplicitAny": false, "removeComments": true, "resolveJsonModule": true, + "useDefineForClassFields": true, /** * Typecheck JS in `.svelte` and `.js` files by default. * Disable checkJs if you'd like to use dynamic types in JS.