Decompiled source of M4A1Fix v1.1.1

plugins/M4A1Fix/M4A1Fix.dll

Decompiled 3 weeks ago
using System;
using System.Collections;
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.Compatibility.IL;
using M4A1Fix.Patches;
using M4A1Fix.Patches.IL;
using M4A1Fix.Runtime;
using M4A1Mod.Survivors.M4A1.Utils;
using Microsoft.CodeAnalysis;
using RoR2;
using UnityEngine;
using UnityEngine.AddressableAssets;
using UnityEngine.Networking;
using UnityEngine.ResourceManagement.AsyncOperations;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: InternalsVisibleTo("M4A1Fix.Tests")]
[assembly: TargetFramework(".NETFramework,Version=v4.7.2", FrameworkDisplayName = ".NET Framework 4.7.2")]
[assembly: AssemblyCompany("M4A1Fix")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("1.1.0.0")]
[assembly: AssemblyInformationalVersion("1.1.0")]
[assembly: AssemblyProduct("M4A1Fix")]
[assembly: AssemblyTitle("M4A1Fix")]
[assembly: AssemblyVersion("1.1.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.1.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.1.0";

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

		private Harmony _revivePriorityHarmony;

		private Harmony _shrineAiRecoveryHarmony;

		private RunStartWarmup _runStartWarmup;

		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.1.0 loaded.");
			_revivePriorityHarmony = new Harmony("com.m4a1fix.revivepriority");
			if (!RevivePriorityPatch.TryInstall(_revivePriorityHarmony, 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));
				return;
			}
			((BaseUnityPlugin)this).Logger.LogInfo((object)"ShrineAIRecovery enabled.");
			if (RunStartWarmup.TryCreate(this, ((BaseUnityPlugin)this).Logger, out _runStartWarmup, out reason))
			{
				((BaseUnityPlugin)this).Logger.LogInfo((object)"RunStartWarmup enabled; waiting for game content.");
			}
			else
			{
				((BaseUnityPlugin)this).Logger.LogWarning((object)("RunStartWarmup disabled: " + reason));
			}
		}

		private void OnDestroy()
		{
			try
			{
				_runStartWarmup?.Dispose();
			}
			catch (Exception ex)
			{
				((BaseUnityPlugin)this).Logger.LogError((object)("RunStartWarmup cleanup failed: " + ex));
			}
			if (_shrineAiRecoveryHarmony != null && !HarmonyPatchLifecycle.TryUnpatch(_shrineAiRecoveryHarmony, out var reason))
			{
				((BaseUnityPlugin)this).Logger.LogError((object)("ShrineAIRecovery cleanup failed: " + reason));
			}
			if (_revivePriorityHarmony != null && !HarmonyPatchLifecycle.TryUnpatch(_revivePriorityHarmony, out var reason2))
			{
				((BaseUnityPlugin)this).Logger.LogError((object)("RevivePriority cleanup failed: " + reason2));
			}
		}
	}
}
namespace M4A1Fix.Runtime
{
	internal interface IAssetWarmupOperation : IDisposable
	{
		bool IsDone { get; }

		bool Succeeded { get; }
	}
	internal enum AssetWarmupStatus
	{
		Loading,
		Completed,
		Skipped,
		Failed
	}
	internal sealed class AssetWarmupJob : IEnumerator, IDisposable
	{
		private readonly List<object> _keys = new List<object>();

		private readonly List<IAssetWarmupOperation> _operations = new List<IAssetWarmupOperation>();

		private readonly Func<object, IAssetWarmupOperation> _load;

		private readonly Func<bool> _canContinue;

		private readonly Action _finalize;

		private readonly int _batchSize;

		private int _next;

		private bool _stopped;

		internal AssetWarmupStatus Status { get; private set; }

		internal string Error { get; private set; }

		internal int Count => _keys.Count;

		public object Current => null;

		internal AssetWarmupJob(IEnumerable<object> keys, Func<object, IAssetWarmupOperation> load, Func<bool> canContinue, Action finalize, int batchSize = 8)
		{
			if (keys == null)
			{
				throw new ArgumentNullException("keys");
			}
			_load = load ?? throw new ArgumentNullException("load");
			_canContinue = canContinue ?? throw new ArgumentNullException("canContinue");
			_finalize = finalize ?? throw new ArgumentNullException("finalize");
			if (batchSize <= 0)
			{
				throw new ArgumentOutOfRangeException("batchSize");
			}
			_batchSize = batchSize;
			HashSet<string> hashSet = new HashSet<string>(StringComparer.Ordinal);
			foreach (object key in keys)
			{
				if (key == null || string.IsNullOrEmpty(key.ToString()))
				{
					throw new ArgumentException("Asset keys must be nonempty.", "keys");
				}
				if (hashSet.Add(key.ToString()))
				{
					_keys.Add(key);
				}
			}
		}

		public bool MoveNext()
		{
			if (_stopped)
			{
				return false;
			}
			try
			{
				if (!_canContinue())
				{
					return Stop(AssetWarmupStatus.Skipped);
				}
				if (_next < _keys.Count)
				{
					int num = Math.Min(_next + _batchSize, _keys.Count);
					while (_next < num)
					{
						IAssetWarmupOperation assetWarmupOperation = _load(_keys[_next++]);
						if (assetWarmupOperation == null)
						{
							throw new InvalidOperationException("Asset loader returned no operation.");
						}
						_operations.Add(assetWarmupOperation);
					}
					return true;
				}
				foreach (IAssetWarmupOperation operation in _operations)
				{
					if (!operation.IsDone)
					{
						return true;
					}
					if (!operation.Succeeded)
					{
						throw new InvalidOperationException("An item-display asset failed to load.");
					}
				}
				if (!_canContinue())
				{
					return Stop(AssetWarmupStatus.Skipped);
				}
				_finalize();
				return Stop(AssetWarmupStatus.Completed);
			}
			catch (Exception ex)
			{
				Error = ex.GetType().Name + ": " + ex.Message;
				return Stop(AssetWarmupStatus.Failed);
			}
		}

		private bool Stop(AssetWarmupStatus status)
		{
			Status = status;
			Dispose();
			return false;
		}

		public void Dispose()
		{
			if (_stopped)
			{
				return;
			}
			_stopped = true;
			if (Status == AssetWarmupStatus.Loading)
			{
				Status = AssetWarmupStatus.Skipped;
			}
			foreach (IAssetWarmupOperation operation in _operations)
			{
				try
				{
					operation.Dispose();
				}
				catch (Exception ex)
				{
					Status = AssetWarmupStatus.Failed;
					Error = ((Error == null) ? "" : (Error + " ")) + "Asset release failed: " + ex.Message;
				}
			}
			_operations.Clear();
		}

