Decompiled source of HolyDLL v1.1.1

holy.dll

Decompiled 19 hours ago
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.ExceptionServices;
using System.Runtime.Versioning;
using System.Security;
using System.Security.Permissions;
using System.Threading.Tasks;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using IL.RoR2;
using Microsoft.CodeAnalysis;
using Mono.Cecil;
using Mono.Cecil.Cil;
using Mono.Collections.Generic;
using MonoMod.Cil;
using MonoMod.RuntimeDetour;
using MonoMod.RuntimeDetour.HookGen;
using MonoMod.Utils;
using R2API;
using RoR2;
using UnityEngine;
using UnityEngine.Networking;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.Default | DebuggableAttribute.DebuggingModes.DisableOptimizations | DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints | DebuggableAttribute.DebuggingModes.EnableEditAndContinue)]
[assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")]
[assembly: AssemblyCompany("holy")]
[assembly: AssemblyConfiguration("Debug")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyInformationalVersion("1.0.0+3203c2dc68c5c9326450bc9f458d2bc2712a64fe")]
[assembly: AssemblyProduct("holy")]
[assembly: AssemblyTitle("holy")]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("1.0.0.0")]
[module: UnverifiableCode]
[module: RefSafetyRules(11)]
[module: UnverifiableCode]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)]
	internal sealed class RefSafetyRulesAttribute : Attribute
	{
		public readonly int Version;

		public RefSafetyRulesAttribute(int P_0)
		{
			Version = P_0;
		}
	}
}
namespace HolyHolyHoly
{
	[BepInPlugin("xyz.yekoc.Holy", "HolyHolyHOLY", "1.1.1")]
	[BepInDependency(/*Could not decode attribute arguments.*/)]
	public class HolyPlugin : BaseUnityPlugin
	{
		private sealed class ReferenceComparer : IEqualityComparer<CharacterBody>
		{
			public bool Equals(CharacterBody a, CharacterBody b)
			{
				return a == b;
			}

			public int GetHashCode(CharacterBody b)
			{
				return RuntimeHelpers.GetHashCode(b);
			}
		}

		[CompilerGenerated]
		private static class <>O
		{
			public static Manipulator <0>__Batchify;

			public static Manipulator <1>__Manipulate;
		}

		public const string GUID = "xyz.yekoc.Holy";

		private static HolyPlugin instance;

		private static MethodInfo recalculateStats;

		private static readonly Dictionary<CharacterBody, HolyData> bodies = new Dictionary<CharacterBody, HolyData>(new ReferenceComparer());

		private static readonly List<CharacterBody> dirty = new List<CharacterBody>();

		private ConfigEntry<bool> enabledConfig;

		private ConfigEntry<int> minimumBatch;

		private ConfigEntry<int> maxThreads;

		private ILHook recalcHook;

		private ILHook sprintHook;

		private MethodBase workerTarget;

		private Action<CharacterBody> worker;

		private bool ready;

		private bool serialFallback;

		private int windowBatches;

		private int windowBodies;

		private int windowBails;

		private readonly Dictionary<int, int> windowBailSites = new Dictionary<int, int>();

		private readonly HashSet<string> loggedErrors = new HashSet<string>();

		private void Awake()
		{
			//IL_0136: Unknown result type (might be due to invalid IL or missing references)
			//IL_013b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0141: Expected O, but got Unknown
			//IL_0157: Unknown result type (might be due to invalid IL or missing references)
			//IL_015c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0162: Expected O, but got Unknown
			//IL_019d: Unknown result type (might be due to invalid IL or missing references)
			//IL_01a7: Expected O, but got Unknown
			//IL_0192: Unknown result type (might be due to invalid IL or missing references)
			//IL_0197: Unknown result type (might be due to invalid IL or missing references)
			//IL_019d: Expected O, but got Unknown
			instance = this;
			enabledConfig = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "Enabled", true, "Recalculate character stats on worker threads. Turning this off makes the mod do nothing.");
			minimumBatch = ((BaseUnityPlugin)this).Config.Bind<int>("General", "Minimum Batch Size", 4, "Batches smaller than this are recalculated on the main thread, where thread overhead would outweigh the gain.");
			maxThreads = ((BaseUnityPlugin)this).Config.Bind<int>("General", "Max Threads", 0, "Upper limit on threads used per batch, 0 uses every core.");
			ConfigEntry<bool> val = ((BaseUnityPlugin)this).Config.Bind<bool>("Compatibility", "Defer Foreign Calls", true, "Let other mods' RecalculateStats IL hooks/Harmony patches that touch main-thread-only code run right after the parallel part. When off, bodies reaching such code are recalculated on the main thread instead (exact, but slower).");
			ConfigEntry<bool> val2 = ((BaseUnityPlugin)this).Config.Bind<bool>("Debug", "Log Analysis", false, "Log the verdict for every call inside RecalculateStats.");
			if (!enabledConfig.Value)
			{
				return;
			}
			if (!HookIntrospection.Available)
			{
				((BaseUnityPlugin)this).Logger.LogError((object)"Unrecognized MonoMod version, can't verify other mods' hooks. Staying disabled.");
				return;
			}
			recalculateStats = typeof(CharacterBody).GetMethod("RecalculateStats", BindingFlags.Instance | BindingFlags.Public);
			RsapiCompat.Init(((BaseUnityPlugin)this).Logger);
			RecalcPatcher.Init(((BaseUnityPlugin)this).Logger);
			RecalcPatcher.DeferForeignCalls = val.Value;
			RecalcPatcher.LogEverything = val2.Value;
			object obj = <>O.<0>__Batchify;
			if (obj == null)
			{
				Manipulator val3 = Batchify;
				<>O.<0>__Batchify = val3;
				obj = (object)val3;
			}
			CharacterBody.FixedUpdate += (Manipulator)obj;
			object obj2 = <>O.<0>__Batchify;
			if (obj2 == null)
			{
				Manipulator val4 = Batchify;
				<>O.<0>__Batchify = val4;
				obj2 = (object)val4;
			}
			CharacterMaster.OnBodyStart += (Manipulator)obj2;
			MethodInfo? setMethod = typeof(CharacterBody).GetProperty("isSprinting").GetSetMethod();
			object obj3 = <>O.<0>__Batchify;
			if (obj3 == null)
			{
				Manipulator val5 = Batchify;
				<>O.<0>__Batchify = val5;
				obj3 = (object)val5;
			}
			sprintHook = new ILHook((MethodBase)setMethod, (Manipulator)obj3);
			RoR2Application.onLoad = (Action)Delegate.Combine(RoR2Application.onLoad, new Action(InstallRecalculateHook));
			Run.onRunStartGlobal += delegate
			{
				Refresh();
			};
			Stage.onStageStartGlobal += delegate
			{
				bodies.Clear();
				dirty.Clear();
			};
			((MonoBehaviour)this).StartCoroutine(BatchLoop());
		}

		private void OnDestroy()
		{
			//IL_0011: Unknown result type (might be due to invalid IL or missing references)
			//IL_0016: Unknown result type (might be due to invalid IL or missing references)
			//IL_001c: Expected O, but got Unknown
			//IL_0032: Unknown result type (might be due to invalid IL or missing references)
			//IL_0037: Unknown result type (might be due to invalid IL or missing references)
			//IL_003d: Expected O, but got Unknown
			object obj = <>O.<0>__Batchify;
			if (obj == null)
			{
				Manipulator val = Batchify;
				<>O.<0>__Batchify = val;
				obj = (object)val;
			}
			CharacterBody.FixedUpdate -= (Manipulator)obj;
			object obj2 = <>O.<0>__Batchify;
			if (obj2 == null)
			{
				Manipulator val2 = Batchify;
				<>O.<0>__Batchify = val2;
				obj2 = (object)val2;
			}
			CharacterMaster.OnBodyStart -= (Manipulator)obj2;
			ILHook obj3 = sprintHook;
			if (obj3 != null)
			{
				obj3.Dispose();
			}
			ILHook obj4 = recalcHook;
			if (obj4 != null)
			{
				obj4.Dispose();
			}
			RsapiCompat.Unload();
			ready = false;
		}

		private void InstallRecalculateHook()
		{
			//IL_0003: Unknown result type (might be due to invalid IL or missing references)
			//IL_002a: Unknown result type (might be due to invalid IL or missing references)
			//IL_002b: Unknown result type (might be due to invalid IL or missing references)
			//IL_004f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0059: Expected O, but got Unknown
			//IL_0042: Unknown result type (might be due to invalid IL or missing references)
			//IL_0047: Unknown result type (might be due to invalid IL or missing references)
			//IL_004d: Expected O, but got Unknown
			ILHookConfig val = new ILHookConfig
			{
				ID = "xyz.yekoc.Holy",
				After = new string[1] { "*" }
			};
			MethodInfo methodInfo = recalculateStats;
			object obj = <>O.<1>__Manipulate;
			if (obj == null)
			{
				Manipulator val2 = RecalcPatcher.Manipulate;
				<>O.<1>__Manipulate = val2;
				obj = (object)val2;
			}
			recalcHook = new ILHook((MethodBase)methodInfo, (Manipulator)obj, ref val);
			UpdateWorker();
		}

		private void Refresh()
		{
			if (recalcHook != null)
			{
				serialFallback = false;
				windowBatches = (windowBodies = (windowBails = 0));
				windowBailSites.Clear();
				RecalcPatcher.BeginSession();
				recalcHook.Undo();
				recalcHook.Apply();
				UpdateWorker();
				((BaseUnityPlugin)this).Logger.LogInfo((object)(ready ? $"RecalculateStats ready for parallel execution ({RecalcPatcher.DeferredSites} deferred calls, {RecalcPatcher.BailSites} main-thread fallbacks)." : "RecalculateStats will stay on the main thread this run, see earlier messages."));
			}
		}

