Added debugger

- disabled already selected dxvk/runner re-selection
- fixed `Voice.current` output
- made `Debug` and `DebugThread` classes
- fixed `winecfg` usage during DXVK installation
- `bash` usage was changed to the `./`
- added voice update state identification
- fixed shaders usage in game launching script
- added debugging to the `[launchder]/logs` folder
This commit is contained in:
Observer KRypt0n_ 2021-12-28 21:22:45 +02:00
parent fca10371b8
commit cec860e69c
No known key found for this signature in database
GPG key ID: DC5D4EC1303465DA
22 changed files with 449 additions and 43 deletions

View file

@ -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 * Rewrite sass code, provide more flexible theming ability
* <s>Add `svelte-i18n`</s> * <s>Add `svelte-i18n`</s>
* <s>Telemetry checking</s> * <s>Telemetry checking</s>
* <s>Tooltips for some options</s>
* <s>Debugger</s>
* Game pre-installation * Game pre-installation
* Launcher auto-updates * Launcher auto-updates
* Statistics window * Statistics window
* Debugger * Splash screen
* Loading screen
* Tooltips for some options
* Theming system * Theming system
* Chengelog window * Chengelog window
* Default runner auto-installation * Default runner auto-installation
@ -180,7 +180,6 @@ This is our current roadmap goals. You can find older ones [here](ROADMAP.md)
* <s>Use `LauncherLib.getGameVersion` function instead of the `config.json`'s `version` property</s> *(deprecated due to the new core functions)* * <s>Use `LauncherLib.getGameVersion` function instead of the `config.json`'s `version` property</s> *(deprecated due to the new core functions)*
* Add downloading pause button * Add downloading pause button
* Fix button flickering at start when the launcher's state updates * Fix button flickering at start when the launcher's state updates
* Game's update pre-installation
* Screenshots explorer * Screenshots explorer
* Add Patch category in settings menu with * Add Patch category in settings menu with
- Always participate in patches testing - Always participate in patches testing

View file

@ -98,7 +98,7 @@
class:list-item-disabled={disabledDxvks[dxvk.version]} class:list-item-disabled={disabledDxvks[dxvk.version]}
on:click|self={() => { on:click|self={() => {
if (installedDxvks[dxvk.version]) if (installedDxvks[dxvk.version] && selectedVersion !== dxvk.version)
selectDxvk(dxvk); selectDxvk(dxvk);
}} }}
> >

View file

