using 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;
}
}
}
}