From f05d25b021cd8a3c9da60bbafcea746da9d2a122 Mon Sep 17 00:00:00 2001 From: Maroxy Date: Sun, 20 Feb 2022 04:52:34 +0100 Subject: [PATCH] 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 --- src/assets/images/integrity.png | Bin 0 -> 10396 bytes src/index.svelte | 5 ++ src/sass/index.sass | 16 +++++ src/ts/launcher/State.ts | 28 ++++++++- src/ts/launcher/states/CheckIntegrity.ts | 75 +++++++++++++++++++++++ 5 files changed, 123 insertions(+), 1 deletion(-) create mode 100644 src/assets/images/integrity.png create mode 100644 src/ts/launcher/states/CheckIntegrity.ts diff --git a/src/assets/images/integrity.png b/src/assets/images/integrity.png new file mode 100644 index 0000000000000000000000000000000000000000..b3f07bc732ee6e63eaf92268dea6c8cc8010e506 GIT binary patch literal 10396 zcmeHrc|26_`}di#?|Wn&YroD*wt+L(orpAi567E==g zOUgg#@Svxq{5EQ1+5iB&6l7)RXNd`b5Qum;4=)_V?+O72!3BD_0YKp3LW*OtfGo2i z`KV!3R}wpCODAp~v(}Xtyv!{3j}F{S~l z@#*{JnPXoQKgf98$V1P4Z!3zsSVQY=S)=Z zian!J5@EHp$v#Czd)`~`=#+yDcva?fz=!1vr)($MblI#Hk5s>nc6UhZ6kX+>^y^nx z2w#Wa$rG)3C|CzZ2hmH6M=Uk(7!=KD<$pT|^bg5l? zAzNn~diwQcLX)(?72(AAy2a?Gg7q1x1R4iLi!&lE*Yh8O={cU-sOf{jnMPT6FDSc- z>p_G_jA-}Rs%yY=;(xn*caa9gy{WaIO z>do)(47t9=y?bsEbqDzXp4x;`yzC<6SHyIc{o&*RZ>jT2xB%Ifh{+1RGZf%cnEPf2 zZpB+P!TmAQC*o7|!Q%Dmc7;m3|F&aG<%aRB_&UcfeMqFXp2*`clMACQjX!y}1+v4Q z-Mc|Zu>%0mH4lA#3sZglKcr6)e0oTthDoCZcNF<)oEQ(Ca*P`F^V3HR3CXLQno z_ z?KggWJol+UuhaYO{7c`)^HcKj)sKZ?B;51md><0&8RT3Tlq7OV~v5F!~*Wa-}79!OxF?R8R;A2#^m@md6v_6=3S> z>I#ZV3Q9^)iUrj7ijN;A5bEPAehBd!h5^nOOY|W4dEk8@hnN@_yuY88m>8uW@<)8$ z1T(Wg;eC96XMw_pLLi2q0Fzf#@b*^tyN9oz;bjWQ?*aXf9==wT1zo`s=Zp6zVsVC- zaXxwg%YsezW*;rK{bJl4Y%`Rh~- zp{As!j&X%5;c&`O6-COKi=vt;R2}XHcg48E;V^aB-=Iu=eEl#!Sll5L1zg^Pf}`Z7 zgvDZA5fpDFEK~)qss>eqxnZDKbvH#U45J29gZ~}EoajMOCC2OTQ5{0LQlJzuFdSAL zp#nuHV=zz^3|s|@L8!o?aAkFvqNwUimlN!?GYL$xw*J9O{$;@XLde3sRqm!}#HeR(QOZme^rPkVDU3 z@`h;sRumHtUy8+*L&g8D`Lno7zg_({1-v|dbwMD%WQ)XLe;dRXa~bFQYaoi>Z(Z06 z7$0{WWq$uIs6X00{)b|zD`M1DRbWa`xRSa$rC3TXP!|kN1?q-DxT#{))m;=Zn17=C z;@$iLFhrcLJ0(*}HWUH<$_66+i^}8wv<|p{JLE}`GPf0#ph}8XiZG;#5>iE3mU5z~ zD5mh=Pf=M#$&I2D7m9SSYETuNG8~F=bHzYi)e){(b!AnIq6+q(LI1y|sDy;6Ayw4= z|D}k-DpCrGK|pZ`xEoXjfmNbZk+PJ+5pGyjb+{`+4GaI@PEm<*=cfnZ|q3jj=9hX)8qP3NOD()pR18PZLIxj{V8!9k9C0N^e$HPE%9yqJ49XKQ6! z@h(G#G464~85(Z73p~*m;!S-5D)z92ePq9|vg*YJzf(Q1iz?kQ%wiFcwNrg_Yj<6-J9>A#A;lj9c~+Gd(x zl>;>`Njh(mn*6%mm#0?gn8|v4j+^O1Qjo-k-p})-u8(AQ0QIIIBFB62cq3fYgO|`N z1jJAH5_Ux-hR2FTKH`&m-~X_*Zr^{*eLrRWRL^aa%gLUgC)Xf8K#t@a)TFLyzeI@C zoUdt0V9l8xRx}sU)XMPUC)6XF_AkKDQPVtE5Hj*ZpVqY~$C;j2Jd4#dmD6Bgpq_7N?2)bPim)<_XrLiLbnHnz>~pz0M!*#ld9Eo;m_e=4_smNZl9Z_sFe*mtbiPN{ROjUG?VOoR9m|cv zUQmBj&hePxSWF=Qo3glyuP9DA>l2yMtPBawknc?SV^^|V%q6Q0s6T^O*aoR|hY(5c z5(vDCk$V!uqX)L2x*K%|&o2V^wdWgX)tYa9EaoVDwHaWw=gFICeu=D{+rTU{<}k|R zwNmzO?*%9DJgd%}ZnKWor&C3s*s*b_HbcUWZW~X9T@kiiWjbF*e;o#<*79G9xfyyRGVE??1Kh1{8U^Vz}Rf@wVpRT?~Yn&Crqk38S@hG(hyf|@;sgQP;s=%$5Jp2vg^V2c1RzrrqUgUYMD`(VyGsl=5r$aMtz3E1CjblKPUjT9x~1@dE@@kH3}hgw zP&|<7LKZdfrsVQuPXm?+5@X79y4xmU6mFKlrWq*>LM;T+I<8Jln(3yGg8Pv@4OR`{ z?+^-<24oFx6OzNkl$6b2jd(L&&jZ ziMQi`E|~N|k#UXv3)m1!sJS;d^3G_0nto!{41R{vBYq(;WIp(9*UAK`F3#I{GS1X! zS+L>Oi@pi|C5*gBvp3ZfZ6=2bB#UDS-6JbDf0_qxv^HF}vAwXytVnkS#8 zNhc}zJ^&F;R8Sj9)vuhYjL62C++`*e>m|9>w0viF+ZO(D;=?gOI)nQCSTV~71jztK z$~)ScG_j;2gE1-i@|ls4avNMm4Yc?U%lVeZRDNcBJjjE8uPW(u%vo5E4mE3dE=Q+( z_7p%k1$Z_3<0mUJ-ikJvE`eiJ-y%Y2D3WzCStQV(^Rf|BWUX`x zT#mc^VXT0i9>6`>Sd!e{`h9X*=*j3$-SWk(v$+rS1YIQUGivflpO7IZ$2|P7bnDoF z*G(Ar94-f&d%dyM{bC^0MiYMzGCVw$0r-AZQnhX@57bs8SynvnsPD{X2bu{YST_MN z)PON>dGhV01tbHB7)>LBQU#MnZ@zFAJP!sG zRvjm$0-0MowkpLDbFW35=f%GFQ*Cs~^O8%6FNGeWRTM#W44fmh>cdHC#9f}Dp;%Fb zc*>PDYU-B&mVT})k}*(jj$^3yF;a-v$vcmsI+Ug%4BG#+FHUspM1hjKW$0ojWK@xg zaK6BGFSz_m4Bo1qDvJx4Kes^G(<+Y@vgKGQoRoT~eK*a&s=>2xlmne`qqXou1{cAp z2M;`xd79m)3|JD#_B-91`g~W6e&RXi&aSQ{>?H!{Vs>DrI;|{Wfl(E?sh7$&m;u=r zd=YC)kj-bJPg%+y(aMDo`^piZjsoN#Ewo_A#w_s&lH@ z0ja7m^2p!r*0*R*%D`o^ktE^WU^-ggtGd#Owmy4LR zlI>d@E!q6>`OVP}DU%<{4(M!4oDO9j)E6scA7F6b{#y3- zD0AZ{_7Id0sj=cJ*LyKeG25n{(pR8VD)HWmFyE;heJyaEWzCn-1Mj*wa7QCUz^=(9 zEMk3@6pYt3X_L4GBCR#?9Pl@>@Pku2FYcz5@pA%q!-TA=Fj_!KS@c5gZ6g$c3cLvs zWi*NTQFdP|H^p#MLm>A?u}E6l^Rs%&eny#R;1_%kEVfGy7aWr$s8=8|TbKWXSdf>R7L*>aClv>^$2J z{V{8QPsf1b!k{+tBCwzhM_vR+X;O!<=*A7#?azlVK%^AKZ5etUa=@Rv_RLJ8K{bUm zqg5J^#ROoKTCLXb0BBK|S|zLA^0(C9L%h5#J5~Jryy}-9kghMY{k}x?=uevDBRnuV z3r>MiYV}&0j{=D|nn%P@(#xN1JNOBW+(%eV*hz-8sc|I#wW150v2+ z6DHI!a(&ywZ|!!{fut>d5AzUUN~BIOUhaUunhNfulS-@?Y`W-)Y?3$ zAWoU4yCK4qo$I<|nw|cc%=eG*ciEs{VHIv(p)Bl3@Yaf`561px?L|6N+0NMGd#VP{ zxjvGWn-Cu}B4)`E>?O#bexH_Idu?_;)p#w*UG6&0%Q3It%a*lgz7~3e_CTOjIXm!a zOdkzpXl*=O1iTzVbYDyX)x{~ckCyP)7!zsIZ%?IZW>M9YBz7(}-_&TFQKbp)Z(o{a z1PDhO5Gy;MV)n_XpJ#>|;UtUxQugi-tQtac1KFLWn@HSI1B4carZbz(v%f=c8A zZ^*p+N}6A5eFtJ{k47#)CjDOiY(J~e|@$-^UXnu9bl6tw{Qa@j!KB4Mfq`mt6=8b@^AIUo<;7`xch1MYJ zx;`+8Hb#S1 zw{#Pu(KpQ(ZgIv;8}iTff@x$q02{;L#13+jCp*}FXH!X_R(>QN6J>wobb7+ccbatz zNUxeuDaZkIKKiRk#5uVw)2zTe!1-)V(ghA+fH2csSC>C7vmCJgiuQACE;r4``DZ5e^~ePp!BN9W1v>A{4;2~V~N)R8$yJ-FSe zQT?F7o~9HIawDnXX~fm&ZuQ49N7t>GfLC&<9d(G25Pj{^P?im6$d_?Amd~S;eMBU+ z6Wf|4&)i8{Ql>Fw^W13Jh7&u5CSo@z1b9#woY* zrrMF|pTA`#nfMpWG&8;04v``-l!WIl-pzQw!72{B0qc+DxT4Iye>v(~@wd*J2a)zn z^Vi2;WLSYoJtsgvKDIj3G3)2b306cJFTX^?7m$%6^{J&)$vP8jayB|ow{H6tnT!=| zzK#BZH=*1TF33=)NaEU$3?wtvYS$yl$AAF?rK;<9l!?t^T72;(eAjM}BvlyCf(9B& zHXB9~b4i@guYsP$@9Boh7(~Mo*K3KALkd$fTimaHE@-csM99xa*q6Uv$-266d;Ne! zf0F+T-~AVAvyYP{NF86D7{D5pgahnQv54qpyVE$WAe#dm9b(IcYd!l&K947Yp8Ctx zOt&51K-+8mlAM{5ZG#0fK)C&^`BX7M=RFR%;W?LY+!>`|wtBR@j`Vu%vH*BxgxTaH zgl2WMPxD8dLO*4-7w@N~{>dVAK&yAaNjT6IdOgjVYK=czb*jZ3oTn{B2-AT@31(t+<-jK2eJ*?PZCEY%k+)*i!BD;M;{_H>mXva$a zq36OkDpC`zemb2r7f~x>3uHFTQ9B4Y(`H;c;AP+7*RJjudZ1sLVI+f&STmly$SUEq zu?Bb)y+EGp0|RaRgY=(k-`{i=sxK}LtYZ`-OL1a=TT3(58YZ-Dd$+}Q1GoUIgNqTL z6k0QWxMzP$p~~)J4VY)8cY-7EtZvVBDScViH{&f}XCR zCUNw=tIUC1WE&cZm!WT|slBHIG&Hm^tzL)pz2A9$f(Dhdb@I6-jLO;Zz+@9z{(e{& zu!|b~M19R>(vv!Gc$n!}yzxB%Xq)5ZMK_(Z$KKPlX+zv(>Eg>JMQl!QzC3mHnGt=4 z{9ylhwE`36$tED8^EH2M8Jiyc4ddkR?`@QyGI}24ncqv)O}G^+>+NEcQ+>8H@4NH{ z@ov}JopLgFu*c1O)k{BF`Wr4; zq4b$`y8P~Tw7wDV8=TDc#gzMNgBpxYr;n8F$Du#$6`;#2Z3%H&i)Rcf=$FjlgR4E!Ubnf4ey^%hbFmYo!?ZwW7>(GFlO~^;bCZ> zV7x_xNKDdETk-Xoy@jU68x;sE$W7h)et&yCKK-$B5$B>HG;N$R z?<<-!P9I9I@J`BH&PTeVYKZPCUF4tM4j8E7&)YtJ?^cP#89GadEj7tX$hTIqj~Td}GauUzyG#7^GXLxJ w&R5M03HwQ1=VNhrI#o+f1=G*1FUjDb&Gq)&m_B>T8%@B}@U%gJo^! + + 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