		public void Reset()
		{
			throw new NotSupportedException();
		}
	}
	internal static class HarmonyPatchLifecycle
	{
		internal static bool TryUnpatch(Harmony harmony, out string reason)
		{
			reason = null;
			try
			{
				harmony.UnpatchSelf();
				return true;
			}
			catch (Exception ex)
			{
				reason = ex.ToString();
				return false;
			}
		}

		internal static bool HasTranspiler(MethodBase target, string owner, MethodInfo method)
		{
			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 && transpiler.PatchMethod == method)
				{
					return true;
				}
			}
			return false;
		}

		internal static bool HasPostfix(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;
		}
	}
}
namespace M4A1Fix.Patches
{
	internal static class RevivePriorityPatch
	{
		private static bool _transpilerMatched;

		private static readonly MethodInfo TranspilerMethod = AccessTools.Method(typeof(RevivePriorityPatch), "CharacterMasterOnBodyDeathTranspiler", (Type[])null, (Type[])null);

		internal static bool TryInstall(Harmony harmony, out MethodInfo target, out string reason)
		{
			//IL_004c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0058: Expected O, but got Unknown
			target = null;
			reason = null;
			_transpilerMatched = false;
			if (harmony == null)
			{
				reason = "Harmony owner was not created.";
				return false;
			}
			bool flag = false;
			try
			{
				if (!M4A1Compatibility.TryValidateRevivePriorityTarget(out target, out reason))
				{
					return false;
				}
				MethodInfo transpilerMethod = TranspilerMethod;
				if (transpilerMethod == null)
				{
					throw new InvalidOperationException("revive-priority transpiler method could not be resolved.");
				}
				flag = true;
				harmony.Patch((MethodBase)target, (HarmonyMethod)null, (HarmonyMethod)null, new HarmonyMethod(transpilerMethod), (HarmonyMethod)null, (HarmonyMethod)null);
				if (!IsHealthy(target))
				{
					throw new InvalidOperationException("revive-priority transpiler was not confirmed on the exact target.");
				}
				return true;
			}
			catch (Exception ex)
			{
				_transpilerMatched = false;
				reason = "installation failed: " + ex;
				if (flag && !HarmonyPatchLifecycle.TryUnpatch(harmony, out var reason2))
				{
					reason = reason + " Cleanup failed: " + reason2;
				}
				target = null;
				return false;
			}
		}

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

		private static IEnumerable<CodeInstruction> CharacterMasterOnBodyDeathTranspiler(IEnumerable<CodeInstruction> instructions, ILGenerator generator)
		{
			_transpilerMatched = false;
			IEnumerable<CodeInstruction> result = RevivePriorityTranspiler.Rewrite(instructions, generator);
			_transpilerMatched = true;
			return result;
		}
	}
	internal static class RevivePriorityPolicy
	{
		internal static bool ShouldDelegateShrineToVanilla(CharacterBody body)
		{
			if (NetworkServer.active && (Object)(object)body != (Object)null)
			{
				return RoR2ReviveContract.HasShrineBuff(body);
			}
			return false;
		}
	}
	internal sealed class RunStartWarmup : IDisposable
	{
		private sealed class AddressableOperation : IAssetWarmupOperation, IDisposable
		{
			private AsyncOperationHandle<Object> _handle;

			public bool IsDone => _handle.IsDone;

			public bool Succeeded
			{
				get
				{
					//IL_0006: Unknown result type (might be due to invalid IL or missing references)
					//IL_000c: Invalid comparison between Unknown and I4
					if ((int)_handle.Status == 1)
					{
						return _handle.Result is GameObject;
					}
					return false;
				}
			}

			internal AddressableOperation(object key)
			{
				//IL_0008: Unknown result type (might be due to invalid IL or missing references)
				//IL_000d: Unknown result type (might be due to invalid IL or missing references)
				_handle = Addressables.LoadAssetAsync<Object>(key);
			}

			public void Dispose()
			{
				//IL_000e: Unknown result type (might be due to invalid IL or missing references)
				if (_handle.IsValid())
				{
					Addressables.Release<Object>(_handle);
				}
			}
		}

		private readonly Plugin _owner;

		private readonly ManualLogSource _log;

		private readonly ItemDisplayWarmupContract _contract;

		private AssetWarmupJob _job;

		private Coroutine _coroutine;

		private bool _disposed;

		private RunStartWarmup(Plugin owner, ManualLogSource log, ItemDisplayWarmupContract contract)
		{
			_owner = owner;
			_log = log;
			_contract = contract;
			RoR2Application.onLoadFinished = (Action)Delegate.Combine(RoR2Application.onLoadFinished, new Action(Begin));
		}

		internal static bool TryCreate(Plugin owner, ManualLogSource log, out RunStartWarmup warmup, out string reason)
		{
			warmup = null;
			try
			{
				if (!ItemDisplayWarmupContract.TryResolve(out var contract, out reason))
				{
					return false;
				}
				warmup = new RunStartWarmup(owner, log, contract);
				return true;
			}
			catch (Exception ex)
			{
				reason = ex.ToString();
				return false;
			}
		}

		private void Begin()
		{
			RoR2Application.onLoadFinished = (Action)Delegate.Remove(RoR2Application.onLoadFinished, new Action(Begin));
			if (_disposed)
			{
				return;
			}
			try
			{
				_coroutine = ((MonoBehaviour)_owner).StartCoroutine(Prepare());
			}
			catch (Exception ex)
			{
				_log.LogWarning((object)("RunStartWarmup fallback: " + ex));
			}
		}