		private void UpdateWorker()
		{
			MethodBase iLTarget = HookIntrospection.GetILTarget(recalcHook);
			if (iLTarget != workerTarget)
			{
				workerTarget = iLTarget;
				worker = null;
				try
				{
					if (iLTarget is DynamicMethod dynamicMethod)
					{
						worker = (Action<CharacterBody>)dynamicMethod.CreateDelegate(typeof(Action<CharacterBody>));
					}
					else if (iLTarget is MethodInfo method)
					{
						worker = (Action<CharacterBody>)Delegate.CreateDelegate(typeof(Action<CharacterBody>), method);
					}
				}
				catch (Exception ex)
				{
					((BaseUnityPlugin)this).Logger.LogError((object)("Couldn't bind the patched RecalculateStats: " + ex));
				}
			}
			ready = worker != null && RecalcPatcher.Healthy;
		}

		private static void Batchify(ILContext il)
		{
			//IL_0002: Unknown result type (might be due to invalid IL or missing references)
			//IL_0008: Expected O, but got Unknown
			//IL_0026: Unknown result type (might be due to invalid IL or missing references)
			ILCursor val = new ILCursor(il);
			MethodInfo method = typeof(HolyPlugin).GetMethod("Enqueue");
			while (val.TryGotoNext(new Func<Instruction, bool>[1]
			{
				(Instruction x) => ILPatternMatchingExt.MatchCallOrCallvirt(x, (MethodBase)recalculateStats)
			}))
			{
				val.Next.OpCode = OpCodes.Call;
				val.Next.Operand = il.Import((MethodBase)method);
				int index = val.Index;
				val.Index = index + 1;
			}
		}

		public static void Enqueue(CharacterBody body)
		{
			if ((Object)(object)instance == (Object)null || !instance.ready || ParallelRuntime.InParallel || !Object.op_Implicit((Object)(object)body) || !Object.op_Implicit((Object)(object)Run.instance))
			{
				body.RecalculateStats();
				return;
			}
			if (!bodies.TryGetValue(body, out var value))
			{
				value = (bodies[body] = new HolyData());
			}
			if (!value.queued)
			{
				value.queued = true;
				dirty.Add(body);
			}
			body.statsDirty = false;
		}

		private IEnumerator BatchLoop()
		{
			WaitForFixedUpdate wait = new WaitForFixedUpdate();
			while (true)
			{
				yield return wait;
				try
				{
					ProcessBatch();
				}
				catch (Exception ex)
				{
					Exception e = ex;
					((BaseUnityPlugin)this).Logger.LogError((object)e);
				}
			}
		}

		private void ProcessBatch()
		{
			//IL_0175: Unknown result type (might be due to invalid IL or missing references)
			if (dirty.Count == 0)
			{
				return;
			}
			List<CharacterBody> batch = new List<CharacterBody>(dirty.Count);
			foreach (CharacterBody item in dirty)
			{
				if (bodies.TryGetValue(item, out var value))
				{
					value.queued = false;
				}
				if (Object.op_Implicit((Object)(object)item))
				{
					batch.Add(item);
				}
				else
				{
					bodies.Remove(item);
				}
			}
			dirty.Clear();
			if (batch.Count == 0)
			{
				return;
			}
			UpdateWorker();
			RecalcPatcher.RunStaticConstructors();
			if (!ready || serialFallback || !Object.op_Implicit((Object)(object)Run.instance) || batch.Count < minimumBatch.Value)
			{
				foreach (CharacterBody item2 in batch)
				{
					RecalculateOnMainThread(item2, null);
				}
				return;
			}
			HolyData[] datas = new HolyData[batch.Count];
			ParallelRuntime.gravity = Physics.gravity.y;
			for (int i = 0; i < batch.Count; i++)
			{
				CharacterBody val = batch[i];
				HolyData holyData = (datas[i] = bodies[val]);
				holyData.needsSync = false;
				holyData.bailSite = 0;
				holyData.error = null;
				holyData.deferred.Clear();
				holyData.prevLevel = val.level;
				holyData.prevMaxHealth = val.maxHealth;
				holyData.prevMaxShield = val.maxShield;
				try
				{
					_ = val.master;
					RsapiCompat.Prepare(val, holyData);
				}
				catch (Exception error)
				{
					holyData.needsSync = true;
					holyData.error = error;
				}
			}
			Action<CharacterBody> run = worker;
			ParallelOptions parallelOptions = new ParallelOptions
			{
				MaxDegreeOfParallelism = ((maxThreads.Value > 0) ? maxThreads.Value : (-1))
			};
			Parallel.For(0, batch.Count, parallelOptions, delegate(int num3)
			{
				HolyData holyData3 = datas[num3];
				if (holyData3.needsSync)
				{
					return;
				}
				ParallelRuntime.Enter(holyData3);
				try
				{
					run(batch[num3]);
				}
				catch (BailException)
				{
					holyData3.needsSync = true;
				}
				catch (Exception error2)
				{
					holyData3.needsSync = true;
					holyData3.error = error2;
				}
				finally
				{
					ParallelRuntime.Exit();
				}
			});
			bool flag = HookIntrospection.DetourCount(recalculateStats) > 1;
			int num = 0;
			for (int num2 = 0; num2 < batch.Count; num2++)
			{
				CharacterBody val2 = batch[num2];
				HolyData holyData2 = datas[num2];
				if (holyData2.needsSync)
				{
					num++;
					windowBailSites.TryGetValue(holyData2.bailSite, out var value2);
					windowBailSites[holyData2.bailSite] = value2 + 1;
					RecalculateOnMainThread(val2, holyData2);
					continue;
				}
				try
				{
					ParallelRuntime.RunDeferred(val2, holyData2);
					if (flag)
					{
						ParallelRuntime.skipTarget = val2;
						try
						{
							val2.RecalculateStats();
						}
						finally
						{
							ParallelRuntime.skipTarget = null;
						}
					}
				}
				catch (Exception e)
				{
					LogOnce("Error while finishing a parallel stat recalculation", e);
				}
			}
			TrackFallbacks(batch.Count, num);
		}

		private void RecalculateOnMainThread(CharacterBody body, HolyData abandoned)
		{
			if (abandoned != null)
			{
				abandoned.deferred.Clear();
				body.level = abandoned.prevLevel;
				body.maxHealth = abandoned.prevMaxHealth;
				body.maxShield = abandoned.prevMaxShield;
			}
			try
			{
				body.RecalculateStats();
				if (abandoned?.error != null)
				{
					LogOnce("RecalculateStats failed on a worker thread but worked on the main thread", abandoned.error);
				}
			}
			catch (Exception e)
			{
				LogOnce("RecalculateStats threw", e);
			}
		}

		private void TrackFallbacks(int count, int bails)
		{
			windowBatches++;
			windowBodies += count;
			windowBails += bails;
			if (windowBatches < 32)
			{
				return;
			}
			if (windowBails * 2 > windowBodies)
			{
				serialFallback = true;
				IEnumerable<string> values = from p in windowBailSites.OrderByDescending((KeyValuePair<int, int> p) => p.Value).Take(3)
					select $"{RecalcPatcher.BailSiteName(p.Key)} ({p.Value})";
				((BaseUnityPlugin)this).Logger.LogWarning((object)string.Format("{0} of {1} recent stat recalculations needed the main thread, running them serially until the next run. Most common causes: {2}", windowBails, windowBodies, string.Join("; ", values)));
			}
			windowBatches = (windowBodies = (windowBails = 0));
			windowBailSites.Clear();
		}

