mirror of
https://github.com/an-anime-team/an-anime-game-launcher.git
synced 2025-01-02 06:57:20 +03:00
Add basic file integrity checking
- Uses md5sum and awk to get it from command - Adds button for checking integrity only if launch ready - Skips UnityPlayer if patched
This commit is contained in:
parent
e0a093237d
commit
f05d25b021
5 changed files with 123 additions and 1 deletions
BIN
src/assets/images/integrity.png
Normal file
BIN
src/assets/images/integrity.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 10 KiB |
|
@ -16,6 +16,7 @@
|
||||||
import Gear from './assets/images/gear.png';
|
import Gear from './assets/images/gear.png';
|
||||||
import GearActive from './assets/images/gear-active.png';
|
import GearActive from './assets/images/gear-active.png';
|
||||||
import Download from './assets/images/cloud-download.png';
|
import Download from './assets/images/cloud-download.png';
|
||||||
|
import Integrity from './assets/images/integrity.png';
|
||||||
|
|
||||||
const launcher = new Launcher(onMount);
|
const launcher = new Launcher(onMount);
|
||||||
|
|
||||||
|
@ -145,6 +146,10 @@
|
||||||
<img src={GearActive} class="active" alt="Settings">
|
<img src={GearActive} class="active" alt="Settings">
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<button class="button hint--left hint--small" aria-label="{typeof $locale === 'string' ? $_('launcher.states.ready.integrity') : ''}" id="integrity">
|
||||||
|
<img src={Integrity} alt="Integrity" />
|
||||||
|
</button>
|
||||||
|
|
||||||
<button class="button hint--left hint--small" aria-label="{typeof $locale === 'string' ? $_('launcher.states.ready.predownload') : ''}" id="predownload">
|
<button class="button hint--left hint--small" aria-label="{typeof $locale === 'string' ? $_('launcher.states.ready.predownload') : ''}" id="predownload">
|
||||||
<img src={Download} alt="Download" />
|
<img src={Download} alt="Download" />
|
||||||
</button>
|
</button>
|
||||||
|
|
|
@ -70,6 +70,22 @@ img.background
|
||||||
width: 60%
|
width: 60%
|
||||||
margin: auto
|
margin: auto
|
||||||
|
|
||||||
|
#integrity
|
||||||
|
position: absolute
|
||||||
|
display: none
|
||||||
|
|
||||||
|
width: 52px
|
||||||
|
height: 52px
|
||||||
|
|
||||||
|
right: 386px
|
||||||
|
bottom: 54px
|
||||||
|
|
||||||
|
border-radius: 8px
|
||||||
|
|
||||||
|
img
|
||||||
|
width: 80%
|
||||||
|
margin: auto
|
||||||
|
|
||||||
#settings
|
#settings
|
||||||
width: 76px
|
width: 76px
|
||||||
height: 76px
|
height: 76px
|
||||||
|
|
|
@ -27,6 +27,7 @@ export default class State
|
||||||
public launchButton: HTMLElement;
|
public launchButton: HTMLElement;
|
||||||
public pauseButton: HTMLElement;
|
public pauseButton: HTMLElement;
|
||||||
public predownloadButton: HTMLElement;
|
public predownloadButton: HTMLElement;
|
||||||
|
public integrityButton: HTMLElement;
|
||||||
public settingsButton: HTMLElement;
|
public settingsButton: HTMLElement;
|
||||||
|
|
||||||
protected _state: LauncherState = 'game-launch-available';
|
protected _state: LauncherState = 'game-launch-available';
|
||||||
|
@ -56,6 +57,7 @@ export default class State
|
||||||
this.launchButton = <HTMLElement>document.getElementById('launch');
|
this.launchButton = <HTMLElement>document.getElementById('launch');
|
||||||
this.pauseButton = <HTMLElement>document.getElementById('pause');
|
this.pauseButton = <HTMLElement>document.getElementById('pause');
|
||||||
this.predownloadButton = <HTMLElement>document.getElementById('predownload');
|
this.predownloadButton = <HTMLElement>document.getElementById('predownload');
|
||||||
|
this.integrityButton = <HTMLElement>document.getElementById('integrity');
|
||||||
this.settingsButton = <HTMLElement>document.getElementById('settings');
|
this.settingsButton = <HTMLElement>document.getElementById('settings');
|
||||||
|
|
||||||
this.launchButton.onclick = () => {
|
this.launchButton.onclick = () => {
|
||||||
|
@ -85,7 +87,7 @@ export default class State
|
||||||
const predownloadModule = import('./states/Predownload');
|
const predownloadModule = import('./states/Predownload');
|
||||||
const predownloadVoiceModule = import('./states/PredownloadVoice');
|
const predownloadVoiceModule = import('./states/PredownloadVoice');
|
||||||
|
|
||||||
(this._state === 'game-pre-installation-available' ? predownloadModule : predownloadVoiceModule)
|
(this._state === 'game-launch-available' ? predownloadModule : predownloadVoiceModule)
|
||||||
.then((module) => {
|
.then((module) => {
|
||||||
module.default(this.launcher).then(() => {
|
module.default(this.launcher).then(() => {
|
||||||
this.update().then(() => {
|
this.update().then(() => {
|
||||||
|
@ -96,6 +98,27 @@ export default class State
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
this.integrityButton.onclick = () => {
|
||||||
|
this.launchButton.style['display'] = 'none';
|
||||||
|
this.integrityButton.style['display'] = 'none';
|
||||||
|
this.settingsButton.style['display'] = 'none';
|
||||||
|
|
||||||
|
// We must specify this files here directly
|
||||||
|
// because otherwise Vite will not bundle 'em
|
||||||
|
const integrityModule = import('./states/CheckIntegrity');
|
||||||
|
|
||||||
|
(this._state === 'game-launch-available' ? integrityModule : null!)
|
||||||
|
.then((module) => {
|
||||||
|
module.default(this.launcher).then(() => {
|
||||||
|
this.update().then(() => {
|
||||||
|
this.launchButton.style['display'] = 'block';
|
||||||
|
this.integrityButton.style['display'] = 'block';
|
||||||
|
this.settingsButton.style['display'] = 'block';
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
this.update().then(async () => {
|
this.update().then(async () => {
|
||||||
// Close splash screen
|
// Close splash screen
|
||||||
IPC.write('launcher-loaded');
|
IPC.write('launcher-loaded');
|
||||||
|
@ -239,6 +262,7 @@ export default class State
|
||||||
|
|
||||||
this.launcher.progressBar!.hide();
|
this.launcher.progressBar!.hide();
|
||||||
this.predownloadButton.style['display'] = 'none';
|
this.predownloadButton.style['display'] = 'none';
|
||||||
|
this.integrityButton.style['display'] = 'none';
|
||||||
|
|
||||||
this.launchButton.classList.remove('button-blue');
|
this.launchButton.classList.remove('button-blue');
|
||||||
this.launchButton.setAttribute('aria-label', '');
|
this.launchButton.setAttribute('aria-label', '');
|
||||||
|
@ -272,6 +296,8 @@ export default class State
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'game-launch-available':
|
case 'game-launch-available':
|
||||||
|
this.integrityButton.style['display'] = 'block';
|
||||||
|
|
||||||
this.launchButton.textContent = dictionary['ready']['launch'];
|
this.launchButton.textContent = dictionary['ready']['launch'];
|
||||||
|
|
||||||
break;
|
break;
|
||||||
|
|
75
src/ts/launcher/states/CheckIntegrity.ts
Normal file
75
src/ts/launcher/states/CheckIntegrity.ts
Normal file
|
@ -0,0 +1,75 @@
|
||||||
|
import type Launcher from '../../Launcher';
|
||||||
|
import { Debug, Notification, fs, path } from '../../../empathize';
|
||||||
|
|
||||||
|
import constants from '../../Constants';
|
||||||
|
import Patch from "../../Patch";
|
||||||
|
import Locales from '../Locales';
|
||||||
|
|
||||||
|
declare const Neutralino;
|
||||||
|
|
||||||
|
export default (launcher: Launcher): Promise<void> => {
|
||||||
|
return new Promise(async (resolve) => {
|
||||||
|
const gameDir = await constants.paths.gameDir;
|
||||||
|
Neutralino.filesystem.readFile(`${gameDir}/pkg_version`)
|
||||||
|
.then(async (files) => {
|
||||||
|
let checkErrors = 0;
|
||||||
|
|
||||||
|
files = files.split(/\r\n|\r|\n/).filter((file) => file != '');
|
||||||
|
|
||||||
|
const patch = await Patch.latest;
|
||||||
|
|
||||||
|
if (files.length > 0)
|
||||||
|
{
|
||||||
|
launcher.progressBar?.init({
|
||||||
|
label: Locales.translate('launcher.progress.game.integrity_check') as string,
|
||||||
|
showSpeed: false,
|
||||||
|
showEta: true,
|
||||||
|
showPercents: true,
|
||||||
|
showTotals: false
|
||||||
|
});
|
||||||
|
|
||||||
|
launcher.progressBar?.show();
|
||||||
|
|
||||||
|
let current = 0, total = files.length;
|
||||||
|
const mismatchedFiles = new Array();
|
||||||
|
|
||||||
|
for (const file of files)
|
||||||
|
{
|
||||||
|
// {"remoteName": "GenshinImpact_Data/StreamingAssets/AssetBundles/blocks/00/16567284.blk", "md5": "79ab71cfff894edeaaef025ef1152b77", "fileSize": 3232361}
|
||||||
|
const fileCheckInfo = JSON.parse(file) as { remoteName: string, md5: string, fileSize: number };
|
||||||
|
|
||||||
|
if (await fs.exists(`${gameDir}/${fileCheckInfo.remoteName}`))
|
||||||
|
{
|
||||||
|
const process = await Neutralino.os.execCommand(`md5sum "${path.addSlashes(`${gameDir}/${fileCheckInfo.remoteName}`)}" | awk '{ print $1 }'`);
|
||||||
|
const md5 = process.stdOut || process.stdErr;
|
||||||
|
|
||||||
|
if (md5.substring(0, md5.length - 1) != fileCheckInfo.md5)
|
||||||
|
{
|
||||||
|
if (fileCheckInfo.remoteName.includes('UnityPlayer.dll') && patch.applied)
|
||||||
|
console.log('UnityPlayer patched. Skipping check...');
|
||||||
|
else
|
||||||
|
{
|
||||||
|
++checkErrors;
|
||||||
|
mismatchedFiles.push(fileCheckInfo);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
launcher.progressBar?.update(++current, total, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
Debug.log({
|
||||||
|
function: 'Launcher/States/Integrity',
|
||||||
|
message: `Checked ${total} files${checkErrors > 0 ? `, there were ${checkErrors} mismatch(es):\n${JSON.stringify(mismatchedFiles, null, 4)}` : ', there were no mismatches'}`
|
||||||
|
});
|
||||||
|
|
||||||
|
mismatchedFiles.length = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
launcher.progressBar?.hide();
|
||||||
|
resolve();
|
||||||
|
})
|
||||||
|
.catch(() => resolve());
|
||||||
|
})
|
||||||
|
}
|
Loading…
Reference in a new issue