Decompiled source of M4A1Fix v1.0.1

plugins/M4A1Fix/M4A1Fix.dll

Decompiled a day ago
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Reflection.Emit;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Security.Cryptography;
using System.Text;
using BepInEx;
using BepInEx.Bootstrap;
using BepInEx.Logging;
using HarmonyLib;
using M4A1Fix.Compatibility;
using M4A1Fix.Patches;
using M4A1Mod.Survivors.M4A1.Utils;
using Microsoft.CodeAnalysis;
using RoR2;
using UnityEngine;
using UnityEngine.Networking;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETFramework,Version=v4.7.2", FrameworkDisplayName = ".NET Framework 4.7.2")]
[assembly: AssemblyCompany("M4A1Fix")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyInformationalVersion("1.0.0")]
[assembly: AssemblyProduct("M4A1Fix")]
[assembly: AssemblyTitle("M4A1Fix")]
[assembly: AssemblyVersion("1.0.0.0")]
[module: RefSafetyRules(11)]
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 M4A1Fix
{
	[BepInPlugin("com.m4a1fix.revivepriority", "M4A1Fix", "1.0.0")]
	[BepInDependency(/*Could not decode attribute arguments.*/)]
	public sealed class Plugin : BaseUnityPlugin
	{
		internal const string PluginGuid = "com.m4a1fix.revivepriority";

		internal const string PluginName = "M4A1Fix";

		internal const string PluginVersion = "1.0.0";

		internal const string M4A1PluginGuid = "com.Snoresville.M4A1";

		private Harmony _revivePriorityHarmony;

		private Harmony _shrineAiRecoveryHarmony;

		private void Awake()
		{
			//IL_0016: Unknown result type (might be due to invalid IL or missing references)
			//IL_0020: Expected O, but got Unknown
			((BaseUnityPlugin)this).Logger.LogInfo((object)"M4A1Fix 1.0.0 loaded.");
			_revivePriorityHarmony = new Harmony("com.m4a1fix.revivepriority");
			if (!RevivePriorityPatch.TryInstall(_revivePriorityHarmony, ((BaseUnityPlugin)this).Logger, out var target, out var reason))
			{
				((BaseUnityPlugin)this).Logger.LogError((object)("RevivePriority disabled: " + reason));
				return;
			}
			((BaseUnityPlugin)this).Logger.LogInfo((object)"RevivePriority enabled.");
			if (!ShrineAiRecoveryPatch.TryInstall(((BaseUnityPlugin)this).Logger, target, out _shrineAiRecoveryHarmony, out reason))
			{
				((BaseUnityPlugin)this).Logger.LogError((object)("ShrineAIRecovery disabled: " + reason));
			}
			else
			{
				((BaseUnityPlugin)this).Logger.LogInfo((object)"ShrineAIRecovery enabled.");
			}
		}

		private void OnDestroy()
		{
			if (_shrineAiRecoveryHarmony != null)
			{
				_shrineAiRecoveryHarmony.UnpatchSelf();
			}
			if (_revivePriorityHarmony != null)
			{
				_revivePriorityHarmony.UnpatchSelf();
			}
		}
	}
}
namespace M4A1Fix.Patches
{
	internal static class RevivePriorityPatch
	{
		private static bool _transpilerMatched;

		internal static bool TryInstall(Harmony harmony, ManualLogSource log, out MethodInfo target, out string reason)
		{
			//IL_0052: Unknown result type (might be due to invalid IL or missing references)
			//IL_005e: Expected O, but got Unknown
			target = null;
			reason = null;
			if (harmony == null)
			{
				reason = "Harmony owner was not created.";
				return false;
			}
			if (!M4A1Compatibility.TryValidateRevivePriorityTarget(out target, out reason))
			{
				return false;
			}
			_transpilerMatched = false;
			MethodInfo methodInfo = AccessTools.Method(typeof(RevivePriorityPatch), "CharacterMasterOnBodyDeathTranspiler", (Type[])null, (Type[])null);
			if (methodInfo == null)
			{
				reason = "revive-priority transpiler method could not be resolved.";
				return false;
			}
			harmony.Patch((MethodBase)target, (HarmonyMethod)null, (HarmonyMethod)null, new HarmonyMethod(methodInfo), (HarmonyMethod)null, (HarmonyMethod)null);
			if (!_transpilerMatched || !HasOwnedTranspiler(target, "com.m4a1fix.revivepriority"))
			{
				harmony.UnpatchSelf();
				reason = "exact M4A1 OnBodyDeath IL pattern did not match; no targeted patch remains installed.";
				return false;
			}
			return true;
		}

		internal static bool IsHealthy(MethodInfo exactTarget)
		{
			if (_transpilerMatched && exactTarget != null)
			{
				return HasOwnedTranspiler(exactTarget, "com.m4a1fix.revivepriority");
			}
			return false;
		}

		private static bool ShouldDelegateShrineToVanilla(CharacterBody body)
		{
			if (NetworkServer.active && (Object)(object)body != (Object)null)
			{
				return body.HasBuff(Buffs.ExtraLifeBuff);
			}
			return false;
		}

		private static IEnumerable<CodeInstruction> CharacterMasterOnBodyDeathTranspiler(IEnumerable<CodeInstruction> instructions, ILGenerator generator)
		{
			//IL_0124: Unknown result type (might be due to invalid IL or missing references)
			//IL_012e: Expected O, but got Unknown
			//IL_0135: Unknown result type (might be due to invalid IL or missing references)
			//IL_013f: Expected O, but got Unknown
			//IL_0147: Unknown result type (might be due to invalid IL or missing references)
			//IL_0151: Expected O, but got Unknown
			//IL_015e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0168: Expected O, but got Unknown
			List<CodeInstruction> list = new List<CodeInstruction>(instructions);
			int num = FindOrigOnBodyDeathInvoke(list);
			int num2 = FindGetSomeChildDummyLink(list);
			if (num < 3 || num2 < 1)
			{
				return list;
			}
			int num3 = num - 3;
			int num4 = num2 - 1;
			if (!IsLdarg(list[num3], 0) || !IsLdarg(list[num3 + 1], 1) || !IsLdarg(list[num3 + 2], 2) || !IsLdarg(list[num4], 2))
			{
				return list;
			}
			int num5 = FindConditionalBranchTo(list, num4, num3);
			if (num5 <= 0 || !IsLocalLoad(list[num5 - 1]))
			{
				return list;
			}
			int index = num5 - 1;
			Label label = generator.DefineLabel();
			Label continueOriginalDecisionLabel = generator.DefineLabel();
			list[num3].labels.Add(label);
			list[index].labels.Add(continueOriginalDecisionLabel);
			CodeInstruction item = CloneWithoutLabelsOrBlocks(list[index]);
			MethodInfo methodInfo = AccessTools.Method(typeof(RevivePriorityPatch), "ShouldDelegateShrineToVanilla", (Type[])null, (Type[])null);
			List<CodeInstruction> list2 = new List<CodeInstruction>
			{
				item,
				new CodeInstruction(OpCodes.Brtrue, (object)continueOriginalDecisionLabel),
				new CodeInstruction(OpCodes.Ldarg_2, (object)null),
				new CodeInstruction(OpCodes.Call, (object)methodInfo),
				new CodeInstruction(OpCodes.Brtrue, (object)label)
			};
			list2[0].labels.AddRange(list[index].labels.Where((Label label2) => !label2.Equals(continueOriginalDecisionLabel)));
			list[index].labels.RemoveAll((Label label2) => !label2.Equals(continueOriginalDecisionLabel));
			list2[0].blocks.AddRange(list[index].blocks);
			list[index].blocks.Clear();
			list.InsertRange(index, list2);
			_transpilerMatched = true;
			return list;
		}

