Decompiled source of UpgradeLagFix v1.0.0

plugins/UpgradeLagFix/UpgradeLagFix.dll

Decompiled a day ago
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using System.Reflection.Emit;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Text;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using Mono.Cecil;
using Mono.Cecil.Cil;
using Mono.Collections.Generic;
using Pigeon.Movement;
using UnityEngine;
using UnityEngine.InputSystem;
using UnityEngine.InputSystem.Controls;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")]
[assembly: AssemblyCompany("UpgradeLagFix")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyInformationalVersion("1.0.0")]
[assembly: AssemblyProduct("UpgradeLagFix")]
[assembly: AssemblyTitle("UpgradeLagFix")]
[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 UpgradeLagFix
{
	internal static class Bench
	{
		private const double TargetMsPerMode = 300.0;

		private const int MaxReps = 500;

		private static readonly FieldRef<PlayerData, Dictionary<int, List<UpgradeInfo>>> DataRef = AccessTools.FieldRefAccess<PlayerData, Dictionary<int, List<UpgradeInfo>>>("data");

		private static int _sink;

		public static void Run()
		{
			ManualLogSource log = Plugin.Log;
			Player localPlayer = Player.LocalPlayer;
			if ((Object)(object)localPlayer == (Object)null)
			{
				log.LogWarning((object)"[bench] No local player yet. Load into the HUB first.");
				return;
			}
			IGear[] gear = localPlayer.Gear;
			if (gear == null)
			{
				log.LogWarning((object)"[bench] Player has no gear array.");
				return;
			}
			int infos;
			int num = CountAllInstances(out infos);
			log.LogInfo((object)("[bench] start. upgrade types in data: " + infos + ", instances (U): " + num + ", cleanup-skip available: " + CleanupMemo.Available + " (per-walk times below)"));
			bool? cacheOverride = Plugin.CacheOverride;
			bool? verifyOverride = Plugin.VerifyOverride;
			bool? cleanupOverride = Plugin.CleanupOverride;
			bool? prepareOverride = Plugin.PrepareOverride;
			bool? sortOverride = Plugin.SortOverride;
			Plugin.VerifyOverride = false;
			try
			{
				IGear[] array = gear;
				foreach (IGear val in array)
				{
					if (val != null)
					{
						IUpgradable prefab;
						try
						{
							prefab = ((IUpgradable)val).Prefab;
						}
						catch
						{
							continue;
						}
						if (prefab != null)
						{
							RunGear(prefab);
						}
					}
				}
			}
			catch (Exception ex)
			{
				log.LogError((object)("[bench] failed: " + ex));
			}
			finally
			{
				Plugin.CacheOverride = cacheOverride;
				Plugin.VerifyOverride = verifyOverride;
				Plugin.CleanupOverride = cleanupOverride;
				Plugin.PrepareOverride = prepareOverride;
				Plugin.SortOverride = sortOverride;
			}
			log.LogInfo((object)"[bench] done.");
		}

		private static void RunGear(IUpgradable prefab)
		{
			ManualLogSource log = Plugin.Log;
			string text = Patches.GearName(prefab);
			SetMode(cache: false, cleanup: false, walk: false);
			List<int> list = Walk(prefab);
			if (list.Count != 0)
			{
				long timestamp = Stopwatch.GetTimestamp();
				Walk(prefab);
				double val = (double)(Stopwatch.GetTimestamp() - timestamp) * Stats.TicksToMs;
				int num = (int)Math.Max(1.0, Math.Min(500.0, Math.Ceiling(300.0 / Math.Max(val, 0.001))));
				Measure(prefab, num, out var msPerWalk, out var lookupsPerWalk);
				SetMode(cache: true, cleanup: false, walk: false);
				InstanceCache.Clear();
				bool flag = SameSequence(list, Walk(prefab));
				Measure(prefab, num, out var msPerWalk2, out var lookupsPerWalk2);
				SetMode(cache: true, cleanup: true, walk: false);
				CleanupMemo.Clear();
				bool flag2 = SameSequence(list, Walk(prefab));
				Measure(prefab, num, out var msPerWalk3, out var lookupsPerWalk3);
				SetMode(cache: true, cleanup: true, walk: true);
				CleanupMemo.Clear();
				bool flag3 = SameSequence(list, Walk(prefab));
				Measure(prefab, num, out var msPerWalk4, out var lookupsPerWalk4);
				log.LogInfo((object)$"[bench] {text}: n={list.Count}, reps={num} | OFF {msPerWalk:F3} ms ({lookupsPerWalk} lookups) | cache {msPerWalk2:F4} ms ({lookupsPerWalk2}) x{Ratio(msPerWalk, msPerWalk2):F1} | +cleanup {msPerWalk3:F4} ms ({lookupsPerWalk3}) x{Ratio(msPerWalk, msPerWalk3):F1} | +prepare+sort {msPerWalk4:F4} ms ({lookupsPerWalk4}) x{Ratio(msPerWalk, msPerWalk4):F1} | same order={flag && flag2 && flag3}");
				if (!(flag && flag2 && flag3))
				{
					log.LogError((object)("[bench] " + text + ": sequence differs between modes! base=[" + string.Join(",", list) + "]"));
				}
			}
		}

		private static void SetMode(bool cache, bool cleanup, bool walk)
		{
			Plugin.CacheOverride = cache;
			Plugin.CleanupOverride = cleanup;
			Plugin.PrepareOverride = walk;
			Plugin.SortOverride = walk;
		}

		private static double Ratio(double a, double b)
		{
			if (!(b > 0.0))
			{
				return double.PositiveInfinity;
			}
			return a / b;
		}

		private static void Measure(IUpgradable prefab, int reps, out double msPerWalk, out long lookupsPerWalk)
		{
			long lookups = Stats.Lookups;
			long timestamp = Stopwatch.GetTimestamp();
			for (int i = 0; i < reps; i++)
			{
				WalkCount(prefab);
			}
			long num = Stopwatch.GetTimestamp() - timestamp;
			msPerWalk = (double)num * Stats.TicksToMs / (double)reps;
			lookupsPerWalk = (Stats.Lookups - lookups) / reps;
		}

		private static List<int> Walk(IUpgradable prefab)
		{
			List<int> list = new List<int>();
			EquippedUpgradeEnumerator val = default(EquippedUpgradeEnumerator);
			((EquippedUpgradeEnumerator)(ref val))..ctor(prefab, true);
			while (((EquippedUpgradeEnumerator)(ref val)).MoveNext())
			{
				UpgradeInstance upgrade = ((EquippedUpgradeEnumerator)(ref val)).Upgrade;
				list.Add((upgrade == null) ? int.MinValue : upgrade.InstanceID);
			}
			return list;
		}

		private static void WalkCount(IUpgradable prefab)
		{
			EquippedUpgradeEnumerator val = default(EquippedUpgradeEnumerator);
			((EquippedUpgradeEnumerator)(ref val))..ctor(prefab, true);
			while (((EquippedUpgradeEnumerator)(ref val)).MoveNext())
			{
				UpgradeInstance upgrade = ((EquippedUpgradeEnumerator)(ref val)).Upgrade;
				if (upgrade != null)
				{
					_sink ^= upgrade.InstanceID;
				}
			}
		}

		private static bool SameSequence(List<int> a, List<int> b)
		{
			if (a.Count != b.Count)
			{
				return false;
			}
			for (int i = 0; i < a.Count; i++)
			{
				if (a[i] != b[i])
				{
					return false;
				}
			}
			return true;
		}

		private static int CountAllInstances(out int infos)
		{
			infos = 0;
			int num = 0;
			Dictionary<int, List<UpgradeInfo>> dictionary = DataRef.Invoke(PlayerData.Instance);
			if (dictionary == null)
			{
				return 0;
			}
			foreach (KeyValuePair<int, List<UpgradeInfo>> item in dictionary)
			{
				if (item.Value == null)
				{
					continue;
				}
				foreach (UpgradeInfo item2 in item.Value)
				{
					infos++;
					if (item2?.Instances != null)
					{
						num += item2.Instances.Count;
					}
				}
			}
			return num;
		}
	}
	internal static class CleanupMemo
	{
		private sealed class Memo
		{
			public int Gen;

			public PlayerData Pd;

			public object Data;

			public List<UpgradeEquipData> Ups;

			public int UpsVer;

			public List<UpgradeEquipData> Skins;

			public int SkinsVer;
		}

		public struct State
		{
			public bool Track;

			public bool ExpectNoop;

			public int Gen;

			public List<UpgradeEquipData> Ups;

			public int UpsVer;

			public List<UpgradeEquipData> Skins;

			public int SkinsVer;
		}

		private static readonly HashSet<string> RemovingMethods = new HashSet<string> { "Remove", "RemoveAt", "RemoveAll", "RemoveRange", "Clear", "set_Item", "TrimExcess" };

		private static readonly HashSet<string> WatchedFields = new HashSet<string> { "PlayerData::data", "PlayerData::_instance", "UpgradeInfo::Instances", "UpgradeInstance::id" };

		private static readonly HashSet<string> PrepFields = new HashSet<string> { "PlayerData::collectedGear", "PlayerData::upgradeIDs", "GearData::gear", "UpgradeInstance::gearID", "UpgradeInstance::upgradeIDv2", "UpgradeInfo::idV2" };

		private static readonly HashSet<string> OrderMethods = new HashSet<string> { "Sort", "Reverse", "Insert", "InsertRange" };

		private static readonly HashSet<string> CleanupTypeNames = new HashSet<string> { "UpgradeInstance", "UpgradeInfo" };

		private static readonly HashSet<string> PrepTypeNames = new HashSet<string> { "PlayerData/GearData", "Upgrade" };

		private static readonly HashSet<string> InfoTypeNames = new HashSet<string> { "UpgradeInfo" };

		private const int KindNone = 0;

		private const int KindCleanup = 1;

		private const int KindPrepare = 2;

		private static readonly HashSet<string> ScratchListGetters = new HashSet<string> { "get_ActiveUpgrades", "get_ActiveSkins" };

		private static readonly ConditionalWeakTable<object, Memo> Memos = new ConditionalWeakTable<object, Memo>();

		private static FieldRef<object, List<UpgradeEquipData>> _upsRef;

		private static FieldRef<object, List<UpgradeEquipData>> _skinsRef;

		private static FieldRef<List<UpgradeEquipData>, int> _versionRef;

		private static FieldRef<PlayerData, Dictionary<int, List<UpgradeInfo>>> _dataRef;

		private static readonly MethodInfo BumpMethod = AccessTools.Method(typeof(CleanupMemo), "Bump", (Type[])null, (Type[])null);

		private static readonly MethodInfo BumpPrepMethod = AccessTools.Method(typeof(CleanupMemo), "BumpPrep", (Type[])null, (Type[])null);

		private static int _gen;

		private static int _prepGen;

		private static int _mismatchLogs;

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

		private static long[] _siteCounts = new long[64];

		private static readonly Type GearDataType = AccessTools.Inner(typeof(PlayerData), "GearData");

		private static readonly MethodInfo InfoListSort = AccessTools.Method(typeof(List<UpgradeInfo>), "Sort", Type.EmptyTypes, (Type[])null);

		private static readonly MethodInfo SortInfosTrackedMethod = AccessTools.Method(typeof(CleanupMemo), "SortInfosTracked", (Type[])null, (Type[])null);

		private static UpgradeInfo[] _sortBuffer = (UpgradeInfo[])(object)new UpgradeInfo[256];

		internal static int PrepGen => _prepGen;

		internal static bool PrepAvailable { get; private set; }

		public static bool Available { get; private set; }

		public static int InstrumentedMethods { get; private set; }

		public static int InstrumentedSites { get; private set; }

		public static void Setup(Harmony harmony)
		{
			//IL_0123: Unknown result type (might be due to invalid IL or missing references)
			//IL_012a: Expected O, but got Unknown
			//IL_020f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0226: Unknown result type (might be due to invalid IL or missing references)
			//IL_0231: Expected O, but got Unknown
			//IL_0231: Expected O, but got Unknown
			ManualLogSource log = Plugin.Log;
			try
			{
				Type type = AccessTools.Inner(typeof(PlayerData), "GearData");
				_upsRef = AccessTools.FieldRefAccess<List<UpgradeEquipData>>(type, "equippedUpgrades");
				_skinsRef = AccessTools.FieldRefAccess<List<UpgradeEquipData>>(type, "equippedSkins");
				_versionRef = AccessTools.FieldRefAccess<List<UpgradeEquipData>, int>("_version");
				_dataRef = AccessTools.FieldRefAccess<PlayerData, Dictionary<int, List<UpgradeInfo>>>("data");
				Stopwatch stopwatch = Stopwatch.StartNew();
				List<string> unpatchable;
				List<string> unpatchablePrep;
				List<MethodBase> list = FindMutators(out unpatchable, out unpatchablePrep);
				log.LogInfo((object)("Cleanup memo: found " + list.Count + " methods with watched instructions in " + stopwatch.ElapsedMilliseconds + " ms"));
				if (unpatchable.Count > 0)
				{
					log.LogWarning((object)("Cleanup/prepare memo disabled: cannot patch " + string.Join(", ", unpatchable)));
					return;
				}
				PrepAvailable = unpatchablePrep.Count == 0;
				if (!PrepAvailable)
				{
					log.LogWarning((object)("Prepare memo disabled: cannot patch " + string.Join(", ", unpatchablePrep)));
				}
				HarmonyMethod val = new HarmonyMethod(typeof(CleanupMemo), "Instrument_Transpiler", (Type[])null);
				foreach (MethodBase item in list)
				{
					try
					{
						harmony.Patch(item, (HarmonyMethod)null, (HarmonyMethod)null, val, (HarmonyMethod)null, (HarmonyMethod)null);
						InstrumentedMethods++;
					}
					catch (Exception ex)
					{
						log.LogWarning((object)("Cleanup/prepare memo disabled: failed to patch " + item.DeclaringType?.Name + "." + item.Name + ": " + ex.Message));
						_gen++;
						_prepGen++;
						PrepAvailable = false;
						return;
					}
				}
				harmony.Patch((MethodBase)AccessTools.Method(type, "Cleanup", (Type[])null, (Type[])null), new HarmonyMethod(typeof(CleanupMemo), "Cleanup_Prefix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, new HarmonyMethod(typeof(CleanupMemo), "Cleanup_Finalizer", (Type[])null), (HarmonyMethod)null);
				Available = true;
				log.LogInfo((object)("Cleanup memo ready: instrumented " + InstrumentedSites + " instructions in " + InstrumentedMethods + " methods"));
			}
			catch (Exception ex2)
			{
				Available = false;
				log.LogError((object)("Cleanup memo setup failed, feature disabled: " + ex2));
			}
		}

		private static List<MethodBase> FindMutators(out List<string> unpatchable, out List<string> unpatchablePrep)
		{
			//IL_005f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0064: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a4: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a9: Unknown result type (might be due to invalid IL or missing references)
			//IL_0146: Unknown result type (might be due to invalid IL or missing references)
			//IL_014b: Unknown result type (might be due to invalid IL or missing references)
			unpatchable = new List<string>();
			unpatchablePrep = new List<string>();
			List<MethodBase> list = new List<MethodBase>();
			Module module = typeof(PlayerData).Module;
			AssemblyDefinition val = AssemblyDefinition.ReadAssembly(typeof(PlayerData).Assembly.Location);
			try
			{
				foreach (TypeDefinition type in val.MainModule.GetTypes())
				{
					Enumerator<MethodDefinition> enumerator2 = type.Methods.GetEnumerator();
					try
					{
						while (enumerator2.MoveNext())
						{
							MethodDefinition current = enumerator2.Current;
							if (!current.HasBody || (current.IsConstructor && current.IsStatic))
							{
								continue;
							}
							int num = 0;
							Enumerator<Instruction> enumerator3 = current.Body.Instructions.GetEnumerator();
							try
							{
								while (enumerator3.MoveNext())
								{
									Instruction current2 = enumerator3.Current;
									num |= ClassifyCecil(current2);
								}
							}
							finally
							{
								((IDisposable)enumerator3/*cast due to .constrained prefix*/).Dispose();
							}
							if (num == 0)
							{
								continue;
							}
							string item = ((MemberReference)current.DeclaringType).FullName + "::" + ((MemberReference)current).Name;
							if (current.DeclaringType.IsInterface || ((MethodReference)current).HasGenericParameters || ((TypeReference)current.DeclaringType).HasGenericParameters)
							{
								if ((num & 1) != 0)
								{
									unpatchable.Add(item);
								}
								else
								{
									unpatchablePrep.Add(item);
								}
								continue;
							}
							MethodBase methodBase;
							try
							{
								MetadataToken metadataToken = ((MemberReference)current).MetadataToken;
								methodBase = module.ResolveMethod(((MetadataToken)(ref metadataToken)).ToInt32());
							}
							catch
							{
								methodBase = null;
							}
							if (methodBase == null)
							{
								if ((num & 1) != 0)
								{
									unpatchable.Add(item);
								}
								else
								{
									unpatchablePrep.Add(item);
								}
							}
							else
							{
								list.Add(methodBase);
							}
						}
					}
					finally
					{
						((IDisposable)enumerator2/*cast due to .constrained prefix*/).Dispose();
					}
				}
				return list;
			}
			finally
			{
				((IDisposable)val)?.Dispose();
			}
		}

		private static int ClassifyCecil(Instruction ins)
		{
			//IL_0001: Unknown result type (might be due to invalid IL or missing references)
			//IL_0006: Unknown result type (might be due to invalid IL or missing references)
			//IL_0009: Unknown result type (might be due to invalid IL or missing references)
			//IL_000e: Unknown result type (might be due to invalid IL or missing references)
			//IL_000f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0012: Invalid comparison between Unknown and I4
			//IL_0014: Unknown result type (might be due to invalid IL or missing references)
			//IL_0017: Invalid comparison between Unknown and I4
			//IL_009c: Unknown result type (might be due to invalid IL or missing references)
			//IL_009f: Invalid comparison between Unknown and I4
			//IL_00a1: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a4: Invalid comparison between Unknown and I4
			OpCode opCode = ins.OpCode;
			Code code = ((OpCode)(ref opCode)).Code;
			if ((int)code == 39 || (int)code == 110)
			{
				object operand = ins.Operand;
				MethodReference val = (MethodReference)((operand is MethodReference) ? operand : null);
				if (val != null)
				{
					TypeReference declaringType = ((MemberReference)val).DeclaringType;
					if (RemovingMethods.Contains(((MemberReference)val).Name))
					{
						if (MentionsCecil(declaringType, CleanupTypeNames))
						{
							return (!IsScratchReceiver(((MemberReference)val).Name, ins.Previous)) ? 1 : 0;
						}
						if (MentionsCecil(declaringType, PrepTypeNames))
						{
							return 2;
						}
					}
					if (OrderMethods.Contains(((MemberReference)val).Name) && MentionsCecil(declaringType, InfoTypeNames))
					{
						return 2;
					}
					return 0;
				}
			}
			if ((int)code == 122 || (int)code == 125)
			{
				object operand2 = ins.Operand;
				FieldReference val2 = (FieldReference)((operand2 is FieldReference) ? operand2 : null);
				if (val2 != null)
				{
					string item = ((MemberReference)((MemberReference)val2).DeclaringType).Name + "::" + ((MemberReference)val2).Name;
					if (WatchedFields.Contains(item))
					{
						return 1;
					}
					if (PrepFields.Contains(item))
					{
						return 2;
					}
				}
			}
			return 0;
		}

		private static bool MentionsCecil(TypeReference t, HashSet<string> names)
		{
			//IL_0020: Unknown result type (might be due to invalid IL or missing references)
			//IL_0025: Unknown result type (might be due to invalid IL or missing references)
			if (names.Contains(((MemberReference)t).FullName))
			{
				return true;
			}
			GenericInstanceType val = (GenericInstanceType)(object)((t is GenericInstanceType) ? t : null);
			if (val != null)
			{
				Enumerator<TypeReference> enumerator = val.GenericArguments.GetEnumerator();
				try
				{
					while (enumerator.MoveNext())
					{
						if (MentionsCecil(enumerator.Current, names))
						{
							return true;
						}
					}
				}
				finally
				{
					((IDisposable)enumerator/*cast due to .constrained prefix*/).Dispose();
				}
			}
			return false;
		}

		private static bool IsScratchReceiver(string method, Instruction prev)
		{
			//IL_0013: Unknown result type (might be due to invalid IL or missing references)
			//IL_0018: Unknown result type (might be due to invalid IL or missing references)
			//IL_001b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0020: Unknown result type (might be due to invalid IL or missing references)
			//IL_0021: Unknown result type (might be due to invalid IL or missing references)
			//IL_0024: Invalid comparison between Unknown and I4
			//IL_0026: Unknown result type (might be due to invalid IL or missing references)
			//IL_0029: Invalid comparison between Unknown and I4
			//IL_005d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0060: Invalid comparison between Unknown and I4
			if (method != "Clear" || prev == null)
			{
				return false;
			}
			OpCode opCode = prev.OpCode;
			Code code = ((OpCode)(ref opCode)).Code;
			if ((int)code == 39 || (int)code == 110)
			{
				object operand = prev.Operand;
				MethodReference val = (MethodReference)((operand is MethodReference) ? operand : null);
				if (val != null)
				{
					if (ScratchListGetters.Contains(((MemberReference)val).Name))
					{
						return val.Parameters.Count == 0;
					}
					return false;
				}
			}
			if ((int)code == 123)
			{
				object operand2 = prev.Operand;
				FieldReference val2 = (FieldReference)((operand2 is FieldReference) ? operand2 : null);
				if (val2 != null)
				{
					if (((MemberReference)val2).Name == "infoBuffer")
					{
						return ((MemberReference)((MemberReference)val2).DeclaringType).Name == "PlayerData";
					}
					return false;
				}
			}
			return false;
		}

		private static int ClassifyRuntime(CodeInstruction ins, CodeInstruction prev)
		{
			if ((ins.opcode == OpCodes.Call || ins.opcode == OpCodes.Callvirt) && ins.operand is MethodInfo methodInfo)
			{
				Type declaringType = methodInfo.DeclaringType;
				if (declaringType == null || !declaringType.IsGenericType)
				{
					return 0;
				}
				if (RemovingMethods.Contains(methodInfo.Name))
				{
					if (Mentions(declaringType, typeof(UpgradeInstance), typeof(UpgradeInfo)))
					{
						return (!IsScratchReceiverRuntime(methodInfo.Name, prev)) ? 1 : 0;
					}
					if (Mentions(declaringType, GearDataType, typeof(Upgrade)))
					{
						return 2;
					}
				}
				if (OrderMethods.Contains(methodInfo.Name) && Mentions(declaringType, typeof(UpgradeInfo)))
				{
					return 2;
				}
				return 0;
			}
			if ((ins.opcode == OpCodes.Stfld || ins.opcode == OpCodes.Stsfld) && ins.operand is FieldInfo fieldInfo && fieldInfo.DeclaringType != null)
			{
				string item = fieldInfo.DeclaringType.Name + "::" + fieldInfo.Name;
				if (WatchedFields.Contains(item))
				{
					return 1;
				}
				if (PrepFields.Contains(item))
				{
					return 2;
				}
			}
			return 0;
		}

		private static bool IsScratchReceiverRuntime(string method, CodeInstruction prev)
		{
			if (method != "Clear" || prev == null)
			{
				return false;
			}
			if ((prev.opcode == OpCodes.Call || prev.opcode == OpCodes.Callvirt) && prev.operand is MethodInfo methodInfo && ScratchListGetters.Contains(methodInfo.Name) && methodInfo.GetParameters().Length == 0)
			{
				return true;
			}
			if (prev.opcode == OpCodes.Ldsfld && prev.operand is FieldInfo { Name: "infoBuffer" } fieldInfo)
			{
				return fieldInfo.DeclaringType == typeof(PlayerData);
			}
			return false;
		}

		private static bool Mentions(Type t, params Type[] targets)
		{
			Type[] array = targets;
			foreach (Type type in array)
			{
				if (t == type)
				{
					return true;
				}
			}
			if (!t.IsGenericType)
			{
				return false;
			}
			array = t.GetGenericArguments();
			for (int i = 0; i < array.Length; i++)
			{
				if (Mentions(array[i], targets))
				{
					return true;
				}
			}
			return false;
		}

		private static IEnumerable<CodeInstruction> Instrument_Transpiler(IEnumerable<CodeInstruction> instructions, MethodBase original)
		{
			//IL_00a5: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ac: Expected O, but got Unknown
			//IL_00d3: Unknown result type (might be due to invalid IL or missing references)
			//IL_00dd: Expected O, but got Unknown
			//IL_014f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0156: Expected O, but got Unknown
			//IL_0189: Unknown result type (might be due to invalid IL or missing references)
			//IL_0193: Expected O, but got Unknown
			List<CodeInstruction> list = instructions.ToList();
			List<CodeInstruction> list2 = new List<CodeInstruction>(list.Count + 8);
			string text = original?.DeclaringType?.Name + "." + original?.Name;
			for (int i = 0; i < list.Count; i++)
			{
				CodeInstruction val = list[i];
				int num = ClassifyRuntime(val, (i > 0) ? list[i - 1] : null);
				if (num == 2 && IsInfoListSort(val))
				{
					int num2 = RegisterSite(text + " @" + i + " Sort, changed order (prepare)");
					CodeInstruction val2 = new CodeInstruction(OpCodes.Ldc_I4, (object)num2);
					CodeInstructionExtensions.MoveLabelsTo(val, val2);
					CodeInstructionExtensions.MoveBlocksTo(val, val2);
					list2.Add(val2);
					list2.Add(new CodeInstruction(OpCodes.Call, (object)SortInfosTrackedMethod));
					InstrumentedSites++;
					continue;
				}
				if (num != 0)
				{
					int num3 = RegisterSite(text + " @" + i + " " + Describe(val) + ((num == 2) ? " (prepare)" : ""));
					CodeInstruction val3 = new CodeInstruction(OpCodes.Ldc_I4, (object)num3);
					CodeInstructionExtensions.MoveLabelsTo(val, val3);
					CodeInstructionExtensions.MoveBlocksTo(val, val3);
					list2.Add(val3);
					list2.Add(new CodeInstruction(OpCodes.Call, (object)((num == 2) ? BumpPrepMethod : BumpMethod)));
					InstrumentedSites++;
				}
				list2.Add(val);
			}
			return list2;
		}

		private static bool IsInfoListSort(CodeInstruction ins)
		{
			if ((ins.opcode == OpCodes.Callvirt || ins.opcode == OpCodes.Call) && ins.operand is MethodInfo methodInfo)
			{
				return methodInfo == InfoListSort;
			}
			return false;
		}

		public static void SortInfosTracked(List<UpgradeInfo> list, int site)
		{
			int count = list.Count;
			if (_sortBuffer.Length < count)
			{
				_sortBuffer = (UpgradeInfo[])(object)new UpgradeInfo[Math.Max(count, _sortBuffer.Length * 2)];
			}
			UpgradeInfo[] sortBuffer = _sortBuffer;
			list.CopyTo(sortBuffer, 0);
			bool flag = false;
			try
			{
				list.Sort();
				flag = true;
			}
			finally
			{
				bool flag2 = !flag || list.Count != count;
				int num = 0;
				while (!flag2 && num < count)
				{
					if (sortBuffer[num] != list[num])
					{
						flag2 = true;
					}
					num++;
				}
				Array.Clear(sortBuffer, 0, count);
				if (flag2)
				{
					BumpPrep(site);
				}
				else
				{
					Stats.InfoSortUnchanged++;
				}
			}
		}

		private static string Describe(CodeInstruction ins)
		{
			if (ins.operand is MethodInfo methodInfo)
			{
				return methodInfo.Name;
			}
			if (ins.operand is FieldInfo fieldInfo)
			{
				return "store " + fieldInfo.Name;
			}
			return ins.opcode.Name;
		}

		private static int RegisterSite(string name)
		{
			SiteNames.Add(name);
			if (SiteNames.Count > _siteCounts.Length)
			{
				Array.Resize(ref _siteCounts, _siteCounts.Length * 2);
			}
			return SiteNames.Count - 1;
		}

		public static void Bump(int site)
		{
			_gen++;
			_prepGen++;
			Stats.GenBumps++;
			if ((uint)site < (uint)_siteCounts.Length)
			{
				_siteCounts[site]++;
			}
		}

		public static void BumpPrep(int site)
		{
			_prepGen++;
			Stats.PrepGenBumps++;
			if ((uint)site < (uint)_siteCounts.Length)
			{
				_siteCounts[site]++;
			}
		}

		public static string TopSites(int max)
		{
			IEnumerable<string> values = from i in (from i in Enumerable.Range(0, SiteNames.Count)
					where _siteCounts[i] > 0
					orderby _siteCounts[i] descending
					select i).Take(max)
				select _siteCounts[i] + "x " + SiteNames[i];
			return string.Join("; ", values);
		}

		public static void ResetSiteCounts()
		{
			Array.Clear(_siteCounts, 0, _siteCounts.Length);
		}

		private static bool Cleanup_Prefix(object __instance, out State __state)
		{
			__state = default(State);
			if (!Plugin.CleanupMemoActive)
			{
				return true;
			}
			List<UpgradeEquipData> list = _upsRef.Invoke(__instance);
			List<UpgradeEquipData> list2 = _skinsRef.Invoke(__instance);
			PlayerData instance = PlayerData.Instance;
			if (instance == null)
			{
				return true;
			}
			if (Memos.TryGetValue(__instance, out var value) && value.Gen == _gen && value.Pd == instance && value.Data == _dataRef.Invoke(instance) && value.Ups == list && (list == null || _versionRef.Invoke(list) == value.UpsVer) && value.Skins == list2 && (list2 == null || _versionRef.Invoke(list2) == value.SkinsVer))
			{
				if (!Plugin.VerifyActive)
				{
					Stats.CleanupSkipped++;
					return false;
				}
				__state.ExpectNoop = true;
			}
			__state.Track = true;
			__state.Gen = _gen;
			__state.Ups = list;
			__state.UpsVer = ((list != null) ? _versionRef.Invoke(list) : 0);
			__state.Skins = list2;
			__state.SkinsVer = ((list2 != null) ? _versionRef.Invoke(list2) : 0);
			return true;
		}

		private static Exception Cleanup_Finalizer(Exception __exception, object __instance, State __state)
		{
			if (!__state.Track)
			{
				return __exception;
			}
			Stats.CleanupRun++;
			List<UpgradeEquipData> list = _upsRef.Invoke(__instance);
			List<UpgradeEquipData> list2 = _skinsRef.Invoke(__instance);
			bool flag = __exception == null && list == __state.Ups && (list == null || _versionRef.Invoke(list) == __state.UpsVer) && list2 == __state.Skins && (list2 == null || _versionRef.Invoke(list2) == __state.SkinsVer);
			if (__state.ExpectNoop)
			{
				Stats.CleanupVerifyChecks++;
				if (!flag)
				{
					Stats.CleanupVerifyMismatches++;
					if (_mismatchLogs++ < 20)
					{
						Plugin.Log.LogError((object)"CLEANUP VERIFY MISMATCH: memo said no-op but Cleanup changed the equipped list");
					}
				}
			}
			Memos.Remove(__instance);
			if (flag && __state.Gen == _gen)
			{
				PlayerData instance = PlayerData.Instance;
				if (instance != null)
				{
					Memos.Add(__instance, new Memo
					{
						Gen = _gen,
						Pd = instance,
						Data = _dataRef.Invoke(instance),
						Ups = list,
						UpsVer = __state.UpsVer,
						Skins = list2,
						SkinsVer = __state.SkinsVer
					});
				}
			}
			return __exception;
		}

		public static void Clear()
		{
			_gen++;
			_prepGen++;
		}
	}
	internal static class InstanceCache
	{
		private struct Entry
		{
			public UpgradeInstance Instance;

			public int Key;

			public UpgradeInfo Info;

			public int InfoIndex;

			public int InstanceIndex;
		}

		private static readonly FieldRef<PlayerData, Dictionary<int, List<UpgradeInfo>>> DataRef = AccessTools.FieldRefAccess<PlayerData, Dictionary<int, List<UpgradeInfo>>>("data");

		private static readonly Dictionary<int, Entry> Map = new Dictionary<int, Entry>(4096);

		private static readonly HashSet<int> Duplicates = new HashSet<int>();

		private static readonly HashSet<int> Bypass = new HashSet<int>();

		private static PlayerData _ownerPlayerData;

		private static Dictionary<int, List<UpgradeInfo>> _ownerData;

		public static int IndexedCount => Map.Count;

		public static int DuplicateCount => Duplicates.Count;

		public static int BypassCount => Bypass.Count;

		public static bool LastRebuildIncomplete { get; private set; }

		public static void Clear()
		{
			Map.Clear();
			Duplicates.Clear();
			Bypass.Clear();
			_ownerPlayerData = null;
			_ownerData = null;
		}

		public static bool TryGet(int id, out UpgradeInstance result)
		{
			result = null;
			PlayerData instance = PlayerData.Instance;
			if (instance == null)
			{
				return false;
			}
			Dictionary<int, List<UpgradeInfo>> dictionary = DataRef.Invoke(instance);
			if (dictionary == null)
			{
				return false;
			}
			if (instance != _ownerPlayerData || dictionary != _ownerData)
			{
				Map.Clear();
				Duplicates.Clear();
				Bypass.Clear();
				_ownerPlayerData = instance;
				_ownerData = dictionary;
				Stats.CacheResets++;
			}
			if (!Map.TryGetValue(id, out var value))
			{
				return false;
			}
			if (!IsStillReachable(ref value, dictionary, id))
			{
				Map.Remove(id);
				Stats.CacheStale++;
				return false;
			}
			result = value.Instance;
			return true;
		}

		private static bool IsStillReachable(ref Entry e, Dictionary<int, List<UpgradeInfo>> data, int id)
		{
			UpgradeInstance instance = e.Instance;
			if (instance == null || instance.InstanceID != id)
			{
				return false;
			}
			if (!data.TryGetValue(e.Key, out var value) || value == null)
			{
				return false;
			}
			if (e.InfoIndex >= value.Count || value[e.InfoIndex] != e.Info)
			{
				int num = IndexOfRef(value, e.Info);
				if (num < 0)
				{
					return false;
				}
				e.InfoIndex = num;
				Map[id] = e;
			}
			List<UpgradeInstance> instances = e.Info.Instances;
			if (instances == null)
			{
				return false;
			}
			if (e.InstanceIndex >= instances.Count || instances[e.InstanceIndex] != instance)
			{
				int num2 = IndexOfRef(instances, instance);
				if (num2 < 0)
				{
					return false;
				}
				e.InstanceIndex = num2;
				Map[id] = e;
			}
			return true;
		}

		private static int IndexOfRef<T>(List<T> list, T item) where T : class
		{
			for (int i = 0; i < list.Count; i++)
			{
				if (list[i] == item)
				{
					return i;
				}
			}
			return -1;
		}

		public static void OnOriginalResult(int id, UpgradeInstance originalResult)
		{
			if (originalResult != null && !Bypass.Contains(id))
			{
				Rebuild();
				if (!Map.ContainsKey(id))
				{
					Bypass.Add(id);
				}
			}
		}

		public static void Rebuild()
		{
			PlayerData instance = PlayerData.Instance;
			if (instance == null)
			{
				return;
			}
			Dictionary<int, List<UpgradeInfo>> dictionary = DataRef.Invoke(instance);
			Map.Clear();
			Duplicates.Clear();
			Bypass.Clear();
			_ownerPlayerData = instance;
			_ownerData = dictionary;
			LastRebuildIncomplete = false;
			Stats.CacheRebuilds++;
			if (dictionary == null)
			{
				return;
			}
			foreach (KeyValuePair<int, List<UpgradeInfo>> item in dictionary)
			{
				List<UpgradeInfo> value = item.Value;
				if (value == null)
				{
					LastRebuildIncomplete = true;
					break;
				}
				for (int i = 0; i < value.Count; i++)
				{
					UpgradeInfo val = value[i];
					if (val == null)
					{
						LastRebuildIncomplete = true;
						return;
					}
					List<UpgradeInstance> instances = val.Instances;
					if (instances == null)
					{
						continue;
					}
					for (int j = 0; j < instances.Count; j++)
					{
						UpgradeInstance val2 = instances[j];
						if (val2 == null)
						{
							LastRebuildIncomplete = true;
							return;
						}
						int instanceID = val2.InstanceID;
						if (!Duplicates.Contains(instanceID))
						{
							if (Map.ContainsKey(instanceID))
							{
								Map.Remove(instanceID);
								Duplicates.Add(instanceID);
								continue;
							}
							Map[instanceID] = new Entry
							{
								Instance = val2,
								Key = item.Key,
								Info = val,
								InfoIndex = i,
								InstanceIndex = j
							};
						}
					}
				}
			}
		}
	}
	internal static class Patches
	{
		public struct LookupState
		{
			public long Start;

			public bool RanOriginal;

			public bool FeedCache;

			public bool Verify;

			public bool CachedHit;

			public UpgradeInstance Cached;
		}

		public struct OpState
		{
			public long Start;

			public long Lookups;

			public long Stacking;

			public long MoveNext;

			public long ApplyTicks;

			public long Bumps;

			public bool Outermost;
		}

		internal static bool Detailed;

		private static int _mismatchLogs;

		private static readonly MethodInfo ApplyUpgradesMethod = AccessTools.Method(typeof(IGear), "ApplyUpgrades", (Type[])null, (Type[])null);

		private static readonly MethodInfo TimedApplyUpgradesMethod = AccessTools.Method(typeof(Patches), "TimedApplyUpgrades", (Type[])null, (Type[])null);

		private static int _applyDepth;

		private static int _reapplyDepth;

		private static int _stackDepth;

		private static int _appliedDepth;

		private static ManualLogSource Log => Plugin.Log;

		public static void ApplyAll(Harmony harmony, bool timing, bool detailed)
		{
			Detailed = detailed;
			Type type = AccessTools.Inner(typeof(PlayerData), "GearData");
			Patch(harmony, AccessTools.Method(typeof(PlayerData), "GetUpgradeInstanceFromID", new Type[1] { typeof(int) }, (Type[])null), "Lookup_Prefix", "Lookup_Postfix");
			if (timing)
			{
				foreach (MethodBase item in ApplyUpgradesCallers())
				{
					Patch(harmony, item, null, null, null, "RedirectApplyUpgrades_Transpiler");
				}
				Patch(harmony, AccessTools.Method(typeof(GearUtil), "ReapplyUpgrades", (Type[])null, (Type[])null), "Reapply_Prefix", null, "Reapply_Finalizer");
			}
			if (!detailed)
			{
				return;
			}
			foreach (MethodInfo declaredMethod in AccessTools.GetDeclaredMethods(typeof(UpgradeProperty)))
			{
				if (declaredMethod.Name == "GetStackingModifier")
				{
					Patch(harmony, declaredMethod, "Stacking_Prefix", null, "Stacking_Finalizer");
				}
			}
			Patch(harmony, AccessTools.Method(typeof(UpgradeProperty), "GetAppliedCount", (Type[])null, (Type[])null), "Applied_Prefix", null, "Applied_Finalizer");
			Patch(harmony, AccessTools.Method(typeof(EquippedUpgradeEnumerator), "MoveNext", (Type[])null, (Type[])null), "MoveNext_Prefix");
			Patch(harmony, (type == null) ? null : AccessTools.Method(type, "Cleanup", (Type[])null, (Type[])null), "Cleanup_Prefix");
		}

		private static void Patch(Harmony harmony, MethodBase target, string prefix = null, string postfix = null, string finalizer = null, string transpiler = null)
		{
			//IL_008a: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a1: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ba: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d4: Unknown result type (might be due to invalid IL or missing references)
			if (target == null)
			{
				Log.LogWarning((object)("Patch target not found (prefix=" + prefix + ")"));
				return;
			}
			string text = target.DeclaringType?.Name + "." + target.Name + "(" + target.GetParameters().Length + ")";
			try
			{
				object obj = (object)((prefix == null) ? ((HarmonyMethod)null) : new HarmonyMethod(typeof(Patches), prefix, (Type[])null));
				object obj2 = (object)((postfix == null) ? ((HarmonyMethod)null) : new HarmonyMethod(typeof(Patches), postfix, (Type[])null));
				HarmonyMethod val = ((finalizer == null) ? ((HarmonyMethod)null) : new HarmonyMethod(typeof(Patches), finalizer, (Type[])null));
				harmony.Patch(target, (HarmonyMethod)obj, (HarmonyMethod)obj2, (transpiler == null) ? ((HarmonyMethod)null) : new HarmonyMethod(typeof(Patches), transpiler, (Type[])null), val, (HarmonyMethod)null);
				Log.LogDebug((object)("Patched " + text));
			}
			catch (Exception ex)
			{
				Log.LogError((object)("Failed to patch " + text + ": " + ex));
			}
		}

		private static bool Lookup_Prefix(int id, ref UpgradeInstance __result, out LookupState __state)
		{
			__state = default(LookupState);
			if (Detailed)
			{
				__state.Start = Stopwatch.GetTimestamp();
			}
			Stats.Lookups++;
			if (!Plugin.CacheActive)
			{
				__state.RanOriginal = true;
				Stats.OriginalCalls++;
				return true;
			}
			bool flag = false;
			UpgradeInstance result = null;
			try
			{
				flag = InstanceCache.TryGet(id, out result);
			}
			catch (Exception e)
			{
				Plugin.OnCacheFault(e);
				__state.RanOriginal = true;
				Stats.OriginalCalls++;
				return true;
			}
			if (Plugin.VerifyActive)
			{
				__state.Verify = true;
				__state.CachedHit = flag;
				__state.Cached = result;
				__state.RanOriginal = true;
				__state.FeedCache = !flag;
				Stats.OriginalCalls++;
				return true;
			}
			if (flag)
			{
				__result = result;
				Stats.CacheHits++;
				return false;
			}
			__state.RanOriginal = true;
			__state.FeedCache = true;
			Stats.OriginalCalls++;
			return true;
		}

		private static void Lookup_Postfix(int id, UpgradeInstance __result, LookupState __state)
		{
			if (__state.RanOriginal)
			{
				if (__result == null)
				{
					Stats.OriginalNull++;
				}
				if (__state.Verify)
				{
					Stats.VerifyChecks++;
					if (__state.CachedHit && __state.Cached != __result)
					{
						Stats.VerifyMismatches++;
						if (_mismatchLogs++ < 20)
						{
							Log.LogError((object)("VERIFY MISMATCH id=" + id + " cached=" + Describe(__state.Cached) + " original=" + Describe(__result)));
						}
					}
				}
				if (__state.FeedCache && Plugin.CacheActive)
				{
					try
					{
						InstanceCache.OnOriginalResult(id, __result);
					}
					catch (Exception e)
					{
						Plugin.OnCacheFault(e);
					}
				}
			}
			if (Detailed)
			{
				Stats.LookupTicks += Stopwatch.GetTimestamp() - __state.Start;
			}
		}

		private static string Describe(UpgradeInstance inst)
		{
			if (inst == null)
			{
				return "null";
			}
			try
			{
				string text = inst.InstanceID.ToString();
				Upgrade upgrade = inst.Upgrade;
				return "#" + text + " " + ((upgrade != null) ? ((Object)upgrade).name : null);
			}
			catch
			{
				return "#?";
			}
		}

		private static IEnumerable<MethodBase> ApplyUpgradesCallers()
		{
			string[] array = new string[7] { "GearUtil:ReapplyUpgrades", "Gun:Setup", "MycoGear:Setup", "ScrapperGear:Setup", "Throwable:Setup", "Wingsuit:Setup", "EquipGearInteractable:EquipGear_ClientRpc" };
			string[] array2 = array;
			foreach (string text in array2)
			{
				string[] parts = text.Split(':');
				Type type = AccessTools.TypeByName(parts[0]);
				if (type == null)
				{
					Log.LogWarning((object)("ApplyUpgrades caller type not found: " + parts[0]));
					continue;
				}
				foreach (MethodInfo declaredMethod in AccessTools.GetDeclaredMethods(type))
				{
					if (declaredMethod.Name == parts[1] && !declaredMethod.IsAbstract)
					{
						yield return declaredMethod;
					}
				}
			}
			Type type2 = AccessTools.TypeByName("Menu");
			MethodInfo methodInfo = ((type2 == null) ? null : AccessTools.Method(type2, "CloseCoroutine", (Type[])null, (Type[])null));
			MethodInfo methodInfo2 = ((methodInfo == null) ? null : AccessTools.EnumeratorMoveNext((MethodBase)methodInfo));
			if (methodInfo2 != null)
			{
				yield return methodInfo2;
			}
			else
			{
				Log.LogWarning((object)"Menu.CloseCoroutine MoveNext not found");
			}
		}

		private static IEnumerable<CodeInstruction> RedirectApplyUpgrades_Transpiler(IEnumerable<CodeInstruction> instructions, MethodBase original)
		{
			int replaced = 0;
			foreach (CodeInstruction instruction in instructions)
			{
				if ((instruction.opcode == OpCodes.Callvirt || instruction.opcode == OpCodes.Call) && instruction.operand is MethodInfo methodInfo && methodInfo == ApplyUpgradesMethod)
				{
					instruction.opcode = OpCodes.Call;
					instruction.operand = TimedApplyUpgradesMethod;
					replaced++;
				}
				yield return instruction;
			}
			string text = original?.DeclaringType?.Name + "." + original?.Name;
			if (replaced == 0)
			{
				Log.LogWarning((object)("No IGear.ApplyUpgrades call found in " + text));
			}
			else
			{
				Log.LogDebug((object)("Redirected " + replaced + " ApplyUpgrades call(s) in " + text));
			}
		}

		public static void TimedApplyUpgrades(IGear gear)
		{
			OpState s = BeginOp(ref _applyDepth);
			try
			{
				gear.ApplyUpgrades();
			}
			finally
			{
				long num = EndOp(ref _applyDepth, in s);
				if (num >= 0)
				{
					Stats.ApplyCalls++;
					Stats.ApplyTicks += num;
					if (num > Stats.ApplyMaxTicks)
					{
						Stats.ApplyMaxTicks = num;
					}
					if (_reapplyDepth == 0)
					{
						LogOp("ApplyUpgrades", GearName((IUpgradable)(object)gear), num, in s);
					}
				}
			}
		}

		private static OpState BeginOp(ref int depth)
		{
			OpState result = new OpState
			{
				Outermost = (depth++ == 0)
			};
			if (result.Outermost)
			{
				result.Lookups = Stats.Lookups;
				result.Stacking = Stats.StackingCalls;
				result.MoveNext = Stats.EnumMoveNext;
				result.ApplyTicks = Stats.ApplyTicks;
				result.Bumps = Stats.GenBumps;
				result.Start = Stopwatch.GetTimestamp();
			}
			return result;
		}

		private static long EndOp(ref int depth, in OpState s)
		{
			depth--;
			if (!s.Outermost)
			{
				return -1L;
			}
			return Stopwatch.GetTimestamp() - s.Start;
		}

		private static void LogOp(string what, string target, long ticks, in OpState s)
		{
			double num = (double)ticks * Stats.TicksToMs;
			if (!(num < Plugin.LogThresholdMs))
			{
				string text = ((what == "ReapplyUpgrades") ? $" (ApplyUpgrades inside {(double)(Stats.ApplyTicks - s.ApplyTicks) * Stats.TicksToMs:F2} ms)" : "");
				Log.LogInfo((object)string.Format("[perf] {0}{1}: {2:F2} ms{3} | lookups {4}, stacking {5}, moveNext {6}, gen bumps {7} | {8}", what, (target == null) ? "" : ("[" + target + "]"), num, text, Stats.Lookups - s.Lookups, Stats.StackingCalls - s.Stacking, Stats.EnumMoveNext - s.MoveNext, Stats.GenBumps - s.Bumps, Plugin.ModeString));
			}
		}

		private static void Reapply_Prefix(out OpState __state)
		{
			__state = BeginOp(ref _reapplyDepth);
		}

		private static Exception Reapply_Finalizer(Exception __exception, OpState __state)
		{
			long num = EndOp(ref _reapplyDepth, in __state);
			if (num >= 0)
			{
				Stats.ReapplyCalls++;
				Stats.ReapplyTicks += num;
				if (num > Stats.ReapplyMaxTicks)
				{
					Stats.ReapplyMaxTicks = num;
				}
				LogOp("ReapplyUpgrades", null, num, in __state);
			}
			return __exception;
		}

		internal static string GearName(IUpgradable gear)
		{
			try
			{
				object obj;
				if (gear == null)
				{
					obj = null;
				}
				else
				{
					GearInfo info = gear.Info;
					obj = ((info != null) ? info.Name : null);
				}
				if (obj == null)
				{
					obj = "?";
				}
				return (string)obj;
			}
			catch
			{
				return "?";
			}
		}

		private static void Stacking_Prefix(out long __state)
		{
			__state = ((_stackDepth++ == 0) ? Stopwatch.GetTimestamp() : 0);
		}

		private static Exception Stacking_Finalizer(Exception __exception, long __state)
		{
			if (--_stackDepth == 0)
			{
				Stats.StackingCalls++;
				Stats.StackingTicks += Stopwatch.GetTimestamp() - __state;
			}
			return __exception;
		}

		private static void Applied_Prefix(out long __state)
		{
			__state = ((_appliedDepth++ == 0) ? Stopwatch.GetTimestamp() : 0);
		}

		private static Exception Applied_Finalizer(Exception __exception, long __state)
		{
			if (--_appliedDepth == 0)
			{
				Stats.AppliedCountCalls++;
				Stats.AppliedCountTicks += Stopwatch.GetTimestamp() - __state;
			}
			return __exception;
		}

		private static void MoveNext_Prefix()
		{
			Stats.EnumMoveNext++;
		}

		private static void Cleanup_Prefix()
		{
			Stats.GearDataCleanup++;
		}
	}
	[BepInPlugin("t4t5u0.UpgradeLagFix", "Upgrade Lag Fix", "1.0.0")]
	[MycoMod(/*Could not decode attribute arguments.*/)]
	public class Plugin : BaseUnityPlugin
	{
		public const string Guid = "t4t5u0.UpgradeLagFix";

		public const string Name = "Upgrade Lag Fix";

		public const string Version = "1.0.0";

		internal static ManualLogSource Log;

		private static ConfigEntry<bool> _cacheEnabled;

		private static ConfigEntry<bool> _verify;

		private static ConfigEntry<bool> _cleanupMemo;

		private static ConfigEntry<bool> _prepareMemo;

		private static ConfigEntry<bool> _sortSkip;

		private static ConfigEntry<bool> _logSlowApplies;

		private static ConfigEntry<float> _logThresholdMs;

		private static ConfigEntry<bool> _hotkeys;

		private static ConfigEntry<bool> _detailedProfiling;

		private static bool _cacheFaulted;

		internal static bool? CacheOverride;

		internal static bool? VerifyOverride;

		internal static bool? CleanupOverride;

		internal static bool? PrepareOverride;

		internal static bool? SortOverride;

		internal static bool CacheActive
		{
			get
			{
				if (!_cacheFaulted)
				{
					return CacheOverride ?? _cacheEnabled.Value;
				}
				return false;
			}
		}

		internal static bool VerifyActive => VerifyOverride ?? _verify.Value;

		internal static bool CleanupMemoActive
		{
			get
			{
				if (CleanupMemo.Available)
				{
					return CleanupOverride ?? _cleanupMemo.Value;
				}
				return false;
			}
		}

		internal static bool PrepareMemoActive
		{
			get
			{
				if (PrepareMemo.PrepareAvailable)
				{
					return PrepareOverride ?? _prepareMemo.Value;
				}
				return false;
			}
		}

		internal static bool SortSkipActive
		{
			get
			{
				if (PrepareMemo.SortAvailable)
				{
					return SortOverride ?? _sortSkip.Value;
				}
				return false;
			}
		}

		internal static string ModeString => "cache=" + OnOff(CacheActive, available: true) + ", cleanup-skip=" + OnOff(CleanupMemoActive, CleanupMemo.Available) + ", prepare-skip=" + OnOff(PrepareMemoActive, PrepareMemo.PrepareAvailable) + ", sort-skip=" + OnOff(SortSkipActive, PrepareMemo.SortAvailable) + (VerifyActive ? ", verify" : "");

		internal static double LogThresholdMs => _logThresholdMs.Value;

		private static string OnOff(bool active, bool available)
		{
			if (!active)
			{
				if (!available)
				{
					return "N/A";
				}
				return "OFF";
			}
			return "ON";
		}

		private void Awake()
		{
			//IL_015d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0162: Unknown result type (might be due to invalid IL or missing references)
			//IL_017c: Expected O, but got Unknown
			//IL_017c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0182: Expected O, but got Unknown
			//IL_0187: Expected O, but got Unknown
			Log = ((BaseUnityPlugin)this).Logger;
			_cacheEnabled = ((BaseUnityPlugin)this).Config.Bind<bool>("Cache", "Enabled", true, "Use the InstanceID index for PlayerData.GetUpgradeInstanceFromID.");
			_verify = ((BaseUnityPlugin)this).Config.Bind<bool>("Debug", "Verify", false, "Run the original code next to every cached or skipped step, compare the results, and log mismatches. The original result is always used, so this is safe but as slow as the unmodded game.");
			_cleanupMemo = ((BaseUnityPlugin)this).Config.Bind<bool>("Walk", "SkipRedundantCleanup", true, "Skip GearData.Cleanup when it provably cannot remove anything. It runs on every step of EquippedUpgradeEnumerator.");
			_prepareMemo = ((BaseUnityPlugin)this).Config.Bind<bool>("Walk", "SkipRedundantPrepare", true, "Skip the destroyed-check in GearData.PrepareUpgrades, which runs at the start of every walk over the equipped upgrades, when it provably cannot remove anything.");
			_sortSkip = ((BaseUnityPlugin)this).Config.Bind<bool>("Walk", "SkipSortedSort", true, "Skip equippedUpgrades.Sort() in PrepareUpgrades when the list is already strictly ascending, where sorting cannot change it.");
			_logSlowApplies = ((BaseUnityPlugin)this).Config.Bind<bool>("Logging", "LogSlowApplies", false, "Time ApplyUpgrades / ReapplyUpgrades and log slow calls. Needs a game restart.");
			_logThresholdMs = ((BaseUnityPlugin)this).Config.Bind<float>("Logging", "LogThresholdMs", 1f, "With LogSlowApplies, log calls that take at least this many milliseconds.");
			_detailedProfiling = ((BaseUnityPlugin)this).Config.Bind<bool>("Debug", "DetailedProfiling", false, "Install per-call profiling hooks (GetStackingModifier / GetAppliedCount timing, MoveNext and Cleanup counts, lookup timing). They slow down the code they measure. Needs a game restart.");
			_hotkeys = ((BaseUnityPlugin)this).Config.Bind<bool>("Debug", "Hotkeys", false, "F7: toggle prepare+sort skip, F8: toggle cleanup skip, F9: toggle cache (each prints and resets stats). F10: benchmark. F11: print and reset stats. Toggles are saved to this file.");
			_cacheEnabled.SettingChanged += delegate
			{
				InstanceCache.Clear();
			};
			Harmony val = new Harmony("t4t5u0.UpgradeLagFix");
			Patches.ApplyAll(val, _logSlowApplies.Value, _detailedProfiling.Value);
			CleanupMemo.Setup(val);
			PrepareMemo.Setup(val);
			Log.LogInfo((object)("Upgrade Lag Fix 1.0.0 loaded. " + ModeString));
		}

		internal static void OnCacheFault(Exception e)
		{
			if (!_cacheFaulted)
			{
				_cacheFaulted = true;
				InstanceCache.Clear();
				Log.LogError((object)("Cache disabled after an unexpected error; falling back to the original lookup. " + e));
			}
		}

		private void Update()
		{
			if (!_hotkeys.Value)
			{
				return;
			}
			Keyboard current;
			try
			{
				current = Keyboard.current;
			}
			catch
			{
				return;
			}
			if (current != null)
			{
				if (((ButtonControl)current.f7Key).wasPressedThisFrame)
				{
					Log.LogInfo((object)Stats.Summary("stats before toggle"));
					Stats.Reset();
					bool value = !_prepareMemo.Value;
					_prepareMemo.Value = value;
					_sortSkip.Value = value;
					CleanupMemo.Clear();
					Log.LogInfo((object)("Mode is now " + ModeString));
				}
				else if (((ButtonControl)current.f8Key).wasPressedThisFrame)
				{
					Log.LogInfo((object)Stats.Summary("stats before toggle"));
					Stats.Reset();
					_cleanupMemo.Value = !_cleanupMemo.Value;
					CleanupMemo.Clear();
					Log.LogInfo((object)("Mode is now " + ModeString));
				}
				else if (((ButtonControl)current.f9Key).wasPressedThisFrame)
				{
					Log.LogInfo((object)Stats.Summary("stats before toggle"));
					Stats.Reset();
					_cacheEnabled.Value = !_cacheEnabled.Value;
					Log.LogInfo((object)("Mode is now " + ModeString));
				}
				else if (((ButtonControl)current.f10Key).wasPressedThisFrame)
				{
					Bench.Run();
				}
				else if (((ButtonControl)current.f11Key).wasPressedThisFrame)
				{
					Log.LogInfo((object)Stats.Summary("stats"));
					Stats.Reset();
				}
			}
		}
	}
	internal static class PrepareMemo
	{
		private sealed class Memo
		{
			public int Gen;

			public PlayerData Pd;

			public object Data;

			public List<UpgradeEquipData> List;

			public int VersionAfter;
		}

		public struct State
		{
			public bool Track;

			public bool ExpectNoop;

			public int Gen;

			public List<UpgradeEquipData> List;

			public int CountBefore;
		}

		private static readonly ConditionalWeakTable<object, Memo> Memos = new ConditionalWeakTable<object, Memo>();

		private static FieldRef<object, List<UpgradeEquipData>> _upsRef;

		private static FieldRef<List<UpgradeEquipData>, int> _versionRef;

		private static FieldRef<PlayerData, Dictionary<int, List<UpgradeInfo>>> _dataRef;

		private static MethodInfo _listSort;

		private static int _mismatchLogs;

		private static int _sortMismatchLogs;

		private static int _sortSitesReplaced;

		public static bool PrepareAvailable { get; private set; }

		public static bool SortAvailable { get; private set; }

		public static void Setup(Harmony harmony)
		{
			//IL_0088: Unknown result type (might be due to invalid IL or missing references)
			//IL_0094: Expected O, but got Unknown
			//IL_00e3: Unknown result type (might be due to invalid IL or missing references)
			//IL_00fa: Unknown result type (might be due to invalid IL or missing references)
			//IL_0105: Expected O, but got Unknown
			//IL_0105: Expected O, but got Unknown
			ManualLogSource log = Plugin.Log;
			try
			{
				Type type = AccessTools.Inner(typeof(PlayerData), "GearData");
				_upsRef = AccessTools.FieldRefAccess<List<UpgradeEquipData>>(type, "equippedUpgrades");
				_versionRef = AccessTools.FieldRefAccess<List<UpgradeEquipData>, int>("_version");
				_dataRef = AccessTools.FieldRefAccess<PlayerData, Dictionary<int, List<UpgradeInfo>>>("data");
				_listSort = AccessTools.Method(typeof(List<UpgradeEquipData>), "Sort", Type.EmptyTypes, (Type[])null);
				MethodInfo methodInfo = AccessTools.Method(type, "PrepareUpgrades", (Type[])null, (Type[])null);
				harmony.Patch((MethodBase)methodInfo, (HarmonyMethod)null, (HarmonyMethod)null, new HarmonyMethod(typeof(PrepareMemo), "Sort_Transpiler", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null);
				SortAvailable = _sortSitesReplaced == 1;
				if (!SortAvailable)
				{
					log.LogWarning((object)("Sort skip disabled: expected 1 List.Sort() call in PrepareUpgrades, found " + _sortSitesReplaced));
				}
				if (CleanupMemo.Available && CleanupMemo.PrepAvailable)
				{
					harmony.Patch((MethodBase)methodInfo, new HarmonyMethod(typeof(PrepareMemo), "Prepare_Prefix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, new HarmonyMethod(typeof(PrepareMemo), "Prepare_Finalizer", (Type[])null), (HarmonyMethod)null);
					PrepareAvailable = true;
				}
				else
				{
					log.LogWarning((object)"Prepare memo disabled: generation tracking is not available");
				}
				log.LogInfo((object)("Prepare memo: destroyed-check skip " + (PrepareAvailable ? "ready" : "N/A") + ", sort skip " + (SortAvailable ? "ready" : "N/A")));
			}
			catch (Exception ex)
			{
				PrepareAvailable = false;
				SortAvailable = false;
				log.LogError((object)("Prepare memo setup failed, feature disabled: " + ex));
			}
		}

		private static IEnumerable<CodeInstruction> Sort_Transpiler(IEnumerable<CodeInstruction> instructions)
		{
			MethodInfo helper = AccessTools.Method(typeof(PrepareMemo), "SortOrSkip", (Type[])null, (Type[])null);
			int replaced = 0;
			foreach (CodeInstruction instruction in instructions)
			{
				if ((instruction.opcode == OpCodes.Callvirt || instruction.opcode == OpCodes.Call) && instruction.operand is MethodInfo methodInfo && methodInfo == _listSort)
				{
					instruction.opcode = OpCodes.Call;
					instruction.operand = helper;
					replaced++;
				}
				yield return instruction;
			}
			_sortSitesReplaced = replaced;
		}

		public static void SortOrSkip(List<UpgradeEquipData> list)
		{
			//IL_002a: Unknown result type (might be due to invalid IL or missing references)
			//IL_002f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0038: Unknown result type (might be due to invalid IL or missing references)
			//IL_003d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0040: Unknown result type (might be due to invalid IL or missing references)
			//IL_0041: Unknown result type (might be due to invalid IL or missing references)
			//IL_004f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0051: Unknown result type (might be due to invalid IL or missing references)
			if (!Plugin.SortSkipActive || !SortAvailable)
			{
				list.Sort();
				return;
			}
			int count = list.Count;
			bool flag = true;
			if (count > 1)
			{
				Comparer<UpgradeEquipData> comparer = Comparer<UpgradeEquipData>.Default;
				try
				{
					UpgradeEquipData x = list[0];
					for (int i = 1; i < count; i++)
					{
						UpgradeEquipData val = list[i];
						if (comparer.Compare(x, val) >= 0)
						{
							flag = false;
							break;
						}
						x = val;
					}
				}
				catch
				{
					flag = false;
				}
			}
			if (!flag)
			{
				Stats.SortRun++;
				list.Sort();
				return;
			}
			if (Plugin.VerifyActive && count > 1)
			{
				VerifySorted(list);
			}
			_versionRef.Invoke(list)++;
			Stats.SortSkipped++;
		}

		private static void VerifySorted(List<UpgradeEquipData> list)
		{
			//IL_0023: Unknown result type (might be due to invalid IL or missing references)
			//IL_0028: Unknown result type (might be due to invalid IL or missing references)
			//IL_002b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0030: Unknown result type (might be due to invalid IL or missing references)
			//IL_0031: Unknown result type (might be due to invalid IL or missing references)
			//IL_0037: Unknown result type (might be due to invalid IL or missing references)
			//IL_003f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0045: Unknown result type (might be due to invalid IL or missing references)
			//IL_004d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0053: Unknown result type (might be due to invalid IL or missing references)
			//IL_005b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0061: Unknown result type (might be due to invalid IL or missing references)
			Stats.SortVerifyChecks++;
			List<UpgradeEquipData> list2 = new List<UpgradeEquipData>(list);
			list2.Sort();
			for (int i = 0; i < list.Count; i++)
			{
				UpgradeEquipData val = list[i];
				UpgradeEquipData val2 = list2[i];
				if (val.upgradeID != val2.upgradeID || val.x != val2.x || val.y != val2.y || val.rotation != val2.rotation)
				{
					Stats.SortVerifyMismatches++;
					if (_sortMismatchLogs++ < 20)
					{
						Plugin.Log.LogError((object)("SORT VERIFY MISMATCH at " + i + ": kept #" + val.upgradeID + ", Sort() gives #" + val2.upgradeID));
					}
					break;
				}
			}
		}

		private static bool Prepare_Prefix(object __instance, bool sort, out State __state)
		{
			__state = default(State);
			if (!Plugin.PrepareMemoActive)
			{
				return true;
			}
			List<UpgradeEquipData> list = _upsRef.Invoke(__instance);
			if (list == null)
			{
				return true;
			}
			PlayerData instance = PlayerData.Instance;
			if (instance == null)
			{
				return true;
			}
			if (Memos.TryGetValue(__instance, out var value) && value.Gen == CleanupMemo.PrepGen && value.Pd == instance && value.Data == _dataRef.Invoke(instance) && value.List == list && _versionRef.Invoke(list) == value.VersionAfter)
			{
				if (!Plugin.VerifyActive)
				{
					if (sort)
					{
						SortOrSkip(list);
					}
					value.VersionAfter = _versionRef.Invoke(list);
					Stats.PrepareSkipped++;
					return false;
				}
				__state.ExpectNoop = true;
			}
			__state.Track = true;
			__state.Gen = CleanupMemo.PrepGen;
			__state.List = list;
			__state.CountBefore = list.Count;
			return true;
		}

		private static Exception Prepare_Finalizer(Exception __exception, object __instance, State __state)
		{
			if (!__state.Track)
			{
				return __exception;
			}
			Stats.PrepareRun++;
			List<UpgradeEquipData> list = _upsRef.Invoke(__instance);
			bool flag = __exception == null && list == __state.List && list.Count == __state.CountBefore;
			if (__state.ExpectNoop)
			{
				Stats.PrepareVerifyChecks++;
				if (!flag)
				{
					Stats.PrepareVerifyMismatches++;
					if (_mismatchLogs++ < 20)
					{
						Plugin.Log.LogError((object)"PREPARE VERIFY MISMATCH: memo said no-op but PrepareUpgrades removed entries");
					}
				}
			}
			Memos.Remove(__instance);
			if (flag && __state.Gen == CleanupMemo.PrepGen)
			{
				PlayerData instance = PlayerData.Instance;
				if (instance != null)
				{
					Memos.Add(__instance, new Memo
					{
						Gen = __state.Gen,
						Pd = instance,
						Data = _dataRef.Invoke(instance),
						List = list,
						VersionAfter = _versionRef.Invoke(list)
					});
				}
			}
			return __exception;
		}
	}
	internal static class Stats
	{
		public static readonly double TicksToMs = 1000.0 / (double)Stopwatch.Frequency;

		public static long Lookups;

		public static long LookupTicks;

		public static long CacheHits;

		public static long OriginalCalls;

		public static long OriginalNull;

		public static long CacheStale;

		public static long CacheRebuilds;

		public static long CacheResets;

		public static long VerifyChecks;

		public static long VerifyMismatches;

		public static long StackingCalls;

		public static long StackingTicks;

		public static long AppliedCountCalls;

		public static long AppliedCountTicks;

		public static long EnumMoveNext;

		public static long GearDataCleanup;

		public static long CleanupSkipped;

		public static long CleanupRun;

		public static long CleanupVerifyChecks;

		public static long CleanupVerifyMismatches;

		public static long GenBumps;

		public static long PrepGenBumps;

		public static long InfoSortUnchanged;

		public static long PrepareSkipped;

		public static long PrepareRun;

		public static long PrepareVerifyChecks;

		public static long PrepareVerifyMismatches;

		public static long SortSkipped;

		public static long SortRun;

		public static long SortVerifyChecks;

		public static long SortVerifyMismatches;

		public static long ApplyCalls;

		public static long ApplyTicks;

		public static long ApplyMaxTicks;

		public static long ReapplyCalls;

		public static long ReapplyTicks;

		public static long ReapplyMaxTicks;

		public static bool HasActivity
		{
			get
			{
				if (Lookups == 0L && ApplyCalls == 0L && ReapplyCalls == 0L)
				{
					return StackingCalls != 0;
				}
				return true;
			}
		}

		public static void Reset()
		{
			Lookups = (LookupTicks = (CacheHits = (OriginalCalls = (OriginalNull = 0L))));
			CacheStale = (CacheRebuilds = (CacheResets = (VerifyChecks = (VerifyMismatches = 0L))));
			StackingCalls = (StackingTicks = (AppliedCountCalls = (AppliedCountTicks = 0L)));
			EnumMoveNext = (GearDataCleanup = 0L);
			CleanupSkipped = (CleanupRun = (CleanupVerifyChecks = (CleanupVerifyMismatches = (GenBumps = 0L))));
			PrepGenBumps = (InfoSortUnchanged = (PrepareSkipped = (PrepareRun = (PrepareVerifyChecks = (PrepareVerifyMismatches = 0L)))));
			SortSkipped = (SortRun = (SortVerifyChecks = (SortVerifyMismatches = 0L)));
			ApplyCalls = (ApplyTicks = (ApplyMaxTicks = 0L));
			ReapplyCalls = (ReapplyTicks = (ReapplyMaxTicks = 0L));
			CleanupMemo.ResetSiteCounts();
		}

		public static string Summary(string title)
		{
			StringBuilder stringBuilder = new StringBuilder();
			stringBuilder.Append("=== ").Append(title).Append(" (")
				.Append(Plugin.ModeString)
				.Append(Patches.Detailed ? ", detailed" : ", detailed profiling off: per-call times/counts are 0")
				.AppendLine(") ===");
			stringBuilder.AppendFormat("  ReapplyUpgrades : {0} calls, total {1:F2} ms, max {2:F2} ms", ReapplyCalls, (double)ReapplyTicks * TicksToMs, (double)ReapplyMaxTicks * TicksToMs).AppendLine();
			stringBuilder.AppendFormat("  ApplyUpgrades   : {0} calls, total {1:F2} ms, max {2:F2} ms", ApplyCalls, (double)ApplyTicks * TicksToMs, (double)ApplyMaxTicks * TicksToMs).AppendLine();
			stringBuilder.AppendFormat("  GetStackingModifier : {0} calls, {1:F2} ms", StackingCalls, (double)StackingTicks * TicksToMs).AppendLine();
			stringBuilder.AppendFormat("  GetAppliedCount     : {0} calls, {1:F2} ms", AppliedCountCalls, (double)AppliedCountTicks * TicksToMs).AppendLine();
			stringBuilder.AppendFormat("  EquippedUpgradeEnumerator.MoveNext : {0}, GearData.Cleanup : {1} (skipped {2}, ran tracked {3}), generation bumps {4}", EnumMoveNext, GearDataCleanup, CleanupSkipped, CleanupRun, GenBumps).AppendLine();
			stringBuilder.AppendFormat("  PrepareUpgrades destroyed-check : skipped {0}, ran tracked {1} | Sort : skipped {2}, ran {3} | prepare-only bumps {4} (List<UpgradeInfo>.Sort with no change: {5})", PrepareSkipped, PrepareRun, SortSkipped, SortRun, PrepGenBumps, InfoSortUnchanged).AppendLine();
			stringBuilder.AppendFormat("  GetUpgradeInstanceFromID : {0} calls, {1:F2} ms (hits {2}, original {3}, original-null {4})", Lookups, (double)LookupTicks * TicksToMs, CacheHits, OriginalCalls, OriginalNull).AppendLine();
			stringBuilder.AppendFormat("  cache: indexed {0}, bypass {1}, duplicates {2}, stale {3}, rebuilds {4}, resets {5}, incomplete={6}", InstanceCache.IndexedCount, InstanceCache.BypassCount, InstanceCache.DuplicateCount, CacheStale, CacheRebuilds, CacheResets, InstanceCache.LastRebuildIncomplete).AppendLine();
			if (GenBumps > 0 || PrepGenBumps > 0)
			{
				stringBuilder.Append("  bump sites: ").AppendLine(CleanupMemo.TopSites(int.MaxValue));
			}
			if (VerifyChecks > 0 || CleanupVerifyChecks > 0 || PrepareVerifyChecks > 0 || SortVerifyChecks > 0)
			{
				stringBuilder.AppendFormat("  verify: lookup {0} checks, {1} mismatches | cleanup {2}/{3} | prepare {4}/{5} | sort {6}/{7} (checks/mismatches)", VerifyChecks, VerifyMismatches, CleanupVerifyChecks, CleanupVerifyMismatches, PrepareVerifyChecks, PrepareVerifyMismatches, SortVerifyChecks, SortVerifyMismatches).AppendLine();
			}
			return stringBuilder.ToString().TrimEnd();
		}
	}
}