diff --git a/src/assets/images/integrity.png b/src/assets/images/integrity.png
new file mode 100644
index 0000000..b3f07bc
Binary files /dev/null and b/src/assets/images/integrity.png differ
diff --git a/src/index.svelte b/src/index.svelte
index 3c2e21c..d2ccb5c 100644
--- a/src/index.svelte
+++ b/src/index.svelte
@@ -16,6 +16,7 @@
import Gear from './assets/images/gear.png';
import GearActive from './assets/images/gear-active.png';
import Download from './assets/images/cloud-download.png';
+ import Integrity from './assets/images/integrity.png';
const launcher = new Launcher(onMount);
@@ -145,6 +146,10 @@
+
+
diff --git a/src/sass/index.sass b/src/sass/index.sass
index ef8c929..1578210 100644
--- a/src/sass/index.sass
+++ b/src/sass/index.sass
@@ -70,6 +70,22 @@ img.background
width: 60%
margin: auto
+#integrity
+ position: absolute
+ display: none
+
+ width: 52px
+ height: 52px
+
+ right: 386px
+ bottom: 54px
+
+ border-radius: 8px
+
+ img
+ width: 80%
+ margin: auto
+
#settings
width: 76px
height: 76px
diff --git a/src/ts/launcher/State.ts b/src/ts/launcher/State.ts
index 32085f9..4f52171 100644
--- a/src/ts/launcher/State.ts
+++ b/src/ts/launcher/State.ts
@@ -27,6 +27,7 @@ export default class State
public launchButton: HTMLElement;
public pauseButton: HTMLElement;
public predownloadButton: HTMLElement;
+ public integrityButton: HTMLElement;
public settingsButton: HTMLElement;
protected _state: LauncherState = 'game-launch-available';
@@ -56,6 +57,7 @@ export default class State
this.launchButton = document.getElementById('launch');
this.pauseButton = document.getElementById('pause');
this.predownloadButton = document.getElementById('predownload');
+ this.integrityButton = document.getElementById('integrity');
this.settingsButton = document.getElementById('settings');
this.launchButton.onclick = () => {
@@ -85,7 +87,7 @@ export default class State
const predownloadModule = import('./states/Predownload');
const predownloadVoiceModule = import('./states/PredownloadVoice');
- (this._state === 'game-pre-installation-available' ? predownloadModule : predownloadVoiceModule)
+ (this._state === 'game-launch-available' ? predownloadModule : predownloadVoiceModule)
.then((module) => {
module.default(this.launcher).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 () => {
// Close splash screen
IPC.write('launcher-loaded');
@@ -239,6 +262,7 @@ export default class State
this.launcher.progressBar!.hide();
this.predownloadButton.style['display'] = 'none';
+ this.integrityButton.style['display'] = 'none';
this.launchButton.classList.remove('button-blue');
this.launchButton.setAttribute('aria-label', '');
@@ -272,6 +296,8 @@ export default class State
break;
case 'game-launch-available':
+ this.integrityButton.style['display'] = 'block';
+
this.launchButton.textContent = dictionary['ready']['launch'];
break;
diff --git a/src/ts/launcher/states/CheckIntegrity.ts b/src/ts/launcher/states/CheckIntegrity.ts
new file mode 100644
index 0000000..323dc3d
--- /dev/null
+++ b/src/ts/launcher/states/CheckIntegrity.ts
@@ -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 => {
+ 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());
+ })
+}
\ No newline at end of file