Decompiled source of RoleShuffleUpgradeKeeper v1.0.1

BepInEx/plugins/RoleShuffleUpgradeKeeper/RoleShufflePreserveUpgrades.dll

Decompiled 4 hours ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using UnityEngine;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: AssemblyVersion("0.0.0.0")]
namespace RoleShufflePreserveUpgrades;

[BepInDependency(/*Could not decode attribute arguments.*/)]
[BepInPlugin("chatgpt.roleshuffle.preserveupgrades", "RoleShuffle Preserve Upgrades", "1.5.1")]
public sealed class Plugin : BaseUnityPlugin
{
	public const string PluginGuid = "chatgpt.roleshuffle.preserveupgrades";

	public const string PluginName = "RoleShuffle Preserve Upgrades";

	public const string PluginVersion = "1.5.1";

	internal static ManualLogSource Log;

	internal static ConfigEntry<bool> Enabled;

	internal static ConfigEntry<bool> Verbose;

	internal static ConfigEntry<bool> AutoSkipPrivateHostPassword;

	internal static ConfigEntry<bool> AutoPickBestRegionPrivateHost;

	private Harmony _harmony;

	private DateTime _nextTracePollUtc = DateTime.MinValue;

	private static readonly Dictionary<string, Dictionary<string, int>> _lastTraceLevels = new Dictionary<string, Dictionary<string, int>>(StringComparer.Ordinal);

