From 245f21c3d84bc09160e27bda31ce8bccc23e77f3 Mon Sep 17 00:00:00 2001 From: Merijn Hendriks Date: Mon, 25 Mar 2024 09:53:20 +0000 Subject: [PATCH] Improve bundle documentation (!101) Most of this code was (re-)written in 0.12.9, almost 3 years ago. Current understanding of how it worked was limited, so I went back and broke it until I understood it properly. I added comments where I could and slightly altered the logic of `GetManifestJson` to make it easier to read. Regarding removal of `GetManifestBundle`: you can't. While `Windows.json` contains most of the bundle info, some info (like `Doge`) resides outside of this. Removing `GetManifestBundle` results in the game failing to load. Reviewed-on: https://dev.sp-tarkov.com/SPT-AKI/Modules/pulls/101 Co-authored-by: Merijn Hendriks Co-committed-by: Merijn Hendriks --- project/Aki.Custom/Patches/EasyAssetsPatch.cs | 55 ++++++++++++------- project/Aki.Custom/Patches/EasyBundlePatch.cs | 7 ++- 2 files changed, 41 insertions(+), 21 deletions(-) diff --git a/project/Aki.Custom/Patches/EasyAssetsPatch.cs b/project/Aki.Custom/Patches/EasyAssetsPatch.cs index a2209b2..a9a3c3f 100644 --- a/project/Aki.Custom/Patches/EasyAssetsPatch.cs +++ b/project/Aki.Custom/Patches/EasyAssetsPatch.cs @@ -11,6 +11,7 @@ using System.IO; using System.Linq; using System.Reflection; using System.Threading.Tasks; +using Aki.Common.Utils; using Aki.Custom.Models; using Aki.Custom.Utils; using DependencyGraph = DependencyGraph; @@ -57,43 +58,55 @@ namespace Aki.Custom.Patches return false; } - private static async Task Init(EasyAssets instance, [CanBeNull] IBundleLock bundleLock, string defaultKey, string rootPath, - string platformName, [CanBeNull] Func shouldExclude, Func bundleCheck) + private static async Task Init(EasyAssets instance, [CanBeNull] IBundleLock bundleLock, string defaultKey, string rootPath, string platformName, [CanBeNull] Func shouldExclude, Func bundleCheck) { // platform manifest var path = $"{rootPath.Replace("file:///", string.Empty).Replace("file://", string.Empty)}/{platformName}/"; var filepath = path + platformName; - var jsonPath = filepath + ".json"; - var manifest = (File.Exists(jsonPath)) ? await GetManifestJson(filepath) : await GetManifestBundle(filepath); + var jsonfile = filepath + ".json"; + var manifest = File.Exists(jsonfile) + ? await GetManifestJson(jsonfile) + : await GetManifestBundle(filepath); - // load bundles - var bundleNames = manifest.GetAllAssetBundles().Union(BundleManager.Bundles.Keys).ToArray(); + // create bundles array from obfuscated type + var bundleNames = manifest.GetAllAssetBundles() + .Union(BundleManager.Bundles.Keys) + .ToArray(); var bundles = (IEasyBundle[])Array.CreateInstance(EasyBundleHelper.Type, bundleNames.Length); + // create bundle lock if (bundleLock == null) { bundleLock = new BundleLock(int.MaxValue); } + // create bundle of obfuscated type for (var i = 0; i < bundleNames.Length; i++) { bundles[i] = (IEasyBundle)Activator.CreateInstance(EasyBundleHelper.Type, new object[] - { - bundleNames[i], - path, - manifest, - bundleLock, - bundleCheck - }); + { + bundleNames[i], + path, + manifest, + bundleLock, + bundleCheck + }); await JobScheduler.Yield(EJobPriority.Immediate); } + // create dependency graph instance.Manifest = manifest; _bundlesField.SetValue(instance, bundles); instance.System = new DependencyGraph(bundles, defaultKey, shouldExclude); } + // NOTE: used by: + // - EscapeFromTarkov_Data/StreamingAssets/Windows/cubemaps + // - EscapeFromTarkov_Data/StreamingAssets/Windows/defaultmaterial + // - EscapeFromTarkov_Data/StreamingAssets/Windows/dissonancesetup + // - EscapeFromTarkov_Data/StreamingAssets/Windows/Doge + // - EscapeFromTarkov_Data/StreamingAssets/Windows/shaders private static async Task GetManifestBundle(string filepath) { var manifestLoading = AssetBundle.LoadFromFileAsync(filepath); @@ -108,16 +121,18 @@ namespace Aki.Custom.Patches private static async Task GetManifestJson(string filepath) { - var text = string.Empty; + var text = VFS.ReadTextFile(filepath); - using (var reader = File.OpenText($"{filepath}.json")) - { - text = await reader.ReadToEndAsync(); - } + /* we cannot parse directly as , because... + [Error : Unity Log] JsonSerializationException: Expected string when reading UnityEngine.Hash128 type, got 'StartObject' <>. Path '['assets/content/weapons/animations/simple_animations.bundle'].Hash', line 1, position 176. + ...so we need to first convert it to a slimmed-down type (BundleItem), then convert back to BundleDetails. + */ + var raw = JsonConvert.DeserializeObject>(text); + var converted = raw.ToDictionary(GetPairKey, GetPairValue); - var data = JsonConvert.DeserializeObject>(text).ToDictionary(GetPairKey, GetPairValue); + // initialize manifest var manifest = ScriptableObject.CreateInstance(); - manifest.SetResults(data); + manifest.SetResults(converted); return manifest; } diff --git a/project/Aki.Custom/Patches/EasyBundlePatch.cs b/project/Aki.Custom/Patches/EasyBundlePatch.cs index 7dad516..5bf7c3f 100644 --- a/project/Aki.Custom/Patches/EasyBundlePatch.cs +++ b/project/Aki.Custom/Patches/EasyBundlePatch.cs @@ -32,7 +32,12 @@ namespace Aki.Custom.Patches if (BundleManager.Bundles.TryGetValue(key, out BundleInfo bundle)) { - dependencyKeys = (dependencyKeys.Length > 0) ? dependencyKeys.Union(bundle.DependencyKeys).ToArray() : bundle.DependencyKeys; + // server bundle + dependencyKeys = (dependencyKeys.Length > 0) + ? dependencyKeys.Union(bundle.DependencyKeys).ToArray() + : bundle.DependencyKeys; + + // set path to either cachedpath (HTTP) or modpath (local) path = bundle.Path; }