		private IEnumerator Prepare()
		{
			yield return null;
			_log.LogInfo((object)"RunStartWarmup: waiting for BodyCatalog.");
			IEnumerator readiness = WaitForCatalog(() => ((ResourceAvailability)(ref BodyCatalog.availability)).available, () => !_disposed && (Object)(object)Run.instance == (Object)null);
			while (readiness.MoveNext())
			{
				yield return null;
			}
			if (_disposed || (Object)(object)Run.instance != (Object)null)
			{
				_log.LogWarning((object)"RunStartWarmup: Skipped while waiting for catalog; original first-run path retained.");
				yield break;
			}
			Stopwatch timer = Stopwatch.StartNew();
			try
			{
				if ((Object)(object)Run.instance != (Object)null || _contract.IsInitialized)
				{
					yield break;
				}
				List<object> keys = CollectKeys();
				_job = new AssetWarmupJob(keys, (object key) => new AddressableOperation(key), () => !_disposed && (Object)(object)Run.instance == (Object)null, delegate
				{
					Stopwatch stopwatch = Stopwatch.StartNew();
					_contract.InitializeCache();
					_log.LogInfo((object)("RunStartWarmup: original cache initialized in " + stopwatch.ElapsedMilliseconds + " ms."));
				});
				_log.LogInfo((object)("RunStartWarmup: asynchronously preparing " + _job.Count + " display assets."));
			}
			catch (Exception ex)
			{
				_log.LogWarning((object)("RunStartWarmup fallback: " + ex));
			}
			if (_job != null)
			{
				while (_job.MoveNext())
				{
					yield return null;
				}
				if (_job.Status == AssetWarmupStatus.Completed)
				{
					_log.LogInfo((object)("RunStartWarmup: ready before first run; background elapsed " + timer.ElapsedMilliseconds + " ms."));
				}
				else
				{
					_log.LogWarning((object)("RunStartWarmup: " + _job.Status.ToString() + "; original first-run path retained. " + _job.Error));
				}
				_job.Dispose();
				_job = null;
				_coroutine = null;
			}
		}

		internal static IEnumerator WaitForCatalog(Func<bool> ready, Func<bool> canContinue)
		{
			while (canContinue() && !ready())
			{
				yield return null;
			}
		}

		private static List<object> CollectKeys()
		{
			//IL_00e1: 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_00e8: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ea: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f6: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f8: Unknown result type (might be due to invalid IL or missing references)
			//IL_010d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0112: Unknown result type (might be due to invalid IL or missing references)
			GameObject val = BodyCatalog.FindBodyPrefab("MercBody");
			ModelLocator val2 = (((Object)(object)val != (Object)null) ? val.GetComponent<ModelLocator>() : null);
			CharacterModel val3 = (((Object)(object)val2 != (Object)null && (Object)(object)val2.modelTransform != (Object)null) ? ((Component)val2.modelTransform).GetComponent<CharacterModel>() : null);
			if ((Object)(object)val3 == (Object)null || (Object)(object)val3.itemDisplayRuleSet == (Object)null)
			{
				throw new InvalidOperationException("MercBody item displays missing after BodyCatalog readiness: body=" + ((Object)(object)val != (Object)null) + ", locator=" + ((Object)(object)val2 != (Object)null) + ", model=" + ((Object)(object)val3 != (Object)null) + ".");
			}
			List<object> list = new List<object>();
			KeyAssetRuleGroup[] keyAssetRuleGroups = val3.itemDisplayRuleSet.keyAssetRuleGroups;
			foreach (KeyAssetRuleGroup val4 in keyAssetRuleGroups)
			{
				if (val4.displayRuleGroup.rules == null)
				{
					continue;
				}
				ItemDisplayRule[] rules = val4.displayRuleGroup.rules;
				foreach (ItemDisplayRule val5 in rules)
				{
					AssetReferenceGameObject followerPrefabAddress = val5.followerPrefabAddress;
					if ((Object)(object)val5.followerPrefab == (Object)null && followerPrefabAddress != null && ((AssetReference)followerPrefabAddress).RuntimeKeyIsValid())
					{
						list.Add(((AssetReference)followerPrefabAddress).RuntimeKey);
					}
				}
			}
			return list;
		}

		public void Dispose()
		{
			_disposed = true;
			RoR2Application.onLoadFinished = (Action)Delegate.Remove(RoR2Application.onLoadFinished, new Action(Begin));
			try
			{
				if (_coroutine != null)
				{
					((MonoBehaviour)_owner).StopCoroutine(_coroutine);
				}
			}
			finally
			{
				_job?.Dispose();
				if (_job?.Error != null)
				{
					_log.LogWarning((object)("RunStartWarmup cleanup: " + _job.Error));
				}
				_job = null;
				_coroutine = null;
			}
		}
	}
	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 static ManualLogSource _log;

		private static MethodInfo _revivePriorityTarget;

