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 PerformanceTracker v1.0.3
PerformanceTracker.dll
Decompiled 2 years agousing System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.Versioning; using System.Security; using System.Security.Permissions; using BepInEx; using BepInEx.Bootstrap; using BepInEx.Configuration; using BepInEx.Logging; using FPSCounter; using HarmonyLib; using JetBrains.Annotations; using Microsoft.CodeAnalysis; using PerformanceTracker.Util; using PerformanceTracker.Util.Helpers; using UnityEngine; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: AssemblyTitle("PerformanceTracker")] [assembly: AssemblyDescription("")] [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("Azumatt")] [assembly: AssemblyProduct("PerformanceTracker")] [assembly: AssemblyCopyright("Copyright © 2022")] [assembly: AssemblyTrademark("")] [assembly: ComVisible(false)] [assembly: Guid("E0E2F92E-557C-4A05-9D89-AA92A0BD75C4")] [assembly: AssemblyFileVersion("1.0.3")] [assembly: TargetFramework(".NETFramework,Version=v4.6.2", FrameworkDisplayName = ".NET Framework 4.6.2")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("1.0.3.0")] [module: UnverifiableCode] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Microsoft.CodeAnalysis.Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Microsoft.CodeAnalysis.Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)] internal sealed class NullableAttribute : Attribute { public readonly byte[] NullableFlags; public NullableAttribute(byte P_0) { NullableFlags = new byte[1] { P_0 }; } public NullableAttribute(byte[] P_0) { NullableFlags = P_0; } } [CompilerGenerated] [Microsoft.CodeAnalysis.Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)] internal sealed class NullableContextAttribute : Attribute { public readonly byte Flag; public NullableContextAttribute(byte P_0) { Flag = P_0; } } } namespace PerformanceTracker { [BepInPlugin("Azumatt.PerformanceTracker", "PerformanceTracker", "1.0.3")] public class PerformanceTrackerPlugin : BaseUnityPlugin { public enum Toggle { On = 1, Off = 0 } public enum CounterColors { White, Black, Outline } private class ConfigurationManagerAttributes { [UsedImplicitly] public int? Order; [UsedImplicitly] public bool? Browsable; [UsedImplicitly] public string? Category; [UsedImplicitly] public Action<ConfigEntryBase>? CustomDrawer; } internal const string ModName = "PerformanceTracker"; internal const string ModVersion = "1.0.3"; internal const string Author = "Azumatt"; internal const string ModGUID = "Azumatt.PerformanceTracker"; private static string ConfigFileName = "Azumatt.PerformanceTracker.cfg"; private static string ConfigFileFullPath; internal static string ConnectionError; private readonly Harmony _harmony = new Harmony("Azumatt.PerformanceTracker"); internal static PerformanceTrackerPlugin ModContext; internal static GameObject pluginGO; public static readonly ManualLogSource PerformanceTrackerLogger; private static ConfigEntry<Toggle> _serverConfigLocked; private static ConfigEntry<KeyboardShortcut> _showCounter; internal static ConfigEntry<CounterColors> _counterColor; internal static ConfigEntry<TextAnchor> _position; internal static ConfigEntry<Toggle> _shown; internal static ConfigEntry<Toggle> _showPluginStats; internal static ConfigEntry<Toggle> _showUnityMethodStats; internal static ConfigEntry<Toggle> _measureMemory; public void Awake() { //IL_002c: Unknown result type (might be due to invalid IL or missing references) ModContext = this; pluginGO = ((Component)this).gameObject; _showCounter = config<KeyboardShortcut>("1 - General", "Toggle counter and reset stats", new KeyboardShortcut((KeyCode)117, (KeyCode[])(object)new KeyCode[1] { (KeyCode)304 }), "Key to enable and disable the plugin."); _shown = config("1 - General", "Enable", Toggle.On, "Monitor performance statistics and show them on the screen. When disabled the plugin has no effect on performance."); _showPluginStats = config("1 - General", "Enable monitoring plugins", Toggle.On, "Count time each plugin takes every frame to execute. Only detects MonoBehaviour event methods, so results might be lower than expected. Has a small performance penalty."); _showUnityMethodStats = config("1 - General", "Show detailed frame stats", Toggle.On, "Show how much time was spent by Unity in each part of the frame, for example how long it took to run all Update methods."); try { MemoryInfo.PROCESS_MEMORY_COUNTERS pROCESS_MEMORY_COUNTERS = MemoryInfo.QueryProcessMemStatus(); MemoryInfo.MEMORYSTATUSEX mEMORYSTATUSEX = MemoryInfo.QuerySystemMemStatus(); if (pROCESS_MEMORY_COUNTERS.WorkingSetSize == 0 || mEMORYSTATUSEX.ullTotalPhys == 0) { throw new IOException("Empty data was returned"); } _measureMemory = config("General", "Show memory and GC stats", Toggle.On, "Show memory usage of the process, free available physical memory and garbage collector statistics (if available)."); } catch (Exception ex) { PerformanceTrackerLogger.LogWarning((object)("Memory statistics are not available - " + ex.Message)); } _position = config<TextAnchor>("Interface", "Screen position", (TextAnchor)8, "Which corner of the screen to display the statistics in."); _counterColor = config("Interface", "Color of the text", CounterColors.White, "Color of the displayed stats. Outline has a performance hit but it always easy to see."); _position.SettingChanged += delegate { UIHelper.UpdateLooks(); }; _counterColor.SettingChanged += delegate { UIHelper.UpdateLooks(); }; _shown.SettingChanged += delegate { UIHelper.UpdateLooks(); Helpers.SetCapturingEnabled(_shown.Value == Toggle.On); }; _showPluginStats.SettingChanged += delegate { Helpers.SetCapturingEnabled(_shown.Value == Toggle.On); }; OnEnable(); Assembly executingAssembly = Assembly.GetExecutingAssembly(); _harmony.PatchAll(executingAssembly); SetupWatcher(); } private void OnEnable() { Helpers.OnEnable(); } private void OnDisable() { Helpers.OnDisable(); } private void Update() { //IL_0005: 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) KeyboardShortcut value = _showCounter.Value; if (((KeyboardShortcut)(ref value)).IsDown()) { _shown.Value = ((_shown.Value == Toggle.Off) ? Toggle.On : Toggle.Off); } } private void OnDestroy() { ((BaseUnityPlugin)this).Config.Save(); Helpers.SetCapturingEnabled(enableCapturing: false); } private void SetupWatcher() { FileSystemWatcher fileSystemWatcher = new FileSystemWatcher(Paths.ConfigPath, ConfigFileName); fileSystemWatcher.Changed += ReadConfigValues; fileSystemWatcher.Created += ReadConfigValues; fileSystemWatcher.Renamed += ReadConfigValues; fileSystemWatcher.IncludeSubdirectories = true; fileSystemWatcher.SynchronizingObject = ThreadingHelper.SynchronizingObject; fileSystemWatcher.EnableRaisingEvents = true; } private void ReadConfigValues(object sender, FileSystemEventArgs e) { if (!File.Exists(ConfigFileFullPath)) { return; } try { PerformanceTrackerLogger.LogDebug((object)"ReadConfigValues called"); ((BaseUnityPlugin)this).Config.Reload(); } catch { PerformanceTrackerLogger.LogError((object)("There was an issue loading your " + ConfigFileName)); PerformanceTrackerLogger.LogError((object)"Please check your config entries for spelling and format!"); } } private ConfigEntry<T> config<T>(string group, string name, T value, ConfigDescription description) { return ((BaseUnityPlugin)this).Config.Bind<T>(group, name, value, description); } private ConfigEntry<T> config<T>(string group, string name, T value, string description) { //IL_000c: Unknown result type (might be due to invalid IL or missing references) //IL_0016: Expected O, but got Unknown return config(group, name, value, new ConfigDescription(description, (AcceptableValueBase)null, Array.Empty<object>())); } static PerformanceTrackerPlugin() { string configPath = Paths.ConfigPath; char directorySeparatorChar = Path.DirectorySeparatorChar; ConfigFileFullPath = configPath + directorySeparatorChar + ConfigFileName; ConnectionError = ""; pluginGO = null; PerformanceTrackerLogger = Logger.CreateLogSource("PerformanceTracker"); _serverConfigLocked = null; } } } namespace PerformanceTracker.Util { internal static class MemoryInfo { [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)] public class MEMORYSTATUSEX { public uint dwLength; public uint dwMemoryLoad; public ulong ullTotalPhys; public ulong ullAvailPhys; public ulong ullTotalPageFile; public ulong ullAvailPageFile; public ulong ullTotalVirtual; public ulong ullAvailVirtual; public ulong ullAvailExtendedVirtual; public MEMORYSTATUSEX() { dwLength = (uint)Marshal.SizeOf(typeof(MEMORYSTATUSEX)); } } [StructLayout(LayoutKind.Sequential, Size = 72)] public class PROCESS_MEMORY_COUNTERS { public uint cb; public uint PageFaultCount; public ulong PeakWorkingSetSize; public ulong WorkingSetSize; public ulong QuotaPeakPagedPoolUsage; public ulong QuotaPagedPoolUsage; public ulong QuotaPeakNonPagedPoolUsage; public ulong QuotaNonPagedPoolUsage; public ulong PagefileUsage; public ulong PeakPagefileUsage; public PROCESS_MEMORY_COUNTERS() { cb = (uint)Marshal.SizeOf(typeof(PROCESS_MEMORY_COUNTERS)); } } private static readonly IntPtr _currentProcessHandle = GetCurrentProcess(); private static readonly MEMORYSTATUSEX _memorystatusex = new MEMORYSTATUSEX(); private static readonly PROCESS_MEMORY_COUNTERS _memoryCounters = new PROCESS_MEMORY_COUNTERS(); public static MEMORYSTATUSEX QuerySystemMemStatus() { if (GlobalMemoryStatusEx(_memorystatusex)) { return _memorystatusex; } throw new Exception("GlobalMemoryStatusEx returned false. Error Code is " + Marshal.GetLastWin32Error()); } public static PROCESS_MEMORY_COUNTERS QueryProcessMemStatus() { if (GetProcessMemoryInfo(_currentProcessHandle, _memoryCounters, _memoryCounters.cb)) { return _memoryCounters; } throw new Exception("GetProcessMemoryInfo returned false. Error Code is " + Marshal.GetLastWin32Error()); } [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)] [return: MarshalAs(UnmanagedType.Bool)] private static extern bool GlobalMemoryStatusEx([In][Out] MEMORYSTATUSEX lpBuffer); [DllImport("kernel32.dll")] private static extern IntPtr GetCurrentProcess(); [DllImport("psapi.dll", SetLastError = true)] private static extern bool GetProcessMemoryInfo(IntPtr hProcess, [In][Out] PROCESS_MEMORY_COUNTERS counters, uint size); } internal class MovingAverage { private readonly int _windowSize; private readonly Queue<long> _samples; private long _sampleAccumulator; public MovingAverage(int windowSize = 11) { _windowSize = windowSize; _samples = new Queue<long>(_windowSize + 1); } public long GetAverage() { return _sampleAccumulator / _samples.Count; } public float GetAverageFloat() { return _sampleAccumulator / _samples.Count; } public void Sample(long newSample) { _sampleAccumulator += newSample; _samples.Enqueue(newSample); if (_samples.Count > _windowSize) { _sampleAccumulator -= _samples.Dequeue(); } } } internal static class PluginCounter { private static readonly Dictionary<BepInPlugin, KeyValuePair<MovingAverage, List<Stopwatch>>> _averages = new Dictionary<BepInPlugin, KeyValuePair<MovingAverage, List<Stopwatch>>>(); private static readonly Dictionary<Type, KeyValuePair<BepInPlugin, Stopwatch>> _timers = new Dictionary<Type, KeyValuePair<BepInPlugin, Stopwatch>>(); private static List<KeyValuePair<string, long>> _sortedList; private static Harmony _harmonyInstance; private static bool _running; private static Action _stopAction; private static readonly WaitForEndOfFrame _waitForEndOfFrame = new WaitForEndOfFrame(); public static List<KeyValuePair<string, long>> SlowPlugins => _sortedList; public static void Start(MonoBehaviour mb, BaseUnityPlugin thisPlugin) { //IL_002e: Unknown result type (might be due to invalid IL or missing references) //IL_0038: Expected O, but got Unknown //IL_01c1: Unknown result type (might be due to invalid IL or missing references) //IL_01dc: Unknown result type (might be due to invalid IL or missing references) //IL_01e9: Expected O, but got Unknown //IL_01e9: Expected O, but got Unknown BaseUnityPlugin thisPlugin2 = thisPlugin; MonoBehaviour mb2 = mb; if (_running) { return; } _running = true; if (_harmonyInstance == null) { _harmonyInstance = new Harmony("Azumatt.PerformanceTracker"); } int num = 0; Type baseType = typeof(MonoBehaviour); string[] array = new string[4] { "FixedUpdate", "Update", "LateUpdate", "OnGUI" }; foreach (BaseUnityPlugin item in Chainloader.Plugins.Where((BaseUnityPlugin x) => (Object)(object)x != (Object)null && (Object)(object)x != (Object)(object)thisPlugin2)) { Stopwatch stopwatch = new Stopwatch(); foreach (Type item2 in (from x in SafeGetTypes(((object)item).GetType().Assembly) where baseType.IsAssignableFrom(x) && !x.IsAbstract select x).ToList()) { string[] array2 = array; foreach (string name in array2) { MethodInfo method = item2.GetMethod(name, BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (method == null) { continue; } if (!_timers.ContainsKey(item2)) { BepInPlugin metadata = item.Info.Metadata; _timers[item2] = new KeyValuePair<BepInPlugin, Stopwatch>(metadata, stopwatch); if (!_averages.TryGetValue(metadata, out KeyValuePair<MovingAverage, List<Stopwatch>> value)) { value = new KeyValuePair<MovingAverage, List<Stopwatch>>(new MovingAverage(60), new List<Stopwatch>()); _averages.Add(metadata, value); } value.Value.Add(stopwatch); } try { _harmonyInstance.Patch((MethodBase)method, new HarmonyMethod(AccessTools.Method(typeof(PluginCounter), "Pre", (Type[])null, (Type[])null)), new HarmonyMethod(AccessTools.Method(typeof(PluginCounter), "Post", (Type[])null, (Type[])null)), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); num++; } catch (Exception ex) { PerformanceTrackerPlugin.PerformanceTrackerLogger.LogError((object)ex); } } } } _sortedList = new List<KeyValuePair<string, long>>(_averages.Count); Coroutine co = mb2.StartCoroutine(CollectLoop()); _stopAction = delegate { mb2.StopCoroutine(co); }; PerformanceTrackerPlugin.PerformanceTrackerLogger.LogDebug((object)$"Attached timers to {num} unity methods in {Chainloader.Plugins.Count} plugins"); } public static void Stop() { if (!_running) { return; } _harmonyInstance.UnpatchSelf(); _running = false; foreach (KeyValuePair<Type, KeyValuePair<BepInPlugin, Stopwatch>> timer in _timers) { timer.Value.Value.Reset(); } _timers.Clear(); _averages.Clear(); _stopAction(); _sortedList = null; } private static IEnumerator CollectLoop() { long num = 100000000 / Stopwatch.Frequency; _ = 1f / ((float)num * 1000f); long cutoffTicks = num * 100; while (true) { yield return _waitForEndOfFrame; _sortedList.Clear(); foreach (KeyValuePair<BepInPlugin, KeyValuePair<MovingAverage, List<Stopwatch>>> average2 in _averages) { long num2 = 0L; int count = average2.Value.Value.Count; for (int i = 0; i < count; i++) { num2 += average2.Value.Value[i].ElapsedTicks; } MovingAverage key = average2.Value.Key; key.Sample(num2); long average = key.GetAverage(); if (average > cutoffTicks) { _sortedList.Add(new KeyValuePair<string, long>(average2.Key.Name, average)); } } foreach (KeyValuePair<Type, KeyValuePair<BepInPlugin, Stopwatch>> timer in _timers) { timer.Value.Value.Reset(); } } } private static void Post(MonoBehaviour __instance, MethodInfo __originalMethod) { if ((FrameCounterHelper.CanProcessOnGui || !(__originalMethod.Name == "OnGUI")) && _timers.TryGetValue(((object)__instance).GetType(), out KeyValuePair<BepInPlugin, Stopwatch> value)) { value.Value.Stop(); } } private static void Pre(MonoBehaviour __instance, MethodInfo __originalMethod) { if ((FrameCounterHelper.CanProcessOnGui || !(__originalMethod.Name == "OnGUI")) && _timers.TryGetValue(((object)__instance).GetType(), out KeyValuePair<BepInPlugin, Stopwatch> value)) { value.Value.Start(); } } private static IEnumerable<Type> SafeGetTypes(Assembly ass) { try { return ass.GetTypes(); } catch (ReflectionTypeLoadException ex) { return ex.Types.Where((Type x) => x != null); } } } internal static class ShadowAndOutline { public static void DrawOutline(Rect rect, string text, GUIStyle style, Color outColor, Color inColor, float size) { //IL_0009: Unknown result type (might be due to invalid IL or missing references) //IL_0014: Unknown result type (might be due to invalid IL or missing references) //IL_001a: Unknown result type (might be due to invalid IL or missing references) //IL_002f: Unknown result type (might be due to invalid IL or missing references) //IL_0047: Unknown result type (might be due to invalid IL or missing references) //IL_006d: Unknown result type (might be due to invalid IL or missing references) //IL_0085: 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_00ae: Unknown result type (might be due to invalid IL or missing references) float num = size * 0.5f; Color color = GUI.color; style.normal.textColor = outColor; GUI.color = outColor; ((Rect)(ref rect)).x = ((Rect)(ref rect)).x - num; GUI.Label(rect, text, style); ((Rect)(ref rect)).x = ((Rect)(ref rect)).x + size; GUI.Label(rect, text, style); ((Rect)(ref rect)).x = ((Rect)(ref rect)).x - num; ((Rect)(ref rect)).y = ((Rect)(ref rect)).y - num; GUI.Label(rect, text, style); ((Rect)(ref rect)).y = ((Rect)(ref rect)).y + size; GUI.Label(rect, text, style); ((Rect)(ref rect)).y = ((Rect)(ref rect)).y - num; style.normal.textColor = inColor; GUI.color = color; GUI.Label(rect, text, style); } public static void DrawShadow(Rect rect, GUIContent content, GUIStyle style, Color txtColor, Color shadowColor, Vector2 direction) { //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_0015: Unknown result type (might be due to invalid IL or missing references) //IL_002a: Unknown result type (might be due to invalid IL or missing references) //IL_0037: Unknown result type (might be due to invalid IL or missing references) //IL_0045: 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_0068: Unknown result type (might be due to invalid IL or missing references) //IL_0075: Unknown result type (might be due to invalid IL or missing references) style.normal.textColor = shadowColor; ((Rect)(ref rect)).x = ((Rect)(ref rect)).x + direction.x; ((Rect)(ref rect)).y = ((Rect)(ref rect)).y + direction.y; GUI.Label(rect, content, style); style.normal.textColor = txtColor; ((Rect)(ref rect)).x = ((Rect)(ref rect)).x - direction.x; ((Rect)(ref rect)).y = ((Rect)(ref rect)).y - direction.y; GUI.Label(rect, content, style); } public static void DrawLayoutShadow(GUIContent content, GUIStyle style, Color txtColor, Color shadowColor, Vector2 direction, params GUILayoutOption[] options) { //IL_0004: Unknown result type (might be due to invalid IL or missing references) //IL_000b: 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_000d: Unknown result type (might be due to invalid IL or missing references) DrawShadow(GUILayoutUtility.GetRect(content, style, options), content, style, txtColor, shadowColor, direction); } public static bool DrawButtonWithShadow(Rect r, GUIContent content, GUIStyle style, float shadowAlpha, Vector2 direction) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Expected O, but got Unknown //IL_002b: Unknown result type (might be due to invalid IL or missing references) //IL_003a: Unknown result type (might be due to invalid IL or missing references) //IL_0059: Unknown result type (might be due to invalid IL or missing references) //IL_004c: Unknown result type (might be due to invalid IL or missing references) //IL_005e: Unknown result type (might be due to invalid IL or missing references) //IL_005f: Unknown result type (might be due to invalid IL or missing references) //IL_0062: 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_0078: Unknown result type (might be due to invalid IL or missing references) GUIStyle val = new GUIStyle(style); val.normal.background = null; val.hover.background = null; val.active.background = null; bool result = GUI.Button(r, content, style); DrawShadow(txtColor: ((Rect)(ref r)).Contains(Event.current.mousePosition) ? val.hover.textColor : val.normal.textColor, rect: r, content: content, style: val, shadowColor: new Color(0f, 0f, 0f, shadowAlpha), direction: direction); return result; } public static bool DrawLayoutButtonWithShadow(GUIContent content, GUIStyle style, float shadowAlpha, Vector2 direction, params GUILayoutOption[] options) { //IL_0004: 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) return DrawButtonWithShadow(GUILayoutUtility.GetRect(content, style, options), content, style, shadowAlpha, direction); } } } namespace PerformanceTracker.Util.Helpers { [DefaultExecutionOrder(int.MinValue)] internal sealed class FrameCounterHelper : MonoBehaviour { [DefaultExecutionOrder(int.MaxValue)] internal sealed class FrameCounterHelper2 : MonoBehaviour { private void LateUpdate() { _lateUpdateTime.Sample(TakeMeasurement()); _onGuiHit = false; CanProcessOnGui = true; } } private static readonly MovingAverage _fixedUpdateTime = new MovingAverage(); private static readonly MovingAverage _updateTime = new MovingAverage(); private static readonly MovingAverage _yieldTime = new MovingAverage(); private static readonly MovingAverage _lateUpdateTime = new MovingAverage(); private static readonly MovingAverage _renderTime = new MovingAverage(); private static readonly MovingAverage _onGuiTime = new MovingAverage(); private static readonly MovingAverage _gcAddedSize = new MovingAverage(60); private static readonly MovingAverage _frameTime = new MovingAverage(); private static Stopwatch _measurementStopwatch; internal static bool CanProcessOnGui; private static bool _onGuiHit; private static readonly WaitForEndOfFrame WaitForEndOfFrame = new WaitForEndOfFrame(); private static readonly KVPluginDataComparer Comparer = new KVPluginDataComparer(); private static long TakeMeasurement() { long elapsedTicks = _measurementStopwatch.ElapsedTicks; _measurementStopwatch.Reset(); _measurementStopwatch.Start(); return elapsedTicks; } private IEnumerator Start() { _measurementStopwatch = new Stopwatch(); Stopwatch totalStopwatch = new Stopwatch(); float nanosecPerTick = 100000000f / (float)Stopwatch.Frequency; float msScale = 1f / (nanosecPerTick * 1000f); long gcPreviousAmount = 0L; while (true) { yield return null; _updateTime.Sample(TakeMeasurement()); yield return WaitForEndOfFrame; if (!_onGuiHit) { _renderTime.Sample(TakeMeasurement()); _onGuiHit = true; } CanProcessOnGui = false; _onGuiTime.Sample(TakeMeasurement()); _measurementStopwatch.Reset(); _frameTime.Sample(totalStopwatch.ElapsedTicks); totalStopwatch.Reset(); totalStopwatch.Start(); long average = _frameTime.GetAverage(); float float_val = 1000000f / ((float)average / nanosecPerTick); UIHelper.fString.Append(float_val, 2u, 2u).Append(" FPS"); if (PerformanceTrackerPlugin._showUnityMethodStats.Value == PerformanceTrackerPlugin.Toggle.On) { long average2 = _fixedUpdateTime.GetAverage(); long average3 = _updateTime.GetAverage(); long average4 = _yieldTime.GetAverage(); long average5 = _lateUpdateTime.GetAverage(); long average6 = _renderTime.GetAverage(); long average7 = _onGuiTime.GetAverage(); long num = average2 + average3 + average4 + average5 + average6 + average7; long num2 = average - num; UIHelper.fString.Append(", ").Append((float)average * msScale, 2u, 2u); UIHelper.fString.Append("ms\nFixed: ").Append((float)average2 * msScale, 2u, 2u); UIHelper.fString.Append("ms\nUpdate: ").Append((float)average3 * msScale, 2u, 2u); UIHelper.fString.Append("ms\nYield/anim: ").Append((float)average4 * msScale, 2u, 2u); UIHelper.fString.Append("ms\nLate: ").Append((float)average5 * msScale, 2u, 2u); UIHelper.fString.Append("ms\nRender/VSync: ").Append((float)average6 * msScale, 2u, 2u); UIHelper.fString.Append("ms\nOnGUI: ").Append((float)average7 * msScale, 2u, 2u); UIHelper.fString.Append("ms\nOther: ").Append((float)num2 * msScale, 2u, 2u).Append("ms"); } if (PerformanceTrackerPlugin._measureMemory != null && PerformanceTrackerPlugin._measureMemory.Value == PerformanceTrackerPlugin.Toggle.On) { ulong num3 = MemoryInfo.QueryProcessMemStatus().WorkingSetSize / 1024 / 1024; ulong num4 = MemoryInfo.QuerySystemMemStatus().ullAvailPhys / 1024 / 1024; UIHelper.fString.Append("\nRAM: ").Append((uint)num3).Append("MB used, "); UIHelper.fString.Append((uint)num4).Append("MB free"); long totalMemory = GC.GetTotalMemory(forceFullCollection: false); if (totalMemory != 0L) { long newSample = totalMemory - gcPreviousAmount; long num5 = totalMemory / 1024 / 1024; _gcAddedSize.Sample(newSample); UIHelper.fString.Append("\nGC: ").Append((int)num5).Append("MB ("); UIHelper.fString.Append(_gcAddedSize.GetAverageFloat() / 1024f, 2u, 4u).Append("KB/s)"); gcPreviousAmount = totalMemory; } int maxGeneration = GC.MaxGeneration; if (maxGeneration > 0) { UIHelper.fString.Append("\nGC hits:"); for (int i = 0; i < maxGeneration; i++) { int int_val = GC.CollectionCount(i); UIHelper.fString.Append(' ').Append(i).Append(':') .Append(int_val); } } } List<KeyValuePair<string, long>> slowPlugins = PluginCounter.SlowPlugins; if (slowPlugins != null) { if (slowPlugins.Count > 0) { slowPlugins.Sort(Comparer); int count = slowPlugins.Count; for (int j = 0; j < count && j < 5; j++) { KeyValuePair<string, long> keyValuePair = slowPlugins[j]; int count2 = ((keyValuePair.Key.Length > 20) ? 20 : keyValuePair.Key.Length); UIHelper.fString.Append("\n[").Append(keyValuePair.Key, 0, count2).Append(": ") .Append((float)keyValuePair.Value * msScale, 1u, 2u) .Append("ms]"); } } else { UIHelper.fString.Append("\nNo slow plugins"); } } UIHelper._frameOutputText = UIHelper.fString.Finalize(); _measurementStopwatch.Reset(); } } private void FixedUpdate() { _measurementStopwatch.Start(); } private void Update() { _fixedUpdateTime.Sample(TakeMeasurement()); } private void LateUpdate() { _yieldTime.Sample(TakeMeasurement()); } private void OnGUI() { //IL_0021: Unknown result type (might be due to invalid IL or missing references) //IL_0027: Invalid comparison between Unknown and I4 if (!_onGuiHit) { _renderTime.Sample(TakeMeasurement()); _onGuiHit = true; } if ((int)Event.current.type == 7) { UIHelper.DrawCounter(); } } } public class Helpers { private static readonly MonoBehaviour[] _helpers = (MonoBehaviour[])(object)new MonoBehaviour[2]; internal static void SetCapturingEnabled(bool enableCapturing) { if (!enableCapturing) { PluginCounter.Stop(); Object.Destroy((Object)(object)_helpers[0]); Object.Destroy((Object)(object)_helpers[1]); return; } if ((Object)(object)_helpers[0] == (Object)null) { _helpers[0] = (MonoBehaviour)(object)PerformanceTrackerPlugin.pluginGO.AddComponent<FrameCounterHelper>(); } if ((Object)(object)_helpers[1] == (Object)null) { _helpers[1] = (MonoBehaviour)(object)PerformanceTrackerPlugin.pluginGO.AddComponent<FrameCounterHelper.FrameCounterHelper2>(); } if (PerformanceTrackerPlugin._showPluginStats.Value == PerformanceTrackerPlugin.Toggle.On) { PluginCounter.Start(_helpers[0], (BaseUnityPlugin)(object)PerformanceTrackerPlugin.ModContext); } else { PluginCounter.Stop(); } } internal static void OnEnable() { ConfigEntry<PerformanceTrackerPlugin.Toggle> shown = PerformanceTrackerPlugin._shown; if (shown != null && shown.Value == PerformanceTrackerPlugin.Toggle.On) { UIHelper.UpdateLooks(); SetCapturingEnabled(enableCapturing: true); } } internal static void OnDisable() { SetCapturingEnabled(enableCapturing: false); } } public class UIHelper { private const int MAX_STRING_SIZE = 499; private static readonly GUIStyle _style = new GUIStyle(); private static Rect _screenRect; private const int ScreenOffset = 10; internal static MutableString fString = new MutableString(499, ignoreOverflow: true); internal static string _frameOutputText; internal static void DrawCounter() { //IL_0031: 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) //IL_001c: Unknown result type (might be due to invalid IL or missing references) //IL_0021: Unknown result type (might be due to invalid IL or missing references) if (PerformanceTrackerPlugin._counterColor.Value == PerformanceTrackerPlugin.CounterColors.Outline) { ShadowAndOutline.DrawOutline(_screenRect, _frameOutputText, _style, Color.black, Color.white, 1.5f); } else { GUI.Label(_screenRect, _frameOutputText, _style); } } internal static void UpdateLooks() { //IL_0016: Unknown result type (might be due to invalid IL or missing references) //IL_0061: Unknown result type (might be due to invalid IL or missing references) //IL_0066: Unknown result type (might be due to invalid IL or missing references) //IL_0075: Unknown result type (might be due to invalid IL or missing references) //IL_0037: Unknown result type (might be due to invalid IL or missing references) if (PerformanceTrackerPlugin._counterColor.Value == PerformanceTrackerPlugin.CounterColors.White) { _style.normal.textColor = Color.white; } if (PerformanceTrackerPlugin._counterColor.Value == PerformanceTrackerPlugin.CounterColors.Black) { _style.normal.textColor = Color.black; } int width = Screen.width; int height = Screen.height; _screenRect = new Rect(10f, 10f, (float)(width - 20), (float)(height - 20)); _style.alignment = PerformanceTrackerPlugin._position.Value; _style.fontSize = height / 65; } } } namespace FPSCounter { internal class KVPluginDataComparer : IComparer<KeyValuePair<string, long>> { public int Compare(KeyValuePair<string, long> val1, KeyValuePair<string, long> val2) { return val2.Value.CompareTo(val1.Value); } } public class MutableString { public enum BaseValue { Binary = 2, Decimal = 10, Hex = 16 } private const uint MAX_DECIMALS = 2u; private const BaseValue DEFAULT_BASE = BaseValue.Decimal; private static readonly char[] DIGITS_LUT = new char[16] { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' }; private int m_Pos; private string m_valueStr; private char m_defaultPadChar = '0'; private readonly bool dontThrow; public char DefaultPadChar { get { return m_defaultPadChar; } set { m_defaultPadChar = value; } } public int Capacity => m_valueStr.Length; public int Length { get { return m_Pos; } set { m_Pos = value; } } public string CurrentRaw => m_valueStr; public MutableString(int size) : this(size, '\0', ignoreOverflow: false) { } public MutableString(int size, bool ignoreOverflow) : this(size, '\0', ignoreOverflow) { } public MutableString(int size, char fillChar) : this(size, fillChar, ignoreOverflow: false) { } public MutableString(int size, char fillChar, bool ignoreOverflow) { if (size < 2) { throw new ArgumentException("Size cannot be 1 or less"); } m_valueStr = new string(fillChar, size); dontThrow = ignoreOverflow; } public string Finalize() { if (m_Pos == 0) { return string.Empty; } int num = m_valueStr.Length - m_Pos; if (num > 0) { repeatChar('\0', num); } m_Pos = 0; return m_valueStr; } public MutableString Append(bool value) { Append(value.ToString()); return this; } public MutableString Append(byte value) { Append((uint)value); return this; } public MutableString Append(sbyte value) { Append((int)value); return this; } public MutableString Append(short value) { Append((int)value); return this; } public MutableString Append(ushort value) { Append((int)value); return this; } public MutableString Append(char[] value, int indx, int count) { if (value == null) { return this; } int num = value.Length; if (count < 1 || indx < 0 || num < count + indx) { return this; } if (num > 1) { AppendInternal(value, indx, count); } else { Append(value[0]); } return this; } public MutableString Append(char[] value) { if (value == null) { return this; } int num = value.Length; if (num > 1) { AppendInternal(value, 0, num); } else { Append(value[0]); } return this; } public MutableString Append(char value) { if (m_Pos >= m_valueStr.Length) { if (dontThrow) { return this; } throw new ArgumentException("Not enough free space to accomodate element!"); } singleChar(value); m_Pos++; return this; } private void AppendInternal(char[] value, int indx, int count) { int num = m_valueStr.Length - m_Pos; if (count > num) { if (!dontThrow) { throw new ArgumentException($"Not enough free space to accomodate {count} elements!"); } } else { charCopy(value, indx, count); m_Pos += count; } } private void AppendInternal(string value, int indx, int count) { int num = m_valueStr.Length - m_Pos; if (count > num) { if (!dontThrow) { throw new ArgumentOutOfRangeException($"Not enough free space to accomodate {count} elements!"); } } else { stringCopy(value, indx, count); m_Pos += count; } } public MutableString Append(string value, int indx, int count) { if (value == null) { return this; } int length = value.Length; if (count < 1 || indx < 0 || length < count + indx) { return this; } if (length > 1) { AppendInternal(value, indx, count); } else { Append(value[0]); } return this; } public MutableString Append(string value) { if (value == null) { return this; } int length = value.Length; if (length > 1) { AppendInternal(value, 0, length); } else { Append(value[0]); } return this; } private unsafe void AppendUINT32(uint uint_val, uint pad_base, char pad_char, bool negative) { int num = CountDigits(uint_val); int num2 = (int)((pad_base > num) ? (pad_base - num) : 0); int num3 = Convert.ToInt32(negative) + num2 + num; int num4 = m_Pos + num3; if (num4 > m_valueStr.Length) { if (!dontThrow) { throw new ArgumentOutOfRangeException($"Not enough free space to accomodate {num3} elements!"); } return; } fixed (char* ptr = m_valueStr) { char* ptr2 = ptr + num4; do { uint num5 = uint_val / 10; *(--ptr2) = (char)(48 + uint_val - num5 * 10); uint_val = num5; } while (uint_val != 0); while (num2 > 0) { *(--ptr2) = pad_char; num2--; } if (negative) { *(--ptr2) = '-'; } } m_Pos = num4; } public MutableString Append(uint uint_val) { AppendUINT32(uint_val, 0u, m_defaultPadChar, negative: false); return this; } public MutableString Append(uint uint_val, uint pad_amount) { AppendUINT32(uint_val, pad_amount, m_defaultPadChar, negative: false); return this; } public MutableString Append(uint uint_val, uint pad_amount, char pad_char) { AppendUINT32(uint_val, pad_amount, pad_char, negative: false); return this; } public MutableString Append(int int_val) { Append(int_val, 0u, m_defaultPadChar); return this; } public MutableString Append(int int_val, uint pad_amount) { Append(int_val, pad_amount, m_defaultPadChar); return this; } public MutableString Append(int int_val, uint pad_base, char pad_char) { bool isNegative; uint positiveEqv = GetPositiveEqv(int_val, out isNegative); AppendUINT32(positiveEqv, pad_base, pad_char, isNegative); return this; } private unsafe void AppendULONG(ulong ulong_val, uint pad_base, char pad_char, bool negative) { int num = CountDigits(ulong_val); int num2 = (int)((pad_base > num) ? (pad_base - num) : 0); int num3 = Convert.ToInt32(negative) + num + num2; int num4 = m_Pos + num3; if (num4 > m_valueStr.Length) { if (!dontThrow) { throw new ArgumentOutOfRangeException($"Not enough free space to accomodate {num3} elements!"); } return; } fixed (char* ptr = m_valueStr) { char* ptr2 = ptr + num4; do { ulong num5 = ulong_val / 10; *(--ptr2) = (char)(48 + ulong_val - num5 * 10); ulong_val = num5; } while (ulong_val != 0L); while (num2 > 0) { *(--ptr2) = pad_char; num2--; } if (negative) { *(--ptr2) = '-'; } } m_Pos = num4; } public MutableString Append(ulong ulong_val) { AppendULONG(ulong_val, 0u, m_defaultPadChar, negative: false); return this; } public MutableString Append(ulong ulong_val, uint pad_amount) { AppendULONG(ulong_val, pad_amount, m_defaultPadChar, negative: false); return this; } public MutableString Append(ulong ulong_val, uint pad_amount, char pad_char) { AppendULONG(ulong_val, pad_amount, pad_char, negative: false); return this; } public MutableString Append(long long_val) { Append(long_val, 0u, m_defaultPadChar); return this; } public MutableString Append(long long_val, uint pad_base) { Append(long_val, pad_base, m_defaultPadChar); return this; } public MutableString Append(long long_val, uint pad_base, char pad_char) { bool flag = long_val < 0; ulong ulong_val = (ulong)(flag ? (-1 - long_val + 1) : long_val); AppendULONG(ulong_val, pad_base, pad_char, flag); return this; } public unsafe MutableString Append(float float_val, uint decimal_places, uint pad_base, char pad_char) { decimal_places = ((decimal_places > 2) ? 2u : decimal_places); bool flag = float_val < 0f; float num = 5f / (float)Math.Pow(10.0, 1 + decimal_places); float num2 = (flag ? (float_val + (0f - num)) : (float_val + num)); if (!IsFinite(float_val)) { if (float_val != float_val) { AppendInternal("NaN", 0, 3); } else { AppendInternal(flag ? "-∞" : "+∞", 0, 2); } return this; } bool isNegative; uint positiveEqv = GetPositiveEqv((int)num2, out isNegative); if (decimal_places == 0) { AppendUINT32(positiveEqv, pad_base, pad_char, flag); return this; } int num3 = CountDigits(positiveEqv); int num4 = num3 + 1 + (int)decimal_places; int num5 = (int)((pad_base > num3) ? (pad_base - num3) : 0); int num6 = Convert.ToInt32(flag) + num5 + num4; int num7 = m_Pos + num6; if (num7 > m_valueStr.Length) { if (dontThrow) { return this; } throw new ArgumentOutOfRangeException($"Not enough free space to accomodate {num6} elements!"); } fixed (char* ptr = m_valueStr) { char* ptr2 = ptr + num7; uint num8 = (uint)(num2 * (float)Math.Pow(10.0, decimal_places)); do { uint num9 = num8 / 10; *(--ptr2) = (char)(48 + num8 - num9 * 10); num8 = num9; decimal_places--; } while (decimal_places != 0); *(--ptr2) = '.'; do { uint num10 = num8 / 10; *(--ptr2) = (char)(48 + num8 - num10 * 10); num8 = num10; num3--; } while (num3 != 0); while (num5 > 0) { *(--ptr2) = pad_char; num5--; } if (flag) { *(--ptr2) = '-'; } } m_Pos = num7; return this; } public MutableString Append(float float_val) { return Append(float_val, 2u, 0u, m_defaultPadChar); } public MutableString Append(float float_val, uint decimal_places) { return Append(float_val, decimal_places, 0u, m_defaultPadChar); } public MutableString Append(float float_val, uint decimal_places, uint pad_amount) { return Append(float_val, decimal_places, pad_amount, m_defaultPadChar); } private static uint GetPositiveEqv(int val, out bool isNegative) { isNegative = val < 0; if (!isNegative) { return (uint)val; } return (uint)(-1 - val + 1); } private static int CountDigits(ulong value) { int num = 1; uint num2; if (value >= 10000000) { if (value >= 100000000000000L) { num2 = (uint)(value / 100000000000000L); num += 14; } else { num2 = (uint)(value / 10000000); num += 7; } } else { num2 = (uint)value; } if (num2 >= 10) { num = ((num2 < 100) ? (num + 1) : ((num2 < 1000) ? (num + 2) : ((num2 < 10000) ? (num + 3) : ((num2 < 100000) ? (num + 4) : ((num2 >= 1000000) ? (num + 6) : (num + 5)))))); } return num; } private static int CountDigits(uint value) { int num = 1; if (value >= 100000) { value /= 100000; num += 5; } if (value >= 10) { num = ((value < 100) ? (num + 1) : ((value < 1000) ? (num + 2) : ((value >= 10000) ? (num + 4) : (num + 3)))); } return num; } private static int CountDecimalTrailingZeros(uint value, out uint valueWithoutTrailingZeros) { int num = 0; if (value != 0) { while (true) { uint num2 = value / 10; if (value != num2 * 10) { break; } value = num2; num++; } } valueWithoutTrailingZeros = value; return num; } private static uint Low32(ulong value) { return (uint)value; } private static uint High32(ulong value) { return (uint)((value & 0xFFFFFFFF00000000uL) >> 32); } private unsafe static int SingleToInt32Bits(float value) { return *(int*)(&value); } private static bool IsFinite(float f) { return (SingleToInt32Bits(f) & 0x7FFFFFFF) < 2139095040; } private unsafe void stringCopy(string value, int indx, int charCount) { fixed (char* ptr = m_valueStr) { fixed (char* ptr2 = value) { wstrCpy(ptr + m_Pos, ptr2 + indx, charCount); } } } private unsafe void charCopy(char[] value, int indx, int charCount) { fixed (char* ptr = m_valueStr) { fixed (char* ptr2 = value) { wstrCpy(ptr + m_Pos, ptr2 + indx, charCount); } } } private unsafe void singleChar(char value) { fixed (char* ptr = m_valueStr) { ptr[m_Pos] = value; } } private unsafe void repeatChar(char value, int count) { int num = m_Pos + count; fixed (char* ptr = m_valueStr) { for (int i = m_Pos; i < num; i++) { ptr[i] = value; } } m_Pos = num; } private unsafe void rawCopy(char* dest, char* src, int charCount) { for (int i = 0; i < charCount; i++) { dest[i] = src[i]; } } private unsafe static void wstrCpy(char* dmem, char* smem, int charCount) { if (((uint)(int)dmem & 2u) != 0) { *dmem = *smem; dmem++; smem++; charCount--; } while (charCount >= 8) { *(int*)dmem = *(int*)smem; *(int*)(dmem + 2) = *(int*)(smem + 2); *(int*)(dmem + 4) = *(int*)(smem + 4); *(int*)(dmem + 6) = *(int*)(smem + 6); dmem += 8; smem += 8; charCount -= 8; } if (((uint)charCount & 4u) != 0) { *(int*)dmem = *(int*)smem; *(int*)(dmem + 2) = *(int*)(smem + 2); dmem += 4; smem += 4; } if (((uint)charCount & 2u) != 0) { *(int*)dmem = *(int*)smem; dmem += 2; smem += 2; } if (((uint)charCount & (true ? 1u : 0u)) != 0) { *dmem = *smem; } } } }