@ -78,7 +78,7 @@
class:list-item-disabled={disabledRunners[runner.name]} class:list-item-disabled={disabledRunners[runner.name]}
on:click|self={() => { on:click|self={() => {
if (installedRunners[runner.name]) if (installedRunners[runner.name] && selectedVersion !== runner.name)
{ {
selectedVersion = runner.name; selectedVersion = runner.name;

View file

@ -1,6 +1,8 @@
import '../i18n'; import '../i18n';
import App from '../index.svelte'; import App from '../index.svelte';
import constants from '../ts/Constants';
import Archive from '../ts/core/Archive'; import Archive from '../ts/core/Archive';
import Debug from '../ts/core/Debug';
import Downloader from '../ts/core/Downloader'; import Downloader from '../ts/core/Downloader';
declare const Neutralino; declare const Neutralino;
@ -13,7 +15,26 @@ Neutralino.events.on('windowClose', () => {
Downloader.closeStreams(true); Downloader.closeStreams(true);
Archive.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({ const app = new App({

View file

@ -1,10 +1,18 @@
import '../i18n'; import '../i18n';
import Debug from '../ts/core/Debug';
import App from '../settings.svelte'; import App from '../settings.svelte';
declare const Neutralino; declare const Neutralino;
Neutralino.init(); Neutralino.init();
Neutralino.events.on('windowClose', async () => {
await Neutralino.storage.setData('log', JSON.stringify(Debug.getRecords()));
Neutralino.app.exit();
});
const app = new App({ const app = new App({
target: document.getElementById('app')! target: document.getElementById('app')!
}); });

View file

@ -10,6 +10,7 @@ import fetch from './core/Fetch';
import AbstractInstaller from './core/AbstractInstaller'; import AbstractInstaller from './core/AbstractInstaller';
import Domain from './core/Domain'; import Domain from './core/Domain';
import promisify from './core/promisify'; import promisify from './core/promisify';
import Debug, { DebugThread } from './core/Debug';
declare const Neutralino; declare const Neutralino;
@ -42,6 +43,11 @@ export default class Game
const buffer = new TextDecoder('ascii').decode(new Uint8Array(config)); const buffer = new TextDecoder('ascii').decode(new Uint8Array(config));
const version = /([1-9]+\.[0-9]+\.[0-9]+)_[\d]+_[\d]+/.exec(buffer); 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] : '<unknown>'}`
});
resolve(version !== null ? version[1] : null); resolve(version !== null ? version[1] : null);
}) })
.catch(() => resolve(null)); .catch(() => resolve(null));
@ -140,6 +146,12 @@ export default class Game
*/ */
public static update(version: string|null = null): Promise<Stream|null> public static update(version: string|null = null): Promise<Stream|null>
{ {
Debug.log(
version !== null ?
`Updating the game from the ${version} version` :
'Installing the game'
);
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
(version === null ? this.latest : this.getDiff(version)) (version === null ? this.latest : this.getDiff(version))
.then((data: Latest|Diff|null) => resolve(data === null ? null : new Stream(data.path))) .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<boolean> public static isTelemetryDisabled(): Promise<boolean>
{ {
const debugThread = new DebugThread('Game.isTelemetryDisabled', 'Checking if the telemetry servers are disabled');
return new Promise(async (resolve) => { return new Promise(async (resolve) => {
const pipeline = promisify({ const pipeline = promisify({
callbacks: await constants.uri.telemetry.map((domain) => { callbacks: await constants.uri.telemetry.map((domain) => {
@ -168,6 +182,8 @@ export default class Game
Object.values(result).forEach((value) => disabled ||= value as boolean); Object.values(result).forEach((value) => disabled ||= value as boolean);
debugThread.log(`Telemetry is ${disabled ? 'not ' : ''}disabled`);
resolve(disabled === false); resolve(disabled === false);
}); });
}); });

View file

@ -6,6 +6,9 @@ import Configs from './Configs';
import ProgressBar from './launcher/ProgressBar'; import ProgressBar from './launcher/ProgressBar';
import State from './launcher/State'; import State from './launcher/State';
import Debug from './core/Debug';
declare const Neutralino;
export default class Launcher export default class Launcher
{ {
@ -36,7 +39,8 @@ export default class Launcher
title: 'Settings', title: 'Settings',
width: 900, width: 900,
height: 600, height: 600,
enableInspector: true enableInspector: true,
exitProcessOnClose: false
}); });
if (window.status) if (window.status)
@ -45,6 +49,14 @@ export default class Launcher
this.settingsMenu.finish(() => { this.settingsMenu.finish(() => {
this.settingsMenu = undefined; this.settingsMenu = undefined;
Neutralino.storage.getData('log')
.then((data) => {
Debug.merge(JSON.parse(data));
Neutralino.storage.setData('log', undefined);
})
.catch(() => {});
Window.current.show(); Window.current.show();
}) })

View file

@ -8,6 +8,7 @@ import fetch from './core/Fetch';
import AbstractInstaller from './core/AbstractInstaller'; import AbstractInstaller from './core/AbstractInstaller';
import promisify from './core/promisify'; import promisify from './core/promisify';
import Process from './neutralino/Process'; import Process from './neutralino/Process';
import Debug, { DebugThread } from './core/Debug';
declare const Neutralino; declare const Neutralino;
@ -132,6 +133,8 @@ export default class Patch
*/ */
public static get latest(): Promise<PatchInfo> public static get latest(): Promise<PatchInfo>
{ {
const debugThread = new DebugThread('Patch.latest', 'Getting the latest patch information');
return new Promise(async (resolve, reject) => { return new Promise(async (resolve, reject) => {
const getLatestPatchInfo = (versions: string[], source: 'origin' | 'additional'): Promise<PatchInfo> => { const getLatestPatchInfo = (versions: string[], source: 'origin' | 'additional'): Promise<PatchInfo> => {
return new Promise(async (resolve) => { return new Promise(async (resolve) => {
@ -145,7 +148,12 @@ export default class Patch
resolve(await getLatestPatchInfo(versions.slice(1), 'origin')); resolve(await getLatestPatchInfo(versions.slice(1), 'origin'));
// Otherwise - return found info // Otherwise - return found info
else resolve(patchInfo); else
{
debugThread.log({ message: patchInfo });
resolve(patchInfo);
}
}) })
.catch(async (error) => { .catch(async (error) => {
// If we couldn't connect to the origin repo // If we couldn't connect to the origin repo
@ -271,6 +279,11 @@ export default class Patch
*/ */
public static install(): Promise<Stream|null> public static install(): Promise<Stream|null>
{ {
Debug.log({
function: 'Patch.install',
message: 'Installing the patch...'
});
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
this.latest this.latest
.then((patch) => { .then((patch) => {

View file

@ -5,6 +5,7 @@ import constants from './Constants';
import Game from './Game'; import Game from './Game';
import AbstractInstaller from './core/AbstractInstaller'; import AbstractInstaller from './core/AbstractInstaller';
import Configs from './Configs'; import Configs from './Configs';
import Debug from './core/Debug';
declare const Neutralino; declare const Neutralino;
@ -41,23 +42,36 @@ export default class Voice
// Parse installed voice packages // Parse installed voice packages
Neutralino.filesystem.readDirectory(await constants.paths.voiceDir) Neutralino.filesystem.readDirectory(await constants.paths.voiceDir)
.then((files) => { .then((files) => {
files = files files = files.filter((file) => file.type == 'DIRECTORY')
.filter((file) => file.type == 'DIRECTORY')
.map((file) => file.entry); .map((file) => file.entry);
Object.keys(langs).forEach((folder) => { Object.keys(langs).forEach((folder) => {
if (files.includes(folder) && langs[folder] !== undefined) if (files.includes(folder))
installedVoice.installed.push(langs[folder]); installedVoice.installed.push(langs[folder]);
}); });
parseActiveVoice();
}) })
.catch(() => {}); .catch(() => parseActiveVoice());
// Parse active voice package // Parse active voice package
Neutralino.filesystem.readFile(persistentPath) const parseActiveVoice = () => {
.then((lang) => installedVoice.active = langs[lang] ?? null) Neutralino.filesystem.readFile(persistentPath)
.catch(() => {}); .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<Stream|null> public static update(lang: string|null = null, version: string|null = null): Promise<Stream|null>
{ {
Debug.log(
version !== null ?
`Updating the voice package from the ${version} version` :
'Installing the voice package'
);
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
(version === null ? this.latest : this.getDiff(version)) (version === null ? this.latest : this.getDiff(version))
.then((data: VoicePack[]|null) => { .then((data: VoicePack[]|null) => {

View file

@ -1,6 +1,7 @@
import constants from '../Constants'; import constants from '../Constants';
import Downloader from './Downloader'; import Downloader from './Downloader';
import Archive from './Archive'; import Archive from './Archive';
import { DebugThread } from './Debug';
declare const Neutralino; declare const Neutralino;
@ -33,6 +34,13 @@ export default abstract class Installer
public constructor(uri: string, unpackDir: string|Promise<string>) public constructor(uri: string, unpackDir: string|Promise<string>)
{ {
const debugThread = new DebugThread('AbstractInstaller', {
message: {
'uri': uri,
'unpack dir': typeof unpackDir === 'string' ? unpackDir : '<promise>'
}
});
constants.paths.launcherDir.then((launcherDir) => { constants.paths.launcherDir.then((launcherDir) => {
const archivePath = `${launcherDir}/${Downloader.fileFromUri(uri)}`; const archivePath = `${launcherDir}/${Downloader.fileFromUri(uri)}`;
@ -79,6 +87,8 @@ export default abstract class Installer
this.unpackFinished = true; this.unpackFinished = true;
Neutralino.filesystem.removeFile(archivePath); Neutralino.filesystem.removeFile(archivePath);
debugThread.log('Installation finished');
if (this.onUnpackFinish) if (this.onUnpackFinish)
this.onUnpackFinish(); this.onUnpackFinish();
@ -86,8 +96,15 @@ export default abstract class Installer
}); });
}; };
const shouldResolve = typeof unpackDir !== 'string';
Promise.resolve(unpackDir) 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) if (this.unpackFinished)
callback(); callback();
} }
} };

View file

@ -5,6 +5,7 @@ import type {
ArchiveInfo ArchiveInfo
} from '../types/Archive'; } from '../types/Archive';
import { DebugThread } from './Debug';
import promisify from './promisify'; import promisify from './promisify';
declare const Neutralino; declare const Neutralino;
@ -52,6 +53,13 @@ class Stream
this.unpackDir = unpackDir; this.unpackDir = unpackDir;
this.started = true; this.started = true;
const debugThread = new DebugThread('Archive/Stream', {
message: {
'path': path,
'unpack dir': unpackDir
}
});
if (this.onStart) if (this.onStart)
this.onStart(); this.onStart();
@ -83,6 +91,8 @@ class Stream
this._id = result.pid; this._id = result.pid;
}); });
debugThread.log(`Unpacking started with command: ${command}`);
const updateProgress = async () => { const updateProgress = async () => {
let difference: number = 0; let difference: number = 0;
let pool: any[] = []; let pool: any[] = [];
@ -122,6 +132,8 @@ class Stream
{ {
this.finished = true; this.finished = true;
debugThread.log('Unpacking finished');
if (this.onFinish) if (this.onFinish)
this.onFinish(); this.onFinish();
} }
@ -222,6 +234,8 @@ export default class Archive
*/ */
public static getInfo(path: string): Promise<ArchiveInfo|null> public static getInfo(path: string): Promise<ArchiveInfo|null>
{ {
const debugThread = new DebugThread('Archive.getInfo', `Getting info about archive: ${path}`);
return new Promise(async (resolve) => { return new Promise(async (resolve) => {
let archive: ArchiveInfo = { let archive: ArchiveInfo = {
size: { 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); resolve(archive);
break; 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); resolve(archive);
break; break;
default: default:
debugThread.log(`Unsupported archive type: ${archive.type}`);
resolve(null); resolve(null);
break; break;
@ -314,7 +348,7 @@ export default class Archive
stream.close(forced); stream.close(forced);
}); });
} }
} };
export { Stream }; export { Stream };

View file

@ -6,6 +6,7 @@ import AbstractInstaller from './AbstractInstaller';
import Process from '../neutralino/Process'; import Process from '../neutralino/Process';
import promisify from './promisify'; import promisify from './promisify';
import Runners from './Runners'; import Runners from './Runners';
import { DebugThread } from './Debug';
declare const Neutralino; declare const Neutralino;
@ -133,13 +134,19 @@ export default class DXVK
*/ */
public static delete(dxvk: TDXVK|TDXVK['version']): Promise<void> public static delete(dxvk: TDXVK|TDXVK['version']): Promise<void>
{ {
const debugThread = new DebugThread('DXVK.delete', `Deleting dxvk ${typeof dxvk === 'string' ? dxvk : dxvk.version}`);
return new Promise(async (resolve) => { return new Promise(async (resolve) => {
const version = typeof dxvk !== 'string' ? const version = typeof dxvk !== 'string' ?
dxvk.version : dxvk; dxvk.version : dxvk;
Process.run(`rm -rf '${Process.addSlashes(await constants.paths.dxvksDir + '/dxvk-' + version)}'`) Process.run(`rm -rf '${Process.addSlashes(await constants.paths.dxvksDir + '/dxvk-' + version)}'`)
.then((process) => { .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) => { return new Promise(async (resolve) => {
const version = typeof dxvk !== 'string' ? const version = typeof dxvk !== 'string' ?
dxvk.version : dxvk; dxvk.version : dxvk;
const debugThread = new DebugThread('DXVK.apply', `Applying dxvk ${version}`);
const dxvkDir = `${await constants.paths.dxvksDir}/dxvk-${version}`; const dxvkDir = `${await constants.paths.dxvksDir}/dxvk-${version}`;
const runner = await Runners.current(); const runner = await Runners.current();
@ -168,22 +177,40 @@ export default class DXVK
* And then run it * And then run it
*/ */
(): Promise<void> => new Promise(async (resolve) => { (): Promise<void> => 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, cwd: dxvkDir,
env: { env: {
WINE: runner ? `${runnerDir}/${runner.files.wine}` : 'wine', WINE: runner ? `${runnerDir}/${runner.files.wine}` : 'wine',
WINECFG: runner ? `${runnerDir}/${runner.files.winecfg}` : 'winecfg',
WINESERVER: runner ? `${runnerDir}/${runner.files.wineserver}` : 'wineserver', WINESERVER: runner ? `${runnerDir}/${runner.files.wineserver}` : 'wineserver',
WINEPREFIX: prefix WINEPREFIX: prefix
} }
}).then((process) => { }).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();
});
}); });
} }
} }

119
src/ts/core/Debug.ts Normal file
View file

@ -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
};

View file

@ -1,11 +1,14 @@
import type { DomainInfo } from '../types/Domain'; import type { DomainInfo } from '../types/Domain';
import Process from '../neutralino/Process'; import Process from '../neutralino/Process';
import { DebugThread } from './Debug';
export default class Domain export default class Domain
{ {
public static getInfo(uri: string): Promise<DomainInfo> public static getInfo(uri: string): Promise<DomainInfo>
{ {
const debugThread = new DebugThread('Domain.getInfo', `Getting info about uri: ${uri}`);
return new Promise(async (resolve) => { return new Promise(async (resolve) => {
const process = await Process.run(`ping -n 1 -w 1 -B ${uri}`); const process = await Process.run(`ping -n 1 -w 1 -B ${uri}`);
@ -23,12 +26,19 @@ export default class Domain
if (regex !== null) if (regex !== null)
{ {
resolve({ process.outputInterval = null;
process.runningInterval = null;
const info: DomainInfo = {
uri: regex[1], uri: regex[1],
remoteIp: regex[2], remoteIp: regex[2],
localIp: regex[3], localIp: regex[3],
available: regex[2] !== regex[3] available: regex[2] !== regex[3]
}); };
debugThread.log({ message: info });
resolve(info);
} }
}); });
}); });

View file

@ -1,3 +1,4 @@
import { DebugThread } from './Debug';
import fetch from './Fetch'; import fetch from './Fetch';
declare const Neutralino; declare const Neutralino;
@ -36,15 +37,27 @@ class Stream
this.total = total; this.total = total;
this.started = true; this.started = true;
const debugThread = new DebugThread('Downloader/Stream', {
message: {
'uri': uri,
'output file': output,
'total size': total
}
});
if (this.onStart) if (this.onStart)
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 background: true
}).then((result) => { }).then((result) => {
this._id = result.pid; this._id = result.pid;
}); });
debugThread.log(`Downloading started with command: ${command}`);
const updateProgress = () => { const updateProgress = () => {
Neutralino.filesystem.getStats(output).then((stats) => { Neutralino.filesystem.getStats(output).then((stats) => {
if (this.onProgress) if (this.onProgress)
@ -56,6 +69,8 @@ class Stream
{ {
this.finished = true; this.finished = true;
debugThread.log('Downloading finished');
if (this.onFinish) if (this.onFinish)
this.onFinish(); this.onFinish();
} }
@ -166,6 +181,6 @@ export default class Downloader
else return 'index.html'; else return 'index.html';
} }
} };
export { Stream }; export { Stream };

View file

@ -1,5 +1,6 @@
import constants from '../Constants'; import constants from '../Constants';
import Process from '../neutralino/Process'; import Process from '../neutralino/Process';
import Debug, { DebugThread } from './Debug';
import Downloader from './Downloader'; import Downloader from './Downloader';
import Runners from './Runners'; import Runners from './Runners';
@ -16,8 +17,22 @@ export default class Prefix
path ??= await constants.paths.prefix.current; path ??= await constants.paths.prefix.current;
Neutralino.filesystem.getStats(`${path}/drive_c`) Neutralino.filesystem.getStats(`${path}/drive_c`)
.then(() => resolve(true)) .then(() => {
.catch(() => resolve(false)); 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<boolean> public static create(path: string, progress?: (output: string, current: number, total: number) => void): Promise<boolean>
{ {
const debugThread = new DebugThread('Prefix.create', 'Creating wine prefix');
const installationSteps = [ const installationSteps = [
// corefonts // corefonts
'Executing w_do_call corefonts', 'Executing w_do_call corefonts',
@ -74,14 +91,20 @@ export default class Prefix
return new Promise((resolve) => { return new Promise((resolve) => {
Runners.current().then((runner) => { Runners.current().then((runner) => {
if (runner === null) if (runner === null)
{
debugThread.log('Runner doesn\'t selected');
resolve(false); resolve(false);
}
else else
{ {
debugThread.log(`Using runner: ${runner.title} (${runner.name})`);
this.getWinetricks().then(async (winetricks) => { this.getWinetricks().then(async (winetricks) => {
let installationProgress = 0; 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: { env: {
WINE: `${await constants.paths.runnersDir}/${runner.name}/${runner.files.wine}`, WINE: `${await constants.paths.runnersDir}/${runner.name}/${runner.files.wine}`,
WINESERVER: `${await constants.paths.runnersDir}/${runner.name}/${runner.files.wineserver}`, WINESERVER: `${await constants.paths.runnersDir}/${runner.name}/${runner.files.wineserver}`,
@ -91,6 +114,7 @@ export default class Prefix
process.outputInterval = null; process.outputInterval = null;
// If progress specified
if (progress) if (progress)
{ {
process.outputInterval = 1500; process.outputInterval = 1500;
@ -116,7 +140,11 @@ export default class Prefix
}); });
} }
process.finish(() => resolve(true)); process.finish(() => {
debugThread.log('Prefix creation completed');
resolve(true);
});
}); });
} }
}); });

View file

@ -6,8 +6,8 @@ import type {
import constants from '../Constants'; import constants from '../Constants';
import Configs from '../Configs'; import Configs from '../Configs';
import AbstractInstaller from './AbstractInstaller'; import AbstractInstaller from './AbstractInstaller';
import Downloader from './Downloader';
import Process from '../neutralino/Process'; import Process from '../neutralino/Process';
import { DebugThread } from './Debug';
declare const Neutralino; declare const Neutralino;
@ -140,13 +140,19 @@ class Runners
*/ */
public static delete(runner: Runner|Runner['name']): Promise<void> public static delete(runner: Runner|Runner['name']): Promise<void>
{ {
const debugThread = new DebugThread('Runners.delete', `Deleting runner ${typeof runner === 'string' ? runner : runner.name}`);
return new Promise(async (resolve) => { return new Promise(async (resolve) => {
const name = typeof runner !== 'string' ? const name = typeof runner !== 'string' ?
runner.name : runner; runner.name : runner;
Process.run(`rm -rf '${Process.addSlashes(await constants.paths.runnersDir + '/' + name)}'`) Process.run(`rm -rf '${Process.addSlashes(await constants.paths.runnersDir + '/' + name)}'`)
.then((process) => { .then((process) => {
process.finish(() => resolve()); process.finish(() => {
debugThread.log('Runner deleted');
resolve();
});
}); });
}); });
} }

View file

@ -5,6 +5,7 @@ import Window from '../neutralino/Window';
import Game from '../Game'; import Game from '../Game';
import Patch from '../Patch'; import Patch from '../Patch';
import Voice from '../Voice';
export default class State export default class State
{ {
@ -35,8 +36,12 @@ export default class State
this.launchButton.onclick = () => { this.launchButton.onclick = () => {
if (this.events[this._state]) if (this.events[this._state])
{ {
this.launchButton.style['display'] = 'none';
this.events[this._state].then((event) => { this.events[this._state].then((event) => {
event.default(this.launcher).then(() => { event.default(this.launcher).then(() => {
this.launchButton.style['display'] = 'block';
this.update(); this.update();
}); });
}); });
@ -118,6 +123,7 @@ export default class State
const gameCurrent = await Game.current; const gameCurrent = await Game.current;
const gameLatest = (await Game.latest).version; const gameLatest = (await Game.latest).version;
const patch = await Patch.latest; const patch = await Patch.latest;
const voiceData = await Voice.current;
if (gameCurrent === null) if (gameCurrent === null)
state = 'game-installation-available'; state = 'game-installation-available';
@ -125,6 +131,10 @@ export default class State
else if (gameCurrent != gameLatest) else if (gameCurrent != gameLatest)
state = 'game-update-available'; 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) else if (!patch.applied)
{ {
state = patch.state == 'preparation' ? state = patch.state == 'preparation' ?

View file

@ -5,8 +5,6 @@ import Prefix from '../../core/Prefix';
export default (launcher: Launcher): Promise<void> => { export default (launcher: Launcher): Promise<void> => {
return new Promise(async (resolve) => { return new Promise(async (resolve) => {
launcher.state!.launchButton!.style['display'] = 'none';
Prefix.exists().then((exists) => { Prefix.exists().then((exists) => {
if (!exists) if (!exists)
{ {

View file

@ -1,5 +1,6 @@
import Configs from '../../Configs'; import Configs from '../../Configs';
import constants from '../../Constants'; import constants from '../../Constants';
import { DebugThread } from '../../core/Debug';
import Notifications from '../../core/Notifications'; import Notifications from '../../core/Notifications';
import Runners from '../../core/Runners'; import Runners from '../../core/Runners';
import Game from '../../Game'; import Game from '../../Game';
@ -9,6 +10,8 @@ declare const Neutralino;
export default (): Promise<void> => { export default (): Promise<void> => {
return new Promise(async (resolve) => { return new Promise(async (resolve) => {
const debugThread = new DebugThread('State/Launch', 'Starting the game');
const telemetry = await Game.isTelemetryDisabled(); const telemetry = await Game.isTelemetryDisabled();
// If telemetry servers are not disabled // If telemetry servers are not disabled
@ -22,6 +25,8 @@ export default (): Promise<void> => {
icon: icon, icon: icon,
importance: 'critical' importance: 'critical'
}); });
debugThread.log('Telemetry is not disabled!');
} }
// Otherwise run the game // Otherwise run the game
@ -51,7 +56,7 @@ export default (): Promise<void> => {
} }
} }
console.log(`Wine executable: ${wineExeutable}`); debugThread.log(`Wine executable path: ${wineExeutable}`);
// Some special variables // Some special variables
let env: any = {}; let env: any = {};
@ -77,9 +82,9 @@ export default (): Promise<void> => {
*/ */
const shaders = await Configs.get('shaders'); 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`; const launcherShadersFile = `${await constants.paths.launcherDir}/vkBasalt.conf`;
env['ENABLE_VKBASALT'] = 1; env['ENABLE_VKBASALT'] = 1;
@ -114,9 +119,7 @@ export default (): Promise<void> => {
/*if (LauncherLib.getConfig('gamemode')) /*if (LauncherLib.getConfig('gamemode'))
command = `gamemoderun ${command}`;*/ command = `gamemoderun ${command}`;*/
const command = `${wineExeutable} launcher.bat`; const command = `'${Process.addSlashes(wineExeutable)}' launcher.bat`;
console.log(`Execution command: ${command}`);
/** /**
* Starting the game * Starting the game

View file

@ -1,3 +1,5 @@
import Debug, { DebugThread } from "../core/Debug";
declare const Neutralino; declare const Neutralino;
declare const NL_CWD; declare const NL_CWD;
@ -49,6 +51,8 @@ class Process
public constructor(pid: number, outputFile: string|null = null) public constructor(pid: number, outputFile: string|null = null)
{ {
const debugThread = new DebugThread('Process/Stream', 'Opened process stream');
this.id = pid; this.id = pid;
this.outputFile = outputFile; this.outputFile = outputFile;
@ -66,6 +70,8 @@ class Process
{ {
this._finished = true; this._finished = true;
debugThread.log('Process stopped');
if (this.onFinish) if (this.onFinish)
this.onFinish(this); this.onFinish(this);
} }
@ -86,7 +92,19 @@ class Process
this.outputOffset = output.length; this.outputOffset = output.length;
if (this._finished) if (this._finished)
{
if (output !== '')
{
debugThread.log({
message: [
'Process output:',
...output.split(/\r\n|\r|\n/)
]
});
}
Neutralino.filesystem.removeFile(this.outputFile); Neutralino.filesystem.removeFile(this.outputFile);
}
else if (this.outputInterval) else if (this.outputInterval)
setTimeout(updateOutput, this.outputInterval); setTimeout(updateOutput, this.outputInterval);
@ -184,6 +202,15 @@ class Process
background: true background: true
}); });
Debug.log({
function: 'Process.run',
message: {
'running command': command,
'cwd': options.cwd,
...options.env
}
});
resolve(new Process(process.pid, tmpFile)); resolve(new Process(process.pid, tmpFile));
}); });
} }

23
src/ts/types/Debug.d.ts vendored Normal file
View file

@ -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 };