		internal static bool TryInstall(ManualLogSource log, MethodInfo revivePriorityTarget, out Harmony harmony, out string reason)
		{
			//IL_00f0: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f6: Expected O, but got Unknown
			//IL_0102: Unknown result type (might be due to invalid IL or missing references)
			//IL_010f: Expected O, but got Unknown
			_log = log;
			harmony = null;
			reason = null;
			_revivePriorityTarget = null;
			try
			{
				if (!RevivePriorityPatch.IsHealthy(revivePriorityTarget))
				{
					reason = "RevivePriority is not healthy on the exact M4A1 death target.";
					return false;
				}
				if (!M4A1AssemblyContract.TryValidateLoadedPlugin(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 (!MethodLookup.TryResolveSingleDeclaredMethod(type, "CharacterMaster_OnBodyDeath", out var method, out reason))
				{
					return false;
				}
				if (!method.Equals(revivePriorityTarget) || !RevivePriorityPatch.IsHealthy(method))
				{
					reason = "RevivePriority is not installed on the exact M4A1 CharacterMaster_OnBodyDeath target.";
					return false;
				}
				if (!MethodLookup.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;
				}
				harmony = new Harmony("com.m4a1fix.revivepriority/shrine-ai-recovery");
				_revivePriorityTarget = revivePriorityTarget;
				harmony.Patch((MethodBase)method2, (HarmonyMethod)null, new HarmonyMethod(method3), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
				if (!HarmonyPatchLifecycle.HasPostfix(method2, "com.m4a1fix.revivepriority/shrine-ai-recovery"))
				{
					throw new InvalidOperationException("Harmony did not report the ShrineAIRecovery postfix after installation.");
				}
				return true;
			}
			catch (Exception ex)
			{
				_revivePriorityTarget = null;
				reason = "installation exception: " + ex;
				if (harmony == null || HarmonyPatchLifecycle.TryUnpatch(harmony, out var reason2))
				{
					harmony = null;
				}
				else
				{
					reason = reason + " Cleanup failed: " + reason2;
				}
				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
			{
				if (!RevivePriorityPatch.IsHealthy(_revivePriorityTarget))
				{
					if (_log != null)
					{
						_log.LogError((object)"ShrineAIRecovery skipped: RevivePriority is no longer healthy on the exact death target.");
					}
					return;
				}
				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.Patches.IL
{
	internal static class RevivePriorityTranspiler
	{
		private readonly struct DeathHookDecision
		{
			internal int OriginalCallStart { get; }

			internal int DecisionLoadIndex { get; }

			internal DeathHookDecision(int originalCallStart, int decisionLoadIndex)
			{
				OriginalCallStart = originalCallStart;
				DecisionLoadIndex = decisionLoadIndex;
			}
		}

		private const int OriginalCallbackArgumentCount = 3;

		private const int BodyArgumentIndex = 2;

		internal static IEnumerable<CodeInstruction> Rewrite(IEnumerable<CodeInstruction> instructions, ILGenerator generator)
		{
			List<CodeInstruction> list = new List<CodeInstruction>(instructions);
			DeathHookDecision decision = FindDecision(list);
			InjectShrineDecision(list, generator, decision);
			return list;
		}

		private static DeathHookDecision FindDecision(List<CodeInstruction> codes)
		{
			int num = FindOrigOnBodyDeathInvoke(codes);
			int num2 = FindGetSomeChildDummyLink(codes);
			if (num < 3 || num2 < 1)
			{
				throw PatternMismatch("original callback or Dummy Link call is missing");
			}
			int num3 = num - 3;
			int num4 = num2 - 1;
			if (num3 >= num4)
			{
				throw PatternMismatch("original callback does not precede Dummy Link selection");
			}
			if (!IsLdarg(codes[num3], 0) || !IsLdarg(codes[num3 + 1], 1) || !IsLdarg(codes[num3 + 2], 2) || !IsLdarg(codes[num4], 2))
			{
				throw PatternMismatch("callback or Dummy Link argument loads changed");
			}
			int num5 = FindConditionalBranchTo(codes, num4, num3);
			if (num5 <= 0 || !IsLocalLoad(codes[num5 - 1]) || (codes[num5].opcode != OpCodes.Brfalse && codes[num5].opcode != OpCodes.Brfalse_S))
			{
				throw PatternMismatch("expected local Boolean/brfalse decision changed");
			}
			int decisionLoadIndex = num5 - 1;
			return new DeathHookDecision(num3, decisionLoadIndex);
		}

		private static void InjectShrineDecision(List<CodeInstruction> codes, ILGenerator generator, DeathHookDecision decision)
		{
			//IL_0094: Unknown result type (might be due to invalid IL or missing references)
			//IL_009e: Expected O, but got Unknown
			//IL_00a5: Unknown result type (might be due to invalid IL or missing references)
			//IL_00af: Expected O, but got Unknown
			//IL_00b6: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c0: Expected O, but got Unknown
			//IL_00cc: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d6: Expected O, but got Unknown
			Label label = generator.DefineLabel();
			Label label2 = generator.DefineLabel();
			codes[decision.OriginalCallStart].labels.Add(label);
			codes[decision.DecisionLoadIndex].labels.Add(label2);
			CodeInstruction item = CloneWithoutLabelsOrBlocks(codes[decision.DecisionLoadIndex]);
			MethodInfo methodInfo = AccessTools.Method(typeof(RevivePriorityPolicy), "ShouldDelegateShrineToVanilla", (Type[])null, (Type[])null);
			if (methodInfo == null)
			{
				throw PatternMismatch("Shrine decision helper is missing");
			}
			List<CodeInstruction> list = new List<CodeInstruction>
			{
				item,
				new CodeInstruction(OpCodes.Brtrue, (object)label2),
				new CodeInstruction(OpCodes.Ldarg_2, (object)null),
				new CodeInstruction(OpCodes.Call, (object)methodInfo),
				new CodeInstruction(OpCodes.Brtrue, (object)label)
			};
			MoveEntryMetadata(codes[decision.DecisionLoadIndex], list[0], label2);
			codes.InsertRange(decision.DecisionLoadIndex, list);
		}

		private static void MoveEntryMetadata(CodeInstruction originalDecisionLoad, CodeInstruction newEntry, Label continueOriginalDecisionLabel)
		{
			newEntry.labels.AddRange(originalDecisionLoad.labels.Where((Label label) => !label.Equals(continueOriginalDecisionLabel)));
			originalDecisionLoad.labels.RemoveAll((Label label) => !label.Equals(continueOriginalDecisionLabel));
			newEntry.blocks.AddRange(originalDecisionLoad.blocks);
			originalDecisionLoad.blocks.Clear();
		}

		private static InvalidOperationException PatternMismatch(string detail)
		{
			return new InvalidOperationException("M4A1 revive-priority IL pattern mismatch: " + detail + ".");
		}

		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)
		{
			int num = -1;
			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")
				{
					if (num >= 0)
					{
						throw PatternMismatch("Dummy Link selection is ambiguous");
					}
					num = i;
				}
			}
			return num;
		}

		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);
		}
	}
}
namespace M4A1Fix.Compatibility
{
	internal static class CurrentRoR2ReviveContract
	{
		internal const string AssemblySha256 = "A5F7D83C7E6872FDD6099FDC299ADB7D4FA03CBF465E526D6F5EFCC35C2C281D";

		internal static bool TryValidate(out Func<CharacterBody, BuffDef, bool> hasShrineBuff, out string reason)
		{
			hasShrineBuff = null;
			if (!TryValidateGameFile(typeof(CharacterMaster).Assembly.Location, Paths.ManagedPath, out reason))
			{
				return false;
			}
			if (!Validate("OnBodyDeath", typeof(void), new Type[1] { typeof(CharacterBody) }, 624, "1050368A38FE8F544DDC254F0F4BC2B02F4FA42C2689E2DF06A5032D25C6D169", out var instructions, out reason) || !Validate("TryReviveOnBodyDeath", typeof(bool), new Type[1] { typeof(CharacterBody) }, 763, "2F1B2850A74F9FFD70F21A5487E65FFB05872A4616BD6530940C3B6090BDCEAA", out var instructions2, out reason) || !Validate("RespawnExtraLifeShrine", typeof(void), Type.EmptyTypes, 256, "B3D08729347A0036302C1F6D2BD05EC0038CB7210F527E4990B1905AA7BDB8AA", out var instructions3, out reason) || !Validate("IsExtraLifePendingServer", typeof(bool), Type.EmptyTypes, 75, "7DDA7E015613A20CBD68394435B95ECE885557F7875832D063F853B764157F74", out var instructions4, out reason))
			{
				return false;
			}
			int num = IlMethodReader.FindMember(instructions2, "DLC2Content+Buffs::ExtraLifeBuff", 0);
			int num2 = IlMethodReader.FindMember(instructions2, "CharacterBody::HasUnmanagedBuff", num + 1);
			int num3 = IlMethodReader.FindMember(instructions2, "CharacterBody::RemoveUnmanagedBuff", num2 + 1);
			int num4 = IlMethodReader.FindString(instructions2, "RespawnExtraLifeShrine", num3 + 1);
			int num5 = IlMethodReader.FindMember(instructions2, "ItemTransformation::TryTake", num4 + 1);
			if (num < 0 || num2 <= num || num3 <= num2 || num4 <= num3 || num5 <= num4 || IlMethodReader.FindMember(instructions, "CharacterMaster::TryReviveOnBodyDeath", 0) < 0 || IlMethodReader.FindMember(instructions3, "CharacterMaster::Respawn", 0) < 0 || IlMethodReader.FindString(instructions4, "RespawnExtraLifeShrine", 0) < 0)
			{
				reason = "Current RoR2 Shrine-before-Dio/respawn/pending landmarks mismatch.";
				return false;
			}
			MethodInfo method = typeof(CharacterBody).GetMethod("HasUnmanagedBuff", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, new Type[1] { typeof(BuffDef) }, null);
			if (method == null || method.ReturnType != typeof(bool))
			{
				reason = "RoR2 HasUnmanagedBuff(BuffDef) signature mismatch.";
				return false;
			}
			hasShrineBuff = (Func<CharacterBody, BuffDef, bool>)Delegate.CreateDelegate(typeof(Func<CharacterBody, BuffDef, bool>), method);
			reason = null;
			return true;
		}

		internal static bool IsSupportedHash(string hash)
		{
			return string.Equals(hash, "A5F7D83C7E6872FDD6099FDC299ADB7D4FA03CBF465E526D6F5EFCC35C2C281D", StringComparison.OrdinalIgnoreCase);
		}

		private static bool TryValidateGameFile(string loadedLocation, string managedDirectory, out string reason)
		{
			reason = null;
			string text = loadedLocation;
			if (string.IsNullOrEmpty(text))
			{
				if (string.IsNullOrWhiteSpace(managedDirectory))
				{
					reason = "RoR2 assembly is memory-loaded and BepInEx ManagedPath is unavailable.";
					return false;
				}
				text = Path.Combine(managedDirectory, "RoR2.dll");
			}
			if (!File.Exists(text))
			{
				reason = "RoR2.dll for compatibility hashing was not found: " + text;
				return false;
			}
			try
			{
				string text2 = Sha256Hash.ComputeFile(text);
				if (IsSupportedHash(text2))
				{
					return true;
				}
				reason = "M4A1 1.1.5 requires the inspected RoR2.dll SHA-256 A5F7D83C7E6872FDD6099FDC299ADB7D4FA03CBF465E526D6F5EFCC35C2C281D, got " + text2 + ".";
			}
			catch (IOException ex)
			{
				reason = "RoR2.dll compatibility hash read failed: " + ex.Message;
			}
			catch (UnauthorizedAccessException ex2)
			{
				reason = "RoR2.dll compatibility hash read denied: " + ex2.Message;
			}
			return false;
		}

		private static bool Validate(string name, Type result, Type[] parameters, int size, string canonicalHash, out List<DecodedInstruction> instructions, out string reason)
		{
			instructions = null;
			MethodInfo method = typeof(CharacterMaster).GetMethod(name, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, parameters, null);
			if (method == null || method.ReturnType != result)
			{
				reason = "Current RoR2 " + name + " signature mismatch.";
				return false;
			}
			return MethodFingerprint.TryValidate(method, size, canonicalHash, "Current RoR2 " + name, out instructions, out reason);
		}
	}
	internal sealed class ItemDisplayWarmupContract
	{
		private readonly MethodInfo _lazyInit;

		private readonly FieldInfo[] _cacheFields;

		internal bool IsInitialized => (bool)_cacheFields[0].GetValue(null);

		private ItemDisplayWarmupContract(MethodInfo lazyInit, FieldInfo[] cacheFields)
		{
			_lazyInit = lazyInit;
			_cacheFields = cacheFields;
		}

		internal static bool TryResolve(out ItemDisplayWarmupContract contract, out string reason)
		{
			contract = null;
			if (!M4A1AssemblyContract.TryGetSupportedAssembly(out var assembly, out reason))
			{
				return false;
			}
			if (!M4A1AssemblyContract.IsCurrentAssembly(assembly))
			{
				reason = "Item-display warmup requires the inspected M4A1 1.1.5.";
				return false;
			}
			Type type = assembly.GetType("M4A1Mod.Modules.ItemDisplays", throwOnError: false);
			if (type == null)
			{
				reason = "Original item-display type is missing.";
				return false;
			}
			string[] array = new string[3] { "LazyInit", "PopulateDisplays", "PopulateFromBody" };
			string[] array2 = new string[3] { "24E9E0D92BED91043AE8B96C4032D02378E33B75ABA5A51482E21EC097C3A948", "435C8CD38B347B07F1BA808C0E9198DDDAB4382E1D136E8D98AFC1E21089517B", "5FA7B92F2D2AC90B56BEB4D85A623082A74FB6567BBE03CB07EC7B7A23C50276" };
			MethodInfo lazyInit = null;
			for (int i = 0; i < array.Length; i++)
			{
				if (!MethodLookup.TryResolveSingleDeclaredMethod(type, array[i], out var method, out reason))
				{
					return false;
				}
				if (!method.IsStatic || method.ReturnType != typeof(void) || method.GetParameters().Length != ((i == 2) ? 1 : 0) || (i == 2 && method.GetParameters()[0].ParameterType != typeof(string)) || !MethodFingerprint.TryValidateRaw(method, array2[i], array[i], out reason))
				{
					reason = reason ?? (array[i] + " signature mismatch.");
					return false;
				}
				if (i == 0)
				{
					lazyInit = method;
				}
			}
			string[] array3 = new string[4] { "initialized", "itemDisplayPrefabs", "KeyAssetDisplayPrefabs", "KeyAssets" };
			FieldInfo[] array4 = new FieldInfo[array3.Length];
			for (int j = 0; j < array3.Length; j++)
			{
				array4[j] = type.GetField(array3[j], BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
				if (array4[j] == null)
				{
					reason = "Item-display cache field missing: " + array3[j];
					return false;
				}
				if (j > 0 && !typeof(IDictionary).IsAssignableFrom(array4[j].FieldType))
				{
					reason = "Item-display dictionary field changed: " + array3[j];
					return false;
				}
			}
			if (array4[0].FieldType != typeof(bool))
			{
				reason = "Item-display initialized flag changed.";
				return false;
			}
			contract = new ItemDisplayWarmupContract(lazyInit, array4);
			return true;
		}

		internal void InitializeCache()
		{
			if (IsInitialized)
			{
				return;
			}
			object[] array = new object[_cacheFields.Length];
			List<DictionaryEntry>[] array2 = new List<DictionaryEntry>[array.Length];
			for (int i = 0; i < array.Length; i++)
			{
				array[i] = _cacheFields[i].GetValue(null);
				if (!(array[i] is IDictionary dictionary))
				{
					continue;
				}
				array2[i] = new List<DictionaryEntry>();
				foreach (DictionaryEntry item in dictionary)
				{
					array2[i].Add(item);
				}
			}
			try
			{
				_lazyInit.Invoke(null, null);
			}
			catch
			{
				for (int j = 0; j < array.Length; j++)
				{
					if (array[j] is IDictionary dictionary2)
					{
						dictionary2.Clear();
						foreach (DictionaryEntry item2 in array2[j])
						{
							dictionary2.Add(item2.Key, item2.Value);
						}
					}
					_cacheFields[j].SetValue(null, array[j]);
				}
				throw;
			}
		}
	}
	internal static class M4A1AssemblyContract
	{
		internal const string ExpectedAssemblySha256 = "600188D306782F6C1B26EA1BDA4A615BFF9AC4437318873D5728CD58B26A7154";

		internal const string CurrentAssemblySha256 = "023B3FE8F86F9DCE8FD9F203BA3580D565A9940D2E0E82AB3FA129EDC33CC6F0";

		private const string ExpectedDeathHookIlSha256 = "79150F880D5A6361BEF0E94A78AABB57B7A56881565C843E8A3D2614D65FE5E4";

		private const string CurrentDeathHookIlSha256 = "702CCE7C58854F4AB9123B01E59E87B363536F46CCB2EE958C78AB5261ECB0C9";

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

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

		internal static bool TryGetSupportedAssembly(out Assembly assembly, out string reason)
		{
			assembly = AppDomain.CurrentDomain.GetAssemblies().FirstOrDefault((Assembly a) => string.Equals(a.GetName().Name, "M4A1Mod", StringComparison.Ordinal));
			reason = null;
			if (assembly == null)
			{
				reason = "M4A1Mod assembly is not loaded.";
				return false;
			}
			return ValidateAssemblyHash(assembly, out reason);
		}

		internal static bool IsCurrentAssembly(Assembly assembly)
		{
			return string.Equals(Sha256Hash.ComputeFile(assembly.Location), "023B3FE8F86F9DCE8FD9F203BA3580D565A9940D2E0E82AB3FA129EDC33CC6F0", StringComparison.OrdinalIgnoreCase);
		}

		internal static bool TryGetDeathHook(Assembly assembly, out MethodInfo target, out string reason)
		{
			target = null;
			reason = null;
			if (!ValidateAssemblyHash(assembly, out reason))
			{
				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;
			}
			MethodInfo method = type.GetMethod("CharacterMaster_OnBodyDeath", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
			if (!ValidateM4A1TargetShape(method, out reason) || !MethodFingerprint.TryValidateRaw(method, IsCurrentAssembly(assembly) ? "702CCE7C58854F4AB9123B01E59E87B363536F46CCB2EE958C78AB5261ECB0C9" : "79150F880D5A6361BEF0E94A78AABB57B7A56881565C843E8A3D2614D65FE5E4", "M4A1 CharacterMaster_OnBodyDeath", out reason))
			{
				return false;
			}
			target = method;
			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, out string reason)
		{
			reason = null;
			string location = assembly.Location;
			if (string.IsNullOrEmpty(location) || !File.Exists(location))
			{
				reason = "M4A1Mod.dll location is unavailable for compatibility hashing.";
				return false;
			}
			string text;
			try
			{
				text = Sha256Hash.ComputeFile(location);
			}
			catch (IOException ex)
			{
				reason = "M4A1Mod.dll hash read failed: " + ex.Message;
				return false;
			}
			catch (UnauthorizedAccessException ex2)
			{
				reason = "M4A1Mod.dll hash read denied: " + ex2.Message;
				return false;
			}
			if (!IsSupportedHash(text))
			{
				reason = "M4A1Mod.dll SHA-256 mismatch. Expected verified M4A1 1.1.4 or 1.1.5, got " + text + ".";
				return false;
			}
			return true;
		}

		internal static bool IsSupportedHash(string hash)
		{
			if (!string.Equals(hash, "600188D306782F6C1B26EA1BDA4A615BFF9AC4437318873D5728CD58B26A7154", StringComparison.OrdinalIgnoreCase))
			{
				return string.Equals(hash, "023B3FE8F86F9DCE8FD9F203BA3580D565A9940D2E0E82AB3FA129EDC33CC6F0", StringComparison.OrdinalIgnoreCase);
			}
			return true;
		}

		internal static bool TryValidateLoadedPlugin(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 (!IsSupportedHash(hashHex))
			{
				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;
			if (!ValidateAssemblyHash(assembly, out reason))
			{
				return false;
			}
			if (!string.Equals(hashHex, Sha256Hash.ComputeFile(assembly.Location), StringComparison.OrdinalIgnoreCase))
			{
				reason = "M4A1 PluginInfo file and live instance assembly have different SHA-256 identities.";
				return false;
			}
			return true;
		}

		private static bool TrySha256(string filePath, out string hashHex, out string reason)
		{
			hashHex = null;
			reason = null;
			try
			{
				hashHex = Sha256Hash.ComputeFile(filePath);
				return true;
			}
			catch (Exception ex)
			{
				reason = "SHA-256 read failed for loaded M4A1 plugin bytes: " + ex.GetType().Name + ": " + ex.Message;
				return false;
			}
		}
	}
	internal static class M4A1Compatibility
	{
		internal static bool TryValidateRevivePriorityTarget(out MethodInfo target, out string reason)
		{
			target = null;
			if (!M4A1AssemblyContract.TryGetSupportedAssembly(out var assembly, out reason) || !RoR2ReviveContract.TryValidateTypeIdentity(out reason) || !M4A1AssemblyContract.TryGetDeathHook(assembly, out var target2, out reason) || !RoR2ReviveContract.TryValidate(M4A1AssemblyContract.IsCurrentAssembly(assembly), out reason))
			{
				return false;
			}
			target = target2;
			return true;
		}
	}
	internal static class MethodLookup
	{
		private const BindingFlags DeclaredMethodFlags = BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic;

		internal 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;
		}
	}
	internal static class RoR2ReviveContract
	{
		private delegate bool MethodValidator(MethodInfo method, out string reason);

		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";

		private static Func<CharacterBody, BuffDef, bool> _hasShrineBuff;

		internal static bool HasShrineBuff(CharacterBody body)
		{
			if (_hasShrineBuff != null)
			{
				return _hasShrineBuff(body, Buffs.ExtraLifeBuff);
			}
			return false;
		}

		internal static bool TryValidateTypeIdentity(out string reason)
		{
			reason = null;
			if (typeof(CharacterMaster).Assembly == typeof(CharacterBody).Assembly)
			{
				return true;
			}
			reason = "RoR2 CharacterMaster and CharacterBody are not from the same assembly.";
			return false;
		}

		internal static bool TryValidate(bool currentUpstream, out string reason)
		{
			_hasShrineBuff = null;
			if (currentUpstream)
			{
				return CurrentRoR2ReviveContract.TryValidate(out _hasShrineBuff, out reason);
			}
			int num;
			if (TryValidateMethod("TryReviveOnBodyDeath", typeof(bool), new Type[1] { typeof(CharacterBody) }, ValidateTryReviveStructure, out reason) && TryValidateMethod("RespawnExtraLifeShrine", typeof(void), Type.EmptyTypes, ValidateShrineRespawnStructure, out reason))
			{
				num = (TryValidateMethod("IsExtraLifePendingServer", typeof(bool), Type.EmptyTypes, ValidatePendingStateStructure, out reason) ? 1 : 0);
				if (num != 0)
				{
					_hasShrineBuff = (CharacterBody body, BuffDef buff) => body.HasBuff(buff);
				}
			}
			else
			{
				num = 0;
			}
			return (byte)num != 0;
		}

		private static bool TryValidateMethod(string name, Type returnType, Type[] arguments, MethodValidator validateBody, out string reason)
		{
			MethodInfo method = typeof(CharacterMaster).GetMethod(name, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, arguments, null);
			reason = null;
			if (method == null || method.ReturnType != returnType)
			{
				reason = "RoR2 " + name + " signature mismatch.";
				return false;
			}
			return validateBody(method, out reason);
		}

		private static bool ValidateTryReviveStructure(MethodInfo method, out string reason)
		{
			if (!MethodFingerprint.TryValidate(method, 632, "43D299DDC88201E148A1D914092175CB58285EDB1683822D825FDCC8E4163395", "RoR2 TryReviveOnBodyDeath", out var instructions, out reason))
			{
				return false;
			}
			int num = IlMethodReader.FindMember(instructions, "DLC2Content+Buffs::ExtraLifeBuff", 0);
			int num2 = IlMethodReader.FindMember(instructions, "CharacterBody::HasBuff", num + 1);
			int num3 = IlMethodReader.FindMember(instructions, "CharacterBody::RemoveBuff", num2 + 1);
			int num4 = IlMethodReader.FindString(instructions, "RespawnExtraLifeShrine", num3 + 1);
			int num5 = IlMethodReader.FindMember(instructions, "UnityEngine.MonoBehaviour::Invoke(System.String,System.Single)", num4 + 1);
			int num6 = IlMethodReader.FindString(instructions, "PlayExtraLifeSFX", num5 + 1);
			int num7 = IlMethodReader.FindMember(instructions, "UnityEngine.MonoBehaviour::Invoke(System.String,System.Single)", num6 + 1);
			int num8 = IlMethodReader.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 (!MethodFingerprint.TryValidate(method, 264, "28D947645DE9C1E53D7C25591434D8B75ECB2E2E42492DF482D9A57296428E1F", "RoR2 RespawnExtraLifeShrine", out var instructions, out reason))
			{
				return false;
			}
			int num = IlMethodReader.FindMember(instructions, "CharacterMaster::Respawn", 0);
			int num2 = IlMethodReader.FindMember(instructions, "RoR2Content+Buffs::Immune", 0);
			int num3 = IlMethodReader.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 (!MethodFingerprint.TryValidate(method, 75, "7DDA7E015613A20CBD68394435B95ECE885557F7875832D063F853B764157F74", "RoR2 IsExtraLifePendingServer", out var instructions, out reason))
			{
				return false;
			}
			int num = IlMethodReader.FindMember(instructions, "ExtraLifeServerBehavior", 0);
			int num2 = IlMethodReader.FindString(instructions, "RespawnSeekerRevive", 0);
			int num3 = IlMethodReader.FindString(instructions, "RespawnExtraLifeShrine", num2 + 1);
			int num4 = IlMethodReader.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;
		}
	}
	internal static class Sha256Hash
	{
		internal static string Compute(byte[] bytes)
		{
			using SHA256 sHA = SHA256.Create();
			return Hex(sHA.ComputeHash(bytes));
		}

		internal static string ComputeFile(string path)
		{
			using FileStream inputStream = File.OpenRead(path);
			using SHA256 sHA = SHA256.Create();
			return Hex(sHA.ComputeHash(inputStream));
		}

		private static string Hex(byte[] bytes)
		{
			return BitConverter.ToString(bytes).Replace("-", string.Empty);
		}
	}
}
namespace M4A1Fix.Compatibility.IL
{
	internal static class CanonicalMethodFormatter
	{
		internal static bool TryBuildCanonicalMethod(MethodInfo method, int expectedCodeSize, string displayName, List<DecodedInstruction> instructions, out string canonical, out string reason)
		{
			canonical = null;
			reason = null;
			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));
				AppendLocals(methodBody, list);
				if (!TryAppendInstructions(instructions, dictionary, displayName, list, out reason) || !TryAppendExceptionRegions(methodBody, dictionary, displayName, list, out reason))
				{
					return false;
				}
				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 void AppendLocals(MethodBody body, List<string> lines)
		{
			IList<LocalVariableInfo> localVariables = body.LocalVariables;
			lines.Add("LOCALS=" + localVariables.Count.ToString(CultureInfo.InvariantCulture));
			for (int i = 0; i < localVariables.Count; i++)
			{
				LocalVariableInfo localVariableInfo = localVariables[i];
				lines.Add("LOCAL[" + i.ToString(CultureInfo.InvariantCulture) + "]=T=" + CanonicalTypeIdentity(localVariableInfo.LocalType) + ";PINNED=" + (localVariableInfo.IsPinned ? "1" : "0"));
			}
		}

		private static bool TryAppendInstructions(List<DecodedInstruction> instructions, Dictionary<int, int> offsetToIndex, string displayName, List<string> lines, out string reason)
		{
			reason = null;
			lines.Add("INSTRUCTIONS=" + instructions.Count.ToString(CultureInfo.InvariantCulture));
			for (int i = 0; i < instructions.Count; i++)
			{
				DecodedInstruction decodedInstruction = instructions[i];
				if (!TryCanonicalOperand(decodedInstruction, offsetToIndex, out var canonical, out reason))
				{
					reason = displayName + " canonical operand failed at instruction " + i.ToString(CultureInfo.InvariantCulture) + ": " + reason;
					return false;
				}
				lines.Add("I[" + i.ToString(CultureInfo.InvariantCulture) + "]=OP=" + decodedInstruction.OpCode.Name + ";O=" + canonical);
			}
			return true;
		}

		private static bool TryAppendExceptionRegions(MethodBody body, Dictionary<int, int> offsetToIndex, string displayName, List<string> lines, out string reason)
		{
			reason = null;
			IList<ExceptionHandlingClause> exceptionHandlingClauses = body.ExceptionHandlingClauses;
			lines.Add("EH=" + exceptionHandlingClauses.Count.ToString(CultureInfo.InvariantCulture));
			for (int i = 0; i < exceptionHandlingClauses.Count; i++)
			{
				ExceptionHandlingClause exceptionHandlingClause = exceptionHandlingClauses[i];
				if (!offsetToIndex.TryGetValue(exceptionHandlingClause.TryOffset, out var value) || !offsetToIndex.TryGetValue(exceptionHandlingClause.TryOffset + exceptionHandlingClause.TryLength, out var value2) || !offsetToIndex.TryGetValue(exceptionHandlingClause.HandlerOffset, out var value3) || !offsetToIndex.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 (!offsetToIndex.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";
				}
				lines.Add("EH[" + i.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);
			}
			return true;
		}

		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 string ToHex(byte[] bytes)
		{
			return BitConverter.ToString(bytes).Replace("-", string.Empty);
		}
	}
	internal sealed class DecodedInstruction
	{
		internal int Offset;

		internal OpCode OpCode;

		internal object Operand;
	}
	internal static class IlMethodReader
	{
		private static readonly Dictionary<ushort, OpCode> OpCodeMap = BuildOpCodeMap();

		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;
		}

		internal static bool TryRead(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 position = 0;
			try
			{
				while (position < array.Length)
				{
					int offset = position;
					ushort num = array[position++];
					if (num == 254)
					{
						if (position >= array.Length)
						{
							throw new InvalidDataException("truncated two-byte opcode");
						}
						num = (ushort)(0xFE00 | array[position++]);
					}
					if (!OpCodeMap.TryGetValue(num, out var value))
					{
						throw new InvalidDataException("unknown opcode 0x" + num.ToString("X4"));
					}
					object operand = ReadOperand(method, array, value, ref position);
					list.Add(new DecodedInstruction
					{
						Offset = offset,
						OpCode = value,
						Operand = operand
					});
				}
			}
			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 object ReadOperand(MethodInfo method, byte[] il, OpCode opcode, ref int position)
		{
			object obj = null;
			switch (opcode.OperandType)
			{
			case OperandType.ShortInlineI:
				obj = (sbyte)il[position++];
				break;
			case OperandType.InlineI:
				obj = BitConverter.ToInt32(il, position);
				position += 4;
				break;
			case OperandType.InlineI8:
				obj = BitConverter.ToInt64(il, position);
				position += 8;
				break;
			case OperandType.ShortInlineR:
				obj = BitConverter.ToSingle(il, position);
				position += 4;
				break;
			case OperandType.InlineR:
				obj = BitConverter.ToDouble(il, position);
				position += 8;
				break;
			case OperandType.ShortInlineVar:
				obj = il[position++];
				break;
			case OperandType.InlineVar:
				obj = BitConverter.ToUInt16(il, position);
				position += 2;
				break;
			case OperandType.ShortInlineBrTarget:
				obj = position + 1 + (sbyte)il[position];
				position++;
				break;
			case OperandType.InlineBrTarget:
			{
				int num3 = BitConverter.ToInt32(il, position);
				position += 4;
				obj = position + num3;
				break;
			}
			case OperandType.InlineSwitch:
			{
				int num = BitConverter.ToInt32(il, position);
				position += 4;
				int num2 = position + 4 * num;
				int[] array = new int[num];
				for (int i = 0; i < num; i++)
				{
					array[i] = num2 + BitConverter.ToInt32(il, position);
					position += 4;
				}
				obj = array;
				break;
			}
			case OperandType.InlineString:
			{
				int metadataToken5 = BitConverter.ToInt32(il, position);
				position += 4;
				obj = method.Module.ResolveString(metadataToken5);
				if (obj == null)
				{
					throw new InvalidDataException("unresolved string token");
				}
				break;
			}
			case OperandType.InlineField:
			{
				int metadataToken4 = BitConverter.ToInt32(il, position);
				position += 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(il, position);
				position += 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(il, position);
				position += 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(il, position);
				position += 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 " + opcode.OperandType);
			case OperandType.InlineNone:
				break;
			}
			return obj;
		}

		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;
		}

		internal 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;
		}

		internal 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;
		}
	}
	internal static class MethodFingerprint
	{
		internal static bool TryValidate(MethodInfo method, int expectedCodeSize, string expectedHash, string displayName, out List<DecodedInstruction> instructions, out string reason)
		{
			reason = null;
			if (!IlMethodReader.TryRead(method, expectedCodeSize, displayName, out instructions, out reason))
			{
				return false;
			}
			if (!CanonicalMethodFormatter.TryBuildCanonicalMethod(method, expectedCodeSize, displayName, instructions, out var canonical, out reason))
			{
				return false;
			}
			string text = Sha256Hash.Compute(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;
		}

		internal static bool TryValidateRaw(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 = Sha256Hash.Compute(array);
			if (!string.Equals(text, expected, StringComparison.OrdinalIgnoreCase))
			{
				reason = displayName + " IL SHA-256 mismatch. Expected " + expected + ", got " + text + ".";
				return false;
			}
			return true;
		}
	}
}