Please disclose if any significant portion of your mod was created using AI tools by adding the 'AI Generated' category. Failing to do so may result in the mod being removed from Thunderstore.
Decompiled source of RoleShuffleUpgradeKeeper v1.0.1
BepInEx/plugins/RoleShuffleUpgradeKeeper/RoleShufflePreserveUpgrades.dll
Decompiled 4 hours agousing 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; } }