diff --git a/README.md b/README.md index d9b1d5e..6706308 100644 --- a/README.md +++ b/README.md @@ -164,12 +164,12 @@ This is our current roadmap goals. You can find older ones [here](ROADMAP.md) * Rewrite sass code, provide more flexible theming ability * Add `svelte-i18n` * Telemetry checking +* Tooltips for some options +* Debugger * Game pre-installation * Launcher auto-updates * Statistics window -* Debugger -* Loading screen -* Tooltips for some options +* Splash screen * Theming system * Chengelog window * Default runner auto-installation @@ -180,7 +180,6 @@ This is our current roadmap goals. You can find older ones [here](ROADMAP.md) * Use `LauncherLib.getGameVersion` function instead of the `config.json`'s `version` property *(deprecated due to the new core functions)* * Add downloading pause button * Fix button flickering at start when the launcher's state updates -* Game's update pre-installation * Screenshots explorer * Add Patch category in settings menu with - Always participate in patches testing diff --git a/src/components/DXVKSelectionList.svelte b/src/components/DXVKSelectionList.svelte index 50284cf..1baaaa7 100644 --- a/src/components/DXVKSelectionList.svelte +++ b/src/components/DXVKSelectionList.svelte @@ -98,7 +98,7 @@ class:list-item-disabled={disabledDxvks[dxvk.version]} on:click|self={() => { - if (installedDxvks[dxvk.version]) + if (installedDxvks[dxvk.version] && selectedVersion !== dxvk.version) selectDxvk(dxvk); }} > diff --git a/src/components/RunnerSelectionList.svelte b/src/components/RunnerSelectionList.svelte index 7041969..8f5d4c1 100644 --- a/src/components/RunnerSelectionList.svelte +++ b/src/components/RunnerSelectionList.svelte @@ -78,7 +78,7 @@ class:list-item-disabled={disabledRunners[runner.name]} on:click|self={() => { - if (installedRunners[runner.name]) + if (installedRunners[runner.name] && selectedVersion !== runner.name) { selectedVersion = runner.name; diff --git a/src/pages/index.ts b/src/pages/index.ts index ec5eb75..8334840 100644 --- a/src/pages/index.ts +++ b/src/pages/index.ts @@ -1,6 +1,8 @@ import '../i18n'; import App from '../index.svelte'; +import constants from '../ts/Constants'; import Archive from '../ts/core/Archive'; +import Debug from '../ts/core/Debug'; import Downloader from '../ts/core/Downloader'; declare const Neutralino; @@ -13,7 +15,26 @@ Neutralino.events.on('windowClose', () => { Downloader.closeStreams(true); Archive.closeStreams(true); - Neutralino.app.exit(); + constants.paths.launcherDir.then(async (path) => { + const time = new Date; + + Neutralino.filesystem.getStats(`${path}/logs`) + .then(() => saveLog()) + .catch(async () => { + await Neutralino.filesystem.createDirectory(`${path}/logs`); + + saveLog(); + }); + + const saveLog = async () => { + const log = Debug.get().join("\r\n"); + + if (log != '') + await Neutralino.filesystem.writeFile(`${path}/logs/${time.getDay()}-${time.getMonth()}-${time.getFullYear()}-${time.getHours()}-${time.getMinutes()}-${time.getSeconds()}.log`, log); + + Neutralino.app.exit(); + }; + }); }); const app = new App({ diff --git a/src/pages/settings.ts b/src/pages/settings.ts index da15863..53ce02b 100644 --- a/src/pages/settings.ts +++ b/src/pages/settings.ts @@ -1,10 +1,18 @@ import '../i18n'; +import Debug from '../ts/core/Debug'; + import App from '../settings.svelte'; declare const Neutralino; Neutralino.init(); +Neutralino.events.on('windowClose', async () => { + await Neutralino.storage.setData('log', JSON.stringify(Debug.getRecords())); + + Neutralino.app.exit(); +}); + const app = new App({ target: document.getElementById('app')! }); diff --git a/src/ts/Game.ts b/src/ts/Game.ts index 9720c97..d04ab16 100644 --- a/src/ts/Game.ts +++ b/src/ts/Game.ts @@ -10,6 +10,7 @@ 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'; declare const Neutralino; @@ -42,6 +43,11 @@ export default class Game const buffer = new TextDecoder('ascii').decode(new Uint8Array(config)); const version = /([1-9]+\.[0-9]+\.[0-9]+)_[\d]+_[\d]+/.exec(buffer); + Debug.log({ + function: 'Game.current', + message: `Current game version: ${version !== null ? version[1] : ''}` + }); + resolve(version !== null ? version[1] : null); }) .catch(() => resolve(null)); @@ -140,6 +146,12 @@ export default class Game */ public static update(version: string|null = null): Promise { + Debug.log( + version !== null ? + `Updating the game from the ${version} version` : + 'Installing the game' + ); + return new Promise((resolve, reject) => { (version === null ? this.latest : this.getDiff(version)) .then((data: Latest|Diff|null) => resolve(data === null ? null : new Stream(data.path))) @@ -152,6 +164,8 @@ export default class Game */ public static isTelemetryDisabled(): Promise { + const debugThread = new DebugThread('Game.isTelemetryDisabled', 'Checking if the telemetry servers are disabled'); + return new Promise(async (resolve) => { const pipeline = promisify({ callbacks: await constants.uri.telemetry.map((domain) => { @@ -168,6 +182,8 @@ export default class Game Object.values(result).forEach((value) => disabled ||= value as boolean); + debugThread.log(`Telemetry is ${disabled ? 'not ' : ''}disabled`); + resolve(disabled === false); }); }); diff --git a/src/ts/Launcher.ts b/src/ts/Launcher.ts index 93ab051..967c6a4 100644 --- a/src/ts/Launcher.ts +++ b/src/ts/Launcher.ts @@ -6,6 +6,9 @@ import Configs from './Configs'; import ProgressBar from './launcher/ProgressBar'; import State from './launcher/State'; +import Debug from './core/Debug'; + +declare const Neutralino; export default class Launcher { @@ -36,7 +39,8 @@ export default class Launcher title: 'Settings', width: 900, height: 600, - enableInspector: true + enableInspector: true, + exitProcessOnClose: false }); if (window.status) @@ -45,6 +49,14 @@ export default class Launcher this.settingsMenu.finish(() => { this.settingsMenu = undefined; + + Neutralino.storage.getData('log') + .then((data) => { + Debug.merge(JSON.parse(data)); + + Neutralino.storage.setData('log', undefined); + }) + .catch(() => {}); Window.current.show(); }) diff --git a/src/ts/Patch.ts b/src/ts/Patch.ts index c26ea9f..6ed562e 100644 --- a/src/ts/Patch.ts +++ b/src/ts/Patch.ts @@ -8,6 +8,7 @@ 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'; declare const Neutralino; @@ -132,6 +133,8 @@ export default class Patch */ public static get latest(): Promise { + const debugThread = new DebugThread('Patch.latest', 'Getting the latest patch information'); + return new Promise(async (resolve, reject) => { const getLatestPatchInfo = (versions: string[], source: 'origin' | 'additional'): Promise => { return new Promise(async (resolve) => { @@ -145,7 +148,12 @@ export default class Patch resolve(await getLatestPatchInfo(versions.slice(1), 'origin')); // Otherwise - return found info - else resolve(patchInfo); + else + { + debugThread.log({ message: patchInfo }); + + resolve(patchInfo); + } }) .catch(async (error) => { // If we couldn't connect to the origin repo @@ -271,6 +279,11 @@ export default class Patch */ public static install(): Promise { + Debug.log({ + function: 'Patch.install', + message: 'Installing the patch...' + }); + return new Promise((resolve, reject) => { this.latest .then((patch) => { diff --git a/src/ts/Voice.ts b/src/ts/Voice.ts index 94ec4f7..ae511f5 100644 --- a/src/ts/Voice.ts +++ b/src/ts/Voice.ts @@ -5,6 +5,7 @@ import constants from './Constants'; import Game from './Game'; import AbstractInstaller from './core/AbstractInstaller'; import Configs from './Configs'; +import Debug from './core/Debug'; declare const Neutralino; @@ -41,23 +42,36 @@ export default class Voice // Parse installed voice packages Neutralino.filesystem.readDirectory(await constants.paths.voiceDir) .then((files) => { - files = files - .filter((file) => file.type == 'DIRECTORY') + files = files.filter((file) => file.type == 'DIRECTORY') .map((file) => file.entry); Object.keys(langs).forEach((folder) => { - if (files.includes(folder) && langs[folder] !== undefined) + if (files.includes(folder)) installedVoice.installed.push(langs[folder]); }); + + parseActiveVoice(); }) - .catch(() => {}); + .catch(() => parseActiveVoice()); // Parse active voice package - Neutralino.filesystem.readFile(persistentPath) - .then((lang) => installedVoice.active = langs[lang] ?? null) - .catch(() => {}); + const parseActiveVoice = () => { + Neutralino.filesystem.readFile(persistentPath) + .then((lang) => { + installedVoice.active = langs[lang] ?? null; - resolve(installedVoice); + Debug.log({ + function: 'Voice.current', + message: { + 'active voice': installedVoice.active, + 'installed voices': installedVoice.installed.join(', ') + } + }); + + resolve(installedVoice); + }) + .catch(() => resolve(installedVoice)); + }; }); } @@ -106,6 +120,12 @@ export default class Voice */ public static update(lang: string|null = null, version: string|null = null): Promise { + Debug.log( + version !== null ? + `Updating the voice package from the ${version} version` : + 'Installing the voice package' + ); + return new Promise((resolve, reject) => { (version === null ? this.latest : this.getDiff(version)) .then((data: VoicePack[]|null) => { diff --git a/src/ts/core/AbstractInstaller.ts b/src/ts/core/AbstractInstaller.ts index f8187fb..ddbf409 100644 --- a/src/ts/core/AbstractInstaller.ts +++ b/src/ts/core/AbstractInstaller.ts @@ -1,6 +1,7 @@ import constants from '../Constants'; import Downloader from './Downloader'; import Archive from './Archive'; +import { DebugThread } from './Debug'; declare const Neutralino; @@ -33,6 +34,13 @@ export default abstract class Installer public constructor(uri: string, unpackDir: string|Promise) { + const debugThread = new DebugThread('AbstractInstaller', { + message: { + 'uri': uri, + 'unpack dir': typeof unpackDir === 'string' ? unpackDir : '' + } + }); + constants.paths.launcherDir.then((launcherDir) => { const archivePath = `${launcherDir}/${Downloader.fileFromUri(uri)}`; @@ -79,6 +87,8 @@ export default abstract class Installer this.unpackFinished = true; Neutralino.filesystem.removeFile(archivePath); + + debugThread.log('Installation finished'); if (this.onUnpackFinish) this.onUnpackFinish(); @@ -86,8 +96,15 @@ export default abstract class Installer }); }; + const shouldResolve = typeof unpackDir !== 'string'; + Promise.resolve(unpackDir) - .then((unpackDir) => unpackArchive(unpackDir)); + .then((unpackDir) => { + if (shouldResolve) + debugThread.log(`Resolved unpack dir: ${unpackDir}`); + + unpackArchive(unpackDir); + }); }); }); }); @@ -164,4 +181,4 @@ export default abstract class Installer if (this.unpackFinished) callback(); } -} +}; diff --git a/src/ts/core/Archive.ts b/src/ts/core/Archive.ts index f72eed2..b62b9d0 100644 --- a/src/ts/core/Archive.ts +++ b/src/ts/core/Archive.ts @@ -5,6 +5,7 @@ import type { ArchiveInfo } from '../types/Archive'; +import { DebugThread } from './Debug'; import promisify from './promisify'; declare const Neutralino; @@ -52,6 +53,13 @@ class Stream this.unpackDir = unpackDir; this.started = true; + const debugThread = new DebugThread('Archive/Stream', { + message: { + 'path': path, + 'unpack dir': unpackDir + } + }); + if (this.onStart) this.onStart(); @@ -83,6 +91,8 @@ class Stream this._id = result.pid; }); + debugThread.log(`Unpacking started with command: ${command}`); + const updateProgress = async () => { let difference: number = 0; let pool: any[] = []; @@ -122,6 +132,8 @@ class Stream { this.finished = true; + debugThread.log('Unpacking finished'); + if (this.onFinish) this.onFinish(); } @@ -222,6 +234,8 @@ export default class Archive */ public static getInfo(path: string): Promise { + const debugThread = new DebugThread('Archive.getInfo', `Getting info about archive: ${path}`); + return new Promise(async (resolve) => { let archive: ArchiveInfo = { size: { @@ -252,6 +266,15 @@ export default class Archive }); } + debugThread.log({ + message: { + 'type': archive.type, + 'compressed size': archive.size.compressed, + 'uncompressed size': archive.size.uncompressed, + 'files amount': archive.files.length + } + }); + resolve(archive); break; @@ -276,11 +299,22 @@ export default class Archive }); } + 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; @@ -314,7 +348,7 @@ export default class Archive stream.close(forced); }); } -} +}; export { Stream }; diff --git a/src/ts/core/DXVK.ts b/src/ts/core/DXVK.ts index a9a76c1..2448ad5 100644 --- a/src/ts/core/DXVK.ts +++ b/src/ts/core/DXVK.ts @@ -6,6 +6,7 @@ import AbstractInstaller from './AbstractInstaller'; import Process from '../neutralino/Process'; import promisify from './promisify'; import Runners from './Runners'; +import { DebugThread } from './Debug'; declare const Neutralino; @@ -133,13 +134,19 @@ export default class DXVK */ public static delete(dxvk: TDXVK|TDXVK['version']): Promise { + const debugThread = new DebugThread('DXVK.delete', `Deleting dxvk ${typeof dxvk === 'string' ? dxvk : dxvk.version}`); + return new Promise(async (resolve) => { const version = typeof dxvk !== 'string' ? dxvk.version : dxvk; Process.run(`rm -rf '${Process.addSlashes(await constants.paths.dxvksDir + '/dxvk-' + version)}'`) .then((process) => { - process.finish(() => resolve()); + process.finish(() => { + debugThread.log('Deletion completed'); + + resolve(); + }); }); }); } @@ -152,6 +159,8 @@ export default class DXVK return new Promise(async (resolve) => { const version = typeof dxvk !== 'string' ? dxvk.version : dxvk; + + const debugThread = new DebugThread('DXVK.apply', `Applying dxvk ${version}`); const dxvkDir = `${await constants.paths.dxvksDir}/dxvk-${version}`; const runner = await Runners.current(); @@ -168,22 +177,40 @@ export default class DXVK * And then run it */ (): Promise => new Promise(async (resolve) => { - Process.run(`bash '${dxvkDir}/setup_dxvk.sh' install`, { + const alias = runner ? `alias winecfg=\\'${runnerDir}/${runner.files.winecfg}\\'\\n` : ''; + + Process.run(`eval $'${alias ? alias : ''}./setup_dxvk.sh install'`, { cwd: dxvkDir, env: { WINE: runner ? `${runnerDir}/${runner.files.wine}` : 'wine', - WINECFG: runner ? `${runnerDir}/${runner.files.winecfg}` : 'winecfg', WINESERVER: runner ? `${runnerDir}/${runner.files.wineserver}` : 'wineserver', WINEPREFIX: prefix } }).then((process) => { - process.finish(() => resolve()); + let processOutput = ''; + + process.output((output) => processOutput += output); + + process.finish(() => { + debugThread.log({ + message: [ + 'Setup script output:', + ...processOutput.split(/\r\n|\r|\n/) + ] + }); + + resolve(); + }); }); }) ] }); - pipeline.then(() => resolve()); + pipeline.then(() => { + debugThread.log('Applying completed'); + + resolve(); + }); }); } } diff --git a/src/ts/core/Debug.ts b/src/ts/core/Debug.ts new file mode 100644 index 0000000..6d6ecce --- /dev/null +++ b/src/ts/core/Debug.ts @@ -0,0 +1,119 @@ +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 +{ + protected static logOutput: LogRecord[] = []; + + 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]}`); + }); + } + + this.logOutput.push(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; + } +} + +export default Debug; + +export { DebugThread }; + +export type { + DebugOptions, + LogRecord +}; diff --git a/src/ts/core/Domain.ts b/src/ts/core/Domain.ts index 7539666..6956f2c 100644 --- a/src/ts/core/Domain.ts +++ b/src/ts/core/Domain.ts @@ -1,11 +1,14 @@ import type { DomainInfo } from '../types/Domain'; import Process from '../neutralino/Process'; +import { DebugThread } from './Debug'; export default class Domain { public static getInfo(uri: string): Promise { + const debugThread = new DebugThread('Domain.getInfo', `Getting info about uri: ${uri}`); + return new Promise(async (resolve) => { const process = await Process.run(`ping -n 1 -w 1 -B ${uri}`); @@ -23,12 +26,19 @@ export default class Domain if (regex !== null) { - resolve({ + process.outputInterval = null; + process.runningInterval = null; + + const info: DomainInfo = { uri: regex[1], remoteIp: regex[2], localIp: regex[3], available: regex[2] !== regex[3] - }); + }; + + debugThread.log({ message: info }); + + resolve(info); } }); }); diff --git a/src/ts/core/Downloader.ts b/src/ts/core/Downloader.ts index a8ee28a..bd37ddc 100644 --- a/src/ts/core/Downloader.ts +++ b/src/ts/core/Downloader.ts @@ -1,3 +1,4 @@ +import { DebugThread } from './Debug'; import fetch from './Fetch'; declare const Neutralino; @@ -36,15 +37,27 @@ class Stream this.total = total; this.started = true; + const debugThread = new DebugThread('Downloader/Stream', { + message: { + 'uri': uri, + 'output file': output, + 'total size': total + } + }); + if (this.onStart) this.onStart(); - Neutralino.os.execCommand(`curl -s -L -N -o "${output}" "${uri}"`, { + const command = `curl -s -L -N -o "${output}" "${uri}"`; + + Neutralino.os.execCommand(command, { background: true }).then((result) => { this._id = result.pid; }); + debugThread.log(`Downloading started with command: ${command}`); + const updateProgress = () => { Neutralino.filesystem.getStats(output).then((stats) => { if (this.onProgress) @@ -56,6 +69,8 @@ class Stream { this.finished = true; + debugThread.log('Downloading finished'); + if (this.onFinish) this.onFinish(); } @@ -166,6 +181,6 @@ export default class Downloader else return 'index.html'; } -} +}; export { Stream }; diff --git a/src/ts/core/Prefix.ts b/src/ts/core/Prefix.ts index 100b55f..312635e 100644 --- a/src/ts/core/Prefix.ts +++ b/src/ts/core/Prefix.ts @@ -1,5 +1,6 @@ import constants from '../Constants'; import Process from '../neutralino/Process'; +import Debug, { DebugThread } from './Debug'; import Downloader from './Downloader'; import Runners from './Runners'; @@ -16,8 +17,22 @@ export default class Prefix path ??= await constants.paths.prefix.current; Neutralino.filesystem.getStats(`${path}/drive_c`) - .then(() => resolve(true)) - .catch(() => resolve(false)); + .then(() => { + Debug.log({ + function: 'Prefix.exists', + message: `Prefix exists here: ${path}` + }); + + resolve(true); + }) + .catch(() => { + Debug.log({ + function: 'Prefix.exists', + message: `Prefix doesn't exist here: ${path}` + }); + + resolve(false); + }); }); } @@ -52,6 +67,8 @@ export default class Prefix */ public static create(path: string, progress?: (output: string, current: number, total: number) => void): Promise { + const debugThread = new DebugThread('Prefix.create', 'Creating wine prefix'); + const installationSteps = [ // corefonts 'Executing w_do_call corefonts', @@ -74,14 +91,20 @@ export default class Prefix return new Promise((resolve) => { Runners.current().then((runner) => { if (runner === null) + { + debugThread.log('Runner doesn\'t selected'); + resolve(false); + } else { + debugThread.log(`Using runner: ${runner.title} (${runner.name})`); + this.getWinetricks().then(async (winetricks) => { let installationProgress = 0; - const process = await Process.run(`bash '${Process.addSlashes(winetricks)}' corefonts usetakefocus=n`, { + const process = await Process.run(`./'${Process.addSlashes(winetricks)}' corefonts usetakefocus=n`, { env: { WINE: `${await constants.paths.runnersDir}/${runner.name}/${runner.files.wine}`, WINESERVER: `${await constants.paths.runnersDir}/${runner.name}/${runner.files.wineserver}`, @@ -91,6 +114,7 @@ export default class Prefix process.outputInterval = null; + // If progress specified if (progress) { process.outputInterval = 1500; @@ -116,7 +140,11 @@ export default class Prefix }); } - process.finish(() => resolve(true)); + process.finish(() => { + debugThread.log('Prefix creation completed'); + + resolve(true); + }); }); } }); diff --git a/src/ts/core/Runners.ts b/src/ts/core/Runners.ts index 927fa49..be63fe8 100644 --- a/src/ts/core/Runners.ts +++ b/src/ts/core/Runners.ts @@ -6,8 +6,8 @@ import type { import constants from '../Constants'; import Configs from '../Configs'; import AbstractInstaller from './AbstractInstaller'; -import Downloader from './Downloader'; import Process from '../neutralino/Process'; +import { DebugThread } from './Debug'; declare const Neutralino; @@ -140,13 +140,19 @@ class Runners */ public static delete(runner: Runner|Runner['name']): Promise { + const debugThread = new DebugThread('Runners.delete', `Deleting runner ${typeof runner === 'string' ? runner : runner.name}`); + return new Promise(async (resolve) => { const name = typeof runner !== 'string' ? runner.name : runner; Process.run(`rm -rf '${Process.addSlashes(await constants.paths.runnersDir + '/' + name)}'`) .then((process) => { - process.finish(() => resolve()); + process.finish(() => { + debugThread.log('Runner deleted'); + + resolve(); + }); }); }); } diff --git a/src/ts/launcher/State.ts b/src/ts/launcher/State.ts index 9463d52..32322eb 100644 --- a/src/ts/launcher/State.ts +++ b/src/ts/launcher/State.ts @@ -5,6 +5,7 @@ import Window from '../neutralino/Window'; import Game from '../Game'; import Patch from '../Patch'; +import Voice from '../Voice'; export default class State { @@ -35,8 +36,12 @@ export default class State this.launchButton.onclick = () => { if (this.events[this._state]) { + this.launchButton.style['display'] = 'none'; + this.events[this._state].then((event) => { event.default(this.launcher).then(() => { + this.launchButton.style['display'] = 'block'; + this.update(); }); }); @@ -118,6 +123,7 @@ export default class State const gameCurrent = await Game.current; const gameLatest = (await Game.latest).version; const patch = await Patch.latest; + const voiceData = await Voice.current; if (gameCurrent === null) state = 'game-installation-available'; @@ -125,6 +131,10 @@ export default class State else if (gameCurrent != gameLatest) state = 'game-update-available'; + // TODO: update this thing if the user selected another voice language + else if (voiceData.installed.length === 0) + state = 'game-voice-update-required'; + else if (!patch.applied) { state = patch.state == 'preparation' ? diff --git a/src/ts/launcher/states/Install.ts b/src/ts/launcher/states/Install.ts index b3df921..c48492a 100644 --- a/src/ts/launcher/states/Install.ts +++ b/src/ts/launcher/states/Install.ts @@ -5,8 +5,6 @@ import Prefix from '../../core/Prefix'; export default (launcher: Launcher): Promise => { return new Promise(async (resolve) => { - launcher.state!.launchButton!.style['display'] = 'none'; - Prefix.exists().then((exists) => { if (!exists) { diff --git a/src/ts/launcher/states/Launch.ts b/src/ts/launcher/states/Launch.ts index f2f1570..d06c1a9 100644 --- a/src/ts/launcher/states/Launch.ts +++ b/src/ts/launcher/states/Launch.ts @@ -1,5 +1,6 @@ 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'; @@ -9,6 +10,8 @@ declare const Neutralino; export default (): Promise => { return new Promise(async (resolve) => { + const debugThread = new DebugThread('State/Launch', 'Starting the game'); + const telemetry = await Game.isTelemetryDisabled(); // If telemetry servers are not disabled @@ -22,6 +25,8 @@ export default (): Promise => { icon: icon, importance: 'critical' }); + + debugThread.log('Telemetry is not disabled!'); } // Otherwise run the game @@ -51,7 +56,7 @@ export default (): Promise => { } } - console.log(`Wine executable: ${wineExeutable}`); + debugThread.log(`Wine executable path: ${wineExeutable}`); // Some special variables let env: any = {}; @@ -77,9 +82,9 @@ export default (): Promise => { */ const shaders = await Configs.get('shaders'); - if (shaders !== null) + if (shaders !== 'none') { - const userShadersFile = `${constants.paths.shadersDir}/${shaders}/vkBasalt.conf`; + const userShadersFile = `${constants.paths.shadersDir}/public/${shaders}/vkBasalt.conf`; const launcherShadersFile = `${await constants.paths.launcherDir}/vkBasalt.conf`; env['ENABLE_VKBASALT'] = 1; @@ -114,9 +119,7 @@ export default (): Promise => { /*if (LauncherLib.getConfig('gamemode')) command = `gamemoderun ${command}`;*/ - const command = `${wineExeutable} launcher.bat`; - - console.log(`Execution command: ${command}`); + const command = `'${Process.addSlashes(wineExeutable)}' launcher.bat`; /** * Starting the game diff --git a/src/ts/neutralino/Process.ts b/src/ts/neutralino/Process.ts index b336a60..24b211f 100644 --- a/src/ts/neutralino/Process.ts +++ b/src/ts/neutralino/Process.ts @@ -1,3 +1,5 @@ +import Debug, { DebugThread } from "../core/Debug"; + declare const Neutralino; declare const NL_CWD; @@ -49,6 +51,8 @@ class Process public constructor(pid: number, outputFile: string|null = null) { + const debugThread = new DebugThread('Process/Stream', 'Opened process stream'); + this.id = pid; this.outputFile = outputFile; @@ -66,6 +70,8 @@ class Process { this._finished = true; + debugThread.log('Process stopped'); + if (this.onFinish) this.onFinish(this); } @@ -86,7 +92,19 @@ class Process 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); @@ -184,6 +202,15 @@ class Process background: true }); + Debug.log({ + function: 'Process.run', + message: { + 'running command': command, + 'cwd': options.cwd, + ...options.env + } + }); + resolve(new Process(process.pid, tmpFile)); }); } diff --git a/src/ts/types/Debug.d.ts b/src/ts/types/Debug.d.ts new file mode 100644 index 0000000..f8715bd --- /dev/null +++ b/src/ts/types/Debug.d.ts @@ -0,0 +1,23 @@ +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 };