		private void LogOnce(string context, Exception e)
		{
			string item = context + e.GetType().FullName + e.Message;
			if (loggedErrors.Count < 64 && loggedErrors.Add(item))
			{
				((BaseUnityPlugin)this).Logger.LogError((object)(context + ":\n" + e));
			}
		}
	}
	internal sealed class MethodComparer : IEqualityComparer<MethodBase>
	{
		public static readonly MethodComparer Instance = new MethodComparer();

		public bool Equals(MethodBase a, MethodBase b)
		{
			if ((object)a == b)
			{
				return true;
			}
			if (a == null || b == null)
			{
				return false;
			}
			try
			{
				return a.MethodHandle == b.MethodHandle;
			}
			catch (Exception)
			{
				return false;
			}
		}

		public int GetHashCode(MethodBase m)
		{
			try
			{
				return m.MethodHandle.GetHashCode();
			}
			catch (Exception)
			{
				return m.GetHashCode();
			}
		}
	}
	internal static class HookIntrospection
	{
		private sealed class HandleIndex
		{
			public int count = -1;

			public readonly Dictionary<RuntimeMethodHandle, object> keys = new Dictionary<RuntimeMethodHandle, object>();
		}

		private const BindingFlags Any = BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic;

		private static readonly FieldInfo detourMap = typeof(Detour).GetField("_DetourMap", BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);

		private static readonly FieldInfo ilHookMap = typeof(ILHook).GetField("_Map", BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);

		private static readonly FieldInfo endpointMap = typeof(HookEndpointManager).GetField("HookEndpointMap", BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);

		private static readonly PropertyInfo ilHookCtx = typeof(ILHook).GetProperty("_Ctx", BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);

		internal static bool InspectHarmony = true;

		private static readonly Dictionary<FieldInfo, HandleIndex> indices = new Dictionary<FieldInfo, HandleIndex>();

		public static bool Available => detourMap != null && ilHookMap != null && endpointMap != null && ilHookCtx != null;

		public static MethodBase GetILTarget(ILHook hook)
		{
			object value = ilHookCtx.GetValue(hook);
			if (value == null)
			{
				return null;
			}
			object? obj = value.GetType().GetField("Detour", BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic)?.GetValue(value);
			return ((Detour)(((obj is Detour) ? obj : null)?)).Target;
		}

		public static int DetourCount(MethodBase m)
		{
			return (Lookup(detourMap, m) is ICollection collection) ? collection.Count : 0;
		}

		public static string CheckHooks(MethodBase m, List<MethodBase> extras)
		{
			//IL_0067: Unknown result type (might be due to invalid IL or missing references)
			//IL_006e: Expected O, but got Unknown
			int num = DetourCount(m);
			int num2 = 0;
			object obj = Lookup(ilHookMap, m);
			if (obj != null && obj.GetType().GetField("Chain", BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic)?.GetValue(obj) is IEnumerable enumerable)
			{
				bool flag = false;
				foreach (ILHook item in enumerable)
				{
					ILHook val = item;
					if (val.IsApplied)
					{
						flag = true;
						Type type = ((Delegate)(object)val.Manipulator)?.Method?.DeclaringType;
						if (!(type?.FullName == "HarmonyLib.Public.Patching.ManagedMethodPatcher") && !(type?.Assembly == typeof(HookIntrospection).Assembly))
						{
							return "IL-hooked by " + Owner((Delegate)(object)val.Manipulator);
						}
					}
				}
				if (flag)
				{
					num2++;
				}
			}
			object obj2 = Lookup(endpointMap, m);
			if (obj2 != null && obj2.GetType().GetField("HookMap", BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic)?.GetValue(obj2) is IDictionary dictionary)
			{
				foreach (DictionaryEntry item2 in dictionary)
				{
					if (!(item2.Key is Delegate obj3) || !(item2.Value is IEnumerable enumerable2))
					{
						continue;
					}
					foreach (object item3 in enumerable2)
					{
						if (item3 is Hook)
						{
							num2++;
							Delegate[] invocationList = obj3.GetInvocationList();
							foreach (Delegate obj4 in invocationList)
							{
								extras.Add(obj4.Method);
							}
						}
					}
				}
			}
			if (num > num2)
			{
				return "detoured by a hook that can't be inspected";
			}
			Patches val2 = (InspectHarmony ? Harmony.GetPatchInfo(m) : null);
			if (val2 != null)
			{
				if (val2.Transpilers.Count > 0)
				{
					return "transpiled by " + string.Join(", ", val2.Transpilers.Select((Patch p) => p.owner));
				}
				extras.AddRange(((IEnumerable<Patch>)val2.Prefixes).Select((Func<Patch, MethodBase>)((Patch p) => p.PatchMethod)));
				extras.AddRange(((IEnumerable<Patch>)val2.Postfixes).Select((Func<Patch, MethodBase>)((Patch p) => p.PatchMethod)));
				extras.AddRange(((IEnumerable<Patch>)val2.Finalizers).Select((Func<Patch, MethodBase>)((Patch p) => p.PatchMethod)));
			}
			return null;
		}

		public static string Owner(Delegate d)
		{
			Type type = d?.Method?.DeclaringType;
			return (type == null) ? "unknown" : (type.Assembly.GetName().Name + " (" + type.FullName + ")");
		}

		public static void BeginSession()
		{
			indices.Clear();
		}

		private static object Lookup(FieldInfo mapField, MethodBase m)
		{
			if (!(mapField.GetValue(null) is IDictionary dictionary))
			{
				return null;
			}
			lock (dictionary)
			{
				if (dictionary.Contains(m))
				{
					return dictionary[m];
				}
				if (!indices.TryGetValue(mapField, out var value))
				{
					value = (indices[mapField] = new HandleIndex());
				}
				if (value.count != dictionary.Count)
				{
					value.keys.Clear();
					foreach (DictionaryEntry item in dictionary)
					{
						try
						{
							if (item.Key is MethodBase methodBase)
							{
								value.keys[methodBase.MethodHandle] = methodBase;
							}
						}
						catch (Exception)
						{
						}
					}
					value.count = dictionary.Count;
				}
				try
				{
					if (value.keys.TryGetValue(m.MethodHandle, out var value2))
					{
						return dictionary[value2];
					}
				}
				catch (Exception)
				{
				}
			}
			return null;
		}
	}
	internal sealed class NativeAnalyzer
	{
		public sealed class Verdict
		{
			public static readonly Verdict Safe = new Verdict(isUnsafe: false, null);

			public readonly bool Unsafe;

			public readonly string Reason;

			public Verdict(bool isUnsafe, string reason)
			{
				Unsafe = isUnsafe;
				Reason = reason;
			}

			public static Verdict No(string reason)
			{
				return new Verdict(isUnsafe: true, reason);
			}
		}

		private sealed class StaticWriteException : Exception
		{
			public StaticWriteException(FieldInfo f)
				: base("writes shared static " + f.DeclaringType?.Name + "." + f.Name)
			{
			}
		}

		public Func<MethodBase, Verdict> Override;

		public Func<MethodBase, IEnumerable<MethodBase>> ExtraTargets;

		public Action<Type> OnTypeTouched;

		public int MaxDepth = 48;

		private readonly Dictionary<string, Verdict> memo = new Dictionary<string, Verdict>();

		private readonly Dictionary<string, int> onStack = new Dictionary<string, int>();

		private static OpCode[] oneByte;

		private static OpCode[] twoByte;

		private static OpCode[] OneByte
		{
			get
			{
				if (oneByte == null)
				{
					BuildTables();
				}
				return oneByte;
			}
		}

		private static OpCode[] TwoByte
		{
			get
			{
				if (twoByte == null)
				{
					BuildTables();
				}
				return twoByte;
			}
		}

		public void Clear()
		{
			memo.Clear();
			onStack.Clear();
		}

		public Verdict Analyze(MethodBase method)
		{
			onStack.Clear();
			int low;
			return Visit(method, 0, out low);
		}

		private Verdict Visit(MethodBase m, int depth, out int low)
		{
			low = int.MaxValue;
			string key = Key(m);
			if (memo.TryGetValue(key, out var value))
			{
				return value;
			}
			if (onStack.TryGetValue(key, out var value2))
			{
				low = value2;
				return Verdict.Safe;
			}
			if (depth > MaxDepth)
			{
				Verdict verdict = Verdict.No("call chain too deep to verify at " + Describe(m));
				memo[key] = verdict;
				return verdict;
			}
			Verdict verdict2 = Classify(m);
			if (verdict2 != null)
			{
				memo[key] = verdict2;
				return verdict2;
			}
			onStack[key] = depth;
			Verdict verdict3 = Verdict.Safe;
			try
			{
				foreach (MethodBase item in Callees(m))
				{
					int low2;
					Verdict verdict4 = Visit(item, depth + 1, out low2);
					if (low2 < low)
					{
						low = low2;
					}
					if (verdict4.Unsafe)
					{
						verdict3 = Verdict.No(Describe(m) + " -> " + verdict4.Reason);
						break;
					}
				}
				if (!verdict3.Unsafe && ExtraTargets != null)
				{
					foreach (MethodBase item2 in ExtraTargets(m))
					{
						if (!(item2 == null))
						{
							int low3;
							Verdict verdict5 = Visit(item2, depth + 1, out low3);
							if (low3 < low)
							{
								low = low3;
							}
							if (verdict5.Unsafe)
							{
								verdict3 = Verdict.No(Describe(m) + " => " + verdict5.Reason);
								break;
							}
						}
					}
				}
			}
			catch (StaticWriteException ex)
			{
				verdict3 = Verdict.No(Describe(m) + " -> " + ex.Message);
			}
			catch (Exception ex2)
			{
				verdict3 = Verdict.No("could not analyze " + Describe(m) + " (" + ex2.GetType().Name + ": " + ex2.Message + ")");
			}
			onStack.Remove(key);
			if (low >= depth)
			{
				low = int.MaxValue;
			}
			if (verdict3.Unsafe || low == int.MaxValue)
			{
				memo[key] = verdict3;
			}
			return verdict3;
		}

		private Verdict Classify(MethodBase m)
		{
			Verdict verdict = Override?.Invoke(m);
			if (verdict != null)
			{
				return verdict;
			}
			Type declaringType = m.DeclaringType;
			string asm = declaringType?.Assembly.GetName().Name ?? "";
			string type = declaringType?.FullName ?? "";
			if (IsWhitelisted(type, m.Name))
			{
				return Verdict.Safe;
			}
			bool flag = IsBcl(asm);
			MethodImplAttributes methodImplementationFlags = m.MethodImplementationFlags;
			if ((methodImplementationFlags & MethodImplAttributes.InternalCall) != MethodImplAttributes.IL || (m.Attributes & MethodAttributes.PinvokeImpl) != MethodAttributes.PrivateScope)
			{
				return flag ? Verdict.Safe : Verdict.No("native " + Describe(m));
			}
			if ((methodImplementationFlags & MethodImplAttributes.CodeTypeMask) == MethodImplAttributes.CodeTypeMask)
			{
				if (!m.IsConstructor && m.Name != "Invoke")
				{
					return Verdict.Safe;
				}
				return m.IsConstructor ? Verdict.Safe : Verdict.No("opaque delegate call " + Describe(m));
			}
			if (flag)
			{
				return Verdict.Safe;
			}
			if (m.IsAbstract)
			{
				return Verdict.Safe;
			}
			return null;
		}

		public static bool IsBcl(string asm)
		{
			int result;
			switch (asm)
			{
			default:
				if (!asm.StartsWith("System.", StringComparison.Ordinal))
				{
					result = ((asm == "Microsoft.CSharp") ? 1 : 0);
					break;
				}
				goto case "mscorlib";
			case "mscorlib":
			case "netstandard":
			case "System":
				result = 1;
				break;
			}
			return (byte)result != 0;
		}

		public static bool IsUnity(string asm)
		{
			return asm.StartsWith("UnityEngine", StringComparison.Ordinal) || asm.StartsWith("Unity.", StringComparison.Ordinal);
		}

		private static bool IsWhitelisted(string type, string name)
		{
			switch (type)
			{
			case "UnityEngine.Mathf":
			case "UnityEngine.Vector2":
			case "UnityEngine.Vector3":
			case "UnityEngine.Vector4":
			case "UnityEngine.Vector2Int":
			case "UnityEngine.Vector3Int":
			case "UnityEngine.Quaternion":
			case "UnityEngine.Matrix4x4":
			case "UnityEngine.Color":
			case "UnityEngine.Color32":
			case "UnityEngine.Bounds":
			case "UnityEngine.Rect":
				return true;
			case "UnityEngine.Debug":
				return name.StartsWith("Log", StringComparison.Ordinal) || name.StartsWith("Assert", StringComparison.Ordinal);
			case "UnityEngine.Object":
			{
				int result;
				switch (name)
				{
				default:
					result = ((name == "GetHashCode") ? 1 : 0);
					break;
				case "op_Implicit":
				case "op_Equality":
				case "op_Inequality":
				case "CompareBaseObjects":
				case "IsNativeObjectAlive":
				case "GetCachedPtr":
				case "Equals":
					result = 1;
					break;
				}
				return (byte)result != 0;
			}
			case "RoR2.ItemDef":
				return name == "get_itemIndex";
			case "RoR2.EquipmentDef":
				return name == "get_equipmentIndex";
			case "RoR2.CharacterBody":
				return name == "get_master";
			default:
				return false;
			}
		}

		private IEnumerable<MethodBase> Callees(MethodBase m)
		{
			MethodBody body = m.GetMethodBody();
			if (body == null)
			{
				yield break;
			}
			byte[] il = body.GetILAsByteArray();
			if (il == null)
			{
				yield break;
			}
			Module module = m.Module;
			Type[] typeArgs = ((m.DeclaringType != null && m.DeclaringType.IsGenericType) ? m.DeclaringType.GetGenericArguments() : null);
			Type[] methodArgs = ((m.IsGenericMethod && !m.IsConstructor) ? m.GetGenericArguments() : null);
			int operandSize;
			for (int i = 0; i < il.Length; i += operandSize)
			{
				byte b = il[i++];
				OpCode op = ((b != 254) ? OneByte[b] : TwoByte[il[i++]]);
				operandSize = OperandSize(op.OperandType, il, i);
				if (op.OperandType == OperandType.InlineMethod)
				{
					int token = BitConverter.ToInt32(il, i);
					MethodBase target = null;
					try
					{
						target = module.ResolveMethod(token, typeArgs, methodArgs);
					}
					catch (Exception)
					{
					}
					if (target == null)
					{
						throw new InvalidOperationException("unresolvable method token 0x" + token.ToString("X8"));
					}
					if (target.DeclaringType != null)
					{
						OnTypeTouched?.Invoke(target.DeclaringType);
					}
					yield return target;
				}
				else
				{
					if (op.OperandType != OperandType.InlineField)
					{
						continue;
					}
					int token2 = BitConverter.ToInt32(il, i);
					FieldInfo field = null;
					try
					{
						field = module.ResolveField(token2, typeArgs, methodArgs);
					}
					catch (Exception)
					{
					}
					if (!(field != null) || !field.IsStatic)
					{
						continue;
					}
					if (field.DeclaringType != null)
					{
						OnTypeTouched?.Invoke(field.DeclaringType);
					}
					if ((op.Value == OpCodes.Stsfld.Value || (op.Value == OpCodes.Ldsflda.Value && !field.IsInitOnly)) && field.GetCustomAttributes(typeof(ThreadStaticAttribute), inherit: false).Length == 0)
					{
						string asm = field.DeclaringType?.Assembly.GetName().Name ?? "";
						if (!IsBcl(asm))
						{
							throw new StaticWriteException(field);
						}
					}
				}
			}
		}

		private static int OperandSize(OperandType t, byte[] il, int at)
		{
			switch (t)
			{
			case OperandType.InlineNone:
				return 0;
			case OperandType.ShortInlineBrTarget:
			case OperandType.ShortInlineI:
			case OperandType.ShortInlineVar:
				return 1;
			case OperandType.InlineVar:
				return 2;
			case OperandType.InlineI8:
			case OperandType.InlineR:
				return 8;
			case OperandType.InlineSwitch:
				return 4 + 4 * BitConverter.ToInt32(il, at);
			default:
				return 4;
			}
		}

		private static void BuildTables()
		{
			OpCode[] array = new OpCode[256];
			OpCode[] array2 = new OpCode[256];
			FieldInfo[] fields = typeof(OpCodes).GetFields(BindingFlags.Static | BindingFlags.Public);
			foreach (FieldInfo fieldInfo in fields)
			{
				if (fieldInfo.GetValue(null) is OpCode opCode)
				{
					ushort num = (ushort)opCode.Value;
					if (opCode.Size == 1)
					{
						array[num] = opCode;
					}
					else if (num >> 8 == 254)
					{
						array2[num & 0xFF] = opCode;
					}
				}
			}
			twoByte = array2;
			oneByte = array;
		}

		public static string Describe(MethodBase m)
		{
			return (m.DeclaringType?.Name ?? "?") + "." + m.Name;
		}

		private static string Key(MethodBase m)
		{
			string text = m.Module.ModuleVersionId.ToString("N") + ":" + m.MetadataToken.ToString("X");
			if (m.DeclaringType != null && m.DeclaringType.IsGenericType)
			{
				text = text + "<" + string.Join(",", Array.ConvertAll(m.DeclaringType.GetGenericArguments(), (Type t) => t.FullName ?? t.Name)) + ">";
			}
			if (m.IsGenericMethod && !m.IsConstructor)
			{
				text = text + "[" + string.Join(",", Array.ConvertAll(m.GetGenericArguments(), (Type t) => t.FullName ?? t.Name)) + "]";
			}
			return text;
		}
	}
	internal sealed class HolyData
	{
		public enum Kind
		{
			Health,
			Level,
			Scale,
			Visuals,
			Event,
			Method,
			Delegate
		}

		public struct Deferred
		{
			public Kind kind;

			public float a;

			public float b;

			public object o;

			public object[] args;
		}

		public bool queued;

		public float prevLevel;

		public float prevMaxHealth;

		public float prevMaxShield;

		public object statMods;

		public object customStats;

		public readonly List<Deferred> deferred = new List<Deferred>();

		public bool needsSync;

		public int bailSite;

		public Exception error;

		public void Add(Kind kind, float a = 0f, float b = 0f, object o = null, object[] args = null)
		{
			deferred.Add(new Deferred
			{
				kind = kind,
				a = a,
				b = b,
				o = o,
				args = args
			});
		}
	}
	internal sealed class BailException : Exception
	{
		public static readonly BailException Instance = new BailException();

		private BailException()
			: base("HolyHolyHOLY: recalculation moved to the main thread")
		{
		}
	}
	public static class ParallelRuntime
	{
		[ThreadStatic]
		private static HolyData current;

		internal static CharacterBody skipTarget;

		internal static float gravity = -30f;

		internal static readonly List<MethodBase> deferSites = new List<MethodBase>();

		public static bool InParallel => current != null;

		internal static void Enter(HolyData data)
		{
			current = data;
		}

		internal static void Exit()
		{
			current = null;
		}

		public static bool ShouldSkip(CharacterBody body)
		{
			if ((Object)(object)skipTarget == (Object)null || skipTarget != body)
			{
				return false;
			}
			skipTarget = null;
			return true;
		}

		public static void BailIfParallel(int site)
		{
			HolyData holyData = current;
			if (holyData == null)
			{
				return;
			}
			holyData.needsSync = true;
			holyData.bailSite = site;
			throw BailException.Instance;
		}

		public static void UpdateVisuals(CharacterBody body)
		{
			HolyData holyData = current;
			if (holyData != null)
			{
				holyData.Add(HolyData.Kind.Visuals);
			}
			else
			{
				body.UpdateAllTemporaryVisualEffects();
			}
		}

		public static void LevelChanged(CharacterBody body, float oldLevel, float newLevel)
		{
			HolyData holyData = current;
			if (holyData != null)
			{
				holyData.Add(HolyData.Kind.Level, oldLevel, newLevel);
			}
			else
			{
				body.OnCalculatedLevelChanged(oldLevel, newLevel);
			}
		}

		public static void SetDesiredScale(CharacterBody body, float value)
		{
			HolyData holyData = current;
			if (holyData == null)
			{
				body.desiredScale = value;
			}
			else if (value != body.desiredScale)
			{
				holyData.Add(HolyData.Kind.Scale, value);
			}
		}

		public static void DisableSkills(CharacterBody body)
		{
			if (current == null)
			{
				body.HandleDisableAllSkillsDebuff();
			}
			else if (body.HasBuff(Buffs.DisableAllSkills) != body.allSkillsDisabled)
			{
				BailIfParallel(-1);
			}
		}

		public static float CalculateApex(float initialSpeed)
		{
			return (current != null) ? Trajectory.CalculateApex(initialSpeed, gravity) : Trajectory.CalculateApex(initialSpeed);
		}

		public static void InvokeRecalculateEvent(Action<CharacterBody> handlers, CharacterBody body)
		{
			HolyData holyData = current;
			if (holyData != null)
			{
				holyData.Add(HolyData.Kind.Event, 0f, 0f, handlers);
			}
			else
			{
				handlers(body);
			}
		}

		public static bool HealthGate(CharacterBody body)
		{
			HolyData holyData = current;
			if (holyData == null)
			{
				return NetworkServer.active;
			}
			holyData.Add(HolyData.Kind.Health);
			return false;
		}

		public static void GetStatMods(CharacterBody body)
		{
			if (current == null)
			{
				RsapiCompat.GetStatMods(body);
			}
		}

		public static object GetThreadStatMods()
		{
			return current.statMods;
		}

		public static object GetThreadCustomStats()
		{
			return current.customStats;
		}

		public static void SetThreadCustomStats(object value)
		{
			current.customStats = value;
		}

		public static void DeferMethod(object[] stack, int site)
		{
			current.Add(HolyData.Kind.Method, 0f, 0f, deferSites[site], stack);
		}

		public static void DeferDelegate(object[] stack, bool delegateLast)
		{
			current.Add(HolyData.Kind.Delegate, delegateLast ? 1f : 0f, 0f, null, stack);
		}

		internal static void RunDeferred(CharacterBody body, HolyData data)
		{
			foreach (HolyData.Deferred item in data.deferred)
			{
				switch (item.kind)
				{
				case HolyData.Kind.Health:
					ApplyHealthDelta(body, data);
					break;
				case HolyData.Kind.Level:
					body.OnCalculatedLevelChanged(item.a, item.b);
					break;
				case HolyData.Kind.Scale:
					body.desiredScale = item.a;
					break;
				case HolyData.Kind.Visuals:
					body.UpdateAllTemporaryVisualEffects();
					break;
				case HolyData.Kind.Event:
					((Action<CharacterBody>)item.o)(body);
					break;
				case HolyData.Kind.Method:
					InvokeDeferredMethod((MethodBase)item.o, item.args);
					break;
				case HolyData.Kind.Delegate:
					InvokeDeferredDelegate(item.args, item.a != 0f);
					break;
				}
			}
			data.deferred.Clear();
		}

		private static void InvokeDeferredMethod(MethodBase m, object[] stack)
		{
			try
			{
				if (m.IsStatic || m.IsConstructor)
				{
					m.Invoke(null, stack);
					return;
				}
				object[] array = new object[stack.Length - 1];
				Array.Copy(stack, 1, array, 0, array.Length);
				m.Invoke(stack[0], array);
			}
			catch (TargetInvocationException ex) when (ex.InnerException != null)
			{
				ExceptionDispatchInfo.Capture(ex.InnerException).Throw();
			}
		}

		private static void InvokeDeferredDelegate(object[] stack, bool delegateLast)
		{
			object[] array = new object[stack.Length - 1];
			Delegate obj;
			if (delegateLast)
			{
				obj = (Delegate)stack[^1];
				Array.Copy(stack, 0, array, 0, array.Length);
			}
			else
			{
				obj = (Delegate)stack[0];
				Array.Copy(stack, 1, array, 0, array.Length);
			}
			try
			{
				obj.DynamicInvoke(array);
			}
			catch (TargetInvocationException ex) when (ex.InnerException != null)
			{
				ExceptionDispatchInfo.Capture(ex.InnerException).Throw();
			}
		}

		private static void ApplyHealthDelta(CharacterBody body, HolyData data)
		{
			//IL_005b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0061: Unknown result type (might be due to invalid IL or missing references)
			if (!NetworkServer.active)
			{
				return;
			}
			HealthComponent healthComponent = body.healthComponent;
			if (Object.op_Implicit((Object)(object)healthComponent))
			{
				float num = body.maxHealth - data.prevMaxHealth;
				float num2 = body.maxShield - data.prevMaxShield;
				if (num > 0f)
				{
					healthComponent.Heal(num, default(ProcChainMask), false);
				}
				else if (healthComponent.health > body.maxHealth)
				{
					healthComponent.Networkhealth = Mathf.Max(healthComponent.health + num, body.maxHealth);
				}
				if (num2 > 0f)
				{
					healthComponent.RechargeShield(num2);
				}
				else if (healthComponent.shield > body.maxShield)
				{
					healthComponent.Networkshield = Mathf.Max(healthComponent.shield + num2, body.maxShield);
				}
				if (healthComponent.regainableHealth > healthComponent.missingHealth)
				{
					healthComponent.NetworkregainableHealth = healthComponent.missingHealth;
				}
			}
		}
	}
	internal static class RecalcPatcher
	{
		private struct ChildType
		{
			public Assembly Assembly;

			public int Token;

			public string Name;
		}

		public static bool DeferForeignCalls = true;

		public static bool LogEverything;

		private static readonly List<string> BailSiteNames = new List<string>();

		private static ManualLogSource log;

		private static readonly NativeAnalyzer analyzer = new NativeAnalyzer();

		private static readonly Dictionary<MethodBase, List<MethodBase>> hookExtras = new Dictionary<MethodBase, List<MethodBase>>(MethodComparer.Instance);

		private static readonly HashSet<Type> touchedTypes = new HashSet<Type>();

		private static readonly HashSet<string> reported = new HashSet<string>();

		private static readonly Dictionary<MethodBase, int> deferSiteIds = new Dictionary<MethodBase, int>(MethodComparer.Instance);

		private static readonly Dictionary<Type, List<Type>> derivedTypes = new Dictionary<Type, List<Type>>();

		private static Dictionary<string, List<ChildType>> childIndex;

		private static int indexedAssemblyCount;

		private static Dictionary<MethodBase, MethodInfo> replacements;

		public static bool Healthy { get; private set; }

		public static int DeferredSites { get; private set; }

		public static int BailSites { get; private set; }

		private static MethodInfo RT(string name)
		{
			return typeof(ParallelRuntime).GetMethod(name, BindingFlags.Static | BindingFlags.Public);
		}

		public static void Init(ManualLogSource logger)
		{
			log = logger;
			analyzer.Override = Override;
			analyzer.ExtraTargets = Extras;
			analyzer.OnTypeTouched = delegate(Type t)
			{
				touchedTypes.Add(t);
			};
			Type typeFromHandle = typeof(CharacterBody);
			Dictionary<MethodBase, MethodInfo> dictionary = new Dictionary<MethodBase, MethodInfo>(MethodComparer.Instance);
			dictionary.Add(typeFromHandle.GetMethod("UpdateAllTemporaryVisualEffects", BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic), RT("UpdateVisuals"));
			dictionary.Add(typeFromHandle.GetMethod("OnCalculatedLevelChanged", BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic), RT("LevelChanged"));
			dictionary.Add(typeFromHandle.GetProperty("desiredScale", BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic).GetSetMethod(nonPublic: true), RT("SetDesiredScale"));
			dictionary.Add(typeFromHandle.GetMethod("HandleDisableAllSkillsDebuff", BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic), RT("DisableSkills"));
			dictionary.Add(typeof(Trajectory).GetMethod("CalculateApex", new Type[1] { typeof(float) }), RT("CalculateApex"));
			replacements = dictionary;
			if (RsapiCompat.GetStatModsMethod != null)
			{
				replacements[RsapiCompat.GetStatModsMethod] = RT("GetStatMods");
			}
		}

		public static void BeginSession()
		{
			analyzer.Clear();
			hookExtras.Clear();
			int num = NonDynamicAssemblyCount();
			if (num != indexedAssemblyCount)
			{
				indexedAssemblyCount = num;
				childIndex = null;
				derivedTypes.Clear();
			}
			HookIntrospection.BeginSession();
		}

		public static void Manipulate(ILContext il)
		{
			Healthy = false;
			try
			{
				Rewrite(il);
				Healthy = true;
			}
			catch (Exception ex)
			{
				log.LogError((object)("Couldn't prepare RecalculateStats for parallel execution, everything will stay on the main thread.\n" + ex));
			}
		}

		private static void Rewrite(ILContext il)
		{
			//IL_0170: Unknown result type (might be due to invalid IL or missing references)
			//IL_0175: Unknown result type (might be due to invalid IL or missing references)
			//IL_01c8: Unknown result type (might be due to invalid IL or missing references)
			//IL_01cd: Unknown result type (might be due to invalid IL or missing references)
			//IL_01e5: Unknown result type (might be due to invalid IL or missing references)
			//IL_01ea: Unknown result type (might be due to invalid IL or missing references)
			//IL_0202: Unknown result type (might be due to invalid IL or missing references)
			//IL_0207: Unknown result type (might be due to invalid IL or missing references)
			//IL_0715: Unknown result type (might be due to invalid IL or missing references)
			//IL_05b2: Unknown result type (might be due to invalid IL or missing references)
			//IL_064c: Unknown result type (might be due to invalid IL or missing references)
			//IL_05c5: Unknown result type (might be due to invalid IL or missing references)
			List<Action> list = new List<Action>();
			List<KeyValuePair<LogLevel, string>> list2 = new List<KeyValuePair<LogLevel, string>>();
			int num = 0;
			int num2 = 0;
			Instruction[] array = il.Instrs.ToArray();
			list.Add(delegate
			{
				//IL_0007: Unknown result type (might be due to invalid IL or missing references)
				//IL_000d: Expected O, but got Unknown
				//IL_0015: Unknown result type (might be due to invalid IL or missing references)
				//IL_0021: Unknown result type (might be due to invalid IL or missing references)
				//IL_0037: 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)
				ILCursor val2 = new ILCursor(il);
				ILLabel val3 = val2.DefineLabel();
				val2.Emit(OpCodes.Ldarg_0);
				val2.Emit(OpCodes.Call, (MethodBase)RT("ShouldSkip"));
				val2.Emit(OpCodes.Brfalse, (object)val3);
				val2.Emit(OpCodes.Ret);
				val2.MarkLabel(val3);
			});
			Instruction val = FindHealthGate(array);
			if (val == null)
			{
				throw new InvalidOperationException("couldn't find the health adjustment block (NetworkServer.active before HealthComponent.Heal)");
			}
			HashSet<Instruction> hashSet = FindRecalculateEventInvokes(array);
			if (hashSet.Count == 0)
			{
				throw new InvalidOperationException("couldn't find the onRecalculateStats invocation");
			}
			Instruction[] array2 = array;
			foreach (Instruction instr in array2)
			{
				if (instr == val)
				{
					list.Add(delegate
					{
						//IL_0007: Unknown result type (might be due to invalid IL or missing references)
						//IL_002a: Unknown result type (might be due to invalid IL or missing references)
						//IL_003c: Unknown result type (might be due to invalid IL or missing references)
						instr.OpCode = OpCodes.Ldarg_0;
						instr.Operand = null;
						new ILCursor(il).Goto(instr, (MoveType)2, false).Emit(OpCodes.Call, (MethodBase)RT("HealthGate"));
					});
				}
				else if (hashSet.Contains(instr))
				{
					MethodReference wrapper = il.Import((MethodBase)RT("InvokeRecalculateEvent"));
					list.Add(delegate
					{
						//IL_0011: Unknown result type (might be due to invalid IL or missing references)
						instr.OpCode = OpCodes.Call;
						instr.Operand = wrapper;
					});
				}
				else if (instr.OpCode == OpCodes.Calli)
				{
					num2++;
					list2.Add(Note((LogLevel)4, "an indirect calli can't be verified, bodies reaching it will be recalculated on the main thread"));
					list.Add(delegate
					{
						EmitBail(il, instr, "an indirect calli");
					});
				}
				else
				{
					if (instr.OpCode != OpCodes.Call && instr.OpCode != OpCodes.Callvirt && instr.OpCode != OpCodes.Newobj)
					{
						continue;
					}
					object operand = instr.Operand;
					MethodReference mref = (MethodReference)((operand is MethodReference) ? operand : null);
					if (mref == null)
					{
						continue;
					}
					MethodBase methodBase;
					try
					{
						methodBase = ReflectionHelper.ResolveReflection(mref);
					}
					catch (Exception)
					{
						methodBase = null;
					}
					if (methodBase == null)
					{
						num2++;
						list2.Add(Note((LogLevel)4, "couldn't resolve " + ((MemberReference)mref).FullName + ", bodies reaching it will be recalculated on the main thread"));
						list.Add(delegate
						{
							EmitBail(il, instr, ((MemberReference)mref).FullName);
						});
					}
					else
					{
						if (methodBase.DeclaringType?.Assembly == typeof(RecalcPatcher).Assembly)
						{
							continue;
						}
						if (replacements.TryGetValue(methodBase, out var value))
						{
							if (MethodComparer.Instance.Equals(methodBase, RsapiCompat.GetStatModsMethod) && !RsapiCompat.Active)
							{
								throw new InvalidOperationException("R2API.RecalculateStats is hooked in but couldn't be made thread safe");
							}
							MethodReference wrapper2 = il.Import((MethodBase)value);
							list.Add(delegate
							{
								//IL_0011: Unknown result type (might be due to invalid IL or missing references)
								instr.OpCode = OpCodes.Call;
								instr.Operand = wrapper2;
							});
						}
						else
						{
							if (IsReferenceBagGetter(methodBase))
							{
								continue;
							}
							Delegate bagged = ResolveBaggedDelegate(instr);
							MethodBase[] array3 = (((object)bagged != null) ? ((IEnumerable<Delegate>)bagged.GetInvocationList()).Select((Func<Delegate, MethodBase>)((Delegate d) => d.Method)).ToArray() : new MethodBase[1] { methodBase });
							NativeAnalyzer.Verdict verdict = NativeAnalyzer.Verdict.Safe;
							MethodBase[] array4 = array3;
							foreach (MethodBase method in array4)
							{
								verdict = analyzer.Analyze(method);
								if (verdict.Unsafe)
								{
									break;
								}
							}
							if (LogEverything)
							{
								list2.Add(Note((LogLevel)32, Describe(array3[0]) + ": " + (verdict.Unsafe ? verdict.Reason : "safe")));
							}
							if (!verdict.Unsafe)
							{
								continue;
							}
							Assembly assembly = array3[0].DeclaringType?.Assembly;
							bool flag = IsPluginAssembly(assembly);
							Type[] stack = StackTypes(methodBase);
							bool flag2 = (object)bagged != null || IsDelegateInvoke(methodBase);
							bool flag3 = DeferForeignCalls && (flag || (flag2 && (object)bagged == null)) && stack != null && ReturnsVoid(methodBase) && !AtExceptionBoundary(il, instr);
							string what = (((object)bagged != null) ? ("IL hook delegate " + Describe(array3[0]) + " from " + assembly?.GetName().Name) : (Describe(methodBase) + (flag ? (" from " + assembly?.GetName().Name) : "")));
							LogLevel level = (LogLevel)(flag ? 16 : 32);
							if (flag3)
							{
								num++;
								list2.Add(Note(level, what + " touches main-thread-only code and will run after the parallel part (" + verdict.Reason + ")"));
								if (flag2)
								{
									list.Add(delegate
									{
										EmitDeferral(il, instr, stack, delegate(ILCursor c)
										{
											//IL_0011: Unknown result type (might be due to invalid IL or missing references)
											//IL_000a: Unknown result type (might be due to invalid IL or missing references)
											//IL_001d: Unknown result type (might be due to invalid IL or missing references)
											c.Emit(((object)bagged != null) ? OpCodes.Ldc_I4_1 : OpCodes.Ldc_I4_0);
											c.Emit(OpCodes.Call, (MethodBase)RT("DeferDelegate"));
										});
									});
									continue;
								}
								int site = DeferSiteId(methodBase);
								list.Add(delegate
								{
									EmitDeferral(il, instr, stack, delegate(ILCursor c)
									{
										//IL_0002: Unknown result type (might be due to invalid IL or missing references)
										//IL_0014: Unknown result type (might be due to invalid IL or missing references)
										c.Emit(OpCodes.Ldc_I4, site);
										c.Emit(OpCodes.Call, (MethodBase)RT("DeferMethod"));
									});
								});
							}
							else
							{
								num2++;
								list2.Add(Note(level, what + " touches main-thread-only code, bodies reaching it will be recalculated on the main thread (" + verdict.Reason + ")"));
								list.Add(delegate
								{
									EmitBail(il, instr, what);
								});
							}
						}
					}
				}
			}
			foreach (Action item in list)
			{
				item();
			}
			DeferredSites = num;
			BailSites = num2;
			foreach (KeyValuePair<LogLevel, string> item2 in list2)
			{
				if (reported.Add(item2.Value))
				{
					log.Log(item2.Key, (object)item2.Value);
				}
			}
		}

		private static KeyValuePair<LogLevel, string> Note(LogLevel level, string text)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			return new KeyValuePair<LogLevel, string>(level, text);
		}

		private static Instruction FindHealthGate(Instruction[] instrs)
		{
			MethodInfo heal = typeof(HealthComponent).GetMethod("Heal");
			MethodInfo getMethod = typeof(NetworkServer).GetProperty("active").GetGetMethod();
			int num = Array.FindIndex(instrs, (Instruction x) => ILPatternMatchingExt.MatchCallOrCallvirt(x, (MethodBase)heal));
			for (int num2 = num - 1; num2 >= 0; num2--)
			{
				if (ILPatternMatchingExt.MatchCall(instrs[num2], (MethodBase)getMethod))
				{
					return instrs[num2];
				}
			}
			return null;
		}

		private static HashSet<Instruction> FindRecalculateEventInvokes(Instruction[] instrs)
		{
			HashSet<Instruction> hashSet = new HashSet<Instruction>();
			MethodInfo method = typeof(Action<CharacterBody>).GetMethod("Invoke");
			for (int i = 0; i < instrs.Length; i++)
			{
				if (!ILPatternMatchingExt.MatchLdfld<CharacterBody>(instrs[i], "onRecalculateStats"))
				{
					continue;
				}
				for (int j = i + 1; j < instrs.Length && j <= i + 12; j++)
				{
					if (ILPatternMatchingExt.MatchCallOrCallvirt(instrs[j], (MethodBase)method))
					{
						hashSet.Add(instrs[j]);
						break;
					}
				}
			}
			return hashSet;
		}

		private static bool IsReferenceBagGetter(MethodBase m)
		{
			return m.Name == "Get" && m.DeclaringType != null && m.DeclaringType.IsGenericType && m.DeclaringType.Name.StartsWith("InnerBag", StringComparison.Ordinal);
		}

		private static Delegate ResolveBaggedDelegate(Instruction invoke)
		{
			Instruction previous = invoke.Previous;
			Instruction val = ((previous != null) ? previous.Previous : null);
			if (previous != null && val != null)
			{
				object operand = previous.Operand;
				MethodReference val2 = (MethodReference)((operand is MethodReference) ? operand : null);
				int num = default(int);
				if (val2 != null && ILPatternMatchingExt.MatchLdcI4(val, ref num))
				{
					try
					{
						MethodBase methodBase = ReflectionHelper.ResolveReflection(val2);
						if (methodBase == null || !IsReferenceBagGetter(methodBase))
						{
							return null;
						}
						return ((MethodInfo)methodBase).Invoke(null, new object[1] { num }) as Delegate;
					}
					catch (Exception)
					{
						return null;
					}
				}
			}
			return null;
		}

		private static bool IsDelegateInvoke(MethodBase m)
		{
			return m.Name == "Invoke" && m.DeclaringType != null && typeof(Delegate).IsAssignableFrom(m.DeclaringType);
		}

		private static bool ReturnsVoid(MethodBase m)
		{
			return m is MethodInfo methodInfo && methodInfo.ReturnType == typeof(void);
		}

		private static Type[] StackTypes(MethodBase m)
		{
			if (m.IsConstructor)
			{
				return null;
			}
			List<Type> list = new List<Type>();
			if (!m.IsStatic)
			{
				if (m.DeclaringType == null || m.DeclaringType.IsValueType)
				{
					return null;
				}
				list.Add(m.DeclaringType);
			}
			ParameterInfo[] parameters = m.GetParameters();
			foreach (ParameterInfo parameterInfo in parameters)
			{
				if (parameterInfo.ParameterType.IsByRef || parameterInfo.ParameterType.IsPointer)
				{
					return null;
				}
				list.Add(parameterInfo.ParameterType);
			}
			if (list.Any((Type t) => t.ContainsGenericParameters))
			{
				return null;
			}
			return list.ToArray();
		}

		private static bool AtExceptionBoundary(ILContext il, Instruction instr)
		{
			//IL_000d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0012: Unknown result type (might be due to invalid IL or missing references)
			Enumerator<ExceptionHandler> enumerator = il.Body.ExceptionHandlers.GetEnumerator();
			try
			{
				while (enumerator.MoveNext())
				{
					ExceptionHandler current = enumerator.Current;
					Instruction[] array = (Instruction[])(object)new Instruction[5] { current.TryStart, current.TryEnd, current.HandlerStart, current.HandlerEnd, current.FilterStart };
					foreach (Instruction val in array)
					{
						if (val != null && (val == instr || val == instr.Next))
						{
							return true;
						}
					}
				}
			}
			finally
			{
				((IDisposable)enumerator/*cast due to .constrained prefix*/).Dispose();
			}
			return false;
		}

		private static void EmitBail(ILContext il, Instruction instr, string site)
		{
			//IL_002f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0045: Unknown result type (might be due to invalid IL or missing references)
			//IL_0052: Unknown result type (might be due to invalid IL or missing references)
			int num = BailSiteNames.IndexOf(site);
			if (num < 0)
			{
				num = BailSiteNames.Count;
				BailSiteNames.Add(site);
			}
			ILCursor val = new ILCursor(il).Goto(instr, (MoveType)0, false);
			val.MoveAfterLabels();
			val.Emit(OpCodes.Ldc_I4, num);
			val.Emit(OpCodes.Call, (MethodBase)RT("BailIfParallel"));
		}

		public static string BailSiteName(int id)
		{
			return (id == -1) ? "the Disable All Skills debuff toggling" : ((id >= 0 && id < BailSiteNames.Count) ? BailSiteNames[id] : "unknown");
		}

		private static void EmitDeferral(ILContext il, Instruction call, Type[] stack, Action<ILCursor> defer)
		{
			//IL_002c: Unknown result type (might be due to invalid IL or missing references)
			//IL_004b: Unknown result type (might be due to invalid IL or missing references)
			//IL_007e: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a3: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b0: Unknown result type (might be due to invalid IL or missing references)
			//IL_00bf: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e6: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f2: Unknown result type (might be due to invalid IL or missing references)
			//IL_0100: Unknown result type (might be due to invalid IL or missing references)
			//IL_0162: Unknown result type (might be due to invalid IL or missing references)
			//IL_013a: Unknown result type (might be due to invalid IL or missing references)
			//IL_011f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0188: Unknown result type (might be due to invalid IL or missing references)
			VariableDefinition[] array = stack.Select(delegate(Type t)
			{
				//IL_000d: Unknown result type (might be due to invalid IL or missing references)
				//IL_0013: Expected O, but got Unknown
				VariableDefinition val5 = new VariableDefinition(il.Import(t));
				il.Body.Variables.Add(val5);
				return val5;
			}).ToArray();
			ILCursor val = new ILCursor(il).Goto(call, (MoveType)0, false);
			val.MoveAfterLabels();
			for (int num = array.Length - 1; num >= 0; num--)
			{
				val.Emit(OpCodes.Stloc, array[num]);
			}
			ILLabel val2 = val.DefineLabel();
			ILLabel val3 = val.DefineLabel();
			val.Emit(OpCodes.Call, (MethodBase)typeof(ParallelRuntime).GetProperty("InParallel").GetGetMethod());
			val.Emit(OpCodes.Brfalse, (object)val2);
			val.Emit(OpCodes.Ldc_I4, array.Length);
			val.Emit(OpCodes.Newarr, il.Import(typeof(object)));
			for (int num2 = 0; num2 < array.Length; num2++)
			{
				val.Emit(OpCodes.Dup);
				val.Emit(OpCodes.Ldc_I4, num2);
				val.Emit(OpCodes.Ldloc, array[num2]);
				if (stack[num2].IsValueType)
				{
					val.Emit(OpCodes.Box, il.Import(stack[num2]));
				}
				val.Emit(OpCodes.Stelem_Ref);
			}
			defer(val);
			val.Emit(OpCodes.Br, (object)val3);
			val.MarkLabel(val2);
			VariableDefinition[] array2 = array;
			foreach (VariableDefinition val4 in array2)
			{
				val.Emit(OpCodes.Ldloc, val4);
			}
			val.Goto(call, (MoveType)2, false);
			val.MarkLabel(val3);
		}

		private static int DeferSiteId(MethodBase m)
		{
			if (!deferSiteIds.TryGetValue(m, out var value))
			{
				value = ParallelRuntime.deferSites.Count;
				ParallelRuntime.deferSites.Add(m);
				deferSiteIds[m] = value;
			}
			return value;
		}

		private static bool IsPluginAssembly(Assembly a)
		{
			if (a == null || a.IsDynamic || NativeAnalyzer.IsBcl(a.GetName().Name))
			{
				return false;
			}
			try
			{
				string location = a.Location;
				if (string.IsNullOrEmpty(location))
				{
					return false;
				}
				string directoryName = Path.GetDirectoryName(typeof(CharacterBody).Assembly.Location);
				return !string.Equals(Path.GetDirectoryName(location), directoryName, StringComparison.OrdinalIgnoreCase);
			}
			catch (Exception)
			{
				return false;
			}
		}

		private static NativeAnalyzer.Verdict Override(MethodBase m)
		{
			if (RsapiCompat.Patched.Contains(m))
			{
				return NativeAnalyzer.Verdict.Safe;
			}
			if (m.Name == "Invoke" && m.DeclaringType != null && m.DeclaringType.Name.StartsWith("orig_", StringComparison.Ordinal))
			{
				string? text = m.DeclaringType.Namespace;
				if (text != null && text.StartsWith("On.", StringComparison.Ordinal))
				{
					return NativeAnalyzer.Verdict.Safe;
				}
			}
			List<MethodBase> list = new List<MethodBase>();
			string text2 = HookIntrospection.CheckHooks(m, list);
			if (text2 != null)
			{
				return NativeAnalyzer.Verdict.No(NativeAnalyzer.Describe(m) + " is " + text2);
			}
			if (list.Count > 0)
			{
				hookExtras[m] = list;
			}
			return null;
		}

		private static IEnumerable<MethodBase> Extras(MethodBase m)
		{
			if (hookExtras.TryGetValue(m, out var hooked))
			{
				foreach (MethodBase item in hooked)
				{
					yield return item;
				}
			}
			MethodInfo mi = m as MethodInfo;
			if ((object)mi == null || !mi.IsVirtual || mi.IsFinal)
			{
				yield break;
			}
			Type declaring = mi.DeclaringType;
			if (declaring == null || declaring.IsSealed || declaring.IsInterface)
			{
				yield break;
			}
			string asm = declaring.Assembly.GetName().Name;
			if (NativeAnalyzer.IsBcl(asm) || NativeAnalyzer.IsUnity(asm))
			{
				yield break;
			}
			MethodInfo baseDef = mi.GetBaseDefinition();
			foreach (Type t in Derived(declaring.IsGenericType ? declaring.GetGenericTypeDefinition() : declaring))
			{
				if (t.ContainsGenericParameters)
				{
					continue;
				}
				MethodInfo[] methods = t.GetMethods(BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
				foreach (MethodInfo cand in methods)
				{
					if (!(cand.Name != mi.Name) && cand.IsVirtual)
					{
						MethodInfo candBase = cand.GetBaseDefinition();
						if (candBase.MetadataToken == baseDef.MetadataToken && candBase.Module == baseDef.Module)
						{
							yield return cand;
						}
					}
				}
			}
		}

		private static List<Type> Derived(Type declaring)
		{
			if (derivedTypes.TryGetValue(declaring, out var value))
			{
				return value;
			}
			if (childIndex == null)
			{
				IndexHierarchy();
			}
			value = new List<Type>();
			Queue<string> queue = new Queue<string>();
			HashSet<string> hashSet = new HashSet<string> { CecilName(declaring) };
			queue.Enqueue(CecilName(declaring));
			while (queue.Count > 0)
			{
				if (!childIndex.TryGetValue(queue.Dequeue(), out var value2))
				{
					continue;
				}
				foreach (ChildType item in value2)
				{
					if (hashSet.Add(item.Name))
					{
						queue.Enqueue(item.Name);
					}
					try
					{
						Type type = item.Assembly.ManifestModule.ResolveType(item.Token);
						if (DerivesFrom(type, declaring))
						{
							value.Add(type);
						}
					}
					catch (Exception)
					{
					}
				}
			}
			derivedTypes[declaring] = value;
			return value;
		}

		private static void IndexHierarchy()
		{
			childIndex = new Dictionary<string, List<ChildType>>();
			Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies();
			foreach (Assembly assembly in assemblies)
			{
				string name = assembly.GetName().Name;
				if (assembly.IsDynamic || NativeAnalyzer.IsBcl(name) || NativeAnalyzer.IsUnity(name) || IndexFromMetadata(assembly))
				{
					continue;
				}
				Type[] array;
				try
				{
					array = assembly.GetTypes();
				}
				catch (ReflectionTypeLoadException ex)
				{
					array = ex.Types.Where((Type t) => t != null).ToArray();
				}
				catch (Exception)
				{
					continue;
				}
				Type[] array2 = array;
				foreach (Type type in array2)
				{
					try
					{
						Type baseType = type.BaseType;
						if (!(baseType == null))
						{
							AddChild(CecilName(baseType.IsGenericType ? baseType.GetGenericTypeDefinition() : baseType), assembly, type.MetadataToken, CecilName(type));
						}
					}
					catch (Exception)
					{
					}
				}
			}
		}

		private static bool IndexFromMetadata(Assembly a)
		{
			//IL_008c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0091: Unknown result type (might be due to invalid IL or missing references)
			try
			{
				string location = a.Location;
				if (string.IsNullOrEmpty(location) || !File.Exists(location))
				{
					return false;
				}
				AssemblyDefinition val = AssemblyDefinition.ReadAssembly(location);
				try
				{
					foreach (TypeDefinition type in val.MainModule.GetTypes())
					{
						TypeReference val2 = type.BaseType;
						if (val2 != null)
						{
							GenericInstanceType val3 = (GenericInstanceType)(object)((val2 is GenericInstanceType) ? val2 : null);
							if (val3 != null)
							{
								val2 = ((TypeSpecification)val3).ElementType;
							}
							string fullName = ((MemberReference)val2).FullName;
							MetadataToken metadataToken = ((MemberReference)type).MetadataToken;
							AddChild(fullName, a, ((MetadataToken)(ref metadataToken)).ToInt32(), ((MemberReference)type).FullName);
						}
					}
				}
				finally
				{
					((IDisposable)val)?.Dispose();
				}
				return true;
			}
			catch (Exception)
			{
				return false;
			}
		}

		private static void AddChild(string baseName, Assembly a, int token, string name)
		{
			if (!childIndex.TryGetValue(baseName, out var value))
			{
				value = (childIndex[baseName] = new List<ChildType>());
			}
			value.Add(new ChildType
			{
				Assembly = a,
				Token = token,
				Name = name
			});
		}

		private static string CecilName(Type t)
		{
			if (t.IsNested)
			{
				return CecilName(t.DeclaringType) + "/" + t.Name;
			}
			return string.IsNullOrEmpty(t.Namespace) ? t.Name : (t.Namespace + "." + t.Name);
		}

		private static bool DerivesFrom(Type t, Type declaring)
		{
			Type baseType = t.BaseType;
			while (baseType != null && baseType != typeof(object))
			{
				if ((baseType.IsGenericType ? baseType.GetGenericTypeDefinition() : baseType) == declaring)
				{
					return true;
				}
				baseType = baseType.BaseType;
			}
			return false;
		}

		private static int NonDynamicAssemblyCount()
		{
			return AppDomain.CurrentDomain.GetAssemblies().Count((Assembly a) => !a.IsDynamic);
		}

		public static void RunStaticConstructors()
		{
			if (touchedTypes.Count == 0)
			{
				return;
			}
			Type[] array = touchedTypes.ToArray();
			foreach (Type type in array)
			{
				if (type.ContainsGenericParameters)
				{
					continue;
				}
				string name = type.Assembly.GetName().Name;
				if (!NativeAnalyzer.IsBcl(name))
				{
					try
					{
						RuntimeHelpers.RunClassConstructor(type.TypeHandle);
					}
					catch (Exception)
					{
					}
				}
			}
			touchedTypes.Clear();
		}

		private static string Describe(MethodBase m)
		{
			return NativeAnalyzer.Describe(m);
		}
	}
	internal static class RsapiCompat
	{
		[CompilerGenerated]
		private static class <>O
		{
			public static Manipulator <0>__RedirectScratch;
		}

		private const BindingFlags Any = BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic;

		public static readonly HashSet<MethodBase> Patched = new HashSet<MethodBase>(MethodComparer.Instance);

		private static FieldInfo statModsField;

		private static FieldInfo customStatsField;

		private static Action<CharacterBody> getStatMods;

		private static Func<CharacterBody, object> getCustomStats;

		private static readonly List<ILHook> hooks = new List<ILHook>();

		public static bool Active { get; private set; }

		public static MethodInfo GetStatModsMethod { get; private set; }

		public static void Init(ManualLogSource log, bool installHooks = true)
		{
			//IL_01d5: Unknown result type (might be due to invalid IL or missing references)
			//IL_01df: Expected O, but got Unknown
			//IL_01ca: Unknown result type (might be due to invalid IL or missing references)
			//IL_01cf: Unknown result type (might be due to invalid IL or missing references)
			//IL_01d5: Expected O, but got Unknown
			Type typeFromHandle = typeof(RecalculateStatsAPI);
			statModsField = typeFromHandle.GetField("StatMods", BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
			customStatsField = typeFromHandle.GetField("BodyCustomStats", BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
			GetStatModsMethod = typeFromHandle.GetMethod("GetStatMods", BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic, null, new Type[1] { typeof(CharacterBody) }, null);
			MethodInfo method = typeFromHandle.GetMethod("GetCustomStatsFromBody", BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic, null, new Type[1] { typeof(CharacterBody) }, null);
			if (statModsField == null || customStatsField == null || GetStatModsMethod == null || method == null)
			{
				log.LogWarning((object)"R2API.RecalculateStats internals changed, can't make it thread safe.");
				return;
			}
			getStatMods = (Action<CharacterBody>)Delegate.CreateDelegate(typeof(Action<CharacterBody>), GetStatModsMethod);
			getCustomStats = (Func<CharacterBody, object>)Delegate.CreateDelegate(typeof(Func<CharacterBody, object>), method);
			Type[] array;
			try
			{
				array = typeFromHandle.Assembly.GetTypes();
			}
			catch (ReflectionTypeLoadException ex)
			{
				array = ex.Types.Where((Type t) => t != null).ToArray();
			}
			Type[] array2 = array;
			foreach (Type type in array2)
			{
				MethodInfo[] methods = type.GetMethods(BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
				foreach (MethodInfo methodInfo in methods)
				{
					if (methodInfo.IsAbstract || methodInfo.ContainsGenericParameters || !TouchesScratch(methodInfo) || methodInfo == GetStatModsMethod)
					{
						continue;
					}
					if (installHooks)
					{
						List<ILHook> list = hooks;
						object obj = <>O.<0>__RedirectScratch;
						if (obj == null)
						{
							Manipulator val = RedirectScratch;
							<>O.<0>__RedirectScratch = val;
							obj = (object)val;
						}
						list.Add(new ILHook((MethodBase)methodInfo, (Manipulator)obj));
					}
					Patched.Add(methodInfo);
				}
			}
			Active = true;
		}

		public static void Unload()
		{
			foreach (ILHook hook in hooks)
			{
				hook.Dispose();
			}
			hooks.Clear();
			Patched.Clear();
			Active = false;
		}

		public static void GetStatMods(CharacterBody body)
		{
			getStatMods(body);
		}

		public static void Prepare(CharacterBody body, HolyData data)
		{
			if (Active)
			{
				getStatMods(body);
				data.statMods = statModsField.GetValue(null);
				data.customStats = getCustomStats(body);
			}
		}

		private static bool TouchesScratch(MethodBase m)
		{
			byte[] array;
			try
			{
				array = m.GetMethodBody()?.GetILAsByteArray();
			}
			catch (Exception)
			{
				return false;
			}
			if (array == null)
			{
				return false;
			}
			int metadataToken = statModsField.MetadataToken;
			int metadataToken2 = customStatsField.MetadataToken;
			for (int i = 0; i + 4 < array.Length; i++)
			{
				if (array[i] == 126 || array[i] == 128)
				{
					int num = BitConverter.ToInt32(array, i + 1);
					if (num == metadataToken || num == metadataToken2)
					{
						return true;
					}
				}
			}
			return false;
		}

		private static void RedirectScratch(ILContext il)
		{
			//IL_0035: Unknown result type (might be due to invalid IL or missing references)
			//IL_003a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0047: Unknown result type (might be due to invalid IL or missing references)
			//IL_004c: 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_00d2: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f2: Unknown result type (might be due to invalid IL or missing references)
			//IL_010c: Unknown result type (might be due to invalid IL or missing references)
			//IL_012f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0149: Unknown result type (might be due to invalid IL or missing references)
			//IL_0142: Unknown result type (might be due to invalid IL or missing references)
			//IL_0158: Unknown result type (might be due to invalid IL or missing references)
			//IL_01c3: Unknown result type (might be due to invalid IL or missing references)
			//IL_017a: Unknown result type (might be due to invalid IL or missing references)
			//IL_01a6: Unknown result type (might be due to invalid IL or missing references)
			MethodInfo getMethod = typeof(ParallelRuntime).GetProperty("InParallel").GetGetMethod();
			Instruction[] array = il.Instrs.ToArray();
			foreach (Instruction val in array)
			{
				if (val.OpCode != OpCodes.Ldsfld && val.OpCode != OpCodes.Stsfld)
				{
					continue;
				}
				object operand = val.Operand;
				FieldReference val2 = (FieldReference)((operand is FieldReference) ? operand : null);
				if (val2 == null)
				{
					continue;
				}
				FieldInfo fieldInfo;
				try
				{
					fieldInfo = ReflectionHelper.ResolveReflection(val2);
				}
				catch (Exception)
				{
					continue;
				}
				bool flag = fieldInfo == statModsField;
				bool flag2 = fieldInfo == customStatsField;
				if (!flag && !flag2)
				{
					continue;
				}
				bool flag3 = val.OpCode == OpCodes.Ldsfld;
				if (!(!flag3 && flag))
				{
					val.OpCode = OpCodes.Call;
					val.Operand = il.Import((MethodBase)getMethod);
					ILCursor val3 = new ILCursor(il).Goto(val, (MoveType)2, false);
					ILLabel val4 = val3.DefineLabel();
					ILLabel val5 = val3.DefineLabel();
					val3.Emit(OpCodes.Brtrue, (object)val4);
					val3.Emit(flag3 ? OpCodes.Ldsfld : OpCodes.Stsfld, val2);
					val3.Emit(OpCodes.Br, (object)val5);
					val3.MarkLabel(val4);
					if (flag3)
					{
						val3.Emit(OpCodes.Call, (MethodBase)typeof(ParallelRuntime).GetMethod(flag ? "GetThreadStatMods" : "GetThreadCustomStats"));
						val3.Emit(OpCodes.Castclass, il.Import(fieldInfo.FieldType));
					}
					else
					{
						val3.Emit(OpCodes.Call, (MethodBase)typeof(ParallelRuntime).GetMethod("SetThreadCustomStats"));
					}
					val3.MarkLabel(val5);
				}
			}
		}
	}
}