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 Enabled; internal static ConfigEntry Verbose; internal static ConfigEntry AutoSkipPrivateHostPassword; internal static ConfigEntry AutoPickBestRegionPrivateHost; private Harmony _harmony; private DateTime _nextTracePollUtc = DateTime.MinValue; private static readonly Dictionary> _lastTraceLevels = new Dictionary>(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("General", "Enabled", true, "Preserve vanilla/shop upgrades while RoleShuffle applies temporary role upgrades."); Verbose = ((BaseUnityPlugin)this).Config.Bind("General", "VerboseLogging", true, "Log captured/restored upgrade levels for troubleshooting."); AutoSkipPrivateHostPassword = ((BaseUnityPlugin)this).Config.Bind("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("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> Baselines = new Dictionary>(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 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 before, Dictionary after) { if (before == null || after == null) { return string.Empty; } HashSet hashSet = new HashSet(before.Keys, StringComparer.OrdinalIgnoreCase); hashSet.UnionWith(after.Keys); List list = new List(); 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 ""; } if (!(targets is IEnumerable enumerable)) { return targets.ToString(); } List list = new List(); 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) ? "" : (method.DeclaringType.FullName + "." + method.Name)); if (args == null) { return text + " args="; } 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 levels) { if (levels == null) { return ""; } 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 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 dictionary2 = new Dictionary(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 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 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 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 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; } }