diff --git a/src/pages/index.ts b/src/pages/index.ts index 7c98edf..a37acf0 100644 --- a/src/pages/index.ts +++ b/src/pages/index.ts @@ -18,6 +18,8 @@ const app = Vue.createApp({ mounted: () => { Window.current.show(); + Patch.latest.then(console.log); + /*fetch('https://sdk-os-static.mihoyo.com/hk4e_global/mdk/launcher/api/resource?key=gcStgarh&launcher_id=10', 1000) .then((response) => { console.log(response); diff --git a/src/ts/Game.ts b/src/ts/Game.ts index 7015667..e41f2e0 100644 --- a/src/ts/Game.ts +++ b/src/ts/Game.ts @@ -75,7 +75,8 @@ export default class Game } /** - * Get game versions list + * Get some latest game versions list in descending order + * e.g. ["2.3.0", "2.2.0", "2.1.0"] * * @returns rejects Error object if company's servers are unreachable or they responded with an error */ diff --git a/src/ts/Patch.ts b/src/ts/Patch.ts index e7ba2bb..fdc52c2 100644 --- a/src/ts/Patch.ts +++ b/src/ts/Patch.ts @@ -6,24 +6,139 @@ import type { import md5 from 'js-md5'; import constants from './Constants'; +import Game from './Game'; +import fetch from './core/Fetch'; declare const Neutralino; export default class Patch { - /*public static get latest(): Promise + public static fetchTimeout: number = 3000; + + /** + * Get information about latest available patch + * + * @returns rejects Error object if the patch's repositories are unreachable or they responded with an error + */ + public static get latest(): Promise { - return new Promise((resolve) => { - + return new Promise(async (resolve, reject) => { + const getLatestPatchInfo = (versions: string[], source: 'origin' | 'additional'): Promise => { + return new Promise(async (resolve) => { + const version = versions[0]; + + this.getPatchInfo(version, source) + .then(async (patchInfo) => { + // Patch with version e.g. [2.4.0] doesn't exist + // so we're looking for [2.3.0] instead + if (patchInfo === null) + resolve(await getLatestPatchInfo(versions.slice(1), 'origin')); + + // Otherwise - return found info + else resolve(patchInfo); + }) + .catch(async (error) => { + // If we couldn't connect to the origin repo + // then we can try to connect to the additional one + if (source === 'origin') + resolve(await getLatestPatchInfo(versions, 'additional')); + + // Otherwise both of origin and additional repos + // are unreachable and we should notice about that + else reject(error); + }); + }); + }; + + resolve(await getLatestPatchInfo(await Game.versions, 'origin')); }); } + /** + * Get information about the patch with specified version + * + * Be aware that `applied = true` field may mean that the game version + * is more modern that the one that this patch was made for because + * this field actually compares some files hashes + * + * @returns null if patch with given version doesn't exist in given source + * @returns rejects Error object if the source is unreachable or it responded with an error + */ public static getPatchInfo(version: string, source: 'origin' | 'additional' = 'origin'): Promise { - return new Promise(async (resolve) => { + return new Promise(async (resolve, reject) => { const patchUri = constants.uri.patch[source]; - const patchSh = await fetch(`${patchUri}/raw/master/${version.replaceAll('.', '')}/patch.sh`); + // @ts-expect-error + fetch(`${patchUri}/raw/master/${version.replaceAll('.', '')}/patch.sh`, this.fetchTimeout) + .then((patcherResponse) => { + // Return an error if patch's server is unavailable + if (patcherResponse.status === null) + reject(new Error(`${source} patch repository is unreachable`)); + + // If [version]/patch.sh file doesn't exist - it means + // that patch repo has no [version] + else if (patcherResponse.status === 404) + resolve(null); + + // Otherwise it should be [preparation], [testing] or [stable] + else + { + // @ts-expect-error + fetch(`${patchUri}/raw/master/${version.replaceAll('.', '')}/patch_files/unityplayer_patch.vcdiff`, this.fetchTimeout) + .then((response) => { + // Return an error if patch's server is unavailable + if (response.status === null) + reject(new Error(`${source} patch repository is unreachable`)); + + // If [version]/patch_files/unityplayer_patch.vcdiff file doesn't exist + // then it's [preparation] state and Krock just moved patch.sh file to the [version] folder + else if (response.status === 404) + { + resolve({ + version: version, + state: 'preparation', + applied: false + }); + } + + // Otherwise it's [testing] or [stable] + else + { + patcherResponse.body(this.fetchTimeout) + .then((response) => { + // Return an error if patch's server is unavailable + if (response === '') + reject(new Error(`${source} patch repository is unreachable`)); + + // Otherwise - let's prepare [testing] or [stable] output + else + { + // If this line is commented - then it's [stable] version + // Otherwise it's [testing] + const stableMark = '#echo "If you would like to test this patch, modify this script and remove the line below this one."'; + + let patchInfo: PatchInfo = { + version: version, + state: response.includes(stableMark) ? 'stable' : 'testing', + applied: false + }; + + const originalPlayer = /if \[ "\${sum}" != "([a-z0-9]{32})" \]; then/mg.exec(response); + + // If we could get original UnityPlayer.dll hash - then we can + // compare it with actual UnityPlayer.dll hash and say whether the patch + // was applied or not + if (originalPlayer !== null) + patchInfo.applied = md5(`${constants.paths.gameDir}/UnityPlayer.dll`) != originalPlayer[1]; + + resolve(patchInfo); + } + }); + } + }); + } + }); }); - }*/ + } } diff --git a/src/ts/types/Patch.d.ts b/src/ts/types/Patch.d.ts index d638e20..41c78d0 100644 --- a/src/ts/types/Patch.d.ts +++ b/src/ts/types/Patch.d.ts @@ -11,14 +11,9 @@ type PatchInfo = { state: PatchState; /** - * If the main UnityPlayer patch applied + * If the patch was applied */ - player: boolean; - - /** - * If the anti-login crash patch applied - */ - xlua: boolean; + applied: boolean; }; export type {