	private void Awake()
	{
		//IL_0096: Unknown result type (might be due to invalid IL or missing references)
		//IL_00a0: Expected O, but got Unknown
		Log = ((BaseUnityPlugin)this).Logger;
		Enabled = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "Enabled", true, "Preserve vanilla/shop upgrades while RoleShuffle applies temporary role upgrades.");
		Verbose = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "VerboseLogging", true, "Log captured/restored upgrade levels for troubleshooting.");
		AutoSkipPrivateHostPassword = ((BaseUnityPlugin)this).Config.Bind<bool>("Lobby Convenience", "Auto Skip Private Host Password", true, "Automatically confirms the password page only when creating a private lobby. It does not bypass or auto-submit passwords when joining somebody else's protected lobby.");
		AutoPickBestRegionPrivateHost = ((BaseUnityPlugin)this).Config.Bind<bool>("Lobby Convenience", "Auto Pick Best Region For Private Host", true, "Automatically chooses the game's Best Region entry when the region page appears during private-lobby creation. Manual region pages opened outside that host flow are left untouched.");
		try
		{
			RoleShuffleBridge.Initialize();
			_harmony = new Harmony("chatgpt.roleshuffle.preserveupgrades");
			PatchLifecycle(_harmony);
			PatchSetLevels(_harmony);
			LobbyConveniencePatches.Install(_harmony);
			((BaseUnityPlugin)this).Logger.LogInfo((object)"RoleShuffle Preserve Upgrades 1.5.1 loaded. Purchased upgrades are preserved as minimums over RoleShuffle role stats.");
		}
		catch (Exception ex)
		{
			((BaseUnityPlugin)this).Logger.LogError((object)("Failed to initialize compatibility patch: " + ex));
		}
	}

	private static void PatchLifecycle(Harmony harmony)
	{
		//IL_0076: Unknown result type (might be due to invalid IL or missing references)
		//IL_0084: Expected O, but got Unknown
		//IL_0097: Unknown result type (might be due to invalid IL or missing references)
		//IL_00ac: Unknown result type (might be due to invalid IL or missing references)
		//IL_00b9: Expected O, but got Unknown
		//IL_00b9: Expected O, but got Unknown
		//IL_00d5: Unknown result type (might be due to invalid IL or missing references)
		//IL_00ea: Unknown result type (might be due to invalid IL or missing references)
		//IL_00f7: Expected O, but got Unknown
		//IL_00f7: Expected O, but got Unknown
		Type stageRoleControllerType = RoleShuffleBridge.StageRoleControllerType;
		MethodInfo methodInfo = AccessTools.Method(stageRoleControllerType, "PrepareStage", (Type[])null, (Type[])null);
		MethodInfo methodInfo2 = AccessTools.Method(stageRoleControllerType, "StageEndingInternal", (Type[])null, (Type[])null);
		MethodInfo methodInfo3 = AccessTools.Method(stageRoleControllerType, "Shutdown", (Type[])null, (Type[])null);
		if (methodInfo == null)
		{
			throw new MissingMethodException(stageRoleControllerType.FullName, "PrepareStage");
		}
		if (methodInfo2 == null)
		{
			throw new MissingMethodException(stageRoleControllerType.FullName, "StageEndingInternal");
		}
		harmony.Patch((MethodBase)methodInfo, new HarmonyMethod(typeof(StageLifecyclePatches), "PrepareStagePrefix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
		harmony.Patch((MethodBase)methodInfo2, new HarmonyMethod(typeof(StageLifecyclePatches), "StageEndingPrefix", (Type[])null), new HarmonyMethod(typeof(StageLifecyclePatches), "StageEndingPostfix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
		if (methodInfo3 != null)
		{
			harmony.Patch((MethodBase)methodInfo3, new HarmonyMethod(typeof(StageLifecyclePatches), "ShutdownPrefix", (Type[])null), new HarmonyMethod(typeof(StageLifecyclePatches), "ShutdownPostfix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
		}
	}

	private static void PatchSetLevels(Harmony harmony)
	{
		//IL_0083: Unknown result type (might be due to invalid IL or missing references)
		//IL_0098: Unknown result type (might be due to invalid IL or missing references)
		//IL_00a5: Expected O, but got Unknown
		//IL_00a5: Expected O, but got Unknown
		//IL_011d: Unknown result type (might be due to invalid IL or missing references)
		//IL_0132: Unknown result type (might be due to invalid IL or missing references)
		//IL_013f: Expected O, but got Unknown
		//IL_013f: Expected O, but got Unknown
		MethodInfo[] array = (from m in RoleShuffleBridge.UpgradeServiceType.GetMethods(BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic)
			where m.Name == "SetLevels"
			select m).ToArray();
		if (array.Length == 0)
		{
			throw new MissingMethodException(RoleShuffleBridge.UpgradeServiceType.FullName, "SetLevels");
		}
		MethodInfo[] array2 = array;
		foreach (MethodInfo methodInfo in array2)
		{
			LoggerStatic("Patching " + DescribeMethod(methodInfo));
			harmony.Patch((MethodBase)methodInfo, new HarmonyMethod(typeof(UpgradePatches), "SetLevelsPrefix", (Type[])null), new HarmonyMethod(typeof(UpgradePatches), "SetLevelsPostfix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
		}
		MethodInfo[] array3 = (from m in RoleShuffleBridge.UpgradeServiceType.GetMethods(BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic)
			where m.Name == "EnsureAtLeastLevels"
			select m).ToArray();
		MethodInfo[] array4 = array3;
		foreach (MethodInfo methodInfo2 in array4)
		{
			LoggerStatic("Observing " + DescribeMethod(methodInfo2));
			harmony.Patch((MethodBase)methodInfo2, new HarmonyMethod(typeof(UpgradePatches), "EnsureAtLeastLevelsPrefix", (Type[])null), new HarmonyMethod(typeof(UpgradePatches), "EnsureAtLeastLevelsPostfix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
		}
	}

	internal static string DescribeMethod(MethodInfo method)
	{
		string text = string.Join(", ", (from p in method.GetParameters()
			select p.ParameterType.FullName + " " + p.Name).ToArray());
		return method.DeclaringType.FullName + "." + method.Name + "(" + text + ")";
	}

	internal static void LoggerStatic(string message)
	{
		if (Log != null)
		{
			Log.LogInfo((object)("[PreserveUpgrades] " + message));
		}
	}

	internal static void ClearTraceCache()
	{
		_lastTraceLevels.Clear();
	}

	private void Update()
	{
	}

	private void OnDestroy()
	{
		try
		{
			if (_harmony != null)
			{
				_harmony.UnpatchSelf();
			}
		}
		catch
		{
		}
	}
}
internal static class LobbyConveniencePatches
{
	private static bool _privateHostPasswordFlowArmed;

	private static float _privateHostPasswordFlowArmedAt = -1000f;

	private static bool _privateHostPasswordConfirmed;

	private static bool _privatePasswordPatchLogged;

	private static bool _privateHostRegionFlowArmed;

	private static float _privateHostRegionFlowArmedAt = -1000f;

	private static bool _privateHostRegionConfirmed;

	private static bool _privateRegionPatchLogged;

	internal static void Install(Harmony harmony)
	{
		if (harmony != null)
		{
			InstallPrivateHostPasswordSkip(harmony);
			InstallPrivateHostBestRegionSkip(harmony);
		}
	}

	private static void InstallPrivateHostPasswordSkip(Harmony harmony)
	{
		//IL_00fd: Unknown result type (might be due to invalid IL or missing references)
		//IL_010b: Expected O, but got Unknown
		//IL_0110: Unknown result type (might be due to invalid IL or missing references)
		//IL_011e: Expected O, but got Unknown
		try
		{
			Type type = AccessTools.TypeByName("MenuManager");
			Type type2 = AccessTools.TypeByName("MenuPagePassword");
			if (type == null || type2 == null)
			{
				WarnOncePassword("Lobby QoL password skip unavailable: MenuManager/MenuPagePassword type not found.");
				return;
			}
			MethodInfo methodInfo = (from m in type.GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)
				where m.Name == "PagePopUpTwoOptions"
				orderby m.GetParameters().Count((ParameterInfo p) => p.ParameterType == typeof(string)) descending
				select m).FirstOrDefault();
			MethodInfo methodInfo2 = AccessTools.Method(type2, "Update", (Type[])null, (Type[])null);
			MethodInfo method = typeof(LobbyConveniencePatches).GetMethod("PrivateHostPopupPrefix", BindingFlags.Static | BindingFlags.NonPublic);
			MethodInfo method2 = typeof(LobbyConveniencePatches).GetMethod("PrivateHostPasswordUpdatePrefix", BindingFlags.Static | BindingFlags.NonPublic);
			if (methodInfo == null || methodInfo2 == null || method == null || method2 == null)
			{
				WarnOncePassword("Lobby QoL password skip unavailable: required menu method not found.");
				return;
			}
			harmony.Patch((MethodBase)methodInfo, new HarmonyMethod(method), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
			harmony.Patch((MethodBase)methodInfo2, new HarmonyMethod(method2), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
			Plugin.Log.LogInfo((object)"[PreserveUpgrades] Lobby QoL private-host password skip patch active.");
		}
		catch (Exception ex)
		{
			WarnOncePassword("Lobby QoL password skip install failed: " + Unwrap(ex));
		}
	}

	private static void PrivateHostPopupPrefix(object[] __args)
	{
		try
		{
			bool flag = Plugin.AutoSkipPrivateHostPassword != null && Plugin.AutoSkipPrivateHostPassword.Value;
			bool flag2 = Plugin.AutoPickBestRegionPrivateHost != null && Plugin.AutoPickBestRegionPrivateHost.Value;
			if ((!flag && !flag2) || __args == null)
			{
				return;
			}
			string[] array = (from string x in __args.Where((object a) => a is string)
				where !string.IsNullOrEmpty(x)
				select x).ToArray();
			if (array.Length == 0)
			{
				return;
			}
			string text = string.Join(" ", array).ToLowerInvariant();
			bool flag3 = text.Contains("random players") || text.Contains("mean people") || text.Contains("public game") || text.Contains("matchmaking");
			if ((!text.Contains("private game") && !text.Contains("best computer") && !text.Contains("are you this friend") && (!text.Contains("host") || !text.Contains("friend"))) || flag3)
			{
				return;
			}
			if (flag)
			{
				_privateHostPasswordFlowArmed = true;
				_privateHostPasswordFlowArmedAt = Time.realtimeSinceStartup;
				_privateHostPasswordConfirmed = false;
				if (Plugin.Verbose.Value)
				{
					Plugin.Log.LogInfo((object)"[PreserveUpgrades] PRIVATE PASSWORD SKIP armed for private-host flow.");
				}
			}
			if (flag2)
			{
				_privateHostRegionFlowArmed = true;
				_privateHostRegionFlowArmedAt = Time.realtimeSinceStartup;
				_privateHostRegionConfirmed = false;
				if (Plugin.Verbose.Value)
				{
					Plugin.Log.LogInfo((object)"[PreserveUpgrades] PRIVATE REGION SKIP armed for private-host flow.");
				}
			}
		}
		catch (Exception ex)
		{
			WarnOncePassword("Lobby QoL private-host popup detection failed: " + Unwrap(ex));
		}
	}

	private static void PrivateHostPasswordUpdatePrefix(object __instance)
	{
		try
		{
			if (Plugin.AutoSkipPrivateHostPassword == null || !Plugin.AutoSkipPrivateHostPassword.Value || !_privateHostPasswordFlowArmed || _privateHostPasswordConfirmed || __instance == null)
			{
				return;
			}
			float num = Time.realtimeSinceStartup - _privateHostPasswordFlowArmedAt;
			if (num < 0f || num > 15f)
			{
				_privateHostPasswordFlowArmed = false;
				_privateHostPasswordConfirmed = false;
				return;
			}
			MethodInfo methodInfo = AccessTools.Method(__instance.GetType(), "ConfirmButton", (Type[])null, (Type[])null);
			if (methodInfo == null)
			{
				WarnOncePassword("Lobby QoL password skip: MenuPagePassword.ConfirmButton not found.");
				_privateHostPasswordFlowArmed = false;
				return;
			}
			_privateHostPasswordConfirmed = true;
			_privateHostPasswordFlowArmed = false;
			methodInfo.Invoke(__instance, null);
			Plugin.Log.LogInfo((object)"[PreserveUpgrades] PRIVATE PASSWORD SKIP confirmed host password page (join-password validation untouched).");
		}
		catch (Exception ex)
		{
			_privateHostPasswordFlowArmed = false;
			WarnOncePassword("Lobby QoL password auto-skip failed: " + Unwrap(ex));
		}
	}

	private static void InstallPrivateHostBestRegionSkip(Harmony harmony)
	{
		//IL_009e: Unknown result type (might be due to invalid IL or missing references)
		//IL_00ab: Expected O, but got Unknown
		try
		{
			Type type = AccessTools.TypeByName("MenuPageRegions");
			if (type == null)
			{
				WarnOnceRegion("Lobby QoL Best Region unavailable: MenuPageRegions type not found.");
				return;
			}
			MethodInfo methodInfo = AccessTools.Method(type, "Start", (Type[])null, (Type[])null);
			MethodInfo method = typeof(LobbyConveniencePatches).GetMethod("PrivateHostRegionsStartPostfix", BindingFlags.Static | BindingFlags.NonPublic);
			MethodInfo methodInfo2 = type.GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic).FirstOrDefault(delegate(MethodInfo m)
			{
				if (m.Name != "PickRegion")
				{
					return false;
				}
				ParameterInfo[] parameters = m.GetParameters();
				return parameters.Length == 1 && parameters[0].ParameterType == typeof(string);
			});
			if (methodInfo == null || method == null || methodInfo2 == null)
			{
				WarnOnceRegion("Lobby QoL Best Region unavailable: MenuPageRegions.Start/PickRegion(string) not found.");
				return;
			}
			harmony.Patch((MethodBase)methodInfo, (HarmonyMethod)null, new HarmonyMethod(method), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
			Plugin.Log.LogInfo((object)"[PreserveUpgrades] Lobby QoL private-host Best Region auto-pick patch active.");
		}
		catch (Exception ex)
		{
			WarnOnceRegion("Lobby QoL Best Region auto-pick install failed: " + Unwrap(ex));
		}
	}

	private static void PrivateHostRegionsStartPostfix(object __instance)
	{
		try
		{
			if (Plugin.AutoPickBestRegionPrivateHost == null || !Plugin.AutoPickBestRegionPrivateHost.Value || !_privateHostRegionFlowArmed || _privateHostRegionConfirmed || __instance == null)
			{
				return;
			}
			float num = Time.realtimeSinceStartup - _privateHostRegionFlowArmedAt;
			if (num < 0f || num > 20f)
			{
				_privateHostRegionFlowArmed = false;
				_privateHostRegionConfirmed = false;
				return;
			}
			MethodInfo methodInfo = __instance.GetType().GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic).FirstOrDefault(delegate(MethodInfo m)
			{
				if (m.Name != "PickRegion")
				{
					return false;
				}
				ParameterInfo[] parameters = m.GetParameters();
				return parameters.Length == 1 && parameters[0].ParameterType == typeof(string);
			});
			if (methodInfo == null)
			{
				WarnOnceRegion("Lobby QoL Best Region: PickRegion(string) not found at runtime.");
				_privateHostRegionFlowArmed = false;
				return;
			}
			_privateHostRegionConfirmed = true;
			_privateHostRegionFlowArmed = false;
			methodInfo.Invoke(__instance, new object[1] { string.Empty });
			Plugin.Log.LogInfo((object)"[PreserveUpgrades] PRIVATE REGION SKIP selected Best Region for private-host flow.");
		}
		catch (Exception ex)
		{
			_privateHostRegionFlowArmed = false;
			WarnOnceRegion("Lobby QoL Best Region auto-pick failed: " + Unwrap(ex));
		}
	}

	private static void WarnOncePassword(string message)
	{
		if (!_privatePasswordPatchLogged)
		{
			_privatePasswordPatchLogged = true;
			if (Plugin.Log != null)
			{
				Plugin.Log.LogWarning((object)("[PreserveUpgrades] " + message));
			}
		}
	}

	private static void WarnOnceRegion(string message)
	{
		if (!_privateRegionPatchLogged)
		{
			_privateRegionPatchLogged = true;
			if (Plugin.Log != null)
			{
				Plugin.Log.LogWarning((object)("[PreserveUpgrades] " + message));
			}
		}
	}

	private static string Unwrap(Exception ex)
	{
		Exception ex2 = ex;
		while (ex2 is TargetInvocationException && ex2.InnerException != null)
		{
			ex2 = ex2.InnerException;
		}
		return ex2.GetType().Name + ": " + ex2.Message;
	}
}
internal static class StageState
{
	internal static bool StagePreparingOrActive;

	internal static bool RestoringBaseline;

	internal static bool ApplyingPostMinimum;

	internal static readonly Dictionary<string, Dictionary<string, int>> Baselines = new Dictionary<string, Dictionary<string, int>>(StringComparer.Ordinal);

	internal static void BeginStage()
	{
		if (!StagePreparingOrActive)
		{
			Baselines.Clear();
			if (Plugin.Verbose.Value)
			{
				Plugin.Log.LogInfo((object)"[PreserveUpgrades] Starting NEW stage capture.");
			}
		}
		else if (Plugin.Verbose.Value)
		{
			Plugin.Log.LogInfo((object)"[PreserveUpgrades] PrepareStage called again; preserving original baseline for role swap.");
		}
		StagePreparingOrActive = true;
		RestoringBaseline = false;
	}

	internal static void BeginRestore()
	{
		RestoringBaseline = true;
		if (Plugin.Verbose.Value)
		{
			Plugin.Log.LogInfo((object)"[PreserveUpgrades] Restoring pre-stage upgrades.");
		}
	}

	internal static void EndStage()
	{
		if (Plugin.Verbose.Value)
		{
			Plugin.Log.LogInfo((object)"[PreserveUpgrades] Stage ended; baseline cache cleared.");
		}
		RestoringBaseline = false;
		ApplyingPostMinimum = false;
		StagePreparingOrActive = false;
		Plugin.ClearTraceCache();
		Baselines.Clear();
	}
}
internal static class StageLifecyclePatches
{
	internal static void PrepareStagePrefix()
	{
		if (Plugin.Enabled.Value)
		{
			if (Plugin.Verbose.Value)
			{
				Plugin.LoggerStatic("TRACE LIFECYCLE PrepareStage PREFIX");
			}
			StageState.BeginStage();
		}
	}

	internal static void StageEndingPrefix(object[] __args)
	{
		if (Plugin.Enabled.Value)
		{
			bool flag = __args != null && __args.Length > 0 && __args[0] is bool && (bool)__args[0];
			Plugin.LoggerStatic("TRACE LIFECYCLE StageEndingInternal PREFIX restoreBase=" + flag + " deactivate=" + ((__args != null && __args.Length > 1) ? Convert.ToString(__args[1]) : "?"));
			if (flag)
			{
				StageState.BeginRestore();
			}
		}
	}

	internal static void StageEndingPostfix(object[] __args)
	{
		if (Plugin.Enabled.Value)
		{
			bool flag = __args != null && __args.Length > 1 && __args[1] is bool && (bool)__args[1];
			if (Plugin.Verbose.Value)
			{
				Plugin.LoggerStatic("TRACE LIFECYCLE StageEndingInternal POSTFIX deactivate=" + flag);
			}
			if (flag)
			{
				StageState.EndStage();
			}
			else
			{
				StageState.RestoringBaseline = false;
			}
		}
	}

	internal static void ShutdownPrefix()
	{
		if (Plugin.Enabled.Value)
		{
			if (Plugin.Verbose.Value)
			{
				Plugin.LoggerStatic("TRACE LIFECYCLE Shutdown PREFIX");
			}
			StageState.BeginRestore();
		}
	}

	internal static void ShutdownPostfix()
	{
		if (Plugin.Enabled.Value)
		{
			Plugin.LoggerStatic("TRACE LIFECYCLE Shutdown POSTFIX");
			StageState.EndStage();
		}
	}
}
internal static class UpgradePatches
{
	internal static void SetLevelsPrefix(MethodBase __originalMethod, object[] __args)
	{
		if (!Plugin.Enabled.Value)
		{
			return;
		}
		if (Plugin.Verbose.Value)
		{
			Plugin.Log.LogInfo((object)("[PreserveUpgrades] CALL " + DescribeCall(__originalMethod, __args)));
		}
		if (StageState.ApplyingPostMinimum || !StageState.StagePreparingOrActive || __args == null || __args.Length < 2)
		{
			return;
		}
		string text = __args[0] as string;
		if (string.IsNullOrEmpty(text))
		{
			return;
		}
		try
		{
			if (!StageState.Baselines.TryGetValue(text, out var value))
			{
				value = RoleShuffleBridge.ReadCurrentLevels(text);
				if (value == null)
				{
					Plugin.Log.LogWarning((object)("[PreserveUpgrades] Could not capture current levels for " + text + "."));
					return;
				}
				StageState.Baselines[text] = value;
				if (Plugin.Verbose.Value)
				{
					Plugin.Log.LogInfo((object)("[PreserveUpgrades] BASELINE " + text + " => " + FormatLevels(value)));
				}
			}
			if (Plugin.Verbose.Value)
			{
				Plugin.Log.LogInfo((object)"[PreserveUpgrades] Leaving RoleShuffle SetLevels targets UNCHANGED; baseline will be re-applied afterwards.");
			}
		}
		catch (Exception ex)
		{
			Plugin.Log.LogError((object)("[PreserveUpgrades] SetLevels capture failed: " + ex));
		}
	}

	internal static void EnsureAtLeastLevelsPrefix(MethodBase __originalMethod, object[] __args)
	{
		if (!Plugin.Enabled.Value || __args == null || __args.Length < 2)
		{
			return;
		}
		if (StageState.ApplyingPostMinimum)
		{
			if (Plugin.Verbose.Value)
			{
				Plugin.Log.LogInfo((object)("[PreserveUpgrades] CALL(minimum/post) " + DescribeCall(__originalMethod, __args)));
			}
			return;
		}
		if (Plugin.Verbose.Value)
		{
			Plugin.Log.LogInfo((object)("[PreserveUpgrades] CALL(minimum) " + DescribeCall(__originalMethod, __args)));
		}
		string text = __args[0] as string;
		object obj = __args[1];
		if (string.IsNullOrEmpty(text) || obj == null || !StageState.Baselines.TryGetValue(text, out var value))
		{
			return;
		}
		try
		{
			object obj2 = RoleShuffleBridge.RebuildTargets(obj, value, restoring: false);
			if (obj2 != null)
			{
				__args[1] = obj2;
				if (Plugin.Verbose.Value)
				{
					Plugin.Log.LogInfo((object)("[PreserveUpgrades] PROTECTED dynamic minimum targets for " + text + "."));
				}
			}
		}
		catch (Exception ex)
		{
			Plugin.Log.LogError((object)("[PreserveUpgrades] EnsureAtLeastLevels protection failed: " + ex));
		}
	}

	internal static void SetLevelsPostfix(MethodBase __originalMethod, object[] __args)
	{
		TraceAfterCall("SetLevels/raw", __originalMethod, __args);
		if (!Plugin.Enabled.Value || StageState.ApplyingPostMinimum || StageState.RestoringBaseline || !StageState.StagePreparingOrActive || __args == null || __args.Length < 1)
		{
			return;
		}
		string text = __args[0] as string;
		if (string.IsNullOrEmpty(text) || !StageState.Baselines.TryGetValue(text, out var value) || value == null || value.Count == 0)
		{
			return;
		}
		try
		{
			StageState.ApplyingPostMinimum = true;
			if (Plugin.Verbose.Value)
			{
				Plugin.Log.LogInfo((object)("[PreserveUpgrades] POST-MINIMUM " + text + " => " + FormatLevels(value)));
			}
			RoleShuffleBridge.EnsureBaselineMinimums(text, value);
		}
		catch (Exception ex)
		{
			Plugin.Log.LogError((object)("[PreserveUpgrades] POST-MINIMUM failed: " + ex));
		}
		finally
		{
			StageState.ApplyingPostMinimum = false;
		}
		TraceAfterCall("SetLevels/post-minimum", __originalMethod, __args);
	}

	internal static void EnsureAtLeastLevelsPostfix(MethodBase __originalMethod, object[] __args)
	{
		TraceAfterCall("EnsureAtLeastLevels", __originalMethod, __args);
	}

	private static void TraceAfterCall(string label, MethodBase method, object[] args)
	{
		if (!Plugin.Enabled.Value || !Plugin.Verbose.Value || args == null || args.Length < 2)
		{
			return;
		}
		string text = args[0] as string;
		if (string.IsNullOrEmpty(text))
		{
			return;
		}
		try
		{
			Dictionary<string, int> dictionary = RoleShuffleBridge.ReadCurrentLevels(text);
			if (dictionary == null)
			{
				dictionary = RoleShuffleBridge.ReadCurrentLevels(text);
			}
			StageState.Baselines.TryGetValue(text, out var value);
			Plugin.LoggerStatic("TRACE AFTER " + label + " " + text + " | TARGETS=" + DescribeTargets(args[1]) + " | LEVELS=" + FormatLevels(dictionary) + " | BASELINE=" + FormatLevels(value));
		}
		catch (Exception ex)
		{
			Plugin.LoggerStatic("TRACE AFTER " + label + " failed: " + ex);
		}
	}

	internal static string FormatDiff(Dictionary<string, int> before, Dictionary<string, int> after)
	{
		if (before == null || after == null)
		{
			return string.Empty;
		}
		HashSet<string> hashSet = new HashSet<string>(before.Keys, StringComparer.OrdinalIgnoreCase);
		hashSet.UnionWith(after.Keys);
		List<string> list = new List<string>();
		foreach (string item in hashSet.OrderBy((string k) => k))
		{
			int value = 0;
			int value2 = 0;
			bool flag = before.TryGetValue(item, out value);
			bool flag2 = after.TryGetValue(item, out value2);
			if (!flag)
			{
				value = 0;
			}
			if (!flag2)
			{
				value2 = 0;
			}
			if (value != value2)
			{
				list.Add(item + ":" + value + "->" + value2);
			}
		}
		return string.Join(", ", list.ToArray());
	}

	private static string DescribeTargets(object targets)
	{
		if (targets == null)
		{
			return "<null>";
		}
		if (!(targets is IEnumerable enumerable))
		{
			return targets.ToString();
		}
		List<string> list = new List<string>();
		foreach (object item in enumerable)
		{
			if (item != null)
			{
				try
				{
					Type type = item.GetType();
					PropertyInfo propertyInfo = AccessTools.Property(type, "CommandName");
					PropertyInfo propertyInfo2 = AccessTools.Property(type, "DictionaryName");
					PropertyInfo propertyInfo3 = AccessTools.Property(type, "Level");
					string text = ((propertyInfo == null) ? "?" : Convert.ToString(propertyInfo.GetValue(item, null)));
					string text2 = ((propertyInfo2 == null) ? "?" : Convert.ToString(propertyInfo2.GetValue(item, null)));
					string text3 = ((propertyInfo3 == null) ? "?" : Convert.ToString(propertyInfo3.GetValue(item, null)));
					list.Add(text + "/" + text2 + "=" + text3);
				}
				catch
				{
					list.Add(item.ToString());
				}
			}
		}
		return string.Join(", ", list.ToArray());
	}

	private static string DescribeCall(MethodBase method, object[] args)
	{
		string text = ((method == null) ? "<unknown>" : (method.DeclaringType.FullName + "." + method.Name));
		if (args == null)
		{
			return text + " args=<null>";
		}
		string[] array = new string[args.Length];
		for (int i = 0; i < args.Length; i++)
		{
			object obj = args[i];
			if (obj == null)
			{
				array[i] = i + ":null";
			}
			else
			{
				array[i] = i + ":" + obj.GetType().FullName;
			}
		}
		return text + " [" + string.Join(", ", array) + "]";
	}

	internal static string FormatLevels(Dictionary<string, int> levels)
	{
		if (levels == null)
		{
			return "<null>";
		}
		return string.Join(", ", (from kv in levels
			orderby kv.Key
			select kv.Key + "=" + kv.Value).ToArray());
	}
}
internal static class RoleShuffleBridge
{
	internal static Type UpgradeServiceType;

	internal static Type StageRoleControllerType;

	private static Type _upgradeGrantType;

	private static MethodInfo _tryGetLevels;

	private static MethodInfo _ensureAtLeastLevels;

	private static ConstructorInfo _grantCtor;

	private static PropertyInfo _commandName;

	private static PropertyInfo _dictionaryName;

	private static PropertyInfo _level;

	internal static void Initialize()
	{
		Assembly assembly = AppDomain.CurrentDomain.GetAssemblies().FirstOrDefault((Assembly a) => string.Equals(a.GetName().Name, "RoleShuffle", StringComparison.OrdinalIgnoreCase));
		if (assembly == null)
		{
			throw new InvalidOperationException("RoleShuffle.dll is not loaded.");
		}
		UpgradeServiceType = assembly.GetType("REPOJP.StageRoles.UpgradeService", throwOnError: true);
		StageRoleControllerType = assembly.GetType("REPOJP.StageRoles.StageRoleController", throwOnError: true);
		_upgradeGrantType = assembly.GetType("REPOJP.StageRoles.UpgradeGrant", throwOnError: true);
		_tryGetLevels = AccessTools.Method(UpgradeServiceType, "TryGetLevels", (Type[])null, (Type[])null);
		_ensureAtLeastLevels = AccessTools.Method(UpgradeServiceType, "EnsureAtLeastLevels", (Type[])null, (Type[])null);
		_grantCtor = _upgradeGrantType.GetConstructor(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, new Type[3]
		{
			typeof(string),
			typeof(string),
			typeof(int)
		}, null);
		_commandName = AccessTools.Property(_upgradeGrantType, "CommandName");
		_dictionaryName = AccessTools.Property(_upgradeGrantType, "DictionaryName");
		_level = AccessTools.Property(_upgradeGrantType, "Level");
		if (_tryGetLevels == null)
		{
			throw new MissingMethodException(UpgradeServiceType.FullName, "TryGetLevels");
		}
		if (_ensureAtLeastLevels == null)
		{
			throw new MissingMethodException(UpgradeServiceType.FullName, "EnsureAtLeastLevels");
		}
		if (_grantCtor == null)
		{
			throw new MissingMethodException(_upgradeGrantType.FullName, "non-public/public .ctor(string,string,int)");
		}
		if (_commandName == null || _dictionaryName == null || _level == null)
		{
			throw new MissingMemberException(_upgradeGrantType.FullName, "CommandName/DictionaryName/Level");
		}
		LogUpgradeGrantShape();
		Plugin.Log.LogInfo((object)string.Concat("Detected ", assembly.GetName().Name, " assembly version ", assembly.GetName().Version, "."));
	}

	internal static Dictionary<string, int> ReadCurrentLevels(string steamId)
	{
		object[] array = new object[2] { steamId, null };
		object obj = _tryGetLevels.Invoke(null, array);
		if (!(obj is bool) || !(bool)obj || array[1] == null)
		{
			return null;
		}
		if (!(array[1] is IDictionary dictionary))
		{
			return null;
		}
		Dictionary<string, int> dictionary2 = new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase);
		foreach (DictionaryEntry item in dictionary)
		{
			if (item.Key != null && item.Value != null)
			{
				string key = item.Key.ToString();
				int value;
				try
				{
					value = Convert.ToInt32(item.Value);
				}
				catch
				{
					continue;
				}
				dictionary2[key] = value;
			}
		}
		return dictionary2;
	}

	internal static void EnsureBaselineMinimums(string steamId, Dictionary<string, int> baseline)
	{
		if (string.IsNullOrEmpty(steamId) || baseline == null || baseline.Count == 0)
		{
			return;
		}
		Type type = typeof(List<>).MakeGenericType(_upgradeGrantType);
		IList list = (IList)Activator.CreateInstance(type);
		foreach (KeyValuePair<string, int> item in baseline)
		{
			string text = CommandNameForDictionary(item.Key);
			if (text != null)
			{
				list.Add(_grantCtor.Invoke(new object[3] { text, item.Key, item.Value }));
			}
		}
		if (list.Count != 0)
		{
			_ensureAtLeastLevels.Invoke(null, new object[2] { steamId, list });
		}
	}

	private static string CommandNameForDictionary(string dictionaryName)
	{
		return dictionaryName switch
		{
			"playerUpgradeHealth" => "Health", 
			"playerUpgradeStamina" => "Stamina", 
			"playerUpgradeExtraJump" => "ExtraJump", 
			"playerUpgradeSpeed" => "Speed", 
			"playerUpgradeStrength" => "Strength", 
			"playerUpgradeRange" => "Range", 
			"playerUpgradeLaunch" => "Launch", 
			"playerUpgradeTumbleClimb" => "TumbleClimb", 
			"playerUpgradeTumbleWings" => "TumbleWings", 
			"playerUpgradeCrouchRest" => "CrouchRest", 
			"playerUpgradeMapPlayerCount" => "MapPlayerCount", 
			"playerUpgradeDeathHeadBattery" => "DeathHeadBattery", 
			_ => null, 
		};
	}

	internal static object RebuildTargets(object originalTargets, Dictionary<string, int> baseline, bool restoring)
	{
		if (!(originalTargets is IEnumerable enumerable))
		{
			return null;
		}
		Type type = typeof(List<>).MakeGenericType(_upgradeGrantType);
		IList list = (IList)Activator.CreateInstance(type);
		foreach (object item in enumerable)
		{
			if (item != null)
			{
				string commandName = (string)_commandName.GetValue(item, null);
				string text = (string)_dictionaryName.GetValue(item, null);
				int num = Convert.ToInt32(_level.GetValue(item, null));
				int value;
				bool flag = TryBaselineLevel(baseline, text, commandName, out value);
				int num2 = num;
				if (flag)
				{
					num2 = (restoring ? value : Math.Max(value, num));
				}
				object value2 = CloneGrantWithLevel(item, num2);
				list.Add(value2);
				if (Plugin.Verbose.Value && num2 != num)
				{
					Plugin.Log.LogInfo((object)("[PreserveUpgrades] " + (restoring ? "restore " : "minimum ") + text + ": requested=" + num + ", saved=" + value + ", applied=" + num2));
				}
			}
		}
		return list;
	}

	private static void LogUpgradeGrantShape()
	{
		try
		{
			string text = string.Join(" | ", (from c in _upgradeGrantType.GetConstructors(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)
				select ".ctor(" + string.Join(", ", (from p in c.GetParameters()
					select p.ParameterType.FullName + " " + p.Name).ToArray()) + ")").ToArray());
			string text2 = string.Join(" | ", (from p in _upgradeGrantType.GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)
				select p.PropertyType.FullName + " " + p.Name + " [get=" + (p.GetGetMethod(nonPublic: true) != null) + ",set=" + (p.GetSetMethod(nonPublic: true) != null) + "]").ToArray());
			string text3 = string.Join(" | ", (from f in _upgradeGrantType.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)
				select f.FieldType.FullName + " " + f.Name + " [readonly=" + f.IsInitOnly + "]").ToArray());
			Plugin.Log.LogInfo((object)("[PreserveUpgrades] UpgradeGrant constructors: " + text));
			Plugin.Log.LogInfo((object)("[PreserveUpgrades] UpgradeGrant properties: " + text2));
			Plugin.Log.LogInfo((object)("[PreserveUpgrades] UpgradeGrant fields: " + text3));
		}
		catch (Exception ex)
		{
			Plugin.Log.LogWarning((object)("[PreserveUpgrades] Could not describe UpgradeGrant: " + ex));
		}
	}

	private static object CloneGrantWithLevel(object original, int finalLevel)
	{
		if (original == null)
		{
			return null;
		}
		string text = (string)_commandName.GetValue(original, null);
		string text2 = (string)_dictionaryName.GetValue(original, null);
		return _grantCtor.Invoke(new object[3] { text, text2, finalLevel });
	}

	private static bool TryBaselineLevel(Dictionary<string, int> baseline, string dictionaryName, string commandName, out int value)
	{
		value = 0;
		if (!string.IsNullOrEmpty(dictionaryName) && baseline.TryGetValue(dictionaryName, out value))
		{
			return true;
		}
		if (!string.IsNullOrEmpty(commandName) && baseline.TryGetValue(commandName, out value))
		{
			return true;
		}
		return false;
	}
}