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 TickProfiler v0.2.5
MagiCorp.ValheimTickProfiler.dll
Decompiled 8 hours agousing System; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.IO; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Text; using System.Threading; using BepInEx; using BepInEx.Bootstrap; using BepInEx.Configuration; using BepInEx.Logging; using HarmonyLib; using UnityEngine; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: TargetFramework(".NETFramework,Version=v4.8", FrameworkDisplayName = ".NET Framework 4.8")] [assembly: AssemblyCompany("MagiCorp.ValheimTickProfiler")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: AssemblyInformationalVersion("1.0.0+8a7348b5dbdb8712868f3841af346c25901bdbff")] [assembly: AssemblyProduct("MagiCorp.ValheimTickProfiler")] [assembly: AssemblyTitle("MagiCorp.ValheimTickProfiler")] [assembly: AssemblyVersion("1.0.0.0")] namespace MagiCorp.ValheimTickProfiler; [BepInPlugin("magicorp.valheim.tickprofiler", "MagiCorp Valheim Tick Profiler", "0.2.5")] public sealed class TickProfilerPlugin : BaseUnityPlugin { private readonly struct TargetSpec { public readonly string TypeName; public readonly string MethodName; public readonly string Label; public readonly bool Deep; public TargetSpec(string typeName, string methodName, string label, bool deep) { TypeName = typeName; MethodName = methodName; Label = label; Deep = deep; } } private struct ScopeFrame { public Metric Metric; public long StartTicks; public long ChildTicks; } private sealed class Metric { public readonly string Name; public readonly string Kind; private long _calls; private long _selfTicks; private long _inclusiveTicks; private long _maxInclusiveTicks; public Metric(string name, string kind) { Name = name; Kind = kind; } public void Add(long selfTicks, long inclusiveTicks) { Interlocked.Increment(ref _calls); Interlocked.Add(ref _selfTicks, selfTicks); Interlocked.Add(ref _inclusiveTicks, inclusiveTicks); long num = Volatile.Read(in _maxInclusiveTicks); while (inclusiveTicks > num) { long num2 = Interlocked.CompareExchange(ref _maxInclusiveTicks, inclusiveTicks, num); if (num2 != num) { num = num2; continue; } break; } } public MetricRaw SnapshotAndReset() { return new MetricRaw(Interlocked.Exchange(ref _calls, 0L), Interlocked.Exchange(ref _selfTicks, 0L), Interlocked.Exchange(ref _inclusiveTicks, 0L), Interlocked.Exchange(ref _maxInclusiveTicks, 0L)); } } private readonly struct MetricRaw { public readonly long Calls; public readonly long SelfTicks; public readonly long InclusiveTicks; public readonly long MaxInclusiveTicks; public MetricRaw(long calls, long selfTicks, long inclusiveTicks, long maxInclusiveTicks) { Calls = calls; SelfTicks = selfTicks; InclusiveTicks = inclusiveTicks; MaxInclusiveTicks = maxInclusiveTicks; } } private sealed class MetricSnapshot { public string Name; public string Kind; public double SelfMsPerSec; public double InclusiveMsPerSec; public double CpuPct; public double CallsPerSec; public double AvgSelfUs; public double MaxInclusiveMs; public static MetricSnapshot From(Metric metric, MetricRaw raw, double seconds) { double num = ToMilliseconds(raw.SelfTicks); return new MetricSnapshot { Name = metric.Name, Kind = metric.Kind, SelfMsPerSec = num / seconds, InclusiveMsPerSec = ToMilliseconds(raw.InclusiveTicks) / seconds, CpuPct = num / (seconds * 1000.0) * 100.0, CallsPerSec = (double)raw.Calls / seconds, AvgSelfUs = ((raw.Calls > 0) ? (num * 1000.0 / (double)raw.Calls) : 0.0), MaxInclusiveMs = ToMilliseconds(raw.MaxInclusiveTicks) }; } } private sealed class GapStats { private readonly double[] _samples; private int _sampleCount; private int _next; private long _count; private double _sum; private double _max; private long _over20; private long _over40; private long _over100; public GapStats(int capacity) { _samples = new double[capacity]; } public void Add(double ms) { _count++; _sum += ms; if (ms > _max) { _max = ms; } if (ms > 20.0) { _over20++; } if (ms > 40.0) { _over40++; } if (ms > 100.0) { _over100++; } _samples[_next++] = ms; if (_next == _samples.Length) { _next = 0; } if (_sampleCount < _samples.Length) { _sampleCount++; } } public GapSnapshot SnapshotAndReset() { double p95Ms = 0.0; if (_sampleCount > 0) { double[] array = new double[_sampleCount]; Array.Copy(_samples, array, _sampleCount); Array.Sort(array); int val = Math.Min(array.Length - 1, (int)Math.Ceiling((double)array.Length * 0.95) - 1); p95Ms = array[Math.Max(0, val)]; } GapSnapshot result = new GapSnapshot { Count = _count, AvgMs = ((_count > 0) ? (_sum / (double)_count) : 0.0), P95Ms = p95Ms, MaxMs = _max, Over20Ms = _over20, Over40Ms = _over40, Over100Ms = _over100 }; _sampleCount = 0; _next = 0; _count = 0L; _sum = 0.0; _max = 0.0; _over20 = 0L; _over40 = 0L; _over100 = 0L; return result; } } private struct GapSnapshot { public long Count; public double AvgMs; public double P95Ms; public double MaxMs; public long Over20Ms; public long Over40Ms; public long Over100Ms; } private sealed class ReportSnapshot { public DateTime TimestampUtc; public bool Requested; public double WallSeconds; public double UpdateHz; public double FixedHz; public double NominalFixedHz; public double FixedDeltaMs; public double TimeScale; public GapSnapshot UpdateGap; public GapSnapshot FixedGap; public double CpuOneCorePct; public double CpuVmPct; public double ManagedMiB; public int Gc0; public int Gc1; public int Gc2; public int PatchedMethods; public List<MetricSnapshot> Metrics; public string[] PatchFailures; } public const string PluginGuid = "magicorp.valheim.tickprofiler"; public const string PluginName = "MagiCorp Valheim Tick Profiler"; public const string PluginVersion = "0.2.5"; private static readonly long Frequency = Stopwatch.Frequency; private static readonly Dictionary<MethodBase, Metric> MethodMetrics = new Dictionary<MethodBase, Metric>(); private static readonly List<Metric> Metrics = new List<Metric>(); private static Harmony _harmony; private static ManualLogSource _log; private static bool _profilingEnabled; private bool _serverMode; private bool _rpcRegistered; private readonly HashSet<long> _overlayPeers = new HashSet<long>(); private string _latestOverlayText = string.Empty; private const string RpcHello = "MagiCorp_TickProfiler_Hello_v1"; private const string RpcSnapshot = "MagiCorp_TickProfiler_Snapshot_v1"; [ThreadStatic] private static ScopeFrame[] _scopeStack; [ThreadStatic] private static int _scopeDepth; private ConfigEntry<bool> _enabled; private ConfigEntry<float> _reportInterval; private ConfigEntry<int> _topConsumers; private ConfigEntry<bool> _deepGameProfiling; private ConfigEntry<bool> _profileModPatches; private ConfigEntry<bool> _logPeriodicSummary; private ConfigEntry<bool> _writeStatusJson; private ConfigEntry<float> _spikeThresholdMs; private readonly GapStats _updateGaps = new GapStats(1024); private readonly GapStats _fixedGaps = new GapStats(1024); private readonly List<string> _patchFailures = new List<string>(); private Process _process; private TimeSpan _lastCpu; private long _lastReportTicks; private long _lastUpdateTicks; private long _lastFixedTicks; private long _updates; private long _fixedUpdates; private int _lastGc0; private int _lastGc1; private int _lastGc2; private string _statusPath; private string _textPath; private string _requestPath; private static readonly TargetSpec[] CoreTargets = new TargetSpec[38] { new TargetSpec("Game", "Update", "Game.Update", deep: false), new TargetSpec("Game", "FixedUpdate", "Game.FixedUpdate", deep: false), new TargetSpec("ZNet", "Update", "Network/ZNet.Update", deep: false), new TargetSpec("ZNet", "FixedUpdate", "Network/ZNet.FixedUpdate", deep: false), new TargetSpec("ZNet", "LateUpdate", "Network/ZNet.LateUpdate", deep: false), new TargetSpec("ZNet", "UpdatePeers", "Network/ZNet.UpdatePeers", deep: false), new TargetSpec("ZNet", "UpdateSave", "World/ZNet.UpdateSave", deep: false), new TargetSpec("ZDOMan", "Update", "Network/ZDOMan.Update", deep: false), new TargetSpec("ZDOMan", "SendZDOToPeers2", "Network/ZDOMan.SendZDOToPeers2", deep: false), new TargetSpec("ZDOMan", "SendZDOs", "Network/ZDOMan.SendZDOs", deep: false), new TargetSpec("ZDOMan", "CreateSyncList", "Network/ZDOMan.CreateSyncList", deep: false), new TargetSpec("ZDOMan", "RPC_ZDOData", "Network/ZDOMan.RPC_ZDOData", deep: false), new TargetSpec("ZNetScene", "Update", "World/ZNetScene.Update", deep: false), new TargetSpec("ZNetScene", "CreateDestroyObjects", "World/ZNetScene.CreateDestroyObjects", deep: false), new TargetSpec("ZoneSystem", "Update", "World/ZoneSystem.Update", deep: false), new TargetSpec("SpawnSystem", "UpdateSpawning", "World/SpawnSystem.UpdateSpawning", deep: false), new TargetSpec("EnvMan", "Update", "World/EnvMan.Update", deep: false), new TargetSpec("EnvMan", "FixedUpdate", "World/EnvMan.FixedUpdate", deep: false), new TargetSpec("Character", "CustomFixedUpdate", "Entities/Character.CustomFixedUpdate", deep: true), new TargetSpec("MonsterAI", "UpdateAI", "AI/MonsterAI.UpdateAI", deep: true), new TargetSpec("BaseAI", "UpdateAI", "AI/BaseAI.UpdateAI", deep: true), new TargetSpec("AnimalAI", "UpdateAI", "AI/AnimalAI.UpdateAI", deep: true), new TargetSpec("ZSyncTransform", "CustomFixedUpdate", "Network/ZSyncTransform.CustomFixedUpdate", deep: true), new TargetSpec("ZSyncTransform", "OwnerSync", "Network/ZSyncTransform.OwnerSync", deep: true), new TargetSpec("ZSyncTransform", "ClientSync", "Network/ZSyncTransform.ClientSync", deep: true), new TargetSpec("ZSyncAnimation", "CustomFixedUpdate", "Network/ZSyncAnimation.CustomFixedUpdate", deep: true), new TargetSpec("Projectile", "FixedUpdate", "Entities/Projectile.FixedUpdate", deep: true), new TargetSpec("Ship", "CustomFixedUpdate", "Entities/Ship.CustomFixedUpdate", deep: true), new TargetSpec("Fish", "CustomFixedUpdate", "Entities/Fish.CustomFixedUpdate", deep: true), new TargetSpec("ItemDrop", "SlowUpdate", "Items/ItemDrop.SlowUpdate", deep: true), new TargetSpec("ItemDrop", "PickupUpdate", "Items/ItemDrop.PickupUpdate", deep: true), new TargetSpec("ItemDrop", "EatUpdate", "Items/ItemDrop.EatUpdate", deep: true), new TargetSpec("WearNTear", "UpdateWear", "Build/WearNTear.UpdateWear", deep: true), new TargetSpec("WearNTear", "UpdateSupport", "Build/WearNTear.UpdateSupport", deep: true), new TargetSpec("Smelter", "UpdateSmelter", "Production/Smelter.UpdateSmelter", deep: true), new TargetSpec("LootSpawner", "UpdateSpawner", "World/LootSpawner.UpdateSpawner", deep: true), new TargetSpec("Heightmap", "CustomLateUpdate", "World/Heightmap.CustomLateUpdate", deep: true), new TargetSpec("ClutterSystem", "LateUpdate", "World/ClutterSystem.LateUpdate", deep: true) }; private void Awake() { //IL_0214: Unknown result type (might be due to invalid IL or missing references) //IL_021e: Expected O, but got Unknown _log = ((BaseUnityPlugin)this).Logger; _serverMode = IsDedicatedServerProcess(); if (!_serverMode) { _profilingEnabled = false; ((BaseUnityPlugin)this).Logger.LogInfo((object)"MagiCorp Valheim Tick Profiler 0.2.5: client process detected; server profiler disabled. Install the companion client overlay from the same package for F8 UI."); return; } _enabled = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "Enabled", true, "Enable collection. Patches remain installed but become a fast no-op when disabled."); _reportInterval = ((BaseUnityPlugin)this).Config.Bind<float>("General", "ReportIntervalSeconds", 10f, "Snapshot/report interval in seconds."); _topConsumers = ((BaseUnityPlugin)this).Config.Bind<int>("General", "TopConsumers", 12, "Number of consumers written to the BepInEx summary."); _deepGameProfiling = ((BaseUnityPlugin)this).Config.Bind<bool>("Profiling", "DeepGameProfiling", true, "Profile per-entity AI, sync, item, building and production hot paths."); _profileModPatches = ((BaseUnityPlugin)this).Config.Bind<bool>("Profiling", "ProfileModHarmonyMethods", true, "Profile Harmony Prefix/Postfix/Finalizer methods belonging to other BepInEx plugins."); _logPeriodicSummary = ((BaseUnityPlugin)this).Config.Bind<bool>("Output", "LogPeriodicSummary", true, "Write a compact summary to BepInEx/LogOutput.log each interval."); _writeStatusJson = ((BaseUnityPlugin)this).Config.Bind<bool>("Output", "WriteStatusJson", true, "Refresh BepInEx/tickprofiler-status.json each interval."); _spikeThresholdMs = ((BaseUnityPlugin)this).Config.Bind<float>("Output", "SpikeThresholdMs", 20f, "Mark a consumer as spiking when its worst observed call exceeds this many milliseconds."); _profilingEnabled = _enabled.Value; _enabled.SettingChanged += delegate { _profilingEnabled = _enabled.Value; }; _statusPath = Path.Combine(Paths.BepInExRootPath, "tickprofiler-status.json"); _textPath = Path.Combine(Paths.BepInExRootPath, "tickprofiler-latest.txt"); _requestPath = Path.Combine(Paths.BepInExRootPath, "tickprofiler.request"); _process = Process.GetCurrentProcess(); _lastCpu = _process.TotalProcessorTime; _lastGc0 = GC.CollectionCount(0); _lastGc1 = GC.CollectionCount(1); _lastGc2 = GC.CollectionCount(2); _lastReportTicks = Stopwatch.GetTimestamp(); _lastUpdateTicks = _lastReportTicks; _lastFixedTicks = _lastReportTicks; _harmony = new Harmony("magicorp.valheim.tickprofiler"); PatchGameTargets(); ((BaseUnityPlugin)this).Logger.LogInfo((object)string.Format("{0} {1}: patched {2} game methods; deep={3}", "MagiCorp Valheim Tick Profiler", "0.2.5", MethodMetrics.Count, _deepGameProfiling.Value)); } private void Start() { if (_serverMode) { TryRegisterProfilerRpc(); if (_profileModPatches.Value) { ProfileLoadedMods(); ((BaseUnityPlugin)this).Logger.LogInfo((object)string.Format("{0}: total instrumented methods after mod scan: {1}", "MagiCorp Valheim Tick Profiler", MethodMetrics.Count)); } } } private void OnDestroy() { try { Harmony harmony = _harmony; if (harmony != null) { harmony.UnpatchSelf(); } } catch { } } private void Update() { if (!_serverMode) { return; } TryRegisterProfilerRpc(); if (!_profilingEnabled) { return; } long timestamp = Stopwatch.GetTimestamp(); _updates++; if (_lastUpdateTicks != 0L) { _updateGaps.Add(ToMilliseconds(timestamp - _lastUpdateTicks)); } _lastUpdateTicks = timestamp; bool flag = false; try { if (File.Exists(_requestPath)) { File.Delete(_requestPath); flag = true; } } catch (Exception ex) { ((BaseUnityPlugin)this).Logger.LogWarning((object)("Unable to consume tickprofiler.request: " + ex.Message)); } double num = ToSeconds(timestamp - _lastReportTicks); double num2 = Math.Max(1.0, _reportInterval.Value); if (flag || num >= num2) { Report(timestamp, flag); } } private void FixedUpdate() { if (_serverMode && _profilingEnabled) { long timestamp = Stopwatch.GetTimestamp(); _fixedUpdates++; if (_lastFixedTicks != 0L) { _fixedGaps.Add(ToMilliseconds(timestamp - _lastFixedTicks)); } _lastFixedTicks = timestamp; } } private void PatchGameTargets() { TargetSpec[] coreTargets = CoreTargets; for (int i = 0; i < coreTargets.Length; i++) { TargetSpec spec = coreTargets[i]; if (spec.Deep && !_deepGameProfiling.Value) { continue; } Type type = AccessTools.TypeByName(spec.TypeName); if (type == null) { AddPatchFailure(spec.Label + ": type not found"); continue; } MethodInfo[] array; try { array = (from m in type.GetMethods(BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic) where m.Name == spec.MethodName && !m.IsAbstract && !m.ContainsGenericParameters select m).ToArray(); } catch (Exception ex) { AddPatchFailure(spec.Label + ": enumeration failed: " + ex.Message); continue; } if (array.Length == 0) { AddPatchFailure(spec.Label + ": method not found"); continue; } MethodInfo[] array2 = array; foreach (MethodInfo method in array2) { InstrumentMethod(method, spec.Label, "game"); } } } private void ProfileLoadedMods() { foreach (KeyValuePair<string, PluginInfo> pluginInfo in Chainloader.PluginInfos) { PluginInfo value = pluginInfo.Value; if (value == null || (Object)(object)value.Instance == (Object)null) { continue; } Assembly assembly = ((object)value.Instance).GetType().Assembly; if (assembly == typeof(TickProfilerPlugin).Assembly) { continue; } string text = ((value.Metadata != null && !string.IsNullOrEmpty(value.Metadata.Name)) ? value.Metadata.Name : pluginInfo.Key); foreach (Type item in SafeGetTypes(assembly)) { if (IsOneShotPatchType(item.Name)) { continue; } bool flag = HasAttributeNamed(item.GetCustomAttributes(inherit: false), "HarmonyPatch"); MethodInfo[] methods; try { methods = item.GetMethods(BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); } catch { continue; } MethodInfo[] array = methods; foreach (MethodInfo methodInfo in array) { if (!methodInfo.IsAbstract && !methodInfo.ContainsGenericParameters && methodInfo.GetMethodBody() != null) { object[] attrs; try { attrs = methodInfo.GetCustomAttributes(inherit: false); } catch { attrs = Array.Empty<object>(); } bool num = HasAttributeNamed(attrs, "HarmonyPrefix") || HasAttributeNamed(attrs, "HarmonyPostfix") || HasAttributeNamed(attrs, "HarmonyFinalizer") || (flag && (methodInfo.Name == "Prefix" || methodInfo.Name == "Postfix" || methodInfo.Name == "Finalizer")); bool flag2 = typeof(BaseUnityPlugin).IsAssignableFrom(item) && (methodInfo.Name == "Update" || methodInfo.Name == "FixedUpdate" || methodInfo.Name == "LateUpdate"); if (num || flag2) { string label = "Mod/" + text + "/" + item.Name + "." + methodInfo.Name; InstrumentMethod(methodInfo, label, "mod"); } } } } } } private static IEnumerable<Type> SafeGetTypes(Assembly assembly) { try { return assembly.GetTypes(); } catch (ReflectionTypeLoadException ex) { return ex.Types.Where((Type t) => t != null); } catch { return Array.Empty<Type>(); } } private static bool IsOneShotPatchType(string typeName) { if (string.IsNullOrEmpty(typeName)) { return false; } if (typeName.IndexOf("Awake", StringComparison.OrdinalIgnoreCase) < 0 && typeName.IndexOf("Start_Patch", StringComparison.OrdinalIgnoreCase) < 0) { return typeName.IndexOf("OnDestroy", StringComparison.OrdinalIgnoreCase) >= 0; } return true; } private static bool HasAttributeNamed(object[] attrs, string name) { for (int i = 0; i < attrs.Length; i++) { Type type = attrs[i]?.GetType(); if (type != null && (type.Name == name || type.FullName == "HarmonyLib." + name)) { return true; } } return false; } private void InstrumentMethod(MethodBase method, string label, string kind) { //IL_0046: Unknown result type (might be due to invalid IL or missing references) //IL_004b: Unknown result type (might be due to invalid IL or missing references) //IL_0057: Expected O, but got Unknown //IL_0067: 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_0074: Expected O, but got Unknown if (method == null || MethodMetrics.ContainsKey(method)) { return; } Metric metric = new Metric(label, kind); MethodMetrics.Add(method, metric); Metrics.Add(metric); try { HarmonyMethod val = new HarmonyMethod(typeof(TickProfilerPlugin), "ProfilePrefix", (Type[])null) { priority = 800 }; HarmonyMethod val2 = new HarmonyMethod(typeof(TickProfilerPlugin), "ProfilePostfix", (Type[])null) { priority = 0 }; _harmony.Patch(method, val, val2, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } catch (Exception ex) { MethodMetrics.Remove(method); Metrics.Remove(metric); AddPatchFailure(label + ": " + ex.GetType().Name + " " + ex.Message); } } private static void ProfilePrefix(MethodBase __originalMethod, out int __state) { __state = -1; if (_profilingEnabled && MethodMetrics.TryGetValue(__originalMethod, out var value)) { if (_scopeStack == null) { _scopeStack = new ScopeFrame[64]; } if (_scopeDepth >= _scopeStack.Length) { Array.Resize(ref _scopeStack, _scopeStack.Length * 2); } int num = _scopeDepth++; _scopeStack[num].Metric = value; _scopeStack[num].StartTicks = Stopwatch.GetTimestamp(); _scopeStack[num].ChildTicks = 0L; __state = num; } } private static void ProfilePostfix(int __state) { if (__state >= 0 && _scopeStack != null && __state < _scopeStack.Length) { long num = Stopwatch.GetTimestamp() - _scopeStack[__state].StartTicks; long childTicks = _scopeStack[__state].ChildTicks; long num2 = num - childTicks; if (num2 < 0) { num2 = 0L; } _scopeStack[__state].Metric?.Add(num2, num); _scopeDepth = __state; if (__state > 0) { _scopeStack[__state - 1].ChildTicks += num; } } } private void Report(long nowTicks, bool requested) { double num = Math.Max(0.001, ToSeconds(nowTicks - _lastReportTicks)); long updates = _updates; long fixedUpdates = _fixedUpdates; _updates = 0L; _fixedUpdates = 0L; _lastReportTicks = nowTicks; double updateHz = (double)updates / num; double fixedHz = (double)fixedUpdates / num; double nominalFixedHz = ((Time.fixedDeltaTime > 0f) ? (1.0 / (double)Time.fixedDeltaTime) : 0.0); GapSnapshot updateGap = _updateGaps.SnapshotAndReset(); GapSnapshot fixedGap = _fixedGaps.SnapshotAndReset(); TimeSpan totalProcessorTime = _process.TotalProcessorTime; double totalMilliseconds = (totalProcessorTime - _lastCpu).TotalMilliseconds; _lastCpu = totalProcessorTime; double num2 = totalMilliseconds / (num * 1000.0) * 100.0; double cpuVmPct = num2 / (double)Math.Max(1, Environment.ProcessorCount); int num3 = GC.CollectionCount(0); int num4 = GC.CollectionCount(1); int num5 = GC.CollectionCount(2); int gc = num3 - _lastGc0; int gc2 = num4 - _lastGc1; int gc3 = num5 - _lastGc2; _lastGc0 = num3; _lastGc1 = num4; _lastGc2 = num5; List<MetricSnapshot> list = new List<MetricSnapshot>(Metrics.Count); foreach (Metric metric in Metrics) { MetricRaw raw = metric.SnapshotAndReset(); if (raw.Calls != 0L) { list.Add(MetricSnapshot.From(metric, raw, num)); } } list.Sort((MetricSnapshot a, MetricSnapshot b) => b.SelfMsPerSec.CompareTo(a.SelfMsPerSec)); double managedMiB = (double)GC.GetTotalMemory(forceFullCollection: false) / 1048576.0; ReportSnapshot s = new ReportSnapshot { TimestampUtc = DateTime.UtcNow, Requested = requested, WallSeconds = num, UpdateHz = updateHz, FixedHz = fixedHz, NominalFixedHz = nominalFixedHz, FixedDeltaMs = (double)Time.fixedDeltaTime * 1000.0, TimeScale = Time.timeScale, UpdateGap = updateGap, FixedGap = fixedGap, CpuOneCorePct = num2, CpuVmPct = cpuVmPct, ManagedMiB = managedMiB, Gc0 = gc, Gc1 = gc2, Gc2 = gc3, Metrics = list, PatchFailures = _patchFailures.ToArray(), PatchedMethods = MethodMetrics.Count }; string contents = BuildText(s); _latestOverlayText = BuildOverlayText(s); BroadcastOverlaySnapshot(); try { File.WriteAllText(_textPath, contents); } catch (Exception ex) { ((BaseUnityPlugin)this).Logger.LogWarning((object)("Unable to write tickprofiler-latest.txt: " + ex.Message)); } if (_writeStatusJson.Value) { try { WriteAtomic(_statusPath, BuildJson(s)); } catch (Exception ex2) { ((BaseUnityPlugin)this).Logger.LogWarning((object)("Unable to write tickprofiler-status.json: " + ex2.Message)); } } if (_logPeriodicSummary.Value || requested) { string[] array = BuildConsoleText(s).Split(new char[2] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries); foreach (string text in array) { ((BaseUnityPlugin)this).Logger.LogInfo((object)text); } } } private static bool IsDedicatedServerProcess() { try { return (Process.GetCurrentProcess().ProcessName ?? string.Empty).IndexOf("valheim_server", StringComparison.OrdinalIgnoreCase) >= 0; } catch { return false; } } private void TryRegisterProfilerRpc() { if (_rpcRegistered) { return; } ZRoutedRpc instance = ZRoutedRpc.instance; if (instance == null) { return; } try { instance.Register("MagiCorp_TickProfiler_Hello_v1", (Action<long>)OnProfilerHello); _rpcRegistered = true; ((BaseUnityPlugin)this).Logger.LogInfo((object)"TickProfiler client-overlay RPC registered."); } catch (Exception ex) { ((BaseUnityPlugin)this).Logger.LogWarning((object)("TickProfiler RPC registration failed: " + ex.Message)); } } private void OnProfilerHello(long sender) { if (!_serverMode || sender == 0L) { return; } if (_overlayPeers.Add(sender)) { ((BaseUnityPlugin)this).Logger.LogInfo((object)("TickProfiler overlay client registered: peer=" + sender + " clients=" + _overlayPeers.Count)); } if (string.IsNullOrEmpty(_latestOverlayText)) { return; } try { ZRoutedRpc instance = ZRoutedRpc.instance; if (instance != null) { instance.InvokeRoutedRPC(sender, "MagiCorp_TickProfiler_Snapshot_v1", new object[1] { _latestOverlayText }); } } catch { } } private void BroadcastOverlaySnapshot() { if (string.IsNullOrEmpty(_latestOverlayText) || _overlayPeers.Count == 0) { return; } ZRoutedRpc instance = ZRoutedRpc.instance; ZNet instance2 = ZNet.instance; if (instance == null || (Object)(object)instance2 == (Object)null) { return; } long[] array = _overlayPeers.ToArray(); foreach (long num in array) { try { if (instance2.GetPeer(num) == null) { _overlayPeers.Remove(num); continue; } instance.InvokeRoutedRPC(num, "MagiCorp_TickProfiler_Snapshot_v1", new object[1] { _latestOverlayText }); } catch { _overlayPeers.Remove(num); } } } private string BuildOverlayText(ReportSnapshot s) { StringBuilder stringBuilder = new StringBuilder(2048); double value = ((s.NominalFixedHz > 0.0) ? Math.Max(0.0, (s.NominalFixedHz - s.FixedHz) / s.NominalFixedHz * 100.0) : 0.0); stringBuilder.Append("SERVER TICK PROFILER\n"); stringBuilder.Append("Fixed ").Append(F(s.FixedHz, 1)).Append('/') .Append(F(s.NominalFixedHz, 1)) .Append(" Hz ") .Append("Update ") .Append(F(s.UpdateHz, 1)) .Append(" Hz ") .Append("Behind ") .Append(F(value, 1)) .Append("%\n"); stringBuilder.Append("Fixed gaps: p95 ").Append(F(s.FixedGap.P95Ms, 1)).Append(" ms worst ") .Append(F(s.FixedGap.MaxMs, 1)) .Append(" ms >40 ") .Append(s.FixedGap.Over40Ms) .Append(" >100 ") .Append(s.FixedGap.Over100Ms) .Append("\n"); stringBuilder.Append("CPU ").Append(F(s.CpuOneCorePct, 1)).Append("% core VM ") .Append(F(s.CpuVmPct, 1)) .Append("% managed ") .Append(F(s.ManagedMiB, 0)) .Append(" MiB GC ") .Append(s.Gc0) .Append('/') .Append(s.Gc1) .Append('/') .Append(s.Gc2) .Append("\n\n"); stringBuilder.Append("TOP CONSUMERS\n"); int num = Math.Min(8, s.Metrics.Count); for (int i = 0; i < num; i++) { MetricSnapshot metricSnapshot = s.Metrics[i]; stringBuilder.Append(i + 1).Append(". ").Append(metricSnapshot.Name) .Append(" ") .Append(F(metricSnapshot.SelfMsPerSec, 2)) .Append(" ms/s") .Append(" max ") .Append(F(metricSnapshot.MaxInclusiveMs, 1)) .Append(" ms"); if (metricSnapshot.MaxInclusiveMs >= (double)_spikeThresholdMs.Value) { stringBuilder.Append(" SPIKE"); } stringBuilder.Append('\n'); } return stringBuilder.ToString().TrimEnd(Array.Empty<char>()); } private string BuildConsoleText(ReportSnapshot s) { StringBuilder stringBuilder = new StringBuilder(2048); double value = ((s.NominalFixedHz > 0.0) ? Math.Max(0.0, (s.NominalFixedHz - s.FixedHz) / s.NominalFixedHz * 100.0) : 0.0); stringBuilder.Append("HEALTH Fixed ").Append(F(s.FixedHz, 1)).Append('/') .Append(F(s.NominalFixedHz, 1)) .Append(" Hz") .Append(" | Update ") .Append(F(s.UpdateHz, 1)) .Append(" Hz") .Append(" | Lag ") .Append(F(value, 1)) .Append('%') .Append(" | CPU ") .Append(F(s.CpuOneCorePct, 1)) .Append("% core") .Append(" | RAM ") .Append(F(s.ManagedMiB, 0)) .Append(" MiB") .Append(" | GC ") .Append(s.Gc0) .Append('/') .Append(s.Gc1) .Append('/') .Append(s.Gc2) .AppendLine(); stringBuilder.Append("STALLS Fixed p95 ").Append(F(s.FixedGap.P95Ms, 1)).Append(" ms") .Append(" | worst ") .Append(F(s.FixedGap.MaxMs, 1)) .Append(" ms") .Append(" | >40ms ") .Append(s.FixedGap.Over40Ms) .Append(" | >100ms ") .Append(s.FixedGap.Over100Ms) .Append(" | Update worst ") .Append(F(s.UpdateGap.MaxMs, 1)) .Append(" ms") .AppendLine(); stringBuilder.Append("TOP CONSUMERS").AppendLine(); stringBuilder.Append("# Consumer ms/s worst calls/s").AppendLine(); stringBuilder.Append("- -------------------------------------------- ----- ------ -------").AppendLine(); int num = Math.Min(5, s.Metrics.Count); for (int i = 0; i < num; i++) { MetricSnapshot metricSnapshot = s.Metrics[i]; string text = metricSnapshot.Name ?? "unknown"; if (text.Length > 44) { text = text.Substring(0, 41) + "..."; } string text2 = F(metricSnapshot.MaxInclusiveMs, 1) + ((metricSnapshot.MaxInclusiveMs >= (double)_spikeThresholdMs.Value) ? "!" : ""); stringBuilder.Append((i + 1).ToString(CultureInfo.InvariantCulture)).Append(" ").Append(text.PadRight(44)) .Append(" ") .Append(F(metricSnapshot.SelfMsPerSec, 1).PadLeft(5)) .Append(" ") .Append(text2.PadLeft(6)) .Append(" ") .Append(F(metricSnapshot.CallsPerSec, 1).PadLeft(7)) .AppendLine(); } if (_patchFailures.Count > 0) { stringBuilder.Append("WARN patch failures: ").Append(_patchFailures.Count).Append(" | instrumented: ") .Append(s.PatchedMethods) .AppendLine(); } return stringBuilder.ToString().TrimEnd(Array.Empty<char>()); } private string BuildText(ReportSnapshot s) { StringBuilder stringBuilder = new StringBuilder(4096); double value = ((s.NominalFixedHz > 0.0) ? Math.Max(0.0, (s.NominalFixedHz - s.FixedHz) / s.NominalFixedHz * 100.0) : 0.0); stringBuilder.Append("[TickProfiler] ").Append(s.TimestampUtc.ToString("O")).Append(" fixed=") .Append(F(s.FixedHz, 1)) .Append('/') .Append(F(s.NominalFixedHz, 1)) .Append("Hz") .Append(" behind=") .Append(F(value, 1)) .Append('%') .Append(" update=") .Append(F(s.UpdateHz, 1)) .Append("Hz") .Append(" CPU=") .Append(F(s.CpuOneCorePct, 1)) .Append("% of one core") .Append(" VMCPU=") .Append(F(s.CpuVmPct, 1)) .Append('%') .Append(" managed=") .Append(F(s.ManagedMiB, 1)) .Append("MiB") .Append(" GC=") .Append(s.Gc0) .Append('/') .Append(s.Gc1) .Append('/') .Append(s.Gc2) .AppendLine(); stringBuilder.Append("[TickProfiler] gaps fixed p95=").Append(F(s.FixedGap.P95Ms, 2)).Append("ms worst=") .Append(F(s.FixedGap.MaxMs, 2)) .Append("ms >40ms=") .Append(s.FixedGap.Over40Ms) .Append(" >100ms=") .Append(s.FixedGap.Over100Ms) .Append(" | update p95=") .Append(F(s.UpdateGap.P95Ms, 2)) .Append("ms worst=") .Append(F(s.UpdateGap.MaxMs, 2)) .Append("ms") .AppendLine(); int num = Math.Min(Math.Max(1, _topConsumers.Value), s.Metrics.Count); for (int i = 0; i < num; i++) { MetricSnapshot metricSnapshot = s.Metrics[i]; stringBuilder.Append("[TickProfiler] #").Append(i + 1).Append(' ') .Append(metricSnapshot.Name) .Append(" self=") .Append(F(metricSnapshot.SelfMsPerSec, 3)) .Append("ms/s") .Append(" (") .Append(F(metricSnapshot.CpuPct, 3)) .Append("% core)") .Append(" incl=") .Append(F(metricSnapshot.InclusiveMsPerSec, 3)) .Append("ms/s") .Append(" calls=") .Append(F(metricSnapshot.CallsPerSec, 1)) .Append("/s") .Append(" avg=") .Append(F(metricSnapshot.AvgSelfUs, 2)) .Append("us") .Append(" max=") .Append(F(metricSnapshot.MaxInclusiveMs, 3)) .Append("ms"); if (metricSnapshot.MaxInclusiveMs >= (double)_spikeThresholdMs.Value) { stringBuilder.Append(" SPIKE"); } stringBuilder.AppendLine(); } if (_patchFailures.Count > 0) { stringBuilder.Append("[TickProfiler] patchFailures=").Append(_patchFailures.Count).Append(" patchedMethods=") .Append(s.PatchedMethods) .AppendLine(); } else { stringBuilder.Append("[TickProfiler] patchedMethods=").Append(s.PatchedMethods).AppendLine(); } return stringBuilder.ToString(); } private string BuildJson(ReportSnapshot s) { StringBuilder stringBuilder = new StringBuilder(16384); stringBuilder.Append('{'); JsonProp(stringBuilder, "plugin", "MagiCorp Valheim Tick Profiler", first: true); JsonProp(stringBuilder, "version", "0.2.5", first: false); JsonProp(stringBuilder, "timestampUtc", s.TimestampUtc.ToString("O"), first: false); JsonProp(stringBuilder, "requested", s.Requested, first: false); JsonProp(stringBuilder, "wallSeconds", s.WallSeconds, first: false); JsonProp(stringBuilder, "updateHz", s.UpdateHz, first: false); JsonProp(stringBuilder, "fixedHz", s.FixedHz, first: false); JsonProp(stringBuilder, "nominalFixedHz", s.NominalFixedHz, first: false); JsonProp(stringBuilder, "fixedDeltaMs", s.FixedDeltaMs, first: false); JsonProp(stringBuilder, "timeScale", s.TimeScale, first: false); JsonProp(stringBuilder, "processCpuOneCorePct", s.CpuOneCorePct, first: false); JsonProp(stringBuilder, "processCpuVmPct", s.CpuVmPct, first: false); JsonProp(stringBuilder, "managedMemoryMiB", s.ManagedMiB, first: false); stringBuilder.Append(",\"gcCollections\":{"); JsonProp(stringBuilder, "gen0", s.Gc0, first: true); JsonProp(stringBuilder, "gen1", s.Gc1, first: false); JsonProp(stringBuilder, "gen2", s.Gc2, first: false); stringBuilder.Append('}'); stringBuilder.Append(",\"fixedGap\":"); AppendGapJson(stringBuilder, s.FixedGap); stringBuilder.Append(",\"updateGap\":"); AppendGapJson(stringBuilder, s.UpdateGap); JsonProp(stringBuilder, "patchedMethods", s.PatchedMethods, first: false); stringBuilder.Append(",\"patchFailures\":["); for (int i = 0; i < s.PatchFailures.Length; i++) { if (i > 0) { stringBuilder.Append(','); } stringBuilder.Append(JsonString(s.PatchFailures[i])); } stringBuilder.Append(']'); stringBuilder.Append(",\"consumers\":["); int num = Math.Min(Math.Max(_topConsumers.Value, 30), s.Metrics.Count); for (int j = 0; j < num; j++) { if (j > 0) { stringBuilder.Append(','); } MetricSnapshot metricSnapshot = s.Metrics[j]; stringBuilder.Append('{'); JsonProp(stringBuilder, "name", metricSnapshot.Name, first: true); JsonProp(stringBuilder, "kind", metricSnapshot.Kind, first: false); JsonProp(stringBuilder, "selfMsPerSec", metricSnapshot.SelfMsPerSec, first: false); JsonProp(stringBuilder, "inclusiveMsPerSec", metricSnapshot.InclusiveMsPerSec, first: false); JsonProp(stringBuilder, "cpuPctOneCore", metricSnapshot.CpuPct, first: false); JsonProp(stringBuilder, "callsPerSec", metricSnapshot.CallsPerSec, first: false); JsonProp(stringBuilder, "avgSelfUs", metricSnapshot.AvgSelfUs, first: false); JsonProp(stringBuilder, "maxInclusiveMs", metricSnapshot.MaxInclusiveMs, first: false); JsonProp(stringBuilder, "spike", metricSnapshot.MaxInclusiveMs >= (double)_spikeThresholdMs.Value, first: false); stringBuilder.Append('}'); } stringBuilder.Append("]}"); return stringBuilder.ToString(); } private static void AppendGapJson(StringBuilder b, GapSnapshot g) { b.Append('{'); JsonProp(b, "count", g.Count, first: true); JsonProp(b, "avgMs", g.AvgMs, first: false); JsonProp(b, "p95Ms", g.P95Ms, first: false); JsonProp(b, "maxMs", g.MaxMs, first: false); JsonProp(b, "over20Ms", g.Over20Ms, first: false); JsonProp(b, "over40Ms", g.Over40Ms, first: false); JsonProp(b, "over100Ms", g.Over100Ms, first: false); b.Append('}'); } private static void JsonProp(StringBuilder b, string name, string value, bool first) { if (!first) { b.Append(','); } b.Append(JsonString(name)).Append(':').Append(JsonString(value)); } private static void JsonProp(StringBuilder b, string name, bool value, bool first) { if (!first) { b.Append(','); } b.Append(JsonString(name)).Append(':').Append(value ? "true" : "false"); } private static void JsonProp(StringBuilder b, string name, int value, bool first) { if (!first) { b.Append(','); } b.Append(JsonString(name)).Append(':').Append(value.ToString(CultureInfo.InvariantCulture)); } private static void JsonProp(StringBuilder b, string name, long value, bool first) { if (!first) { b.Append(','); } b.Append(JsonString(name)).Append(':').Append(value.ToString(CultureInfo.InvariantCulture)); } private static void JsonProp(StringBuilder b, string name, double value, bool first) { if (!first) { b.Append(','); } b.Append(JsonString(name)).Append(':').Append(value.ToString("0.######", CultureInfo.InvariantCulture)); } private static string JsonString(string value) { if (value == null) { return "null"; } StringBuilder stringBuilder = new StringBuilder(value.Length + 8); stringBuilder.Append('"'); foreach (char c in value) { switch (c) { case '"': stringBuilder.Append("\\\""); continue; case '\\': stringBuilder.Append("\\\\"); continue; case '\n': stringBuilder.Append("\\n"); continue; case '\r': stringBuilder.Append("\\r"); continue; case '\t': stringBuilder.Append("\\t"); continue; } if (c < ' ') { StringBuilder stringBuilder2 = stringBuilder.Append("\\u"); int num = c; stringBuilder2.Append(num.ToString("x4")); } else { stringBuilder.Append(c); } } stringBuilder.Append('"'); return stringBuilder.ToString(); } private static void WriteAtomic(string path, string content) { string text = path + ".tmp"; File.WriteAllText(text, content); if (File.Exists(path)) { File.Delete(path); } File.Move(text, path); } private void AddPatchFailure(string message) { _patchFailures.Add(message); ((BaseUnityPlugin)this).Logger.LogWarning((object)("TickProfiler patch skipped: " + message)); } private static string F(double value, int decimals) { return value.ToString("F" + decimals, CultureInfo.InvariantCulture); } private static double ToMilliseconds(long ticks) { return (double)ticks * 1000.0 / (double)Frequency; } private static double ToSeconds(long ticks) { return (double)ticks / (double)Frequency; } }
MagiCorp.ValheimTickProfiler.Client.dll
Decompiled 8 hours agousing System; using System.Diagnostics; using System.Globalization; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using BepInEx; using BepInEx.Configuration; using UnityEngine; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: TargetFramework(".NETFramework,Version=v4.8", FrameworkDisplayName = ".NET Framework 4.8")] [assembly: AssemblyCompany("MagiCorp.ValheimTickProfiler.Client")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: AssemblyInformationalVersion("1.0.0+8a7348b5dbdb8712868f3841af346c25901bdbff")] [assembly: AssemblyProduct("MagiCorp.ValheimTickProfiler.Client")] [assembly: AssemblyTitle("MagiCorp.ValheimTickProfiler.Client")] [assembly: AssemblyVersion("1.0.0.0")] namespace MagiCorp.ValheimTickProfiler.Client; [BepInPlugin("magicorp.valheim.tickprofiler.client", "MagiCorp Valheim Tick Profiler Client Overlay", "0.2.5")] [BepInProcess("valheim.exe")] public sealed class TickProfilerClientOverlay : BaseUnityPlugin { public const string PluginGuid = "magicorp.valheim.tickprofiler.client"; public const string PluginName = "MagiCorp Valheim Tick Profiler Client Overlay"; public const string PluginVersion = "0.2.5"; private const string RpcHello = "MagiCorp_TickProfiler_Hello_v1"; private const string RpcSnapshot = "MagiCorp_TickProfiler_Snapshot_v1"; private ConfigEntry<KeyCode> _toggleKey; private ConfigEntry<bool> _visibleByDefault; private ConfigEntry<int> _fontSize; private ConfigEntry<float> _x; private ConfigEntry<float> _y; private ConfigEntry<float> _width; private ConfigEntry<float> _uiScale; private bool _visible; private bool _rpcRegistered; private ZRoutedRpc _registeredRpcInstance; private long _lastHelloTicks; private string _snapshot = string.Empty; private DateTime _snapshotUtc = DateTime.MinValue; private GUIStyle _textStyle; private bool _loggedFirstSnapshot; private bool _loggedPeerIdFailure; private static readonly MethodInfo GetServerPeerIdMethod = typeof(ZRoutedRpc).GetMethod("GetServerPeerID", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); private void Awake() { //IL_0143: Unknown result type (might be due to invalid IL or missing references) _toggleKey = ((BaseUnityPlugin)this).Config.Bind<KeyCode>("Client UI", "ToggleKey", (KeyCode)289, "Show or hide the server TickProfiler overlay."); _visibleByDefault = ((BaseUnityPlugin)this).Config.Bind<bool>("Client UI", "VisibleByDefault", false, "Show the overlay when Valheim starts."); _fontSize = ((BaseUnityPlugin)this).Config.Bind<int>("Client UI", "FontSize", 14, "Overlay font size."); _x = ((BaseUnityPlugin)this).Config.Bind<float>("Client UI", "X", 16f, "Overlay X position in pixels."); _y = ((BaseUnityPlugin)this).Config.Bind<float>("Client UI", "Y", 16f, "Overlay Y position in pixels."); _width = ((BaseUnityPlugin)this).Config.Bind<float>("Client UI", "Width", 820f, "Overlay width at 1080p before UI scaling."); _uiScale = ((BaseUnityPlugin)this).Config.Bind<float>("Client UI", "UIScale", 0f, "Overlay scale. 0 = automatic based on vertical resolution (1x at 1080p, 2x at 4K)."); _visible = _visibleByDefault.Value; ((BaseUnityPlugin)this).Logger.LogInfo((object)string.Format("{0} {1} loaded in process {2}. Toggle: {3}", "MagiCorp Valheim Tick Profiler Client Overlay", "0.2.5", Process.GetCurrentProcess().ProcessName, _toggleKey.Value)); } private void Update() { //IL_0058: Unknown result type (might be due to invalid IL or missing references) //IL_005d: Unknown result type (might be due to invalid IL or missing references) TryRegisterRpc(); if (TogglePressed()) { _visible = !_visible; ((BaseUnityPlugin)this).Logger.LogInfo((object)("TickProfiler overlay " + (_visible ? "shown" : "hidden") + " via " + ((object)_toggleKey.Value/*cast due to .constrained prefix*/).ToString() + ".")); } long timestamp = Stopwatch.GetTimestamp(); if (_rpcRegistered && (double)(timestamp - _lastHelloTicks) / (double)Stopwatch.Frequency >= 10.0) { SendHello(); } } private void TryRegisterRpc() { ZRoutedRpc instance = ZRoutedRpc.instance; if (instance == null) { _rpcRegistered = false; _registeredRpcInstance = null; } else if (!_rpcRegistered || _registeredRpcInstance != instance) { _rpcRegistered = false; _registeredRpcInstance = null; try { instance.Register<string>("MagiCorp_TickProfiler_Snapshot_v1", (Action<long, string>)OnSnapshot); _registeredRpcInstance = instance; _rpcRegistered = true; _lastHelloTicks = 0L; ((BaseUnityPlugin)this).Logger.LogInfo((object)"TickProfiler snapshot RPC registered on current ZRoutedRpc instance."); SendHello(); } catch (Exception ex) { ((BaseUnityPlugin)this).Logger.LogWarning((object)("TickProfiler overlay RPC registration failed: " + ex.Message)); } } } private void SendHello() { ZRoutedRpc instance = ZRoutedRpc.instance; if (instance == null) { return; } long serverPeerId = GetServerPeerId(instance); if (serverPeerId == 0L) { return; } try { instance.InvokeRoutedRPC(serverPeerId, "MagiCorp_TickProfiler_Hello_v1", Array.Empty<object>()); _lastHelloTicks = Stopwatch.GetTimestamp(); ((BaseUnityPlugin)this).Logger.LogDebug((object)("TickProfiler hello sent to server peer " + serverPeerId + ".")); } catch { } } private long GetServerPeerId(ZRoutedRpc routed) { if (routed == null || GetServerPeerIdMethod == null) { return 0L; } try { return (GetServerPeerIdMethod.Invoke(routed, null) is long num) ? num : 0; } catch (Exception ex) { if (!_loggedPeerIdFailure) { _loggedPeerIdFailure = true; ((BaseUnityPlugin)this).Logger.LogWarning((object)("TickProfiler could not resolve the server peer ID: " + ex.GetBaseException().Message)); } return 0L; } } private void OnSnapshot(long sender, string text) { if (string.IsNullOrEmpty(text)) { return; } ZRoutedRpc instance = ZRoutedRpc.instance; if (instance != null) { long serverPeerId = GetServerPeerId(instance); if (serverPeerId != 0L && sender != serverPeerId) { return; } } _snapshot = text; _snapshotUtc = DateTime.UtcNow; if (!_loggedFirstSnapshot) { _loggedFirstSnapshot = true; ((BaseUnityPlugin)this).Logger.LogInfo((object)"TickProfiler first server snapshot received."); } } private bool TogglePressed() { //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_001b: Unknown result type (might be due to invalid IL or missing references) try { return ZInput.GetKeyDown(_toggleKey.Value, true); } catch (Exception ex) { try { return Input.GetKeyDown(_toggleKey.Value); } catch { ((BaseUnityPlugin)this).Logger.LogWarning((object)("TickProfiler toggle input failed: " + ex.Message)); return false; } } } private void OnGUI() { //IL_01c2: Unknown result type (might be due to invalid IL or missing references) //IL_01c7: 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_0086: Unknown result type (might be due to invalid IL or missing references) //IL_008d: Unknown result type (might be due to invalid IL or missing references) //IL_0094: Unknown result type (might be due to invalid IL or missing references) //IL_00a0: Expected O, but got Unknown //IL_00ab: Unknown result type (might be due to invalid IL or missing references) //IL_00d9: 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_028f: Unknown result type (might be due to invalid IL or missing references) //IL_02d7: Unknown result type (might be due to invalid IL or missing references) if (!_visible) { return; } float num = ((_uiScale.Value > 0f) ? Mathf.Clamp(_uiScale.Value, 0.75f, 4f) : Mathf.Clamp((float)Screen.height / 1080f, 1f, 3f)); int fontSize = Math.Max(10, Mathf.RoundToInt((float)_fontSize.Value * num)); if (_textStyle == null) { _textStyle = new GUIStyle(GUI.skin.label) { richText = false, wordWrap = false, alignment = (TextAnchor)0 }; _textStyle.normal.textColor = Color.white; } _textStyle.fontSize = fontSize; string text; if (string.IsNullOrEmpty(_snapshot)) { text = "SERVER TICK PROFILER\nWaiting for TickProfiler data from this server...\nToggle " + ((object)_toggleKey.Value/*cast due to .constrained prefix*/).ToString(); } else { double num2 = Math.Max(0.0, (DateTime.UtcNow - _snapshotUtc).TotalSeconds); float num3 = -1f; try { if ((Object)(object)ZNet.instance != (Object)null) { num3 = ZNet.instance.GetServerPing() * 1000f; } } catch { } text = _snapshot + "\n\nAge " + num2.ToString("F1", CultureInfo.InvariantCulture) + "s" + ((num3 >= 0f) ? (" Ping " + num3.ToString("F0", CultureInfo.InvariantCulture) + "ms") : string.Empty) + " Toggle " + ((object)_toggleKey.Value/*cast due to .constrained prefix*/).ToString(); } int num4 = text.Count((char c) => c == '\n') + 1; float num5 = _x.Value * num; float num6 = _y.Value * num; float val = 520f * num; float num7 = Math.Min(Math.Max(val, _width.Value * num), Math.Max(val, (float)Screen.width - num5 - 12f * num)); float num8 = 20f * num + (float)num4 * ((float)_textStyle.fontSize + 5f * num); Rect val2 = default(Rect); ((Rect)(ref val2))..ctor(num5, num6, num7, num8); GUI.Box(val2, GUIContent.none); GUI.Label(new Rect(((Rect)(ref val2)).x + 10f * num, ((Rect)(ref val2)).y + 8f * num, ((Rect)(ref val2)).width - 20f * num, ((Rect)(ref val2)).height - 16f * num), text, _textStyle); } }