Decompiled source of ModulWorkshop Optimizer v0.1.0

00.Sirdoggy.ModulWorkshopOptimizer.dll

Decompiled a day ago
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<bool> ForceSynchronousLoading;

		public static ConfigEntry<bool> 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<bool>("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<bool>("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<string> GroupIDs { get; set; } = new List<string>();

		public List<string> SkinPaths { get; set; } = new List<string>();
	}
	internal class CacheDto
	{
		public Dictionary<string, CachedBundleDto> Bundles { get; } = new Dictionary<string, CachedBundleDto>();
	}
	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<CacheDto>(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<string>? groupIds, List<string>? 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<string>()),
					SkinPaths = (skinPaths ?? new List<string>())
				};
				_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<ModularWorkshopPartsDefinition>();
				array2 = val.LoadAllAssets<ModularWorkshopSkinsDefinition>();
			}
			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<string> list = new List<string>();
			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<string> list2 = new List<string>();
			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<string> LoadingStartedBundlePaths = new HashSet<string>();

		private static readonly Dictionary<string, AssetBundle> PathToLoadedBundleDic = new Dictionary<string, AssetBundle>();

		public static bool IsBundleRegistered(string fullPath)
		{
			return PathToLoadedBundleDic.ContainsKey(fullPath);
		}

		public static void EnsureBundlesAreLoaded(List<string> 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<string> bundlePaths)
		{
			foreach (string bundlePath in bundlePaths)
			{
				yield return SodaliteUtils.TryCatch(LoadAndRegisterBundleAsync(bundlePath), (Action<Exception>)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<string, List<string>> GroupIDToBundlesDic = new Dictionary<string, List<string>>();

		public static readonly Dictionary<string, List<string>> SkinPathToBundlesDic = new Dictionary<string, List<string>>();

		public static void SaveModularBundleMetadata(string bundlePath, IEnumerable<string> groupIDs, IEnumerable<string> 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<string> GetBundlesByObject(ModularFVRPhysicalObject modularObject)
		{
			List<string> list = new List<string>();
			string text = modularObject.SkinPath;
			if (string.IsNullOrEmpty(text))
			{
				text = modularObject.MainObject.ObjectWrapper.ItemID + "/Receiver";
			}
			list.AddRange(GetBundlesBySkinPath(text));
			foreach (KeyValuePair<string, ModularWeaponPartsAttachmentPoint> allAttachmentPoint in modularObject.AllAttachmentPoints)
			{
				list.AddRange(GetBundlesByGroupID(allAttachmentPoint.Key));
			}
			return list;
		}

		public static List<string> GetBundlesByFireArm(ModularFVRFireArm modularFireArm, FVRFireArm fireArm)
		{
			List<string> list = new List<string>();
			string text = modularFireArm.SkinPath;
			if (string.IsNullOrEmpty(text))
			{
				text = ((FVRPhysicalObject)fireArm).ObjectWrapper.ItemID + "/Receiver";
			}
			list.AddRange(GetBundlesBySkinPath(text));
			foreach (KeyValuePair<string, ModularWeaponPartsAttachmentPoint> allAttachmentPoint in modularFireArm.AllAttachmentPoints)
			{
				list.AddRange(GetBundlesByGroupID(allAttachmentPoint.Key));
			}
			return list;
		}

		public static List<string> GetBundlesByGroupID(string? groupID)
		{
			if (string.IsNullOrEmpty(groupID))
			{
				return new List<string>();
			}
			return GroupIDToBundlesDic.GetValueOrNull(groupID) ?? new List<string>();
		}

		public static List<string> GetBundlesBySkinPath(string? skinPath)
		{
			if (string.IsNullOrEmpty(skinPath))
			{
				return new List<string>();
			}
			return SkinPathToBundlesDic.GetValueOrNull(skinPath) ?? new List<string>();
		}
	}
	internal static class ModularWorkshopRegisterer
	{
		private static readonly List<ModularWorkshopPartsDefinition> s_foundModularPartsDefinitions = AccessTools.StaticFieldRefAccess<ModularWorkshopManager, List<ModularWorkshopPartsDefinition>>("s_foundModularPartsDefinitions");

		private static readonly List<ModularWorkshopSkinsDefinition> s_foundModularSkinsDefinitions = AccessTools.StaticFieldRefAccess<ModularWorkshopManager, List<ModularWorkshopSkinsDefinition>>("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<GameObject>, AnvilCallback<GameObject>?> TempToTrueCallbacks = new Dictionary<AnvilCallback<GameObject>, AnvilCallback<GameObject>>();

		private static readonly HashSet<AssetID> AllowProcessNextLoadAsync = new HashSet<AssetID>();

		[HarmonyWrapSafe]
		[HarmonyPrefix]
		[HarmonyPatch(typeof(AnvilManager), "LoadAsync")]
		private static bool LoadAsync_Prefix(ref AnvilCallback<GameObject> __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<GameObject> val = new AnvilCallback<GameObject>((AsyncOperation)null, (AnvilCallback<AssetBundle>)null);
			TempToTrueCallbacks.Add(val, null);
			__result = val;
			CoroutineRunner.Run(LoadAndInspectAsset(val, assetID));
			return false;
		}

		private static IEnumerator LoadAndInspectAsset(AnvilCallback<GameObject> 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<GameObject> 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<ModularFVRPhysicalObject>(true);
				ModularFVRPhysicalObject[] array = modularObjects;
				for (int i = 0; i < array.Length; i++)
				{
					List<string> bundlesByObject = ModularBundlesMetadataRepository.GetBundlesByObject(array[i]);
					yield return ModularBundlesLoader.EnsureBundlesAreLoadedAndRegisteredAsync(bundlesByObject);
				}
				IModularWeapon[] modularWeapons = gameObject.GetComponentsInChildren<IModularWeapon>(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<FVRFireArm>() : null);
					if (getModularFVRFireArm != null && !((Object)(object)val == (Object)null))
					{
						List<string> 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<GameObject> action in tempCallback.m_completed ?? new List<Action<GameObject>>())
			{
				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<GameObject>), "get_Result")]
		private static bool Result_Prefix(AnvilCallback<GameObject> __instance, ref GameObject __result)
		{
			if (__instance == null)
			{
				return true;
			}
			AnvilCallback<GameObject> 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<GameObject>), "Pump")]
		private static bool Pump_Prefix(AnvilCallback<GameObject> __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<CoroutineRunner>();
			}
			((MonoBehaviour)_instance).StartCoroutine(coroutine);
		}
	}
	internal static class DictionaryExtensions
	{
		public static TValue GetOrCreateDefault<TKey, TValue>(this Dictionary<TKey, TValue> dictionary, TKey key) where TValue : new()
		{
			if (!dictionary.ContainsKey(key))
			{
				dictionary.Add(key, new TValue());
			}
			return dictionary[key];
		}

		public static bool AddIfUnique<TKey, TValue>(this Dictionary<TKey, TValue> dictionary, TKey key, TValue value)
		{
			if (!dictionary.ContainsKey(key))
			{
				dictionary.Add(key, value);
				return true;
			}
			return false;
		}

		public static TValue? GetValueOrNull<TKey, TValue>(this Dictionary<TKey, TValue> dictionary, TKey key) where TKey : notnull where TValue : class?
		{
			if (!dictionary.TryGetValue(key, out TValue value))
			{
				return null;
			}
			return value;
		}

		public static TValue? GetStructOrNull<TKey, TValue>(this Dictionary<TKey, TValue> 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<T>(this List<T> list, T value)
		{
			if (!list.Contains(value))
			{
				list.Add(value);
				return true;
			}
			return false;
		}

		public static bool ContainsNotNull<T>(this List<T> 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<Exception>? 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<T>(string nameOfType, [ParamCollection] IEnumerable<T> properties)
		{
			if (properties is string)
			{
				return $"{nameOfType}({properties})";
			}
			return nameOfType + "(" + JoinValues(properties) + ")";
		}

		public static string JoinValues<T>(IEnumerable<T> 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);
		}
	}
}