		private static int FindOrigOnBodyDeathInvoke(List<CodeInstruction> codes)
		{
			for (int i = 0; i < codes.Count; i++)
			{
				MethodInfo methodInfo = codes[i].operand as MethodInfo;
				if (!(methodInfo == null) && !(methodInfo.Name != "Invoke") && !(methodInfo.DeclaringType == null) && !(methodInfo.DeclaringType.Name != "orig_OnBodyDeath"))
				{
					ParameterInfo[] parameters = methodInfo.GetParameters();
					if (methodInfo.ReturnType == typeof(void) && parameters.Length == 2 && parameters[0].ParameterType == typeof(CharacterMaster) && parameters[1].ParameterType == typeof(CharacterBody))
					{
						return i;
					}
				}
			}
			return -1;
		}

		private static int FindGetSomeChildDummyLink(List<CodeInstruction> codes)
		{
			for (int i = 0; i < codes.Count; i++)
			{
				MethodInfo methodInfo = codes[i].operand as MethodInfo;
				if (methodInfo != null && methodInfo.Name == "GetSomeChildDummyLink" && methodInfo.DeclaringType != null && methodInfo.DeclaringType.FullName == "M4A1Mod.Survivors.M4A1.Utils.M4A1")
				{
					return i;
				}
			}
			return -1;
		}

		private static int FindConditionalBranchTo(List<CodeInstruction> codes, int targetIndex, int searchEndExclusive)
		{
			List<Label> labels = codes[targetIndex].labels;
			for (int num = searchEndExclusive - 1; num >= 0; num--)
			{
				if (codes[num].opcode.FlowControl == FlowControl.Cond_Branch && codes[num].operand is Label item && labels.Contains(item))
				{
					return num;
				}
			}
			return -1;
		}

		private static bool IsLdarg(CodeInstruction instruction, int index)
		{
			if (index == 0 && instruction.opcode == OpCodes.Ldarg_0)
			{
				return true;
			}
			if (index == 1 && instruction.opcode == OpCodes.Ldarg_1)
			{
				return true;
			}
			if (index == 2 && instruction.opcode == OpCodes.Ldarg_2)
			{
				return true;
			}
			if (index == 3 && instruction.opcode == OpCodes.Ldarg_3)
			{
				return true;
			}
			if (instruction.opcode != OpCodes.Ldarg && instruction.opcode != OpCodes.Ldarg_S)
			{
				return false;
			}
			if (instruction.operand is byte b)
			{
				return b == index;
			}
			if (instruction.operand is short num)
			{
				return num == index;
			}
			if (instruction.operand is ParameterInfo parameterInfo)
			{
				return parameterInfo.Position == index;
			}
			return false;
		}

		private static bool IsLocalLoad(CodeInstruction instruction)
		{
			if (!(instruction.opcode == OpCodes.Ldloc) && !(instruction.opcode == OpCodes.Ldloc_S) && !(instruction.opcode == OpCodes.Ldloc_0) && !(instruction.opcode == OpCodes.Ldloc_1) && !(instruction.opcode == OpCodes.Ldloc_2))
			{
				return instruction.opcode == OpCodes.Ldloc_3;
			}
			return true;
		}

		private static CodeInstruction CloneWithoutLabelsOrBlocks(CodeInstruction instruction)
		{
			//IL_000c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0012: Expected O, but got Unknown
			return new CodeInstruction(instruction.opcode, instruction.operand);
		}

		private static bool HasOwnedTranspiler(MethodBase target, string owner)
		{
			Patches patchInfo = Harmony.GetPatchInfo(target);
			if (patchInfo == null || patchInfo.Transpilers == null)
			{
				return false;
			}
			foreach (Patch transpiler in patchInfo.Transpilers)
			{
				if (transpiler != null && transpiler.owner == owner)
				{
					return true;
				}
			}
			return false;
		}
	}
	internal static class ShrineAiRecoveryPatch
	{
		private const string M4A1BodyName = "M4A1Body";

		private const string DummyLinkDeathMethodName = "CharacterMaster_OnBodyDeath";

		private const string ShrineRespawnMethodName = "RespawnExtraLifeShrine";

		private const string HarmonyOwner = "com.m4a1fix.revivepriority/shrine-ai-recovery";

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

		private static ManualLogSource _log;

