using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Security; using System.Security.Permissions; using Anvil; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using FistVR; using HarmonyLib; using Microsoft.CodeAnalysis; using ModularWorkshop; using Sirdoggy.ModulWorkshopOptimizer.Common; using Sirdoggy.ModulWorkshopOptimizer.Core; using Sirdoggy.ModulWorkshopOptimizer.Core.Patches; using Sirdoggy.ModulWorkshopOptimizer.LoadingInterception; using Sodalite.Api; using Sodalite.ModPanel; using Sodalite.Utilities; using UnityEngine; using Valve.Newtonsoft.Json; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: AssemblyCompany("Sirdoggy")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyDescription("Adds on-demand loading to ModularWorkshop! Greatly decreasing game launch times and initial RAM usage")] [assembly: AssemblyFileVersion("0.1.0.0")] [assembly: AssemblyInformationalVersion("0.1.0")] [assembly: AssemblyProduct("00.Sirdoggy.ModulWorkshopOptimizer")] [assembly: AssemblyTitle("ModulWorkshop_Optimizer")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("0.1.0.0")] [module: UnverifiableCode] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Embedded] internal sealed class ParamCollectionAttribute : Attribute { } [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)] internal sealed class NullableAttribute : Attribute { public readonly byte[] NullableFlags; public NullableAttribute(byte P_0) { NullableFlags = new byte[1] { P_0 }; } public NullableAttribute(byte[] P_0) { NullableFlags = P_0; } } [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)] internal sealed class NullableContextAttribute : Attribute { public readonly byte Flag; public NullableContextAttribute(byte P_0) { Flag = P_0; } } [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)] internal sealed class RefSafetyRulesAttribute : Attribute { public readonly int Version; public RefSafetyRulesAttribute(int P_0) { Version = P_0; } } } namespace BepInEx { [AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = false)] [Conditional("CodeGeneration")] internal sealed class BepInAutoPluginAttribute : Attribute { public BepInAutoPluginAttribute(string id = null, string name = null, string version = null) { } } } namespace BepInEx.Preloader.Core.Patching { [AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = false)] [Conditional("CodeGeneration")] internal sealed class PatcherAutoPluginAttribute : Attribute { public PatcherAutoPluginAttribute(string id = null, string name = null, string version = null) { } } } namespace Sirdoggy.ModulWorkshopOptimizer { [BepInProcess("h3vr.exe")] [BepInDependency(/*Could not decode attribute arguments.*/)] [BepInDependency(/*Could not decode attribute arguments.*/)] [BepInPlugin("00.Sirdoggy.ModulWorkshopOptimizer", "ModulWorkshop_Optimizer", "0.1.0")] public class Plugin : BaseUnityPlugin { internal static ManualLogSource Log; internal static ExceptionCatcher ExceptionCatcher; public const string Id = "00.Sirdoggy.ModulWorkshopOptimizer"; public static string Name => "ModulWorkshop_Optimizer"; public static string Version => "0.1.0"; private void Awake() { Log = ((BaseUnityPlugin)this).Logger; ExceptionCatcher = new ExceptionCatcher(Log); PluginConfig.Init(((BaseUnityPlugin)this).Info); ExceptionCatcher.Run(delegate { BundleCacheRepository.Init(); Harmony.CreateAndPatchAll(typeof(AnvilLoadingPatcher), (string)null); Harmony.CreateAndPatchAll(typeof(SodalitePreloadPatcher), (string)null); Harmony.CreateAndPatchAll(typeof(ModularObjectPatcher), (string)null); }); } } internal static class PluginConfig { public static ConfigEntry ForceSynchronousLoading; public static ConfigEntry OptimizeRamAfterCaching; private const string ConfigFileName = "ModulWorkshopOptimizer.cfg"; private static ConfigFile _configFile; public static void Init(PluginInfo pluginInfo) { //IL_0010: Unknown result type (might be due to invalid IL or missing references) //IL_001a: Expected O, but got Unknown _configFile = new ConfigFile(Path.Combine(Paths.ConfigPath, "ModulWorkshopOptimizer.cfg"), true); BindFields(); UniversalModPanel.RegisterPluginSettings(pluginInfo, _configFile); } private static void BindFields() { ForceSynchronousLoading = _configFile.Bind("General", "Disable async loading", false, "(!) Forces the mod to always use synchronous loading, which is less invasive, but will FREEZE THE GAME whenever modular parts are loading in! You should only enable this setting if you're having issues with spawning."); OptimizeRamAfterCaching = _configFile.Bind("General", "Optimize RAM after caching", true, "(!) Having this enabled can cause instability on Linux/Proton! When enabled, modular parts will be unloaded from memory after being cached. Caching is done during game startup and only needs to be done once for every installed mod."); } } } namespace Sirdoggy.ModulWorkshopOptimizer.LoadingInterception { internal class CachedBundleDto { public string BundlePath { get; set; } = ""; public long FileLastWriteTimestampUtc { get; set; } public long FileSize { get; set; } public bool IsModularBundle { get; set; } public List GroupIDs { get; set; } = new List(); public List SkinPaths { get; set; } = new List(); } internal class CacheDto { public Dictionary Bundles { get; } = new Dictionary(); } internal static class BundleCacheRepository { private static readonly string CacheFilePath = Path.Combine(Paths.CachePath, "Sirdoggy.ModulWorkshopOptimizer.cache"); private const string T = "[Cache]"; private static CacheDto _cache = new CacheDto(); public static void Init() { try { if (File.Exists(CacheFilePath)) { CacheDto cacheDto = JsonConvert.DeserializeObject(File.ReadAllText(CacheFilePath)); if (cacheDto != null) { _cache = cacheDto; Plugin.Log.LogInfo((object)string.Format("{0} Loaded cache with {1} bundle entries", "[Cache]", _cache.Bundles.Count)); } } } catch (Exception ex) { Plugin.Log.LogWarning((object)("[Cache] Failed to load cache: " + ex.Message)); } } public static void CacheBundle(string fullPath, bool isModular, List? groupIds, List? skinPaths) { try { FileInfo fileInfo = new FileInfo(fullPath); CachedBundleDto value = new CachedBundleDto { BundlePath = fullPath, FileLastWriteTimestampUtc = (fileInfo.Exists ? fileInfo.LastWriteTimeUtc.Ticks : 0), FileSize = (fileInfo.Exists ? fileInfo.Length : 0), IsModularBundle = isModular, GroupIDs = (groupIds ?? new List()), SkinPaths = (skinPaths ?? new List()) }; _cache.Bundles[fullPath] = value; SaveCacheFileToDisk(); } catch (Exception ex) { Plugin.Log.LogWarning((object)("[Cache] Failed to cache bundle '" + fullPath + "': " + ex.Message)); } } public static CachedBundleDto? TryGetCachedBundleData(string fullPath) { CachedBundleDto valueOrNull = _cache.Bundles.GetValueOrNull(fullPath); if (valueOrNull == null) { return null; } try { FileInfo fileInfo = new FileInfo(fullPath); if (!fileInfo.Exists) { return null; } if (fileInfo.LastWriteTimeUtc.Ticks == valueOrNull.FileLastWriteTimestampUtc && fileInfo.Length == valueOrNull.FileSize) { return valueOrNull; } } catch { return null; } return null; } private static void SaveCacheFileToDisk() { try { string contents = JsonConvert.SerializeObject((object)_cache, (Formatting)1); File.WriteAllText(CacheFilePath, contents); } catch (Exception ex) { Plugin.Log.LogWarning((object)("[Cache] Failed to save cache file to disk: " + ex.Message)); } } } [HarmonyPatch(typeof(GameAPI))] internal static class SodalitePreloadPatcher { [HarmonyPrefix] [HarmonyPatch("PreloadAllAssets")] private static bool PreloadAllAssets_Prefix(string assetBundle) { if (string.IsNullOrEmpty(assetBundle)) { return true; } string fullPath; string fileName; try { fullPath = Path.GetFullPath(assetBundle); if (!File.Exists(fullPath)) { return true; } fileName = Path.GetFileName(assetBundle); } catch { return true; } CachedBundleDto cachedBundleDto = BundleCacheRepository.TryGetCachedBundleData(fullPath); if (cachedBundleDto != null) { if (!cachedBundleDto.IsModularBundle) { return true; } ModularBundlesMetadataRepository.SaveModularBundleMetadata(fullPath, cachedBundleDto.GroupIDs, cachedBundleDto.SkinPaths); Plugin.Log.LogInfo((object)("Registered cached metadata for modular bundle '" + fileName + "': " + $"{cachedBundleDto.GroupIDs.Count} part groups, {cachedBundleDto.SkinPaths.Count} skins")); return false; } AssetBundle val; ModularWorkshopPartsDefinition[] array; ModularWorkshopSkinsDefinition[] array2; try { val = AssetBundle.LoadFromFile(fullPath); array = val.LoadAllAssets(); array2 = val.LoadAllAssets(); } catch (Exception ex) { Plugin.Log.LogWarning((object)("Failed to inspect bundle for caching '" + fullPath + "': " + ex.Message)); return true; } if (array.Length == 0 && array2.Length == 0) { BundleCacheRepository.CacheBundle(fullPath, isModular: false, null, null); return true; } List list = new List(); ModularWorkshopPartsDefinition[] array3 = array; foreach (ModularWorkshopPartsDefinition val2 in array3) { if ((Object)(object)val2 != (Object)null && !string.IsNullOrEmpty(val2.ModularPartsGroupID) && !list.Contains(val2.ModularPartsGroupID)) { list.Add(val2.ModularPartsGroupID); } } List list2 = new List(); ModularWorkshopSkinsDefinition[] array4 = array2; foreach (ModularWorkshopSkinsDefinition val3 in array4) { if ((Object)(object)val3 != (Object)null && !val3.AutomaticallyCreated) { string item = val3.ModularPartsGroupID + "/" + val3.PartName; if (!list2.Contains(item)) { list2.Add(item); } } } BundleCacheRepository.CacheBundle(fullPath, isModular: true, list, list2); ModularBundlesMetadataRepository.SaveModularBundleMetadata(fullPath, list, list2); Plugin.Log.LogInfo((object)("Registered and cached metadata for new modular bundle '" + fileName + "': " + $"{list.Count} part groups, {list2.Count} skins")); if (PluginConfig.OptimizeRamAfterCaching.Value) { Plugin.Log.LogInfo((object)("Unloading bundle '" + fileName + "' from RAM...")); val.Unload(true); } else { Plugin.Log.LogInfo((object)("RAM optimization after caching is disabled! Bundle '" + fileName + "' will remain loaded in RAM.")); } return false; } } } namespace Sirdoggy.ModulWorkshopOptimizer.Core { internal static class ModularBundlesLoader { private const string T = "[BundleLoader]"; private static readonly HashSet LoadingStartedBundlePaths = new HashSet(); private static readonly Dictionary PathToLoadedBundleDic = new Dictionary(); public static bool IsBundleRegistered(string fullPath) { return PathToLoadedBundleDic.ContainsKey(fullPath); } public static void EnsureBundlesAreLoaded(List bundlePaths) { Plugin.ExceptionCatcher.Run(delegate { foreach (string bundlePath in bundlePaths) { LoadAndRegisterBundle(bundlePath); } }, delegate(Exception e) { Plugin.Log.LogError((object)$"Exception when trying to load and register modular bundles: {e}"); }); } public static IEnumerator EnsureBundlesAreLoadedAndRegisteredAsync(List bundlePaths) { foreach (string bundlePath in bundlePaths) { yield return SodaliteUtils.TryCatch(LoadAndRegisterBundleAsync(bundlePath), (Action)delegate(Exception e) { Plugin.Log.LogError((object)$"Exception when trying to async load and register modular bundles: {e}"); }); } } private static void LoadAndRegisterBundle(string bundlePath) { if (IsBundleRegistered(bundlePath) || LoadingStartedBundlePaths.Contains(bundlePath)) { return; } Plugin.Log.LogMessage((object)("[BundleLoader] On-demand loading modular bundle: " + bundlePath)); if (!File.Exists(bundlePath)) { Plugin.Log.LogWarning((object)("[BundleLoader] Bundle file does not exist: " + bundlePath)); return; } AssetBundle val = AssetBundle.LoadFromFile(bundlePath); if ((Object)(object)val == (Object)null) { Plugin.Log.LogError((object)("[BundleLoader] Failed to load bundle from file: " + bundlePath)); return; } ModularWorkshopRegisterer.RegisterDefinitionsFromBundle(val.LoadAllAssets()); PathToLoadedBundleDic[bundlePath] = val; } private static IEnumerator LoadAndRegisterBundleAsync(string bundlePath) { if (IsBundleRegistered(bundlePath)) { yield break; } if (!LoadingStartedBundlePaths.Add(bundlePath)) { while (!IsBundleRegistered(bundlePath)) { yield return null; } yield break; } Plugin.Log.LogMessage((object)("[BundleLoader] On-demand async loading modular bundle: " + bundlePath)); if (!File.Exists(bundlePath)) { Plugin.Log.LogWarning((object)("[BundleLoader] Bundle file does not exist: " + bundlePath)); yield break; } AssetBundleCreateRequest bundleRequest = AssetBundle.LoadFromFileAsync(bundlePath); yield return bundleRequest; AssetBundle bundle = ((bundleRequest != null) ? bundleRequest.assetBundle : null); if ((Object)(object)bundle == (Object)null) { Plugin.Log.LogError((object)("[BundleLoader] Failed to async load bundle from file: " + bundlePath)); yield break; } AssetBundleRequest assets = bundle.LoadAllAssetsAsync(); yield return assets; ModularWorkshopRegisterer.RegisterDefinitionsFromBundle((assets != null) ? assets.allAssets : null); PathToLoadedBundleDic[bundlePath] = bundle; } } internal static class ModularBundlesMetadataRepository { public static readonly Dictionary> GroupIDToBundlesDic = new Dictionary>(); public static readonly Dictionary> SkinPathToBundlesDic = new Dictionary>(); public static void SaveModularBundleMetadata(string bundlePath, IEnumerable groupIDs, IEnumerable skinPaths) { string fullPath = Path.GetFullPath(bundlePath); foreach (string groupID in groupIDs) { if (!string.IsNullOrEmpty(groupID)) { GroupIDToBundlesDic.GetOrCreateDefault(groupID).AddIfUnique(fullPath); } } foreach (string skinPath in skinPaths) { if (!string.IsNullOrEmpty(skinPath)) { SkinPathToBundlesDic.GetOrCreateDefault(skinPath).AddIfUnique(fullPath); } } } public static List GetBundlesByObject(ModularFVRPhysicalObject modularObject) { List list = new List(); string text = modularObject.SkinPath; if (string.IsNullOrEmpty(text)) { text = modularObject.MainObject.ObjectWrapper.ItemID + "/Receiver"; } list.AddRange(GetBundlesBySkinPath(text)); foreach (KeyValuePair allAttachmentPoint in modularObject.AllAttachmentPoints) { list.AddRange(GetBundlesByGroupID(allAttachmentPoint.Key)); } return list; } public static List GetBundlesByFireArm(ModularFVRFireArm modularFireArm, FVRFireArm fireArm) { List list = new List(); string text = modularFireArm.SkinPath; if (string.IsNullOrEmpty(text)) { text = ((FVRPhysicalObject)fireArm).ObjectWrapper.ItemID + "/Receiver"; } list.AddRange(GetBundlesBySkinPath(text)); foreach (KeyValuePair allAttachmentPoint in modularFireArm.AllAttachmentPoints) { list.AddRange(GetBundlesByGroupID(allAttachmentPoint.Key)); } return list; } public static List GetBundlesByGroupID(string? groupID) { if (string.IsNullOrEmpty(groupID)) { return new List(); } return GroupIDToBundlesDic.GetValueOrNull(groupID) ?? new List(); } public static List GetBundlesBySkinPath(string? skinPath) { if (string.IsNullOrEmpty(skinPath)) { return new List(); } return SkinPathToBundlesDic.GetValueOrNull(skinPath) ?? new List(); } } internal static class ModularWorkshopRegisterer { private static readonly List s_foundModularPartsDefinitions = AccessTools.StaticFieldRefAccess>("s_foundModularPartsDefinitions"); private static readonly List s_foundModularSkinsDefinitions = AccessTools.StaticFieldRefAccess>("s_foundModularSkinsDefinitions"); public static void RegisterDefinitionsFromBundle(Object[]? assets) { if (assets == null || assets.Length == 0) { return; } foreach (Object val in assets) { ModularWorkshopPartsDefinition val2 = (ModularWorkshopPartsDefinition)(object)((val is ModularWorkshopPartsDefinition) ? val : null); if (val2 == null) { ModularWorkshopSkinsDefinition val3 = (ModularWorkshopSkinsDefinition)(object)((val is ModularWorkshopSkinsDefinition) ? val : null); if (val3 != null) { TryRegisterSkin(val3); } } else { TryRegisterPart(val2); } } } private static void TryRegisterPart(ModularWorkshopPartsDefinition partDefinition) { if (s_foundModularPartsDefinitions.Contains(partDefinition)) { return; } s_foundModularPartsDefinitions.Add(partDefinition); if (!ModularWorkshopManager.ModularWorkshopPartsGroupsDictionary.TryGetValue(partDefinition.ModularPartsGroupID, out var value)) { if (partDefinition.DisplayName == string.Empty) { partDefinition.DisplayName = partDefinition.ModularPartsGroupID; } ModularWorkshopManager.ModularWorkshopPartsGroupsDictionary.Add(partDefinition.ModularPartsGroupID, partDefinition); Plugin.Log.LogInfo((object)("(On-demand) Loaded ModularWorkshopPartsDefinition \"" + ((Object)partDefinition).name + "\" with ModularPartsGroupID \"" + partDefinition.ModularPartsGroupID + "\"")); } else { value.ModularPrefabs.AddRange(partDefinition.ModularPrefabs); Plugin.Log.LogInfo((object)("(On-demand) Added more parts from ModularWorkshopPartsDefinition \"" + ((Object)partDefinition).name + "\" to ModularPartsGroupID \"" + partDefinition.ModularPartsGroupID + "\".")); } } private static void TryRegisterSkin(ModularWorkshopSkinsDefinition skinDefinition) { if (s_foundModularSkinsDefinitions.Contains(skinDefinition)) { return; } s_foundModularSkinsDefinitions.Add(skinDefinition); string text = skinDefinition.ModularPartsGroupID + "/" + skinDefinition.PartName; if (ModularWorkshopManager.ModularWorkshopSkinsDictionary.TryGetValue(text, out var value)) { value.SkinDefinitions.AddRange(skinDefinition.SkinDefinitions); Plugin.Log.LogInfo((object)("(On-demand) Added more skins from ModularWorkshopSkinsDefinition \"" + ((Object)skinDefinition).name + "\" to ModularSkin \"" + text + "\".")); return; } foreach (SkinDefinition skinDefinition2 in skinDefinition.SkinDefinitions) { if (skinDefinition2.DisplayName == string.Empty) { skinDefinition2.DisplayName = skinDefinition2.ModularSkinID; } } ModularWorkshopManager.ModularWorkshopSkinsDictionary.Add(text, skinDefinition); Plugin.Log.LogInfo((object)("(On-demand) Loaded ModularWorkshopSkinsDefinition \"" + ((Object)skinDefinition).name + "\" with ModularSkin \"" + text + "\".")); } } } namespace Sirdoggy.ModulWorkshopOptimizer.Core.Patches { internal static class AnvilLoadingPatcher { private const string T = "[AnvilPatch]"; private static readonly Dictionary, AnvilCallback?> TempToTrueCallbacks = new Dictionary, AnvilCallback>(); private static readonly HashSet AllowProcessNextLoadAsync = new HashSet(); [HarmonyWrapSafe] [HarmonyPrefix] [HarmonyPatch(typeof(AnvilManager), "LoadAsync")] private static bool LoadAsync_Prefix(ref AnvilCallback __result, AssetID assetID) { //IL_0013: Unknown result type (might be due to invalid IL or missing references) //IL_0046: Unknown result type (might be due to invalid IL or missing references) //IL_0073: Unknown result type (might be due to invalid IL or missing references) //IL_0025: Unknown result type (might be due to invalid IL or missing references) if (PluginConfig.ForceSynchronousLoading.Value) { return true; } if (AllowProcessNextLoadAsync.Contains(assetID)) { Plugin.Log.LogDebug((object)("[AnvilPatch] (" + assetID.AssetName + ") 3. Loading asset via Anvil")); return true; } Plugin.Log.LogDebug((object)("[AnvilPatch] (" + assetID.AssetName + ") 1. Asset load intercepted")); AnvilCallback val = new AnvilCallback((AsyncOperation)null, (AnvilCallback)null); TempToTrueCallbacks.Add(val, null); __result = val; CoroutineRunner.Run(LoadAndInspectAsset(val, assetID)); return false; } private static IEnumerator LoadAndInspectAsset(AnvilCallback tempCallback, AssetID assetID) { //IL_000e: Unknown result type (might be due to invalid IL or missing references) //IL_000f: Unknown result type (might be due to invalid IL or missing references) Plugin.Log.LogDebug((object)("[AnvilPatch] (" + assetID.AssetName + ") 2. Started custom coroutine")); AllowProcessNextLoadAsync.Add(assetID); AnvilCallback trueCallback = AnvilManager.LoadAsync(assetID); TempToTrueCallbacks[tempCallback] = trueCallback; yield return trueCallback; AllowProcessNextLoadAsync.Remove(assetID); Plugin.Log.LogDebug((object)("[AnvilPatch] (" + assetID.AssetName + ") 4. Inspecting")); GameObject gameObject = trueCallback?.m_result; if ((Object)(object)gameObject != (Object)null) { Plugin.Log.LogDebug((object)("[AnvilPatch] (" + assetID.AssetName + ") 4.1 GameObject found: " + ((Object)gameObject).name)); ModularFVRPhysicalObject[] modularObjects = gameObject.GetComponentsInParent(true); ModularFVRPhysicalObject[] array = modularObjects; for (int i = 0; i < array.Length; i++) { List bundlesByObject = ModularBundlesMetadataRepository.GetBundlesByObject(array[i]); yield return ModularBundlesLoader.EnsureBundlesAreLoadedAndRegisteredAsync(bundlesByObject); } IModularWeapon[] modularWeapons = gameObject.GetComponentsInChildren(true); IModularWeapon[] array2 = modularWeapons; foreach (IModularWeapon obj in array2) { ModularFVRFireArm getModularFVRFireArm = obj.GetModularFVRFireArm; IModularWeapon obj2 = ((obj is Component) ? obj : null); FVRFireArm val = ((obj2 != null) ? ((Component)obj2).GetComponent() : null); if (getModularFVRFireArm != null && !((Object)(object)val == (Object)null)) { List bundlesByFireArm = ModularBundlesMetadataRepository.GetBundlesByFireArm(getModularFVRFireArm, val); yield return ModularBundlesLoader.EnsureBundlesAreLoadedAndRegisteredAsync(bundlesByFireArm); } } Plugin.Log.LogDebug((object)(string.Format("{0} ({1}) 4.2 modularObjects: {2}, ", "[AnvilPatch]", assetID.AssetName, modularObjects.Length) + $"modularWeapons: {modularWeapons.Length}")); } Plugin.Log.LogDebug((object)("[AnvilPatch] (" + assetID.AssetName + ") 5. Inspection finished, completing AnvilCallback")); tempCallback.m_result = TempToTrueCallbacks[tempCallback]?.m_result; ((AnvilCallbackBase)tempCallback).IsCompleted = true; foreach (Action action in tempCallback.m_completed ?? new List>()) { Plugin.ExceptionCatcher.Run(delegate { action(tempCallback.m_result); }); } tempCallback.m_completed?.Clear(); TempToTrueCallbacks.Remove(tempCallback); Plugin.Log.LogDebug((object)("[AnvilPatch] (" + assetID.AssetName + ") 6. AnvilCallback completed!")); } [HarmonyWrapSafe] [HarmonyPrefix] [HarmonyPatch(typeof(AnvilCallback), "get_Result")] private static bool Result_Prefix(AnvilCallback __instance, ref GameObject __result) { if (__instance == null) { return true; } AnvilCallback valueOrNull = TempToTrueCallbacks.GetValueOrNull(__instance); if (valueOrNull == null) { return true; } Plugin.Log.LogDebug((object)"[AnvilPatch] !!! Result requested early"); ((AnvilCallbackBase)valueOrNull).CompleteNow(); __result = valueOrNull.m_result; return false; } [HarmonyWrapSafe] [HarmonyPrefix] [HarmonyPatch(typeof(AnvilCallback), "Pump")] private static bool Pump_Prefix(AnvilCallback __instance) { if (__instance == null || !TempToTrueCallbacks.ContainsKey(__instance)) { return true; } return false; } } internal static class ModularObjectPatcher { private const string T = "[ModularObjPatch]"; [HarmonyWrapSafe] [HarmonyPrefix] [HarmonyPatch(typeof(ModularFVRFireArm), "Awake")] private static void ModularFVRFireArm_Awake_Prefix(ModularFVRFireArm __instance, FVRFireArm fireArm) { Plugin.Log.LogDebug((object)string.Format("{0} ModulFireArm awake '{1}', '{2}'", "[ModularObjPatch]", __instance, fireArm)); ModularBundlesLoader.EnsureBundlesAreLoaded(ModularBundlesMetadataRepository.GetBundlesByFireArm(__instance, fireArm)); } [HarmonyWrapSafe] [HarmonyPrefix] [HarmonyPatch(typeof(ModularFVRFireArm), "ConfigureModularWeaponPart")] private static void ModularFVRFireArm_ConfigureModularWeaponPart_Prefix(ModularWeaponPartsAttachmentPoint modularWeaponPartsAttachmentPoint) { Plugin.Log.LogDebug((object)string.Format("{0} ModulFireArm configure part '{1}'", "[ModularObjPatch]", modularWeaponPartsAttachmentPoint)); ModularBundlesLoader.EnsureBundlesAreLoaded(ModularBundlesMetadataRepository.GetBundlesByGroupID(modularWeaponPartsAttachmentPoint?.ModularPartsGroupID)); } [HarmonyWrapSafe] [HarmonyPrefix] [HarmonyPatch(typeof(ModularFVRPhysicalObject), "Awake")] private static void ModularFVRPhysicalObject_Awake_Prefix(ModularFVRPhysicalObject __instance) { Plugin.Log.LogDebug((object)string.Format("{0} ModulObj awake '{1}'", "[ModularObjPatch]", __instance)); ModularBundlesLoader.EnsureBundlesAreLoaded(ModularBundlesMetadataRepository.GetBundlesByObject(__instance)); } [HarmonyWrapSafe] [HarmonyPrefix] [HarmonyPatch(typeof(ModularFVRPhysicalObject), "ConfigureModularWeaponPart")] private static void ModularFVRPhysicalObject_ConfigureModularWeaponPart_Prefix(ModularWeaponPartsAttachmentPoint modularWeaponPartsAttachmentPoint) { Plugin.Log.LogDebug((object)string.Format("{0} ModulObj configure part '{1}'", "[ModularObjPatch]", modularWeaponPartsAttachmentPoint)); ModularBundlesLoader.EnsureBundlesAreLoaded(ModularBundlesMetadataRepository.GetBundlesByGroupID(modularWeaponPartsAttachmentPoint?.ModularPartsGroupID)); } } } namespace Sirdoggy.ModulWorkshopOptimizer.Common { internal class CoroutineRunner : MonoBehaviour { private static CoroutineRunner _instance; public static void Run(IEnumerator coroutine) { //IL_0011: Unknown result type (might be due to invalid IL or missing references) //IL_0016: Unknown result type (might be due to invalid IL or missing references) //IL_001e: Unknown result type (might be due to invalid IL or missing references) //IL_0024: Expected O, but got Unknown if (!Object.op_Implicit((Object)(object)_instance)) { GameObject val = new GameObject("CoroutineRunner") { hideFlags = (HideFlags)61 }; Object.DontDestroyOnLoad((Object)val); _instance = val.AddComponent(); } ((MonoBehaviour)_instance).StartCoroutine(coroutine); } } internal static class DictionaryExtensions { public static TValue GetOrCreateDefault(this Dictionary dictionary, TKey key) where TValue : new() { if (!dictionary.ContainsKey(key)) { dictionary.Add(key, new TValue()); } return dictionary[key]; } public static bool AddIfUnique(this Dictionary dictionary, TKey key, TValue value) { if (!dictionary.ContainsKey(key)) { dictionary.Add(key, value); return true; } return false; } public static TValue? GetValueOrNull(this Dictionary dictionary, TKey key) where TKey : notnull where TValue : class? { if (!dictionary.TryGetValue(key, out TValue value)) { return null; } return value; } public static TValue? GetStructOrNull(this Dictionary dictionary, TKey key) where TKey : notnull where TValue : struct { if (!dictionary.TryGetValue(key, out var value)) { return null; } return value; } } internal static class ListExtensions { public static bool AddIfUnique(this List list, T value) { if (!list.Contains(value)) { list.Add(value); return true; } return false; } public static bool ContainsNotNull(this List list, T? value) where T : struct { if (value.HasValue) { return list.Contains(value.Value); } return false; } } internal class ExceptionCatcher { private readonly ManualLogSource _log; public ExceptionCatcher(ManualLogSource log) { _log = log; } public void Run(Action action, Action? customHandler = null) { try { action(); } catch (Exception ex) { if (customHandler == null) { _log.LogError((object)$"Caught unhandled exception: {ex}"); } else { customHandler(ex); } } } } internal static class StringUtils { public static string FormatTypeProperties(string nameOfType, [ParamCollection] IEnumerable properties) { if (properties is string) { return $"{nameOfType}({properties})"; } return nameOfType + "(" + JoinValues(properties) + ")"; } public static string JoinValues(IEnumerable values, string seperator = ", ") { string[] value = values.Select((T element) => element?.ToString()).ToArray(); return string.Join(seperator, value); } public static string RemoveFirstOccurenceOf(string input, string toRemove) { int num = input.IndexOf(toRemove, StringComparison.Ordinal); if (num < 0) { return input; } return input.Remove(num, toRemove.Length); } } }