using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; using System.Reflection; using System.Reflection.Emit; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Text; using BepInEx; using BepInEx.Bootstrap; using BepInEx.Configuration; using BepInEx.Logging; using Damntry.Utils.ExtensionMethods; using Damntry.Utils.Logging; using Damntry.Utils.Maths; using Damntry.Utils.Reflection; using Damntry.UtilsBepInEx.Configuration.ConfigurationManager; using Damntry.UtilsBepInEx.Configuration.ConfigurationManager.Patch; using Damntry.UtilsBepInEx.Configuration.ConfigurationManager.SettingAttributes; using Damntry.UtilsBepInEx.HarmonyPatching.Attributes; using Damntry.UtilsBepInEx.HarmonyPatching.AutoPatching.Attributes; using Damntry.UtilsBepInEx.HarmonyPatching.AutoPatching.BaseClasses; using Damntry.UtilsBepInEx.HarmonyPatching.AutoPatching.Interfaces; using Damntry.UtilsBepInEx.HarmonyPatching.Exceptions; using Damntry.UtilsBepInEx.IL; using Damntry.UtilsBepInEx.MirrorNetwork.Attributes; using Damntry.UtilsBepInEx.MirrorNetwork.Components; using Damntry.UtilsBepInEx.MirrorNetwork.Helpers; using Damntry.UtilsBepInEx.MirrorNetwork.SyncVar; using HarmonyLib; using Microsoft.CodeAnalysis; using Mirror; using Mono.Cecil; using Mono.Cecil.Cil; using Mono.Collections.Generic; using MonoMod.Utils; using Newtonsoft.Json; using UnityEngine; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: AssemblyTitle("Damntry BepInEx Globals")] [assembly: AssemblyDescription("Utils usable in any type of BepInEx project")] [assembly: AssemblyCompany("Damntry")] [assembly: AssemblyProduct("Damntry BepInEx Globals")] [assembly: AssemblyCopyright("Copyright © Damntry 2025")] [assembly: AssemblyFileVersion("0.5.9.3")] [assembly: TargetFramework(".NETFramework,Version=v4.8.1", FrameworkDisplayName = ".NET Framework 4.8.1")] [assembly: AssemblyVersion("0.5.9.3")] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Microsoft.CodeAnalysis.Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Microsoft.CodeAnalysis.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 Damntry.UtilsBepInEx.ModHelpers { public abstract class ExternalModHelper { protected enum ModLoadStatus { NotLoaded, DifferentVersion, LoadedOk } protected ModLoadStatus ModStatus { get; private set; } public bool IsModLoadedAndEnabled => ModStatus != ModLoadStatus.NotLoaded; public ModInfoData ModInfo { get; private set; } private ExternalModHelper() { } public ExternalModHelper(string GUID, string modName, Version supportedVersion) { checkArgsForErrors(GUID, modName, supportedVersion); Init(GUID, modName, supportedVersion); } public ExternalModHelper(string GUID, string modName, string supportedVersion) { checkArgsForErrors(GUID, modName, supportedVersion, out var parsedSupportedVersion); Init(GUID, modName, parsedSupportedVersion); } private void checkArgsForErrors(string GUID, string modName, string supportedVersion, out Version parsedSupportedVersion) { if (supportedVersion == null) { throw new ArgumentNullException("supportedVersion"); } if (!Version.TryParse(supportedVersion, out parsedSupportedVersion)) { throw new ArgumentException("The arg supportedVersion doesnt have a valid Version format."); } checkArgsForErrors(GUID, modName, parsedSupportedVersion); } private void checkArgsForErrors(string GUID, string modName, Version supportedVersion) { if (GUID == null) { throw new ArgumentNullException("GUID"); } if (modName == null) { throw new ArgumentNullException("modName"); } if (supportedVersion == null) { throw new ArgumentNullException("supportedVersion"); } } private void Init(string GUID, string modName, Version supportedVersion) { ModInfo = new ModInfoData(GUID, modName, supportedVersion); ModStatus = IsModLoaded(); } protected virtual ModLoadStatus IsModLoaded() { if (Chainloader.PluginInfos.TryGetValue(ModInfo.GUID, out var value)) { ModInfo.LoadedVersion = value.Metadata.Version; if (ModInfo.LoadedVersion != ModInfo.SupportedVersion) { return ModLoadStatus.DifferentVersion; } return ModLoadStatus.LoadedOk; } return ModLoadStatus.NotLoaded; } } public class ModInfoData { public string GUID { get; internal set; } public string Name { get; internal set; } public Version SupportedVersion { get; internal set; } public Version LoadedVersion { get; internal set; } internal ModInfoData(string GUID, string modName, Version SupportedVersion) { this.GUID = GUID; Name = modName; this.SupportedVersion = SupportedVersion; } public ModInfoData(ModInfoData modInfo) { GUID = modInfo.GUID; Name = modInfo.Name; SupportedVersion = modInfo.SupportedVersion; LoadedVersion = modInfo.LoadedVersion; } } } namespace Damntry.UtilsBepInEx.MirrorNetwork.SyncVar { public interface ISyncVar { void SetToDefaultValue(); bool Writable(); void InitializeSyncObject(NetworkBehaviour netBehaviour); } [Serializable] public class SyncVar : SyncObject, ISyncVar, IEquatable { [SerializeField] protected T _Value; private NetworkBehaviour networkBehaviourContainer; public Action OnValueChangedCallback; private bool hookGuard; public virtual T Value { [MethodImpl(MethodImplOptions.AggressiveInlining)] get { return _Value; } set { SetValue(value, checkWritable: true); } } public T DefaultValue { get; private set; } protected void SetValue(T value, bool checkWritable) { if ((!checkWritable || Writable()) && !Equals(value)) { T value2 = _Value; _Value = value; if (base.OnDirty != null) { base.OnDirty(); } if (!hookGuard && (Object)(object)networkBehaviourContainer != (Object)null && !networkBehaviourContainer.authority) { hookGuard = true; InvokeCallback(value2, value); hookGuard = false; } } } [MethodImpl(MethodImplOptions.AggressiveInlining)] protected virtual void InvokeCallback(T oldValue, T newValue) { OnValueChangedCallback?.Invoke(oldValue, newValue); } public override void ClearChanges() { } public override void Reset() { } public virtual void SetToDefaultValue() { NetworkSpawnManager.DebugLog(() => $"Setting SyncVar from {_Value} to its DefaultValue {DefaultValue}"); _Value = DefaultValue; } public SyncVar(T defaultValue) { InitValues(defaultValue, defaultValue, null); } public SyncVar(T value, T defaultValue) { InitValues(value, defaultValue, null); } public SyncVar(T value, T defaultValue, Action onValueChangedCallback) { OnValueChangedCallback = (Action)Delegate.Combine(OnValueChangedCallback, onValueChangedCallback); InitValues(value, defaultValue, null); } public SyncVar(NetworkBehaviour netBehaviour, T value, T defaultValue) { if ((Object)(object)netBehaviour == (Object)null) { throw new ArgumentNullException("netBehaviour"); } InitValues(value, defaultValue, netBehaviour); } public SyncVar(NetworkBehaviour netBehaviour, T value, T defaultValue, Action onValueChangedCallback) { if ((Object)(object)netBehaviour == (Object)null) { throw new ArgumentNullException("netBehaviour"); } OnValueChangedCallback = (Action)Delegate.Combine(OnValueChangedCallback, onValueChangedCallback); InitValues(value, defaultValue, netBehaviour); } private void InitValues(T value, T defaultValue, NetworkBehaviour netBehaviour) { _Value = value; DefaultValue = defaultValue; if (typeof(T).IsEnum) { RegisterCustomEnum(value); } if ((Object)(object)netBehaviour != (Object)null) { InitSyncObjectReflection(netBehaviour); } } private void RegisterCustomEnum(TEnum value) { if (Writer.write == null) { Writer.write = delegate(NetworkWriter writer, TEnum value) { NetworkWriterExtensions.WriteLong(writer, EnumExtension.EnumToLong(value)); }; } if (Reader.read == null) { Reader.read = (NetworkReader reader) => EnumExtension.LongToEnumUnconstrained(NetworkReaderExtensions.ReadLong(reader)); } } public bool Writable() { //IL_0073: Unknown result type (might be due to invalid IL or missing references) //IL_0079: Invalid comparison between Unknown and I4 //IL_004c: Unknown result type (might be due to invalid IL or missing references) //IL_009b: Unknown result type (might be due to invalid IL or missing references) //IL_00a1: Invalid comparison between Unknown and I4 if ((Object)(object)networkBehaviourContainer == (Object)null) { bool isNetworkOffline = !NetworkSpawnManager.IsNetworkOnline(); NetworkSpawnManager.DebugLog(() => $"Writable returned {isNetworkOffline} for a non networked SyncVar!"); return isNetworkOffline; } if (NetworkServer.active && NetworkClient.active) { if ((int)networkBehaviourContainer.syncDirection != 0) { return networkBehaviourContainer.netIdentity.isOwned; } return true; } if (NetworkServer.active) { return (int)networkBehaviourContainer.syncDirection == 0; } if (NetworkClient.active) { if (networkBehaviourContainer.netIdentity.netId != 0) { if ((int)networkBehaviourContainer.syncDirection == 1) { return networkBehaviourContainer.netIdentity.isOwned; } return false; } return true; } return true; } public virtual void InitializeSyncObject(NetworkBehaviour netBehaviour) { InitSyncObjectReflection(netBehaviour); } protected void InitSyncObjectReflection(NetworkBehaviour netBehaviour) { networkBehaviourContainer = netBehaviour; NetworkSpawnManager.DebugLog(() => "Initializing SyncVar of Value type " + typeof(T).Name + " in " + ((Object)netBehaviour).name); ReflectionHelper.CallMethod((object)netBehaviour, "InitSyncObject", new object[1] { this }); NetworkSpawnManager.DebugLog(() => "Finished initializing SyncVar of Value type " + typeof(T).Name + " in " + ((Object)netBehaviour).name); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static implicit operator T(SyncVar field) { return field.Value; } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static implicit operator SyncVar(T value) { return new SyncVar(value); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public override void OnSerializeAll(NetworkWriter writer) { writer.Write(Value); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public override void OnSerializeDelta(NetworkWriter writer) { writer.Write(Value); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public override void OnDeserializeAll(NetworkReader reader) { SetValue(reader.Read(), checkWritable: false); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public override void OnDeserializeDelta(NetworkReader reader) { SetValue(reader.Read(), checkWritable: false); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool Equals(T other) { return EqualityComparer.Default.Equals(Value, other); } public override string ToString() { return Value.ToString(); } } public class SyncVarSetting : SyncVar, ISyncVar { public ConfigEntry ConfigEntry { get; private set; } public SyncVarSetting(T defaultValue, ConfigEntry configEntry) : base(defaultValue, defaultValue) { Init(configEntry); } public SyncVarSetting(T defaultValue, Action onValueChangedCallback, ConfigEntry configEntry) : base(defaultValue, defaultValue, onValueChangedCallback) { Init(configEntry); } private void Init(ConfigEntry configEntry) { if (configEntry == null) { throw new ArgumentNullException("configEntry", "SyncVarSetting ConfigEntry cannot be null. Make sure that Bepinex config binding has been completed successfully before this call. If not using a ConfigEntry is intended, use SyncVar instead."); } NetworkSpawnManager.DebugLog(() => "SyncVarSetting constructor. " + $"Setting SyncVar to its configEntry value ({configEntry.Value})."); ConfigEntry = configEntry; _Value = configEntry.Value; RegisterSyncvarSetting(); } public void RegisterSyncvarSetting() { UnregisterSyncvarSetting(); ConfigEntry.SettingChanged += SetValueFromConfig; } public void UnregisterSyncvarSetting() { ConfigEntry.SettingChanged -= SetValueFromConfig; } public override void InitializeSyncObject(NetworkBehaviour netBehaviour) { InitSyncObjectReflection(netBehaviour); NetworkSpawnManager.DebugLog(() => "SyncVarSetting fully initialized. " + $"Setting SyncVar from {_Value} to its configEntry value ({ConfigEntry.Value})."); SetValueFromConfig(); } private void SetValueFromConfig(object sender, EventArgs e) { SetValueFromConfig(); } public void SetValueFromConfig() { Value = ConfigEntry.Value; } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static implicit operator T(SyncVarSetting field) { return field.Value; } } } namespace Damntry.UtilsBepInEx.MirrorNetwork.Helpers { public static class NetworkPrefabHelper { private static readonly string NetworkRootObjName = "NetworkedObjects"; public static GameObject GetNetworkReadyPrefab(string prefabName, out NetworkBehaviour networkBehaviour) where T : NetworkBehaviour { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Expected O, but got Unknown GameObject val = new GameObject(prefabName); val.SetActive(false); val.transform.parent = GetRootNetworkTransform(); val.AddComponent(); networkBehaviour = (NetworkBehaviour)(object)val.AddComponent(); return val; } private static Transform GetRootNetworkTransform() { //IL_0013: Unknown result type (might be due to invalid IL or missing references) //IL_0019: Expected O, but got Unknown GameObject val = GameObject.Find(NetworkRootObjName); if (val == null) { val = new GameObject(NetworkRootObjName); } return val.transform; } public static bool AssetIdExists(uint assetId) { return NetworkClient.prefabs.ContainsKey(assetId); } public static bool IsPrefabGameObjectRegistered(GameObject prefabObj, uint expectedAssetId) { if (NetworkClient.prefabs != null && NetworkClient.prefabs.TryGetValue(expectedAssetId, out var value)) { return prefabObj == value; } return false; } public static bool IsPrefabHandlerRegistered(SpawnHandlerDelegate spawnHandlerDelegate, uint expectedAssetId) { if (FindSpawnhandlerByAssetId(expectedAssetId, out var IsFieldNotFound, out var spawnDelegate)) { return spawnHandlerDelegate == spawnDelegate; } return IsFieldNotFound; } public static bool FindSpawnhandlerByAssetId(uint assetId, out bool IsFieldNotFound, out SpawnHandlerDelegate spawnDelegate) { spawnDelegate = null; IsFieldNotFound = false; try { string text = "spawnHandlers"; FieldInfo field = typeof(NetworkClient).GetField("spawnHandlers", AccessTools.all); if (field != null) { return ((Dictionary)field.GetValue(null)).TryGetValue(assetId, out spawnDelegate); } TimeLogger.Logger.LogTimeError("The field " + text + " could not be found in type " + typeof(NetworkClient).Name + ".", (LogCategories)512); } catch (Exception ex) { TimeLogger.Logger.LogTimeException(ex, (LogCategories)512); } IsFieldNotFound = true; return false; } } public interface INetworkPrefabSpawner { Type NetworkBehaviourType { get; } uint DefinedAssetId { get; } void AddToPrefabs(); void Spawn(); } public class NetworkPrefabSpawner : INetworkPrefabSpawner where T : NetworkBehaviour { [Serializable] [CompilerGenerated] private sealed class <>c { public static readonly <>c <>9 = new <>c(); public static UnSpawnDelegate <>9__15_1; internal void b__15_1(GameObject spawned) { } } private GameObject prefabObj; private NetworkBehaviour networkBehaviourInstance; public uint DefinedAssetId { get; init; } public bool PrefabRegisterOk { get; private set; } public Type NetworkBehaviourType { get; private set; } public NetworkPrefabSpawner(uint assetId) { NetworkBehaviourType = typeof(T); DefinedAssetId = assetId; } public void AddToPrefabs() { //IL_00ae: Unknown result type (might be due to invalid IL or missing references) //IL_00b4: Expected O, but got Unknown //IL_00c8: Unknown result type (might be due to invalid IL or missing references) //IL_00cd: Unknown result type (might be due to invalid IL or missing references) //IL_00d3: Expected O, but got Unknown string typeName = NetworkBehaviourType.Name; prefabObj = NetworkPrefabHelper.GetNetworkReadyPrefab(typeName, out networkBehaviourInstance); if ((Object)(object)prefabObj == (Object)null) { TimeLogger.Logger.LogTimeError("A prefab object couldnt be created from type " + typeName, (LogCategories)512); return; } uint definedAssetId = DefinedAssetId; if (NetworkPrefabHelper.AssetIdExists(definedAssetId)) { TimeLogger.Logger.LogTimeError($"The specified assetId \"{definedAssetId}\" for the NetworkBehaviour " + "class " + typeName + " was already found in the NetworkClient.", (LogCategories)512); return; } SpawnHandlerDelegate val = (SpawnHandlerDelegate)((SpawnMessage msg) => prefabObj); object obj = <>c.<>9__15_1; if (obj == null) { UnSpawnDelegate val2 = delegate { }; <>c.<>9__15_1 = val2; obj = (object)val2; } UnSpawnDelegate val3 = (UnSpawnDelegate)obj; NetworkClient.RegisterPrefab(prefabObj, DefinedAssetId, val, val3); PrefabRegisterOk = NetworkPrefabHelper.IsPrefabHandlerRegistered(val, DefinedAssetId); if (!PrefabRegisterOk) { NetworkSpawnManager.DebugLog(() => "Prefab NOT registered for NetworkBehaviour " + typeName + "."); return; } NetworkSpawnManager.DebugLog(() => "Prefab was registered correctly for NetworkBehaviour " + typeName + "."); TriggerStartNetworkSession(NetworkSpawnManager.GetCurrentNetworkMode()); } private void TriggerStartNetworkSession(NetworkMode networkMode) { if (typeof(ISyncVarBehaviour).IsAssignableFrom(typeof(T))) { ((ISyncVarBehaviour)networkBehaviourInstance).StartNetworkSession(networkMode); } } public void Spawn() { if (PrefabRegisterOk) { if ((Object)(object)prefabObj == (Object)null) { TimeLogger.Logger.LogTimeFatal("The prefab object to spawn for type " + NetworkBehaviourType.Name + ". is null. Make sure to call AddToPrefabs() before spawning happens.", (LogCategories)512); } else { NetworkServer.Spawn(prefabObj, (NetworkConnection)null); } } } } public enum NetworkMode { NotInitialized, Offline, ServerOnly, ClientOnly, Host } [HarmonyPatch] public class NetworkSpawnManager { private static readonly Harmony harmony; private static readonly Dictionary networkBehaviourRegistry; private static uint _assetIdSignature; public static bool NetworkDebugLog; static NetworkSpawnManager() { //IL_0015: Unknown result type (might be due to invalid IL or missing references) //IL_001f: Expected O, but got Unknown NetworkDebugLog = true; harmony = new Harmony(typeof(NetworkSpawnManager).FullName); networkBehaviourRegistry = new Dictionary(); } public static bool IsNetworkActive() { if (!NetworkServer.active) { return NetworkClient.active; } return true; } public static bool IsNetworkOnline() { //IL_0012: Unknown result type (might be due to invalid IL or missing references) //IL_0018: Invalid comparison between Unknown and I4 if ((Object)(object)NetworkManager.singleton != (Object)null) { return (int)NetworkManager.singleton.mode > 0; } return false; } public static bool IsNetworkOnlineHosting() { //IL_001f: Unknown result type (might be due to invalid IL or missing references) //IL_0025: Invalid comparison between Unknown and I4 //IL_0012: Unknown result type (might be due to invalid IL or missing references) //IL_0018: Invalid comparison between Unknown and I4 if (!((Object)(object)NetworkManager.singleton != (Object)null) || (int)NetworkManager.singleton.mode != 3) { return (int)NetworkManager.singleton.mode == 1; } return true; } public static bool IsNetworkClientOnly() { //IL_0012: Unknown result type (might be due to invalid IL or missing references) //IL_0018: Invalid comparison between Unknown and I4 if ((Object)(object)NetworkManager.singleton != (Object)null) { return (int)NetworkManager.singleton.mode == 2; } return false; } public static NetworkMode GetCurrentNetworkMode() { //IL_0016: Unknown result type (might be due to invalid IL or missing references) //IL_001b: Unknown result type (might be due to invalid IL or missing references) //IL_001c: Unknown result type (might be due to invalid IL or missing references) //IL_0032: Expected I4, but got Unknown if ((Object)(object)NetworkManager.singleton == (Object)null) { return NetworkMode.NotInitialized; } NetworkManagerMode mode = NetworkManager.singleton.mode; return (int)mode switch { 0 => NetworkMode.Offline, 1 => NetworkMode.ServerOnly, 2 => NetworkMode.ClientOnly, 3 => NetworkMode.Host, _ => throw new NotImplementedException(), }; } public static void Initialize(int assetIdSignature) { if (assetIdSignature < 1 || assetIdSignature > 4293) { TimeLogger.Logger.LogTimeFatal($"The value of assetIdSignature is {assetIdSignature} " + "but it must be between 1 and 4293, both inclusive. All related network functionality will not work.", (LogCategories)512); return; } _assetIdSignature = (uint)assetIdSignature; harmony.PatchAll(typeof(NetworkSpawnManager)); if (!harmony.GetPatchedMethods().Any()) { TimeLogger.Logger.LogTimeFatal("NetworkSpawnManager patch failed. Modded network features wont work.", (LogCategories)512); } } [HarmonyPatch(typeof(NetworkServer), "SpawnObjects")] [HarmonyPatch(typeof(NetworkClient), "PrepareToSpawnSceneObjects")] [HarmonyPostfix] public static void OnStartNetworkSessionPrefix(MethodBase __originalMethod) { DebugLog(() => "OnStartNetworkSessionPrefix - Coming from method " + $"{__originalMethod.Name} - Network mode: {NetworkManager.singleton.mode}"); if (IsNetworkActive()) { DebugLog("Mirror network system is active. Adding network spawns"); AddNetworkSpawns(); } else { DebugLog("Mirror network system is NOT active. Skipping this session initialization"); } } public static void RegisterNetwork(uint assetId) where T : NetworkBehaviour { if (assetId == 0) { throw new ArgumentException("The assetId parameter cant be 0."); } if (MathMethods.CountDigits(assetId) > 6) { throw new ArgumentException("The assetId parameter must have less than 7 digits [1 to 999999]."); } Type typeFromHandle = typeof(T); if (networkBehaviourRegistry.ContainsKey(typeFromHandle)) { throw new InvalidOperationException("The NetworkBehaviour of type " + typeFromHandle.Name + " was already registered."); } NetworkPrefabSpawner value = new NetworkPrefabSpawner(GetAssetIdFromSignature(assetId)); networkBehaviourRegistry.Add(typeFromHandle, value); } private static void AddNetworkSpawns() { foreach (KeyValuePair item in networkBehaviourRegistry) { item.Value.AddToPrefabs(); } if (NetworkServer.active) { SpawnHost(); } } private static void SpawnHost() { DebugLog(() => $"Spawning objects - NetworkServer active? {NetworkServer.active}"); if (!NetworkServer.active) { return; } foreach (KeyValuePair item in networkBehaviourRegistry) { item.Value.Spawn(); } } private static uint GetAssetIdFromSignature(uint assetId) { return _assetIdSignature * 1000000 + assetId; } public static void DebugLog(Func textLambda) { } public static void DebugLog(string text) { } } } namespace Damntry.UtilsBepInEx.MirrorNetwork.Components { internal interface ISyncVarBehaviour { void StartNetworkSession(NetworkMode networkMode); } public abstract class SyncVarNetworkBehaviour : NetworkBehaviour, ISyncVarBehaviour where T : SyncVarNetworkBehaviour { private static readonly BindingFlags SearchFlags = BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic; private readonly Type derivedType = typeof(T); private readonly List validSyncVarList = new List(); private static readonly Dictionary methodRedirects = new Dictionary(); private readonly Harmony harmony = new Harmony(typeof(SyncVarNetworkBehaviour).FullName); private const bool IS_DEBUG_NETWORK_TESTS__ENABLED = false; protected virtual void OnStartNetworkSession(NetworkMode networkMode) { } protected virtual void OnSyncVarValuesDefaulted() { } protected virtual void OnSyncVarsNetworkReady() { } void ISyncVarBehaviour.StartNetworkSession(NetworkMode networkMode) { NetworkSpawnManager.DebugLog(() => "OnStartNetworkSession call begins for type " + derivedType.Name + "."); OnStartNetworkSession(networkMode); validSyncVarList.Clear(); foreach (MemberInfoHelper item in GetDerivedSyncVarsOfType(typeof(SyncVar<>))) { if (CheckSyncVarValidity(item, out var syncVarInstance)) { validSyncVarList.Add(syncVarInstance); syncVarInstance.SetToDefaultValue(); } } OnSyncVarValuesDefaulted(); } private void Awake() { foreach (ISyncVar validSyncVar in validSyncVarList) { InitializeSyncVar(validSyncVar, (T)this); } validSyncVarList.Clear(); NetworkSpawnManager.DebugLog(() => "OnSyncVarsNetworkReady call begins for type " + derivedType.Name + "."); OnSyncVarsNetworkReady(); } private List GetDerivedSyncVarsOfType(Type searchType) { IEnumerable enumerable = derivedType.GetFields(SearchFlags).Cast().Concat(derivedType.GetProperties(SearchFlags)) .Cast(); if (enumerable == null || enumerable.Count() == 0) { return new List(); } return (from mif in enumerable.Where((MemberInfo mi) => ReflectionExtension.HasCustomAttribute(mi)).Select((Func)((MemberInfo mi) => new MemberInfoHelper(mi))) where ReflectionExtension.IsSubclassOfRawGeneric(mif.MemberInfoType, searchType) select mif).ToList(); } private bool CheckSyncVarValidity(MemberInfoHelper syncVarInfoHelper, out ISyncVar syncVarInstance) { syncVarInstance = null; Type memberInfoType = syncVarInfoHelper.MemberInfoType; if (!memberInfoType.IsSubclassOf(typeof(SyncObject))) { TimeLogger.Logger.LogTimeWarning("The var " + derivedType.Name + "." + ((MemberInfo)(object)syncVarInfoHelper).Name + " does not inherit from SyncObject and will be skipped. Make sure that the SyncVarNetworkAttribute annotation was intended.", (LogCategories)512); return false; } if (!typeof(ISyncVar).IsAssignableFrom(memberInfoType)) { TimeLogger.Logger.LogTimeWarning("The var " + derivedType.Name + "." + ((MemberInfo)(object)syncVarInfoHelper).Name + " does not inherit from ISyncVar and will be skipped. Make sure that the SyncVarNetworkAttribute annotation was intended.", (LogCategories)512); return false; } if (memberInfoType.GetGenericArguments() == null || memberInfoType.GetGenericArguments().Length == 0) { TimeLogger.Logger.LogTimeWarning("The var " + derivedType.Name + "." + ((MemberInfo)(object)syncVarInfoHelper).Name + " does not declare any generic type parameters and will be skipped. Make sure that the type derives from SyncVar.", (LogCategories)512); return false; } syncVarInstance = (ISyncVar)syncVarInfoHelper.GetValueStaticAgnostic((object)(T)this); if (syncVarInstance == null) { TimeLogger.Logger.LogTimeWarning("The var " + derivedType.Name + "." + ((MemberInfo)(object)syncVarInfoHelper).Name + " has not been instantiated and will be skipped. Make sure to call its constructor.", (LogCategories)512); return false; } return true; } private void InitializeSyncVar(ISyncVar syncVarInstance, T netBehaviourInstance) { //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_000c: Expected O, but got Unknown if (!IsSyncVarNetworkInitialized((SyncObject)syncVarInstance)) { syncVarInstance.InitializeSyncObject((NetworkBehaviour)(object)netBehaviourInstance); } } private bool IsSyncVarNetworkInitialized(SyncObject syncVarInstance) { return base.syncObjects.Contains(syncVarInstance); } private Delegate GetOnChangeDelegate(string onChangeMethodName, T netBehaviourInstance, Type syncVarType) { Type type = syncVarType.GetGenericArguments()[0]; MethodInfo method = derivedType.GetMethod(onChangeMethodName, SearchFlags, null, new Type[2] { type, type }, null); if (method == null) { TimeLogger.Logger.LogTimeWarning("Method \"" + onChangeMethodName + "\" could not be found in type " + derivedType.Name + ", or does not have the required signature: " + onChangeMethodName + "(" + syncVarType.Name + " oldValue, " + syncVarType.Name + " newValue).", (LogCategories)512); return null; } Delegate @delegate = Delegate.CreateDelegate(typeof(Action<, >).MakeGenericType(type, type), netBehaviourInstance, method); if ((object)@delegate == null) { TimeLogger.Logger.LogTimeWarning("A delegate for method \"" + derivedType.Name + "." + onChangeMethodName + "\" could not be created.", (LogCategories)512); return null; } return @delegate; } private void InitializeRedirectsRPC_CMD() { //IL_008b: Unknown result type (might be due to invalid IL or missing references) //IL_0097: Expected O, but got Unknown List<(MethodInfo, MethodInfo)> rPC_MethodTargets = GetRPC_MethodTargets(); LOG.TEMPWARNING($"{rPC_MethodTargets.Count} RPC methods", true); foreach (var item in rPC_MethodTargets) { if (!methodRedirects.ContainsKey(item.Item1)) { methodRedirects.Add(item.Item1, item.Item2); MethodInfo method = new Func, ILGenerator, MethodBase, IEnumerable>(TranspileRPC_Call).Method; harmony.Patch((MethodBase)item.Item1, (HarmonyMethod)null, (HarmonyMethod)null, new HarmonyMethod(method), (HarmonyMethod)null, (HarmonyMethod)null); } } LOG.TEMPWARNING("Method patching finished.", true); } public override void OnStopClient() { harmony.UnpatchSelf(); methodRedirects.Clear(); } private List<(MethodInfo origMethod, MethodInfo targetMethod)> GetRPC_MethodTargets() { return (from mi in derivedType.GetMethods(SearchFlags) where ReflectionExtension.HasCustomAttribute((MemberInfo)mi) select (mi, GetMethodInfoFromRPCAttribute(mi)) into tup where tup.Item2 != null select tup).ToList(); } private MethodInfo GetMethodInfoFromRPCAttribute(MethodInfo methodInfo) { RPC_CallOnClientAttribute customAttribute = methodInfo.GetCustomAttribute(); if (Type.GetType(customAttribute.declaringType.AssemblyQualifiedName) == null) { TimeLogger.Logger.LogTimeError("The type FullName could not be found.", (LogCategories)512); } MethodInfo methodInfo2 = AccessTools.Method(customAttribute.declaringType, customAttribute.targetMethodName, customAttribute.parameters, customAttribute.generics); if (methodInfo2 != null && !CompareMethodSignatures(methodInfo, methodInfo2)) { methodInfo2 = null; } return methodInfo2; } public static bool CompareMethodSignatures(MethodInfo mi1, MethodInfo mi2, bool compareDeclaringType = false) { List list = new List(); if (mi1.IsGenericMethod || mi2.IsGenericMethod) { list.Add("generic methods not supported"); } if (mi1.ReturnType != mi2.ReturnType) { list.Add("return type"); } if (mi1.GetParameters().Length != mi2.GetParameters().Length) { list.Add("number of parameters"); } else { for (int i = 0; i < mi1.GetParameters().Length; i++) { if (mi1.GetParameters()[i].ParameterType != mi2.GetParameters()[i].ParameterType) { list.Add($"param {i + 1} type"); } } } if (mi1.IsStatic != mi2.IsStatic) { list.Add("static modifier"); } if (list.Count > 0) { TimeLogger.Logger.LogTimeError("The methods " + mi1.DeclaringType.Name + "." + mi1.Name + " and " + mi2.DeclaringType.Name + "." + mi2.Name + " need to have the same method signature. Fix the following differences: " + string.Join(", ", list), (LogCategories)32); } return list.Count == 0; } [HarmonyDebug] private static IEnumerable TranspileRPC_Call(IEnumerable instructions, ILGenerator generator, MethodBase originalMethod) { //IL_0010: Unknown result type (might be due to invalid IL or missing references) //IL_0016: Expected O, but got Unknown methodRedirects.TryGetValue(originalMethod, out var value); CodeMatcher val = new CodeMatcher(instructions, (ILGenerator)null); LOG.TEMPWARNING("Before emitting delegate " + val.InstructionEnumeration().GetFormattedIL(), true); if (NetworkServer.active) { LOG.TEMPWARNING("Host. RPC network send logic.", true); val.Advance(1); InsertRPC_GenerationCall(val, generator, value.GetParameters(), value.IsStatic); } else if (NetworkClient.active) { LOG.TEMPWARNING("Client. Executing next.", true); EmitCallTargetMethod(val, value); } else { TimeLogger.Logger.LogTimeError("RPC method " + originalMethod.Name + " was called when no Mirror network component was active.", (LogCategories)512); } LOG.TEMPWARNING("After emitting delegate " + val.InstructionEnumeration().GetFormattedIL(), true); return val.InstructionEnumeration(); } private static void InsertRPC_GenerationCall(CodeMatcher codeMatcher, ILGenerator generator, ParameterInfo[] parameters, bool isStatic) { //IL_001f: Unknown result type (might be due to invalid IL or missing references) //IL_0025: Expected O, but got Unknown //IL_0041: Unknown result type (might be due to invalid IL or missing references) //IL_0047: Expected O, but got Unknown //IL_0068: Unknown result type (might be due to invalid IL or missing references) //IL_006e: Expected O, but got Unknown //IL_0086: Unknown result type (might be due to invalid IL or missing references) //IL_008c: Expected O, but got Unknown //IL_00e4: Unknown result type (might be due to invalid IL or missing references) //IL_00ea: Expected O, but got Unknown //IL_00c9: Unknown result type (might be due to invalid IL or missing references) //IL_00cf: Expected O, but got Unknown int num = parameters.Length; if (num > 0) { codeMatcher.InsertAndAdvance((CodeInstruction[])(object)new CodeInstruction[1] { new CodeInstruction(OpCodes.Ldc_I4, (object)num) }).InsertAndAdvance((CodeInstruction[])(object)new CodeInstruction[1] { new CodeInstruction(OpCodes.Newarr, (object)typeof(object)) }); int num2 = ((!isStatic) ? 1 : 0); for (int i = 0; i < num; i++) { codeMatcher.InsertAndAdvance((CodeInstruction[])(object)new CodeInstruction[1] { new CodeInstruction(OpCodes.Dup, (object)null) }).InsertAndAdvance((CodeInstruction[])(object)new CodeInstruction[1] { new CodeInstruction(OpCodes.Ldc_I4, (object)i) }).InsertAndAdvance((CodeInstruction[])(object)new CodeInstruction[1] { CodeInstructionNew.LoadArgument(i + num2) }); Type parameterType = parameters[i].ParameterType; if (parameterType.IsValueType) { codeMatcher.InsertAndAdvance((CodeInstruction[])(object)new CodeInstruction[1] { new CodeInstruction(OpCodes.Box, (object)parameterType) }); } codeMatcher.InsertAndAdvance((CodeInstruction[])(object)new CodeInstruction[1] { new CodeInstruction(OpCodes.Stelem_Ref, (object)null) }); } } codeMatcher.Insert((CodeInstruction[])(object)new CodeInstruction[1] { Transpilers.EmitDelegate>((Action)MakeRPC_Call) }); } private static void MakeRPC_Call(object[] args) { LOG.TEMPWARNING(string.Format("MakeRPC_Call - {0} ({1}) params: {2}", args.Length, args[0], string.Join(", ", args)), true); NetworkWriterPooled val = NetworkWriterPool.Get(); for (int i = 0; i < args.Length; i++) { _ = args[i]; } NetworkWriterPool.Return(val); LOG.TEMPWARNING("MakeRPC_Call finished", true); } private static void EmitCallTargetMethod(CodeMatcher codeMatcher, MethodInfo targetMethod) { //IL_0023: Unknown result type (might be due to invalid IL or missing references) //IL_0029: Expected O, but got Unknown codeMatcher.End().Advance(-1); LoadMethodArgIntoStack(codeMatcher, targetMethod); codeMatcher.Insert((CodeInstruction[])(object)new CodeInstruction[1] { new CodeInstruction(OpCodes.Call, (object)targetMethod) }); } private static void LoadMethodArgIntoStack(CodeMatcher codeMatcher, MethodInfo targetMethod) { int num = targetMethod.GetParameters().Count() + ((!targetMethod.IsStatic) ? 1 : 0); for (int i = 0; i < num; i++) { codeMatcher.InsertAndAdvance((CodeInstruction[])(object)new CodeInstruction[1] { CodeInstructionNew.LoadArgument(i) }); } } [RPC_CallOnClient(typeof(SyncVarNetworkAttribute), "UpdateSupermarketNameOnClient", null)] private void UpdateSupermarketName(string marketName) { } private static void UpdateSupermarketNameOnClient() { } } } namespace Damntry.UtilsBepInEx.MirrorNetwork.Attributes { [AttributeUsage(AttributeTargets.Method)] public class RPC_CallOnClientAttribute : Attribute { public Type declaringType; public string targetMethodName; public Type[] parameters; public Type[] generics; public RPC_CallOnClientAttribute(Type declaringType, string targetMethodName, Type[] parameters = null) { SetTargetMethod(declaringType, targetMethodName, parameters); } public RPC_CallOnClientAttribute(Type declaringType, string targetMethodName, Type[] parameters, Type[] generics) { SetTargetMethod(declaringType, targetMethodName, parameters, generics); } private void SetTargetMethod(Type declaringType, string targetMethodName, Type[] parameters = null, Type[] generics = null) { if (declaringType == null) { TimeLogger.Logger.LogTimeError("Parameter declaringType is null.", (LogCategories)512); return; } if (string.IsNullOrEmpty(targetMethodName)) { TimeLogger.Logger.LogTimeError("Parameter targetMethodName is null or empty.", (LogCategories)512); return; } this.declaringType = declaringType; this.targetMethodName = targetMethodName; this.parameters = parameters; this.generics = generics; } } [AttributeUsage(AttributeTargets.Property | AttributeTargets.Field)] public class SyncVarNetworkAttribute : Attribute { } } namespace Damntry.UtilsBepInEx.Logging { public sealed class BepInExTimeLogger : TimeLogger { private static ManualLogSource bepinexLogger; protected override void InitializeLogger(params object[] args) { if (args == null) { throw new ArgumentNullException("args"); } if (args.Length == 0 || !(args[0] is string)) { throw new ArgumentException("The argument is empty or its first index is not a string"); } bepinexLogger = Logger.CreateLogSource((string)args[0]); } protected override void LogMessage(string message, LogTier logLevel) { //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Unknown result type (might be due to invalid IL or missing references) bepinexLogger.Log(convertLogLevel(logLevel), (object)message); } private LogLevel convertLogLevel(LogTier logLevel) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0002: Invalid comparison between Unknown and I4 //IL_0007: Unknown result type (might be due to invalid IL or missing references) if ((int)logLevel == -1) { return (LogLevel)63; } return (LogLevel)logLevel; } } } namespace Damntry.UtilsBepInEx.IL { public static class ILExtensionMethods { public static int LocalIndex(this CodeInstruction code) { if (code.opcode == OpCodes.Ldloc_0 || code.opcode == OpCodes.Stloc_0) { return 0; } if (code.opcode == OpCodes.Ldloc_1 || code.opcode == OpCodes.Stloc_1) { return 1; } if (code.opcode == OpCodes.Ldloc_2 || code.opcode == OpCodes.Stloc_2) { return 2; } if (code.opcode == OpCodes.Ldloc_3 || code.opcode == OpCodes.Stloc_3) { return 3; } if (code.opcode == OpCodes.Ldloc_S || code.opcode == OpCodes.Ldloc) { return GetLocalOperandIndex(code.operand); } if (code.opcode == OpCodes.Stloc_S || code.opcode == OpCodes.Stloc) { return GetLocalOperandIndex(code.operand); } if (code.opcode == OpCodes.Ldloca_S || code.opcode == OpCodes.Ldloca) { return GetLocalOperandIndex(code.operand); } throw new ArgumentException("Instruction is not a load or store", "code"); } private static int GetLocalOperandIndex(object operand) { if (operand.GetType() == typeof(LocalBuilder)) { return ((LocalBuilder)operand).LocalIndex; } return Convert.ToInt32(operand); } public static int ArgumentIndex(this CodeInstruction code) { if (code.opcode == OpCodes.Ldarg_0) { return 0; } if (code.opcode == OpCodes.Ldarg_1) { return 1; } if (code.opcode == OpCodes.Ldarg_2) { return 2; } if (code.opcode == OpCodes.Ldarg_3) { return 3; } if (code.opcode == OpCodes.Ldarg_S || code.opcode == OpCodes.Ldarg) { return Convert.ToInt32(code.operand); } if (code.opcode == OpCodes.Starg_S || code.opcode == OpCodes.Starg) { return Convert.ToInt32(code.operand); } if (code.opcode == OpCodes.Ldarga_S || code.opcode == OpCodes.Ldarga) { return Convert.ToInt32(code.operand); } throw new ArgumentException("Instruction is not a load or store", "code"); } public static string GetFormattedIL(this IEnumerable instrList) { return instrList.Aggregate("", (string combinedText, CodeInstruction instr) => combinedText + "\n" + GetFormattedILSingleLine(instr)); } private static string GetFormattedILSingleLine(CodeInstruction instruction) { string text = FormatArgument(instruction.operand); string text2 = ((text.Length > 0) ? " " : ""); string text3 = instruction.opcode.ToString(); if (instruction.opcode.FlowControl == FlowControl.Branch || instruction.opcode.FlowControl == FlowControl.Cond_Branch) { text3 += " =>"; } text3 = text3.PadRight(10); return $"{CodePos(instruction)}{text3}{text2}{text}"; } private static string CodePos(CodeInstruction instruction) { return ""; } private static string FormatArgument(object argument, string extra = null) { if (argument == null) { return ""; } Type type = argument.GetType(); if (argument is MethodBase methodBase) { return GeneralExtensions.FullDescription(methodBase) + ((extra != null) ? (" " + extra) : ""); } if (argument is FieldInfo fieldInfo) { return GeneralExtensions.FullDescription(fieldInfo.FieldType) + " " + GeneralExtensions.FullDescription(fieldInfo.DeclaringType) + "::" + fieldInfo.Name; } if (type == typeof(Label)) { return $"Label{((Label)argument).GetHashCode()}"; } if (type == typeof(Label[])) { return "Labels" + string.Join(",", ((Label[])argument).Select((Label l) => l.GetHashCode().ToString()).ToArray()); } if (type == typeof(LocalBuilder)) { return $"{((LocalBuilder)argument).LocalIndex} ({((LocalBuilder)argument).LocalType})"; } if (type == typeof(string)) { return GeneralExtensions.ToLiteral(argument.ToString(), "\""); } return argument.ToString().Trim(); } } public static class CodeInstructionNew { public static CodeInstruction LoadLocal(int index, bool useAddress = false) { //IL_003b: Unknown result type (might be due to invalid IL or missing references) //IL_0041: Expected O, but got Unknown //IL_002c: Unknown result type (might be due to invalid IL or missing references) //IL_0032: Expected O, but got Unknown //IL_001b: Unknown result type (might be due to invalid IL or missing references) //IL_0021: Expected O, but got Unknown //IL_004b: Unknown result type (might be due to invalid IL or missing references) //IL_0051: Expected O, but got Unknown //IL_005b: Unknown result type (might be due to invalid IL or missing references) //IL_0061: Expected O, but got Unknown //IL_006b: Unknown result type (might be due to invalid IL or missing references) //IL_0071: Expected O, but got Unknown //IL_009a: Unknown result type (might be due to invalid IL or missing references) //IL_00a0: Expected O, but got Unknown //IL_0089: Unknown result type (might be due to invalid IL or missing references) //IL_008f: Expected O, but got Unknown if (useAddress) { if (index >= 256) { return new CodeInstruction(OpCodes.Ldloca, (object)index); } return new CodeInstruction(OpCodes.Ldloca_S, (object)Convert.ToByte(index)); } if (index != 0) { if (index != 1) { if (index != 2) { if (index != 3) { if (index >= 256) { return new CodeInstruction(OpCodes.Ldloc, (object)index); } return new CodeInstruction(OpCodes.Ldloc_S, (object)Convert.ToByte(index)); } return new CodeInstruction(OpCodes.Ldloc_3, (object)null); } return new CodeInstruction(OpCodes.Ldloc_2, (object)null); } return new CodeInstruction(OpCodes.Ldloc_1, (object)null); } return new CodeInstruction(OpCodes.Ldloc_0, (object)null); } public static CodeInstruction StoreLocal(int index) { //IL_0009: Unknown result type (might be due to invalid IL or missing references) //IL_000f: Expected O, but got Unknown //IL_0019: Unknown result type (might be due to invalid IL or missing references) //IL_001f: Expected O, but got Unknown //IL_0029: Unknown result type (might be due to invalid IL or missing references) //IL_002f: Expected O, but got Unknown //IL_0039: Unknown result type (might be due to invalid IL or missing references) //IL_003f: Expected O, but got Unknown //IL_0068: Unknown result type (might be due to invalid IL or missing references) //IL_006e: Expected O, but got Unknown //IL_0057: Unknown result type (might be due to invalid IL or missing references) //IL_005d: Expected O, but got Unknown if (index != 0) { if (index != 1) { if (index != 2) { if (index != 3) { if (index >= 256) { return new CodeInstruction(OpCodes.Stloc, (object)index); } return new CodeInstruction(OpCodes.Stloc_S, (object)Convert.ToByte(index)); } return new CodeInstruction(OpCodes.Stloc_3, (object)null); } return new CodeInstruction(OpCodes.Stloc_2, (object)null); } return new CodeInstruction(OpCodes.Stloc_1, (object)null); } return new CodeInstruction(OpCodes.Stloc_0, (object)null); } public static CodeInstruction LoadArgument(int index, bool useAddress = false) { //IL_003b: Unknown result type (might be due to invalid IL or missing references) //IL_0041: Expected O, but got Unknown //IL_002c: Unknown result type (might be due to invalid IL or missing references) //IL_0032: Expected O, but got Unknown //IL_001b: Unknown result type (might be due to invalid IL or missing references) //IL_0021: Expected O, but got Unknown //IL_004b: Unknown result type (might be due to invalid IL or missing references) //IL_0051: Expected O, but got Unknown //IL_005b: Unknown result type (might be due to invalid IL or missing references) //IL_0061: Expected O, but got Unknown //IL_006b: Unknown result type (might be due to invalid IL or missing references) //IL_0071: Expected O, but got Unknown //IL_009a: Unknown result type (might be due to invalid IL or missing references) //IL_00a0: Expected O, but got Unknown //IL_0089: Unknown result type (might be due to invalid IL or missing references) //IL_008f: Expected O, but got Unknown if (useAddress) { if (index >= 256) { return new CodeInstruction(OpCodes.Ldarga, (object)index); } return new CodeInstruction(OpCodes.Ldarga_S, (object)Convert.ToByte(index)); } if (index != 0) { if (index != 1) { if (index != 2) { if (index != 3) { if (index >= 256) { return new CodeInstruction(OpCodes.Ldarg, (object)index); } return new CodeInstruction(OpCodes.Ldarg_S, (object)Convert.ToByte(index)); } return new CodeInstruction(OpCodes.Ldarg_3, (object)null); } return new CodeInstruction(OpCodes.Ldarg_2, (object)null); } return new CodeInstruction(OpCodes.Ldarg_1, (object)null); } return new CodeInstruction(OpCodes.Ldarg_0, (object)null); } public static CodeInstruction StoreArgument(int index) { //IL_0029: Unknown result type (might be due to invalid IL or missing references) //IL_002f: Expected O, but got Unknown //IL_0018: Unknown result type (might be due to invalid IL or missing references) //IL_001e: Expected O, but got Unknown if (index >= 256) { return new CodeInstruction(OpCodes.Starg, (object)index); } return new CodeInstruction(OpCodes.Starg_S, (object)Convert.ToByte(index)); } } } namespace Damntry.UtilsBepInEx.HarmonyPatching { public class HarmonyInstancePatcher { private enum PatchRecursiveAction { StopAll, SkipAndContinueNested, PatchAndContinueNested } private readonly Type harmonyInstanceType; private readonly Lazy harmonyPatch; public HarmonyInstancePatcher(Type harmonyInstanceType) { this.harmonyInstanceType = harmonyInstanceType; harmonyPatch = new Lazy((Func)(() => new Harmony(GetHarmonyInstanceId()))); } internal string GetHarmonyInstanceId() { return harmonyInstanceType.FullName; } public List PatchInstance() { return StartRecursivePatching(harmonyPatch.Value); } public void UnpatchInstance() { harmonyPatch.Value.UnpatchSelf(); } public int GetPatchedCount() { return harmonyPatch.Value.GetPatchedMethods().Count(); } private List StartRecursivePatching(Harmony harmonyPatch) { List list = new List(); if (PatchClass(harmonyInstanceType, harmonyPatch, list)) { PatchNestedClassesRecursive(new List(), harmonyInstanceType, harmonyPatch, list); } return list; } private void PatchNestedClassesRecursive(List classList, Type classType, Harmony harmony, List listPatchedMethods) { Type[] nestedTypes = classType.GetNestedTypes(BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); foreach (Type type in nestedTypes) { if (type.IsClass && PatchClass(type, harmony, listPatchedMethods)) { PatchNestedClassesRecursive(classList, type, harmony, listPatchedMethods); } } } private bool PatchClass(Type classType, Harmony harmonyPatch, List listPatchedMethods) { switch (CheckAttributesForAllowedAction(classType)) { case PatchRecursiveAction.StopAll: return false; case PatchRecursiveAction.PatchAndContinueNested: { PatchClassProcessor val = harmonyPatch.CreateClassProcessor(classType, true); listPatchedMethods.AddRange(val.Patch()); break; } } return true; } private PatchRecursiveAction CheckAttributesForAllowedAction(Type classType) { if (ReflectionExtension.HasCustomAttribute((MemberInfo)classType)) { return PatchRecursiveAction.StopAll; } if (ReflectionExtension.HasCustomAttribute((MemberInfo)classType)) { return PatchRecursiveAction.SkipAndContinueNested; } return PatchRecursiveAction.PatchAndContinueNested; } public List PatchClassByType(Type classType) { ThrowIfNotOwnInstanceNestedClass(classType); return harmonyPatch.Value.CreateClassProcessor(classType).Patch(); } public void UnpatchMethod(Type originalClassType, string originalMethodName) { if (originalMethodName == null) { throw new ArgumentNullException("originalMethodName"); } MethodInfo methodInfo = AccessTools.Method(originalClassType, originalMethodName, (Type[])null, (Type[])null); if (methodInfo == null) { throw new InvalidOperationException("The method \"" + originalMethodName + "\" couldnt be found in the type " + originalClassType.FullName + "."); } harmonyPatch.Value.Unpatch((MethodBase)methodInfo, (HarmonyPatchType)0, harmonyPatch.Value.Id); } private void ThrowIfNotOwnInstanceNestedClass(Type classType) { if (classType == null) { throw new ArgumentNullException("classType"); } if (classType == harmonyInstanceType) { throw new InvalidOperationException("Use the method PatchInstance/UnpatchInstance() instead."); } if (GetTopClassOfNested(classType) != harmonyInstanceType) { throw new InvalidOperationException("Class must be a nested class of " + harmonyInstanceType.FullName + ". If you want to do this action on another Instance, use that instance methods instead."); } } private Type GetTopClassOfNested(Type classType) { while (classType.IsNested) { classType = classType.DeclaringType; } return classType; } } public class HarmonyMonoMethods { public static MethodDefinition GetMethodDefinition(MethodInfo methodInfo) { MethodDefinition val = MethodBaseToMethodDefinition(methodInfo); if (val == null) { AssemblyDefinition val2 = AssemblyDefinition.ReadAssembly(AssemblyUtils.GetAssemblyDllFilePath(methodInfo.DeclaringType)); try { val = Extensions.FindMethod(val2.MainModule.GetType(methodInfo.DeclaringType.FullName), Extensions.GetID((MethodBase)methodInfo, (string)null, (string)null, true, false, false), false); } catch (Exception ex) { TimeLogger.Logger.LogTimeDebug(TimeLogger.FormatException(ex, "Error while trying to convert MethodInfo to MethodDefinition. You can safely ignore this error if you are not the dev."), (LogCategories)4194304); } } return val; } public static MethodDefinition MethodBaseToMethodDefinition(MethodBase method) { //IL_002f: Unknown result type (might be due to invalid IL or missing references) //IL_0044: Unknown result type (might be due to invalid IL or missing references) //IL_004a: Expected O, but got Unknown return (MethodDefinition)((MemberReference)(TypeDefinition)ModuleDefinition.ReadModule((Stream)new MemoryStream(File.ReadAllBytes(method.DeclaringType.Module.FullyQualifiedName))).LookupToken(method.DeclaringType.MetadataToken)).Module.LookupToken(method.MetadataToken); } public static IEnumerable<(MethodInfo methodInfo, HarmonyMethod harmonyMethod)> GetAllPatchMethodTargets(Type assemblyType, bool skipNonExecutablePatches) { HarmonyMethod harmonyMethod; return (from type in Assembly.GetAssembly(assemblyType).GetTypes() where !skipNonExecutablePatches || IsPatchExecutable(type) select type).SelectMany((Type type) => from mInfo in type.GetMethods(BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic) where IsHarmonyAttribute(mInfo) select (GetTargetMethodFromHarmonyPatchMethod(type, mInfo, out harmonyMethod), harmonyMethod)); } private static bool IsPatchExecutable(Type methodClassType) { MethodInfo methodInfo = (from m in methodClassType.GetMethods(AccessTools.all) where m.ReturnType == typeof(bool) select m).FirstOrDefault((MethodInfo m) => m.Name == "Prepare" || m.GetCustomAttributes(inherit: true).Any((object attr) => attr.GetType().FullName == typeof(HarmonyPrepare).FullName)); if (methodInfo != null) { object[] array = AccessTools.ActualParameters((MethodBase)methodInfo, Array.Empty()); return ReflectionHelper.CallMethod((object)null, methodInfo, array); } return true; } public static bool IsHarmonyAttribute(MethodInfo methodInfo) { IEnumerable enumerable = null; try { enumerable = methodInfo.GetCustomAttributes(); } catch (TypeNotFoundInAssemblyException) { return false; } return enumerable.Any((Attribute attr) => attr is HarmonyAttribute); } public static MethodInfo GetTargetMethodFromHarmonyPatchMethod(Type methodClassType, MethodInfo patchMethodInfo, out HarmonyMethod harmonyMethod) { //IL_009a: Unknown result type (might be due to invalid IL or missing references) //IL_009f: Unknown result type (might be due to invalid IL or missing references) //IL_00af: Unknown result type (might be due to invalid IL or missing references) //IL_00b2: Unknown result type (might be due to invalid IL or missing references) IEnumerable source = from attr in patchMethodInfo.GetCustomAttributes(inherit: true) where attr is HarmonyAttribute select ((HarmonyAttribute)attr).info; harmonyMethod = HarmonyMethod.Merge(source.ToList()); if (harmonyMethod.method == null && harmonyMethod.declaringType == null) { HarmonyMethod val = HarmonyMethod.Merge(HarmonyMethodExtensions.GetFromType(methodClassType)); harmonyMethod = HarmonyMethodExtensions.Merge(val, harmonyMethod); } HarmonyMethod val2 = harmonyMethod; MethodType valueOrDefault = val2.methodType.GetValueOrDefault(); if (!val2.methodType.HasValue) { valueOrDefault = (MethodType)0; val2.methodType = valueOrDefault; } MethodInfo methodInfo = AccessTools.Method("HarmonyLib.PatchTools:GetOriginalMethod", new Type[1] { typeof(HarmonyMethod) }, (Type[])null); if (methodInfo != null) { return (MethodInfo)methodInfo.Invoke(null, new object[1] { harmonyMethod }); } TimeLogger.Logger.LogTimeWarning("Reflection access to \"HarmonyLib.PatchTools:GetOriginalMethod\" returned null. Using backup method.", (LogCategories)4194304); return AccessTools.Method(harmonyMethod.declaringType, harmonyMethod.methodName, harmonyMethod.argumentTypes, (Type[])null); } } public class MethodSignatureChecker { internal class MethodSignature { public string Arguments { get; set; } public string ReturnType { get; set; } public int IL_BodyHashcode { get; set; } public void Set_IL_BodyHashcode(Collection instructions) { //IL_0012: Unknown result type (might be due to invalid IL or missing references) //IL_0017: Unknown result type (might be due to invalid IL or missing references) if (instructions == null) { throw new ArgumentNullException("instructions"); } int num = 17; Enumerator enumerator = instructions.GetEnumerator(); try { while (enumerator.MoveNext()) { Instruction current = enumerator.Current; num = num * 71 + ((object)current).ToString().GetHashCode(); } } finally { ((IDisposable)enumerator).Dispose(); } IL_BodyHashcode = num; } internal bool Equals(MethodSignature other, out string errorDetail) { errorDetail = null; if (other == null) { errorDetail = "\tWrong method call or no longer exists."; } else if (Arguments != other.Arguments) { errorDetail = "\tArguments are different"; } else if (ReturnType != other.ReturnType) { errorDetail = "\tReturn types are different"; } else if (IL_BodyHashcode != other.IL_BodyHashcode) { errorDetail = "\tIL method body hashcode is different"; } if (errorDetail != null) { errorDetail = errorDetail.PadRight(38); } return errorDetail == null; } } private readonly struct MethodInfoData { private readonly struct FullMethodName { private readonly string method; private readonly string declaringType; private readonly string[] parameters; public FullMethodName(string declaredTypeName, string methodName, string[] typeParameterNames = null) { method = methodName; declaringType = ReflectionHelper.ConvertFullTypeToNormal(declaredTypeName); parameters = ReflectionHelper.ConvertFullTypesToNormal(typeParameterNames); } public FullMethodName(string declaredTypeName, string methodName, Type[] typeParameter = null) { method = methodName; declaringType = ReflectionHelper.ConvertFullTypeToNormal(declaredTypeName); string[] array = null; if (typeParameter != null && typeParameter.Length != 0) { array = (from t in typeParameter where t != null select t.Name).ToArray(); } parameters = array; } public string GetText() { string text = ""; if (parameters != null && parameters.Length != 0) { text = string.Join(", ", parameters); } return declaringType + "." + method + "(" + text + ")"; } } private readonly bool isMethodInfo; private readonly MethodInfo methodInfo; private readonly Type declaringType; private readonly string methodName; private readonly Type[] parameters; private readonly Type[] generics; private readonly string fullTypeName; private readonly string[] fullTypeParameters; private readonly FullMethodName fullMethodName; public MethodInfoData(MethodInfo methodInfo, string declaredTypeName, string methodName, string[] typeParameterNames = null) { declaringType = null; this.methodName = null; parameters = null; generics = null; fullTypeName = null; fullTypeParameters = null; if (string.IsNullOrEmpty(declaredTypeName)) { throw new ArgumentNullException("declaredTypeName cannot be empty."); } if (string.IsNullOrEmpty(methodName)) { throw new ArgumentNullException("methodName cannot be empty."); } this.methodInfo = methodInfo; fullMethodName = new FullMethodName(declaredTypeName, methodName, typeParameterNames); isMethodInfo = true; } public MethodInfoData(MethodInfo methodInfo, Type declaringType, string methodName, Type[] parameters = null, Type[] generics = null) { this.declaringType = null; this.methodName = null; this.parameters = null; this.generics = null; fullTypeName = null; fullTypeParameters = null; if (declaringType == null) { throw new ArgumentNullException("declaringType cannot be null."); } if (string.IsNullOrEmpty(methodName)) { throw new ArgumentException("methodName cannot be null or empty."); } this.methodInfo = methodInfo; fullMethodName = new FullMethodName(declaringType.Name, methodName, parameters); isMethodInfo = true; } public MethodInfoData(Type declaringType, string methodName, Type[] parameters = null, Type[] generics = null) { isMethodInfo = false; methodInfo = null; fullTypeName = null; fullTypeParameters = null; if (declaringType == null) { throw new ArgumentNullException("declaringType cannot be null."); } if (string.IsNullOrEmpty(methodName)) { throw new ArgumentException("methodName cannot be null or empty."); } this.declaringType = declaringType; this.methodName = methodName; this.parameters = parameters; this.generics = generics; fullMethodName = new FullMethodName(declaringType.Name, methodName, parameters); } public MethodInfoData(Type declaringType, string methodName, string[] fullTypeParameters = null, Type[] generics = null) { isMethodInfo = false; methodInfo = null; parameters = null; fullTypeName = null; if (declaringType == null) { throw new ArgumentNullException("declaringType cannot be null."); } if (string.IsNullOrEmpty(methodName)) { throw new ArgumentException("methodName cannot be null or empty."); } this.declaringType = declaringType; this.methodName = methodName; this.fullTypeParameters = fullTypeParameters; this.generics = generics; fullMethodName = new FullMethodName(declaringType.Name, methodName, fullTypeParameters); } public MethodInfoData(string fullTypeName, string methodName, string[] fullTypeParameters = null, Type[] generics = null) { isMethodInfo = false; methodInfo = null; declaringType = null; parameters = null; if (string.IsNullOrEmpty(fullTypeName)) { throw new ArgumentNullException("fullTypeName cannot be null or empty."); } if (string.IsNullOrEmpty(methodName)) { throw new ArgumentException("methodName cannot be null or empty."); } this.fullTypeName = fullTypeName; this.methodName = methodName; this.fullTypeParameters = fullTypeParameters; this.generics = generics; fullMethodName = new FullMethodName(fullTypeName, methodName, fullTypeParameters); } public MethodInfo GetMethodInfo() { if (isMethodInfo) { return methodInfo; } Type obj = ((declaringType != null) ? declaringType : AssemblyUtils.GetTypeFromLoadedAssemblies(fullTypeName, true)); Type[] array = ((parameters != null) ? parameters : AssemblyUtils.GetTypesFromLoadedAssemblies(true, fullTypeParameters)); return AccessTools.Method(obj, methodName, array, generics); } public string GetFullMethodName() { return fullMethodName.GetText(); } } private List listMethodInfoData; private Dictionary previousMethodSignatures; private readonly string pathFileMethodSignatures; private readonly Type pluginType; public CheckResult LastCheckResult { get; private set; } public MethodSignatureChecker(Type pluginType) { this.pluginType = pluginType; LastCheckResult = new CheckResult(); string combinedPathFromAssemblyFolder = AssemblyUtils.GetCombinedPathFromAssemblyFolder(pluginType, "MethodSignatureChecker"); if (!Directory.Exists(combinedPathFromAssemblyFolder)) { Directory.CreateDirectory(combinedPathFromAssemblyFolder); } char directorySeparatorChar = Path.DirectorySeparatorChar; pathFileMethodSignatures = combinedPathFromAssemblyFolder + directorySeparatorChar + "methodsignatures.json"; listMethodInfoData = new List(); } public static MethodSignatureChecker StartSignatureCheck(Type pluginType) { MethodSignatureChecker methodSignatureChecker = new MethodSignatureChecker(pluginType); methodSignatureChecker.PopulateMethodSignaturesFromHarmonyPatches(); methodSignatureChecker.StartSignatureCheck(); return methodSignatureChecker; } public void PopulateMethodSignaturesFromHarmonyPatches() { foreach (var (methodInfo, val) in HarmonyMonoMethods.GetAllPatchMethodTargets(pluginType, skipNonExecutablePatches: true)) { AddMethod(methodInfo, val.declaringType, val.methodName, val.argumentTypes); } } public void AddMethod(MethodInfo methodInfo, string declaredTypeName, string methodName, string[] typeParameterNames = null) { listMethodInfoData.Add(new MethodInfoData(methodInfo, declaredTypeName, methodName, typeParameterNames)); } public void AddMethod(MethodInfo methodInfo, Type declaringType, string methodName, Type[] parameters = null) { listMethodInfoData.Add(new MethodInfoData(methodInfo, declaringType, methodName, parameters)); } public void AddMethod(string fullTypeName, string methodName, string[] fullTypeParameters = null, Type[] generics = null) { listMethodInfoData.Add(new MethodInfoData(fullTypeName, methodName, fullTypeParameters, generics)); } public void AddMethod(Type declaringType, string methodName, string[] fullTypeParameters = null, Type[] generics = null) { listMethodInfoData.Add(new MethodInfoData(declaringType, methodName, fullTypeParameters, generics)); } public void AddMethod(Type declaringType, string methodName) { listMethodInfoData.Add(new MethodInfoData(declaringType, methodName, (Type[])null, (Type[])null)); } public void AddMethod(Type declaringType, string methodName, Type[] parameters = null, Type[] generics = null) { listMethodInfoData.Add(new MethodInfoData(declaringType, methodName, parameters, generics)); } private MethodSignature CreateMethodSignature(MethodInfo methodInfo, MethodDefinition methodDef) { MethodSignature methodSignature = new MethodSignature(); object[] parameters = methodInfo.GetParameters(); methodSignature.Arguments = string.Join("", parameters); methodSignature.ReturnType = methodInfo.ReturnType.FullName; methodSignature.Set_IL_BodyHashcode(methodDef.Body.Instructions); return methodSignature; } public CheckResult StartSignatureCheck() { CheckResult checkResult = new CheckResult(CheckResult.SignatureCheckResult.Started, ""); Dictionary dictionary = new Dictionary(); foreach (MethodInfoData listMethodInfoDatum in listMethodInfoData) { if (GetMethodSignature(listMethodInfoDatum, out var methodSigEntry) && !dictionary.ContainsKey(methodSigEntry.Key)) { dictionary.Add(methodSigEntry.Key, methodSigEntry.Value); } } if (dictionary == null || !dictionary.Any()) { return checkResult.SetValues(CheckResult.SignatureCheckResult.NoSignaturesAdded, "No method signature has been added. Signature comparison will be skipped."); } if (TryLoadSignaturesFromFile(checkResult)) { CompareOldSignaturesAgainstNew(checkResult, dictionary); } if (checkResult.Result != CheckResult.SignatureCheckResult.SignaturesDifferent) { SaveSignaturesToFile(dictionary); } if (checkResult.Result == CheckResult.SignatureCheckResult.Started) { throw new InvalidOperationException("Something went wrong. Method check finished with result \"Started\"."); } return LastCheckResult = checkResult; } private bool GetMethodSignature(MethodInfoData methodInfoData, out KeyValuePair methodSigEntry) { string text = ""; MethodSignature value = null; MethodInfo methodInfo = methodInfoData.GetMethodInfo(); if (methodInfo != null) { MethodDefinition methodDefinition = HarmonyMonoMethods.GetMethodDefinition(methodInfo); if (methodDefinition == null) { methodSigEntry = default(KeyValuePair); return false; } value = CreateMethodSignature(methodInfo, methodDefinition); } text = methodInfoData.GetFullMethodName(); methodSigEntry = new KeyValuePair(text, value); return true; } private bool TryLoadSignaturesFromFile(CheckResult checkResult) { if (!File.Exists(pathFileMethodSignatures)) { checkResult.SetValues(CheckResult.SignatureCheckResult.NoPreviousSignatures, "No method signature file. Skipped check."); return false; } try { string text = File.ReadAllText(pathFileMethodSignatures, Encoding.Unicode); previousMethodSignatures = JsonConvert.DeserializeObject>(text); } catch (Exception ex) { checkResult.SetValues(CheckResult.SignatureCheckResult.FileError, TimeLogger.FormatException(ex, "Error while reading and deserializing from file \"" + pathFileMethodSignatures + "\". It might be corrupted. Trying to delete file and skipping loading signatures.")); try { File.Delete(pathFileMethodSignatures); } catch { } return false; } return true; } private void SaveSignaturesToFile(Dictionary currentMethodSignatures) { string contents = JsonConvert.SerializeObject((object)currentMethodSignatures, (Formatting)1); File.WriteAllText(pathFileMethodSignatures, contents, Encoding.Unicode); } private void CompareOldSignaturesAgainstNew(CheckResult checkResult, Dictionary currentMethodSignatures) { Dictionary dictionary = previousMethodSignatures; if (dictionary == null || !dictionary.Any() || currentMethodSignatures == null || !currentMethodSignatures.Any()) { return; } foreach (KeyValuePair currentMethodSignature in currentMethodSignatures) { if (previousMethodSignatures.TryGetValue(currentMethodSignature.Key, out var value)) { MethodSignature value2 = currentMethodSignature.Value; if (value != null && !value.Equals(value2, out var errorDetail)) { checkResult.Result = CheckResult.SignatureCheckResult.SignaturesDifferent; checkResult.AddErrorMessage(errorDetail + " | " + currentMethodSignature.Key); } } } if (checkResult.Result != CheckResult.SignatureCheckResult.SignaturesDifferent) { checkResult.SetValues(CheckResult.SignatureCheckResult.SignaturesOk, "Method signature check ok."); } } } public class CheckResult { public enum SignatureCheckResult { Unchecked, Started, FileError, NoSignaturesAdded, NoPreviousSignatures, SignaturesDifferent, SignaturesOk } public static CheckResult UnknownError = new CheckResult(SignatureCheckResult.Unchecked, "Unknown Error due to exception"); private string _result; public SignatureCheckResult Result { get; internal set; } public string ResultMessage { get { return _result; } private set { if (value == null) { _result = ""; } _result = value; } } internal CheckResult() { ResultMessage = ""; Result = SignatureCheckResult.Unchecked; } internal CheckResult(SignatureCheckResult result, string resultMessage) { ResultMessage = resultMessage; Result = result; } internal CheckResult SetValues(SignatureCheckResult result, string resultMessage) { ResultMessage = resultMessage; Result = result; return this; } internal void AddErrorMessage(string resultMessage) { if (string.IsNullOrEmpty(ResultMessage)) { ResultMessage = "Method signatures have changed since last successful check:"; } ResultMessage = ResultMessage + "\n" + resultMessage; } public void LogResultMessage(LogTier logLevel, bool onlyWhenNotOk, bool showInGame) { //IL_000e: Unknown result type (might be due to invalid IL or missing references) if (ShouldLogMessage(onlyWhenNotOk)) { TimeLogger.Logger.LogTime(logLevel, ResultMessage, (LogCategories)4194304, showInGame); } } private bool ShouldLogMessage(bool onlyWhenNotOk) { if (string.IsNullOrEmpty(ResultMessage)) { if (Result == SignatureCheckResult.Unchecked) { return false; } ResultMessage = "Result message was empty for a result in which this is not allowed."; return true; } if (onlyWhenNotOk && (Result == SignatureCheckResult.SignaturesOk || Result == SignatureCheckResult.NoPreviousSignatures)) { return false; } return true; } } } namespace Damntry.UtilsBepInEx.HarmonyPatching.Helpers { public class ArgumentHelper { public T Value { get; set; } public CodeInstruction LoadFieldArgHelper_IL { get; private set; } public CodeInstruction GetterValue_IL { get; private set; } public CodeInstruction SetterValue_IL { get; private set; } public ArgumentHelper(Type declaringClassType, string instanceName, T argumentValue) { //IL_0037: Unknown result type (might be due to invalid IL or missing references) //IL_0041: Expected O, but got Unknown //IL_007a: Unknown result type (might be due to invalid IL or missing references) //IL_0084: Expected O, but got Unknown //IL_0090: Unknown result type (might be due to invalid IL or missing references) //IL_009a: Expected O, but got Unknown Value = argumentValue; FieldInfo fieldInfo = AccessTools.Field(declaringClassType, instanceName); if (fieldInfo == null) { throw new InvalidOperationException($"No field was found in the type {declaringClassType} with name {instanceName}."); } LoadFieldArgHelper_IL = new CodeInstruction(OpCodes.Ldsfld, (object)fieldInfo); if (!fieldInfo.IsStatic) { LoadFieldArgHelper_IL.opcode = OpCodes.Ldfld; } PropertyInfo propertyInfo = AccessTools.Property(typeof(ArgumentHelper), "Value"); GetterValue_IL = new CodeInstruction(OpCodes.Callvirt, (object)propertyInfo.GetGetMethod()); SetterValue_IL = new CodeInstruction(OpCodes.Callvirt, (object)propertyInfo.GetSetMethod()); } } } namespace Damntry.UtilsBepInEx.HarmonyPatching.Exceptions { public class TranspilerDefaultMsgException : Exception { private const string defaultErrorText = "Transpiler couldnt perform its changes due to unexpected source code changes."; public TranspilerDefaultMsgException() : base(composeErrorText()) { } public TranspilerDefaultMsgException(string errorDetail) : base(composeErrorText(errorDetail)) { } public TranspilerDefaultMsgException(string errorDetail, Exception inner) : base(composeErrorText(errorDetail), inner) { } private static string composeErrorText(string errorDetail = "") { return "Transpiler couldnt perform its changes due to unexpected source code changes.\n" + errorDetail; } } public class TypeNotFoundInAssemblyException : Exception { public TypeNotFoundInAssemblyException() { } public TypeNotFoundInAssemblyException(string errorDetail) : base(errorDetail) { } public TypeNotFoundInAssemblyException(string errorDetail, Exception inner) : base(errorDetail, inner) { } } } namespace Damntry.UtilsBepInEx.HarmonyPatching.AutoPatching { public static class Container where T : AutoPatchedInstanceBase { public static T Instance => AutoPatchContainer.GetInstance(); } internal static class AutoPatchContainer { private static Dictionary registeredInstances = new Dictionary(); internal static T GetInstance() where T : AutoPatchedInstanceBase { ThrowIfTypeInvalidOrNotRegistered(); return (T)registeredInstances[typeof(T)]; } internal static AutoPatchedInstanceBase GetAbstractInstance(Type patchType) { ThrowIfTypeInvalidOrNotRegistered(patchType); return registeredInstances[patchType]; } internal static IReadOnlyDictionary GetRegisteredAutoPatches() { return registeredInstances; } internal static void RegisterPatchClass(Type autoPatchType) { ThrowIfTypeInvalidOrAlreadyRegistered(autoPatchType); object obj = Activator.CreateInstance(autoPatchType); registeredInstances.Add(autoPatchType, (AutoPatchedInstanceBase)obj); } internal static void UnregisterPatchClass(Type autoPatchClass) { ThrowIfTypeInvalidOrNotRegistered(autoPatchClass); registeredInstances.Remove(autoPatchClass); } private static void ThrowIfTypeInvalidOrAlreadyRegistered() { ThrowIfTypeInvalidOrAlreadyRegistered(typeof(T)); } private static void ThrowIfTypeInvalidOrAlreadyRegistered(Type patchClassType) { if (patchClassType.IsAbstract || !patchClassType.IsSubclassOf(typeof(AutoPatchedInstanceBase))) { throw new InvalidOperationException("The type " + patchClassType.FullName + " must be a non abstract subclass of AutoPatchedInstanceBase."); } if (registeredInstances.ContainsKey(patchClassType)) { throw new InvalidOperationException("The type " + patchClassType.FullName + " has already been registered."); } } private static void ThrowIfTypeInvalidOrNotRegistered() { ThrowIfTypeInvalidOrNotRegistered(typeof(T)); } private static void ThrowIfTypeInvalidOrNotRegistered(Type patchClassType) { if (patchClassType.IsAbstract || !patchClassType.IsSubclassOf(typeof(AutoPatchedInstanceBase))) { throw new InvalidOperationException("The type " + patchClassType.FullName + " must be a non abstract subclass of AutoPatchedInstanceBase."); } if (!registeredInstances.ContainsKey(patchClassType)) { throw new InvalidOperationException("The type " + patchClassType.FullName + " is not registered."); } } } public static class AutoPatcher { public enum AutoPatchResult { none, error, disabled, success } public static bool RegisterAllAutoPatchContainers() { List list = GetAutoPatchClasses(Assembly.GetCallingAssembly()).ToList(); if (list.Count == 0) { return false; } foreach (Type item in list) { AutoPatchContainer.RegisterPatchClass(item); } return true; } public static bool StartAutoPatcher() { int num = 0; int num2 = 0; int num3 = 0; foreach (KeyValuePair registeredAutoPatch in AutoPatchContainer.GetRegisteredAutoPatches()) { AutoPatchResult autoPatchResult = Patch(registeredAutoPatch.Key, registeredAutoPatch.Value); if (autoPatchResult == AutoPatchResult.disabled) { num2++; continue; } num3++; if (autoPatchResult == AutoPatchResult.error) { num++; } } if (num == 0) { TimeLogger.Logger.LogTimeInfo($"All {num3} auto-patches applied successfully.", (LogCategories)8); return true; } TimeLogger.Logger.LogTimeFatal($"Oh oh, {num} out of {num3} auto-patches failed. " + "Check above for errors.", (LogCategories)8); return false; } private static AutoPatchResult Patch(Type autoPatchType, AutoPatchedInstanceBase autoPatchInstance) { AutoPatchResult autoPatchResult = AutoPatchResult.none; try { if (autoPatchInstance == null) { throw new InvalidOperationException("Auto patch received a null instance from the registered type " + autoPatchType.FullName + "."); } if (autoPatchInstance.IsAutoPatchEnabled) { autoPatchInstance.PatchInstance(); autoPatchResult = AutoPatchResult.success; } else { autoPatchResult = AutoPatchResult.disabled; } } catch (Exception ex) { autoPatchResult = AutoPatchResult.error; TimeLogger.Logger.LogTimeExceptionWithMessage("Error auto patching class " + autoPatchType.FullName + ".", ex, (LogCategories)8); if (autoPatchInstance != null) { TimeLogger.Logger.LogTimeError(autoPatchInstance.ErrorMessageOnAutoPatchFail, (LogCategories)2097152); if (autoPatchInstance.IsRollbackOnAutoPatchFail && autoPatchInstance.GetPatchedCount() > 0) { autoPatchInstance.UnpatchInstance(); } } } finally { try { autoPatchInstance?.RaiseEventOnAutoPatchFinish(autoPatchResult); } catch (Exception ex2) { TimeLogger.Logger.LogTimeExceptionWithMessage("Error calling RaiseEventOnAutoPatchFinish:", ex2, (LogCategories)2097152); } } return autoPatchResult; } public static bool IsPatchActiveFromResult(AutoPatchResult autoPatchResult) { switch (autoPatchResult) { case AutoPatchResult.success: return true; case AutoPatchResult.error: case AutoPatchResult.disabled: return false; default: throw new NotImplementedException($"The switch case {autoPatchResult} is not implemented or should not have happened."); } } private static IEnumerable GetAutoPatchClasses(Assembly assembly) { return from type in assembly.GetTypes() where type.IsClass && !type.IsAbstract && type.IsSubclassOf(typeof(AutoPatchedInstanceBase)) select type; } public static string[] GetHarmonyInstanceIdsForAttribute(Type[] autoPatchTypes) { return autoPatchTypes.Select((Type autoPatchType) => AutoPatchContainer.GetAbstractInstance(autoPatchType).harmonyPatchInstance.Value.GetHarmonyInstanceId()).ToArray(); } } } namespace Damntry.UtilsBepInEx.HarmonyPatching.AutoPatching.Interfaces { public interface IAutoPatchSupport : ISelfPatch { bool IsAutoPatchEnabled { get; } bool IsRollbackOnAutoPatchFail { get; } string ErrorMessageOnAutoPatchFail { get; } void RaiseEventOnAutoPatchFinish(AutoPatcher.AutoPatchResult patchResult); } public interface IConfigPatchDependence { void SetSettingPatchDependence(ConfigEntry configEntry); } public interface ISelfPatch { bool IsPatchActive { get; } event Action OnPatchFinished; List PatchInstance(); void UnpatchInstance(); } } namespace Damntry.UtilsBepInEx.HarmonyPatching.AutoPatching.BaseClasses { public abstract class AutoPatchedInstanceBase : IAutoPatchSupport, ISelfPatch, IConfigPatchDependence { internal readonly Lazy harmonyPatchInstance; public abstract bool IsAutoPatchEnabled { get; } public abstract bool IsRollbackOnAutoPatchFail { get; } public abstract bool IsPatchActive { get; protected set; } public abstract string ErrorMessageOnAutoPatchFail { get; protected set; } public abstract event Action OnPatchFinished; internal AutoPatchedInstanceBase() { harmonyPatchInstance = new Lazy(() => new HarmonyInstancePatcher(GetType())); } public List PatchInstance() { return harmonyPatchInstance.Value.PatchInstance(); } public void UnpatchInstance() { harmonyPatchInstance.Value.UnpatchInstance(); } public int GetPatchedCount() { return harmonyPatchInstance.Value.GetPatchedCount(); } public abstract void RaiseEventOnAutoPatchFinish(AutoPatcher.AutoPatchResult autoPatchResult); public void SetSettingPatchDependence(ConfigEntry configEntry) { OnPatchFinished += delegate(bool IsPatchActive) { configEntry.SetConfigAttribute(ConfigurationManagerAttributes.ConfigAttributes.Browsable, IsPatchActive); }; } } } namespace Damntry.UtilsBepInEx.HarmonyPatching.AutoPatching.BaseClasses.Inheritable { public abstract class FullyAutoPatchedInstance : AutoPatchedInstanceBase { public override bool IsRollbackOnAutoPatchFail => true; public override bool IsPatchActive { get; protected set; } public override event Action OnPatchFinished; public void AutoPatchResultEvent(AutoPatcher.AutoPatchResult autoPatchResult) { IsPatchActive = AutoPatcher.IsPatchActiveFromResult(autoPatchResult); } public virtual void OnPatchFinishedVirtual(bool IsActive) { } public override void RaiseEventOnAutoPatchFinish(AutoPatcher.AutoPatchResult autoPatchResult) { AutoPatchResultEvent(autoPatchResult); if (OnPatchFinished != null) { OnPatchFinished(IsPatchActive); } OnPatchFinishedVirtual(IsPatchActive); } } public abstract class HybridPatchedInstance : AutoPatchedInstanceBase { public override void RaiseEventOnAutoPatchFinish(AutoPatcher.AutoPatchResult autoPatchResult) { } public List PatchClassByType(Type classType) { return harmonyPatchInstance.Value.PatchClassByType(classType); } public void UnpatchMethod(Type classType, string methodName) { harmonyPatchInstance.Value.UnpatchMethod(classType, methodName); } } } namespace Damntry.UtilsBepInEx.HarmonyPatching.AutoPatching.Attributes { [AttributeUsage(AttributeTargets.Class, AllowMultiple = false)] public class AutoPatchIgnoreClass : AutoPatchAttribute { } [AttributeUsage(AttributeTargets.Class, AllowMultiple = false)] public class AutoPatchIgnoreClassAndNested : AutoPatchAttribute { } public abstract class AutoPatchAttribute : Attribute { } [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)] public class HarmonyBeforeInstance : HarmonyPatch { private HarmonyBeforeInstance() { } public HarmonyBeforeInstance(params Type[] beforeInstances) { ((HarmonyAttribute)this).info.before = AutoPatcher.GetHarmonyInstanceIdsForAttribute(beforeInstances); } } [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)] public class HarmonyAfterInstance : HarmonyPatch { private HarmonyAfterInstance() { } public HarmonyAfterInstance(params Type[] afterInstances) { ((HarmonyAttribute)this).info.after = AutoPatcher.GetHarmonyInstanceIdsForAttribute(afterInstances); } } } namespace Damntry.UtilsBepInEx.HarmonyPatching.Attributes { [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method | AttributeTargets.Delegate, AllowMultiple = true)] public class HarmonyPatchStringTypes : HarmonyAttribute { public HarmonyPatchStringTypes(string fullTypeName, string methodName) { SetMethodInfo(fullTypeName, methodName, (Type[])null); } public HarmonyPatchStringTypes(string fullTypeName, string methodName, Type[] argumentTypes) { SetMethodInfo(fullTypeName, methodName, argumentTypes); } private void SetMethodInfo(string fullTypeName, string methodName, string[] argumentFullTypeNames) { Type[] typesFromLoadedAssemblies = AssemblyUtils.GetTypesFromLoadedAssemblies(true, argumentFullTypeNames); SetMethodInfo(fullTypeName, methodName, typesFromLoadedAssemblies); } private void SetMethodInfo(string fullTypeName, string methodName, Type[] argumentTypes) { Type typeFromLoadedAssemblies = AssemblyUtils.GetTypeFromLoadedAssemblies(fullTypeName, true); base.info.declaringType = typeFromLoadedAssemblies; base.info.methodName = methodName; base.info.argumentTypes = argumentTypes; } } } namespace Damntry.UtilsBepInEx.Configuration { public static class BepinexConfigExtensions { public static T GetMaxValue(this AcceptableValueBase acceptableVal) where T : IComparable, IEquatable { if (acceptableVal is AcceptableValueRange) { return ((AcceptableValueRange)(object)acceptableVal).GetMaxNumericValue(); } if (acceptableVal is AcceptableValueList) { return ((AcceptableValueList)(object)acceptableVal).GetMaxNumericValue(); } return default(T); } public static T GetMaxNumericValue(this AcceptableValueRange acceptableVal) where T : IComparable { return acceptableVal.MaxValue; } public static T GetMaxNumericValue(this AcceptableValueList acceptableVal) where T : IEquatable, IComparable { if (!NumberExtensionMethods.IsNumeric(typeof(T))) { throw new InvalidOperationException("Only numeric types are allowed, but received a " + typeof(T).FullName); } return acceptableVal.AcceptableValues.OrderBy((T x) => x).Last(); } } } namespace Damntry.UtilsBepInEx.Configuration.ConfigurationManager { public enum MultiplayerModInstallSide { Unspecified, Any, HostSideOnly, HostAndOptionalClient, HostAndSoftClient, HostAndHardClient } public class ConfigManagerController { internal const string ConfigMngFullTypeName = "ConfigurationManager.ConfigurationManager"; private const string InstallSideInitialDescription = "[Multiplayer requirements] This setting requires that the mod "; private ConfigFile configFile; private string currentSectionName; private int currentSectionOrder; private int currentConfigOrder; public ConfigManagerController(ConfigFile configFile) { this.configFile = configFile ?? throw new ArgumentNullException("configFile"); currentSectionOrder = 0; currentConfigOrder = int.MaxValue; if (AssemblyUtils.GetTypeFromLoadedAssemblies("ConfigurationManager.ConfigurationManager", true) != null) { ConfigurationManagerPatch.PatchSelf(); } ConfigEntryBasePatch.PatchSelf(); } private void SetSection(string sectionName, bool skipSectionOrder = false) { if (!(sectionName == currentSectionName)) { if (!skipSectionOrder) { currentSectionOrder++; } currentConfigOrder = int.MaxValue; currentSectionName = sectionName; } } public static bool RefreshGUI() { if (ConfigurationManagerPatch.ConfigMngInstance != null) { string text = "BuildSettingList"; try { ReflectionHelper.CallMethod(ConfigurationManagerPatch.ConfigMngInstance, text, (object[])null); return true; } catch (Exception ex) { TimeLogger.Logger.LogTimeExceptionWithMessage("Error when trying to call the method to refresh ConfigurationManager GUI.", ex, (LogCategories)64); } } return false; } public ConfigEntry AddSectionNote(string sectionText, string description = null, IConfigPatchDependence patchInstanceDependency = null, bool hidden = false, bool isAdvanced = false) { string text = "\u200b"; ConfigEntryBasePatch.AddNote(text); return AddConfig(sectionText, text, defaultValue: true, description, patchInstanceDependency, MultiplayerModInstallSide.Unspecified, hidden, disabled: true, null, showRangeAsPercent: false, hideDefaultButton: true, isAdvanced, skipSectionIncrease: true); } public ConfigEntry AddQuasiNote(string sectionName, string key, string textboxMessage, string description = null, IConfigPatchDependence patchInstanceDependency = null, bool hidden = false, bool isAdvanced = false) { return AddQuasiNote(sectionName, key, textboxMessage, description, patchInstanceDependency, hidden, isAdvanced, skipSectionIncrease: false); } public ConfigEntry AddGUIHiddenNote(string sectionName, string key, string description = null, IConfigPatchDependence patchInstanceDependency = null, bool isAdvanced = false) { return AddQuasiNote(sectionName, key, null, description, patchInstanceDependency, hidden: true, isAdvanced, skipSectionIncrease: true); } private ConfigEntry AddQuasiNote(string sectionName, string key, string textboxMessage, string description = null, IConfigPatchDependence patchInstanceDependency = null, bool hidden = false, bool isAdvanced = false, bool skipSectionIncrease = false) { if (textboxMessage == null) { textboxMessage = ""; } ConfigEntryBasePatch.AddNote(key); return AddConfig(sectionName, key, textboxMessage, description, patchInstanceDependency, MultiplayerModInstallSide.Unspecified, hidden, disabled: true, null, showRangeAsPercent: false, hideDefaultButton: true, isAdvanced, skipSectionIncrease); } public ConfigEntry AddConfig(string sectionName, string key, T defaultValue, string description = null, IConfigPatchDependence patchInstanceDependency = null, MultiplayerModInstallSide modInstallSide = MultiplayerModInstallSide.Unspecified, bool hidden = false, bool disabled = false, bool hideDefaultButton = false, bool isAdvanced = false) { return AddConfig(sectionName, key, defaultValue, description, patchInstanceDependency, modInstallSide, hidden, disabled, null, showRangeAsPercent: false, hideDefaultButton, isAdvanced); } public ConfigEntry AddConfigWithAcceptableValues(string sectionName, string key, T defaultValue, string description = null, IConfigPatchDependence patchInstanceDependency = null, MultiplayerModInstallSide modInstallSide = MultiplayerModInstallSide.Unspecified, bool hidden = false, bool disabled = false, AcceptableValueList acceptableValueList = null, bool hideDefaultButton = false, bool isAdvanced = false) where T : IEquatable { return AddConfig(sectionName, key, defaultValue, description, patchInstanceDependency, modInstallSide, hidden, disabled, (AcceptableValueBase)(object)acceptableValueList, showRangeAsPercent: false, hideDefaultButton, isAdvanced); } public ConfigEntry AddConfigWithAcceptableValues(string sectionName, string key, T defaultValue, string description = null, IConfigPatchDependence patchInstanceDependency = null, MultiplayerModInstallSide modInstallSide = MultiplayerModInstallSide.Unspecified, bool hidden = false, bool disabled = false, AcceptableValueRange acceptableValueRange = null, bool showRangeAsPercent = false, bool hideDefaultButton = false, bool isAdvanced = false) where T : IComparable { return AddConfig(sectionName, key, defaultValue, description, patchInstanceDependency, modInstallSide, hidden, disabled, (AcceptableValueBase)(object)acceptableValueRange, showRangeAsPercent, hideDefaultButton, isAdvanced); } private ConfigEntry AddConfig(string sectionName, string key, T defaultValue, string description = null, IConfigPatchDependence patchInstanceDependency = null, MultiplayerModInstallSide modInstallSide = MultiplayerModInstallSide.Unspecified, bool hidden = false, bool disabled = false, AcceptableValueBase acceptableVal = null, bool showRangeAsPercent = false, bool hideDefaultButton = false, bool isAdvanced = false, bool skipSectionIncrease = false) { SetSection(sectionName, skipSectionIncrease); return AddConfig(key, defaultValue, description, patchInstanceDependency, modInstallSide, hidden, disabled, acceptableVal, showRangeAsPercent, hideDefaultButton, isAdvanced); } private ConfigEntry AddConfig(string key, T defaultValue, string description = null, IConfigPatchDependence patchInstanceDependency = null, MultiplayerModInstallSide modInstallSide = MultiplayerModInstallSide.Unspecified, bool hidden = false, bool disabled = false, AcceptableValueBase acceptableVal = null, bool showRangeAsPercent = false, bool hideDefaultButton = false, bool isAdvanced = false) { //IL_00df: Unknown result type (might be due to invalid IL or missing references) //IL_00e5: Expected O, but got Unknown if (description == null) { description = ""; } if (patchInstanceDependency != null) { hidden = true; } if (modInstallSide != 0) { string descriptionFromInstallSide = GetDescriptionFromInstallSide(modInstallSide); if (!Utility.IsNullOrWhiteSpace(description)) { description += "\n\n"; } description += descriptionFromInstallSide; } ConfigDescription val = new ConfigDescription(description, acceptableVal, new object[1] { new ConfigurationManagerAttributes { IsAdvanced = isAdvanced, Order = currentConfigOrder--, Browsable = !hidden, DefaultValue = defaultValue, ShowRangeAsPercent = showRangeAsPercent, ReadOnly = disabled, HideDefaultButton = hideDefaultButton, Category = currentSectionOrder.ToString("D2") + ". " + currentSectionName } }); ConfigEntry val2 = configFile.Bind(currentSectionName, key, defaultValue, val); patchInstanceDependency?.SetSettingPatchDependence(val2); return val2; } public bool Remove(string section, string key) { //IL_0008: Unknown result type (might be due to invalid IL or missing references) //IL_0012: Expected O, but got Unknown return configFile.Remove(new ConfigDefinition(section, key)); } public bool Remove(ConfigDefinition key) { return configFile.Remove(key); } public void ClearAllConfigs() { configFile.Clear(); } public int Count() { return configFile.Count; } private string GetDescriptionFromInstallSide(MultiplayerModInstallSide modInstallSide) { return "[Multiplayer requirements] This setting requires that the mod " + modInstallSide switch { MultiplayerModInstallSide.Any => "is installed for the player/s that wants its functionality, hosting or not.", MultiplayerModInstallSide.HostSideOnly => "is installed in the host. It is not needed in the client.", MultiplayerModInstallSide.HostAndOptionalClient => "is installed in the host. Clients can also install it.", MultiplayerModInstallSide.HostAndSoftClient => "is installed in the host, and should be installed in the clients.", MultiplayerModInstallSide.HostAndHardClient => "is installed in both the host and the clients.", _ => "", }; } public bool SetConfigAttribute(string section, string key, ConfigurationManagerAttributes.ConfigAttributes configAttribute, T2 attrValue) { return GetConfigEntry(section, key).SetConfigAttribute(configAttribute, attrValue); } public R GetConfigAttribute(string section, string key, ConfigurationManagerAttributes.ConfigAttributes configAttribute) { return GetConfigEntry(section, key).GetConfigAttribute(configAttribute); } private ConfigEntry GetConfigEntry(string section, string key) { //IL_0008: Unknown result type (might be due to invalid IL or missing references) //IL_0014: Expected O, but got Unknown ConfigEntry val = default(ConfigEntry); if (!configFile.TryGetEntry(new ConfigDefinition(section, key), ref val) || ((ConfigEntryBase)val).Description == null || ((ConfigEntryBase)val).Description.Tags == null) { throw new InvalidOperationException("The config entry with section \"" + section + "\" and key \"" + key + "\" could not be found."); } return val; } } public static class ConfigManagerExtension { public static bool SetConfigAttribute(this ConfigEntry configEntry, ConfigurationManagerAttributes.ConfigAttributes configAttribute, T2 attrValue) { ConfigurationManagerAttributes configManagerAttribInstance; FieldInfo configAttributeFieldInfo = GetConfigAttributeFieldInfo(configEntry, configAttribute, out configManagerAttribInstance); if (configAttributeFieldInfo == null) { return false; } configAttributeFieldInfo.SetValue(configManagerAttribInstance, attrValue); return true; } public static R GetConfigAttribute(this ConfigEntry configEntry, ConfigurationManagerAttributes.ConfigAttributes configAttribute) { ConfigurationManagerAttributes configManagerAttribInstance; FieldInfo configAttributeFieldInfo = GetConfigAttributeFieldInfo(configEntry, configAttribute, out configManagerAttribInstance); if (configAttributeFieldInfo == null) { throw new InvalidOperationException("The attribute \"" + configAttribute.ToString() + "\" could not be found."); } object value = configAttributeFieldInfo.GetValue(configManagerAttribInstance); if (value != null && !(value is R)) { throw new InvalidOperationException("The attribute value is of type \"" + value.GetType().FullName + "\" and is not compatible with the type \"" + typeof(R).FullName + "\" passed by parameter."); } return (R)value; } private static FieldInfo GetConfigAttributeFieldInfo(ConfigEntry configEntry, ConfigurationManagerAttributes.ConfigAttributes configAttribute, out ConfigurationManagerAttributes configManagerAttribInstance) { configManagerAttribInstance = null; object[] tags = ((ConfigEntryBase)configEntry).Description.Tags; foreach (object obj in tags) { if (obj is ConfigurationManagerAttributes) { configManagerAttribInstance = (ConfigurationManagerAttributes)obj; break; } } if (configManagerAttribInstance == null) { return null; } FieldInfo fieldInfo = AccessTools.Field(typeof(ConfigurationManagerAttributes), configAttribute.ToString()); if (fieldInfo == null) { return null; } return fieldInfo; } } } namespace Damntry.UtilsBepInEx.Configuration.ConfigurationManager.SettingAttributes { public sealed class ConfigurationManagerAttributes { public enum ConfigAttributes { ShowRangeAsPercent, CustomDrawer, CustomHotkeyDrawer, Browsable, Category, DefaultValue, HideDefaultButton, HideSettingName, Description, DispName, Order, ReadOnly, IsAdvanced } public delegate void CustomHotkeyDrawerFunc(ConfigEntryBase setting, ref bool isCurrentlyAcceptingInput); public bool? ShowRangeAsPercent; public Action CustomDrawer; public CustomHotkeyDrawerFunc CustomHotkeyDrawer; public bool? Browsable; public string Category; public object DefaultValue; public bool? HideDefaultButton; public bool? HideSettingName; public string Description; public string DispName; public int? Order; public bool? ReadOnly; public bool? IsAdvanced; public Func ObjToStr; public Func StrToObj; } } namespace Damntry.UtilsBepInEx.Configuration.ConfigurationManager.Patch { internal class ConfigEntryBasePatch { private static readonly HashSet keyNotes; internal static Lazy Harmony { get; } static ConfigEntryBasePatch() { Harmony = new Lazy((Func)(() => new Harmony(typeof(ConfigEntryBasePatch).FullName))); keyNotes = new HashSet(); } internal static void PatchSelf() { try { Harmony.Value.PatchAll(typeof(ConfigEntryBasePatch)); } catch (Exception ex) { TimeLogger.Logger.LogTimeExceptionWithMessage("Error while trying to apply patch in type ConfigEntryBasePatch", ex, (LogCategories)64); } } public static void AddNote(string configKey) { keyNotes.Add(configKey); } [HarmonyPatch(typeof(ConfigEntryBase), "WriteDescription")] [HarmonyPrefix] public static bool WriteDescriptionPatch(ConfigEntryBase __instance, StreamWriter writer) { if (keyNotes.Contains(__instance.Definition.Key)) { if (!string.IsNullOrEmpty(__instance.Description.Description)) { writer.WriteLine("## " + __instance.Description.Description.Replace("\n", "\n## ")); } return false; } return true; } } [HarmonyPatch] public class ConfigurationManagerPatch { private static Lazy Harmony { get; } = new Lazy((Func)(() => new Harmony(typeof(ConfigurationManagerPatch).FullName))); internal static object ConfigMngInstance { get; private set; } internal static void PatchSelf() { try { Harmony.Value.PatchAll(typeof(ConfigurationManagerPatch)); } catch (Exception ex) { TimeLogger.Logger.LogTimeExceptionWithMessage("Error while trying to apply patch in type ConfigurationManagerPatch", ex, (LogCategories)64); } } [HarmonyPatchStringTypes("ConfigurationManager.ConfigurationManager", "Update")] [HarmonyPostfix] internal static void GetConfigManagerInstancePatch(object __instance) { if (__instance != null) { ConfigMngInstance = __instance; Harmony.Value.UnpatchSelf(); } } } }