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 LateGamePerformance v1.5.1
LateGamePerformance.dll
Decompiled 2 days ago
The result has been truncated due to the large size, download it to view full contents!
using System; using System.Collections; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Diagnostics; using System.Globalization; using System.Reflection; using System.Reflection.Emit; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Security; using System.Security.Permissions; using System.Text; using BepInEx; using BepInEx.Bootstrap; using BepInEx.Configuration; using BepInEx.Logging; using EntityStates; using EntityStates.FalseSon; using HarmonyLib; using LateGamePerformance.Patches; using LateGamePerformance.Ui; using Microsoft.CodeAnalysis; using Rewired; using RoR2; using RoR2.Navigation; using RoR2.Networking; using RoR2.Skills; using RoR2.UI; using RoR2.UI.SkinControllers; using TMPro; using UnityEngine; using UnityEngine.EventSystems; using UnityEngine.Events; using UnityEngine.Networking; using UnityEngine.Rendering; using UnityEngine.UI; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: IgnoresAccessChecksTo("RoR2")] [assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")] [assembly: AssemblyCompany("LateGamePerformance")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("1.5.1.0")] [assembly: AssemblyInformationalVersion("1.5.1")] [assembly: AssemblyProduct("LateGamePerformance")] [assembly: AssemblyTitle("LateGamePerformance")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("1.5.1.0")] [module: UnverifiableCode] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)] internal sealed class RefSafetyRulesAttribute : Attribute { public readonly int Version; public RefSafetyRulesAttribute(int P_0) { Version = P_0; } } } namespace LateGamePerformance { internal sealed class DpsHud : MonoBehaviour { private sealed class RowView { internal RectTransform root; internal Image bar; internal HGTextMeshProUGUI name; internal HGTextMeshProUGUI damage; internal HGTextMeshProUGUI share; internal HGTextMeshProUGUI dps; internal HGTextMeshProUGUI now; } private const float Width = 470f; private const float Pad = 14f; private const float TitleHeight = 30f; private const float ColumnHeight = 20f; private const float RowHeight = 30f; private const float FooterHeight = 22f; private const float NameWidth = 150f; private const float DamageWidth = 78f; private const float ShareWidth = 56f; private const float DpsWidth = 66f; private const float NowWidth = 72f; private static readonly Color[] Ranks = (Color[])(object)new Color[3] { new Color(1f, 0.82f, 0.36f, 1f), new Color(0.8f, 0.84f, 0.9f, 1f), new Color(0.79f, 0.55f, 0.36f, 1f) }; private Canvas canvas; private RectTransform panel; private RectTransform rowRoot; private HGTextMeshProUGUI clock; private HGTextMeshProUGUI footer; private readonly List<RowView> views = new List<RowView>(); private bool hidden; private float nextRefresh; private bool wasVisible; internal static DpsHud Instance { get; private set; } internal static void Install(GameObject host) { Instance = host.AddComponent<DpsHud>(); } private void Update() { //IL_0013: Unknown result type (might be due to invalid IL or missing references) //IL_0018: Unknown result type (might be due to invalid IL or missing references) //IL_00e0: Unknown result type (might be due to invalid IL or missing references) if (DpsMeterPatch.Active && DpsMeterPatch.ToggleKey != null) { KeyboardShortcut value = DpsMeterPatch.ToggleKey.Value; if (((KeyboardShortcut)(ref value)).IsDown() && !ModMenu.IsBinding) { hidden = !hidden; } } if (!DpsMeterPatch.Active || hidden || !DpsMeterPatch.ShowDuringRun.Value || !Object.op_Implicit((Object)(object)Run.instance) || !Object.op_Implicit((Object)(object)Stage.instance) || ModMenu.IsOpen || StatPointsWindow.IsOpen) { UiFactory.SetVisible(canvas, visible: false); wasVisible = false; return; } GameSkin.Refresh(); if (GameSkin.Ready) { if ((Object)(object)canvas == (Object)null) { Build(); } UiFactory.SetVisible(canvas, visible: true); UiFactory.Position(panel, DpsMeterPatch.PanelX.Value, DpsMeterPatch.PanelY.Value, panel.sizeDelta); if (!wasVisible || Time.unscaledTime >= nextRefresh) { wasVisible = true; nextRefresh = Time.unscaledTime + 0.1f; Refresh(); } } } private void OnDestroy() { if ((Object)(object)canvas != (Object)null) { Object.Destroy((Object)(object)((Component)canvas).gameObject); } } private void Build() { //IL_0054: Unknown result type (might be due to invalid IL or missing references) //IL_0066: Unknown result type (might be due to invalid IL or missing references) //IL_0078: Unknown result type (might be due to invalid IL or missing references) //IL_0088: Unknown result type (might be due to invalid IL or missing references) //IL_00a2: Unknown result type (might be due to invalid IL or missing references) //IL_01ac: Unknown result type (might be due to invalid IL or missing references) //IL_01b2: Expected O, but got Unknown //IL_021d: Unknown result type (might be due to invalid IL or missing references) canvas = UiFactory.CreateCanvas("LGP_DamageMeter", 100, interactive: false); panel = UiFactory.Panel(((Component)canvas).transform, "Panel"); float value = DpsMeterPatch.PanelX.Value; float num = 1f - DpsMeterPatch.PanelY.Value; panel.anchorMin = new Vector2(value, num); panel.anchorMax = new Vector2(value, num); panel.pivot = new Vector2(value, num); panel.anchoredPosition = Vector2.zero; panel.sizeDelta = new Vector2(470f, 200f); UiFactory.Row(((TMP_Text)UiFactory.Label((Transform)(object)panel, "Title", "DAMAGE THIS STAGE", GameSkin.TextKind.Header, 22f, (TextAlignmentOptions)513)).rectTransform, 14f, 352f, 14f, 30f); clock = UiFactory.Label((Transform)(object)panel, "Clock", "0:00", GameSkin.TextKind.Header, 22f, (TextAlignmentOptions)516); UiFactory.Row(((TMP_Text)clock).rectTransform, 366f, 90f, 14f, 30f); float num2 = 44f; Column("Player", (TextAlignmentOptions)513, 0, num2); Column("Damage", (TextAlignmentOptions)516, 1, num2); Column("Share", (TextAlignmentOptions)516, 2, num2); Column("DPS", (TextAlignmentOptions)516, 3, num2); Column("Now 6s", (TextAlignmentOptions)516, 4, num2); GameObject val = new GameObject("Rows", new Type[1] { typeof(RectTransform) }); rowRoot = val.GetComponent<RectTransform>(); ((Transform)rowRoot).SetParent((Transform)(object)panel, false); UiFactory.Row(rowRoot, 0f, 470f, num2 + 20f, 0f); footer = UiFactory.Label((Transform)(object)panel, "Footer", string.Empty, GameSkin.TextKind.Body, 15f, (TextAlignmentOptions)513); ((Graphic)footer).color = GameSkin.QuietColor; } private void Column(string text, TextAlignmentOptions alignment, int index, float top) { //IL_0018: Unknown result type (might be due to invalid IL or missing references) //IL_001f: Unknown result type (might be due to invalid IL or missing references) HGTextMeshProUGUI obj = UiFactory.Label((Transform)(object)panel, "Column" + text, text, GameSkin.TextKind.Body, 16f, alignment); ((Graphic)obj).color = GameSkin.QuietColor; UiFactory.Row(((TMP_Text)obj).rectTransform, 14f + OffsetOf(index), WidthOf(index), top, 20f); } private static float WidthOf(int index) { return index switch { 0 => 150f, 1 => 78f, 2 => 56f, 3 => 66f, _ => 72f, }; } private static float OffsetOf(int index) { float num = 0f; for (int i = 0; i < index; i++) { num += WidthOf(i); } return num; } private RowView CreateRow(int index) { //IL_0024: Unknown result type (might be due to invalid IL or missing references) //IL_0080: Unknown result type (might be due to invalid IL or missing references) RectTransform component = new GameObject("Row" + index, new Type[1] { typeof(RectTransform) }).GetComponent<RectTransform>(); ((Transform)component).SetParent((Transform)(object)rowRoot, false); UiFactory.Row(component, 0f, 470f, (float)index * 30f, 30f); RowView obj = new RowView { root = component, bar = UiFactory.Fill((Transform)(object)component, "Bar", new Color(1f, 1f, 1f, 0.16f)) }; UiFactory.Row(((Graphic)obj.bar).rectTransform, 7f, 456f, 2f, 26f); obj.name = Cell(component, "Name", 0, (TextAlignmentOptions)513, 20f, GameSkin.TextKind.Body); ((TMP_Text)obj.name).overflowMode = (TextOverflowModes)1; ((TMP_Text)obj.name).richText = false; obj.damage = Cell(component, "Damage", 1, (TextAlignmentOptions)516, 20f, GameSkin.TextKind.Header); obj.share = Cell(component, "Share", 2, (TextAlignmentOptions)516, 19f, GameSkin.TextKind.Body); obj.dps = Cell(component, "Dps", 3, (TextAlignmentOptions)516, 19f, GameSkin.TextKind.Body); obj.now = Cell(component, "Now", 4, (TextAlignmentOptions)516, 20f, GameSkin.TextKind.Header); return obj; } private static HGTextMeshProUGUI Cell(RectTransform parent, string name, int index, TextAlignmentOptions alignment, float size, GameSkin.TextKind kind) { //IL_000b: Unknown result type (might be due to invalid IL or missing references) HGTextMeshProUGUI obj = UiFactory.Label((Transform)(object)parent, name, string.Empty, kind, size, alignment); UiFactory.Row(((TMP_Text)obj).rectTransform, 14f + OffsetOf(index), WidthOf(index), 0f, 30f); return obj; } private void Refresh() { //IL_02c5: Unknown result type (might be due to invalid IL or missing references) //IL_0317: Unknown result type (might be due to invalid IL or missing references) //IL_0116: Unknown result type (might be due to invalid IL or missing references) //IL_0129: Unknown result type (might be due to invalid IL or missing references) //IL_0131: Unknown result type (might be due to invalid IL or missing references) //IL_0124: Unknown result type (might be due to invalid IL or missing references) //IL_0246: Unknown result type (might be due to invalid IL or missing references) //IL_024d: Unknown result type (might be due to invalid IL or missing references) //IL_0254: Unknown result type (might be due to invalid IL or missing references) //IL_0260: Unknown result type (might be due to invalid IL or missing references) //IL_0281: Unknown result type (might be due to invalid IL or missing references) List<DpsMeterPatch.Row> list = DpsMeterPatch.Snapshot(); while (views.Count < list.Count) { views.Add(CreateRow(views.Count)); } for (int i = list.Count; i < views.Count; i++) { ((Component)views[i].root).gameObject.SetActive(false); } float stageSeconds = DpsMeterPatch.StageSeconds; double num = 0.0; for (int j = 0; j < list.Count; j++) { num += list[j].total; } double num2 = ((list.Count > 0) ? list[0].total : 0.0); for (int k = 0; k < list.Count; k++) { DpsMeterPatch.Row row = list[k]; RowView rowView = views[k]; ((Component)rowView.root).gameObject.SetActive(true); Color val = ((k < Ranks.Length && row.total > 0.0) ? Ranks[k] : GameSkin.BodyColor); ((Graphic)rowView.name).color = val; ((TMP_Text)rowView.name).text = ((row.name == null) ? "Player" : row.name.Replace('\n', ' ').Replace('\r', ' ').Replace('\t', ' ')); ((TMP_Text)rowView.damage).text = Short(row.total); ((TMP_Text)rowView.share).text = ((num > 0.0) ? (((float)(row.total / num) * 100f).ToString("0", CultureInfo.InvariantCulture) + "%") : "-"); ((TMP_Text)rowView.dps).text = Short((stageSeconds > 0.5f) ? (row.total / (double)stageSeconds) : 0.0); ((TMP_Text)rowView.now).text = Short(row.Recent); float num3 = ((num2 > 0.0) ? Mathf.Clamp01((float)(row.total / num2)) : 0f); ((Graphic)rowView.bar).color = new Color(val.r, val.g, val.b, 0.2f); ((Graphic)rowView.bar).rectTransform.sizeDelta = new Vector2(456f * num3, 26f); } ((TMP_Text)clock).text = Clock(stageSeconds); ((TMP_Text)footer).text = $"Total {Short(num)} - {DpsMeterPatch.ToggleKey.Value} hides this"; ((TMP_Text)footer).overflowMode = (TextOverflowModes)1; float num4 = 64f; float num5 = num4 + (float)list.Count * 30f + 22f + 14f; panel.sizeDelta = new Vector2(470f, num5); UiFactory.Row(((TMP_Text)footer).rectTransform, 14f, 442f, num4 + (float)list.Count * 30f + 2f, 22f); } private static string Clock(float seconds) { int num = Mathf.Max(0, Mathf.FloorToInt(seconds)); return (num / 60).ToString(CultureInfo.InvariantCulture) + ":" + (num % 60).ToString("00", CultureInfo.InvariantCulture); } private static string Short(double value) { if (value >= 1000000.0) { return (value / 1000000.0).ToString("0.0", CultureInfo.InvariantCulture) + "M"; } if (value >= 1000.0) { return (value / 1000.0).ToString("0.0", CultureInfo.InvariantCulture) + "k"; } return value.ToString("0", CultureInfo.InvariantCulture); } } internal static class FaultDiagnostics { private static readonly Dictionary<string, int> counts = new Dictionary<string, int>(); internal static void Record(string operation, Exception error = null) { counts.TryGetValue(operation, out var value); value = ((value == int.MaxValue) ? value : (value + 1)); counts[operation] = value; if (value == 1 || value == 10 || value == 100) { Plugin.Log.LogWarning((object)$"{operation}: recovery count {value}. {error?.Message}"); } } internal static int Count(string operation) { if (!counts.TryGetValue(operation, out var value)) { return 0; } return value; } internal static void Reset() { counts.Clear(); } } internal static class GameplayRules { internal static float PreserveHealth(float current, float previousMaximum, float maximum) { return Math.Max(0f, Math.Min(current, maximum)); } internal static void ClampAllocations(int[] spent, int budget, int cap) { int num = Math.Max(0, budget); for (int i = 0; i < spent.Length; i++) { spent[i] = Math.Min(Math.Max(0, spent[i]), Math.Min(Math.Max(0, cap), num)); num -= spent[i]; } } internal static uint SharedMoney(uint amount, float scalar) { if (float.IsNaN(scalar) || scalar <= 0f) { return 0u; } if (scalar >= 1f) { return amount; } return (uint)Math.Min(4294967295.0, Math.Ceiling((double)amount * (double)scalar)); } } internal static class MenuCatalog { private sealed class Entry { internal string Category; internal string Title; internal string Blurb; } internal const string Gameplay = "Gameplay"; internal const string Maintenance = "Fixes and performance"; private static readonly Dictionary<string, Entry> features = new Dictionary<string, Entry> { ["SkipIntro"] = new Entry { Category = "Gameplay", Title = "Skip the intro video", Blurb = "Skip the intro video. Skip the remaining logo delay after game initialization when the logo setting is on." }, ["CritOverstack"] = new Entry { Category = "Gameplay", Title = "Crit overstacking", Blurb = "Crit chance above 100 percent increases crit damage." }, ["AutoSprint"] = new Entry { Category = "Gameplay", Title = "Automatic sprint", Blurb = "Your character sprints while it moves. The hold setting lets the sprint key stop automatic sprint." }, ["EmptyChests"] = new Entry { Category = "Gameplay", Title = "Clear empty chests", Blurb = "Remove selected empty interactables after a delay. Settings select chests, barrels, shops, and scavenger bags." }, ["Sharing"] = new Entry { Category = "Gameplay", Title = "Share items and money", Blurb = "Share eligible pickups and gold with other players. Filters select item tiers, dead players, equipment, and printer output. Original rewards stay intact. Only the host needs this feature. This feature starts off and changes game balance." }, ["DpsMeter"] = new Entry { Category = "Gameplay", Title = "Damage meter", Blurb = "Show player damage, recent damage per second, and damage share. Collection starts at stage start, feature enable, or a filter change. Matching peers receive host totals." }, ["StatPoints"] = new Entry { Category = "Gameplay", Title = "Character stat points", Blurb = "Earn points from character levels. Spend points on health, damage, attack speed, crit chance, armor, regeneration, or speed. Use the selected key or pause screen to open the window. Only the host spends points. All connected peers must support stat points before bonuses apply." }, ["BugFixes"] = new Entry { Category = "Fixes and performance", Title = "Fix game bugs", Blurb = "Add seven guards for specific game exceptions. A failed guard reports its state and leaves other guards active." }, ["BuffStatsFilter"] = new Entry { Category = "Fixes and performance", Title = "Faster damage-over-time ticks", Blurb = "Skip redundant stat updates for selected damage buffs. By default, another plugin or stat hook keeps all game stat updates." }, ["NavPathBudget"] = new Entry { Category = "Fixes and performance", Title = "Spread out enemy pathfinding", Blurb = "Limit path calculations per physics tick. Each agent receives a turn. A lower limit can delay a new path." }, ["TargetSearchGc"] = new Entry { Category = "Fixes and performance", Title = "Cheaper target search", Blurb = "Reuse target candidates and remove repeated query allocations. Missiles, drones, turrets, and other target searches use this feature." }, ["DamageNumberBatch"] = new Entry { Category = "Fixes and performance", Title = "Faster damage numbers", Blurb = "Collect damage numbers for one update. Process the group through the game particle system." }, ["RaycastThrottle"] = new Entry { Category = "Fixes and performance", Title = "Cheaper enemy steering", Blurb = "Retain obstacle checks for a short interval. A large position change or sharp turn requires a new check." }, ["HealthBarThrottle"] = new Entry { Category = "Fixes and performance", Title = "Cheaper health bars", Blurb = "Set the interval between health bar updates. A larger interval delays visual feedback." }, ["TeamItemCountCache"] = new Entry { Category = "Fixes and performance", Title = "Faster teleporter events", Blurb = "Retain holdout item counts for a short interval. Inventory, team, character, and user changes clear those counts." }, ["ModelCameraPass"] = new Entry { Category = "Fixes and performance", Title = "Cheaper character drawing", Blurb = "Reduce repeated buff overlay scans during camera updates. Fade and flash values update each frame. External overlay hooks keep game scans." }, ["PerfReadout"] = new Entry { Category = "Fixes and performance", Title = "Performance readout", Blurb = "Show local frame measurements and entity counts. Logs include real time, simulation time, and physics limits. Use the selected key to show the panel." }, ["FixedTimeCatchup"] = new Entry { Category = "Fixes and performance", Title = "Limit solo physics steps", Blurb = "Limit physics steps per frame during solo stages. Heavy load can slow simulation time. Multiplayer and stage transitions use the previous limit." } }; private static readonly HashSet<string> playerFacing = new HashSet<string> { "Menu/ToggleKey", "Menu/ShowEverySetting", "SkipIntro/AlsoSkipLogoScreen", "Sharing/ShareItems", "Sharing/ShareMoney", "Sharing/ShareWhiteItems", "Sharing/ShareGreenItems", "Sharing/ShareRedItems", "Sharing/ShareBossItems", "Sharing/ShareLunarItems", "Sharing/ShareVoidItems", "Sharing/ShareToDeadPlayers", "Sharing/ShareEquipment", "Sharing/DropReplacedEquipment", "Sharing/SharePrinterOutput", "Sharing/MoneyShareScalar", "EmptyChests/Chests", "EmptyChests/Barrels", "EmptyChests/Multishops", "EmptyChests/ScavengerBags", "EmptyChests/DelaySeconds", "AutoSprint/HoldSprintKeyToWalk", "AutoSprint/HoldOffSeconds", "DpsMeter/ShowDuringRun", "DpsMeter/ToggleKey", "DpsMeter/IncludeMinions", "DpsMeter/CountAllDamage", "DpsMeter/PanelX", "DpsMeter/PanelY", "StatPoints/ShowBadge", "StatPoints/OpenKey", "StatPoints/CursorKey", "StatPoints/BadgeX", "StatPoints/BadgeY", "StatPoints/CharacterLevelsPerPoint", "StatPoints/MaxPointsPerStat", "StatPoints/HealthPerPoint", "StatPoints/RegenerationPerPoint", "StatPoints/DamagePerPoint", "StatPoints/AttackSpeedPerPoint", "StatPoints/CritChancePerPoint", "StatPoints/ArmorPerPoint", "StatPoints/MoveSpeedPerPoint", "FixedTimeCatchup/MaxCatchUpTicks", "BuffStatsFilter/RespectOtherMods", "ModelCameraPass/OverlayFrameStride", "PerfReadout/ShowPanel", "PerfReadout/ToggleKey", "PerfReadout/LogSeconds", "PerfReadout/PanelX", "PerfReadout/PanelY" }; internal static bool IsPlayerFacing(string section, string key) { return playerFacing.Contains(section + "/" + key); } internal static string CategoryOf(string featureKey) { if (!features.TryGetValue(featureKey, out var value)) { return "Gameplay"; } return value.Category; } internal static string TitleOf(string featureKey) { if (!features.TryGetValue(featureKey, out var value)) { return featureKey; } return value.Title; } internal static string BlurbOf(string featureKey, string fallback) { if (!features.TryGetValue(featureKey, out var value)) { return fallback; } return value.Blurb; } } internal sealed class ModMenu : MonoBehaviour { private const float WindowWidth = 1000f; private const float WindowHeight = 620f; private const float ListWidth = 340f; private static ConfigEntry<bool> showEverySetting; private Canvas canvas; private RectTransform listContent; private RectTransform detailContent; private HGTextMeshProUGUI detailTitle; private HGTextMeshProUGUI detailBlurb; private HGTextMeshProUGUI detailState; private Button showEveryButton; private readonly List<SettingRow> rows = new List<SettingRow>(); private readonly List<HGTextMeshProUGUI> listLabels = new List<HGTextMeshProUGUI>(); private readonly List<HGTextMeshProUGUI> listPicks = new List<HGTextMeshProUGUI>(); private readonly List<PatchRegistry.Group> listGroups = new List<PatchRegistry.Group>(); private string selectedKey; private bool listDirty = true; private bool detailDirty = true; private readonly CursorHold cursor = new CursorHold(); private ConfigEntryBase awaitingBind; private SettingRow awaitingRow; private static readonly KeyCode[] bindingKeys = (KeyCode[])Enum.GetValues(typeof(KeyCode)); private readonly List<KeyCode> modifiers = new List<KeyCode>(4); private bool captureReady; private int lastCaptureFrame = -1; private float nextListRefresh; private Run openedRun; internal static ModMenu Instance { get; private set; } internal static bool IsOpen { get; private set; } internal static LocalUser Owner { get; private set; } internal static bool IsBinding { get { if ((Object)(object)Instance != (Object)null) { if (Instance.awaitingBind == null) { return Instance.lastCaptureFrame == Time.frameCount; } return true; } return false; } } internal static ConfigEntry<KeyboardShortcut> Hotkey { get; private set; } internal static void Install(Harmony harmony, GameObject host) { //IL_0019: Unknown result type (might be due to invalid IL or missing references) //IL_0074: Unknown result type (might be due to invalid IL or missing references) //IL_0081: Expected O, but got Unknown //IL_00e4: Unknown result type (might be due to invalid IL or missing references) //IL_00f1: Expected O, but got Unknown Hotkey = Plugin.Cfg.Bind<KeyboardShortcut>("Menu", "ToggleKey", new KeyboardShortcut((KeyCode)285, Array.Empty<KeyCode>()), "Key that opens and closes the settings page."); showEverySetting = Plugin.Cfg.Bind<bool>("Menu", "ShowEverySetting", false, "Also show the fine-tuning values that most players never need."); harmony.Patch((MethodBase)AccessTools.Method(typeof(PlayerCharacterMasterController), "CanSendBodyInput", (Type[])null, (Type[])null), (HarmonyMethod)null, new HarmonyMethod(typeof(ModMenu), "BlockInputPostfix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); Instance = host.AddComponent<ModMenu>(); PatchRegistry.StateChanged += Instance.OnGroupChanged; showEverySetting.SettingChanged += Instance.OnVisibilitySettingChanged; harmony.Patch((MethodBase)AccessTools.Method(typeof(PauseScreenController), "Awake", (Type[])null, (Type[])null), (HarmonyMethod)null, new HarmonyMethod(typeof(ModMenu), "PauseScreenPostfix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } private static void BlockInputPostfix(NetworkUser networkUser, ref bool __result, ref bool onlyAllowMovement) { LocalUser val = (Object.op_Implicit((Object)(object)networkUser) ? networkUser.localUser : null); if (val != null && ((IsOpen && Owner == val) || (StatPointsWindow.IsOpen && StatPointsWindow.Owner == val) || (StatPointsHud.CursorActive && StatPointsHud.CursorOwner == val))) { __result = false; onlyAllowMovement = false; } } private static void PauseScreenPostfix(PauseScreenController __instance) { //IL_0069: Unknown result type (might be due to invalid IL or missing references) //IL_0078: Unknown result type (might be due to invalid IL or missing references) //IL_0087: Unknown result type (might be due to invalid IL or missing references) //IL_0096: Unknown result type (might be due to invalid IL or missing references) GameSkin.Refresh(); if (!GameSkin.Ready || !Object.op_Implicit((Object)(object)__instance.rootPanel) || !Object.op_Implicit((Object)(object)((Component)__instance.rootPanel).GetComponentInParent<Canvas>())) { return; } RectTransform obj = UiFactory.Vertical((Transform)(object)__instance.rootPanel, "LGP_Shortcuts", 8f); UiFactory.Place(obj, new Vector2(1f, 0f), new Vector2(1f, 0f), new Vector2(-24f, 24f), new Vector2(280f, 80f)); UiFactory.Size((Component)(object)UiFactory.Button((Transform)(object)obj, "Settings", "Mod settings", delegate { LocalUser localUser = __instance.localUser; EventSystem current = EventSystem.current; if (((MPEventSystem)(((current is MPEventSystem) ? current : null)?)).localUser == localUser) { Instance?.SetOpen(open: true, localUser); } }), 280f, 34f); UiFactory.Size((Component)(object)UiFactory.Button((Transform)(object)obj, "Points", "Stat points", delegate { LocalUser localUser = __instance.localUser; EventSystem current = EventSystem.current; if (((MPEventSystem)(((current is MPEventSystem) ? current : null)?)).localUser == localUser) { StatPointsWindow.Open(localUser); } }), 280f, 34f); } private void OnGroupChanged(PatchRegistry.Group group) { if (selectedKey == group.Key) { detailDirty = true; } nextListRefresh = 0f; } private void OnVisibilitySettingChanged(object sender, EventArgs args) { listDirty = true; detailDirty = true; } internal static void Close() { Instance?.SetOpen(open: false); } internal static void OpenAt(string featureKey) { if (!((Object)(object)Instance == (Object)null)) { Instance.selectedKey = featureKey; Instance.detailDirty = true; Instance.SetOpen(open: true); } } private void Update() { //IL_0031: Unknown result type (might be due to invalid IL or missing references) //IL_0036: Unknown result type (might be due to invalid IL or missing references) bool flag = awaitingBind != null; if (awaitingBind != null) { lastCaptureFrame = Time.frameCount; CaptureKey(); } else if (Hotkey != null) { KeyboardShortcut value = Hotkey.Value; if (((KeyboardShortcut)(ref value)).IsDown()) { SetOpen(!IsOpen); } } if (!IsOpen) { return; } if (Input.GetKeyDown((KeyCode)27) && !flag) { SetOpen(open: false); return; } if (openedRun != Run.instance || !UiFactory.OwnerExists(Owner)) { SetOpen(open: false); return; } cursor.Set(wanted: true, Owner); GameSkin.Refresh(); if (!GameSkin.Ready) { SetOpen(open: false); return; } if (listDirty) { BuildList(); } if (detailDirty) { BuildDetail(); } foreach (SettingRow row in rows) { row.Update(); } if (Time.unscaledTime >= nextListRefresh) { nextListRefresh = Time.unscaledTime + 0.1f; RefreshListState(); } } private void OnDisable() { SetOpen(open: false); } private void OnDestroy() { PatchRegistry.StateChanged -= OnGroupChanged; showEverySetting.SettingChanged -= OnVisibilitySettingChanged; DisposeRows(); if ((Object)(object)canvas != (Object)null) { Object.Destroy((Object)(object)((Component)canvas).gameObject); } } private void SetOpen(bool open, LocalUser owner = null) { if (open == IsOpen) { return; } if (open) { GameSkin.Refresh(); if (!GameSkin.Ready) { return; } if ((Object)(object)canvas == (Object)null) { try { Build(); } catch (Exception ex) { Plugin.Log.LogError((object)("The settings window failed to open: " + ex)); DisposeRows(); if ((Object)(object)canvas != (Object)null) { Object.Destroy((Object)(object)((Component)canvas).gameObject); } canvas = null; return; } } Owner = (Object.op_Implicit((Object)(object)Run.instance) ? (owner ?? UiFactory.FirstLocalUser()) : null); openedRun = Run.instance; UiFactory.SetOwner(canvas, Owner); StatPointsWindow.Close(); } IsOpen = open; CancelBind(); UiFactory.SetVisible(canvas, open); if (open) { detailDirty = true; UiFactory.Focus(canvas); } else { cursor.Release(); Owner = null; openedRun = null; } } private void CancelBind() { awaitingRow?.SetBindPrompt(waiting: false); awaitingBind = null; awaitingRow = null; modifiers.Clear(); } private void CaptureKey() { //IL_0040: Unknown result type (might be due to invalid IL or missing references) //IL_0041: Unknown result type (might be due to invalid IL or missing references) //IL_0047: Invalid comparison between Unknown and I4 //IL_0054: Unknown result type (might be due to invalid IL or missing references) //IL_0049: Unknown result type (might be due to invalid IL or missing references) //IL_004f: Invalid comparison between Unknown and I4 //IL_0064: Unknown result type (might be due to invalid IL or missing references) //IL_008c: Unknown result type (might be due to invalid IL or missing references) //IL_008e: Unknown result type (might be due to invalid IL or missing references) //IL_00b2: Unknown result type (might be due to invalid IL or missing references) //IL_009d: Unknown result type (might be due to invalid IL or missing references) //IL_00c8: Unknown result type (might be due to invalid IL or missing references) if (Input.GetKeyDown((KeyCode)27)) { CancelBind(); } else if (!captureReady) { captureReady = !Input.GetMouseButton(0); } else { if (!Input.anyKeyDown) { return; } KeyCode[] array = bindingKeys; foreach (KeyCode val in array) { if (((int)val >= 323 && (int)val <= 329) || !Input.GetKeyDown(val) || Array.IndexOf(ShortcutCapture.ModifierKeys, val) >= 0) { continue; } modifiers.Clear(); KeyCode[] modifierKeys = ShortcutCapture.ModifierKeys; foreach (KeyCode val2 in modifierKeys) { if (Input.GetKey(val2)) { modifiers.Add(val2); } } if (ShortcutCapture.TryCreate(val, modifiers, out var shortcut)) { awaitingBind.BoxedValue = shortcut; awaitingRow?.Refresh(); awaitingRow?.SetBindPrompt(waiting: false); CancelBind(); break; } } } } private void Build() { //IL_003a: Unknown result type (might be due to invalid IL or missing references) //IL_0081: Unknown result type (might be due to invalid IL or missing references) //IL_0096: Unknown result type (might be due to invalid IL or missing references) //IL_00ab: Unknown result type (might be due to invalid IL or missing references) //IL_00b6: Unknown result type (might be due to invalid IL or missing references) //IL_00cb: Unknown result type (might be due to invalid IL or missing references) //IL_02c8: Unknown result type (might be due to invalid IL or missing references) canvas = UiFactory.CreateCanvas("LGP_Settings", 200, interactive: true); Image obj = UiFactory.Fill(((Component)canvas).transform, "Dim", new Color(0f, 0f, 0f, 0.66f)); UiFactory.Stretch(((Graphic)obj).rectTransform); ((Graphic)obj).raycastTarget = true; RectTransform obj2 = UiFactory.Panel(((Component)canvas).transform, "Window", GameSkin.PanelKind.Main, opaque: true); obj2.anchorMin = new Vector2(0.5f, 0.5f); obj2.anchorMax = new Vector2(0.5f, 0.5f); obj2.pivot = new Vector2(0.5f, 0.5f); obj2.anchoredPosition = Vector2.zero; obj2.sizeDelta = new Vector2(1000f, 620f); RectTransform obj3 = UiFactory.Vertical((Transform)(object)obj2, "Body", 10f, 18); UiFactory.Stretch(obj3); RectTransform parent = UiFactory.Horizontal((Transform)(object)obj3, "TitleBar", 10f, 34f); UiFactory.Size((Component)(object)UiFactory.Label((Transform)(object)parent, "Title", "LateGamePerformance 1.5.1", GameSkin.TextKind.Header, 26f, (TextAlignmentOptions)513), 800f, 32f); UiFactory.Size((Component)(object)UiFactory.Button((Transform)(object)parent, "Close", "Close", delegate { SetOpen(open: false); }), 120f, 32f); RectTransform obj4 = UiFactory.Horizontal((Transform)(object)obj3, "Columns", 16f, 0f); UiFactory.Size((Component)(object)obj4, 0f, 530f); RectTransform val = UiFactory.Vertical((Transform)(object)obj4, "Left", 8f); UiFactory.Size((Component)(object)val, 340f, 530f); listContent = UiFactory.Scroll((Transform)(object)val, "FeatureList", out var scroll); UiFactory.Size((Component)(object)scroll, 340f, 480f); showEveryButton = UiFactory.Button((Transform)(object)val, "ShowEvery", string.Empty, delegate { showEverySetting.Value = !showEverySetting.Value; listDirty = true; detailDirty = true; }); UiFactory.Size((Component)(object)showEveryButton, 340f, 34f); RectTransform val2 = UiFactory.Vertical((Transform)(object)obj4, "Right", 6f); UiFactory.Size((Component)(object)val2, 608f, 530f); detailTitle = UiFactory.Label((Transform)(object)val2, "DetailTitle", string.Empty, GameSkin.TextKind.Header, 24f, (TextAlignmentOptions)513); UiFactory.Size((Component)(object)detailTitle, 0f, 30f); detailBlurb = UiFactory.Label((Transform)(object)val2, "DetailBlurb", string.Empty, GameSkin.TextKind.Body, 17f, (TextAlignmentOptions)257); ((TMP_Text)detailBlurb).enableWordWrapping = true; UiFactory.Size((Component)(object)detailBlurb, 0f, 44f); detailState = UiFactory.Label((Transform)(object)val2, "DetailState", string.Empty, GameSkin.TextKind.Body, 16f, (TextAlignmentOptions)513); ((Graphic)detailState).color = GameSkin.QuietColor; UiFactory.Size((Component)(object)detailState, 0f, 22f); detailContent = UiFactory.Scroll((Transform)(object)val2, "Detail", out var scroll2); UiFactory.Size((Component)(object)scroll2, 608f, 200f).flexibleHeight = 1f; listDirty = true; detailDirty = true; } private void BuildList() { listDirty = false; Clear(listContent); listLabels.Clear(); listPicks.Clear(); listGroups.Clear(); if (string.IsNullOrEmpty(selectedKey)) { selectedKey = "Menu"; } UiFactory.Size((Component)(object)UiFactory.Button((Transform)(object)listContent, "General", "General settings", delegate { selectedKey = "Menu"; detailDirty = true; }), 340f, 34f); AddCategory("Gameplay"); if (showEverySetting.Value) { AddCategory("Fixes and performance"); } HGTextMeshProUGUI componentInChildren = ((Component)showEveryButton).GetComponentInChildren<HGTextMeshProUGUI>(); if ((Object)(object)componentInChildren != (Object)null) { ((TMP_Text)componentInChildren).text = (showEverySetting.Value ? "Hide fixes and tuning" : "Show fixes and tuning"); } } private void AddCategory(string category) { UiFactory.Size((Component)(object)UiFactory.Label((Transform)(object)listContent, "Header" + category, category.ToUpperInvariant(), GameSkin.TextKind.Header, 17f, (TextAlignmentOptions)513), 0f, 26f); IReadOnlyList<PatchRegistry.Group> groups = PatchRegistry.Groups; for (int i = 0; i < groups.Count; i++) { PatchRegistry.Group obj = groups[i]; if (!(MenuCatalog.CategoryOf(obj.Key) != category)) { if (string.IsNullOrEmpty(selectedKey)) { selectedKey = obj.Key; } RectTransform parent = UiFactory.Horizontal((Transform)(object)listContent, "Row" + obj.Key, 8f, 34f); PatchRegistry.Group captured = obj; Button val = UiFactory.Button((Transform)(object)parent, "Toggle", string.Empty, delegate { PatchRegistry.SetEnabled(captured, !captured.Active); }); UiFactory.Size((Component)(object)val, 62f, 30f); Button obj2 = UiFactory.Button((Transform)(object)parent, "Pick", MenuCatalog.TitleOf(obj.Key), delegate { selectedKey = captured.Key; detailDirty = true; }); UiFactory.Size((Component)(object)obj2, 254f, 30f); HGTextMeshProUGUI componentInChildren = ((Component)obj2).GetComponentInChildren<HGTextMeshProUGUI>(); if ((Object)(object)componentInChildren != (Object)null) { ((TMP_Text)componentInChildren).alignment = (TextAlignmentOptions)513; ((TMP_Text)componentInChildren).fontSize = 17f; } listLabels.Add(((Component)val).GetComponentInChildren<HGTextMeshProUGUI>()); listPicks.Add(componentInChildren); listGroups.Add(obj); } } } private void RefreshListState() { //IL_00ac: Unknown result type (might be due to invalid IL or missing references) //IL_00a5: Unknown result type (might be due to invalid IL or missing references) //IL_004f: Unknown result type (might be due to invalid IL or missing references) //IL_0048: Unknown result type (might be due to invalid IL or missing references) for (int i = 0; i < listGroups.Count && i < listLabels.Count; i++) { HGTextMeshProUGUI val = listLabels[i]; if ((Object)(object)val != (Object)null) { bool active = listGroups[i].Active; ((TMP_Text)val).text = (active ? "ON" : "OFF"); ((Graphic)val).color = (active ? GameSkin.HeaderColor : GameSkin.QuietColor); } if (i < listPicks.Count && (Object)(object)listPicks[i] != (Object)null) { ((Graphic)listPicks[i]).color = ((listGroups[i].Key == selectedKey) ? GameSkin.HeaderColor : GameSkin.BodyColor); } } } private void BuildDetail() { //IL_01ed: Unknown result type (might be due to invalid IL or missing references) //IL_01b2: Unknown result type (might be due to invalid IL or missing references) detailDirty = false; CancelBind(); DisposeRows(); Clear(detailContent); PatchRegistry.Group obj = FindSelected(); bool flag = selectedKey == "Menu"; if (obj == null && !flag) { return; } string text = (flag ? "Menu" : obj.Key); ((TMP_Text)detailTitle).text = (flag ? "General settings" : MenuCatalog.TitleOf(obj.Key)); ((TMP_Text)detailBlurb).text = (flag ? "Set the menu key here. The pause screen also has buttons for this mod." : MenuCatalog.BlurbOf(obj.Key, obj.Description)); UiFactory.Wrap(detailBlurb, 608f); ((TMP_Text)detailState).text = (flag ? string.Empty : (obj.Active ? "Turned on" : "Turned off")); if (!flag && !string.IsNullOrEmpty(obj.Error)) { HGTextMeshProUGUI obj2 = detailState; ((TMP_Text)obj2).text = ((TMP_Text)obj2).text + " - last error: " + obj.Error; } ((TMP_Text)detailState).overflowMode = (TextOverflowModes)1; int num = 0; ConfigEntryBase[] configEntries = Plugin.Cfg.GetConfigEntries(); foreach (ConfigEntryBase val in configEntries) { if (!(val.Definition.Section != text) && (showEverySetting.Value || MenuCatalog.IsPlayerFacing(text, val.Definition.Key))) { rows.Add(SettingRow.Create((Transform)(object)detailContent, val, RequestBind)); num++; } } if (num == 0) { HGTextMeshProUGUI obj3 = UiFactory.Label((Transform)(object)detailContent, "None", "Nothing to set for this one.", GameSkin.TextKind.Body, 17f, (TextAlignmentOptions)513); ((Graphic)obj3).color = GameSkin.QuietColor; UiFactory.Size((Component)(object)obj3, 0f, 26f); } HGTextMeshProUGUI obj4 = UiFactory.Label((Transform)(object)detailContent, "Note", "Changes save by themselves.", GameSkin.TextKind.Body, 15f, (TextAlignmentOptions)513); ((Graphic)obj4).color = GameSkin.QuietColor; UiFactory.Size((Component)(object)obj4, 0f, 24f); } private void RequestBind(ConfigEntryBase entry) { CancelBind(); awaitingBind = entry; captureReady = false; awaitingRow = null; for (int i = 0; i < rows.Count; i++) { bool flag = rows[i].Entry == entry; rows[i].SetBindPrompt(flag); if (flag) { awaitingRow = rows[i]; } } } private PatchRegistry.Group FindSelected() { IReadOnlyList<PatchRegistry.Group> groups = PatchRegistry.Groups; for (int i = 0; i < groups.Count; i++) { if (groups[i].Key == selectedKey) { return groups[i]; } } if (groups.Count <= 0) { return null; } return groups[0]; } private void DisposeRows() { foreach (SettingRow row in rows) { row.Dispose(); } rows.Clear(); } private static void Clear(RectTransform parent) { if (!((Object)(object)parent == (Object)null)) { for (int num = ((Transform)parent).childCount - 1; num >= 0; num--) { Object.Destroy((Object)(object)((Component)((Transform)parent).GetChild(num)).gameObject); } } } } internal static class PatchGroups { internal static void RegisterAll() { PatchRegistry.Register("BuffStatsFilter", "Reduce stat calculations after changes to damage-over-time buffs. Other stat hooks require a compatibility check.", defaultOn: true, BuffStatsFilterPatch.Apply); PatchRegistry.Register("NavPathBudget", "Limit AI path calculations per physics tick. Give each agent a turn and distribute its next calculation time.", defaultOn: true, NavPathBudgetPatch.Apply, NavPathBudgetPatch.Unapply); PatchRegistry.Register("TargetSearchGc", "Reduce allocations in target searches. Each result retains its own array.", defaultOn: true, TargetSearchGcPatch.Apply); PatchRegistry.Register("DamageNumberBatch", "Combine writes to damage-number particles for each manager. Numbers can appear one frame later.", defaultOn: true, DamageNumberBatchPatch.Apply, DamageNumberBatchPatch.Unapply); PatchRegistry.Register("RaycastThrottle", "Limit the frequency of AI obstruction raycasts. Refresh results after large movement or direction changes.", defaultOn: true, RaycastThrottlePatch.Apply, RaycastThrottlePatch.Unapply); PatchRegistry.Register("HealthBarThrottle", "Set the interval for health bar updates. A larger interval delays visual changes.", defaultOn: true, HealthBarThrottlePatch.Apply, HealthBarThrottlePatch.Unapply); PatchRegistry.Register("TeamItemCountCache", "Reuse team item counts briefly where the query permits it. Clear stored counts after relevant changes.", defaultOn: true, TeamItemCountCachePatch.Apply, TeamItemCountCachePatch.Unapply); PatchRegistry.Register("ModelCameraPass", "Reduce repeated camera calculations and buff scans. Keep fade and flash updates separate from the buff interval.", defaultOn: true, ModelCameraPassPatch.Apply, ModelCameraPassPatch.Unapply); PatchRegistry.Register("PerfReadout", "Show frame times and entity counts on the display and in the log.", defaultOn: true, PerfReadoutPatch.Apply, PerfReadoutPatch.Unapply); PatchRegistry.Register("FixedTimeCatchup", "Limit catch-up ticks in a solo run. Under heavy load, this limit slows simulation and game time.", defaultOn: true, FixedTimeCatchupPatch.Apply, FixedTimeCatchupPatch.Unapply); PatchRegistry.Register("SkipIntro", "Skip the startup video after the game completes initialization.", defaultOn: true, SkipIntroPatch.Apply, SkipIntroPatch.Unapply); PatchRegistry.Register("CritOverstack", "Convert crit chance above 100 percent into extra crit damage. This feature changes game balance.", defaultOn: false, CritOverstackPatch.Apply); PatchRegistry.Register("BugFixes", "Add exception guards to selected game methods. The log reports each guard that fails to apply.", defaultOn: true, BugFixesPatch.Apply); PatchRegistry.Register("Sharing", "Share items and money between players. This feature changes game balance. The host controls it. Clients need no mod.", defaultOn: false, SharingPatch.Apply, SharingPatch.Unapply); PatchRegistry.Register("EmptyChests", "Remove opened chests, used barrels, and spent multishops after a delay.", defaultOn: true, EmptyChestsPatch.Apply, EmptyChestsPatch.Unapply); PatchRegistry.Register("AutoSprint", "Start sprint automatically during movement. Hold the sprint key to walk.", defaultOn: true, AutoSprintPatch.Apply, AutoSprintPatch.Unapply); PatchRegistry.Register("DpsMeter", "Show damage for each player during the current collection period.", defaultOn: true, DpsMeterPatch.Apply, DpsMeterPatch.Unapply); PatchRegistry.Register("StatPoints", "Earn points from character levels and spend them on stats. This feature changes game balance and requires compatible peers.", defaultOn: false, StatPointsPatch.Apply, StatPointsPatch.Unapply); } } internal static class PatchRegistry { internal delegate void GroupApplier(Harmony harmony); internal delegate void GroupReverter(); internal sealed class Group { internal string Key; internal string Description; internal bool DefaultOn; internal GroupApplier Apply; internal GroupReverter Revert; internal ConfigEntry<bool> Toggle; internal Harmony Harmony; internal bool Active; internal string Error; internal EventHandler SettingHandler; internal bool Changing; } private static readonly List<Group> groups = new List<Group>(); internal static IReadOnlyList<Group> Groups => groups; internal static event Action<Group> StateChanged; internal static void Register(string key, string description, bool defaultOn, GroupApplier apply, GroupReverter revert = null) { foreach (Group group in groups) { if (group.Key == key) { throw new InvalidOperationException("Duplicate feature: " + key); } } groups.Add(new Group { Key = key, Description = description, DefaultOn = defaultOn, Apply = apply, Revert = revert }); } internal static int ApplyAll() { Shutdown(); Group[] array = groups.ToArray(); groups.Clear(); PatchGroups.RegisterAll(); int num = 0; foreach (Group group in groups) { Group[] array2 = array; foreach (Group obj in array2) { if (obj.Key == group.Key && obj.Harmony != null) { group.Harmony = obj.Harmony; group.Error = obj.Error; break; } } group.Toggle = Plugin.Cfg.Bind<bool>("Features", group.Key, group.DefaultOn, group.Description); Group captured = group; group.SettingHandler = delegate { if (captured.Toggle.Value) { Enable(captured); } else { Disable(captured); } }; group.Toggle.SettingChanged += group.SettingHandler; if (group.Toggle.Value && Enable(group)) { num++; } else if (!group.Toggle.Value && group.Harmony != null) { Disable(group); } } return num; } internal static bool Enable(Group group) { //IL_0040: Unknown result type (might be due to invalid IL or missing references) //IL_004a: Expected O, but got Unknown if (group.Active) { return true; } if (group.Changing) { return false; } group.Changing = true; try { if (group.Harmony != null && !Cleanup(group)) { return false; } group.Harmony = new Harmony("com.tim.LateGamePerformance." + group.Key); group.Apply(group.Harmony); group.Active = true; group.Error = null; return true; } catch (Exception ex) { group.Error = ex.Message; Plugin.Log.LogError((object)$"Feature '{group.Key}' failed: {ex}"); Cleanup(group); return false; } finally { group.Changing = false; Notify(group); } } internal static void Disable(Group group) { if (group.Changing || (!group.Active && group.Harmony == null)) { return; } group.Changing = true; try { if (Cleanup(group)) { group.Error = null; } } finally { group.Changing = false; Notify(group); } } private static bool Cleanup(Group group) { bool flag = true; try { group.Revert?.Invoke(); } catch (Exception ex) { flag = false; group.Error = "Cleanup failed: " + ex.Message; Plugin.Log.LogWarning((object)$"Feature '{group.Key}' cleanup failed: {ex}"); } try { Harmony harmony = group.Harmony; if (harmony != null) { harmony.UnpatchSelf(); } } catch (Exception ex2) { flag = false; group.Error = "Patch removal failed: " + ex2.Message; Plugin.Log.LogWarning((object)$"Feature '{group.Key}' patch removal failed: {ex2}"); } if (flag) { group.Harmony = null; } group.Active = false; return flag; } internal static void SetEnabled(Group group, bool enabled) { if (group.Toggle != null && group.Toggle.Value != enabled) { group.Toggle.Value = enabled; } else if (enabled) { Enable(group); } else { Disable(group); } } internal static void DisableAll() { foreach (Group group in groups) { Disable(group); } } internal static void Shutdown() { DisableAll(); foreach (Group group in groups) { if (group.Toggle != null && group.SettingHandler != null) { group.Toggle.SettingChanged -= group.SettingHandler; group.SettingHandler = null; } } PatchRegistry.StateChanged = null; } private static void Notify(Group group) { if (PatchRegistry.StateChanged == null) { return; } Delegate[] invocationList = PatchRegistry.StateChanged.GetInvocationList(); for (int i = 0; i < invocationList.Length; i++) { Action<Group> action = (Action<Group>)invocationList[i]; try { action(group); } catch (Exception ex) { Plugin.Log.LogWarning((object)("Feature display update failed: " + ex.Message)); } } } } internal sealed class PerfHud : MonoBehaviour { private const int SampleCount = 512; private const float PeakWindowSeconds = 10f; private const float Width = 290f; private const float Pad = 14f; private const float TitleHeight = 28f; private const float LineHeight = 22f; private static readonly string[] Titles = new string[8] { "Frame avg", "FPS", "Peak ~10s", "Peak sampled", "Monsters", "Hurtboxes", "GC0 / s", "Heap" }; private readonly float[] samples = new float[512]; private int sampleIndex; private int sampleFill; private readonly float[] secondPeaks = new float[10]; private int lastSecond = -1; private float runPeakFrame; private int lastGcCount; private float gcStamp; private float gcPerSecond; private float nextLog; private bool wasRunning; private float resetSimTime; private Canvas canvas; private HGTextMeshProUGUI[] values; private RectTransform panel; private int sampleEpoch = int.MinValue; private Run sampledRun; private float nextRefresh; private bool wasVisible; internal static PerfHud Instance { get; private set; } internal static void Install(GameObject host) { Instance = host.AddComponent<PerfHud>(); } private void Update() { //IL_003b: Unknown result type (might be due to invalid IL or missing references) //IL_0040: Unknown result type (might be due to invalid IL or missing references) //IL_00ea: Unknown result type (might be due to invalid IL or missing references) if (sampleEpoch != PerfReadoutPatch.SampleEpoch || sampledRun != Run.instance) { ResetMeasurements(); } if (!PerfReadoutPatch.Active) { Show(visible: false); return; } if (PerfReadoutPatch.ToggleKey != null) { KeyboardShortcut value = PerfReadoutPatch.ToggleKey.Value; if (((KeyboardShortcut)(ref value)).IsDown() && !ModMenu.IsBinding) { PerfReadoutPatch.ShowPanel.Value = !PerfReadoutPatch.ShowPanel.Value; } } Sample(); if (!PerfReadoutPatch.ShowPanel.Value || !Object.op_Implicit((Object)(object)Run.instance) || ModMenu.IsOpen || StatPointsWindow.IsOpen) { Show(visible: false); return; } GameSkin.Refresh(); if (GameSkin.Ready) { if ((Object)(object)canvas == (Object)null) { Build(); } Show(visible: true); UiFactory.Position(panel, PerfReadoutPatch.PanelX.Value, PerfReadoutPatch.PanelY.Value, panel.sizeDelta); if (!wasVisible || Time.unscaledTime >= nextRefresh) { wasVisible = true; nextRefresh = Time.unscaledTime + 0.2f; WriteValues(); } } } private void OnDestroy() { if ((Object)(object)canvas != (Object)null) { Object.Destroy((Object)(object)((Component)canvas).gameObject); } } private void Show(bool visible) { UiFactory.SetVisible(canvas, visible); if (!visible) { wasVisible = false; } } private void ResetMeasurements() { sampleEpoch = PerfReadoutPatch.SampleEpoch; sampledRun = Run.instance; resetSimTime = Time.time; Array.Clear(samples, 0, samples.Length); Array.Clear(secondPeaks, 0, secondPeaks.Length); sampleIndex = 0; sampleFill = 0; lastSecond = -1; runPeakFrame = 0f; lastGcCount = GC.CollectionCount(0); gcStamp = Time.unscaledTime; gcPerSecond = 0f; nextLog = Time.unscaledTime + 2f; wasRunning = Object.op_Implicit((Object)(object)Run.instance); nextRefresh = 0f; } private void Sample() { float unscaledDeltaTime = Time.unscaledDeltaTime; samples[sampleIndex] = unscaledDeltaTime; sampleIndex = (sampleIndex + 1) % 512; if (sampleFill < 512) { sampleFill++; } float unscaledTime = Time.unscaledTime; RollPeaks(Mathf.FloorToInt(unscaledTime), unscaledDeltaTime); if (!Object.op_Implicit((Object)(object)Run.instance)) { runPeakFrame = 0f; } else if (unscaledDeltaTime > runPeakFrame) { runPeakFrame = unscaledDeltaTime; } if (unscaledTime - gcStamp >= 1f) { int num = GC.CollectionCount(0); gcPerSecond = (float)(num - lastGcCount) / (unscaledTime - gcStamp); lastGcCount = num; gcStamp = unscaledTime; } float value = PerfReadoutPatch.LogInterval.Value; bool flag = Object.op_Implicit((Object)(object)Run.instance); if (flag != wasRunning) { wasRunning = flag; nextLog = unscaledTime + 2f; } if (value > 0f && flag && unscaledTime >= nextLog) { nextLog = unscaledTime + value; Plugin.Log.LogInfo((object)LogLine()); } } private void Build() { //IL_0054: Unknown result type (might be due to invalid IL or missing references) //IL_0066: Unknown result type (might be due to invalid IL or missing references) //IL_0078: Unknown result type (might be due to invalid IL or missing references) //IL_0088: Unknown result type (might be due to invalid IL or missing references) //IL_00b1: Unknown result type (might be due to invalid IL or missing references) //IL_0150: Unknown result type (might be due to invalid IL or missing references) canvas = UiFactory.CreateCanvas("LGP_PerfReadout", 99, interactive: false); panel = UiFactory.Panel(((Component)canvas).transform, "Panel"); float value = PerfReadoutPatch.PanelX.Value; float num = 1f - PerfReadoutPatch.PanelY.Value; panel.anchorMin = new Vector2(value, num); panel.anchorMax = new Vector2(value, num); panel.pivot = new Vector2(value, num); panel.anchoredPosition = Vector2.zero; panel.sizeDelta = new Vector2(290f, 56f + (float)Titles.Length * 22f); UiFactory.Row(((TMP_Text)UiFactory.Label((Transform)(object)panel, "Title", "PERFORMANCE", GameSkin.TextKind.Header, 20f, (TextAlignmentOptions)513)).rectTransform, 14f, 262f, 14f, 28f); values = (HGTextMeshProUGUI[])(object)new HGTextMeshProUGUI[Titles.Length]; for (int i = 0; i < Titles.Length; i++) { float y = 42f + (float)i * 22f; HGTextMeshProUGUI obj = UiFactory.Label((Transform)(object)panel, "Label" + i, Titles[i], GameSkin.TextKind.Body, 16f, (TextAlignmentOptions)513); ((Graphic)obj).color = GameSkin.QuietColor; UiFactory.Row(((TMP_Text)obj).rectTransform, 14f, 130f, y, 22f); values[i] = UiFactory.Label((Transform)(object)panel, "Value" + i, string.Empty, GameSkin.TextKind.Header, 17f, (TextAlignmentOptions)516); UiFactory.Row(((TMP_Text)values[i]).rectTransform, 144f, 132f, y, 22f); } } private void WriteValues() { float num = Average(); ((TMP_Text)values[0]).text = $"{num * 1000f:0.0} ms"; ((TMP_Text)values[1]).text = ((num > 0f) ? (1f / num) : 0f).ToString("0", CultureInfo.InvariantCulture); ((TMP_Text)values[2]).text = $"{PeakFrame() * 1000f:0.0} ms"; ((TMP_Text)values[3]).text = $"{runPeakFrame * 1000f:0.0} ms"; ((TMP_Text)values[4]).text = Monsters().ToString(CultureInfo.InvariantCulture); ((TMP_Text)values[5]).text = HurtBox.readOnlyBullseyesList.Count.ToString(CultureInfo.InvariantCulture); ((TMP_Text)values[6]).text = gcPerSecond.ToString("0.0", CultureInfo.InvariantCulture); ((TMP_Text)values[7]).text = $"{GC.GetTotalMemory(forceFullCollection: false) / 1048576} MB"; } private void RollPeaks(int second, float frame) { int num = secondPeaks.Length; if (lastSecond < 0) { lastSecond = second; } else if (second > lastSecond) { int num2 = second - lastSecond; if (num2 >= num) { Array.Clear(secondPeaks, 0, num); } else { for (int i = 1; i <= num2; i++) { secondPeaks[((lastSecond + i) % num + num) % num] = 0f; } } lastSecond = second; } int num3 = (second % num + num) % num; if (frame > secondPeaks[num3]) { secondPeaks[num3] = frame; } } private float PeakFrame() { float num = 0f; for (int i = 0; i < secondPeaks.Length; i++) { if (secondPeaks[i] > num) { num = secondPeaks[i]; } } return num; } private float Average() { if (sampleFill == 0) { return 0f; } float num = 0f; for (int i = 0; i < sampleFill; i++) { num += samples[i]; } return num / (float)sampleFill; } private static int Monsters() { return TeamComponent.GetTeamMembers((TeamIndex)2).Count; } private string LogLine() { float num = Average(); return string.Format(CultureInfo.InvariantCulture, "PERF collection {0} | frame avg {1:0.0} ms ({2:0} fps), peak {3:0.0} ms in {4:0} s, sampled peak {5:0.0} ms | bodies {6}, monsters {7}, models {8}, hurtboxes {9} | gc0 {10:0.0}/s, heap {11:0} MB | real_seconds={12:0.000} sim_seconds={13:0.000} fixed_dt={14:0.000000} max_dt={15:0.000000} catchup={16} scope={17} | {18}", Clock(Time.unscaledTime - PerfReadoutPatch.SamplingSince), num * 1000f, (num > 0f) ? (1f / num) : 0f, PeakFrame() * 1000f, 10f, runPeakFrame * 1000f, CharacterBody.readOnlyInstancesList.Count, Monsters(), InstanceTracker.GetInstancesList<CharacterModel>().Count, HurtBox.readOnlyBullseyesList.Count, gcPerSecond, GC.GetTotalMemory(forceFullCollection: false) / 1048576, Time.unscaledTime - PerfReadoutPatch.SamplingSince, Time.time - resetSimTime, Time.fixedDeltaTime, Time.maximumDeltaTime, FixedTimeCatchupPatch.IsClamped, FixedTimeCatchupPatch.CurrentScope, "The samples measure local rendered frames during active collection. The samples include stage transitions and exclude intervals when this feature is off."); } private static string Clock(float seconds) { int num = Mathf.Max(0, Mathf.FloorToInt(seconds)); return (num / 60).ToString(CultureInfo.InvariantCulture) + ":" + (num % 60).ToString("00", CultureInfo.InvariantCulture); } } [BepInPlugin("com.tim.LateGamePerformance", "LateGamePerformance", "1.5.1")] public sealed class Plugin : BaseUnityPlugin { public const string Guid = "com.tim.LateGamePerformance"; public const string Name = "LateGamePerformance"; public const string Version = "1.5.1"; internal static ManualLogSource Log; internal static ConfigFile Cfg; private Harmony coreHarmony; private void Awake() { //IL_004c: Unknown result type (might be due to invalid IL or missing references) //IL_0056: Expected O, but got Unknown Log = ((BaseUnityPlugin)this).Logger; Cfg = ((BaseUnityPlugin)this).Config; FaultDiagnostics.Reset(); InstallInterface("Network initialization", GameplayNetwork.Initialize); int num = PatchRegistry.ApplyAll(); coreHarmony = new Harmony("com.tim.LateGamePerformance.core"); InstallInterface("Menu", delegate { ModMenu.Install(coreHarmony, ((Component)this).gameObject); }); InstallInterface("Stat badge", delegate { StatPointsHud.Install(((Component)this).gameObject); }); InstallInterface("Damage meter", delegate { DpsHud.Install(((Component)this).gameObject); }); InstallInterface("Stat window", delegate { StatPointsWindow.Install(((Component)this).gameObject); }); InstallInterface("Performance display", delegate { PerfHud.Install(((Component)this).gameObject); }); Log.LogInfo((object)string.Format("{0} {1}: {2} of {3} features active.", "LateGamePerformance", "1.5.1", num, PatchRegistry.Groups.Count)); } private static void InstallInterface(string name, Action install) { try { install(); } catch (Exception arg) { Log.LogError((object)$"{name} initialization failed: {arg}"); } } private void OnDestroy() { InstallInterface("Menu close", ModMenu.Close); InstallInterface("Stat window close", StatPointsWindow.Close); PatchRegistry.Shutdown(); InstallInterface("Network cleanup", GameplayNetwork.Shutdown); try { Harmony obj = coreHarmony; if (obj != null) { obj.UnpatchSelf(); } } catch (Exception arg) { Log.LogWarning((object)$"Interface patch removal failed: {arg}"); } } } internal sealed class StatPointsHud : MonoBehaviour { private const float BadgeSize = 74f; private static ConfigEntry<bool> showBadge; private static ConfigEntry<KeyboardShortcut> openKey; private static ConfigEntry<KeyboardShortcut> cursorKey; private static ConfigEntry<float> badgeX; private static ConfigEntry<float> badgeY; private Canvas canvas; private RectTransform badge; private HGTextMeshProUGUI count; private HGTextMeshProUGUI word; private Image backing; private HGTextMeshProUGUI hint; private int displayedCount = -1; private KeyboardShortcut displayedKey; private float nextBuildAttempt; private bool buildWarning; private readonly CursorHold cursor = new CursorHold(); internal static StatPointsHud Instance { get; private set; } internal static bool CursorActive { get; private set; } internal static LocalUser CursorOwner { get; private set; } internal static void Install(GameObject host) { //IL_0038: Unknown result type (might be due to invalid IL or missing references) //IL_0065: Unknown result type (might be due to invalid IL or missing references) //IL_00a6: Unknown result type (might be due to invalid IL or missing references) //IL_00b0: Expected O, but got Unknown //IL_00e2: Unknown result type (might be due to invalid IL or missing references) //IL_00ec: Expected O, but got Unknown showBadge = Plugin.Cfg.Bind<bool>("StatPoints", "ShowBadge", true, "Show a badge on screen while you have points to spend."); openKey = Plugin.Cfg.Bind<KeyboardShortcut>("StatPoints", "OpenKey", new KeyboardShortcut((KeyCode)284, Array.Empty<KeyCode>()), "Key that opens the stat points window."); cursorKey = Plugin.Cfg.Bind<KeyboardShortcut>("StatPoints", "CursorKey", new KeyboardShortcut((KeyCode)283, Array.Empty<KeyCode>()), "Hold this key to use the stat badge cursor. Your character input stops until you release the key."); badgeX = Plugin.Cfg.Bind<float>("StatPoints", "BadgeX", 0.028f, new ConfigDescription("Badge position across the screen, 0 is left and 1 is right.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 1f), Array.Empty<object>())); badgeY = Plugin.Cfg.Bind<float>("StatPoints", "BadgeY", 0.4f, new ConfigDescription("Badge position down the screen, 0 is top and 1 is bottom.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 1f), Array.Empty<object>())); Instance = host.AddComponent<StatPointsHud>(); } private unsafe void Update() { //IL_0013: Unknown result type (might be due to invalid IL or missing references) //IL_0018: Unknown result type (might be due to invalid IL or missing references) //IL_0192: Unknown result type (might be due to invalid IL or missing references) //IL_0197: Unknown result type (might be due to invalid IL or missing references) //IL_01d5: Unknown result type (might be due to invalid IL or missing references) //IL_0221: Unknown result type (might be due to invalid IL or missing references) //IL_0226: Unknown result type (might be due to invalid IL or missing references) //IL_022a: Unknown result type (might be due to invalid IL or missing references) //IL_028a: Unknown result type (might be due to invalid IL or missing references) //IL_028f: Unknown result type (might be due to invalid IL or missing references) //IL_0296: Unknown result type (might be due to invalid IL or missing references) //IL_029c: Unknown result type (might be due to invalid IL or missing references) //IL_02a2: Unknown result type (might be due to invalid IL or missing references) //IL_02af: Unknown result type (might be due to invalid IL or missing references) //IL_0247: Unknown result type (might be due to invalid IL or missing references) //IL_024c: Unknown result type (might be due to invalid IL or missing references) KeyboardShortcut value; if (StatPointsPatch.Active && openKey != null) { value = openKey.Value; if (((KeyboardShortcut)(ref value)).IsDown() && !ModMenu.IsOpen && !ModMenu.IsBinding && Object.op_Implicit((Object)(object)Run.instance)) { StatPointsWindow.Toggle(UiFactory.FirstLocalUser()); } } int num = Available(); if (showBadge == null || !showBadge.Value || !StatPointsPatch.Active || !StatPointsPatch.CanSpend || num <= 0 || !Object.op_Implicit((Object)(object)Run.instance) || ModMenu.IsOpen || StatPointsWindow.IsOpen) { SetCursor(wanted: false); UiFactory.SetVisible(canvas, visible: false); return; } GameSkin.Refresh(); if (!GameSkin.Ready) { SetCursor(wanted: false); UiFactory.SetVisible(canvas, visible: false); return; } if ((Object)(object)canvas == (Object)null) { if (Time.unscaledTime < nextBuildAttempt) { SetCursor(wanted: false); return; } try { Build(); } catch (Exception ex) { SetCursor(wanted: false); if ((Object)(object)canvas != (Object)null) { Object.Destroy((Object)(object)((Component)canvas).gameObject); } canvas = null; nextBuildAttempt = Time.unscaledTime + 1f; if (!buildWarning) { Plugin.Log.LogError((object)("The stat badge failed to open: " + ex)); } buildWarning = true; return; } buildWarning = false; } UiFactory.SetVisible(canvas, visible: true); UiFactory.SetOwner(canvas, UiFactory.FirstLocalUser()); value = cursorKey.Value; SetCursor(((KeyboardShortcut)(ref value)).IsPressed() && !ModMenu.IsBinding); UiFactory.Position(badge, badgeX.Value, badgeY.Value, new Vector2(74f, 92f)); if (num != displayedCount) { displayedCount = num; ((TMP_Text)count).text = num.ToString(); ((TMP_Text)word).text = ((num == 1) ? "point" : "points"); } value = openKey.Value; object obj = displayedKey; if (!((object)(*(KeyboardShortcut*)(&value))/*cast due to .constrained prefix*/).Equals(obj)) { displayedKey = openKey.Value; ((TMP_Text)hint).text = ((object)Unsafe.As<KeyboardShortcut, KeyboardShortcut>(ref displayedKey)/*cast due to .constrained prefix*/).ToString(); } float num2 = 0.75f + 0.25f * Mathf.Sin(Time.unscaledTime * 3f); Color headerColor = GameSkin.HeaderColor; ((Graphic)backing).color = new Color(headerColor.r, headerColor.g, headerColor.b, 0.2f * num2); } private void OnDestroy() { cursor.Release(); CursorActive = false; CursorOwner = null; if ((Object)(object)canvas != (Object)null) { Object.Destroy((Object)(object)((Component)canvas).gameObject); } } private void OnDisable() { cursor.Release(); CursorActive = false; CursorOwner = null; UiFactory.SetVisible(canvas, visible: false); } private void SetCursor(bool wanted) { CursorActive = wanted; CursorOwner = (wanted ? UiFactory.FirstLocalUser() : null); cursor.Set(wanted, CursorOwner); } private static int Available() { if (!StatPointsPatch.TryGetLocal(out var tracker, out var _)) { return 0; } return tracker.AvailablePoints(); } private void Build() { //IL_005a: Unknown result type (might be due to invalid IL or missing references) //IL_006c: Unknown result type (might be due to invalid IL or missing references) //IL_007e: Unknown result type (might be due to invalid IL or missing references) //IL_008e: Unknown result type (might be due to invalid IL or missing references) //IL_00a8: Unknown result type (might be due to invalid IL or missing references) //IL_00be: Unknown result type (might be due to invalid IL or missing references) //IL_0119: Unknown result type (might be due to invalid IL or missing references) //IL_011f: Expected O, but got Unknown //IL_015f: Unknown result type (might be due to invalid IL or missing references) //IL_0194: Unknown result type (might be due to invalid IL or missing references) //IL_019e: Expected O, but got Unknown //IL_0214: Unknown result type (might be due to invalid IL or missing references) //IL_0253: Unknown result type (might be due to invalid IL or missing references) //IL_0258: Unknown result type (might be due to invalid IL or missing references) //IL_0282: Unknown result type (might be due to invalid IL or missing references) canvas = UiFactory.CreateCanvas("LGP_StatBadge", 98, interactive: true); badge = UiFactory.Panel(((Component)canvas).transform, "Badge"); float value = badgeX.Value; float num = 1f - badgeY.Value; badge.anchorMin = new Vector2(value, num); badge.anchorMax = new Vector2(value, num); badge.pivot = new Vector2(value, num); badge.anchoredPosition = Vector2.zero; badge.sizeDelta = new Vector2(74f, 92f); backing = UiFactory.Fill((Transform)(object)badge, "Pulse", Color.clear); UiFactory.Stretch(((Graphic)backing).rectTransform, 3f, 3f); GameObject val = new GameObject("Open", new Type[3] { typeof(RectTransform), typeof(CanvasRenderer), typeof(Image) }); RectTransform component = val.GetComponent<RectTransform>(); ((Transform)component).SetParent((Transform)(object)badge, false); UiFactory.Stretch(component); Image component2 = val.GetComponent<Image>(); ((Graphic)component2).color = new Color(0f, 0f, 0f, 0.01f); Button button = val.AddComponent<Button>(); ((Selectable)button).targetGraphic = (Graphic)(object)component2; ((UnityEvent)button.onClick).AddListener((UnityAction)delegate { if (UiFactory.CanInteract((Component)(object)button)) { StatPointsWindow.Open(UiFactory.FirstLocalUser()); } }); count = UiFactory.Label((Transform)(object)badge, "Count", "0", GameSkin.TextKind.Header, 30f, (TextAlignmentOptions)514); UiFactory.Row(((TMP_Text)count).rectTransform, 0f, 74f, 6f, 34f); word = UiFactory.Label((Transform)(object)badge, "Word", "points", GameSkin.TextKind.Body, 14f, (TextAlignmentOptions)514); ((Graphic)word).color = GameSkin.QuietColor; UiFactory.Row(((TMP_Text)word).rectTransform, 0f, 74f, 40f, 18f); hint = UiFactory.Label((Transform)(object)badge, "Hint", ((object)openKey.Value/*cast due to .constrained prefix*/).ToString(), GameSkin.TextKind.Body, 13f, (TextAlignmentOptions)514); ((Graphic)hint).color = GameSkin.QuietColor; ((TMP_Text)hint).overflowMode = (TextOverflowModes)1; UiFactory.Row(((TMP_Text)hint).rectTransform, 0f, 74f, 60f, 18f); } } internal sealed class StatPointsWindow : MonoBehaviour { private const float WindowWidth = 520f; private const float Pad = 18f; private Canvas canvas; private StatPointsPanel panel; private readonly CursorHold cursor = new CursorHold(); private float nextRefresh; private Run openedRun; internal static StatPointsWindow Instance { get; private set; } internal static bool IsOpen { get; private set; } internal static LocalUser Owner { get; private set; } internal static void Install(GameObject host) { Instance = host.AddComponent<StatPointsWindow>(); } internal static void Toggle(LocalUser owner = null) { Instance?.SetOpen(!IsOpen, owner); } internal static void Open(LocalUser owner = null) { Instance?.SetOpen(open: true, owner); } internal static void Close() { Instance?.SetOpen(open: false); } private void Update() { if (!IsOpen) { return; } if (Input.GetKeyDown((KeyCode)27) || !Object.op_Implicit((Object)(object)Run.instance) || openedRun != Run.instance || ModMenu.IsOpen || !StatPointsPatch.Active || !UiFactory.OwnerExists(Owner)) { SetOpen(open: false); return; } cursor.Set(wanted: true, Owner); GameSkin.Refresh(); if (!GameSkin.Ready) { SetOpen(open: false); return; } UiFactory.SetVisible(canvas, visible: true); if (Time.unscaledTime >= nextRefresh) { nextRefresh = Time.unscaledTime + 0.1f; panel.Refresh(); } } private void OnDisable() { SetOpen(open: false); } private void OnDestroy() { if ((Object)(object)canvas != (Object)null) { Object.Destroy((Object)(object)((Component)canvas).gameObject); } } private void SetOpen(bool open, LocalUser owner = null) { if (open == IsOpen) { return; } if (open) { LocalUser val = owner ?? UiFactory.FirstLocalUser(); if (!Object.op_Implicit((Object)(object)Run.instance) || !StatPointsPatch.Active || val == null || !UiFactory.OwnerExists(val)) { return; } GameSkin.Refresh(); if (!GameSkin.Ready) { return; } if ((Object)(object)canvas == (Object)null) { try { Build(); } catch (Exception ex) { Plugin.Log.LogError((object)("The stat window failed to open: " + ex)); if ((Object)(object)canvas != (Object)null) { Object.Destroy((Object)(object)((Component)canvas).gameObject); } canvas = null; panel = null; return; } } Owner = val; openedRun = Run.instance; UiFactory.SetOwner(canvas, Owner); ModMenu.Close(); nextRefresh = 0f; } IsOpen = open; UiFactory.SetVisible(canvas, open); if (open) { UiFactory.Focus(canvas); } if (!open) { cursor.Release(); Owner = null; openedRun = null; } } private void Build() { //IL_003a: Unknown result type (might be due to invalid IL or missing references) //IL_0081: Unknown result type (might be due to invalid IL or missing references) //IL_0096: Unknown result type (might be due to invalid IL or missing references) //IL_00ab: Unknown result type (might be due to invalid IL or missing references) //IL_00b6: Unknown result type (might be due to invalid IL or missing references) //IL_00cb: Unknown result type (might be due to invalid IL or missing references) canvas = UiFactory.CreateCanvas("LGP_StatPoints", 190, interactive: true); Image obj = UiFactory.Fill(((Component)canvas).transform, "Dim", new Color(0f, 0f, 0f, 0.55f)); UiFactory.Stretch(((Graphic)obj).rectTransform); ((Graphic)obj).raycastTarget = true; RectTransform obj2 = UiFactory.Panel(((Component)canvas).transform, "Window", GameSkin.PanelKind.Main, opaque: true); obj2.anchorMin = new Vector2(0.5f, 0.5f); obj2.anchorMax = new Vector2(0.5f, 0.5f); obj2.pivot = new Vector2(0.5f, 0.5f); obj2.anchoredPosition = Vector2.zero; obj2.sizeDelta = new Vector2(520f, 480f); RectTransform val = UiFactory.Vertical((Transform)(object)obj2, "Body", 10f, 18); UiFactory.Stretch(val); RectTransform parent = UiFactory.Horizontal((Transform)(object)val, "TitleBar", 10f, 34f); UiFactory.Size((Component)(object)UiFactory.Label((Transform)(object)parent, "Title", "LEVEL UP", GameSkin.TextKind.Header, 26f, (TextAlignmentOptions)513), 340f, 32f); UiFactory.Size((Component)(object)UiFactory.Button((Transform)(object)parent, "Close", "Close", delegate { SetOpen(open: false); }), 110f, 32f); panel = new StatPointsPanel((Transform)(object)val); } } } namespace LateGamePerformance.Ui { internal sealed class CursorHold { private readonly List<MPEventSystem> linked = new List<MPEventSystem>(); internal void Set(bool wanted, LocalUser owner = null) { if (!wanted) { Release(); return; } for (int num = linked.Count - 1; num >= 0; num--) { MPEventSystem val = linked[num]; if ((Object)(object)val == (Object)null || (owner != null && val.localUser != owner) || (owner == null && !val.isCombinedEventSystem)) { if ((Object)(object)val != (Object)null) { int cursorOpenerCount = val.cursorOpenerCount; val.cursorOpenerCount = cursorOpenerCount - 1; cursorOpenerCount = val.cursorOpenerForGamepadCount; val.cursorOpenerForGamepadCount = cursorOpenerCount - 1; } linked.RemoveAt(num); } } ReadOnlyCollection<MPEventSystem> readOnlyInstancesList = MPEventSystem.readOnlyInstancesList; for (int i = 0; i < readOnlyInstancesList.Count; i++) { MPEventSystem val2 = readOnlyInstancesList[i]; if (!((Object)(object)val2 == (Object)null) && !linked.Contains(val2) && (owner == null || val2.localUser == owner) && (owner != null || val2.isCombinedEventSystem)) { int cursorOpenerCount = val2.cursorOpenerCount; val2.cursorOpenerCount = cursorOpenerCount + 1; cursorOpenerCount = val2.cursorOpenerForGamepadCount; val2.cursorOpenerForGamepadCount = cursorOpenerCount + 1; linked.Add(val2); } } } internal void Release() { for (int i = 0; i < linked.Count; i++) { MPEventSystem val = linked[i]; if (!((Object)(object)val == (Object)null)) { int cursorOpenerCount = val.cursorOpenerCount; val.cursorOpenerCount = cursorOpenerCount - 1; cursorOpenerCount = val.cursorOpenerForGamepadCount; val.cursorOpenerForGamepadCount = cursorOpenerCount - 1; } } linked.Clear(); } } internal static class GameSkin { internal enum PanelKind { Main, Header, Detail } internal enum TextKind { Body, Header, Detail } private static UISkinData data; private static float nextSearch; private static bool warned; internal static UISkinData Data { get { if ((Object)(object)data == (Object)null && Time.unscaledTime >= nextSearch) { nextSearch = Time.unscaledTime + 1f; data = Find(); if ((Object)(object)data == (Object)null && !warned) { warned = true; Plugin.Log.LogWarning((object)"No complete UI skin exists. The mod retries once per second. Player control stays available."); } if ((Object)(object)data != (Object)null) { warned = false; } } return data; } } internal static bool Ready => (Object)(object)Data != (Object)null; internal static float ButtonHeight { get { if (!((Object)(object)Data != (Object)null)) { return 32f; } return Mathf.Max(28f, Data.buttonStyle.recommendedHeight); } } internal static Color HeaderColor { get { //IL_002c: Unknown result type (might be due to invalid IL or missing references) //IL_001c: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)Data != (Object)null)) { return new Color(1f, 0.78f, 0.33f); } return Data.headerTextStyle.color; } } internal static Color BodyColor { get { //IL_001d: Unknown result type (might be due to invalid IL or missing references) //IL_000d: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)Data != (Object)null)) { return Color.white; } return Data.bodyTextStyle.color; } } internal static Color DetailColor { get { //IL_002c: Unknown result type (might be due to invalid IL or missing references) //IL_001c: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)Data != (Object)null)) { return new Color(0.6f, 0.64f, 0.71f); } return Data.detailTextStyle.color; } } internal static Color QuietColor { get { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_000c: Unknown result type (might be due to invalid IL or missing references) //IL_0012: Unknown result type (might be due to invalid IL or missing references) //IL_0018: Unknown result type (might be due to invalid IL or missing references) //IL_0024: Unknown result type (might be due to invalid IL or missing references) Color bodyColor = BodyColor; return new Color(bodyColor.r, bodyColor.g, bodyColor.b, bodyColor.a * 0.55f); } } internal static void Refresh() { _ = Data; } private static UISkinData Find() { UISkinData val = null; int num = 0; List<UISkinData> list = new List<UISkinData>(Resources.FindObjectsOfTypeAll<UISkinData>()); BaseSkinController[] array = Resources.FindObjectsOfTypeAll<BaseSkinController>(); for (int i = 0; i < array.Length; i++) { if ((Object)(object)array[i] != (Object)null && (Object)(object)array[i].skinData != (Object)null && !list.Contains(array[i].skinData)) { list.Add(array[i].skinData); } } for (int j = 0; j < list.Count; j++) { int num2 = Score(list[j]); if (num2 > num || (num2 > 0 && num2 == num && string.CompareOrdinal(SkinKey(list[j]), SkinKey(val)) < 0)) { num = num2; val = list[j]; } } if ((Object)(object)val != (Object)null) { Plugin.Log.LogInfo((object)$"UI skin: '{((Object)val).name}' out of {list.Count} candidates, score {num}."); } return val; } private static string SkinKey(UISkinData skin) { //IL_009f: Unknown result type (might be due to invalid IL or missing references) //IL_00bc: Unknown result type (might be due to invalid IL or missing references) //IL_00de: Unknown result type (might be due to invalid IL or missing references) //IL_00fb: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)skin == (Object)null)) { return ((Object)skin).name + "|" + ((Object)skin.bodyTextStyle.font).name + "|" + ((Object)skin.headerTextStyle.font).name + "|" + ((Object)skin.buttonStyle.interactableTextStyle.font).name + "|" + ((Object)skin.mainPanelStyle.sprite).name + "|" + ColorKey(skin.bodyTextStyle.color) + "|" + ColorKey(skin.headerTextStyle.color) + "|" + ColorKey(skin.buttonStyle.interactableTextStyle.color) + "|" + ColorKey(skin.mainPanelStyle.color); } return string.Empty; } private static string ColorKey(Color color) { return color.r.ToString("R", CultureInfo.InvariantCulture) + "," + color.g.ToString("R", CultureInfo.InvariantCulture) + "," + color.b.ToString("R", CultureInfo.InvariantCulture) + "," + color.a.ToString("R", CultureInfo.InvariantCulture); } private static int Score(UISkinData skin) { if ((Object)(object)skin == (Object)null || (Object)(object)skin.bodyTextStyle.font == (Object)null || (Object)(object)skin.headerTextStyle.font == (Object)null || (Object)(object)skin.buttonStyle.interactableTextStyle.font == (Object)null || (Object)(object)skin.mainPanelStyle.sprite == (Object)null || skin.bodyTextStyle.color.a <= 0.5f || skin.headerTextStyle.color.a <= 0.5f || skin.buttonStyle.interactableTextStyle.color.a <= 0.5f || skin.mainPanelStyle.color.a <= 0.2f) { return 0; } int num = 1; if ((Object)(object)skin.bodyTextStyle.font != (Object)null) { num += 4; } if ((Object)(object)skin.headerTextStyle.font != (Object)null) { num += 2; } if (skin.bodyTextStyle.color.a > 0.5f) { num += 4; } if (skin.headerTextStyle.color.a > 0.5f) { num += 2; } if ((Object)(object)skin.mainPanelStyle.sprite != (Object)null) { num += 4; } if (skin.mainPanelStyle.color.a > 0.2f) { num += 2; } if ((Object)(object)skin.buttonStyle.sprite != (Object)null) { num += 2; } return num; } internal static void ApplyPanel(Image image, PanelKind kind) { //IL_0060: Unknown result type (might be due to invalid IL or missing references) //IL_0065: Unknown result type (might be due to invalid IL or missing references) UISkinData val = Data; if (!((Object)(object)val == (Object)null) && !((Object)(object)image == (Object)null)) { switch (kind) { case PanelKind.Header: ((PanelStyle)(ref val.headerPanelStyle)).Apply(image); break; case PanelKind.Detail: ((PanelStyle)(ref val.detailPanelStyle)).Apply(image); break; default: ((PanelStyle)(ref val.mainPanelStyle)).Apply(image); break; } if ((Object)(object)image.sprite != (Object)null) { image.type = (Type)(!(image.sprite.border == Vector4.zero)); } } } internal static void ApplyText(TextMeshProUGUI label, TextKind kind, bool useSkinAlignment = false) { UISkinData val = Data; if (!((Object)(object)val == (Object)null) && !((Object)(object)label == (Object)null)) { switch (kind) { case TextKind.Header: ((TextStyle)(ref val.headerTextStyle)).Apply(label, useSkinAlignment); break; case TextKind.Detail: ((TextStyle)(ref val.detailTextStyle)).Apply(label, useSkinAlignment); break; default: ((TextStyle)(ref val.bodyTextStyle)).Apply(label, useSkinAlignment); break; } } } internal static void ApplyButton(Button button, Image image, TextMeshProUGUI label) { //IL_0011: Unknown result type (might be due to invalid IL or missing references) //IL_0016: Unknown result type (might be due to invalid IL or missing references) //IL_0017: Unknown result type (might be due to invalid IL or missing references) //IL_001c: Unknown result type (might be due to invalid IL or missing references) //IL_0033: Unknown result type (might be due to invalid IL or missing references) //IL_0053: Unknown result type (might be due to invalid IL or missing references) //IL_0073: Unknown result type (might be due to invalid IL or missing references) //IL_0093: Unknown result type (might be due to invalid IL or missing references) //IL_00b3: Unknown result type (might be due to invalid IL or missing references) //IL_00d3: Unknown result type (might be due to invalid IL or missing references) //IL_00f1: Unknown result type (might be due to invalid IL or missing references) UISkinData val = Data; if (!((Object)(object)val == (Object)null)) { ButtonStyle buttonStyle = val.buttonStyle; ColorBlock defaultColorBlock = ColorBlock.defaultColorBlock; ((ColorBlock)(ref defaultColorBlock)).normalColor = new Color(1f, 1f, 1f, 0.1f); ((ColorBlock)(ref defaultColorBlock)).highlightedColor = new Color(1f, 1f, 1f, 0.26f); ((ColorBlock)(ref defaultColorBlock)).pressedColor = new Color(1f, 1f, 1f, 0.4f); ((ColorBlock)(ref defaultColorBlock)).selectedColor = new Color(1f, 1f, 1f, 0.16f); ((ColorBlock)(ref defaultColorBlock)).disabledColor = new Color(1f, 1f, 1f, 0.04f); ((ColorBlock)(ref defaultColorBlock)).fadeDuration = 0.08f; if ((Object)(object)button != (Object)null) { ((Selectable)button).colors = defaultColorBlock; } if ((Object)(object)image != (Object)null) { image.sprite = null; ((Graphic)image).material = null; ((Graphic)image).color = Color.white; } if ((Object)(object)label != (Object)null) { ((TextStyle)(ref buttonStyle.interactableTextStyle)).Apply(label, false); } } } internal static Color PanelColor(PanelKind kind) { //IL_0023: Unknown result type (might be due to invalid IL or missing references) //IL_0039: Unknown result type (might be due to invalid IL or missing references) //IL_0045: Unknown result type (might be due to invalid IL or missing references) //IL_0051: Unknown result type (might be due to invalid IL or missing references) UISkinData val = Data; if ((Object)(object)val == (Object)null) { return new Color(0.06f, 0.07f, 0.09f, 1f); } return (Color)(kind switch { PanelKind.Header => val.headerPanelStyle.color, PanelKind.Detail => val.detailPanelStyle.color, _ => val.mainPanelStyle.color, }); } } internal sealed class SettingRow : IDisposable { private readonly ConfigEntryBase entry; private readonly Button toggleButton; private readonly HGTextMeshProUGUI toggleLabel; private readonly Slider slider; private readonly HGTextMeshProUGUI readout; private bool writing; private bool dirty; private bool binding; private readonly Button reset; internal ConfigEntryBase Entry => entry; internal static SettingRow Create(Transform parent, ConfigEntryBase entry, Action<ConfigEntryBase> onKeyBindRequest) { return new SettingRow(parent, entry, onKeyBindRequest); } private SettingRow(Transform parent, ConfigEntryBase entry, Action<ConfigEntryBase> onKeyBindRequest) { //IL_02f1: Unknown result type (might be due to invalid IL or missing references) this.entry = entry; RectTransform parent2 = UiFactory.Vertical(parent, "Setting_" + entry.Definition.Key, 2f); RectTransform parent3 = UiFactory.Horizontal((Transform)(object)parent2, "Line", 10f, 30f); HGTextMeshProUGUI obj = UiFactory.Label((Transform)(object)parent3, "Name", Spaced(entry.Definition.Key), GameSkin.TextKind.Body, 18f, (TextAlignmentOptions)513); ((TMP_Text)obj).overflowMode = (TextOverflowModes)1; UiFactory.Size((Component)(object)obj, 170f, 28f); Type settingType = entry.SettingType; if (settingType == typeof(bool)) { toggleButton = UiFactory.Button((Transform)(object)parent3, "Toggle", string.Empty, Flip); toggleLabel = ((Component)toggleButton).GetComponentInChildren<HGTextMeshProUGUI>(); UiFactory.Size((Component)(object)toggleButton, 78f, 28f); } else if (settingType == typeof(float) || settingType == typeof(int)) { slider = MakeSlider((Transform)(object)parent3, settingType == typeof(int)); readout = UiFactory.Label((Transform)(object)parent3, "Value", string.Empty, GameSkin.TextKind.Header, 18f, (TextAlignmentOptions)516); UiFactory.Size((Component)(object)readout, 74f, 28f); } else if (settingType == typeof(KeyboardShortcut)) { toggleButton = UiFactory.Button((Transform)(object)parent3, "Bind", string.Empty, delegate { onKeyBindRequest?.Invoke(entry); }); toggleLabel = ((Component)toggleButton).GetComponentInChildren<HGTextMeshProUGUI>(); UiFactory.Size((Component)(object)toggleButton, 150f, 28f); } else if (settingType.IsEnum) { toggleButton = UiFactory.Button((Transform)(object)parent3, "Cycle", string.Empty, CycleEnum); toggleLabel = ((Component)toggleButton).GetComponentInChildren<HGTextMeshProUGUI>(); UiFactory.Size((Component)(object)toggleButton, 170f, 28f); } else { readout = UiFactory.Label((Transform)(object)parent3, "Value", entry.GetSerializedValue(), GameSkin.TextKind.Body, 17f, (TextAlignmentOptions)513); UiFactory.Size((Component)(object)readout, 200f, 28f); } reset = UiFactory.Button((Transform)(object)parent3, "Reset", "Reset", delegate { entry.BoxedValue = entry.DefaultValue; }); UiFactory.Size((Component)(object)reset, 64f, 28f); ConfigDescription description = entry.Description; string text = ((description != null) ? description.Description : null); if (!string.IsNullOrEmpty(text)) { HGTextMeshProUGUI obj2 = UiFactory.Label((Transform)(object)parent2, "Help", text, GameSkin.TextKind.Body, 15f, (TextAlignmentOptions)257); ((Graphic)obj2).color = GameSkin.QuietColor; ((TMP_Text)obj2).enableWordWrapping = true; ((TMP_Text)obj2).overflowMode = (TextOverflowModes)0; UiFactory.Wrap(obj2, 580f); } Plugin.Cfg.SettingChanged += OnChanged; Refresh(); } private Slider MakeSlider(Transform parent, bool whole) { //IL_0018: Unknown result type (might be due to invalid IL or missing references) //IL_001e: Expected O, but got Unknown //IL_0062: Unknown result type (might be due to invalid IL or missing references) //IL_009f: Unknown result type (might be due to invalid IL or missing references) //IL_00cb: Unknown result type (might be due to invalid IL or missing references) //IL_0103: Unknown result type (might be due to invalid IL or missing references) //IL_012f: Unknown result type (might be due to invalid IL or missing references) //IL_0151: Unknown result type (might be due to invalid IL or missing references) GameObject val = new GameObject("Slider", new Type[1] { typeof(RectTransform) }); ((Transform)val.GetComponent<RectTransform>()).SetParent(parent, false); Slider val2 = val.AddComponent<Slider>(); UiFactory.Size((Component)(object)val2, 220f, 24f); Image obj = UiFactory.Fill(val.transform, "Track", new Color(1f, 1f, 1f, 0.14f)); ((Graphic)obj).raycastTarget = true; UiFactory.Stretch(((Graphic)obj).rectTransform, 0f, 9f); RectTransform component = new GameObject("FillArea", new Type[1] { typeof(RectTransform) }).GetComponent<RectTransform>(); ((Transform)component).SetParent(val.transform, false); UiFactory.Stretch(component, 0f, 9f); Image val3 = UiFactory.Fill((Transform)(object)component, "Fill", GameSkin.HeaderColor); UiFactory.Stretch(((Graphic)val3).rectTransform); RectTransform component2 = new GameObject("HandleArea", new Type[1] { typeof(RectTransform) }).GetComponent<RectTransform>(); ((Transform)component2).SetParent(val.transform, false); UiFactory.Stretch(component2); Image val4 = UiFactory.Fill((Transform)(object)component2, "Handle", GameSkin.HeaderColor); ((Graphic)val4).raycastTarget = true; ((Graphic)val4).rectTransform.sizeDelta = new Vector2(12f, 0f); val2.fillRect = ((Graphic)val3).rectTransform; val2.handleRect = ((Graphic)val4).rectTransform; ((Selectable)val2).targetGraphic = (Graphic)(object)val4; val2.wholeNumbers = whole; GetRange(out var min, out var max); val2.minValue = min; val2.maxValue = max; ((UnityEvent<float>)(object)val2.onValueChanged).AddListener((UnityAction<float>)OnSlider); return val2; } private void OnSlider(float value) { if (!writing && UiFactory.CanInteract((Component)(object)slider)) { if (entry.SettingType == typeof(int)) { entry.BoxedValue = Mathf.RoundToInt(value); } else { entry.BoxedValue = value; } WriteReadout(); } } private void Flip() { entry.BoxedValue = !(bool)entry.BoxedValue; Refresh(); } private void CycleEnum() { Type settingType = entry.SettingType; string[] names = Enum.GetNames(settingType); string value = entry.BoxedValue.ToString(); int num = Array.IndexOf(names, value); entry.BoxedValue = Enum.Parse(settingType, names[(num + 1) % names.Length]); Refresh(); } private void GetRange(out float min, out float max) { ConfigDescription description = entry.Description; AcceptableValueBase val = ((description != null) ? description.AcceptableValues : null); if (!(val is AcceptableValueRange<float> val2)) { if (val is AcceptableValueRange<int> val3) { min = val3.MinValue; max = val3.MaxValue; } else { min = 0f; max = ((entry.SettingType == typeof(int)) ? 100f : 10f); } } else { min = val2.MinValue; max = val2.MaxValue; } } internal void Refresh() { //IL_006c: Unknown result type (might be due to invalid IL or missing references) //IL_0065: Unknown result type (might be due to invalid IL or missing references) writing = true; try { if (entry.SettingType == typeof(bool) && (Object)(object)toggleLabel != (Object)null) { bool flag = (bool)entry.BoxedValue; ((TMP_Text)toggleLabel).text = (flag ? "ON" : "OFF"); ((Graphic)toggleLabel).color = (flag ? GameSkin.HeaderColor : GameSkin.QuietColor); } else if (entry.SettingType == typeof(KeyboardShortcut) && (Object)(object)toggleLabel != (Object)null) { ((TMP_Text)toggleLabel).text = (binding ? "press a key" : entry.BoxedValue.ToString()); } else if (entry.SettingType.IsEnum && (Object)(object)toggleLabel != (Object)null) { ((TMP_Text)toggleLabel).text = entry.BoxedValue.ToString(); } else if ((Object)(object)slider != (Object)null) { slider.value = Convert.ToSingle(entry.BoxedValue, CultureInfo.InvariantCulture); WriteReadout(); } } finally { writing = false; } dirty = false; ((Selectable)reset).interactable = !object.Equals(entry.BoxedValue, entry.DefaultValue); } private void OnChanged(object sender, SettingChangedEventArgs args) { if (args.ChangedSetting == entry) { dirty = true; } } internal void Update() { if (dirty) { Refresh(); } } public void Dispose() { Plugin.Cfg.SettingChanged -= OnChanged; } internal void SetBindPrompt(bool waiting) { binding = waiting; if ((Object)(object)toggleLabel != (Object)null && entry.SettingType == typeof(KeyboardShortcut)) { ((TMP_Text)toggleLabel).text = (waiting ? "press a key" : entry.BoxedValue.ToString()); } } private void WriteReadout() { if (!((Object)(object)readout == (Object)null)) { ((TMP_Text)readout).text = ((entry.SettingType == typeof(int)) ? ((int)entry.BoxedValue).ToString(CultureInfo.InvariantCulture) : ((float)entry.BoxedValue).ToString("0.###", CultureInfo.InvariantCulture)); } } private static string Spaced(string key) { StringBuilder stringBuilder = new StringBuilder(key.Length + 6); for (int i = 0; i < key.Length; i++) { char c = key[i]; if (i > 0 && char.IsUpper(c) && !char.IsUpper(key[i - 1])) { stringBuilder.Append(' '); stringBuilder.Append(char.ToLowerInvariant(c)); } else { stringBuilder.Append((i == 0) ? c : char.ToLowerInvariant(c)); } } return stringBuilder.ToString(); } } internal static class ShortcutCapture { internal static readonly KeyCode[] ModifierKeys; internal static bool TryCreate(KeyCode mainKey, IReadOnlyList<KeyCode> heldModifiers, out KeyboardShortcut shortcut) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_000a: Unknown result type (might be due to invalid IL or missing references) //IL_000d: Invalid comparison between Unknown and I4 //IL_000f: Unknown result type (might be due to invalid IL or missing references) //IL_0015: Invalid comparison between Unknown and I4 //IL_0024: Unknown result type (might be due to invalid IL or missing references) //IL_0017: Unknown result type (might be due to invalid IL or missing references) //IL_001d: Invalid comparison between Unknown and I4 //IL_0042: Unknown result type (might be due to invalid IL or missing references) //IL_0047: Unknown result type (might be due to invalid IL or missing references) //IL_004d: Unknown result type (might be due to invalid IL or missing reference