		internal static bool TryInstall(ManualLogSource log, MethodInfo revivePriorityTarget, out Harmony harmony, out string reason)
		{
			//IL_00c6: Unknown result type (might be due to invalid IL or missing references)
			//IL_00cc: Expected O, but got Unknown
			//IL_00d2: Unknown result type (might be due to invalid IL or missing references)
			//IL_00df: Expected O, but got Unknown
			_log = log;
			harmony = null;
			reason = null;
			if (!RevivePriorityPatch.IsHealthy(revivePriorityTarget))
			{
				reason = "RevivePriority is not healthy on the exact M4A1 death target.";
				return false;
			}
			if (!TryValidateExactM4A1Plugin(out var assembly, out reason))
			{
				return false;
			}
			Type type = assembly.GetType("M4A1Mod.Survivors.M4A1.Hooks.DummyLink", throwOnError: false);
			if (type == null)
			{
				reason = "exact M4A1 DummyLink type is missing.";
				return false;
			}
			if (!TryResolveSingleDeclaredMethod(type, "CharacterMaster_OnBodyDeath", out var method, out reason))
			{
				return false;
			}
			if (!method.Equals(revivePriorityTarget) || !HasOwnedTranspiler(method, "com.m4a1fix.revivepriority"))
			{
				reason = "RevivePriority is not installed on the exact M4A1 CharacterMaster_OnBodyDeath target.";
				return false;
			}
			if (!TryResolveSingleDeclaredMethod(typeof(CharacterMaster), "RespawnExtraLifeShrine", out var method2, out reason))
			{
				return false;
			}
			MethodInfo method3 = typeof(ShrineAiRecoveryPatch).GetMethod("RespawnExtraLifeShrinePostfix", BindingFlags.Static | BindingFlags.NonPublic);
			if (method3 == null)
			{
				reason = "ShrineAIRecovery postfix method could not be resolved.";
				return false;
			}
			try
			{
				harmony = new Harmony("com.m4a1fix.revivepriority/shrine-ai-recovery");
				harmony.Patch((MethodBase)method2, (HarmonyMethod)null, new HarmonyMethod(method3), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
				if (!HasOwnedPostfix(method2, "com.m4a1fix.revivepriority/shrine-ai-recovery"))
				{
					harmony.UnpatchSelf();
					harmony = null;
					reason = "Harmony did not report the ShrineAIRecovery postfix after installation.";
					return false;
				}
				return true;
			}
			catch (Exception ex)
			{
				try
				{
					if (harmony != null)
					{
						harmony.UnpatchSelf();
					}
				}
				catch
				{
				}
				harmony = null;
				reason = "installation exception: " + ex.GetType().Name + ": " + ex.Message;
				return false;
			}
		}

		private static bool TryValidateExactM4A1Plugin(out Assembly assembly, out string reason)
		{
			assembly = null;
			reason = null;
			if (!Chainloader.PluginInfos.TryGetValue("com.Snoresville.M4A1", out var value) || value == null)
			{
				reason = "required M4A1 plugin is not loaded.";
				return false;
			}
			string location;
			try
			{
				location = value.Location;
			}
			catch (Exception ex)
			{
				reason = "M4A1 PluginInfo.Location read failed: " + ex.GetType().Name + ": " + ex.Message;
				return false;
			}
			if (string.IsNullOrWhiteSpace(location) || !File.Exists(location))
			{
				reason = "loaded M4A1 plugin file location is missing or unreadable.";
				return false;
			}
			if (!TrySha256(location, out var hashHex, out reason))
			{
				return false;
			}
			if (!string.Equals(hashHex, "600188D306782F6C1B26EA1BDA4A615BFF9AC4437318873D5728CD58B26A7154", StringComparison.OrdinalIgnoreCase))
			{
				reason = "loaded M4A1 plugin SHA-256 mismatch.";
				return false;
			}
			if ((Object)(object)value.Instance == (Object)null)
			{
				reason = "loaded M4A1 PluginInfo has no live plugin instance.";
				return false;
			}
			assembly = ((object)value.Instance).GetType().Assembly;
			return true;
		}

		private static bool TrySha256(string filePath, out string hashHex, out string reason)
		{
			hashHex = null;
			reason = null;
			try
			{
				using (FileStream inputStream = File.OpenRead(filePath))
				{
					using SHA256 sHA = SHA256.Create();
					hashHex = BitConverter.ToString(sHA.ComputeHash(inputStream)).Replace("-", string.Empty);
				}
				return true;
			}
			catch (Exception ex)
			{
				reason = "SHA-256 read failed for loaded M4A1 plugin bytes: " + ex.GetType().Name + ": " + ex.Message;
				return false;
			}
		}

		private static bool TryResolveSingleDeclaredMethod(Type type, string methodName, out MethodInfo method, out string reason)
		{
			method = null;
			reason = null;
			int num = 0;
			MethodInfo[] methods = type.GetMethods(BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
			foreach (MethodInfo methodInfo in methods)
			{
				if (!(methodInfo.Name != methodName))
				{
					method = methodInfo;
					num++;
				}
			}
			if (num == 1)
			{
				return true;
			}
			method = null;
			reason = ((num == 0) ? ("exact method is missing: " + type.FullName + "." + methodName) : ("exact method resolution is ambiguous: " + type.FullName + "." + methodName + " (count=" + num + ")"));
			return false;
		}

		private static bool HasOwnedTranspiler(MethodBase target, string owner)
		{
			Patches patchInfo = Harmony.GetPatchInfo(target);
			if (patchInfo == null || patchInfo.Transpilers == null)
			{
				return false;
			}
			foreach (Patch transpiler in patchInfo.Transpilers)
			{
				if (transpiler != null && transpiler.owner == owner)
				{
					return true;
				}
			}
			return false;
		}

		private static bool HasOwnedPostfix(MethodBase target, string owner)
		{
			Patches patchInfo = Harmony.GetPatchInfo(target);
			if (patchInfo == null || patchInfo.Postfixes == null)
			{
				return false;
			}
			foreach (Patch postfix in patchInfo.Postfixes)
			{
				if (postfix != null && postfix.owner == owner)
				{
					return true;
				}
			}
			return false;
		}

		private static void RespawnExtraLifeShrinePostfix(CharacterMaster __instance)
		{
			//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_0048: Unknown result type (might be due to invalid IL or missing references)
			//IL_004a: Invalid comparison between Unknown and I4
			//IL_004d: 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)
			if (!NetworkServer.active || (Object)(object)__instance == (Object)null)
			{
				return;
			}
			CharacterBody body = __instance.GetBody();
			if ((Object)(object)body == (Object)null || (Object)(object)body.healthComponent == (Object)null || !body.healthComponent.alive)
			{
				return;
			}
			BodyIndex val = BodyCatalog.FindBodyIndex("M4A1Body");
			if ((int)val == -1 || body.bodyIndex != val)
			{
				return;
			}
			try
			{
				M4A1.ReassignDummyLinks(body);
				if (_log != null)
				{
					_log.LogInfo((object)"ShrineAIRecovery: dummy leaders reassigned after Shrine of Shaping revive.");
				}
			}
			catch (Exception ex)
			{
				if (_log != null)
				{
					_log.LogError((object)("ShrineAIRecovery repair failed: " + ex.GetType().Name + ": " + ex.Message));
				}
			}
		}
	}
}
namespace M4A1Fix.Compatibility
{
	internal static class M4A1Compatibility
	{
		private sealed class DecodedInstruction
		{
			internal int Offset;

			internal OpCode OpCode;

			internal object Operand;
		}

		internal const string ExpectedM4A1AssemblySha256 = "600188D306782F6C1B26EA1BDA4A615BFF9AC4437318873D5728CD58B26A7154";

		private const string ExpectedM4A1OnBodyDeathIlSha256 = "79150F880D5A6361BEF0E94A78AABB57B7A56881565C843E8A3D2614D65FE5E4";

		private const int ExpectedTryReviveCodeSize = 632;

		private const int ExpectedRespawnExtraLifeShrineCodeSize = 264;

		private const int ExpectedPendingStateCodeSize = 75;

		private const string ExpectedTryReviveCanonicalSha256 = "43D299DDC88201E148A1D914092175CB58285EDB1683822D825FDCC8E4163395";

		private const string ExpectedRespawnExtraLifeShrineCanonicalSha256 = "28D947645DE9C1E53D7C25591434D8B75ECB2E2E42492DF482D9A57296428E1F";

