mirror of
https://github.com/an-anime-team/an-anime-game-launcher.git
synced 2024-12-30 05:28:15 +03:00
Improved integrity checking, added pause/resume button
This commit is contained in:
parent
40ed431438
commit
cbe542e995
1 changed files with 302 additions and 88 deletions
|
@ -1,4 +1,5 @@
|
||||||
import type Launcher from '../../Launcher';
|
import type Launcher from '../../Launcher';
|
||||||
|
import type { PatchInfo } from '../../types/Patch';
|
||||||
|
|
||||||
import { fs, path, Downloader } from '../../../empathize';
|
import { fs, path, Downloader } from '../../../empathize';
|
||||||
import { DebugThread } from '@empathize/framework/dist/meta/Debug';
|
import { DebugThread } from '@empathize/framework/dist/meta/Debug';
|
||||||
|
@ -17,60 +18,307 @@ type FileInfo = {
|
||||||
fileSize: number;
|
fileSize: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
class FilesVerifier
|
||||||
* Try to the repair game's file
|
|
||||||
*/
|
|
||||||
function repairFile(fileInfo: FileInfo): Promise<boolean>
|
|
||||||
{
|
{
|
||||||
return new Promise(async (resolve) => {
|
protected files: string[];
|
||||||
const gameDir = await constants.paths.gameDir;
|
protected gameDir: string;
|
||||||
const fileUri = `${(await Game.getLatestData()).game.latest.decompressed_path}/${fileInfo.remoteName}`;
|
protected patch: PatchInfo;
|
||||||
|
protected launcher: Launcher;
|
||||||
|
protected debugThread: DebugThread;
|
||||||
|
|
||||||
Downloader.download(fileUri, `${gameDir}/${fileInfo.remoteName}.new`).then((stream) => {
|
protected current: number = 0;
|
||||||
stream.finish(async () => {
|
protected total: number;
|
||||||
const process = await Neutralino.os.execCommand(`md5sum "${path.addSlashes(`${gameDir}/${fileInfo.remoteName}.new`)}"`);
|
|
||||||
|
|
||||||
if ((process.stdOut || process.stdErr).split(' ')[0] == fileInfo.md5)
|
protected mismatches: FileInfo[] = [];
|
||||||
|
|
||||||
|
protected paused: boolean = false;
|
||||||
|
|
||||||
|
protected onProgress: null|((current: number, total: number) => void) = null;
|
||||||
|
protected onFinished: null|((mismatches: FileInfo[]) => void) = null;
|
||||||
|
|
||||||
|
protected ignoringFiles = [
|
||||||
|
'crashreport.exe',
|
||||||
|
'upload_crash.exe'
|
||||||
|
];
|
||||||
|
|
||||||
|
public constructor(files: string[], gameDir: string, patch: PatchInfo, launcher: Launcher, debugThread: DebugThread)
|
||||||
|
{
|
||||||
|
this.files = files;
|
||||||
|
this.gameDir = gameDir;
|
||||||
|
this.patch = patch;
|
||||||
|
this.launcher = launcher;
|
||||||
|
this.debugThread = debugThread;
|
||||||
|
this.total = files.length;
|
||||||
|
|
||||||
|
// Show pause/resume button
|
||||||
|
launcher.state!.pauseButton.style['display'] = 'block';
|
||||||
|
|
||||||
|
launcher.state!.pauseButton.onclick = () => {
|
||||||
|
if (this.paused)
|
||||||
|
{
|
||||||
|
launcher.state!.pauseButton.textContent = Locales.translate('launcher.progress.pause');
|
||||||
|
|
||||||
|
this.resume();
|
||||||
|
}
|
||||||
|
|
||||||
|
else
|
||||||
|
{
|
||||||
|
launcher.state!.pauseButton.textContent = Locales.translate('launcher.progress.resume');
|
||||||
|
|
||||||
|
this.pause();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
this.process();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected async process()
|
||||||
|
{
|
||||||
|
const file = this.files[this.current++];
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// {"remoteName": "AnAnimeGame_Data/StreamingAssets/AssetBundles/blocks/00/16567284.blk", "md5": "79ab71cfff894edeaaef025ef1152b77", "fileSize": 3232361}
|
||||||
|
const fileCheckInfo: FileInfo = JSON.parse(file);
|
||||||
|
|
||||||
|
let skipping = false;
|
||||||
|
|
||||||
|
for (const ignoringFile of this.ignoringFiles)
|
||||||
|
if (fileCheckInfo.remoteName.includes(ignoringFile))
|
||||||
{
|
{
|
||||||
await fs.remove(`${gameDir}/${fileInfo.remoteName}`);
|
skipping = true;
|
||||||
await fs.move(`${gameDir}/${fileInfo.remoteName}.new`, `${gameDir}/${fileInfo.remoteName}`);
|
|
||||||
|
|
||||||
resolve(true);
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
else
|
if (!skipping)
|
||||||
|
{
|
||||||
|
if (!await fs.exists(`${this.gameDir}/${fileCheckInfo.remoteName}`))
|
||||||
{
|
{
|
||||||
await fs.remove(`${gameDir}/${fileInfo.remoteName}.new`);
|
this.mismatches.push(fileCheckInfo);
|
||||||
|
|
||||||
resolve(false);
|
this.debugThread.log({
|
||||||
|
message: [
|
||||||
|
'File is missing',
|
||||||
|
`[path] ${fileCheckInfo.remoteName}`,
|
||||||
|
`[hash] ${fileCheckInfo.md5}`
|
||||||
|
]
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
else if (!fileCheckInfo.remoteName.includes('UnityPlayer.dll') || !this.patch.applied)
|
||||||
|
{
|
||||||
|
const process = await Neutralino.os.execCommand(`md5sum "${path.addSlashes(`${this.gameDir}/${fileCheckInfo.remoteName}`)}"`);
|
||||||
|
const fileHash = (process.stdOut || process.stdErr).split(' ')[0];
|
||||||
|
|
||||||
|
if (fileHash != fileCheckInfo.md5)
|
||||||
|
{
|
||||||
|
this.mismatches.push(fileCheckInfo);
|
||||||
|
|
||||||
|
this.debugThread.log({
|
||||||
|
message: [
|
||||||
|
'Wrong file hash found',
|
||||||
|
`[path] ${fileCheckInfo.remoteName}`,
|
||||||
|
`[hash] ${fileHash}`,
|
||||||
|
`[remote hash] ${fileCheckInfo.md5}`
|
||||||
|
]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
catch {}
|
||||||
|
|
||||||
|
if (this.onProgress)
|
||||||
|
this.onProgress(this.current, this.total);
|
||||||
|
|
||||||
|
if (this.current == this.total)
|
||||||
|
{
|
||||||
|
if (this.onFinished)
|
||||||
|
this.onFinished(this.mismatches);
|
||||||
|
|
||||||
|
// Hide pause/resume button
|
||||||
|
this.launcher.state!.pauseButton.style['display'] = 'none';
|
||||||
|
}
|
||||||
|
|
||||||
|
else if (!this.paused)
|
||||||
|
this.process();
|
||||||
|
}
|
||||||
|
|
||||||
|
public pause()
|
||||||
|
{
|
||||||
|
this.paused = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public resume()
|
||||||
|
{
|
||||||
|
this.paused = false;
|
||||||
|
|
||||||
|
this.process();
|
||||||
|
}
|
||||||
|
|
||||||
|
public progress(callback: (current: number, total: number) => void)
|
||||||
|
{
|
||||||
|
this.onProgress = callback;
|
||||||
|
}
|
||||||
|
|
||||||
|
public finish(callback: (mismatches: FileInfo[]) => void): void
|
||||||
|
{
|
||||||
|
this.onFinished = callback;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class FilesRepairer
|
||||||
|
{
|
||||||
|
protected mismatches: FileInfo[];
|
||||||
|
protected launcher: Launcher;
|
||||||
|
protected debugThread: DebugThread;
|
||||||
|
|
||||||
|
protected current: number = 0;
|
||||||
|
protected total: number;
|
||||||
|
|
||||||
|
protected paused: boolean = false;
|
||||||
|
|
||||||
|
protected onProgress: null|((current: number, total: number) => void) = null;
|
||||||
|
protected onFinished: null|(() => void) = null;
|
||||||
|
|
||||||
|
public constructor(mismatches: FileInfo[], launcher: Launcher, debugThread: DebugThread)
|
||||||
|
{
|
||||||
|
this.mismatches = mismatches;
|
||||||
|
this.launcher = launcher;
|
||||||
|
this.debugThread = debugThread;
|
||||||
|
this.total = mismatches.length;
|
||||||
|
|
||||||
|
// Show pause/resume button
|
||||||
|
launcher.state!.pauseButton.style['display'] = 'block';
|
||||||
|
|
||||||
|
launcher.state!.pauseButton.onclick = () => {
|
||||||
|
if (this.paused)
|
||||||
|
{
|
||||||
|
launcher.state!.pauseButton.textContent = Locales.translate('launcher.progress.pause');
|
||||||
|
|
||||||
|
this.resume();
|
||||||
|
}
|
||||||
|
|
||||||
|
else
|
||||||
|
{
|
||||||
|
launcher.state!.pauseButton.textContent = Locales.translate('launcher.progress.resume');
|
||||||
|
|
||||||
|
this.pause();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
this.process();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Try to the repair game's file
|
||||||
|
*/
|
||||||
|
protected repairFile(fileInfo: FileInfo): Promise<boolean>
|
||||||
|
{
|
||||||
|
return new Promise(async (resolve) => {
|
||||||
|
const gameDir = await constants.paths.gameDir;
|
||||||
|
const fileUri = `${(await Game.getLatestData()).game.latest.decompressed_path}/${fileInfo.remoteName}`;
|
||||||
|
|
||||||
|
Downloader.download(fileUri, `${gameDir}/${fileInfo.remoteName}.new`).then((stream) => {
|
||||||
|
stream.finish(async () => {
|
||||||
|
const process = await Neutralino.os.execCommand(`md5sum "${path.addSlashes(`${gameDir}/${fileInfo.remoteName}.new`)}"`);
|
||||||
|
|
||||||
|
if ((process.stdOut || process.stdErr).split(' ')[0] == fileInfo.md5)
|
||||||
|
{
|
||||||
|
await fs.remove(`${gameDir}/${fileInfo.remoteName}`);
|
||||||
|
await fs.move(`${gameDir}/${fileInfo.remoteName}.new`, `${gameDir}/${fileInfo.remoteName}`);
|
||||||
|
|
||||||
|
resolve(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
else
|
||||||
|
{
|
||||||
|
await fs.remove(`${gameDir}/${fileInfo.remoteName}.new`);
|
||||||
|
|
||||||
|
resolve(false);
|
||||||
|
}
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
}
|
||||||
|
|
||||||
|
protected async process()
|
||||||
|
{
|
||||||
|
if (this.mismatches[this.current] === undefined)
|
||||||
|
return;
|
||||||
|
|
||||||
|
const fileInfo = this.mismatches[this.current++];
|
||||||
|
|
||||||
|
await this.repairFile(fileInfo).then((success) => {
|
||||||
|
if (!success)
|
||||||
|
this.debugThread.log(`Repair failed: ${fileInfo.remoteName}`);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (this.onProgress)
|
||||||
|
this.onProgress(this.current, this.total);
|
||||||
|
|
||||||
|
if (this.current == this.total)
|
||||||
|
{
|
||||||
|
if (this.onFinished)
|
||||||
|
this.onFinished();
|
||||||
|
|
||||||
|
// Hide pause/resume button
|
||||||
|
this.launcher.state!.pauseButton.style['display'] = 'none';
|
||||||
|
}
|
||||||
|
|
||||||
|
else if (!this.paused)
|
||||||
|
this.process();
|
||||||
|
}
|
||||||
|
|
||||||
|
public pause()
|
||||||
|
{
|
||||||
|
this.paused = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public resume()
|
||||||
|
{
|
||||||
|
this.paused = false;
|
||||||
|
|
||||||
|
this.process();
|
||||||
|
}
|
||||||
|
|
||||||
|
public progress(callback: (current: number, total: number) => void)
|
||||||
|
{
|
||||||
|
this.onProgress = callback;
|
||||||
|
}
|
||||||
|
|
||||||
|
public finish(callback: () => void): void
|
||||||
|
{
|
||||||
|
this.onFinished = callback;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default (launcher: Launcher): Promise<void> => {
|
export default (launcher: Launcher): Promise<void> => {
|
||||||
return new Promise(async (resolve) => {
|
return new Promise(async (resolve) => {
|
||||||
const gameDir = await constants.paths.gameDir;
|
const gameDir = await constants.paths.gameDir;
|
||||||
|
|
||||||
const debugThread = new DebugThread('State/IntegrityCheck', 'Checking files integrity...');
|
const debugThread = new DebugThread('Launcher/State/CheckIntegrity', 'Checking files integrity...');
|
||||||
|
|
||||||
// Check Game and Voice Pack integrity
|
// Check Game and Voice Pack integrity
|
||||||
Neutralino.filesystem.readFile(`${gameDir}/pkg_version`)
|
Neutralino.filesystem.readFile(`${gameDir}/pkg_version`)
|
||||||
.then(async (files) => {
|
.then(async (files) => {
|
||||||
files = files.split(/\r\n|\r|\n/).filter((file) => file != '');
|
files = files.split(/\r\n|\r|\n/);
|
||||||
|
|
||||||
// Add voice packages integrity info
|
// Add voice packages integrity info
|
||||||
for (const voice of await Voice.installed)
|
for (const voice of await Voice.installed)
|
||||||
Neutralino.filesystem.readFile(`${gameDir}/Audio_${Voice.langs[voice.lang]}_pkg_version`)
|
Neutralino.filesystem.readFile(`${gameDir}/Audio_${Voice.langs[voice.lang]}_pkg_version`)
|
||||||
.then(async (voiceFiles) => {
|
.then(async (voiceFiles) => {
|
||||||
files.push(...voiceFiles.split(/\r\n|\r|\n/).filter((file) => file != ''));
|
files.push(...voiceFiles.split(/\r\n|\r|\n/));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
files = files
|
||||||
|
.map((file) => file.trim())
|
||||||
|
.filter((file: string) => file.length > 30);
|
||||||
|
|
||||||
if (files.length > 0)
|
if (files.length > 0)
|
||||||
{
|
{
|
||||||
const patch = await Patch.latest;
|
|
||||||
|
|
||||||
launcher.progressBar?.init({
|
launcher.progressBar?.init({
|
||||||
label: Locales.translate<string>('launcher.progress.game.integrity_check'),
|
label: Locales.translate<string>('launcher.progress.game.integrity_check'),
|
||||||
showSpeed: false,
|
showSpeed: false,
|
||||||
|
@ -81,80 +329,46 @@ export default (launcher: Launcher): Promise<void> => {
|
||||||
|
|
||||||
launcher.progressBar?.show();
|
launcher.progressBar?.show();
|
||||||
|
|
||||||
let current = 0, total = files.length;
|
debugThread.log(`Verifying ${files.length} files...`);
|
||||||
let mismatchedFiles = new Array();
|
|
||||||
|
|
||||||
debugThread.log(`Verifying ${total} files...`);
|
// Find broken files
|
||||||
|
const verifier = new FilesVerifier(files, gameDir, await Patch.latest, launcher, debugThread);
|
||||||
|
|
||||||
for (const file of files)
|
verifier.progress((current, total) => launcher.progressBar?.update(current, total, 1));
|
||||||
{
|
|
||||||
// {"remoteName": "AnAnimeGame_Data/StreamingAssets/AssetBundles/blocks/00/16567284.blk", "md5": "79ab71cfff894edeaaef025ef1152b77", "fileSize": 3232361}
|
|
||||||
const fileCheckInfo: FileInfo = JSON.parse(file);
|
|
||||||
|
|
||||||
// If the file exists and it's not UnityPlayer.dll
|
verifier.finish(async (mismatches) => {
|
||||||
// or if it's UnityPlayer.dll but the patch wasn't applied
|
debugThread.log({
|
||||||
if (await fs.exists(`${gameDir}/${fileCheckInfo.remoteName}`) &&
|
message: mismatches.length == 0 ?
|
||||||
(!fileCheckInfo.remoteName.includes('UnityPlayer.dll') || !patch.applied))
|
`Checked ${files.length} files with ${mismatches.length} mismatches` :
|
||||||
{
|
[
|
||||||
const process = await Neutralino.os.execCommand(`md5sum "${path.addSlashes(`${gameDir}/${fileCheckInfo.remoteName}`)}"`);
|
`Checked ${files.length} files with ${mismatches.length} mismatch(es):`,
|
||||||
const fileHash = (process.stdOut || process.stdErr).split(' ')[0];
|
...mismatches.map((mismatch) => `[${mismatch.md5}] ${mismatch.remoteName}`)
|
||||||
|
]
|
||||||
if (fileHash != fileCheckInfo.md5)
|
|
||||||
{
|
|
||||||
mismatchedFiles.push(fileCheckInfo);
|
|
||||||
|
|
||||||
debugThread.log({
|
|
||||||
message: [
|
|
||||||
'Wrong file hash found',
|
|
||||||
`[path] ${fileCheckInfo.remoteName}`,
|
|
||||||
`[hash] ${fileHash}`,
|
|
||||||
`[remote hash] ${fileCheckInfo.md5}`
|
|
||||||
]
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
launcher.progressBar?.update(++current, total, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
debugThread.log({
|
|
||||||
message: mismatchedFiles.length == 0 ?
|
|
||||||
`Checked ${total} files with ${mismatchedFiles.length} mismatches` :
|
|
||||||
[
|
|
||||||
`Checked ${total} files with ${mismatchedFiles.length} mismatch(es):`,
|
|
||||||
...mismatchedFiles.map((mismatch) => `[${mismatch.md5}] ${mismatch.remoteName}`)
|
|
||||||
]
|
|
||||||
});
|
|
||||||
|
|
||||||
// Replace mismatched files
|
|
||||||
|
|
||||||
if (mismatchedFiles.length > 0)
|
|
||||||
{
|
|
||||||
launcher.progressBar?.init({
|
|
||||||
label: Locales.translate<string>('launcher.progress.game.download_mismatch_files'),
|
|
||||||
showSpeed: false,
|
|
||||||
showEta: false,
|
|
||||||
showPercents: true,
|
|
||||||
showTotals: false
|
|
||||||
});
|
});
|
||||||
|
|
||||||
let current = 0, total = mismatchedFiles.length;
|
// And then repair them
|
||||||
|
if (mismatches.length > 0)
|
||||||
for (const fileInfo of mismatchedFiles)
|
|
||||||
{
|
{
|
||||||
await repairFile(fileInfo).then((success) => {
|
launcher.progressBar?.init({
|
||||||
if (!success)
|
label: Locales.translate<string>('launcher.progress.game.download_mismatch_files'),
|
||||||
debugThread.log(`Repair failed: ${fileInfo.remoteName}`);
|
showSpeed: false,
|
||||||
|
showEta: false,
|
||||||
|
showPercents: true,
|
||||||
|
showTotals: false
|
||||||
});
|
});
|
||||||
|
|
||||||
launcher.progressBar?.update(++current, total, 1);
|
const repairer = new FilesRepairer(mismatches, launcher, debugThread);
|
||||||
|
|
||||||
|
repairer.progress((current, total) => launcher.progressBar?.update(current, total, 1));
|
||||||
|
|
||||||
|
repairer.finish(() => {
|
||||||
|
launcher.progressBar?.hide();
|
||||||
|
|
||||||
|
resolve();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
|
|
||||||
launcher.progressBar?.hide();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
resolve();
|
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch(() => {
|
||||||
debugThread.log('No pkg_version file provided');
|
debugThread.log('No pkg_version file provided');
|
||||||
|
|
Loading…
Reference in a new issue