using System; using System.Collections.Generic; using System.Runtime.InteropServices; using AssetsTools.NET; using AssetsTools.NET.Extra; using NitroxModel.DataStructures.Unity; namespace NitroxServer_Subnautica.Resources.Parsers.Helper; public class AssetsBundleManager : AssetsManager { private readonly string aaRootPath; private readonly Dictionary dependenciesByAssetFileInst = new(); private ThreadSafeMonoCecilTempGenerator monoTempGenerator; public AssetsBundleManager(string aaRootPath) { this.aaRootPath = aaRootPath; } public string CleanBundlePath(string bundlePath) { if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux) || RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) { bundlePath = bundlePath.Replace('\\', '/'); } return aaRootPath + bundlePath.Substring(bundlePath.IndexOf('}') + 1); } public AssetsFileInstance LoadBundleWithDependencies(string[] bundlePaths) { BundleFileInstance bundleFile = LoadBundleFile(CleanBundlePath(bundlePaths[0])); AssetsFileInstance assetFileInstance = LoadAssetsFileFromBundle(bundleFile, 0); dependenciesByAssetFileInst[assetFileInstance] = bundlePaths; return assetFileInstance; } /// /// Copied from https://github.com/nesrak1/AssetsTools.NET#full-monobehaviour-writing-example /// /// instance currently used /// of the target GameObject /// Class name of the target MonoBehaviour public AssetFileInfo GetMonoBehaviourFromGameObject(AssetsFileInstance inst, AssetFileInfo targetGameObjectValue, string targetClassName) { //example for finding a specific script and modifying the script on a GameObject AssetTypeValueField playerBf = GetBaseField(inst, targetGameObjectValue); AssetTypeValueField playerComponentArr = playerBf["m_Component"]["Array"]; AssetFileInfo monoBehaviourInf = null; //first let's search for the MonoBehaviour we want in a GameObject foreach (AssetTypeValueField child in playerComponentArr.Children) { //get component info (but don't deserialize yet, loading assets we don't need is wasteful) AssetTypeValueField childPtr = child["component"]; AssetExternal childExt = GetExtAsset(inst, childPtr, true); AssetFileInfo childInf = childExt.info; //skip if not MonoBehaviour if (childInf.GetTypeId(inst.file) != (int)AssetClassID.MonoBehaviour) { continue; } //actually deserialize the MonoBehaviour asset now AssetTypeValueField childBf = GetExtAssetSafe(inst, childPtr).baseField; AssetTypeValueField monoScriptPtr = childBf["m_Script"]; //get MonoScript from MonoBehaviour AssetExternal monoScriptExt = GetExtAsset(childExt.file, monoScriptPtr); AssetTypeValueField monoScriptBf = monoScriptExt.baseField; string className = monoScriptBf["m_ClassName"].AsString; if (className == targetClassName) { monoBehaviourInf = childInf; break; } } return monoBehaviourInf; } public NitroxTransform GetTransformFromGameObject(AssetsFileInstance assetFileInst, AssetTypeValueField rootGameObject) { AssetTypeValueField componentArray = rootGameObject["m_Component"]["Array"]; AssetTypeValueField transformRef = componentArray[0]["component"]; AssetTypeValueField transformField = GetExtAsset(assetFileInst, transformRef).baseField; return new(transformField["m_LocalPosition"].ToNitroxVector3(), transformField["m_LocalRotation"].ToNitroxQuaternion(), transformField["m_LocalScale"].ToNitroxVector3()); } public new void SetMonoTempGenerator(IMonoBehaviourTemplateGenerator generator) { monoTempGenerator = (ThreadSafeMonoCecilTempGenerator)generator; base.SetMonoTempGenerator(generator); } /// /// Returns a ready to use with loaded , and /// . /// public AssetsBundleManager Clone() { AssetsBundleManager bundleManagerInst = new(aaRootPath) { classDatabase = classDatabase, classPackage = classPackage }; bundleManagerInst.SetMonoTempGenerator(monoTempGenerator); return bundleManagerInst; } /// public new void UnloadAll(bool unloadClassData = false) { if (unloadClassData) { monoTempGenerator.Dispose(); } dependenciesByAssetFileInst.Clear(); base.UnloadAll(unloadClassData); } private AssetExternal GetExtAssetSafe(AssetsFileInstance relativeTo, AssetTypeValueField valueField) { string[] bundlePaths = dependenciesByAssetFileInst[relativeTo]; for (int i = 0; i < bundlePaths.Length; i++) { if (i != 0) { BundleFileInstance dependenciesBundleFile = LoadBundleFile(CleanBundlePath(bundlePaths[i])); LoadAssetsFileFromBundle(dependenciesBundleFile, 0); } try { return GetExtAsset(relativeTo, valueField); } catch (Exception) { // ignored } } throw new InvalidOperationException("Could find AssetTypeValueField in given dependencies"); } }