		private const string ExpectedPendingStateCanonicalSha256 = "7DDA7E015613A20CBD68394435B95ECE885557F7875832D063F853B764157F74";

		internal const string M4A1DummyLinkHooksTypeName = "M4A1Mod.Survivors.M4A1.Hooks.DummyLink";

		internal const string M4A1UtilsTypeName = "M4A1Mod.Survivors.M4A1.Utils.M4A1";

		private static readonly Dictionary<ushort, OpCode> OpCodeMap = BuildOpCodeMap();

		internal static bool TryValidateRevivePriorityTarget(out MethodInfo target, out string reason)
		{
			target = null;
			reason = null;
			Assembly assembly = AppDomain.CurrentDomain.GetAssemblies().FirstOrDefault((Assembly a) => string.Equals(a.GetName().Name, "M4A1Mod", StringComparison.Ordinal));
			if (assembly == null)
			{
				reason = "M4A1Mod assembly is not loaded.";
				return false;
			}
			if (!ValidateAssemblyHash(assembly, "600188D306782F6C1B26EA1BDA4A615BFF9AC4437318873D5728CD58B26A7154", "M4A1Mod.dll", out reason))
			{
				return false;
			}
			if (typeof(CharacterMaster).Assembly != typeof(CharacterBody).Assembly)
			{
				reason = "RoR2 CharacterMaster and CharacterBody are not from the same assembly.";
				return false;
			}
			Type type = assembly.GetType("M4A1Mod.Survivors.M4A1.Hooks.DummyLink", throwOnError: false);
			if (type == null)
			{
				reason = "M4A1 DummyLink hook type is missing.";
				return false;
			}
			target = type.GetMethod("CharacterMaster_OnBodyDeath", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
			if (!ValidateM4A1TargetShape(target, out reason))
			{
				target = null;
				return false;
			}
			if (!ValidateMethodIlHash(target, "79150F880D5A6361BEF0E94A78AABB57B7A56881565C843E8A3D2614D65FE5E4", "M4A1 CharacterMaster_OnBodyDeath", out reason))
			{
				target = null;
				return false;
			}
			MethodInfo method = typeof(CharacterMaster).GetMethod("TryReviveOnBodyDeath", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, new Type[1] { typeof(CharacterBody) }, null);
			if (method == null || method.ReturnType != typeof(bool) || !ValidateTryReviveStructure(method, out reason))
			{
				target = null;
				if (reason == null)
				{
					reason = "RoR2 TryReviveOnBodyDeath signature mismatch.";
				}
				return false;
			}
			MethodInfo method2 = typeof(CharacterMaster).GetMethod("RespawnExtraLifeShrine", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, Type.EmptyTypes, null);
			if (method2 == null || method2.ReturnType != typeof(void) || !ValidateShrineRespawnStructure(method2, out reason))
			{
				target = null;
				if (reason == null)
				{
					reason = "RoR2 RespawnExtraLifeShrine signature mismatch.";
				}
				return false;
			}
			MethodInfo method3 = typeof(CharacterMaster).GetMethod("IsExtraLifePendingServer", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, Type.EmptyTypes, null);
			if (method3 == null || method3.ReturnType != typeof(bool) || !ValidatePendingStateStructure(method3, out reason))
			{
				target = null;
				if (reason == null)
				{
					reason = "RoR2 IsExtraLifePendingServer signature mismatch.";
				}
				return false;
			}
			return true;
		}

		private static bool ValidateM4A1TargetShape(MethodInfo method, out string reason)
		{
			reason = null;
			if (method == null || !method.IsStatic || method.ReturnType != typeof(void))
			{
				reason = "M4A1 CharacterMaster_OnBodyDeath method shape is missing or incompatible.";
				return false;
			}
			ParameterInfo[] parameters = method.GetParameters();
			if (parameters.Length != 3 || parameters[0].ParameterType.Name != "orig_OnBodyDeath" || parameters[1].ParameterType != typeof(CharacterMaster) || parameters[2].ParameterType != typeof(CharacterBody))
			{
				reason = "M4A1 CharacterMaster_OnBodyDeath parameter shape is incompatible.";
				return false;
			}
			return true;
		}

		private static bool ValidateAssemblyHash(Assembly assembly, string expected, string displayName, out string reason)
		{
			reason = null;
			string location = assembly.Location;
			if (string.IsNullOrEmpty(location) || !File.Exists(location))
			{
				reason = displayName + " location is unavailable for compatibility hashing.";
				return false;
			}
			string text;
			try
			{
				using FileStream inputStream = File.OpenRead(location);
				using SHA256 sHA = SHA256.Create();
				text = ToHex(sHA.ComputeHash(inputStream));
			}
			catch (IOException ex)
			{
				reason = displayName + " hash read failed: " + ex.Message;
				return false;
			}
			catch (UnauthorizedAccessException ex2)
			{
				reason = displayName + " hash read denied: " + ex2.Message;
				return false;
			}
			if (!string.Equals(text, expected, StringComparison.OrdinalIgnoreCase))
			{
				reason = displayName + " SHA-256 mismatch. Expected " + expected + ", got " + text + ".";
				return false;
			}
			return true;
		}

		private static bool ValidateTryReviveStructure(MethodInfo method, out string reason)
		{
			if (!ValidateCanonicalFingerprint(method, 632, "43D299DDC88201E148A1D914092175CB58285EDB1683822D825FDCC8E4163395", "RoR2 TryReviveOnBodyDeath", out reason))
			{
				return false;
			}
			if (!TryDecode(method, 632, "RoR2 TryReviveOnBodyDeath", out var instructions, out reason))
			{
				return false;
			}
			int num = FindMember(instructions, "DLC2Content+Buffs::ExtraLifeBuff", 0);
			int num2 = FindMember(instructions, "CharacterBody::HasBuff", num + 1);
			int num3 = FindMember(instructions, "CharacterBody::RemoveBuff", num2 + 1);
			int num4 = FindString(instructions, "RespawnExtraLifeShrine", num3 + 1);
			int num5 = FindMember(instructions, "UnityEngine.MonoBehaviour::Invoke(System.String,System.Single)", num4 + 1);
			int num6 = FindString(instructions, "PlayExtraLifeSFX", num5 + 1);
			int num7 = FindMember(instructions, "UnityEngine.MonoBehaviour::Invoke(System.String,System.Single)", num6 + 1);
			int num8 = FindMember(instructions, "ItemTransformation::TryTake", 0);
			if (num < 0 || num2 <= num || num3 <= num2 || num4 <= num3 || num5 <= num4 || num6 <= num5 || num7 <= num6 || (num8 >= 0 && num8 <= num7))
			{
				reason = "RoR2 TryReviveOnBodyDeath normalized structural fingerprint mismatch in Shrine-before-Dio decision landmarks.";
				return false;
			}
			return true;
		}

		private static bool ValidateShrineRespawnStructure(MethodInfo method, out string reason)
		{
			if (!ValidateCanonicalFingerprint(method, 264, "28D947645DE9C1E53D7C25591434D8B75ECB2E2E42492DF482D9A57296428E1F", "RoR2 RespawnExtraLifeShrine", out reason))
			{
				return false;
			}
			if (!TryDecode(method, 264, "RoR2 RespawnExtraLifeShrine", out var instructions, out reason))
			{
				return false;
			}
			int num = FindMember(instructions, "CharacterMaster::Respawn", 0);
			int num2 = FindMember(instructions, "RoR2Content+Buffs::Immune", 0);
			int num3 = FindMember(instructions, "CharacterBody::AddTimedBuff", (num2 >= 0) ? num2 : 0);
			if (num < 0 || num2 < 0 || num3 < num2)
			{
				reason = "RoR2 RespawnExtraLifeShrine normalized structural fingerprint mismatch in respawn/immunity landmarks.";
				return false;
			}
			return true;
		}

		private static bool ValidatePendingStateStructure(MethodInfo method, out string reason)
		{
			if (!ValidateCanonicalFingerprint(method, 75, "7DDA7E015613A20CBD68394435B95ECE885557F7875832D063F853B764157F74", "RoR2 IsExtraLifePendingServer", out reason))
			{
				return false;
			}
			if (!TryDecode(method, 75, "RoR2 IsExtraLifePendingServer", out var instructions, out reason))
			{
				return false;
			}
			int num = FindMember(instructions, "ExtraLifeServerBehavior", 0);
			int num2 = FindString(instructions, "RespawnSeekerRevive", 0);
			int num3 = FindString(instructions, "RespawnExtraLifeShrine", num2 + 1);
			int num4 = FindString(instructions, "RespawnExtraLifeHealAndRevive", num3 + 1);
			if (num < 0 || num2 < 0 || num3 <= num2 || num4 <= num3)
			{
				reason = "RoR2 IsExtraLifePendingServer normalized structural fingerprint mismatch in pending-revive landmarks.";
				return false;
			}
			return true;
		}

		private static bool ValidateCanonicalFingerprint(MethodInfo method, int expectedCodeSize, string expectedHash, string displayName, out string reason)
		{
			reason = null;
			if (!TryBuildCanonicalMethod(method, expectedCodeSize, displayName, out var canonical, out reason))
			{
				return false;
			}
			string text;
			using (SHA256 sHA = SHA256.Create())
			{
				text = ToHex(sHA.ComputeHash(Encoding.UTF8.GetBytes(canonical)));
			}
			if (!string.Equals(text, expectedHash, StringComparison.OrdinalIgnoreCase))
			{
				reason = displayName + " canonical IL SHA-256 mismatch. Expected " + expectedHash + ", got " + text + ".";
				return false;
			}
			return true;
		}

		private static bool TryBuildCanonicalMethod(MethodInfo method, int expectedCodeSize, string displayName, out string canonical, out string reason)
		{
			canonical = null;
			reason = null;
			if (!TryDecode(method, expectedCodeSize, displayName, out var instructions, out reason))
			{
				return false;
			}
			try
			{
				MethodBody methodBody = method.GetMethodBody();
				if (methodBody == null)
				{
					reason = displayName + " has no method body.";
					return false;
				}
				Dictionary<int, int> dictionary = new Dictionary<int, int>();
				for (int i = 0; i < instructions.Count; i++)
				{
					dictionary.Add(instructions[i].Offset, i);
				}
				dictionary.Add(expectedCodeSize, instructions.Count);
				List<string> list = new List<string>();
				list.Add("CANONICAL_IL_V1");
				list.Add("METHOD=" + CanonicalMethodIdentity(method));
				list.Add("INIT_LOCALS=" + (methodBody.InitLocals ? "1" : "0"));
				list.Add("MAX_STACK=" + methodBody.MaxStackSize.ToString(CultureInfo.InvariantCulture));
				IList<LocalVariableInfo> localVariables = methodBody.LocalVariables;
				list.Add("LOCALS=" + localVariables.Count.ToString(CultureInfo.InvariantCulture));
				for (int j = 0; j < localVariables.Count; j++)
				{
					LocalVariableInfo localVariableInfo = localVariables[j];
					list.Add("LOCAL[" + j.ToString(CultureInfo.InvariantCulture) + "]=T=" + CanonicalTypeIdentity(localVariableInfo.LocalType) + ";PINNED=" + (localVariableInfo.IsPinned ? "1" : "0"));
				}
				list.Add("INSTRUCTIONS=" + instructions.Count.ToString(CultureInfo.InvariantCulture));
				for (int k = 0; k < instructions.Count; k++)
				{
					DecodedInstruction decodedInstruction = instructions[k];
					if (!TryCanonicalOperand(decodedInstruction, dictionary, out var canonical2, out reason))
					{
						reason = displayName + " canonical operand failed at instruction " + k.ToString(CultureInfo.InvariantCulture) + ": " + reason;
						return false;
					}
					list.Add("I[" + k.ToString(CultureInfo.InvariantCulture) + "]=OP=" + decodedInstruction.OpCode.Name + ";O=" + canonical2);
				}
				IList<ExceptionHandlingClause> exceptionHandlingClauses = methodBody.ExceptionHandlingClauses;
				list.Add("EH=" + exceptionHandlingClauses.Count.ToString(CultureInfo.InvariantCulture));
				for (int l = 0; l < exceptionHandlingClauses.Count; l++)
				{
					ExceptionHandlingClause exceptionHandlingClause = exceptionHandlingClauses[l];
					if (!dictionary.TryGetValue(exceptionHandlingClause.TryOffset, out var value) || !dictionary.TryGetValue(exceptionHandlingClause.TryOffset + exceptionHandlingClause.TryLength, out var value2) || !dictionary.TryGetValue(exceptionHandlingClause.HandlerOffset, out var value3) || !dictionary.TryGetValue(exceptionHandlingClause.HandlerOffset + exceptionHandlingClause.HandlerLength, out var value4))
					{
						reason = displayName + " has an EH boundary outside canonical instruction boundaries.";
						return false;
					}
					string text = "NONE";
					string text2 = "NONE";
					string text3;
					if (exceptionHandlingClause.Flags == ExceptionHandlingClauseOptions.Clause)
					{
						text3 = "CATCH";
						if (exceptionHandlingClause.CatchType == null)
						{
							reason = displayName + " has a catch clause without a catch type.";
							return false;
						}
						text2 = CanonicalTypeIdentity(exceptionHandlingClause.CatchType);
					}
					else if (exceptionHandlingClause.Flags == ExceptionHandlingClauseOptions.Filter)
					{
						text3 = "FILTER";
						if (!dictionary.TryGetValue(exceptionHandlingClause.FilterOffset, out var value5))
						{
							reason = displayName + " has a filter boundary outside canonical instruction boundaries.";
							return false;
						}
						text = value5.ToString(CultureInfo.InvariantCulture);
					}
					else if (exceptionHandlingClause.Flags == ExceptionHandlingClauseOptions.Finally)
					{
						text3 = "FINALLY";
					}
					else
					{
						if (exceptionHandlingClause.Flags != ExceptionHandlingClauseOptions.Fault)
						{
							reason = displayName + " has an unsupported EH clause kind: " + exceptionHandlingClause.Flags.ToString() + ".";
							return false;
						}
						text3 = "FAULT";
					}
					list.Add("EH[" + l.ToString(CultureInfo.InvariantCulture) + "]=K=" + text3 + ";TRY=" + value.ToString(CultureInfo.InvariantCulture) + ".." + value2.ToString(CultureInfo.InvariantCulture) + ";HANDLER=" + value3.ToString(CultureInfo.InvariantCulture) + ".." + value4.ToString(CultureInfo.InvariantCulture) + ";FILTER=" + text + ";CATCH=" + text2);
				}
				canonical = string.Join("\n", list.ToArray()) + "\n";
				return true;
			}
			catch (Exception ex)
			{
				reason = displayName + " canonicalization failed closed: " + ex.GetType().Name + ": " + ex.Message;
				return false;
			}
		}

		private static bool TryCanonicalOperand(DecodedInstruction instruction, Dictionary<int, int> offsetToIndex, out string canonical, out string reason)
		{
			canonical = null;
			reason = null;
			object operand = instruction.Operand;
			switch (instruction.OpCode.OperandType)
			{
			case OperandType.InlineNone:
				canonical = "NONE";
				return true;
			case OperandType.ShortInlineI:
				if (!(operand is sbyte b))
				{
					reason = "ShortInlineI operand type mismatch.";
					return false;
				}
				canonical = "I=" + b.ToString(CultureInfo.InvariantCulture);
				return true;
			case OperandType.InlineI:
				if (!(operand is int num3))
				{
					reason = "InlineI operand type mismatch.";
					return false;
				}
				canonical = "I=" + num3.ToString(CultureInfo.InvariantCulture);
				return true;
			case OperandType.InlineI8:
				if (!(operand is long num4))
				{
					reason = "InlineI8 operand type mismatch.";
					return false;
				}
				canonical = "I=" + num4.ToString(CultureInfo.InvariantCulture);
				return true;
			case OperandType.ShortInlineR:
				if (!(operand is float value3))
				{
					reason = "ShortInlineR operand type mismatch.";
					return false;
				}
				canonical = "R4=" + BitConverter.ToUInt32(BitConverter.GetBytes(value3), 0).ToString("X8", CultureInfo.InvariantCulture);
				return true;
			case OperandType.InlineR:
				if (!(operand is double value2))
				{
					reason = "InlineR operand type mismatch.";
					return false;
				}
				canonical = "R8=" + BitConverter.ToUInt64(BitConverter.GetBytes(value2), 0).ToString("X16", CultureInfo.InvariantCulture);
				return true;
			case OperandType.InlineVar:
			case OperandType.ShortInlineVar:
			{
				int num;
				if (operand is byte b2)
				{
					num = b2;
				}
				else
				{
					if (!(operand is ushort num2))
					{
						reason = "variable operand type mismatch.";
						return false;
					}
					num = num2;
				}
				string text = instruction.OpCode.Name ?? string.Empty;
				string text2 = ((text.IndexOf("arg", StringComparison.Ordinal) >= 0) ? "ARG" : ((text.IndexOf("loc", StringComparison.Ordinal) >= 0) ? "LOC" : "VAR"));
				canonical = text2 + "=" + num.ToString(CultureInfo.InvariantCulture);
				return true;
			}
			case OperandType.InlineBrTarget:
			case OperandType.ShortInlineBrTarget:
			{
				if (!(operand is int key))
				{
					reason = "branch operand type mismatch.";
					return false;
				}
				if (!offsetToIndex.TryGetValue(key, out var value5))
				{
					reason = "branch target is not an instruction boundary.";
					return false;
				}
				canonical = "BR=" + value5.ToString(CultureInfo.InvariantCulture);
				return true;
			}
			case OperandType.InlineSwitch:
			{
				if (!(operand is int[] array))
				{
					reason = "switch operand type mismatch.";
					return false;
				}
				string[] array2 = new string[array.Length];
				for (int i = 0; i < array.Length; i++)
				{
					if (!offsetToIndex.TryGetValue(array[i], out var value4))
					{
						reason = "switch target is not an instruction boundary.";
						return false;
					}
					array2[i] = value4.ToString(CultureInfo.InvariantCulture);
				}
				canonical = "SW=[" + string.Join(",", array2) + "]";
				return true;
			}
			case OperandType.InlineString:
				if (!(operand is string value))
				{
					reason = "string token did not resolve to a string.";
					return false;
				}
				canonical = "STR=" + Utf8Hex(value);
				return true;
			case OperandType.InlineField:
			case OperandType.InlineMethod:
			case OperandType.InlineTok:
			case OperandType.InlineType:
				canonical = "META=" + CanonicalMemberIdentity(operand);
				return true;
			case OperandType.InlineSig:
				reason = "InlineSig is forbidden by CANONICAL_IL_V1.";
				return false;
			default:
				reason = "unsupported operand type " + instruction.OpCode.OperandType.ToString() + ".";
				return false;
			}
		}

		private static string CanonicalMemberIdentity(object member)
		{
			if (member is Type type)
			{
				return CanonicalTypeIdentity(type);
			}
			if (member is FieldInfo field)
			{
				return CanonicalFieldIdentity(field);
			}
			if (member is MethodBase method)
			{
				return CanonicalMethodIdentity(method);
			}
			if (member is MemberInfo memberInfo)
			{
				throw new InvalidDataException("Unsupported resolved member kind: " + memberInfo.MemberType);
			}
			throw new InvalidDataException("Metadata operand did not resolve to a supported semantic member.");
		}

		private static string CanonicalFieldIdentity(FieldInfo field)
		{
			Type declaringType = field.DeclaringType;
			if (declaringType == null)
			{
				throw new InvalidDataException("Field has no declaring type.");
			}
			FieldInfo fieldInfo = field;
			if (declaringType.IsGenericType && !declaringType.IsGenericTypeDefinition)
			{
				fieldInfo = declaringType.GetGenericTypeDefinition().GetFields(BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic).FirstOrDefault((FieldInfo candidate) => candidate.MetadataToken == field.MetadataToken);
				if (fieldInfo == null)
				{
					throw new InvalidDataException("Could not recover generic field definition by metadata token.");
				}
			}
			return "F(D=" + CanonicalTypeIdentity(declaringType) + ";N=" + Utf8Hex(field.Name) + ";T=" + CanonicalTypeIdentity(fieldInfo.FieldType) + ")";
		}

		private static string CanonicalMethodIdentity(MethodBase method)
		{
			if (method is MethodInfo { IsGenericMethod: not false, IsGenericMethodDefinition: false } methodInfo)
			{
				return "MS(B=" + CanonicalMethodIdentity(methodInfo.GetGenericMethodDefinition()) + ";A=[" + string.Join(",", methodInfo.GetGenericArguments().Select(CanonicalTypeIdentity).ToArray()) + "])";
			}
			Type declaringType = method.DeclaringType;
			if (declaringType == null)
			{
				throw new InvalidDataException("Method has no declaring type.");
			}
			MethodBase methodBase = method;
			if (declaringType.IsGenericType && !declaringType.IsGenericTypeDefinition)
			{
				Type genericTypeDefinition = declaringType.GetGenericTypeDefinition();
				methodBase = genericTypeDefinition.GetMethods(BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic).Cast<MethodBase>().Concat(genericTypeDefinition.GetConstructors(BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic).Cast<MethodBase>())
					.FirstOrDefault((MethodBase candidate) => candidate.MetadataToken == method.MetadataToken);
				if (methodBase == null)
				{
					throw new InvalidDataException("Could not recover generic method definition by metadata token.");
				}
			}
			MethodInfo methodInfo2 = methodBase as MethodInfo;
			Type type = ((methodInfo2 != null) ? methodInfo2.ReturnType : typeof(void));
			int num = (methodBase.IsGenericMethod ? methodBase.GetGenericArguments().Length : 0);
			ParameterInfo[] parameters = methodBase.GetParameters();
			return "M(D=" + CanonicalTypeIdentity(declaringType) + ";N=" + Utf8Hex(method.Name) + ";S=" + (method.IsStatic ? "1" : "0") + ";GA=" + num.ToString(CultureInfo.InvariantCulture) + ";R=" + CanonicalTypeIdentity(type) + ";P=[" + string.Join(",", parameters.Select((ParameterInfo p) => CanonicalTypeIdentity(p.ParameterType)).ToArray()) + "])";
		}

		private static string CanonicalTypeIdentity(Type type)
		{
			if (type == null)
			{
				throw new InvalidDataException("Null type cannot be canonicalized.");
			}
			if (type.IsByRef)
			{
				return "BR(" + CanonicalTypeIdentity(type.GetElementType()) + ")";
			}
			if (type.IsPointer)
			{
				return "PTR(" + CanonicalTypeIdentity(type.GetElementType()) + ")";
			}
			if (type.IsArray)
			{
				Type elementType = type.GetElementType();
				if (type.GetArrayRank() == 1 && type == elementType.MakeArrayType())
				{
					return "SZ(" + CanonicalTypeIdentity(elementType) + ")";
				}
				return "AR(R=" + type.GetArrayRank().ToString(CultureInfo.InvariantCulture) + ";T=" + CanonicalTypeIdentity(elementType) + ";S=;L=)";
			}
			if (type.IsGenericParameter)
			{
				string text = ((type.DeclaringMethod != null) ? "M" : "T");
				return "GP(" + text + ";P=" + type.GenericParameterPosition.ToString(CultureInfo.InvariantCulture) + ")";
			}
			if (type.IsGenericType && !type.IsGenericTypeDefinition)
			{
				Type genericTypeDefinition = type.GetGenericTypeDefinition();
				return "GI(D=" + CanonicalNamedType(genericTypeDefinition) + ";A=[" + string.Join(",", type.GetGenericArguments().Select(CanonicalTypeIdentity).ToArray()) + "])";
			}
			return CanonicalNamedType(type);
		}

		private static string CanonicalNamedType(Type type)
		{
			string fullName = type.FullName;
			if (string.IsNullOrEmpty(fullName))
			{
				throw new InvalidDataException("Named type has no FullName: " + type);
			}
			string text = type.Assembly.GetName().Name;
			if (string.IsNullOrEmpty(text))
			{
				throw new InvalidDataException("Named type has no assembly simple name: " + fullName);
			}
			switch (text)
			{
			case "mscorlib":
			case "System":
			case "System.Core":
			case "netstandard":
				text = "CORE";
				break;
			}
			return "N(A=" + Utf8Hex(text) + ";F=" + Utf8Hex(fullName) + ")";
		}

		private static string Utf8Hex(string value)
		{
			return ToHex(Encoding.UTF8.GetBytes(value));
		}

		private static bool ValidateMethodIlHash(MethodInfo method, string expected, string displayName, out string reason)
		{
			reason = null;
			byte[] array = method.GetMethodBody()?.GetILAsByteArray();
			if (array == null)
			{
				reason = displayName + " has no inspectable IL body.";
				return false;
			}
			string text;
			using (SHA256 sHA = SHA256.Create())
			{
				text = ToHex(sHA.ComputeHash(array));
			}
			if (!string.Equals(text, expected, StringComparison.OrdinalIgnoreCase))
			{
				reason = displayName + " IL SHA-256 mismatch. Expected " + expected + ", got " + text + ".";
				return false;
			}
			return true;
		}

		private static Dictionary<ushort, OpCode> BuildOpCodeMap()
		{
			Dictionary<ushort, OpCode> dictionary = new Dictionary<ushort, OpCode>();
			FieldInfo[] fields = typeof(OpCodes).GetFields(BindingFlags.Static | BindingFlags.Public);
			foreach (FieldInfo fieldInfo in fields)
			{
				if (!(fieldInfo.FieldType != typeof(OpCode)))
				{
					OpCode value = (OpCode)fieldInfo.GetValue(null);
					dictionary[(ushort)value.Value] = value;
				}
			}
			return dictionary;
		}

		private static bool TryDecode(MethodInfo method, int expectedCodeSize, string displayName, out List<DecodedInstruction> instructions, out string reason)
		{
			instructions = null;
			reason = null;
			byte[] array = method.GetMethodBody()?.GetILAsByteArray();
			if (array == null)
			{
				reason = displayName + " has no inspectable IL body.";
				return false;
			}
			if (array.Length != expectedCodeSize)
			{
				reason = displayName + " IL code-size mismatch. Expected " + expectedCodeSize.ToString(CultureInfo.InvariantCulture) + ", got " + array.Length.ToString(CultureInfo.InvariantCulture) + ".";
				return false;
			}
			List<DecodedInstruction> list = new List<DecodedInstruction>();
			int num = 0;
			try
			{
				while (num < array.Length)
				{
					int offset = num;
					ushort num2 = array[num++];
					if (num2 == 254)
					{
						if (num >= array.Length)
						{
							throw new InvalidDataException("truncated two-byte opcode");
						}
						num2 = (ushort)(0xFE00 | array[num++]);
					}
					if (!OpCodeMap.TryGetValue(num2, out var value))
					{
						throw new InvalidDataException("unknown opcode 0x" + num2.ToString("X4"));
					}
					object obj = null;
					switch (value.OperandType)
					{
					case OperandType.ShortInlineI:
						obj = (sbyte)array[num++];
						break;
					case OperandType.InlineI:
						obj = BitConverter.ToInt32(array, num);
						num += 4;
						break;
					case OperandType.InlineI8:
						obj = BitConverter.ToInt64(array, num);
						num += 8;
						break;
					case OperandType.ShortInlineR:
						obj = BitConverter.ToSingle(array, num);
						num += 4;
						break;
					case OperandType.InlineR:
						obj = BitConverter.ToDouble(array, num);
						num += 8;
						break;
					case OperandType.ShortInlineVar:
						obj = array[num++];
						break;
					case OperandType.InlineVar:
						obj = BitConverter.ToUInt16(array, num);
						num += 2;
						break;
					case OperandType.ShortInlineBrTarget:
						obj = num + 1 + (sbyte)array[num];
						num++;
						break;
					case OperandType.InlineBrTarget:
					{
						int num5 = BitConverter.ToInt32(array, num);
						num += 4;
						obj = num + num5;
						break;
					}
					case OperandType.InlineSwitch:
					{
						int num3 = BitConverter.ToInt32(array, num);
						num += 4;
						int num4 = num + 4 * num3;
						int[] array2 = new int[num3];
						for (int i = 0; i < num3; i++)
						{
							array2[i] = num4 + BitConverter.ToInt32(array, num);
							num += 4;
						}
						obj = array2;
						break;
					}
					case OperandType.InlineString:
					{
						int metadataToken5 = BitConverter.ToInt32(array, num);
						num += 4;
						obj = method.Module.ResolveString(metadataToken5);
						if (obj == null)
						{
							throw new InvalidDataException("unresolved string token");
						}
						break;
					}
					case OperandType.InlineField:
					{
						int metadataToken4 = BitConverter.ToInt32(array, num);
						num += 4;
						obj = method.Module.ResolveField(metadataToken4, method.DeclaringType.GetGenericArguments(), method.GetGenericArguments());
						if (obj == null)
						{
							throw new InvalidDataException("unresolved field token");
						}
						break;
					}
					case OperandType.InlineMethod:
					{
						int metadataToken3 = BitConverter.ToInt32(array, num);
						num += 4;
						obj = method.Module.ResolveMethod(metadataToken3, method.DeclaringType.GetGenericArguments(), method.GetGenericArguments());
						if (obj == null)
						{
							throw new InvalidDataException("unresolved method token");
						}
						break;
					}
					case OperandType.InlineType:
					{
						int metadataToken2 = BitConverter.ToInt32(array, num);
						num += 4;
						obj = method.Module.ResolveType(metadataToken2, method.DeclaringType.GetGenericArguments(), method.GetGenericArguments());
						if (obj == null)
						{
							throw new InvalidDataException("unresolved type token");
						}
						break;
					}
					case OperandType.InlineTok:
					{
						int metadataToken = BitConverter.ToInt32(array, num);
						num += 4;
						obj = method.Module.ResolveMember(metadataToken, method.DeclaringType.GetGenericArguments(), method.GetGenericArguments());
						if (obj == null)
						{
							throw new InvalidDataException("unresolved member token");
						}
						break;
					}
					case OperandType.InlineSig:
						throw new InvalidDataException("InlineSig is not accepted by this fail-closed contract");
					default:
						throw new InvalidDataException("unsupported operand type " + value.OperandType);
					case OperandType.InlineNone:
						break;
					}
					list.Add(new DecodedInstruction
					{
						Offset = offset,
						OpCode = value,
						Operand = obj
					});
				}
			}
			catch (Exception ex)
			{
				reason = displayName + " normalized IL inspection failed closed: " + ex.GetType().Name + ": " + ex.Message;
				return false;
			}
			HashSet<int> offsets = new HashSet<int>(list.Select((DecodedInstruction instruction) => instruction.Offset));
			offsets.Add(array.Length);
			foreach (DecodedInstruction item2 in list)
			{
				if (item2.Operand is int item && (item2.OpCode.OperandType == OperandType.InlineBrTarget || item2.OpCode.OperandType == OperandType.ShortInlineBrTarget) && !offsets.Contains(item))
				{
					reason = displayName + " has a branch target outside instruction boundaries.";
					return false;
				}
				if (item2.Operand is int[] source && source.Any((int target) => !offsets.Contains(target)))
				{
					reason = displayName + " has an invalid switch target.";
					return false;
				}
			}
			instructions = list;
			return true;
		}

		private static string MemberId(object operand)
		{
			MemberInfo memberInfo = operand as MemberInfo;
			if (memberInfo == null)
			{
				return null;
			}
			if (memberInfo is MethodBase methodBase)
			{
				string text = string.Join(",", (from parameter in methodBase.GetParameters()
					select parameter.ParameterType.FullName ?? parameter.ParameterType.Name).ToArray());
				MethodInfo methodInfo = methodBase as MethodInfo;
				string text2 = ((methodInfo != null) ? (methodInfo.ReturnType.FullName ?? methodInfo.ReturnType.Name) : "void");
				return ((memberInfo.DeclaringType != null) ? memberInfo.DeclaringType.FullName : string.Empty) + "::" + memberInfo.Name + "(" + text + ")->" + text2;
			}
			if (memberInfo is FieldInfo fieldInfo)
			{
				return ((fieldInfo.DeclaringType != null) ? fieldInfo.DeclaringType.FullName : string.Empty) + "::" + fieldInfo.Name + ":" + (fieldInfo.FieldType.FullName ?? fieldInfo.FieldType.Name);
			}
			if (memberInfo is Type type)
			{
				return type.FullName;
			}
			return ((memberInfo.DeclaringType != null) ? memberInfo.DeclaringType.FullName : string.Empty) + "::" + memberInfo.Name;
		}

		private static int FindMember(List<DecodedInstruction> instructions, string contains, int start)
		{
			for (int i = Math.Max(start, 0); i < instructions.Count; i++)
			{
				string text = MemberId(instructions[i].Operand);
				if (text != null && text.Contains(contains))
				{
					return i;
				}
			}
			return -1;
		}

		private static int FindString(List<DecodedInstruction> instructions, string expected, int start)
		{
			for (int i = Math.Max(start, 0); i < instructions.Count; i++)
			{
				if (string.Equals(instructions[i].Operand as string, expected, StringComparison.Ordinal))
				{
					return i;
				}
			}
			return -1;
		}

		private static string ToHex(byte[] bytes)
		{
			return BitConverter.ToString(bytes).Replace("-", string.Empty);
		}
	}
}