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.Security;
using System.Security.Permissions;
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 MycopunkShared;
using Pigeon.Movement;
using UnityEngine;
using UnityEngine.InputSystem;
using UnityEngine.InputSystem.Controls;
using UnityEngine.SceneManagement;
[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: IgnoresAccessChecksTo("Assembly-CSharp")]
[assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")]
[assembly: AssemblyCompany("UpgradeLagFix")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("1.1.2.0")]
[assembly: AssemblyInformationalVersion("1.1.2+fe8cef1291afd0a7f7ea8a9ddfd28d3607c4cdd5")]
[assembly: AssemblyProduct("UpgradeLagFix")]
[assembly: AssemblyTitle("UpgradeLagFix")]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("1.1.2.0")]
[module: UnverifiableCode]
[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 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>> data = PlayerData.Instance.data;
if (data == null)
{
return 0;
}
foreach (KeyValuePair<int, List<UpgradeInfo>> item in data)
{
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;
}
[HarmonyPatch]
private static class InstrumentPatch
{
internal static List<MethodBase> Targets;
[HarmonyTargetMethods]
private static IEnumerable<MethodBase> TargetMethods()
{
return Targets;
}
[HarmonyTranspiler]
private static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions, MethodBase original)
{
return Instrument(instructions, original);
}
}
private sealed class RefEq<T> : IEqualityComparer<T> where T : class
{
public static readonly RefEq<T> Instance = new RefEq<T>();
public bool Equals(T a, T b)
{
return a == b;
}
public int GetHashCode(T o)
{
return RuntimeHelpers.GetHashCode(o);
}
}
[HarmonyPatch(typeof(GearData), "Cleanup")]
private static class CleanupPatch
{
[HarmonyPrefix]
private static bool Prefix(GearData __instance, out State __state)
{
__state = default(State);
if (!Plugin.CleanupMemoActive)
{
return true;
}
List<UpgradeEquipData> equippedUpgrades = __instance.equippedUpgrades;
List<UpgradeEquipData> equippedSkins = __instance.equippedSkins;
PlayerData instance = PlayerData.Instance;
if (instance == null)
{
return true;
}
if (Memos.TryGetValue(__instance, out var value) && value.Gen == _gen && value.Pd == instance && value.Data == instance.data && value.Ups == equippedUpgrades && (equippedUpgrades == null || _versionRef.Invoke(equippedUpgrades) == value.UpsVer) && value.Skins == equippedSkins && (equippedSkins == null || _versionRef.Invoke(equippedSkins) == value.SkinsVer))
{
if (!Plugin.VerifyActive)
{
Stats.CleanupSkipped++;
return false;
}
__state.ExpectNoop = true;
}
__state.Track = true;
__state.Gen = _gen;
__state.Ups = equippedUpgrades;
__state.UpsVer = ((equippedUpgrades != null) ? _versionRef.Invoke(equippedUpgrades) : 0);
__state.Skins = equippedSkins;
__state.SkinsVer = ((equippedSkins != null) ? _versionRef.Invoke(equippedSkins) : 0);
return true;
}
[HarmonyFinalizer]
private static Exception Finalizer(Exception __exception, GearData __instance, ref State __state)
{
if (!__state.Track)
{
return __exception;
}
Stats.CleanupRun++;
List<UpgradeEquipData> equippedUpgrades = __instance.equippedUpgrades;
List<UpgradeEquipData> equippedSkins = __instance.equippedSkins;
bool flag = __exception == null && equippedUpgrades == __state.Ups && (equippedUpgrades == null || _versionRef.Invoke(equippedUpgrades) == __state.UpsVer) && equippedSkins == __state.Skins && (equippedSkins == null || _versionRef.Invoke(equippedSkins) == __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 = instance.data,
Ups = equippedUpgrades,
UpsVer = __state.UpsVer,
Skins = equippedSkins,
SkinsVer = __state.SkinsVer
});
}
}
return __exception;
}
}
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>
{
FieldKey("PlayerData", "data"),
FieldKey("PlayerData", "_instance"),
FieldKey("UpgradeInfo", "Instances"),
FieldKey("UpgradeInstance", "id")
};
private static readonly HashSet<string> PrepFields = new HashSet<string>
{
FieldKey("PlayerData", "collectedGear"),
FieldKey("PlayerData", "upgradeIDs"),
FieldKey("GearData", "gear"),
FieldKey("UpgradeInstance", "gearID"),
FieldKey("UpgradeInstance", "upgradeIDv2"),
FieldKey("UpgradeInfo", "idV2"),
FieldKey("UpgradeInstance", "timeEquipped"),
FieldKey("Upgrade", "_name"),
FieldKey("Upgrade", "<Priority>k__BackingField")
};
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<GearData, Memo> Memos = new ConditionalWeakTable<GearData, Memo>();
private static FieldRef<List<UpgradeEquipData>, int> _versionRef;
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 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];
private static readonly Dictionary<Upgrade, UpgradeInfo> _firstInfo = new Dictionary<Upgrade, UpgradeInfo>(RefEq<Upgrade>.Instance);
private static readonly HashSet<int> _seenIds = new HashSet<int>();
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; }
internal static int Gen => _gen;
private static string FieldKey(string type, string field)
{
return type + "::" + field;
}
public static void Setup(Harmony harmony)
{
ManualLogSource log = Plugin.Log;
try
{
_versionRef = AccessTools.FieldRefAccess<List<UpgradeEquipData>, int>("_version");
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)));
}
InstrumentPatch.Targets = list;
try
{
harmony.CreateClassProcessor(typeof(InstrumentPatch)).Patch();
InstrumentedMethods = list.Count;
}
catch (Exception ex)
{
log.LogWarning((object)("Cleanup/prepare memo disabled: failed to patch: " + ex.Message));
_gen++;
_prepGen++;
PrepAvailable = false;
return;
}
harmony.CreateClassProcessor(typeof(CleanupPatch)).Patch();
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, typeof(GearData), 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(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++;
}
if (!flag2)
{
Stats.InfoSortUnchanged++;
}
else if (flag && list.Count == count && ReorderIsUnobservable(sortBuffer, list, count))
{
Stats.InfoSortHarmless++;
}
else
{
BumpPrep(site);
}
Array.Clear(sortBuffer, 0, count);
}
}
private static bool ReorderIsUnobservable(UpgradeInfo[] before, List<UpgradeInfo> after, int n)
{
try
{
_firstInfo.Clear();
for (int i = 0; i < n; i++)
{
UpgradeInfo val = before[i];
if (val == null)
{
return false;
}
Upgrade upgrade = val.Upgrade;
if (upgrade == null)
{
return false;
}
if (!_firstInfo.ContainsKey(upgrade))
{
_firstInfo.Add(upgrade, val);
}
}
int num = 0;
for (int j = 0; j < n; j++)
{
UpgradeInfo val2 = after[j];
Upgrade upgrade2 = val2.Upgrade;
if (upgrade2 == null || !_firstInfo.TryGetValue(upgrade2, out var value))
{
return false;
}
if (value != null)
{
if (value != val2)
{
return false;
}
_firstInfo[upgrade2] = null;
num++;
}
}
if (num != _firstInfo.Count)
{
return false;
}
_seenIds.Clear();
for (int k = 0; k < n; k++)
{
List<UpgradeInstance> instances = after[k].Instances;
if (instances == null)
{
continue;
}
for (int l = 0; l < instances.Count; l++)
{
UpgradeInstance val3 = instances[l];
if (val3 == null)
{
return false;
}
if (!_seenIds.Add(val3.InstanceID))
{
return false;
}
}
}
return true;
}
catch
{
return false;
}
finally
{
_firstInfo.Clear();
_seenIds.Clear();
}
}
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);
}
internal static bool IsClean(GearData gearData, PlayerData pd)
{
if (!Plugin.CleanupMemoActive || pd == null)
{
return false;
}
List<UpgradeEquipData> equippedUpgrades = gearData.equippedUpgrades;
List<UpgradeEquipData> equippedSkins = gearData.equippedSkins;
if (Memos.TryGetValue(gearData, out var value) && value.Gen == _gen && value.Pd == pd && value.Data == pd.data && value.Ups == equippedUpgrades && (equippedUpgrades == null || _versionRef.Invoke(equippedUpgrades) == value.UpsVer) && value.Skins == equippedSkins)
{
if (equippedSkins != null)
{
return _versionRef.Invoke(equippedSkins) == value.SkinsVer;
}
return true;
}
return false;
}
internal static void AdvanceUpsVersion(GearData gearData, int version)
{
if (Memos.TryGetValue(gearData, out var value))
{
value.UpsVer = version;
}
}
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 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>> data = instance.data;
if (data == null)
{
return false;
}
if (instance != _ownerPlayerData || data != _ownerData)
{
Map.Clear();
Duplicates.Clear();
Bypass.Clear();
_ownerPlayerData = instance;
_ownerData = data;
Stats.CacheResets++;
}
if (!Map.TryGetValue(id, out var value))
{
return false;
}
if (!IsStillReachable(ref value, data, 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>> data = instance.data;
Map.Clear();
Duplicates.Clear();
Bypass.Clear();
_ownerPlayerData = instance;
_ownerData = data;
LastRebuildIncomplete = false;
Stats.CacheRebuilds++;
if (data == null)
{
return;
}
foreach (KeyValuePair<int, List<UpgradeInfo>> item in data)
{
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 MenuText
{
private static readonly Dictionary<string, Dictionary<string, string>> Names = new Dictionary<string, Dictionary<string, string>>
{
["en"] = new Dictionary<string, string>
{
["perf.cache"] = "Upgrade lookup cache",
["perf.verify"] = "Verify the cache (debug, slow)",
["perf.cleanup"] = "Skip redundant cleanup",
["perf.prepare"] = "Skip redundant prepare",
["perf.sort"] = "Skip sorting a sorted list",
["perf.stacking"] = "Stacking table",
["perf.hotkeys"] = "F6-F11 hotkeys"
},
["ja"] = new Dictionary<string, string>
{
["perf.cache"] = "アップグレード検索のキャッシュ",
["perf.verify"] = "キャッシュの検証(デバッグ・低速)",
["perf.cleanup"] = "不要なクリーンアップを省略",
["perf.prepare"] = "不要な準備処理を省略",
["perf.sort"] = "整列済みリストの並べ替えを省略",
["perf.stacking"] = "重ねがけの順位表",
["perf.hotkeys"] = "F6〜F11 のホットキー"
}
};
private static readonly Dictionary<string, Dictionary<string, string>> Descriptions = new Dictionary<string, Dictionary<string, string>> { ["ja"] = new Dictionary<string, string>
{
["perf.cache"] = "アップグレード検索にキャッシュを使います。ゲーム中は F9 でも切り替えられます。",
["perf.verify"] = "デバッグ用。元の検索結果とキャッシュを毎回比較して不一致を記録します。元の結果を使うため安全ですが低速です。",
["perf.cleanup"] = "何も削除されないことが確実なときに、アップグレードの不要な整理処理を省略します。キャッシュ検証中は元の処理も実行します。F8 でも切り替えられます。",
["perf.prepare"] = "何も削除されないことが確実なときに、アップグレードの破棄チェックを省略します。F7 で並べ替えの省略と一緒に切り替えられます。",
["perf.sort"] = "アップグレードがすでに整列済みのときは並べ替えを省略します。F7 で準備処理の省略と一緒に切り替えられます。",
["perf.stacking"] = "アップグレードの重ねがけ順を装備ごとの表から取得します。安全に省略できる場合だけ使い、準備処理と整理処理の省略が必要です。F6 でも切り替えられます。",
["perf.hotkeys"] = "F6〜F9 で各最適化を切り替え、F10 でベンチマークを実行、F11 で統計を出力・リセットします。既定ではオフです。"
} };
public static object[] Label(string key)
{
return ModsMenuTag.Label(key, (IDictionary<string, Dictionary<string, string>>)Names, (IDictionary<string, Dictionary<string, string>>)Descriptions);
}
}
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;
}
[HarmonyPatch(typeof(PlayerData), "GetUpgradeInstanceFromID", new Type[] { typeof(int) })]
private static class LookupPatch
{
[HarmonyPrefix]
private static bool 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;
}
[HarmonyPostfix]
private static void Postfix(int id, UpgradeInstance __result, ref 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 class RedirectApplyUpgradesPatch
{
internal static IEnumerable<MethodBase> TargetMethods()
{
(Type, string)[] array = new(Type, string)[7]
{
(typeof(GearUtil), "ReapplyUpgrades"),
(typeof(Gun), "Setup"),
(typeof(MycoGear), "Setup"),
(typeof(ScrapperGear), "Setup"),
(typeof(Throwable), "Setup"),
(typeof(Wingsuit), "Setup"),
(typeof(EquipGearInteractable), "EquipGear_ClientRpc")
};
(Type, string)[] array2 = array;
for (int i = 0; i < array2.Length; i++)
{
var (type, method) = array2[i];
foreach (MethodInfo declaredMethod in AccessTools.GetDeclaredMethods(type))
{
if (declaredMethod.Name == method && !declaredMethod.IsAbstract)
{
yield return declaredMethod;
}
}
}
MethodInfo methodInfo = AccessTools.Method(typeof(Menu), "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");
}
}
internal static IEnumerable<CodeInstruction> 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.LogInfo((object)(" redirected " + replaced + " ApplyUpgrades call(s) in " + text));
}
}
}
public struct OpState
{
public long Start;
public long Lookups;
public long Stacking;
public long MoveNext;
public long ApplyTicks;
public long Bumps;
public bool Outermost;
}
[HarmonyPatch(typeof(GearUtil), "ReapplyUpgrades")]
private static class ReapplyPatch
{
[HarmonyPrefix]
private static void Prefix(out OpState __state)
{
__state = BeginOp(ref _reapplyDepth);
}
[HarmonyFinalizer]
private static Exception 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;
}
}
[HarmonyPatch]
private static class StackingTimingPatch
{
[HarmonyTargetMethods]
private static IEnumerable<MethodBase> TargetMethods()
{
return (from m in AccessTools.GetDeclaredMethods(typeof(UpgradeProperty))
where m.Name == "GetStackingModifier"
select m).Cast<MethodBase>();
}
[HarmonyPrefix]
private static void Prefix(out long __state)
{
__state = ((_stackDepth++ == 0) ? Stopwatch.GetTimestamp() : 0);
}
[HarmonyFinalizer]
private static Exception Finalizer(Exception __exception, long __state)
{
if (--_stackDepth == 0)
{
Stats.StackingCalls++;
Stats.StackingTicks += Stopwatch.GetTimestamp() - __state;
}
return __exception;
}
}
[HarmonyPatch(typeof(UpgradeProperty), "GetAppliedCount")]
private static class AppliedTimingPatch
{
[HarmonyPrefix]
private static void Prefix(out long __state)
{
__state = ((_appliedDepth++ == 0) ? Stopwatch.GetTimestamp() : 0);
}
[HarmonyFinalizer]
private static Exception Finalizer(Exception __exception, long __state)
{
if (--_appliedDepth == 0)
{
Stats.AppliedCountCalls++;
Stats.AppliedCountTicks += Stopwatch.GetTimestamp() - __state;
}
return __exception;
}
}
[HarmonyPatch(typeof(EquippedUpgradeEnumerator), "MoveNext")]
private static class MoveNextCountPatch
{
[HarmonyPrefix]
private static void Prefix()
{
Stats.EnumMoveNext++;
}
}
[HarmonyPatch(typeof(GearData), "Cleanup")]
private static class CleanupCountPatch
{
[HarmonyPrefix]
private static void Prefix()
{
Stats.GearDataCleanup++;
}
}
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 detailed)
{
//IL_002c: Unknown result type (might be due to invalid IL or missing references)
//IL_0032: Expected O, but got Unknown
Detailed = detailed;
Apply(harmony, typeof(LookupPatch));
HarmonyMethod val = new HarmonyMethod(AccessTools.Method(typeof(RedirectApplyUpgradesPatch), "Transpiler", (Type[])null, (Type[])null));
foreach (MethodBase item in RedirectApplyUpgradesPatch.TargetMethods())
{
try
{
harmony.Patch(item, (HarmonyMethod)null, (HarmonyMethod)null, val, (HarmonyMethod)null, (HarmonyMethod)null);
Log.LogInfo((object)("Patched " + Describe(item)));
}
catch (Exception ex)
{
Log.LogError((object)("Failed to patch " + Describe(item) + ": " + ex));
}
}
Apply(harmony, typeof(ReapplyPatch));
if (!detailed)
{
Log.LogInfo((object)"Detailed profiling off: per-call hooks (GetStackingModifier, GetAppliedCount, MoveNext, Cleanup counter, lookup timing) not installed");
return;
}
Apply(harmony, typeof(StackingTimingPatch));
Apply(harmony, typeof(AppliedTimingPatch));
Apply(harmony, typeof(MoveNextCountPatch));
Apply(harmony, typeof(CleanupCountPatch));
}
private static void Apply(Harmony harmony, Type patchClass)
{
try
{
harmony.CreateClassProcessor(patchClass).Patch();
foreach (MethodBase patchedMethod in harmony.GetPatchedMethods())
{
if (IsPatchedBy(patchedMethod, patchClass))
{
Log.LogInfo((object)("Patched " + Describe(patchedMethod)));
}
}
}
catch (Exception ex)
{
Log.LogError((object)("Failed to patch " + patchClass.Name + ": " + ex));
}
}
private static bool IsPatchedBy(MethodBase original, Type patchClass)
{
Patches patchInfo = Harmony.GetPatchInfo(original);
return patchInfo?.Prefixes.Concat(patchInfo.Postfixes).Concat(patchInfo.Transpilers).Concat(patchInfo.Finalizers)
.Any((Patch p) => p.PatchMethod?.DeclaringType == patchClass) ?? false;
}
private static string Describe(MethodBase target)
{
return target.DeclaringType?.Name + "." + target.Name + "(" + target.GetParameters().Length + ")";
}
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 "#?";
}
}
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));
}
}
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 "?";
}
}
}
[BepInPlugin("t4t5u0.UpgradeLagFix", "Upgrade Lag Fix", "1.1.2")]
[BepInDependency("doku_macaron.mycopunk.macaroncore", "1.1.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.1.2";
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> _stackTable;
private static ConfigEntry<float> _logThresholdMs;
private static ConfigEntry<bool> _hotkeys;
private static ConfigEntry<bool> _detailedProfiling;
private static ConfigEntry<bool> _sessionFile;
private static ConfigEntry<float> _dumpSeconds;
private static ConfigEntry<bool> _dumpOnEvents;
private static bool _cacheFaulted;
private Harmony _harmony;
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 bool StackTableActive
{
get
{
if (StackingMemo.Available && PrepareMemoActive && CleanupMemoActive)
{
return _stackTable.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) + ", stack-table=" + OnOff(StackTableActive, StackingMemo.Available) + (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_002c: Unknown result type (might be due to invalid IL or missing references)
//IL_0036: Expected O, but got Unknown
//IL_005c: Unknown result type (might be due to invalid IL or missing references)
//IL_0066: Expected O, but got Unknown
//IL_008c: Unknown result type (might be due to invalid IL or missing references)
//IL_0096: Expected O, but got Unknown
//IL_00bc: Unknown result type (might be due to invalid IL or missing references)
//IL_00c6: Expected O, but got Unknown
//IL_00ec: Unknown result type (might be due to invalid IL or missing references)
//IL_00f6: Expected O, but got Unknown
//IL_011c: Unknown result type (might be due to invalid IL or missing references)
//IL_0126: Expected O, but got Unknown
//IL_0190: Unknown result type (might be due to invalid IL or missing references)
//IL_019a: Expected O, but got Unknown
//IL_02fe: Unknown result type (might be due to invalid IL or missing references)
//IL_0308: Expected O, but got Unknown
Log = ((BaseUnityPlugin)this).Logger;
_cacheEnabled = ((BaseUnityPlugin)this).Config.Bind<bool>("Cache", "Enabled", true, new ConfigDescription("Use the InstanceID index for PlayerData.GetUpgradeInstanceFromID. Toggle in game with F9.", (AcceptableValueBase)null, MenuText.Label("perf.cache")));
_verify = ((BaseUnityPlugin)this).Config.Bind<bool>("Cache", "Verify", false, new ConfigDescription("Debug: run the original lookup on every call, compare it with the cache, and log mismatches. The original result is always used, so this is safe but slow.", (AcceptableValueBase)null, MenuText.Label("perf.verify")));
_cleanupMemo = ((BaseUnityPlugin)this).Config.Bind<bool>("Cleanup", "SkipRedundant", true, new ConfigDescription("Skip GearData.Cleanup when it provably cannot remove anything (it runs on every step of EquippedUpgradeEnumerator). With Cache.Verify the original Cleanup still runs and is checked. Toggle in game with F8.", (AcceptableValueBase)null, MenuText.Label("perf.cleanup")));
_prepareMemo = ((BaseUnityPlugin)this).Config.Bind<bool>("Walk", "SkipRedundantPrepare", true, new ConfigDescription("Skip the destroyed-check in GearData.PrepareUpgrades (run at the start of every equipped-upgrade walk) when it provably cannot remove anything. Toggle in game with F7 (together with SkipSortedSort).", (AcceptableValueBase)null, MenuText.Label("perf.prepare")));
_sortSkip = ((BaseUnityPlugin)this).Config.Bind<bool>("Walk", "SkipSortedSort", true, new ConfigDescription("Skip equippedUpgrades.Sort() in PrepareUpgrades when the list is already strictly ascending, where sorting cannot change it. Toggle in game with F7 (together with SkipRedundantPrepare).", (AcceptableValueBase)null, MenuText.Label("perf.sort")));
_stackTable = ((BaseUnityPlugin)this).Config.Bind<bool>("Walk", "StackingTable", true, new ConfigDescription("Answer GetStackingModifier / GetAppliedCount (how many copies of an upgrade come before this one) from a per-gear table instead of walking the equipped list on every call. Used only while the walk would provably change nothing; needs SkipRedundantPrepare and Cleanup.SkipRedundant. Toggle in game with F6.", (AcceptableValueBase)null, MenuText.Label("perf.stacking")));
_logThresholdMs = ((BaseUnityPlugin)this).Config.Bind<float>("Profiling", "LogThresholdMs", 1f, "Log ApplyUpgrades / ReapplyUpgrades calls that take at least this many milliseconds.");
_detailedProfiling = ((BaseUnityPlugin)this).Config.Bind<bool>("Profiling", "Detailed", false, "Install per-call profiling hooks (GetStackingModifier / GetAppliedCount timing, MoveNext and Cleanup counts, lookup timing). They add overhead to the hot path, so leave this off except when investigating. Needs a game restart.");
_hotkeys = ((BaseUnityPlugin)this).Config.Bind<bool>("Profiling", "Hotkeys", false, new ConfigDescription("F6: toggle stacking table, F7: toggle prepare+sort skip, F8: toggle cleanup skip, F9: toggle cache (all print and reset stats). F10: run benchmark. F11: print and reset stats. Off by default: switch these in the MODS tab of the options window instead.", (AcceptableValueBase)null, MenuText.Label("perf.hotkeys")));
_sessionFile = ((BaseUnityPlugin)this).Config.Bind<bool>("Profiling", "SessionLogFile", true, "Also write this mod's log lines to BepInEx/UpgradeLagFix/logs/perf-<start time>.log, one file per launch (LogOutput.log is overwritten on every launch). Needs a game restart.");
_dumpSeconds = ((BaseUnityPlugin)this).Config.Bind<float>("Profiling", "StatsDumpSeconds", 120f, "Print and reset the stats (like F11) every this many seconds of real time. 0 turns it off. Nothing is printed when no ApplyUpgrades ran since the last dump. Needs a game restart.");
_dumpOnEvents = ((BaseUnityPlugin)this).Config.Bind<bool>("Profiling", "StatsDumpOnEvents", true, "Also print and reset the stats on scene changes, when an Ouroboros room is completed, and on quit. Needs a game restart.");
_cacheEnabled.SettingChanged += delegate
{
InstanceCache.Clear();
};
ConfigEntry<bool>[] array = new ConfigEntry<bool>[6] { _cacheEnabled, _verify, _cleanupMemo, _prepareMemo, _sortSkip, _stackTable };
for (int num = 0; num < array.Length; num++)
{
array[num].SettingChanged += delegate
{
OnModeChanged();
};
}
if (_hotkeys.Value)
{
Hotkeys.Register((BaseUnityPlugin)(object)this, (Key)99, "toggle stacking table");
Hotkeys.Register((BaseUnityPlugin)(object)this, (Key)100, "toggle prepare+sort skip");
Hotkeys.Register((BaseUnityPlugin)(object)this, (Key)101, "toggle cleanup skip");
Hotkeys.Register((BaseUnityPlugin)(object)this, (Key)102, "toggle cache");
Hotkeys.Register((BaseUnityPlugin)(object)this, (Key)103, "benchmark");
Hotkeys.Register((BaseUnityPlugin)(object)this, (Key)104, "print stats");
}
Hotkeys.ReportConflicts((BaseUnityPlugin)(object)this);
_harmony = new Harmony("t4t5u0.UpgradeLagFix");
ModsMenu.Install(_harmony, (BaseUnityPlugin)(object)this);
Patches.ApplyAll(_harmony, _detailedProfiling.Value);
CleanupMemo.Setup(_harmony);
PrepareMemo.Setup(_harmony);
StackingMemo.Setup(_harmony);
SessionLog.Setup(_harmony, Log, _sessionFile.Value, _dumpSeconds.Value, _dumpOnEvents.Value);
Log.LogInfo((object)("Upgrade Lag Fix 1.1.2 loaded. " + ModeString + ", detailed profiling=" + _detailedProfiling.Value + ((SessionLog.FilePath != null) ? (", session log: " + SessionLog.FilePath) : "")));
}
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 OnApplicationQuit()
{
SessionLog.OnQuit();
}
private void OnDestroy()
{
SessionLog.OnQuit();
Harmony harmony = _harmony;
if (harmony != null)
{
harmony.UnpatchSelf();
}
}
private static void OnModeChanged()
{
try
{
Log.LogInfo((object)Stats.Summary("stats before toggle"));
Stats.Reset();
CleanupMemo.Clear();
Log.LogInfo((object)("Mode is now " + ModeString));
}
catch (Exception ex)
{
Log.LogWarning((object)("Mode change handling failed: " + ex.Message));
}
}
private void Update()
{
SessionLog.Tick();
if (!_hotkeys.Value)
{
return;
}
Keyboard current;
try
{
current = Keyboard.current;
}
catch
{
return;
}
if (current != null)
{
if (((ButtonControl)current.f6Key).wasPressedThisFrame)
{
_stackTable.Value = !_stackTable.Value;
}
else if (((ButtonControl)current.f7Key).wasPressedThisFrame)
{
bool value = !_prepareMemo.Value;
_prepareMemo.Value = value;
_sortSkip.Value = value;
}
else if (((ButtonControl)current.f8Key).wasPressedThisFrame)
{
_cleanupMemo.Value = !_cleanupMemo.Value;
}
else if (((ButtonControl)current.f9Key).wasPressedThisFrame)
{
_cacheEnabled.Value = !_cacheEnabled.Value;
}
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;
}
[HarmonyPatch(typeof(GearData), "PrepareUpgrades")]
private static class SortPatch
{
[HarmonyTranspiler]
private static IEnumerable<CodeInstruction> 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;
}
}
[HarmonyPatch(typeof(GearData), "PrepareUpgrades")]
private static class PreparePatch
{
[HarmonyPrefix]
private static bool Prefix(GearData __instance, bool sort, out State __state)
{
__state = default(State);
if (!Plugin.PrepareMemoActive)
{
return true;
}
List<UpgradeEquipData> equippedUpgrades = __instance.equippedUpgrades;
if (equippedUpgrades == 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 == instance.data && value.List == equippedUpgrades && _versionRef.Invoke(equippedUpgrades) == value.VersionAfter)
{
if (!Plugin.VerifyActive)
{
if (sort)
{
SortOrSkip(equippedUpgrades);
}
value.VersionAfter = _versionRef.Invoke(equippedUpgrades);
Stats.PrepareSkipped++;
return false;
}
__state.ExpectNoop = true;
}
__state.Track = true;
__state.Gen = CleanupMemo.PrepGen;
__state.List = equippedUpgrades;
__state.CountBefore = equippedUpgrades.Count;
return true;
}
[HarmonyFinalizer]
private static Exception Finalizer(Exception __exception, GearData __instance, ref State __state)
{
if (!__state.Track)
{
return __exception;
}
Stats.PrepareRun++;
List<UpgradeEquipData> equippedUpgrades = __instance.equippedUpgrades;
bool flag = __exception == null && equippedUpgrades == __state.List && equippedUpgrades.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 = instance.data,
List = equippedUpgrades,
VersionAfter = _versionRef.Invoke(equippedUpgrades)
});
}
}
return __exception;
}
}
private static readonly ConditionalWeakTable<GearData, Memo> Memos = new ConditionalWeakTable<GearData, Memo>();
private static FieldRef<List<UpgradeEquipData>, int> _versionRef;
private static readonly MethodInfo ListSort = AccessTools.Method(typeof(List<UpgradeEquipData>), "Sort", Type.EmptyTypes, (Type[])null);
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)
{
ManualLogSource log = Plugin.Log;
try
{
_versionRef = AccessTools.FieldRefAccess<List<UpgradeEquipData>, int>("_version");
harmony.CreateClassProcessor(typeof(SortPatch)).Patch();
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.CreateClassProcessor(typeof(PreparePatch)).Patch();
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));
}
}
internal static bool IsClean(GearData gearData, List<UpgradeEquipData> ups, PlayerData pd)
{
if (!Plugin.PrepareMemoActive || ups == null || pd == null)
{
return false;
}
if (Memos.TryGetValue(gearData, out var value) && value.Gen == CleanupMemo.PrepGen && value.Pd == pd && value.Data == pd.data && value.List == ups)
{
return _versionRef.Invoke(ups) == value.VersionAfter;
}
return false;
}
internal static void AdvanceVersion(GearData gearData, int version)
{
if (Memos.TryGetValue(gearData, out var value))
{
value.VersionAfter = version;
}
}
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;
}
}
}
}
internal static class SessionLog
{
[HarmonyPatch(typeof(OuroMission), "OnRoomCompleted")]
private static class RoomCompletedPatch
{
[HarmonyPostfix]
private static void Postfix()
{
Request("ouroboros room completed");
}
}
private static SessionLogFile _file;
private static float _intervalSeconds;
private static bool _onEvents;
private static float _nextDump;
private static string _pendingReason;
public static string FilePath
{
get
{
SessionLogFile file = _file;
if (file == null)
{
return null;
}
return file.FilePath;
}
}
public static void Setup(Harmony harmony, ManualLogSource log, bool toFile, float intervalSeconds, bool onEvents)
{
_intervalSeconds = intervalSeconds;
_onEvents = onEvents;
_nextDump = ((intervalSeconds > 0f) ? (Time.realtimeSinceStartup + intervalSeconds) : float.MaxValue);
if (toFile)
{
_file = SessionLogFile.Open(log, "UpgradeLagFix", "perf");
}
if (onEvents)
{
SceneManager.activeSceneChanged += ActiveSceneChanged;
try
{
harmony.CreateClassProcessor(typeof(RoomCompletedPatch)).Patch();
}
catch (Exception ex)
{
log.LogWarning((object)("Could not hook OuroMission.OnRoomCompleted: " + ex.Message));
}
}
}
private static void ActiveSceneChanged(Scene from, Scene to)
{
Request("scene " + ((Scene)(ref from)).name + " -> " + ((Scene)(ref to)).name);
}
private static void Request(string reason)
{
if (_onEvents)
{
_pendingReason = ((_pendingReason == null) ? reason : (_pendingReason + "; " + reason));
}
}
public static void Tick()
{
float realtimeSinceStartup = Time.realtimeSinceStartup;
string title;
if (_pendingReason != null)
{
title = "auto stats: " + _pendingReason;
_pendingReason = null;
}
else
{
if (!(realtimeSinceStartup >= _nextDump))
{
return;
}
title = "auto stats: every " + _intervalSeconds.ToString("0") + " s";
}
Dump(title);
if (_intervalSeconds > 0f)
{
_nextDump = realtimeSinceStartup + _intervalSeconds;
}
}
public static void OnQuit()
{
SceneManager.activeSceneChanged -= ActiveSceneChanged;
Dump("auto stats: quit");
SessionLogFile file = _file;
if (file != null)
{
file.Dispose();
}
_file = null;
}
private static void Dump(string title)
{
if (Stats.HasApplyActivity)
{
Plugin.Log.LogInfo((object)Stats.Summary(title));
Stats.Reset();
}
}
}
internal static class StackingMemo
{
private sealed class RefEq<T> : IEqualityComparer<T> where T : class
{
public static readonly RefEq<T> Instance = new RefEq<T>();
public bool Equals(T a, T b)
{
return a == b;
}
public int GetHashCode(T o)
{
return RuntimeHelpers.GetHashCode(o);
}
}
private sealed class Table
{
public List<UpgradeEquipData> List;
public int Version;
public int Gen;
public int PrepGen;
public PlayerData Pd;
public object Data;
public bool Built;
public bool Failed;
public int FailGen;
public int FailPrepGen;
public readonly Dictionary<UpgradeInstance, int> FirstRank = new Dictionary<UpgradeInstance, int>(RefEq<UpgradeInstance>.Instance);
public readonly Dictionary<Upgrade, int> Total = new Dictionary<Upgrade, int>(RefEq<Upgrade>.Instance);
public bool Matches(List<UpgradeEquipData> ups, int ver, PlayerData pd, object data)
{
if (List == ups && Version == ver && Gen == CleanupMemo.Gen && PrepGen == CleanupMemo.PrepGen && Pd == pd)
{
return Data == data;
}
return false;
}
public void SetKey(List<UpgradeEquipData> ups, int ver, int gen, int prepGen, PlayerData pd, object data)
{
List = ups;
Version = ver;
Gen = gen;
PrepGen = prepGen;
Pd = pd;
Data = data;
Built = false;
}
public void Invalidate()
{
List = null;
Built = false;
}
}
public struct State
{
public bool Track;
public GearData Gd;
public List<UpgradeEquipData> Ups;
public int VerBefore;
public int CountBefore;
public int Gen;
public int PrepGen;
public bool TableUsable;
public bool HasPrediction;
public int PredCount;
public bool PredFound;
}
[HarmonyPatch(/*Could not decode attribute arguments.*/)]
private static class StackingPatch
{
[HarmonyPrefix]
private static bool Prefix(IUpgradable gear, UpgradeInstance instance, float coefficient, bool mustBeEquipped, ref int prevCount, ref float __result, out State __state)
{
if (!Begin(gear, instance, out __state, out var count, out var found))
{
return true;
}
if (Plugin.VerifyActive)
{
__state.HasPrediction = true;
__state.PredCount = count;
__state.PredFound = found;
return true;
}
Commit(ref __state);
if (mustBeEquipped && !found)
{
prevCount = 0;
__result = 1f;
}
else
{
prevCount = count;
__result = (float)count * coefficient + 1f;
}
return false;
}
[HarmonyFinalizer]
private static Exception Finalizer(Exception __exception, UpgradeInstance instance, float coefficient, bool mustBeEquipped, int prevCount, ref float __result, ref State __state)
{
if (!__state.Track)
{
return __exception;
}
if (__state.HasPrediction && __exception == null)
{
int num = ((!mustBeEquipped || __state.PredFound) ? __state.PredCount : 0);
float value = ((mustBeEquipped && !__state.PredFound) ? 1f : ((float)__state.PredCount * coefficient + 1f));
Check(num == prevCount && BitConverter.SingleToInt32Bits(value) == BitConverter.SingleToInt32Bits(__result), "GetStackingModifier", instance, "table count " + num + " / " + value.ToString("R") + ", original " + prevCount + " / " + __result.ToString("R"));
}
AfterOriginal(ref __state, __exception);
return __exception;
}
}
[HarmonyPatch(typeof(UpgradeProperty), "GetAppliedCount", new Type[]
{
typeof(IUpgradable),
typeof(UpgradeInstance),
typeof(bool)
})]
private static class AppliedPatch
{
[HarmonyPrefix]
private static bool Prefix(IUpgradable gear, UpgradeInstance instance, bool mustBeEquipped, ref int __result, out State __state)
{
if (!Begin(gear, instance, out __state, out var count, out var found))
{
return true;
}
if (Plugin.VerifyActive)
{
__state.HasPrediction = true;
__state.PredCount = count;
__state.PredFound = found;
return true;
}
Commit(ref __state);
__result = ((!mustBeEquipped || found) ? count : 0);
return false;
}
[HarmonyFinalizer]
private static Exception Finalizer(Exception __exception, UpgradeInstance instance, bool mustBeEquipped, int __result, ref State __state)
{
if (!__state.Track)
{
return __exception;
}
if (__state.HasPrediction && __exception == null)
{
int num = ((!mustBeEquipped || __state.PredFound) ? __state.PredCount : 0);
Check(num == __result, "GetAppliedCount", instance, "table " + num + ", original " + __result);
}
AfterOriginal(ref __state, __exception);
return __exception;
}
}
private static readonly ConditionalWeakTable<GearData, Table> Tables = new ConditionalWeakTable<GearData, Table>();
private static FieldRef<List<UpgradeEquipData>, int> _versionRef;
private static int _mismatchLogs;
public static bool Available { get; private set; }
public static void Setup(Harmony harmony)
{
ManualLogSource log = Plugin.Log;
try
{
if (!CleanupMemo.Available || !PrepareMemo.PrepareAvailable)
{
log.LogWarning((object)"Stacking table disabled: needs both the cleanup and the prepare memo");
return;
}
_versionRef = AccessTools.FieldRefAccess<List<UpgradeEquipData>, int>("_version");
harmony.CreateClassProcessor(typeof(StackingPatch)).Patch();
harmony.CreateClassProcessor(typeof(AppliedPatch)).Patch();
Available = true;
log.LogInfo((object)"Stacking table ready");
}
catch (Exception ex)
{
Available = false;
log.LogError((object)("Stacking table setup failed, feature disabled: " + ex));
}
}
private static IUpgradable ResolveWalkTarget(IUpgradable gear)
{
IUpgradable val = gear.Prefab ?? gear;
MonoBehaviour val2 = (MonoBehaviour)(object)((val is MonoBehaviour) ? val : null);
if (val2 != null)
{
GameObject gameObject = ((Component)val2).gameObject;
Character[] characters = Global.Instance.Characters;
for (int i = 0; i < characters.Length; i++)
{
if (gameObject == characters[i].UtilityItem)
{
val = (IUpgradable)(object)characters[i];
}
}
}
else if ((object)val == Global.Instance)
{
val = (IUpgradable)(object)Player.LocalPlayer.Character;
}
return val.Prefab ?? val;
}
private static bool Begin(IUpgradable gear, UpgradeInstance instance, out State st, out int count, out bool found)
{
st = default(State);
count = 0;
found = false;
if (!Plugin.StackTableActive || gear == null || instance == null)
{
return false;
}
try
{
GearData gearData = PlayerData.GetGearData(ResolveWalkTarget(gear));
if (gearData == null)
{
return false;
}
List<UpgradeEquipData> equippedUpgrades = gearData.equippedUpgrades;
PlayerData instance2 = PlayerData.Instance;
if (equippedUpgrades == null || instance2 == null)
{
return false;
}
int num = _versionRef.Invoke(equippedUpgrades);
st.Track = true;
st.Gd = gearData;
st.Ups = equippedUpgrades;
st.VerBefore = num;
st.CountBefore = equippedUpgrades.Count;
st.Gen = CleanupMemo.Gen;
st.PrepGen = CleanupMemo.PrepGen;
if (!IsClean(gearData, equippedUpgrades, instance2))
{
return false;
}
Dictionary<int, List<UpgradeInfo>> data = instance2.data;
Table value = Tables.GetValue(gearData, (GearData _) => new Table());
if (!value.Matches(equippedUpgrades, num, instance2, data))
{
return false;
}
if (!value.Built && !Build(value, equippedUpgrades))
{
return false;
}
st.TableUsable = true;
Upgrade upgrade = instance.Upgrade;
if ((Object)(object)upgrade == (Object)null)
{
return false;
}
if (value.FirstRank.TryGetValue(instance, out count))
{
found = true;
}
else
{
value.Total.TryGetValue(upgrade, out count);
found = false;
}
return true;
}
catch
{
st = default(State);
count = 0;
found = false;
return false;
}
}
private static bool IsClean(GearData gd, List<UpgradeEquipData> ups, PlayerData pd)
{
if (PrepareMemo.IsClean(gd, ups, pd))
{
return CleanupMemo.IsClean(gd, pd);
}
return false;
}
private static bool Build(Table t, List<UpgradeEquipData> ups)
{
//IL_0043: Unknown result type (might be due to invalid IL or missing references)
if (t.Failed && t.FailGen == CleanupMemo.Gen && t.FailPrepGen == CleanupMemo.PrepGen)
{
return false;
}
t.FirstRank.Clear();
t.Total.Clear();
for (int i = 0; i < ups.Count; i++)
{
UpgradeInstance upgradeInstanceFromID = PlayerData.GetUpgradeInstanceFromID(ups[i].upgradeID);
Upgrade val = ((upgradeInstanceFromID != null) ? upgradeInstanceFromID.Upgrade : null);
if (upgradeInstanceFromID == null || (Object)(object)val == (Object)null)
{
t.FirstRank.Clear();
t.Total.Clear();
t.Failed = true;
t.FailGen = CleanupMemo.Gen;
t.FailPrepGen = CleanupMemo.PrepGen;
Stats.StackBuildFailed++;
return false;
}
t.Total.TryGetValue(val, out var value);
if (!t.FirstRank.ContainsKey(upgradeInstanceFromID))
{
t.FirstRank.Add(upgradeInstanceFromID, value);
}
t.Total[val] = value + 1;
}
t.Built = true;
t.Failed = false;
Stats.StackBuilds++;
return true;
}
private static void Commit(ref State st)
{
List<UpgradeEquipData> ups = st.Ups;
int version = ++_versionRef.Invoke(ups);
PrepareMemo.AdvanceVersion(st.Gd, version);
CleanupMemo.AdvanceUpsVersion(st.Gd, version);
if (Tables.TryGetValue(st.Gd, out var value))
{
value.Version = version;
}
Stats.StackFast++;
st.Track = false;
}
private static void AfterOriginal(ref State st, Exception exception)
{
Stats.StackRun++;
try
{
GearData gd = st.Gd;
Table value = Tables.GetValue(gd, (GearData _) => new Table());
List<UpgradeEquipData> equippedUpgrades = gd.equippedUpgrades;
PlayerData instance = PlayerData.Instance;
if (exception != null || equippedUpgrades == null || instance == null || !IsClean(gd, equippedUpgrades, instance))
{
value.Invalidate();
return;
}
int num = _versionRef.Invoke(equippedUpgrades);
if (st.TableUsable && value.Built && equippedUpgrades == st.Ups && num == st.VerBefore + 1 && equippedUpgrades.Count == st.CountBefore && st.Gen == CleanupMemo.Gen && st.PrepGen == CleanupMemo.PrepGen && value.Pd == instance && value.Data == instance.data)
{
value.Version = num;
}
else
{
value.SetKey(equippedUpgrades, num, CleanupMemo.Gen, CleanupMemo.PrepGen, instance, instance.data);
}
}
catch
{
if (st.Gd != null && Tables.TryGetValue(st.Gd, out var value2))
{
value2.Invalidate();
}
}
}
private static void Check(bool ok, string what, UpgradeInstance instance, string detail)
{
Stats.StackVerifyChecks++;
if (ok)
{
return;
}
Stats.StackVerifyMismatches++;
if (_mismatchLogs++ >= 20)
{
return;
}
string text;
try
{
object obj;
if (instance == null)
{
obj = null;
}
else
{
Upgrade upgrade = instance.Upgrade;
obj = ((upgrade != null) ? upgrade.APIName : null);
}
if (obj == null)
{
obj = "?";
}
text = (string)obj;
}
catch
{
text = "?";
}
Plugin.Log.LogError((object)("STACKING VERIFY MISMATCH in " + what + " for " + text + ": " + detail));
}
}
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 InfoSortHarmless;
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 StackFast;
public static long StackRun;
public static long StackBuilds;
public static long StackBuildFailed;
public static long StackVerifyChecks;
public static long StackVerifyMismatches;
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 HasApplyActivity
{
get
{
if (ApplyCalls <= 0 && ReapplyCalls <= 0 && StackFast <= 0)
{
return StackRun > 0;
}
return true;
}
}
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 = (InfoSortHarmless = (PrepareSkipped = (PrepareRun = (PrepareVerifyChecks = (PrepareVerifyMismatches = 0L))))));
SortSkipped = (SortRun = (SortVerifyChecks = (SortVerifyMismatches = 0L)));
StackFast = (StackRun = (StackBuilds = (StackBuildFailed = (StackVerifyChecks = (StackVerifyMismatches = 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}, reordered but unobservable: {6})", PrepareSkipped, PrepareRun, SortSkipped, SortRun, PrepGenBumps, InfoSortUnchanged, InfoSortHarmless).AppendLine();
stringBuilder.AppendFormat(" stacking table : answered {0}, original ran {1}, builds {2} (failed {3})", StackFast, StackRun, StackBuilds, StackBuildFailed).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 || StackVerifyChecks > 0)
{
stringBuilder.AppendFormat(" verify: lookup {0} checks, {1} mismatches | cleanup {2}/{3} | prepare {4}/{5} | sort {6}/{7} | stacking {8}/{9} (checks/mismatches)", VerifyChecks, VerifyMismatches, CleanupVerifyChecks, CleanupVerifyMismatches, PrepareVerifyChecks, PrepareVerifyMismatches, SortVerifyChecks, SortVerifyMismatches, StackVerifyChecks, StackVerifyMismatches).AppendLine();
}
return stringBuilder.ToString().TrimEnd();
}
}
}
namespace System.Runtime.CompilerServices
{
[AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)]
internal sealed class IgnoresAccessChecksToAttribute : Attribute
{
public IgnoresAccessChecksToAttribute(string assemblyName)
{
}
}
}