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 @@ Settings + + 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