2.2.0-beta2

- updated readme; added roadmap and requirements
- added patch_anti_logincrash applying
- partially fixed input issues
- made LauncherUI class to where have moved a lot of code from the index.ts
- fixed ETA calculation
This commit is contained in:
Observer KRypt0n_ 2021-10-15 14:16:13 +02:00
parent 377e416825
commit b8457b9c5a
No known key found for this signature in database
GPG key ID: DC5D4EC1303465DA
6 changed files with 247 additions and 275 deletions

View file

@ -10,10 +10,19 @@
| Game version | Launcher version | Patch version |
| :---: | :---: | :---: |
| 2.2.0 | 2.2.0-beta1 | 2.2.0-testing |
| 2.2.0 | 2.2.0-beta2 | 2.2.0-testing |
Download from [Releases](https://notabug.org/nobody/an-anime-game-launcher/releases)
# Requirements
To work this launcher requires
* wine
* winecfg
* winetricks
* unzip
# Development
## Build from source
@ -28,6 +37,17 @@ npm run build:linux
npm start
```
# Roadmap
To 2.2.0-release1
* Fix AppImage builds
* Make Proton-GE default compatibility tool and fix game input issues
* Add additional telemetry checking
* Add preferences menu
* Add background banners for different languages
* Add launcher updates notifications
<br>
### Please, try to use "An Anime Game" phrase instead of the real game name to avoid search engines parsing

View file

@ -1,6 +1,6 @@
{
"name": "an-anime-game-linux-launcher",
"version": "2.2.0-beta1",
"version": "2.2.0-beta2",
"description": "An Anime Game Linux Launcher",
"author": "Nikita Podvirnyy",
"license": "GPL-3.0",

View file

@ -1,10 +1,5 @@
#!/usr/bin/env bash
echo "[NOTE] This patch is not required as of 2021-10-13. However, it might become"
echo " necessary afterwards (Friday?). If that's the case, comment the line below."
exit 0
# MacOS and *BSD do not have md5sum: use md5 instead
if [[ $(uname) == "Darwin" || $(uname) == *"BSD" ]]; then
md5sum() {
@ -41,13 +36,6 @@ echo "[INFO] Patch to fix a login and runtime crash"
echo ""
# ===========================================================
echo "[WARNING] Hereby you are violating the game's Terms of Service!"
echo " Do you accept the risk and possible consequences?"
read -p "Accept? [y/n] " choice
if [[ ! "$choice" == [JjSsYy]* ]]; then
exit 1
fi
echo
echo "--- Applying xLua patch"

View file

@ -21,6 +21,7 @@ export class Genshinlib
public static readonly patchDir: string = path.join(path.dirname(__dirname), 'patch');
public static readonly patchJson: string = path.join(this.patchDir, 'patch.json');
public static readonly patchSh = path.join(this.patchDir, 'patch.sh');
public static readonly patchAntiCrashSh = path.join(this.patchDir, 'patch_anti_logincrash.sh');
public static readonly launcherDir: string = path.join(os.homedir(), 'genshin-impact-launcher');
public static readonly launcherJson: string = path.join(this.launcherDir, 'launcher.json');
@ -186,13 +187,14 @@ export class Genshinlib
'Executing load_times',
'Executing load_trebuchet',
'Executing load_verdana',
'Executing load_webdings'
'Executing load_webdings',
'Executing load_usetakefocus n'
];
return new Promise((resolve) => {
let installationProgress = 0;
let installerProcess = spawn('winetricks', ['corefonts'], {
let installerProcess = spawn('winetricks', ['corefonts', 'usetakefocus=n'], {
env: {
...process.env,
WINEPREFIX: path
@ -221,4 +223,38 @@ export class Genshinlib
{
return fs.existsSync(path.join(prefixPath, 'drive_c'));
}
public static applyPatch (onFinish: () => void, onData: (data: string) => void)
{
let patcherProcess = spawn('bash', [Genshinlib.patchSh], {
cwd: Genshinlib.gameDir,
env: {
...process.env,
WINEPREFIX: Genshinlib.prefixDir
}
});
patcherProcess.stdout.on('data', (data: string) => onData(data));
patcherProcess.on('close', () => {
let patcherAntiCrashProcess = spawn('bash', [Genshinlib.patchAntiCrashSh], {
cwd: Genshinlib.gameDir,
env: {
...process.env,
WINEPREFIX: Genshinlib.prefixDir
}
});
patcherAntiCrashProcess.stdout.on('data', (data: string) => onData(data));
patcherAntiCrashProcess.on('close', () => {
Genshinlib.setConfig({
...Genshinlib.getConfig(),
patch: Genshinlib.getPatchInfo()
});
onFinish();
});
});
}
}

150
src/ts/LauncherUI.ts Normal file
View file

@ -0,0 +1,150 @@
import $ from 'cash-dom';
type LauncherState =
'patch-unavailable' |
'test-patch-available' |
'patch-applying' |
'game-update-available' |
'game-installation-available' |
'game-launch-available';
export class LauncherUI
{
protected static _launcherState: LauncherState = 'game-launch-available';
public static get launcherState(): LauncherState
{
return this._launcherState;
}
public static setState (state: LauncherState)
{
$('#downloader-panel').css('display', 'none');
$('#launch').css('display', 'block');
switch (state)
{
case 'patch-unavailable':
$('#launch').text('Patch required');
$('#launch').attr('disabled', 'disabled');
$('#launch').addClass('hint--top')
.addClass('hint--medium');
$('#launch').attr('data-hint', 'This game version doesn\'t have the anti-cheat patch. Please, wait a few days before it will be created');
break;
case 'test-patch-available':
$('#launch').text('Apply test patch');
$('#launch').addClass('button-blue')
.addClass('hint--top')
.addClass('hint--large');
$('#launch').attr('data-hint', 'This game version has the anti-cheat patch, but it is in the test phase. You can wait a few days until it will become stable or apply it on your own risc');
break;
case 'patch-applying':
$('#launch').text('Applying patch');
$('#launch').attr('disabled', 'disabled');
break;
case 'game-update-available':
$('#launch').text('Update');
break;
case 'game-installation-available':
$('#launch').text('Install');
break;
case 'game-launch-available':
$('#launch').removeAttr('disabled')
.removeAttr('data-hint');
$('#launch').removeClass('button-blue')
.removeClass('hint--top')
.removeClass('hint--medium')
.removeClass('hint--large');
$('#launch').text('Launch');
break;
}
this._launcherState = state;
}
protected static progressBar = {
beganAt: 0,
prevTime: 0,
temp: 0
};
public static initProgressBar (): void
{
this.progressBar = {
beganAt: Date.now(),
prevTime: Date.now(),
temp: 0
};
$('#downloaded').text('');
$('#speed').text('');
$('#eta').text('');
$('#downloader .progress').css('width', '0');
$('#downloader-panel').css('display', 'block');
$('#launch').css('display', 'none');
}
public static updateProgressBar (prefix: string, current: number, total: number, difference: number): void
{
$('#downloaded').text(`${prefix}: ${ Math.round(current / total * 100) }% (${ (current / 1024 / 1024 / 1024).toFixed(2) } GB / ${ Math.round(total / 1024 / 1024 / 1024).toFixed(2) } GB)`);
this.progressBar.temp += difference;
if (Date.now() - this.progressBar.prevTime > 1000)
{
let elapsed = (Date.now() - this.progressBar.beganAt) / 1000;
let eta = Math.round(total * elapsed / current - elapsed);
let etaHours = Math.floor(eta / 3600),
etaMinutes = Math.floor((eta - etaHours * 3600) / 60),
etaSeconds = eta - etaHours * 3600 - etaMinutes * 60;
if (etaHours < 10) // @ts-expect-error
etaHours = '0' + etaHours.toString();
if (etaMinutes < 10) // @ts-expect-error
etaMinutes = '0' + etaMinutes.toString();
if (etaSeconds < 10) // @ts-expect-error
etaSeconds = '0' + etaSeconds.toString();
$('#downloader .progress').css('width', `${ Math.round(current / total * 100) }%`);
$('#speed').text(`${ (this.progressBar.temp / (Date.now() - this.progressBar.prevTime) * 1000 / 1024 / 1024).toFixed(2) } MB/s`);
$('#eta').text(`ETA: ${etaHours}:${etaMinutes}:${etaSeconds}`);
this.progressBar.prevTime = Date.now();
this.progressBar.temp = 0;
}
}
public static clearProgressBar(): void
{
$('#downloader-panel').css('display', 'none');
$('#launch').css('display', 'block');
$('#downloaded').text('');
$('#speed').text('');
$('#eta').text('');
$('#downloader .progress').css('width', '0');
}
}

View file

@ -1,11 +1,12 @@
const path = require('path');
const fs = require('fs');
const { exec, spawn } = require('child_process');
const { exec } = require('child_process');
const { ipcRenderer } = require('electron');
import $ from 'cash-dom';
import { Genshinlib } from './Genshinlib';
import { LauncherUI } from './LauncherUI';
if (!fs.existsSync(Genshinlib.prefixDir))
fs.mkdirSync(Genshinlib.prefixDir, { recursive: true });
@ -16,28 +17,17 @@ $(() => {
$('body').css('background-image', `url(${ Genshinlib.getBackgroundUri() })`);
// TODO: create LauncherUI class and move a lot of code there
// because it becomes a fucking unfunny joke
Genshinlib.getData().then(data => {
// Update available
if (Genshinlib.version != data.game.latest.version)
$('#launch').text(Genshinlib.version === null ? 'Install' : 'Update');
LauncherUI.setState(Genshinlib.version === null ? 'game-installation-available' : 'game-update-available');
// Patch version is incorrect
else if (Genshinlib.getConfig().patch.version != Genshinlib.getPatchInfo().version)
{
// Patch is not available
if (Genshinlib.getPatchInfo().version !== data.game.latest.version)
{
$('#launch').attr('disabled', 'disabled');
$('#launch').text('Patch required');
$('#launch').addClass('hint--top');
$('#launch').addClass('hint--medium');
$('#launch').attr('data-hint', 'This game version doesn\'t have the anti-cheat patch. Please, wait a few days before it will be created');
}
LauncherUI.setState('patch-unavailable');
// Patch available
else if (Genshinlib.getPatchInfo().version === data.game.latest.version)
@ -47,41 +37,15 @@ $(() => {
{
console.log(`%c> Applying patch...`, 'font-size: 16px');
$('#launch').attr('disabled', 'disabled');
$('#launch').text('Applying patch...');
LauncherUI.setState('patch-applying');
let patcherProcess = spawn('bash', [Genshinlib.patchSh], {
cwd: Genshinlib.gameDir,
env: {
...process.env,
WINEPREFIX: Genshinlib.prefixDir
}
});
patcherProcess.stdout.on('data', (data: string) => console.log(data.toString()));
patcherProcess.on('close', () => {
Genshinlib.setConfig({
...Genshinlib.getConfig(),
patch: Genshinlib.getPatchInfo()
});
$('#launch').removeAttr('disabled');
$('#launch').text('Launch');
});
Genshinlib.applyPatch(() => {
LauncherUI.setState('game-launch-available');
}, (data) => console.log(data.toString()));
}
// Patch is in testing phase
else
{
$('#launch').text('Apply test patch');
$('#launch').addClass('button-blue');
$('#launch').addClass('hint--top');
$('#launch').addClass('hint--large');
$('#launch').attr('data-hint', 'This game version has the anti-cheat patch, but it is in the test phase. You can wait a few days until it will become stable or apply it on your own risc');
}
else LauncherUI.setState('test-patch-available');
}
}
@ -91,28 +55,11 @@ $(() => {
{
console.log(`%c> Applying patch...`, 'font-size: 16px');
$('#launch').attr('disabled', 'disabled');
$('#launch').text('Applying patch...');
LauncherUI.setState('patch-applying');
let patcherProcess = spawn('bash', [Genshinlib.patchSh], {
cwd: Genshinlib.gameDir,
env: {
...process.env,
WINEPREFIX: Genshinlib.prefixDir
}
});
patcherProcess.stdout.on('data', (data: string) => console.log(data.toString()));
patcherProcess.on('close', () => {
Genshinlib.setConfig({
...Genshinlib.getConfig(),
patch: Genshinlib.getPatchInfo()
});
$('#launch').removeAttr('disabled');
$('#launch').text('Launch');
});
Genshinlib.applyPatch(() => {
LauncherUI.setState('game-launch-available');
}, (data) => console.log(data.toString()));
}
$('#launch').on('click', async () => {
@ -168,34 +115,11 @@ $(() => {
{
console.log(`%c> Applying patch...`, 'font-size: 16px');
$('#launch').attr('disabled', 'disabled');
$('#launch').text('Applying patch...');
LauncherUI.setState('patch-applying');
let patcherProcess = spawn('bash', [Genshinlib.patchSh], {
cwd: Genshinlib.gameDir,
env: {
...process.env,
WINEPREFIX: Genshinlib.prefixDir
}
});
patcherProcess.stdout.on('data', (data: string) => console.log(data.toString()));
patcherProcess.on('close', () => {
Genshinlib.setConfig({
...Genshinlib.getConfig(),
patch: Genshinlib.getPatchInfo()
});
$('#launch').removeClass('button-blue');
$('#launch').removeClass('hint--top');
$('#launch').removeClass('hint--large');
$('#launch').removeAttr('disabled');
$('#launch').removeAttr('data-hint');
$('#launch').text('Launch');
});
Genshinlib.applyPatch(() => {
LauncherUI.setState('game-launch-available');
}, (data) => console.log(data.toString()));
}
// Installing game
@ -203,9 +127,6 @@ $(() => {
{
console.log(`%c> Downloading game data...`, 'font-size: 16px');
$('#launch').css('display', 'none');
$('#downloader-panel').css('display', 'block');
let diff = {
path: data.game.latest.path,
name: `latest-${data.game.latest.version}.zip`,
@ -223,41 +144,14 @@ $(() => {
if (fs.existsSync(path.join(Genshinlib.gameDir, diff.name)))
fs.unlinkSync(path.join(Genshinlib.gameDir, diff.name));
let beganAt = Date.now(), prevTime = Date.now(), downloaded = 0;
/**
* Downloading game
*/
LauncherUI.initProgressBar();
Genshinlib.downloadFile(diff.path, path.join(Genshinlib.launcherDir, diff.name), (current: number, total: number, difference: number) => {
$('#downloaded').text(`Downloaded: ${ Math.round(current / total * 100) }% (${ (current / 1024 / 1024 / 1024).toFixed(2) } GB / ${ Math.round(total / 1024 / 1024 / 1024).toFixed(2) } GB)`);
downloaded += difference;
if (Date.now() - prevTime > 1000)
{
let eta = Math.round(total / current * (Date.now() - beganAt) / 1000); // seconds
let etaHours = Math.floor(eta / 3600),
etaMinutes = Math.floor((eta - etaHours * 3600) / 60),
etaSeconds = eta - etaHours * 3600 - etaMinutes * 60;
if (etaHours < 10) // @ts-expect-error
etaHours = '0' + etaHours.toString();
if (etaMinutes < 10) // @ts-expect-error
etaMinutes = '0' + etaMinutes.toString();
if (etaSeconds < 10) // @ts-expect-error
etaSeconds = '0' + etaSeconds.toString();
$('#downloader .progress').css('width', `${ Math.round(current / total * 100) }%`);
$('#speed').text(`${ (downloaded / (Date.now() - prevTime) * 1000 / 1024 / 1024).toFixed(2) } MB/s`);
$('#eta').text(`ETA: ${etaHours}:${etaMinutes}:${etaSeconds}`);
prevTime = Date.now();
downloaded = 0;
}
LauncherUI.updateProgressBar('Downloaded', current, total, difference);
}).then(() => {
/**
* Unpacking downloaded game
@ -265,44 +159,18 @@ $(() => {
console.log(`%c> Unpacking game data...`, 'font-size: 16px');
$('#speed').text('');
$('#eta').text('');
if (!fs.existsSync(Genshinlib.gameDir))
fs.mkdirSync(Genshinlib.gameDir, { recursive: true });
let beganAt = Date.now(), prevTime = Date.now(), unpacked = 0;
LauncherUI.initProgressBar();
Genshinlib.unzip(path.join(Genshinlib.launcherDir, diff.name), Genshinlib.gameDir, (current: number, total: number, difference: number) => {
$('#downloaded').text(`Unpacking: ${ Math.round(current / total * 100) }% (${ (current / 1024 / 1024 / 1024).toFixed(2) } GB / ${ Math.round(total / 1024 / 1024 / 1024).toFixed(2) } GB)`);
unpacked += difference;
if (Date.now() - prevTime > 1000)
{
let eta = Math.round(total / current * (Date.now() - beganAt) / 1000); // seconds
let etaHours = Math.floor(eta / 3600),
etaMinutes = Math.floor((eta - etaHours * 3600) / 60),
etaSeconds = eta - etaHours * 3600 - etaMinutes * 60;
if (etaHours < 10) // @ts-expect-error
etaHours = '0' + etaHours.toString();
if (etaMinutes < 10) // @ts-expect-error
etaMinutes = '0' + etaMinutes.toString();
if (etaSeconds < 10) // @ts-expect-error
etaSeconds = '0' + etaSeconds.toString();
$('#downloader .progress').css('width', `${ Math.round(current / total * 100) }%`);
$('#speed').text(`${ (unpacked / (Date.now() - prevTime) * 1000 / 1024 / 1024).toFixed(2) } MB/s`);
$('#eta').text(`ETA: ${etaHours}:${etaMinutes}:${etaSeconds}`);
prevTime = Date.now();
unpacked = 0;
}
LauncherUI.updateProgressBar('Unpacking', current, total, difference);
}).then(() => {
/**
* Downloading voice data
*/
console.log(`%c> Downloading voice data...`, 'font-size: 16px');
fs.unlinkSync(path.join(Genshinlib.launcherDir, diff.name));
@ -317,82 +185,21 @@ $(() => {
break;
}
let beganAt = Date.now(), prevTime = Date.now(), downloaded = 0;
/**
* Downloading voice data
*/
LauncherUI.initProgressBar();
Genshinlib.downloadFile(voicePack.path, path.join(Genshinlib.launcherDir, voicePack.name), (current: number, total: number, difference: number) => {
$('#downloaded').text(`Downloaded: ${ Math.round(current / total * 100) }% (${ (current / 1024 / 1024 / 1024).toFixed(2) } GB / ${ Math.round(total / 1024 / 1024 / 1024).toFixed(2) } GB)`);
downloaded += difference;
if (Date.now() - prevTime > 1000)
{
let eta = Math.round(total / current * (Date.now() - beganAt) / 1000); // seconds
let etaHours = Math.floor(eta / 3600),
etaMinutes = Math.floor((eta - etaHours * 3600) / 60),
etaSeconds = eta - etaHours * 3600 - etaMinutes * 60;
if (etaHours < 10) // @ts-expect-error
etaHours = '0' + etaHours.toString();
if (etaMinutes < 10) // @ts-expect-error
etaMinutes = '0' + etaMinutes.toString();
if (etaSeconds < 10) // @ts-expect-error
etaSeconds = '0' + etaSeconds.toString();
$('#downloader .progress').css('width', `${ Math.round(current / total * 100) }%`);
$('#speed').text(`${ (downloaded / (Date.now() - prevTime) * 1000 / 1024 / 1024).toFixed(2) } MB/s`);
$('#eta').text(`ETA: ${etaHours}:${etaMinutes}:${etaSeconds}`);
prevTime = Date.now();
downloaded = 0;
}
LauncherUI.updateProgressBar('Downloaded', current, total, difference);
}).then(() => {
/**
* Unpacking downloaded game
*/
console.log(`%c> Unpacking voice data...`, 'font-size: 16px');
$('#speed').text('');
$('#eta').text('');
console.log(`%c> Unpacking voice data...`, 'font-size: 16px');
let beganAt = Date.now(), prevTime = Date.now(), unpacked = 0;
LauncherUI.initProgressBar();
Genshinlib.unzip(path.join(Genshinlib.launcherDir, voicePack.name), Genshinlib.gameDir, (current: number, total: number, difference: number) => {
$('#downloaded').text(`Unpacking: ${ Math.round(current / total * 100) }% (${ (current / 1024 / 1024 / 1024).toFixed(2) } GB / ${ Math.round(total / 1024 / 1024 / 1024).toFixed(2) } GB)`);
unpacked += difference;
if (Date.now() - prevTime > 1000)
{
let eta = Math.round(total / current * (Date.now() - beganAt) / 1000); // seconds
let etaHours = Math.floor(eta / 3600),
etaMinutes = Math.floor((eta - etaHours * 3600) / 60),
etaSeconds = eta - etaHours * 3600 - etaMinutes * 60;
if (etaHours < 10) // @ts-expect-error
etaHours = '0' + etaHours.toString();
if (etaMinutes < 10) // @ts-expect-error
etaMinutes = '0' + etaMinutes.toString();
if (etaSeconds < 10) // @ts-expect-error
etaSeconds = '0' + etaSeconds.toString();
$('#downloader .progress').css('width', `${ Math.round(current / total * 100) }%`);
$('#speed').text(`${ (unpacked / (Date.now() - prevTime) * 1000 / 1024 / 1024).toFixed(2) } MB/s`);
$('#eta').text(`ETA: ${etaHours}:${etaMinutes}:${etaSeconds}`);
prevTime = Date.now();
unpacked = 0;
}
LauncherUI.updateProgressBar('Unpacking', current, total, difference);
}).then(() => {
fs.unlinkSync(path.join(Genshinlib.launcherDir, voicePack.name));
@ -408,45 +215,16 @@ $(() => {
console.log(`%c> Applying patch...`, 'font-size: 16px');
// patch-applying state changes only button text
$('#downloaded').text('Applying patch...');
let patcherProcess = spawn('bash', [Genshinlib.patchSh], {
cwd: Genshinlib.gameDir,
env: {
...process.env,
WINEPREFIX: Genshinlib.prefixDir
}
});
patcherProcess.stdout.on('data', (data: string) => console.log(data.toString()));
patcherProcess.on('close', () => {
Genshinlib.setConfig({
...Genshinlib.getConfig(),
patch: Genshinlib.getPatchInfo()
});
$('#launch').css('display', 'block');
$('#downloader-panel').css('display', 'none');
$('#launch').text('Launch');
});
Genshinlib.applyPatch(() => {
LauncherUI.setState('game-launch-available');
}, (data) => console.log(data.toString()));
}
// Patch is not available
else
{
$('#launch').css('display', 'block');
$('#downloader-panel').css('display', 'none');
$('#launch').attr('disabled', 'disabled');
$('#launch').text('Patch required');
$('#launch').addClass('hint--top');
$('#launch').addClass('hint--medium');
$('#launch').attr('data-hint', 'This game version doesn\'t have the anti-cheat patch. Please, wait a few days before it will be created');
}
else LauncherUI.setState('patch-unavailable');
});
}).catch(err => console.log(err));
}).catch(err => console.log(err));