using System; using System.Collections; using System.Collections.Concurrent; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.IO; using System.IO.Compression; using System.Linq; using System.Reflection; using System.Reflection.Emit; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Security; using System.Security.Permissions; using System.Text; using System.Text.RegularExpressions; using System.Threading; using BepInEx; using BepInEx.Bootstrap; using BepInEx.Configuration; using BepInEx.Logging; using HarmonyLib; using Microsoft.CodeAnalysis; using UnityEngine; using UnityEngine.SceneManagement; using UnityEngine.UI; using ValheimProfiler.Configuration; using ValheimProfiler.Core; using ValheimProfiler.Core.Logging; using ValheimProfiler.Core.Profiling; using ValheimProfiler.Server; using ValheimProfiler.Tools.LogMonitor; using ValheimProfiler.Tools.MonoBehaviourCallProfiler; using ValheimProfiler.Tools.MonoBehaviourProfiler; using ValheimProfiler.Tools.NetworkProfiler; using ValheimProfiler.Tools.PatchProfiler; using ValheimProfiler.Tools.ServerLogMonitor; using ValheimProfiler.Tools.ValheimUpdateProfiler; using ValheimProfiler.UI; using ValheimProfiler.Valheim; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.Default | DebuggableAttribute.DebuggingModes.DisableOptimizations | DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints | DebuggableAttribute.DebuggingModes.EnableEditAndContinue)] [assembly: TargetFramework(".NETFramework,Version=v4.7.2", FrameworkDisplayName = ".NET Framework 4.7.2")] [assembly: AssemblyCompany("ValheimProfiler")] [assembly: AssemblyConfiguration("Debug")] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: AssemblyInformationalVersion("1.0.0+87c9dc75dea5601eb74fd122b36ec1687652ddf4")] [assembly: AssemblyProduct("ValheimProfiler")] [assembly: AssemblyTitle("ValheimProfiler")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("1.0.0.0")] [module: UnverifiableCode] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)] internal sealed class RefSafetyRulesAttribute : Attribute { public readonly int Version; public RefSafetyRulesAttribute(int P_0) { Version = P_0; } } } namespace ValheimProfiler { [BepInPlugin("shudnal.ValheimProfiler", "Valheim Profiler", "1.0.0")] public sealed class ValheimProfilerPlugin : BaseUnityPlugin { public const string PluginGuid = "shudnal.ValheimProfiler"; public const string PluginName = "Valheim Profiler"; public const string PluginVersion = "1.0.0"; internal const string CoreHarmonyId = "shudnal.ValheimProfiler.Core"; private Harmony _coreHarmony; private bool _headlessServerBackend; internal static ValheimProfilerPlugin Instance { get; private set; } internal ValheimProfilerApp App { get; private set; } internal ServerLogService ServerLogService { get; private set; } internal NetworkProfilerService NetworkProfilerService { get; private set; } private void Awake() { //IL_001a: Unknown result type (might be due to invalid IL or missing references) //IL_0024: Expected O, but got Unknown //IL_0025: Unknown result type (might be due to invalid IL or missing references) //IL_002b: Invalid comparison between Unknown and I4 Instance = this; ValheimProfilerConfig config = new ValheimProfilerConfig(((BaseUnityPlugin)this).Config); _coreHarmony = new Harmony("shudnal.ValheimProfiler.Core"); _headlessServerBackend = (int)SystemInfo.graphicsDeviceType == 4; if (_headlessServerBackend) { ServerLogService = new ServerLogService(config); NetworkProfilerService = new NetworkProfilerService(); PatchHeadlessNetworkBridges(); ServerLogRpcBridge.RegisterForCurrentNetwork(); NetworkProfilerRpcBridge.RegisterForCurrentNetwork(); } else { App = new ValheimProfilerApp(config, ((BaseUnityPlugin)this).Logger); _coreHarmony.PatchAll(typeof(ValheimProfilerPlugin).Assembly); App.Initialize(); ServerLogRpcBridge.RegisterForCurrentNetwork(); NetworkProfilerRpcBridge.RegisterForCurrentNetwork(); } } private void PatchHeadlessNetworkBridges() { //IL_0034: Unknown result type (might be due to invalid IL or missing references) //IL_0041: Expected O, but got Unknown //IL_0074: Unknown result type (might be due to invalid IL or missing references) //IL_0082: Expected O, but got Unknown _coreHarmony.Patch((MethodBase)AccessTools.Method(typeof(ZNet), "Awake", (Type[])null, (Type[])null), (HarmonyMethod)null, new HarmonyMethod(AccessTools.Method(typeof(ZNetAwakeServerLogPatch), "Postfix", (Type[])null, (Type[])null)), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); _coreHarmony.Patch((MethodBase)AccessTools.Method(typeof(ZNet), "OnDestroy", (Type[])null, (Type[])null), new HarmonyMethod(AccessTools.Method(typeof(ZNetOnDestroyServerLogPatch), "Prefix", (Type[])null, (Type[])null)), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } private void Start() { App?.MarkGameWindowReady(); } private void Update() { if (_headlessServerBackend) { ServerLogService?.Update(); NetworkProfilerService?.Update(); } else { App?.Update(); } } private void LateUpdate() { App?.LateUpdate(); } private void OnGUI() { App?.OnGUI(); } private void OnDisable() { App?.SetUiVisible(visible: false); App?.ReleaseCursorAndPause(); } private void OnDestroy() { try { App?.Shutdown(); ServerLogService?.Shutdown(); NetworkProfilerService?.Shutdown(); } finally { Harmony coreHarmony = _coreHarmony; if (coreHarmony != null) { coreHarmony.UnpatchSelf(); } _coreHarmony = null; App = null; ServerLogService = null; NetworkProfilerService = null; ServerLogTransport.Clear(); NetworkProfilerTransport.Clear(); if ((Object)(object)Instance == (Object)(object)this) { Instance = null; } } } } } namespace ValheimProfiler.Valheim { [HarmonyPatch] internal static class GuiPointScalingPatch { private static IEnumerable TargetMethods() { yield return AccessTools.Method(typeof(GUIUtility), "ScreenToGUIPoint", (Type[])null, (Type[])null); yield return AccessTools.Method(typeof(GUIUtility), "GUIToScreenPoint", (Type[])null, (Type[])null); } [HarmonyPriority(800)] private static void Prefix(ref Matrix4x4 __state) { //IL_003d: Unknown result type (might be due to invalid IL or missing references) //IL_0042: Unknown result type (might be due to invalid IL or missing references) //IL_0047: Unknown result type (might be due to invalid IL or missing references) ValheimProfilerPlugin instance = ValheimProfilerPlugin.Instance; if (instance != null && instance.App?.IsDrawingUi == true) { __state = GUI.matrix; GUI.matrix = Matrix4x4.identity; } } [HarmonyPriority(800)] private static void Postfix(Matrix4x4 __state) { //IL_0037: Unknown result type (might be due to invalid IL or missing references) ValheimProfilerPlugin instance = ValheimProfilerPlugin.Instance; if (instance != null && instance.App?.IsDrawingUi == true) { GUI.matrix = __state; } } } internal sealed class ValheimCursorController { private bool _captured; private CursorLockMode _previousLockState; private bool _previousVisible; internal void Update(bool active) { if (active) { Unlock(); } else { Release(); } } internal void LateUpdate(bool active) { if (active) { Unlock(); } } internal void OnGUI(bool active) { if (active) { Unlock(); } } internal void Release() { //IL_0011: Unknown result type (might be due to invalid IL or missing references) if (_captured) { Cursor.lockState = _previousLockState; Cursor.visible = _previousVisible; _captured = false; } } internal void OverrideReleaseState(CursorLockMode lockState, bool visible) { //IL_0011: Unknown result type (might be due to invalid IL or missing references) //IL_0012: Unknown result type (might be due to invalid IL or missing references) if (_captured) { _previousLockState = lockState; _previousVisible = visible; } } private void Unlock() { //IL_0009: 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) //IL_0026: Unknown result type (might be due to invalid IL or missing references) if (!_captured || (int)Cursor.lockState != 0 || !Cursor.visible) { _previousLockState = Cursor.lockState; _previousVisible = Cursor.visible; _captured = true; } Cursor.lockState = (CursorLockMode)0; Cursor.visible = true; } } [HarmonyPatch(typeof(FejdStartup), "Start")] internal static class FejdStartupStartCursorPatch { private static void Postfix() { ValheimProfilerPlugin.Instance?.App?.OverrideCursorReleaseState((CursorLockMode)0, visible: true); } } [HarmonyPatch(typeof(FejdStartup), "OnDestroy")] internal static class FejdStartupOnDestroyCursorPatch { private static void Prefix() { ValheimProfilerPlugin.Instance?.App?.OverrideCursorReleaseState((CursorLockMode)1, visible: false); } } internal static class ValheimInputState { internal static bool ShouldBlockAll { get { ValheimProfilerPlugin instance = ValheimProfilerPlugin.Instance; return instance != null && instance.App?.ShouldBlockGameInput == true; } } internal static bool ShouldBlockMouse { get { ValheimProfilerPlugin instance = ValheimProfilerPlugin.Instance; return instance != null && instance.App?.ShouldBlockMouseInput == true; } } } internal static class ZInputPatchMethods { private const BindingFlags AllMethods = BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic; internal static IEnumerable FindBooleanMethods(params string[] names) { HashSet acceptedNames = new HashSet(names ?? Array.Empty(), StringComparer.Ordinal); return (from method in typeof(ZInput).GetMethods(BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic) where method.ReturnType == typeof(bool) && acceptedNames.Contains(method.Name) select method).Cast().Distinct(); } internal static IEnumerable FindStringButtonMethods() { return FindBooleanMethods("GetButton", "GetButtonDown", "GetButtonUp").Where(delegate(MethodBase method) { ParameterInfo[] parameters = method.GetParameters(); return parameters.Length != 0 && parameters[0].ParameterType == typeof(string); }); } } internal static class ZInputMouseBindingResolver { private sealed class CachedMouseBindings { public bool DefinitionFound; public bool BindingSourceResolved; public object ButtonAction; public object[] MouseControls = Array.Empty(); public int MouseButtonMask; } private const BindingFlags InstanceMembers = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic; private static readonly Dictionary BindingCache = new Dictionary(StringComparer.Ordinal); internal static void ClearCache() { BindingCache.Clear(); } internal static void Invalidate(string action) { if (!string.IsNullOrWhiteSpace(action)) { BindingCache.Remove(action); } } internal static bool IsMouseBindingActive(string action, string queryMethodName, bool actionResultIsKnownTrue = false) { if (string.IsNullOrWhiteSpace(action)) { return false; } CachedMouseBindings configuredMouseBindings = GetConfiguredMouseBindings(action); if (configuredMouseBindings.DefinitionFound) { object memberValue = GetMemberValue(configuredMouseBindings.ButtonAction, "activeControl"); if (memberValue != null) { bool flag = IsMouseControl(memberValue); bool flag2 = IsControlActive(memberValue, queryMethodName); if (flag && (actionResultIsKnownTrue || flag2)) { return true; } if (!flag && (actionResultIsKnownTrue || flag2)) { return false; } } for (int i = 0; i < configuredMouseBindings.MouseControls.Length; i++) { if (IsControlActive(configuredMouseBindings.MouseControls[i], queryMethodName)) { return true; } } int num = configuredMouseBindings.MouseButtonMask; int num2 = 0; while (num != 0 && num2 < 31) { if ((num & 1) != 0 && IsMouseButtonActive(num2, queryMethodName)) { return true; } num2++; num >>= 1; } if (configuredMouseBindings.BindingSourceResolved) { return false; } } if (1 == 0) { } int num3; switch (action) { case "Attack": num3 = 0; break; case "Block": case "BuildMenu": num3 = 1; break; case "SecondaryAttack": case "Remove": num3 = 2; break; default: num3 = -1; break; } if (1 == 0) { } int num4 = num3; return num4 >= 0 && IsMouseButtonActive(num4, queryMethodName); } private static CachedMouseBindings GetConfiguredMouseBindings(string action) { if (BindingCache.TryGetValue(action, out var value)) { return value; } CachedMouseBindings cachedMouseBindings = ResolveConfiguredMouseBindings(action); BindingCache[action] = cachedMouseBindings; return cachedMouseBindings; } private static CachedMouseBindings ResolveConfiguredMouseBindings(string action) { CachedMouseBindings cachedMouseBindings = new CachedMouseBindings(); try { ZInput instance = ZInput.instance; if (instance?.m_buttons == null || !instance.m_buttons.TryGetValue(action, out var value)) { return cachedMouseBindings; } cachedMouseBindings.DefinitionFound = true; ResolveDirectButtonDefinition(value, cachedMouseBindings); object memberValue = GetMemberValue(value, "ButtonAction"); if (memberValue == null) { memberValue = GetMemberValue(value, "m_buttonAction"); } if (memberValue == null) { return cachedMouseBindings; } cachedMouseBindings.BindingSourceResolved = true; cachedMouseBindings.ButtonAction = memberValue; object memberValue2 = GetMemberValue(memberValue, "bindings"); if (memberValue2 is IEnumerable enumerable) { foreach (object item in enumerable) { string text = GetMemberValue(item, "effectivePath") as string; if (string.IsNullOrWhiteSpace(text)) { text = GetMemberValue(item, "path") as string; } if (TryParseMouseButton(text, out var mouseButton) && mouseButton < 31) { cachedMouseBindings.MouseButtonMask |= 1 << mouseButton; } } } object memberValue3 = GetMemberValue(memberValue, "controls"); if (memberValue3 is IEnumerable enumerable2) { List list = new List(); foreach (object item2 in enumerable2) { if (item2 != null && IsMouseControl(item2)) { list.Add(item2); } } cachedMouseBindings.MouseControls = list.ToArray(); } } catch { return new CachedMouseBindings(); } return cachedMouseBindings; } private static void ResolveDirectButtonDefinition(object definition, CachedMouseBindings resolved) { if (definition == null || resolved == null) { return; } bool value; bool flag = TryGetBooleanMember(definition, "m_bMouseButtonSet", out value) || TryGetBooleanMember(definition, "m_mouseButtonSet", out value); object memberValue = GetMemberValue(definition, "m_mouseButton"); int mouseButton2; if (flag) { resolved.BindingSourceResolved = true; if (value && TryConvertMouseButton(memberValue, out var mouseButton)) { resolved.MouseButtonMask |= 1 << mouseButton; } } else if (memberValue != null && TryConvertMouseButton(memberValue, out mouseButton2)) { string text = memberValue.ToString() ?? string.Empty; if (!text.Equals("None", StringComparison.OrdinalIgnoreCase) && !text.Equals("Back", StringComparison.OrdinalIgnoreCase)) { resolved.BindingSourceResolved = true; resolved.MouseButtonMask |= 1 << mouseButton2; } } object memberValue2 = GetMemberValue(definition, "m_key"); if (memberValue2 != null) { string text2 = memberValue2.ToString() ?? string.Empty; if (TryParseLegacyMouseKeyName(text2, out var mouseButton3)) { resolved.BindingSourceResolved = true; resolved.MouseButtonMask |= 1 << mouseButton3; } else if (!text2.Equals("None", StringComparison.OrdinalIgnoreCase)) { resolved.BindingSourceResolved = true; } } } private static bool TryConvertMouseButton(object value, out int mouseButton) { mouseButton = -1; if (value == null) { return false; } string text = value.ToString() ?? string.Empty; if (text.Equals("Left", StringComparison.OrdinalIgnoreCase) || text.Equals("LeftButton", StringComparison.OrdinalIgnoreCase)) { mouseButton = 0; return true; } if (text.Equals("Right", StringComparison.OrdinalIgnoreCase) || text.Equals("RightButton", StringComparison.OrdinalIgnoreCase)) { mouseButton = 1; return true; } if (text.Equals("Middle", StringComparison.OrdinalIgnoreCase) || text.Equals("MiddleButton", StringComparison.OrdinalIgnoreCase)) { mouseButton = 2; return true; } if (text.Equals("Forward", StringComparison.OrdinalIgnoreCase) || text.Equals("ForwardButton", StringComparison.OrdinalIgnoreCase)) { mouseButton = 3; return true; } if (text.Equals("Back", StringComparison.OrdinalIgnoreCase) || text.Equals("BackButton", StringComparison.OrdinalIgnoreCase)) { mouseButton = 4; return true; } try { int num = Convert.ToInt32(value); if (num >= 0 && num < 31) { mouseButton = num; return true; } } catch { } return false; } private static bool TryParseLegacyMouseKeyName(string keyName, out int mouseButton) { mouseButton = -1; if (string.IsNullOrWhiteSpace(keyName) || !keyName.StartsWith("Mouse", StringComparison.OrdinalIgnoreCase)) { return false; } return int.TryParse(keyName.Substring(5), out mouseButton) && mouseButton >= 0 && mouseButton < 31; } private static object GetMemberValue(object instance, string memberName) { if (instance == null || string.IsNullOrEmpty(memberName)) { return null; } try { Type type = instance.GetType(); PropertyInfo property = type.GetProperty(memberName, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (property != null && property.GetIndexParameters().Length == 0) { return property.GetValue(instance, null); } return type.GetField(memberName, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)?.GetValue(instance); } catch { return null; } } private static bool TryGetBooleanMember(object instance, string memberName, out bool value) { value = false; if (!(GetMemberValue(instance, memberName) is bool flag) || 1 == 0) { return false; } value = flag; return true; } private static bool IsMouseControl(object control) { if (control == null) { return false; } string text = GetMemberValue(control, "path") as string; if (!string.IsNullOrWhiteSpace(text) && text.IndexOf("/Mouse/", StringComparison.OrdinalIgnoreCase) >= 0) { return true; } object memberValue = GetMemberValue(control, "device"); string text2 = memberValue?.GetType().FullName ?? string.Empty; if (text2.EndsWith(".Mouse", StringComparison.Ordinal) || string.Equals(memberValue?.GetType().Name, "Mouse", StringComparison.Ordinal)) { return true; } string a = GetMemberValue(memberValue, "displayName") as string; return string.Equals(a, "Mouse", StringComparison.OrdinalIgnoreCase); } private static bool IsControlActive(object control, string queryMethodName) { if (control == null) { return false; } if (1 == 0) { } string text = ((queryMethodName == "GetButtonDown") ? "wasPressedThisFrame" : ((!(queryMethodName == "GetButtonUp")) ? "isPressed" : "wasReleasedThisFrame")); if (1 == 0) { } string memberName = text; if (TryGetBooleanMember(control, memberName, out var value)) { return value; } MethodInfo method = control.GetType().GetMethod("ReadValueAsButton", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, Type.EmptyTypes, null); if (method != null && method.ReturnType == typeof(bool)) { try { return (bool)method.Invoke(control, null); } catch { } } return false; } private static bool TryParseMouseButton(string path, out int mouseButton) { mouseButton = -1; if (string.IsNullOrWhiteSpace(path) || path.IndexOf("", StringComparison.OrdinalIgnoreCase) < 0) { return false; } string text = path.Replace(" ", string.Empty).ToLowerInvariant(); if (text.EndsWith("/leftbutton", StringComparison.Ordinal)) { mouseButton = 0; } else if (text.EndsWith("/rightbutton", StringComparison.Ordinal)) { mouseButton = 1; } else if (text.EndsWith("/middlebutton", StringComparison.Ordinal)) { mouseButton = 2; } else if (text.EndsWith("/forwardbutton", StringComparison.Ordinal)) { mouseButton = 3; } else if (text.EndsWith("/backbutton", StringComparison.Ordinal)) { mouseButton = 4; } else { int num = text.LastIndexOf("/button", StringComparison.Ordinal); if (num < 0 || !int.TryParse(text.Substring(num + 7), out mouseButton)) { return false; } } return mouseButton >= 0; } private static bool IsMouseButtonActive(int mouseButton, string queryMethodName) { if (TryGetInputSystemMouseButton(mouseButton, out var control) && IsControlActive(control, queryMethodName)) { return true; } try { if (1 == 0) { } bool result = ((queryMethodName == "GetButtonDown") ? Input.GetMouseButtonDown(mouseButton) : ((!(queryMethodName == "GetButtonUp")) ? Input.GetMouseButton(mouseButton) : Input.GetMouseButtonUp(mouseButton))); if (1 == 0) { } return result; } catch { return false; } } private static bool TryGetInputSystemMouseButton(int mouseButton, out object control) { control = null; try { object obj = Type.GetType("UnityEngine.InputSystem.Mouse, Unity.InputSystem", throwOnError: false)?.GetProperty("current", BindingFlags.Static | BindingFlags.Public)?.GetValue(null, null); if (obj == null) { return false; } if (1 == 0) { } string text = mouseButton switch { 0 => "leftButton", 1 => "rightButton", 2 => "middleButton", 3 => "forwardButton", 4 => "backButton", _ => null, }; if (1 == 0) { } string text2 = text; if (text2 == null) { return false; } control = GetMemberValue(obj, text2); return control != null; } catch { return false; } } } [HarmonyPatch] internal static class ZInputMouseBindingCachePatch { private static IEnumerable TargetMethods() { return typeof(ZInput).GetMethods(BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic).Where(delegate(MethodInfo method) { if (method.Name == "Load") { return true; } if (method.Name != "AddButton") { return false; } ParameterInfo[] parameters = method.GetParameters(); return parameters.Length != 0 && parameters[0].ParameterType == typeof(string); }).Cast() .Distinct(); } private static void Postfix(MethodBase __originalMethod, object[] __args) { if (__originalMethod?.Name == "Load") { ZInputMouseBindingResolver.ClearCache(); return; } string action = ((__args != null && __args.Length != 0) ? (__args[0] as string) : null); ZInputMouseBindingResolver.Invalidate(action); } } [HarmonyPatch(typeof(PlayerController), "TakeInput")] [HarmonyPriority(0)] internal static class PlayerControllerTakeInputPatch { private static void Postfix(ref bool __result) { if (ValheimInputState.ShouldBlockAll) { __result = false; } } } [HarmonyPatch(typeof(TextInput), "IsVisible")] [HarmonyPriority(0)] internal static class TextInputIsVisiblePatch { private static void Postfix(ref bool __result) { if (ValheimInputState.ShouldBlockAll) { __result = true; } } } [HarmonyPatch] internal static class ValheimMouseInteractionBlockPatch { private static IEnumerable TargetMethods() { return new MethodInfo[7] { AccessTools.Method(typeof(InventoryGrid), "OnLeftClick", (Type[])null, (Type[])null), AccessTools.Method(typeof(InventoryGrid), "OnRightClick", (Type[])null, (Type[])null), AccessTools.Method(typeof(InventoryGui), "OnSelectedItem", (Type[])null, (Type[])null), AccessTools.Method(typeof(InventoryGui), "OnRightClickItem", (Type[])null, (Type[])null), AccessTools.Method(typeof(Toggle), "OnPointerClick", (Type[])null, (Type[])null), AccessTools.Method(typeof(Button), "OnPointerClick", (Type[])null, (Type[])null), AccessTools.Method(typeof(ScrollRect), "OnScroll", (Type[])null, (Type[])null) }.Where((MethodInfo method) => method != null); } [HarmonyPriority(800)] private static bool Prefix() { return !ValheimInputState.ShouldBlockMouse; } } [HarmonyPatch] internal static class ValheimAllInputInteractionBlockPatch { private static IEnumerable TargetMethods() { return new MethodInfo[4] { AccessTools.Method(typeof(Toggle), "OnSubmit", (Type[])null, (Type[])null), AccessTools.Method(typeof(Button), "OnSubmit", (Type[])null, (Type[])null), AccessTools.Method(typeof(Button), "Press", (Type[])null, (Type[])null), AccessTools.Method(typeof(Player), "UseHotbarItem", (Type[])null, (Type[])null) }.Where((MethodInfo method) => method != null); } [HarmonyPriority(800)] private static bool Prefix() { return !ValheimInputState.ShouldBlockAll; } } [HarmonyPatch] internal static class ZInputAllBooleanBlockPatch { private static IEnumerable TargetMethods() { return ZInputPatchMethods.FindBooleanMethods("ShouldAcceptInputFromSource", "GetKey", "GetKeyUp", "GetKeyDown", "GetButton", "GetButtonDown", "GetButtonUp", "GetRadialTap", "GetRadialMultiTap"); } [HarmonyPriority(800)] private static bool Prefix(ref bool __result) { if (!ValheimInputState.ShouldBlockAll) { return true; } __result = false; return false; } } [HarmonyPatch] internal static class ZInputMouseMappedButtonBlockPatch { private static IEnumerable TargetMethods() { return ZInputPatchMethods.FindStringButtonMethods(); } [HarmonyPriority(0)] private static Exception Finalizer(Exception __exception, MethodBase __originalMethod, object[] __args, ref bool __result) { if (__exception != null || !__result || ValheimInputState.ShouldBlockAll || !ValheimInputState.ShouldBlockMouse) { return __exception; } string action = ((__args != null && __args.Length != 0) ? (__args[0] as string) : null); if (ZInputMouseBindingResolver.IsMouseBindingActive(action, __originalMethod?.Name, actionResultIsKnownTrue: true)) { __result = false; } return __exception; } } [HarmonyPatch] internal static class PlayerSetControlsMouseBlockPatch { private const BindingFlags AllMethods = BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic; private static IEnumerable TargetMethods() { string[] requiredNames = new string[6] { "attack", "attackHold", "secondaryAttack", "secondaryAttackHold", "block", "blockHold" }; return (from method in typeof(Player).GetMethods(BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic) where method.Name == "SetControls" select method).Where(delegate(MethodInfo method) { HashSet hashSet = new HashSet(from parameter in method.GetParameters() select parameter.Name, StringComparer.Ordinal); return requiredNames.All(hashSet.Contains); }).Cast().Distinct(); } [HarmonyPriority(0)] private static void Prefix(ref bool attack, ref bool attackHold, ref bool secondaryAttack, ref bool secondaryAttackHold, ref bool block, ref bool blockHold) { if (ValheimInputState.ShouldBlockMouse && !ValheimInputState.ShouldBlockAll) { if ((attack | attackHold) && ZInputMouseBindingResolver.IsMouseBindingActive("Attack", "GetButton", actionResultIsKnownTrue: true)) { attack = false; attackHold = false; } if ((secondaryAttack | secondaryAttackHold) && ZInputMouseBindingResolver.IsMouseBindingActive("SecondaryAttack", "GetButton", actionResultIsKnownTrue: true)) { secondaryAttack = false; secondaryAttackHold = false; } if ((block | blockHold) && ZInputMouseBindingResolver.IsMouseBindingActive("Block", "GetButton", actionResultIsKnownTrue: true)) { block = false; blockHold = false; } } } } [HarmonyPatch] internal static class ZInputMouseBooleanBlockPatch { private static IEnumerable TargetMethods() { return ZInputPatchMethods.FindBooleanMethods("GetMouseButton", "GetMouseButtonDown", "GetMouseButtonUp"); } [HarmonyPriority(800)] private static bool Prefix(ref bool __result) { if (!ValheimInputState.ShouldBlockMouse) { return true; } __result = false; return false; } } [HarmonyPatch] internal static class ZInputAllFloatBlockPatch { private static IEnumerable TargetMethods() { return new MethodInfo[6] { AccessTools.Method(typeof(ZInput), "GetJoyLeftStickX", (Type[])null, (Type[])null), AccessTools.Method(typeof(ZInput), "GetJoyLeftStickY", (Type[])null, (Type[])null), AccessTools.Method(typeof(ZInput), "GetJoyRTrigger", (Type[])null, (Type[])null), AccessTools.Method(typeof(ZInput), "GetJoyLTrigger", (Type[])null, (Type[])null), AccessTools.Method(typeof(ZInput), "GetJoyRightStickX", (Type[])null, (Type[])null), AccessTools.Method(typeof(ZInput), "GetJoyRightStickY", (Type[])null, (Type[])null) }.Where((MethodInfo method) => method != null); } [HarmonyPriority(0)] private static void Postfix(ref float __result) { if (ValheimInputState.ShouldBlockAll) { __result = 0f; } } } [HarmonyPatch(typeof(ZInput), "GetMouseScrollWheel")] internal static class ZInputMouseScrollBlockPatch { [HarmonyPriority(0)] private static void Postfix(ref float __result) { if (ValheimInputState.ShouldBlockMouse) { __result = 0f; } } } [HarmonyPatch(typeof(ZInput), "GetMouseDelta")] internal static class ZInputMouseDeltaBlockPatch { [HarmonyPriority(0)] private static void Postfix(ref Vector2 __result) { //IL_000b: Unknown result type (might be due to invalid IL or missing references) //IL_0010: Unknown result type (might be due to invalid IL or missing references) if (ValheimInputState.ShouldBlockMouse) { __result = Vector2.zero; } } } internal sealed class ValheimPauseController { private readonly ValheimProfilerConfig _config; private bool _pausedByProfiler; internal ValheimPauseController(ValheimProfilerConfig config) { _config = config; } internal void Update(bool hasVisibleWindows) { if (!_config.PauseGame.Value || !Object.op_Implicit((Object)(object)Game.instance)) { if (_pausedByProfiler) { Release(); } } else if (hasVisibleWindows) { if (!_pausedByProfiler && !Game.IsPaused() && Game.CanPause()) { Game.Pause(); _pausedByProfiler = true; } } else if (_pausedByProfiler) { Release(); } } internal void Release() { if (_pausedByProfiler && Object.op_Implicit((Object)(object)Game.instance) && !Menu.IsActive() && Game.IsPaused()) { Game.Unpause(); } _pausedByProfiler = false; } } } namespace ValheimProfiler.UI { internal sealed class GuiScaleController { private readonly ValheimProfilerConfig _config; private bool _temporaryUnity6000Window = true; internal float ScaleFactor { get { float num = Mathf.Clamp(_config.UiScale.Value, 0.25f, 4f); if (_config.UseValheimGuiScale.Value) { num *= GetValheimGuiScale(); } return Mathf.Max(0.01f, num); } } internal int PhysicalWidth => _temporaryUnity6000Window ? GetDisplayWidth() : Mathf.Max(1, Screen.width); internal int PhysicalHeight => _temporaryUnity6000Window ? GetDisplayHeight() : Mathf.Max(1, Screen.height); internal float LogicalWidth => (float)PhysicalWidth / ScaleFactor; internal float LogicalHeight => (float)PhysicalHeight / ScaleFactor; internal Matrix4x4 Matrix => Matrix4x4.TRS(Vector3.zero, Quaternion.identity, new Vector3(ScaleFactor, ScaleFactor, 1f)); internal GuiScaleController(ValheimProfilerConfig config) { _config = config; } internal void MarkGameWindowReady() { _temporaryUnity6000Window = false; } internal Vector2 GetLogicalMousePosition() { //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_000a: Unknown result type (might be due to invalid IL or missing references) //IL_000f: Unknown result type (might be due to invalid IL or missing references) //IL_0017: Unknown result type (might be due to invalid IL or missing references) //IL_001c: Unknown result type (might be due to invalid IL or missing references) //IL_0021: Unknown result type (might be due to invalid IL or missing references) //IL_0022: Unknown result type (might be due to invalid IL or missing references) //IL_002e: Unknown result type (might be due to invalid IL or missing references) //IL_0035: 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_003d: Unknown result type (might be due to invalid IL or missing references) Matrix4x4 val = Matrix; val = ((Matrix4x4)(ref val)).inverse; Vector3 val2 = ((Matrix4x4)(ref val)).MultiplyPoint(UnityInput.Current.mousePosition); return new Vector2(val2.x, LogicalHeight - val2.y); } private float GetValheimGuiScale() { try { float num = (float)PhysicalWidth / (float)GuiScaler.m_minWidth; float num2 = (float)PhysicalHeight / (float)GuiScaler.m_minHeight; return Mathf.Max(0.01f, Mathf.Min(num, num2) * GuiScaler.m_largeGuiScale); } catch { return 1f; } } private static int GetDisplayWidth() { try { return (Display.main != null) ? Mathf.Max(1, Display.main.systemWidth) : Mathf.Max(1, Screen.width); } catch { return Mathf.Max(1, Screen.width); } } private static int GetDisplayHeight() { try { return (Display.main != null) ? Mathf.Max(1, Display.main.systemHeight) : Mathf.Max(1, Screen.height); } catch { return Mathf.Max(1, Screen.height); } } } internal static class ProfilerGui { internal static bool ToggleLayout(ThemeManager theme, bool value, GUIContent content, float width, GUIStyle labelStyle = null, float verticalOffset = 1f) { //IL_0043: Unknown result type (might be due to invalid IL or missing references) //IL_0048: Unknown result type (might be due to invalid IL or missing references) //IL_004a: Unknown result type (might be due to invalid IL or missing references) GUIStyle val = labelStyle ?? GUI.skin.label; float num = Mathf.Max(18f, val.lineHeight + 4f); Rect rect = GUILayoutUtility.GetRect(width, num, (GUILayoutOption[])(object)new GUILayoutOption[2] { GUILayout.Width(width), GUILayout.Height(num) }); return Toggle(theme, rect, value, content, val, verticalOffset); } internal static bool Toggle(ThemeManager theme, Rect rect, bool value, GUIContent content, GUIStyle labelStyle = null, float verticalOffset = 1f) { //IL_002e: Unknown result type (might be due to invalid IL or missing references) //IL_0034: Expected O, but got Unknown //IL_0034: Unknown result type (might be due to invalid IL or missing references) //IL_009a: 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_00bd: Expected O, but got Unknown //IL_0119: Unknown result type (might be due to invalid IL or missing references) //IL_0121: Unknown result type (might be due to invalid IL or missing references) //IL_012c: Expected O, but got Unknown GUIStyle val = labelStyle ?? GUI.skin.label; string text = ((content != null) ? content.tooltip : null) ?? string.Empty; GUIContent val2 = new GUIContent(string.Empty, text); bool flag = GUI.Toggle(rect, value, val2, GUIStyle.none); float num = theme?.CompactToggleSize ?? 10f; float num2 = Mathf.Ceil(Mathf.Max(0f, (((Rect)(ref rect)).height - num) * 0.5f)); Rect val3 = default(Rect); ((Rect)(ref val3))..ctor(((Rect)(ref rect)).x + 1f, ((Rect)(ref rect)).y + num2 + verticalOffset, num, num); GUI.Box(val3, new GUIContent(string.Empty, text), flag ? theme.CompactToggleOnStyle : theme.CompactToggleOffStyle); string text2 = ((content != null) ? content.text : null) ?? string.Empty; if (!string.IsNullOrEmpty(text2)) { float num3 = ((Rect)(ref val3)).xMax + 4f; GUI.Label(new Rect(num3, ((Rect)(ref rect)).y, Mathf.Max(0f, ((Rect)(ref rect)).xMax - num3), ((Rect)(ref rect)).height), new GUIContent(text2, text), val); } return flag; } } internal sealed class ProfilerWindow : IDisposable { private readonly ConfigEntry _positionConfig; private readonly ConfigEntry _sizeConfig; private readonly object _layoutSync = new object(); private bool _layoutChangePending; private Vector2 _pendingPosition; private Vector2 _pendingSize; internal string Key { get; } internal string Title { get; set; } internal int WindowId { get; } internal Rect DefaultRect { get; } internal Vector2 MinimumSize { get; } internal bool Resizable { get; } internal bool CenterWhenPositionIsNegative { get; } internal bool AllowTooltipOverflow { get; } internal Action DrawContents { get; } internal WindowFunction WindowFunction { get; set; } internal bool RequestedVisible { get; set; } internal Rect Rect { get; set; } internal bool LayoutDirty { get; set; } internal float SaveAfterRealtime { get; set; } internal ProfilerWindow(string key, string title, Rect defaultRect, Vector2 minimumSize, bool resizable, bool requestedVisible, Action drawContents, ConfigEntry positionConfig = null, ConfigEntry sizeConfig = null, bool centerWhenPositionIsNegative = false, bool allowTooltipOverflow = false) { //IL_0023: Unknown result type (might be due to invalid IL or missing references) //IL_0025: 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_0078: 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_0096: 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_009b: Unknown result type (might be due to invalid IL or missing references) //IL_009c: Unknown result type (might be due to invalid IL or missing references) //IL_009f: Unknown result type (might be due to invalid IL or missing references) //IL_00a4: Unknown result type (might be due to invalid IL or missing references) //IL_00a9: Unknown result type (might be due to invalid IL or missing references) //IL_00ad: Unknown result type (might be due to invalid IL or missing references) //IL_00b4: Unknown result type (might be due to invalid IL or missing references) //IL_00b9: Unknown result type (might be due to invalid IL or missing references) //IL_00be: Unknown result type (might be due to invalid IL or missing references) //IL_00c4: Unknown result type (might be due to invalid IL or missing references) //IL_00c5: Unknown result type (might be due to invalid IL or missing references) //IL_00c6: Unknown result type (might be due to invalid IL or missing references) //IL_00eb: Unknown result type (might be due to invalid IL or missing references) //IL_0102: Unknown result type (might be due to invalid IL or missing references) //IL_0126: Unknown result type (might be due to invalid IL or missing references) Key = key; Title = title; MinimumSize = minimumSize; Resizable = resizable; RequestedVisible = requestedVisible; DrawContents = drawContents ?? throw new ArgumentNullException("drawContents"); _positionConfig = positionConfig; _sizeConfig = sizeConfig; CenterWhenPositionIsNegative = centerWhenPositionIsNegative; AllowTooltipOverflow = allowTooltipOverflow; Vector2 val = positionConfig?.Value ?? ((Rect)(ref defaultRect)).position; Vector2 val2 = ResolveConfiguredSize(sizeConfig?.Value ?? ((Rect)(ref defaultRect)).size, ((Rect)(ref defaultRect)).size); DefaultRect = new Rect(((Rect)(ref defaultRect)).position, ((Rect)(ref defaultRect)).size); Rect = new Rect(val, val2); WindowId = StableHash(key); if (_sizeConfig != null && (_sizeConfig.Value.x <= 0f || _sizeConfig.Value.y <= 0f)) { _sizeConfig.Value = val2; } if (_positionConfig != null) { _positionConfig.SettingChanged += OnLayoutConfigChanged; } if (_sizeConfig != null) { _sizeConfig.SettingChanged += OnLayoutConfigChanged; } } internal void ApplyPendingLayoutChanges() { //IL_0027: Unknown result type (might be due to invalid IL or missing references) //IL_002c: Unknown result type (might be due to invalid IL or missing references) //IL_002e: Unknown result type (might be due to invalid IL or missing references) //IL_0033: Unknown result type (might be due to invalid IL or missing references) //IL_004b: Unknown result type (might be due to invalid IL or missing references) //IL_0050: 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_0096: Unknown result type (might be due to invalid IL or missing references) //IL_007a: Unknown result type (might be due to invalid IL or missing references) //IL_007c: 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_0085: Unknown result type (might be due to invalid IL or missing references) //IL_008a: Unknown result type (might be due to invalid IL or missing references) Vector2 pendingPosition; Vector2 pendingSize; lock (_layoutSync) { if (!_layoutChangePending) { return; } pendingPosition = _pendingPosition; pendingSize = _pendingSize; _layoutChangePending = false; } Rect rect = Rect; if (_positionConfig != null) { ((Rect)(ref rect)).position = pendingPosition; } if (_sizeConfig != null) { Rect defaultRect = DefaultRect; ((Rect)(ref rect)).size = ResolveConfiguredSize(pendingSize, ((Rect)(ref defaultRect)).size); } Rect = rect; LayoutDirty = false; } internal void ResetLayoutToDefault() { //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_002c: Unknown result type (might be due to invalid IL or missing references) //IL_0018: Unknown result type (might be due to invalid IL or missing references) //IL_001d: Unknown result type (might be due to invalid IL or missing references) //IL_0020: Unknown result type (might be due to invalid IL or missing references) Rect defaultRect = DefaultRect; if (_sizeConfig == null) { Rect rect = Rect; ((Rect)(ref defaultRect)).size = ((Rect)(ref rect)).size; } Rect = defaultRect; LayoutDirty = false; } internal void SaveLayout() { //IL_0015: 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_001d: Unknown result type (might be due to invalid IL or missing references) //IL_003c: Unknown result type (might be due to invalid IL or missing references) //IL_0041: Unknown result type (might be due to invalid IL or missing references) //IL_0044: Unknown result type (might be due to invalid IL or missing references) Rect rect; if (_positionConfig != null) { ConfigEntry positionConfig = _positionConfig; rect = Rect; positionConfig.Value = ((Rect)(ref rect)).position; } if (_sizeConfig != null) { ConfigEntry sizeConfig = _sizeConfig; rect = Rect; sizeConfig.Value = ((Rect)(ref rect)).size; } LayoutDirty = false; } public void Dispose() { if (_positionConfig != null) { _positionConfig.SettingChanged -= OnLayoutConfigChanged; } if (_sizeConfig != null) { _sizeConfig.SettingChanged -= OnLayoutConfigChanged; } } private void OnLayoutConfigChanged(object sender, EventArgs e) { //IL_002f: Unknown result type (might be due to invalid IL or missing references) //IL_0020: Unknown result type (might be due to invalid IL or missing references) //IL_0025: Unknown result type (might be due to invalid IL or missing references) //IL_0028: Unknown result type (might be due to invalid IL or missing references) //IL_0034: Unknown result type (might be due to invalid IL or missing references) //IL_0054: 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_004a: Unknown result type (might be due to invalid IL or missing references) //IL_004d: Unknown result type (might be due to invalid IL or missing references) //IL_0059: Unknown result type (might be due to invalid IL or missing references) lock (_layoutSync) { ConfigEntry positionConfig = _positionConfig; Rect rect; Vector2 pendingPosition; if (positionConfig == null) { rect = Rect; pendingPosition = ((Rect)(ref rect)).position; } else { pendingPosition = positionConfig.Value; } _pendingPosition = pendingPosition; ConfigEntry sizeConfig = _sizeConfig; Vector2 pendingSize; if (sizeConfig == null) { rect = Rect; pendingSize = ((Rect)(ref rect)).size; } else { pendingSize = sizeConfig.Value; } _pendingSize = pendingSize; _layoutChangePending = true; } } private static Vector2 ResolveConfiguredSize(Vector2 configured, Vector2 fallback) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0016: Unknown result type (might be due to invalid IL or missing references) //IL_000e: 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_0031: Unknown result type (might be due to invalid IL or missing references) //IL_0029: 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_003c: Unknown result type (might be due to invalid IL or missing references) //IL_003f: Unknown result type (might be due to invalid IL or missing references) return new Vector2((configured.x > 0f) ? configured.x : fallback.x, (configured.y > 0f) ? configured.y : fallback.y); } private static int StableHash(string value) { uint num = 2166136261u; string text = value ?? string.Empty; foreach (char c in text) { num ^= c; num *= 16777619; } int num2 = (int)(num & 0x7FFFFFFF); return (num2 == 0) ? 1 : num2; } } internal sealed class ThemeManager { private readonly ValheimProfilerConfig _config; private GUISkin _sourceSkin; private GUISkin _runtimeSkin; private bool _dirty = true; private Texture2D _windowTexture; private Texture2D _borderTexture; private Texture2D _entryTexture; private Texture2D _buttonTexture; private Texture2D _buttonHoverTexture; private Texture2D _buttonActiveTexture; private Texture2D _accentTexture; private Texture2D _accentHoverTexture; private Texture2D _scrollbarTrackTexture; private Texture2D _tooltipTexture; private Texture2D _toggleOffTexture; private Texture2D _toggleOnTexture; private Texture2D _toggleOffHoverTexture; private Texture2D _toggleOnHoverTexture; private GUIStyle _accentButtonStyle; private GUIStyle _accentLabelStyle; private GUIStyle _tooltipStyle; private GUIStyle _resizeHandleStyle; private GUIStyle _compactToggleOffStyle; private GUIStyle _compactToggleOnStyle; internal Color TextColor => _config.TextColor.Value; internal Color HeaderTextColor => _config.HeaderTextColor.Value; internal Color AccentColor => _config.AccentColor.Value; internal GUISkin Skin { get { EnsureStyles(); return _runtimeSkin ?? GUI.skin; } } internal Texture2D WindowTexture { get { EnsureStyles(); return _windowTexture; } } internal Texture2D BorderTexture { get { EnsureStyles(); return _borderTexture; } } internal GUIStyle AccentButtonStyle { get { EnsureStyles(); return _accentButtonStyle ?? GUI.skin.button; } } internal GUIStyle AccentLabelStyle { get { EnsureStyles(); return _accentLabelStyle ?? GUI.skin.label; } } internal GUIStyle TooltipStyle { get { EnsureStyles(); return _tooltipStyle ?? GUI.skin.box; } } internal GUIStyle ResizeHandleStyle { get { EnsureStyles(); return _resizeHandleStyle ?? GUI.skin.box; } } internal GUIStyle CompactToggleOffStyle { get { EnsureStyles(); return _compactToggleOffStyle ?? GUI.skin.box; } } internal GUIStyle CompactToggleOnStyle { get { EnsureStyles(); return _compactToggleOnStyle ?? GUI.skin.box; } } internal float CompactToggleSize { get { EnsureStyles(); return Mathf.Clamp((float)_config.FontSize.Value - 2f, 9f, 16f); } } internal ThemeManager(ValheimProfilerConfig config) { _config = config; _config.FontSize.SettingChanged += OnThemeChanged; _config.WindowBackground.SettingChanged += OnThemeChanged; _config.WindowBorder.SettingChanged += OnThemeChanged; _config.EntryBackground.SettingChanged += OnThemeChanged; _config.TextColor.SettingChanged += OnThemeChanged; _config.HeaderTextColor.SettingChanged += OnThemeChanged; _config.ButtonBackground.SettingChanged += OnThemeChanged; _config.ButtonTextColor.SettingChanged += OnThemeChanged; _config.AccentColor.SettingChanged += OnThemeChanged; } internal void EnsureStyles() { GUISkin skin = GUI.skin; if (_dirty || !((Object)(object)_runtimeSkin != (Object)null) || (!((Object)(object)_sourceSkin == (Object)(object)skin) && !((Object)(object)_runtimeSkin == (Object)(object)skin))) { Rebuild(((Object)(object)skin == (Object)(object)_runtimeSkin) ? _sourceSkin : skin); } } internal void Shutdown() { _config.FontSize.SettingChanged -= OnThemeChanged; _config.WindowBackground.SettingChanged -= OnThemeChanged; _config.WindowBorder.SettingChanged -= OnThemeChanged; _config.EntryBackground.SettingChanged -= OnThemeChanged; _config.TextColor.SettingChanged -= OnThemeChanged; _config.HeaderTextColor.SettingChanged -= OnThemeChanged; _config.ButtonBackground.SettingChanged -= OnThemeChanged; _config.ButtonTextColor.SettingChanged -= OnThemeChanged; _config.AccentColor.SettingChanged -= OnThemeChanged; DestroyResources(); } private void OnThemeChanged(object sender, EventArgs e) { _dirty = true; } private void Rebuild(GUISkin baseSkin) { //IL_002c: Unknown result type (might be due to invalid IL or missing references) //IL_0031: Unknown result type (might be due to invalid IL or missing references) //IL_003d: Unknown result type (might be due to invalid IL or missing references) //IL_0042: Unknown result type (might be due to invalid IL or missing references) //IL_004e: 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_005f: Unknown result type (might be due to invalid IL or missing references) //IL_0064: 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_007d: 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_0099: Unknown result type (might be due to invalid IL or missing references) //IL_00a5: Unknown result type (might be due to invalid IL or missing references) //IL_00ab: Unknown result type (might be due to invalid IL or missing references) //IL_00bb: Unknown result type (might be due to invalid IL or missing references) //IL_00c1: Unknown result type (might be due to invalid IL or missing references) //IL_00d1: Unknown result type (might be due to invalid IL or missing references) //IL_00dd: Unknown result type (might be due to invalid IL or missing references) //IL_00e3: Unknown result type (might be due to invalid IL or missing references) //IL_00f3: Unknown result type (might be due to invalid IL or missing references) //IL_00f9: Unknown result type (might be due to invalid IL or missing references) //IL_0109: Unknown result type (might be due to invalid IL or missing references) //IL_010f: Unknown result type (might be due to invalid IL or missing references) //IL_0115: Unknown result type (might be due to invalid IL or missing references) //IL_0120: Unknown result type (might be due to invalid IL or missing references) //IL_012f: Unknown result type (might be due to invalid IL or missing references) //IL_0130: Unknown result type (might be due to invalid IL or missing references) //IL_0131: Unknown result type (might be due to invalid IL or missing references) //IL_0136: Unknown result type (might be due to invalid IL or missing references) //IL_0143: Unknown result type (might be due to invalid IL or missing references) //IL_0148: Unknown result type (might be due to invalid IL or missing references) //IL_014b: Unknown result type (might be due to invalid IL or missing references) //IL_014d: Unknown result type (might be due to invalid IL or missing references) //IL_015a: Unknown result type (might be due to invalid IL or missing references) //IL_015b: Unknown result type (might be due to invalid IL or missing references) //IL_0168: Unknown result type (might be due to invalid IL or missing references) //IL_016f: Unknown result type (might be due to invalid IL or missing references) //IL_0174: Unknown result type (might be due to invalid IL or missing references) //IL_017b: Unknown result type (might be due to invalid IL or missing references) //IL_018b: Unknown result type (might be due to invalid IL or missing references) //IL_0191: Unknown result type (might be due to invalid IL or missing references) //IL_0196: Unknown result type (might be due to invalid IL or missing references) //IL_019d: Unknown result type (might be due to invalid IL or missing references) //IL_01fd: Unknown result type (might be due to invalid IL or missing references) //IL_0202: Unknown result type (might be due to invalid IL or missing references) //IL_020f: Unknown result type (might be due to invalid IL or missing references) //IL_0214: Unknown result type (might be due to invalid IL or missing references) //IL_0221: Unknown result type (might be due to invalid IL or missing references) //IL_0226: Unknown result type (might be due to invalid IL or missing references) //IL_0233: Unknown result type (might be due to invalid IL or missing references) //IL_0248: Unknown result type (might be due to invalid IL or missing references) //IL_025d: Unknown result type (might be due to invalid IL or missing references) //IL_0272: Unknown result type (might be due to invalid IL or missing references) //IL_0287: Unknown result type (might be due to invalid IL or missing references) //IL_029c: Unknown result type (might be due to invalid IL or missing references) //IL_02b1: Unknown result type (might be due to invalid IL or missing references) //IL_02e2: Unknown result type (might be due to invalid IL or missing references) //IL_02ec: Expected O, but got Unknown //IL_02fc: Unknown result type (might be due to invalid IL or missing references) //IL_0306: Expected O, but got Unknown //IL_0316: Unknown result type (might be due to invalid IL or missing references) //IL_0320: Expected O, but got Unknown //IL_0347: Unknown result type (might be due to invalid IL or missing references) //IL_0351: Expected O, but got Unknown //IL_0361: Unknown result type (might be due to invalid IL or missing references) //IL_036b: Expected O, but got Unknown //IL_037b: Unknown result type (might be due to invalid IL or missing references) //IL_0385: Expected O, but got Unknown //IL_03ac: Unknown result type (might be due to invalid IL or missing references) //IL_03b6: Expected O, but got Unknown //IL_03c6: Unknown result type (might be due to invalid IL or missing references) //IL_03d0: Expected O, but got Unknown //IL_03e0: Unknown result type (might be due to invalid IL or missing references) //IL_03ea: Expected O, but got Unknown //IL_03fa: Unknown result type (might be due to invalid IL or missing references) //IL_0404: Expected O, but got Unknown //IL_0414: Unknown result type (might be due to invalid IL or missing references) //IL_041e: Expected O, but got Unknown //IL_042e: Unknown result type (might be due to invalid IL or missing references) //IL_0438: Expected O, but got Unknown //IL_0448: Unknown result type (might be due to invalid IL or missing references) //IL_0452: Expected O, but got Unknown //IL_0462: Unknown result type (might be due to invalid IL or missing references) //IL_046c: Expected O, but got Unknown //IL_0498: Unknown result type (might be due to invalid IL or missing references) //IL_049d: Unknown result type (might be due to invalid IL or missing references) //IL_04ae: Expected O, but got Unknown //IL_0528: Unknown result type (might be due to invalid IL or missing references) //IL_052d: Unknown result type (might be due to invalid IL or missing references) //IL_0539: Unknown result type (might be due to invalid IL or missing references) //IL_0541: Unknown result type (might be due to invalid IL or missing references) //IL_054e: Expected O, but got Unknown //IL_0554: Unknown result type (might be due to invalid IL or missing references) //IL_055a: Unknown result type (might be due to invalid IL or missing references) //IL_0573: Unknown result type (might be due to invalid IL or missing references) //IL_0578: Unknown result type (might be due to invalid IL or missing references) //IL_0584: Unknown result type (might be due to invalid IL or missing references) //IL_058c: Unknown result type (might be due to invalid IL or missing references) //IL_0594: Unknown result type (might be due to invalid IL or missing references) //IL_059c: Unknown result type (might be due to invalid IL or missing references) //IL_05a3: Unknown result type (might be due to invalid IL or missing references) //IL_05ad: Expected O, but got Unknown //IL_05ae: Unknown result type (might be due to invalid IL or missing references) //IL_05b3: Unknown result type (might be due to invalid IL or missing references) //IL_05bd: Expected O, but got Unknown //IL_05be: Unknown result type (might be due to invalid IL or missing references) //IL_05c3: Unknown result type (might be due to invalid IL or missing references) //IL_05cd: Expected O, but got Unknown //IL_05d3: Expected O, but got Unknown //IL_05d9: Unknown result type (might be due to invalid IL or missing references) //IL_0601: Unknown result type (might be due to invalid IL or missing references) //IL_0606: Unknown result type (might be due to invalid IL or missing references) //IL_0612: Unknown result type (might be due to invalid IL or missing references) //IL_0617: Unknown result type (might be due to invalid IL or missing references) //IL_0621: Expected O, but got Unknown //IL_0622: Unknown result type (might be due to invalid IL or missing references) //IL_0627: Unknown result type (might be due to invalid IL or missing references) //IL_0631: Expected O, but got Unknown //IL_0632: Unknown result type (might be due to invalid IL or missing references) //IL_0637: Unknown result type (might be due to invalid IL or missing references) //IL_0641: Expected O, but got Unknown //IL_0647: Expected O, but got Unknown //IL_0693: Unknown result type (might be due to invalid IL or missing references) //IL_0698: Unknown result type (might be due to invalid IL or missing references) //IL_06a4: Unknown result type (might be due to invalid IL or missing references) //IL_06a9: Unknown result type (might be due to invalid IL or missing references) //IL_06b3: Expected O, but got Unknown //IL_06b4: Unknown result type (might be due to invalid IL or missing references) //IL_06b9: Unknown result type (might be due to invalid IL or missing references) //IL_06c3: Expected O, but got Unknown //IL_06c4: Unknown result type (might be due to invalid IL or missing references) //IL_06c9: Unknown result type (might be due to invalid IL or missing references) //IL_06d3: Expected O, but got Unknown //IL_06d9: Expected O, but got Unknown //IL_0720: Unknown result type (might be due to invalid IL or missing references) //IL_0725: Unknown result type (might be due to invalid IL or missing references) //IL_0731: Unknown result type (might be due to invalid IL or missing references) //IL_0736: Unknown result type (might be due to invalid IL or missing references) //IL_0740: Expected O, but got Unknown //IL_0746: Expected O, but got Unknown DestroyResources(); _sourceSkin = baseSkin; if (!((Object)(object)baseSkin == (Object)null)) { Color value = _config.WindowBackground.Value; Color value2 = _config.EntryBackground.Value; Color value3 = _config.ButtonBackground.Value; Color value4 = _config.AccentColor.Value; _windowTexture = CreateTexture(value); _borderTexture = CreateTexture(_config.WindowBorder.Value); _entryTexture = CreateTexture(value2); _buttonTexture = CreateTexture(value3); _buttonHoverTexture = CreateTexture(Lighten(value3, 0.12f)); _buttonActiveTexture = CreateTexture(Darken(value4, 0.1f)); _accentTexture = CreateTexture(value4); _accentHoverTexture = CreateTexture(Lighten(value4, 0.12f)); _scrollbarTrackTexture = CreateTexture(Darken(value2, 0.08f)); _tooltipTexture = CreateTexture(new Color(value2.r, value2.g, value2.b, 1f)); Color val = CreateNeutralToggleColor(value2, value3); Color value5 = _config.WindowBorder.Value; _toggleOffTexture = CreateBorderedTexture(val, value5); _toggleOnTexture = CreateBorderedTexture(value4, value5); _toggleOffHoverTexture = CreateBorderedTexture(Lighten(val, 0.12f), Lighten(value5, 0.2f)); _toggleOnHoverTexture = CreateBorderedTexture(Lighten(value4, 0.12f), Lighten(value5, 0.2f)); _runtimeSkin = Object.Instantiate(baseSkin); ((Object)_runtimeSkin).name = "ValheimProfilerSkin"; ((Object)_runtimeSkin).hideFlags = (HideFlags)61; int fontSize = Mathf.Clamp(_config.FontSize.Value, 9, 28); Color value6 = _config.TextColor.Value; Color value7 = _config.HeaderTextColor.Value; Color value8 = _config.ButtonTextColor.Value; ConfigureTextStyle(_runtimeSkin.label, value6, fontSize); ConfigureTextStyle(_runtimeSkin.box, value6, fontSize); ConfigureTextStyle(_runtimeSkin.window, value7, fontSize); ConfigureTextStyle(_runtimeSkin.button, value8, fontSize); ConfigureTextStyle(_runtimeSkin.toggle, value6, fontSize); ConfigureTextStyle(_runtimeSkin.textField, value6, fontSize); ConfigureTextStyle(_runtimeSkin.textArea, value6, fontSize); SetAllBackgrounds(_runtimeSkin.window, _windowTexture); _runtimeSkin.window.padding = new RectOffset(4, 4, 21, 4); _runtimeSkin.window.margin = new RectOffset(0, 0, 0, 0); _runtimeSkin.window.border = new RectOffset(0, 0, 0, 0); SetAllBackgrounds(_runtimeSkin.box, _entryTexture); _runtimeSkin.box.padding = new RectOffset(4, 4, 3, 3); _runtimeSkin.box.margin = new RectOffset(1, 1, 1, 1); _runtimeSkin.box.border = new RectOffset(0, 0, 0, 0); SetAllBackgrounds(_runtimeSkin.textField, _entryTexture); _runtimeSkin.textField.padding = new RectOffset(4, 4, 1, 1); _runtimeSkin.textField.margin = new RectOffset(1, 1, 1, 1); _runtimeSkin.textField.border = new RectOffset(0, 0, 0, 0); _runtimeSkin.button.padding = new RectOffset(5, 5, 2, 2); _runtimeSkin.button.margin = new RectOffset(2, 2, 1, 1); _runtimeSkin.button.border = new RectOffset(0, 0, 0, 0); _runtimeSkin.toggle.margin = new RectOffset(1, 1, 1, 1); _runtimeSkin.label.margin = new RectOffset(1, 1, 0, 0); SetButtonBackgrounds(_runtimeSkin.button); ConfigureSquareScrollbars(_runtimeSkin); _accentButtonStyle = new GUIStyle(_runtimeSkin.button) { name = "ValheimProfilerAccentButton" }; SetAllBackgrounds(_accentButtonStyle, _accentTexture); _accentButtonStyle.hover.background = _accentHoverTexture; _accentButtonStyle.onHover.background = _accentHoverTexture; _accentButtonStyle.active.background = _buttonActiveTexture; _accentButtonStyle.onActive.background = _buttonActiveTexture; _accentLabelStyle = new GUIStyle(_runtimeSkin.label) { name = "ValheimProfilerAccentLabel", fontStyle = (FontStyle)1, alignment = (TextAnchor)3 }; ConfigureTextStyle(_accentLabelStyle, Lighten(value4, 0.35f), fontSize); _tooltipStyle = new GUIStyle(_runtimeSkin.box) { name = "ValheimProfilerTooltip", wordWrap = true, richText = false, alignment = (TextAnchor)3, padding = new RectOffset(10, 10, 5, 5), margin = new RectOffset(0, 0, 0, 0), border = new RectOffset(0, 0, 0, 0) }; ConfigureTextStyle(_tooltipStyle, value6, fontSize); SetAllBackgrounds(_tooltipStyle, _tooltipTexture); _resizeHandleStyle = new GUIStyle(_runtimeSkin.box) { name = "ValheimProfilerResizeHandle", padding = new RectOffset(0, 0, 0, 0), margin = new RectOffset(0, 0, 0, 0), border = new RectOffset(0, 0, 0, 0) }; SetAllBackgrounds(_resizeHandleStyle, _accentTexture); _resizeHandleStyle.hover.background = _accentHoverTexture; _resizeHandleStyle.active.background = _buttonActiveTexture; _compactToggleOffStyle = new GUIStyle(_runtimeSkin.box) { name = "ValheimProfilerCompactToggleOff", padding = new RectOffset(0, 0, 0, 0), margin = new RectOffset(0, 0, 0, 0), border = new RectOffset(1, 1, 1, 1) }; SetAllBackgrounds(_compactToggleOffStyle, _toggleOffTexture); _compactToggleOffStyle.hover.background = _toggleOffHoverTexture; _compactToggleOffStyle.active.background = _toggleOffHoverTexture; _compactToggleOnStyle = new GUIStyle(_compactToggleOffStyle) { name = "ValheimProfilerCompactToggleOn", border = new RectOffset(1, 1, 1, 1) }; SetAllBackgrounds(_compactToggleOnStyle, _toggleOnTexture); _compactToggleOnStyle.hover.background = _toggleOnHoverTexture; _compactToggleOnStyle.active.background = _toggleOnHoverTexture; _dirty = false; } } private void ConfigureSquareScrollbars(GUISkin skin) { //IL_0057: Unknown result type (might be due to invalid IL or missing references) //IL_0061: Expected O, but got Unknown if (skin.scrollView != null) { RectOffset padding = skin.scrollView.padding; skin.scrollView.padding = new RectOffset((padding != null) ? padding.left : 0, Mathf.Max(1, (padding != null) ? padding.right : 0), (padding != null) ? padding.top : 0, (padding != null) ? padding.bottom : 0); } ConfigureScrollbarTrack(skin.horizontalScrollbar, horizontal: true, 13f); ConfigureScrollbarTrack(skin.verticalScrollbar, horizontal: false, 13f); ConfigureScrollbarThumb(skin.horizontalScrollbarThumb, horizontal: true, 13f); ConfigureScrollbarThumb(skin.verticalScrollbarThumb, horizontal: false, 13f); HideScrollbarButton(skin.horizontalScrollbarLeftButton); HideScrollbarButton(skin.horizontalScrollbarRightButton); HideScrollbarButton(skin.verticalScrollbarUpButton); HideScrollbarButton(skin.verticalScrollbarDownButton); } private void ConfigureScrollbarTrack(GUIStyle style, bool horizontal, float size) { //IL_001d: Unknown result type (might be due to invalid IL or missing references) //IL_0027: Expected O, but got Unknown //IL_003b: Unknown result type (might be due to invalid IL or missing references) //IL_0030: Unknown result type (might be due to invalid IL or missing references) //IL_0045: Expected O, but got Unknown //IL_004b: Unknown result type (might be due to invalid IL or missing references) //IL_0055: Expected O, but got Unknown if (style != null) { SetAllBackgrounds(style, _scrollbarTrackTexture); style.border = new RectOffset(0, 0, 0, 0); style.margin = (horizontal ? new RectOffset(0, 0, 0, 0) : new RectOffset(0, 1, 0, 0)); style.padding = new RectOffset(0, 0, 0, 0); if (horizontal) { style.fixedHeight = size; } else { style.fixedWidth = size; } } } private void ConfigureScrollbarThumb(GUIStyle style, bool horizontal, float size) { //IL_0044: Unknown result type (might be due to invalid IL or missing references) //IL_004e: Expected O, but got Unknown //IL_0054: Unknown result type (might be due to invalid IL or missing references) //IL_005e: Expected O, but got Unknown //IL_0064: Unknown result type (might be due to invalid IL or missing references) //IL_006e: Expected O, but got Unknown if (style != null) { SetAllBackgrounds(style, _accentTexture); style.hover.background = _accentHoverTexture; style.active.background = _buttonActiveTexture; style.border = new RectOffset(0, 0, 0, 0); style.margin = new RectOffset(1, 1, 1, 1); style.padding = new RectOffset(0, 0, 0, 0); if (horizontal) { style.fixedHeight = Mathf.Max(1f, size - 2f); } else { style.fixedWidth = Mathf.Max(1f, size - 2f); } } } private static void HideScrollbarButton(GUIStyle style) { //IL_0018: Unknown result type (might be due to invalid IL or missing references) //IL_0022: Expected O, but got Unknown //IL_0028: Unknown result type (might be due to invalid IL or missing references) //IL_0032: Expected O, but got Unknown //IL_0038: Unknown result type (might be due to invalid IL or missing references) //IL_0042: Expected O, but got Unknown if (style != null) { SetAllBackgrounds(style, null); style.border = new RectOffset(0, 0, 0, 0); style.margin = new RectOffset(0, 0, 0, 0); style.padding = new RectOffset(0, 0, 0, 0); style.fixedWidth = 0f; style.fixedHeight = 0f; style.stretchWidth = false; style.stretchHeight = false; } } private void SetButtonBackgrounds(GUIStyle style) { if (style != null) { style.normal.background = _buttonTexture; style.onNormal.background = _accentTexture; style.hover.background = _buttonHoverTexture; style.onHover.background = _buttonHoverTexture; style.active.background = _buttonActiveTexture; style.onActive.background = _buttonActiveTexture; style.focused.background = _buttonTexture; style.onFocused.background = _accentTexture; } } private static void SetAllBackgrounds(GUIStyle style, Texture2D texture) { if (style != null) { style.normal.background = texture; style.hover.background = texture; style.active.background = texture; style.focused.background = texture; style.onNormal.background = texture; style.onHover.background = texture; style.onActive.background = texture; style.onFocused.background = texture; } } private static void ConfigureTextStyle(GUIStyle style, Color color, int fontSize) { //IL_0019: Unknown result type (might be due to invalid IL or missing references) //IL_0026: Unknown result type (might be due to invalid IL or missing references) //IL_0033: Unknown result type (might be due to invalid IL or missing references) //IL_0040: Unknown result type (might be due to invalid IL or missing references) //IL_004d: Unknown result type (might be due to invalid IL or missing references) //IL_005a: Unknown result type (might be due to invalid IL or missing references) //IL_0067: Unknown result type (might be due to invalid IL or missing references) //IL_0074: Unknown result type (might be due to invalid IL or missing references) if (style != null) { style.fontSize = fontSize; SetTextColor(style.normal, color); SetTextColor(style.hover, color); SetTextColor(style.active, color); SetTextColor(style.focused, color); SetTextColor(style.onNormal, color); SetTextColor(style.onHover, color); SetTextColor(style.onActive, color); SetTextColor(style.onFocused, color); } } private static void SetTextColor(GUIStyleState state, Color color) { //IL_000a: Unknown result type (might be due to invalid IL or missing references) if (state != null) { state.textColor = color; } } private static Texture2D CreateTexture(Color color) { //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) //IL_0013: Unknown result type (might be due to invalid IL or missing references) //IL_0020: Expected O, but got Unknown //IL_0023: Unknown result type (might be due to invalid IL or missing references) Texture2D val = new Texture2D(1, 1, (TextureFormat)4, false) { hideFlags = (HideFlags)61, name = "ValheimProfilerColor" }; val.SetPixel(0, 0, color); val.Apply(false, true); return val; } private static Texture2D CreateBorderedTexture(Color fill, Color border) { //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) //IL_0013: Unknown result type (might be due to invalid IL or missing references) //IL_0020: Expected O, but got Unknown //IL_003d: 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) Texture2D val = new Texture2D(3, 3, (TextureFormat)4, false) { hideFlags = (HideFlags)61, name = "ValheimProfilerBorderedColor" }; for (int i = 0; i < 3; i++) { for (int j = 0; j < 3; j++) { val.SetPixel(j, i, (j == 0 || i == 0 || j == 2 || i == 2) ? border : fill); } } val.Apply(false, true); return val; } private static Color Lighten(Color color, float amount) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0011: Unknown result type (might be due to invalid IL or missing references) //IL_0022: Unknown result type (might be due to invalid IL or missing references) //IL_0033: Unknown result type (might be due to invalid IL or missing references) //IL_0039: Unknown result type (might be due to invalid IL or missing references) return new Color(Mathf.Lerp(color.r, 1f, amount), Mathf.Lerp(color.g, 1f, amount), Mathf.Lerp(color.b, 1f, amount), color.a); } private static Color Darken(Color color, float amount) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_000e: 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_002a: Unknown result type (might be due to invalid IL or missing references) //IL_0030: Unknown result type (might be due to invalid IL or missing references) return new Color(color.r * (1f - amount), color.g * (1f - amount), color.b * (1f - amount), color.a); } private static Color CreateNeutralToggleColor(Color entry, Color button) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_0008: 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_000e: 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_0027: Unknown result type (might be due to invalid IL or missing references) //IL_004e: 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_0056: Unknown result type (might be due to invalid IL or missing references) Color val = Color.Lerp(entry, button, 0.55f); float num = val.r * 0.299f + val.g * 0.587f + val.b * 0.114f; num = Mathf.Lerp(num, 1f, 0.08f); return new Color(num, num, num, 1f); } private void DestroyResources() { Destroy((Object)(object)_runtimeSkin); Destroy((Object)(object)_windowTexture); Destroy((Object)(object)_borderTexture); Destroy((Object)(object)_entryTexture); Destroy((Object)(object)_buttonTexture); Destroy((Object)(object)_buttonHoverTexture); Destroy((Object)(object)_buttonActiveTexture); Destroy((Object)(object)_accentTexture); Destroy((Object)(object)_accentHoverTexture); Destroy((Object)(object)_scrollbarTrackTexture); Destroy((Object)(object)_tooltipTexture); Destroy((Object)(object)_toggleOffTexture); Destroy((Object)(object)_toggleOnTexture); Destroy((Object)(object)_toggleOffHoverTexture); Destroy((Object)(object)_toggleOnHoverTexture); _runtimeSkin = null; _windowTexture = null; _borderTexture = null; _entryTexture = null; _buttonTexture = null; _buttonHoverTexture = null; _buttonActiveTexture = null; _accentTexture = null; _accentHoverTexture = null; _scrollbarTrackTexture = null; _tooltipTexture = null; _toggleOffTexture = null; _toggleOnTexture = null; _toggleOffHoverTexture = null; _toggleOnHoverTexture = null; _accentButtonStyle = null; _accentLabelStyle = null; _tooltipStyle = null; _resizeHandleStyle = null; _compactToggleOffStyle = null; _compactToggleOnStyle = null; } private static void Destroy(Object value) { if (value != (Object)null) { Object.Destroy(value); } } } internal sealed class TooltipManager { private const float PointerOffsetY = 25f; private const float MaxAbsoluteWidth = 620f; private const float MaxRelativeWidth = 0.8f; private const float ExtraHeight = 10f; private readonly ThemeManager _theme; internal TooltipManager(ThemeManager theme) { _theme = theme; } internal void Draw(Rect area) { //IL_000b: Unknown result type (might be due to invalid IL or missing references) //IL_0010: Unknown result type (might be due to invalid IL or missing references) Draw(GUI.tooltip, Event.current.mousePosition, area); } internal void Draw(string rawTooltip, Vector2 pointerPosition, Rect area) { //IL_0048: Unknown result type (might be due to invalid IL or missing references) //IL_004e: Expected O, but got Unknown //IL_0073: Unknown result type (might be due to invalid IL or missing references) //IL_0081: Expected O, but got Unknown //IL_00fd: Unknown result type (might be due to invalid IL or missing references) //IL_00fe: Unknown result type (might be due to invalid IL or missing references) //IL_0100: Unknown result type (might be due to invalid IL or missing references) //IL_0112: Unknown result type (might be due to invalid IL or missing references) //IL_0126: Unknown result type (might be due to invalid IL or missing references) //IL_014e: Unknown result type (might be due to invalid IL or missing references) //IL_013f: Unknown result type (might be due to invalid IL or missing references) //IL_01ac: Unknown result type (might be due to invalid IL or missing references) if (string.IsNullOrEmpty(rawTooltip)) { return; } GUIStyle tooltipStyle = _theme.TooltipStyle; if (tooltipStyle != null) { string text = rawTooltip.Replace("\r\n", "\n").Replace('\r', '\n'); GUIContent val = new GUIContent(text); float num = 0f; string[] array = text.Split(new char[1] { '\n' }); float num2 = default(float); float num3 = default(float); for (int i = 0; i < array.Length; i++) { tooltipStyle.CalcMinMaxWidth(new GUIContent(array[i]), ref num2, ref num3); num = Mathf.Max(num, num3); } float num4 = Mathf.Max(1f, Mathf.Min(620f, ((Rect)(ref area)).width * 0.8f)); num = Mathf.Clamp(num + 2f, 1f, num4); float num5 = Mathf.Min(tooltipStyle.CalcHeight(val, num) + 10f, Mathf.Max(1f, ((Rect)(ref area)).height)); float num6 = ((pointerPosition.x + num > ((Rect)(ref area)).xMax) ? (((Rect)(ref area)).xMax - num) : pointerPosition.x); float num7 = ((pointerPosition.y + 25f + num5 > ((Rect)(ref area)).yMax) ? (pointerPosition.y - num5) : (pointerPosition.y + 25f)); num6 = Mathf.Clamp(num6, ((Rect)(ref area)).xMin, Mathf.Max(((Rect)(ref area)).xMin, ((Rect)(ref area)).xMax - num)); num7 = Mathf.Clamp(num7, ((Rect)(ref area)).yMin, Mathf.Max(((Rect)(ref area)).yMin, ((Rect)(ref area)).yMax - num5)); GUI.Box(new Rect(num6, num7, num, num5), text, tooltipStyle); } } } internal sealed class WindowManager { internal const float DefaultToolWindowWidthFraction = 0.75f; internal const float DefaultCompactWindowWidthFraction = 0.25f; private const float TitleBarHeight = 22f; private const float ResizeEdgeHitSize = 4f; private const float ResizeHandleHitSize = 12f; private const float ResizeHandleVisualSize = 8f; private const float WindowBorderWidth = 1f; private const float LayoutSaveDelay = 0.5f; private readonly ValheimProfilerConfig _config; private readonly GuiScaleController _scale; private readonly ThemeManager _theme; private readonly TooltipManager _tooltips; private readonly List _windows = new List(); private ProfilerWindow _resizingWindow; private Vector2 _resizeStartMouse; private Rect _resizeStartRect; private bool _resizeWidth; private bool _resizeHeight; private int _bringToFrontId; private bool _resetLayoutRequested; private string _overflowTooltip = string.Empty; private Vector2 _overflowTooltipPointer; internal IReadOnlyList Windows => _windows; internal bool HasRequestedVisibleWindows { get { for (int i = 0; i < _windows.Count; i++) { if (_windows[i].RequestedVisible) { return true; } } return false; } } internal WindowManager(ValheimProfilerConfig config, GuiScaleController scale, ThemeManager theme, TooltipManager tooltips) { _config = config; _scale = scale; _theme = theme; _tooltips = tooltips; } internal Vector2 GetDefaultToolWindowSize(float preferredHeight, Vector2 minimumSize) { //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_0008: Unknown result type (might be due to invalid IL or missing references) return GetDefaultWindowSize(preferredHeight, minimumSize, 0.75f); } internal Vector2 GetDefaultCompactWindowSize(float preferredHeight, Vector2 minimumSize) { //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_0008: Unknown result type (might be due to invalid IL or missing references) return GetDefaultWindowSize(preferredHeight, minimumSize, 0.25f); } private Vector2 GetDefaultWindowSize(float preferredHeight, Vector2 minimumSize, float widthFraction) { //IL_0001: 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) //IL_0021: 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_004a: Unknown result type (might be due to invalid IL or missing references) //IL_004d: Unknown result type (might be due to invalid IL or missing references) float num = Mathf.Max(minimumSize.x, _scale.LogicalWidth * widthFraction); float num2 = Mathf.Clamp(preferredHeight, minimumSize.y, Mathf.Max(minimumSize.y, _scale.LogicalHeight * 0.9f)); return new Vector2(num, num2); } internal ProfilerWindow Register(ProfilerWindow window) { //IL_003a: Unknown result type (might be due to invalid IL or missing references) //IL_0044: Expected O, but got Unknown if (window == null) { throw new ArgumentNullException("window"); } window.WindowFunction = (WindowFunction)delegate(int id) { DrawWindow(window, id); }; _windows.Add(window); return window; } internal void BringToFront(ProfilerWindow window) { if (window != null) { _bringToFrontId = window.WindowId; } } internal void DrawAll() { //IL_0056: Unknown result type (might be due to invalid IL or missing references) //IL_005b: Unknown result type (might be due to invalid IL or missing references) //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_0075: Unknown result type (might be due to invalid IL or missing references) //IL_0090: Unknown result type (might be due to invalid IL or missing references) //IL_009f: Unknown result type (might be due to invalid IL or missing references) //IL_00a4: Unknown result type (might be due to invalid IL or missing references) //IL_00af: Unknown result type (might be due to invalid IL or missing references) //IL_00b1: Unknown result type (might be due to invalid IL or missing references) //IL_012a: Unknown result type (might be due to invalid IL or missing references) //IL_014f: Unknown result type (might be due to invalid IL or missing references) _overflowTooltip = string.Empty; if (_resetLayoutRequested) { ResetLayoutNow(); } for (int i = 0; i < _windows.Count; i++) { ProfilerWindow profilerWindow = _windows[i]; profilerWindow.ApplyPendingLayoutChanges(); if (profilerWindow.RequestedVisible) { ResolveInitialPosition(profilerWindow); profilerWindow.Rect = ClampRect(profilerWindow, profilerWindow.Rect); Rect rect = profilerWindow.Rect; profilerWindow.Rect = GUI.Window(profilerWindow.WindowId, profilerWindow.Rect, profilerWindow.WindowFunction, profilerWindow.Title, GUI.skin.window); profilerWindow.Rect = ClampRect(profilerWindow, profilerWindow.Rect); if (RectChanged(rect, profilerWindow.Rect)) { MarkLayoutDirty(profilerWindow); } } } if (_bringToFrontId != 0) { GUI.BringWindowToFront(_bringToFrontId); _bringToFrontId = 0; } if (!string.IsNullOrEmpty(_overflowTooltip)) { _tooltips.Draw(_overflowTooltip, _overflowTooltipPointer, new Rect(0f, 0f, _scale.LogicalWidth, _scale.LogicalHeight)); } } internal void RequestResetLayout() { _resetLayoutRequested = true; } internal void UpdatePersistence() { if (_resizingWindow != null && !UnityInput.Current.GetKey((KeyCode)323)) { ClearResizeState(); } float realtimeSinceStartup = Time.realtimeSinceStartup; bool flag = false; for (int i = 0; i < _windows.Count; i++) { ProfilerWindow profilerWindow = _windows[i]; profilerWindow.ApplyPendingLayoutChanges(); if (profilerWindow.LayoutDirty && !(realtimeSinceStartup < profilerWindow.SaveAfterRealtime)) { profilerWindow.SaveLayout(); flag = true; } } if (flag) { _config.ConfigFile.Save(); } } internal void SaveAll() { for (int i = 0; i < _windows.Count; i++) { _windows[i].SaveLayout(); } _config.ConfigFile.Save(); } internal void Shutdown() { for (int i = 0; i < _windows.Count; i++) { _windows[i].Dispose(); } _windows.Clear(); ClearResizeState(); } private void ResetLayoutNow() { //IL_0034: Unknown result type (might be due to invalid IL or missing references) //IL_0039: Unknown result type (might be due to invalid IL or missing references) _resetLayoutRequested = false; ClearResizeState(); for (int i = 0; i < _windows.Count; i++) { ProfilerWindow profilerWindow = _windows[i]; profilerWindow.ResetLayoutToDefault(); ResolveInitialPosition(profilerWindow); profilerWindow.Rect = ClampRect(profilerWindow, profilerWindow.Rect); profilerWindow.SaveLayout(); } _config.ConfigFile.Save(); } private void DrawWindow(ProfilerWindow window, int id) { //IL_001f: Unknown result type (might be due to invalid IL or missing references) //IL_0024: Unknown result type (might be due to invalid IL or missing references) //IL_0159: Unknown result type (might be due to invalid IL or missing references) //IL_00eb: Unknown result type (might be due to invalid IL or missing references) //IL_00f0: Unknown result type (might be due to invalid IL or missing references) //IL_00fa: Unknown result type (might be due to invalid IL or missing references) //IL_00ff: Unknown result type (might be due to invalid IL or missing references) //IL_0108: Unknown result type (might be due to invalid IL or missing references) //IL_0124: Unknown result type (might be due to invalid IL or missing references) //IL_0129: Unknown result type (might be due to invalid IL or missing references) //IL_0142: Unknown result type (might be due to invalid IL or missing references) //IL_00b7: Unknown result type (might be due to invalid IL or missing references) //IL_00bc: Unknown result type (might be due to invalid IL or missing references) //IL_00c0: Unknown result type (might be due to invalid IL or missing references) //IL_00c6: Unknown result type (might be due to invalid IL or missing references) //IL_00cb: Unknown result type (might be due to invalid IL or missing references) //IL_00d0: Unknown result type (might be due to invalid IL or missing references) _theme.EnsureStyles(); GUI.tooltip = string.Empty; Event current = Event.current; Vector2 mousePosition = current.mousePosition; bool flag = TryApplyRealtimeMousePosition(window, current); try { HandleResizeInput(window); try { window.DrawContents(id); } catch (Exception ex) { GUILayout.Label("Window error: " + ex.GetType().Name + ": " + ex.Message, Array.Empty()); } DrawWindowBorder(window); DrawResizeHandle(window); Rect rect; if (window.AllowTooltipOverflow) { string tooltip = GUI.tooltip; if (!string.IsNullOrEmpty(tooltip)) { _overflowTooltip = tooltip; rect = window.Rect; _overflowTooltipPointer = ((Rect)(ref rect)).position + current.mousePosition; } } else { TooltipManager tooltips = _tooltips; rect = window.Rect; float width = ((Rect)(ref rect)).width; rect = window.Rect; tooltips.Draw(new Rect(0f, 0f, width, ((Rect)(ref rect)).height)); } rect = window.Rect; GUI.DragWindow(new Rect(0f, 0f, Mathf.Max(0f, ((Rect)(ref rect)).width - 4f), 22f)); } finally { if (flag) { current.mousePosition = mousePosition; } } } private bool TryApplyRealtimeMousePosition(ProfilerWindow window, Event current) { //IL_0014: Unknown result type (might be due to invalid IL or missing references) //IL_001a: Invalid comparison between Unknown and I4 //IL_001d: Unknown result type (might be due to invalid IL or missing references) //IL_0023: Invalid comparison between Unknown and I4 //IL_0039: Unknown result type (might be due to invalid IL or missing references) //IL_003f: Unknown result type (might be due to invalid IL or missing references) //IL_0044: Unknown result type (might be due to invalid IL or missing references) //IL_0048: Unknown result type (might be due to invalid IL or missing references) //IL_004d: Unknown result type (might be due to invalid IL or missing references) //IL_0052: 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_0060: Unknown result type (might be due to invalid IL or missing references) //IL_007c: Unknown result type (might be due to invalid IL or missing references) if (window == null || current == null) { return false; } if ((int)current.type != 7 && (int)current.type != 8) { return false; } Vector2 logicalMousePosition = _scale.GetLogicalMousePosition(); Rect rect = window.Rect; Vector2 val = logicalMousePosition - ((Rect)(ref rect)).position; if (!IsFinite(val.x) || !IsFinite(val.y)) { return false; } current.mousePosition = val; return true; } private static bool IsFinite(float value) { return !float.IsNaN(value) && !float.IsInfinity(value); } private void HandleResizeInput(ProfilerWindow window) { //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) //IL_0023: Unknown result type (might be due to invalid IL or missing references) //IL_0028: Unknown result type (might be due to invalid IL or missing references) //IL_002b: Unknown result type (might be due to invalid IL or missing references) //IL_0035: Unknown result type (might be due to invalid IL or missing references) //IL_003c: Unknown result type (might be due to invalid IL or missing references) //IL_0041: Unknown result type (might be due to invalid IL or missing references) //IL_0052: 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_005e: 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_0099: Unknown result type (might be due to invalid IL or missing references) //IL_0069: Unknown result type (might be due to invalid IL or missing references) //IL_00e3: Unknown result type (might be due to invalid IL or missing references) //IL_00aa: Unknown result type (might be due to invalid IL or missing references) //IL_00b1: Unknown result type (might be due to invalid IL or missing references) //IL_00b6: Unknown result type (might be due to invalid IL or missing references) //IL_0076: Unknown result type (might be due to invalid IL or missing references) //IL_00c1: Unknown result type (might be due to invalid IL or missing references) //IL_00ce: Unknown result type (might be due to invalid IL or missing references) //IL_0111: Unknown result type (might be due to invalid IL or missing references) //IL_0116: Unknown result type (might be due to invalid IL or missing references) //IL_011d: Unknown result type (might be due to invalid IL or missing references) //IL_0122: Unknown result type (might be due to invalid IL or missing references) //IL_0179: Unknown result type (might be due to invalid IL or missing references) //IL_017f: Unknown result type (might be due to invalid IL or missing references) //IL_0184: Unknown result type (might be due to invalid IL or missing references) //IL_0189: Unknown result type (might be due to invalid IL or missing references) //IL_018c: Unknown result type (might be due to invalid IL or missing references) //IL_0191: Unknown result type (might be due to invalid IL or missing references) //IL_01ac: Unknown result type (might be due to invalid IL or missing references) //IL_01e4: Unknown result type (might be due to invalid IL or missing references) //IL_01e6: Unknown result type (might be due to invalid IL or missing references) //IL_01f9: Unknown result type (might be due to invalid IL or missing references) //IL_01fe: Unknown result type (might be due to invalid IL or missing references) //IL_0200: Unknown result type (might be due to invalid IL or missing references) //IL_01d3: Unknown result type (might be due to invalid IL or missing references) //IL_0206: Unknown result type (might be due to invalid IL or missing references) //IL_0209: Invalid comparison between Unknown and I4 if (!window.Resizable) { return; } Event current = Event.current; Vector2 mousePosition = current.mousePosition; Rect resizeHandleHitRect = GetResizeHandleHitRect(window); bool flag = ((Rect)(ref resizeHandleHitRect)).Contains(mousePosition); Rect rect; int num; if (!flag) { float x = mousePosition.x; rect = window.Rect; if (x >= ((Rect)(ref rect)).width - 4f) { float x2 = mousePosition.x; rect = window.Rect; if (x2 <= ((Rect)(ref rect)).width && mousePosition.y >= 22f) { num = ((mousePosition.y < ((Rect)(ref resizeHandleHitRect)).yMin) ? 1 : 0); goto IL_0088; } } } num = 0; goto IL_0088; IL_00e0: int num2; bool flag2 = (byte)num2 != 0; bool flag3; if ((int)current.type == 0 && current.button == 0 && (flag || flag3 || flag2)) { _resizingWindow = window; _resizeStartMouse = _scale.GetLogicalMousePosition(); _resizeStartRect = window.Rect; _resizeWidth = flag || flag3; _resizeHeight = flag || flag2; current.Use(); } if (_resizingWindow != window) { return; } if (UnityInput.Current.GetKey((KeyCode)323)) { Vector2 val = _scale.GetLogicalMousePosition() - _resizeStartMouse; Rect resizeStartRect = _resizeStartRect; if (_resizeWidth) { ((Rect)(ref resizeStartRect)).width = ((Rect)(ref _resizeStartRect)).width + val.x; } if (_resizeHeight) { ((Rect)(ref resizeStartRect)).height = ((Rect)(ref _resizeStartRect)).height + val.y; } window.Rect = ClampRect(window, resizeStartRect); MarkLayoutDirty(window); EventType type = current.type; if (((int)type == 0 || (int)type == 3) ? true : false) { current.Use(); } } if (UnityInput.Current.GetKeyUp((KeyCode)323)) { ClearResizeState(); } return; IL_0088: flag3 = (byte)num != 0; if (!flag) { float y = mousePosition.y; rect = window.Rect; if (y >= ((Rect)(ref rect)).height - 4f) { float y2 = mousePosition.y; rect = window.Rect; if (y2 <= ((Rect)(ref rect)).height && mousePosition.x >= 0f) { num2 = ((mousePosition.x < ((Rect)(ref resizeHandleHitRect)).xMin) ? 1 : 0); goto IL_00e0; } } } num2 = 0; goto IL_00e0; } private void DrawWindowBorder(ProfilerWindow window) { //IL_0023: Unknown result type (might be due to invalid IL or missing references) //IL_0028: Unknown result type (might be due to invalid IL or missing references) //IL_003d: Unknown result type (might be due to invalid IL or missing references) //IL_0042: 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_007f: Unknown result type (might be due to invalid IL or missing references) //IL_009b: Unknown result type (might be due to invalid IL or missing references) //IL_00b9: Unknown result type (might be due to invalid IL or missing references) Texture2D borderTexture = _theme.BorderTexture; if (!((Object)(object)borderTexture == (Object)null)) { Rect rect = window.Rect; float num = Mathf.Max(1f, ((Rect)(ref rect)).width); rect = window.Rect; float num2 = Mathf.Max(1f, ((Rect)(ref rect)).height); GUI.DrawTexture(new Rect(0f, 0f, num, 1f), (Texture)(object)borderTexture); GUI.DrawTexture(new Rect(0f, num2 - 1f, num, 1f), (Texture)(object)borderTexture); GUI.DrawTexture(new Rect(0f, 0f, 1f, num2), (Texture)(object)borderTexture); GUI.DrawTexture(new Rect(num - 1f, 0f, 1f, num2), (Texture)(object)borderTexture); } } private void DrawResizeHandle(ProfilerWindow window) { //IL_0016: 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) //IL_0072: Unknown result type (might be due to invalid IL or missing references) //IL_0078: Expected O, but got Unknown //IL_0078: Unknown result type (might be due to invalid IL or missing references) //IL_008a: Unknown result type (might be due to invalid IL or missing references) //IL_0090: Invalid comparison between Unknown and I4 //IL_0035: Unknown result type (might be due to invalid IL or missing references) //IL_00a0: Unknown result type (might be due to invalid IL or missing references) //IL_00c2: Unknown result type (might be due to invalid IL or missing references) //IL_0120: Unknown result type (might be due to invalid IL or missing references) //IL_0147: Unknown result type (might be due to invalid IL or missing references) if (window.Resizable) { Rect resizeHandleHitRect = GetResizeHandleHitRect(window); Texture2D windowTexture = _theme.WindowTexture; if ((Object)(object)windowTexture != (Object)null) { GUI.DrawTexture(resizeHandleHitRect, (Texture)(object)windowTexture); } Rect val = default(Rect); ((Rect)(ref val))..ctor(((Rect)(ref resizeHandleHitRect)).xMax - 8f, ((Rect)(ref resizeHandleHitRect)).yMax - 8f, 8f, 8f); GUIContent val2 = new GUIContent(string.Empty, "Drag to resize"); GUI.Label(val, val2, GUIStyle.none); if ((int)Event.current.type == 7) { bool flag = ((Rect)(ref val)).Contains(Event.current.mousePosition); bool flag2 = _resizingWindow == window; _theme.ResizeHandleStyle.Draw(val, val2, flag, flag2, false, false); } Texture2D borderTexture = _theme.BorderTexture; if (!((Object)(object)borderTexture == (Object)null)) { float num = ((Rect)(ref val)).xMax - 1f; float num2 = ((Rect)(ref val)).yMax - 1f; GUI.DrawTexture(new Rect(num - 3f, num2, 3f, 1f), (Texture)(object)borderTexture); GUI.DrawTexture(new Rect(num - 1f, num2 - 2f, 1f, 2f), (Texture)(object)borderTexture); } } } private static Rect GetResizeHandleHitRect(ProfilerWindow window) { //IL_0007: 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_002b: Unknown result type (might be due to invalid IL or missing references) //IL_0030: 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_0058: Unknown result type (might be due to invalid IL or missing references) //IL_005b: Unknown result type (might be due to invalid IL or missing references) Rect rect = window.Rect; float num = Mathf.Max(0f, ((Rect)(ref rect)).width - 12f - 2f); rect = window.Rect; return new Rect(num, Mathf.Max(0f, ((Rect)(ref rect)).height - 12f - 2f), 12f, 12f); } private void ClearResizeState() { _resizingWindow = null; _resizeWidth = false; _resizeHeight = false; } private void ResolveInitialPosition(ProfilerWindow window) { //IL_000a: Unknown result type (might be due to invalid IL or missing references) //IL_000f: Unknown result type (might be due to invalid IL or missing references) //IL_002b: Unknown result type (might be due to invalid IL or missing references) //IL_0030: Unknown result type (might be due to invalid IL or missing references) //IL_0076: Unknown result type (might be due to invalid IL or missing references) if (window.CenterWhenPositionIsNegative) { Rect rect = window.Rect; if (!(((Rect)(ref rect)).x >= 0f)) { Rect rect2 = window.Rect; ((Rect)(ref rect2)).x = Mathf.Max(0f, (_scale.LogicalWidth - ((Rect)(ref rect2)).width) * 0.5f); ((Rect)(ref rect2)).y = Mathf.Max(0f, ((Rect)(ref rect2)).y); window.Rect = rect2; } } } private Rect ClampRect(ProfilerWindow window, Rect rect) { //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_001e: Unknown result type (might be due to invalid IL or missing references) //IL_0043: Unknown result type (might be due to invalid IL or missing references) //IL_0063: Unknown result type (might be due to invalid IL or missing references) //IL_00e1: Unknown result type (might be due to invalid IL or missing references) //IL_00e2: Unknown result type (might be due to invalid IL or missing references) //IL_00e5: Unknown result type (might be due to invalid IL or missing references) float num = Mathf.Max(window.MinimumSize.x, _scale.LogicalWidth); float num2 = Mathf.Max(window.MinimumSize.y, _scale.LogicalHeight); ((Rect)(ref rect)).width = Mathf.Clamp(((Rect)(ref rect)).width, window.MinimumSize.x, num); ((Rect)(ref rect)).height = Mathf.Clamp(((Rect)(ref rect)).height, window.MinimumSize.y, num2); ((Rect)(ref rect)).x = Mathf.Clamp(((Rect)(ref rect)).x, 0f, Mathf.Max(0f, _scale.LogicalWidth - 40f)); ((Rect)(ref rect)).y = Mathf.Clamp(((Rect)(ref rect)).y, 0f, Mathf.Max(0f, _scale.LogicalHeight - 22f)); return rect; } private static bool RectChanged(Rect left, Rect right) { return Mathf.Abs(((Rect)(ref left)).x - ((Rect)(ref right)).x) > 0.01f || Mathf.Abs(((Rect)(ref left)).y - ((Rect)(ref right)).y) > 0.01f || Mathf.Abs(((Rect)(ref left)).width - ((Rect)(ref right)).width) > 0.01f || Mathf.Abs(((Rect)(ref left)).height - ((Rect)(ref right)).height) > 0.01f; } private static void MarkLayoutDirty(ProfilerWindow window) { window.LayoutDirty = true; window.SaveAfterRealtime = Time.realtimeSinceStartup + 0.5f; } } } namespace ValheimProfiler.Tools.ValheimUpdateProfiler { internal sealed class ValheimUpdateProfilerTool : IProfilerTool { private struct BatchTimingState { internal long StartTicks; internal int Items; internal ValheimUpdateKind Kind; internal string Scope; internal object FirstItem; } private struct TopLevelTimingState { internal long StartTicks; internal ValheimUpdateKind Kind; internal bool PreviousActive; internal ValheimUpdateKind PreviousKind; internal long PreviousMeasuredTicks; } private struct WearTimingState { internal long StartTicks; internal bool FullPass; internal int InstanceCount; internal int StartIndex; internal int PlannedItems; internal int UpdatesPerFrame; internal float TimeArgument; internal float SleepUntilNext; internal object FirstItem; } private enum MainTab { Profiler, Help } [ThreadStatic] private static ValheimUpdateKind _activeTopLevelKind; [ThreadStatic] private static bool _topLevelActive; [ThreadStatic] private static long _activeMeasuredTicks; internal const string ToolId = "ValheimUpdateProfiler"; internal const string HarmonyId = "shudnal.ValheimProfiler.ValheimUpdateProfiler"; internal const string DisplayTitle = "Valheim Update Profiler"; private const string TotalScope = "$total"; private const string MeasuredScope = "$measured"; private const string RemainderScope = "$remainder"; private const string WearFullPassScope = "WearNTear.Full pass"; private const string WearBatchScope = "WearNTear.Wear batch"; private const string WearTotalScope = "WearNTear.Total"; private static readonly HashSet KnownVanillaScopes = new HashSet(StringComparer.Ordinal) { "MonoUpdaters.FixedUpdate.ZSyncTransform", "MonoUpdaters.FixedUpdate.ZSyncAnimation", "MonoUpdaters.FixedUpdate.Floating", "MonoUpdaters.FixedUpdate.Ship", "MonoUpdaters.FixedUpdate.Fish", "MonoUpdaters.FixedUpdate.CharacterAnimEvent", "MonoUpdaters.FixedUpdate.BaseAI", "MonoUpdaters.FixedUpdate.Character", "MonoUpdaters.FixedUpdate.Aoe", "MonoUpdaters.FixedUpdate.EffectArea", "MonoUpdaters.FixedUpdate.RandomFlyingBird", "MonoUpdaters.FixedUpdate.MeleeWeaponTrail", "MonoUpdaters.Update.Smoke", "MonoUpdaters.Update.ZSFX", "MonoUpdaters.Update.VisEquipment", "MonoUpdaters.Update.FootStep", "MonoUpdaters.Update.InstanceRenderer", "MonoUpdaters.Update.WaterTrigger", "MonoUpdaters.Update.LightFlicker", "MonoUpdaters.Update.SmokeSpawner", "MonoUpdaters.Update.CraftingStation", "MonoUpdaters.LateUpdate.ZSyncTransform", "MonoUpdaters.LateUpdate.CharacterAnimEvent", "MonoUpdaters.LateUpdate.Heightmap", "MonoUpdaters.LateUpdate.ShipEffects", "MonoUpdaters.LateUpdate.Tail", "MonoUpdaters.LateUpdate.LineAttach" }; private static readonly double MsPerTick = 1000.0 / (double)Stopwatch.Frequency; private static int _mainThreadId; private static ValheimUpdateProfilerTool _instance; private readonly ValheimProfilerApp _app; private readonly ManualLogSource _logger; private readonly WindowManager _windows; private readonly ThemeManager _theme; private readonly Harmony _harmony; private readonly ProfilerWindow _window; private readonly object _sync = new object(); private readonly Dictionary _entries = new Dictionary(); private readonly Dictionary _groupExpanded = new Dictionary(); private bool _profilingActive; private bool _statsFrozen; private float _frozenRealtime; private int _frozenFrame; private float _nextAnalyticsUpdate; private string _status = "Ready. Start profiling to instrument centralized Valheim update loops."; private ValheimUpdateView _view; private ValheimUpdateSortColumn _avgSortColumn; private ValheimUpdateSortColumn _maxSortColumn; private string _search = string.Empty; private Vector2 _scroll; private Vector2 _helpScroll; private GUISkin _styleSkin; private GUIStyle _labelStyle; private GUIStyle _headerStyle; private GUIStyle _activeHeaderStyle; private GUIStyle _groupStyle; private GUIStyle _compactButtonStyle; private int _wearCurrentInstances; private int _wearCurrentUpdatesPerFrame; private int _wearCurrentIndex; private bool _wearCycleActive; private float _wearCycleStartRealtime; private double _wearLastCycleDurationMs; private double _wearMaxCycleDurationMs; private double _wearLastCycleLagMs; private double _wearMaxCycleLagMs; private MainTab _mainTab; private const float ScopeWidth = 420f; private const float NumberWidth = 92f; private const float WideNumberWidth = 112f; string IProfilerTool.Id => "ValheimUpdateProfiler"; string IProfilerTool.DisplayName => "Valheim Updates"; bool IProfilerTool.IsWindowVisible => IsWindowVisible; bool IProfilerTool.IsActive => _profilingActive; internal bool IsWindowVisible { get { return _window.RequestedVisible; } set { _window.RequestedVisible = value; } } private int DisplayFrame => _statsFrozen ? _frozenFrame : Time.frameCount; private float DisplayRealtime => _statsFrozen ? _frozenRealtime : Time.realtimeSinceStartup; private static void CustomFixedPrefix(List container, List source, string profileScope, ref BatchTimingState __state) { BeginBatch(container, source, profileScope, ValheimUpdateKind.FixedUpdate, ref __state); } private static Exception CustomFixedFinalizer(Exception __exception, BatchTimingState __state) { return EndBatch(__exception, __state); } private static void CustomUpdatePrefix(List container, List source, string profileScope, ref BatchTimingState __state) { BeginBatch(container, source, profileScope, ValheimUpdateKind.Update, ref __state); } private static Exception CustomUpdateFinalizer(Exception __exception, BatchTimingState __state) { return EndBatch(__exception, __state); } private static void CustomLatePrefix(List container, List source, string profileScope, ref BatchTimingState __state) { BeginBatch(container, source, profileScope, ValheimUpdateKind.LateUpdate, ref __state); } private static Exception CustomLateFinalizer(Exception __exception, BatchTimingState __state) { return EndBatch(__exception, __state); } private static void UpdateAIPrefix(List container, List source, string profileScope, ref BatchTimingState __state) { BeginBatch(container, source, profileScope, ValheimUpdateKind.AI, ref __state); } private static Exception UpdateAIFinalizer(Exception __exception, BatchTimingState __state) { return EndBatch(__exception, __state); } private static void FixedUpdatePrefix(ref TopLevelTimingState __state) { BeginTopLevel(ValheimUpdateKind.FixedUpdate, ref __state); } private static Exception FixedUpdateFinalizer(Exception __exception, TopLevelTimingState __state) { return EndTopLevel(__exception, __state); } private static void UpdatePrefix(ref TopLevelTimingState __state) { BeginTopLevel(ValheimUpdateKind.Update, ref __state); } private static Exception UpdateFinalizer(Exception __exception, TopLevelTimingState __state) { return EndTopLevel(__exception, __state); } private static void LateUpdatePrefix(ref TopLevelTimingState __state) { BeginTopLevel(ValheimUpdateKind.LateUpdate, ref __state); } private static Exception LateUpdateFinalizer(Exception __exception, TopLevelTimingState __state) { return EndTopLevel(__exception, __state); } private static void WearPrefix(WearNTearUpdater __instance, float time, ref WearTimingState __state) { __state.StartTicks = Stopwatch.GetTimestamp(); __state.TimeArgument = time; __state.SleepUntilNext = __instance?.m_sleepUntilNext ?? 0f; __state.StartIndex = __instance?.m_index ?? 0; __state.UpdatesPerFrame = __instance?.m_updatesPerFrame ?? 0; __state.FullPass = (Object)(object)__instance != (Object)null && __instance.m_sleepUntilNext.Equals(__instance.m_sleepUntil); try { List allInstances = WearNTear.GetAllInstances(); __state.InstanceCount = allInstances?.Count ?? 0; __state.PlannedItems = (__state.FullPass ? __state.InstanceCount : Math.Min(Math.Max(0, __state.UpdatesPerFrame), Math.Max(0, __state.InstanceCount - __state.StartIndex))); if (__state.InstanceCount > 0) { int index = ((!__state.FullPass) ? Math.Min(Math.Max(0, __state.StartIndex), __state.InstanceCount - 1) : 0); __state.FirstItem = allInstances[index]; } } catch { __state.InstanceCount = 0; __state.PlannedItems = 0; __state.FirstItem = null; } } private static Exception WearFinalizer(Exception __exception, WearNTearUpdater __instance, WearTimingState __state) { try { ValheimUpdateProfilerTool instance = _instance; if (instance == null || !instance._profilingActive || __state.StartTicks <= 0) { return __exception; } if (Thread.CurrentThread.ManagedThreadId != _mainThreadId) { return __exception; } long num = Stopwatch.GetTimestamp() - __state.StartTicks; if (num < 0) { return __exception; } double elapsedMs = (double)num * MsPerTick; int frameCount = Time.frameCount; float realtimeSinceStartup = Time.realtimeSinceStartup; instance.RecordWear(__state, __instance, elapsedMs, frameCount, realtimeSinceStartup); } catch { } return __exception; } private static void BeginBatch(List container, List source, string profileScope, ValheimUpdateKind kind, ref BatchTimingState state) where T : class { state.StartTicks = Stopwatch.GetTimestamp(); state.Kind = kind; state.Scope = profileScope ?? string.Empty; int num = container?.Count ?? 0; int num2 = source?.Count ?? 0; state.Items = num + num2; state.FirstItem = ((num2 > 0) ? source[0] : ((num > 0) ? container[0] : null)); } private static Exception EndBatch(Exception exception, BatchTimingState state) { try { ValheimUpdateProfilerTool instance = _instance; if (instance == null || !instance._profilingActive || state.StartTicks <= 0) { return exception; } if (Thread.CurrentThread.ManagedThreadId != _mainThreadId) { return exception; } long num = Stopwatch.GetTimestamp() - state.StartTicks; if (num < 0) { return exception; } if (_topLevelActive) { _activeMeasuredTicks += num; } double elapsedMs = (double)num * MsPerTick; instance.RecordScope(state.Kind, state.Scope, state.Items, state.FirstItem, elapsedMs, Time.frameCount, Time.realtimeSinceStartup); } catch { } return exception; } private static void BeginTopLevel(ValheimUpdateKind kind, ref TopLevelTimingState state) { state.StartTicks = Stopwatch.GetTimestamp(); state.Kind = kind; state.PreviousActive = _topLevelActive; state.PreviousKind = _activeTopLevelKind; state.PreviousMeasuredTicks = _activeMeasuredTicks; _topLevelActive = true; _activeTopLevelKind = kind; _activeMeasuredTicks = 0L; } private static Exception EndTopLevel(Exception exception, TopLevelTimingState state) { try { ValheimUpdateProfilerTool instance = _instance; if (instance != null && instance._profilingActive && state.StartTicks > 0 && Thread.CurrentThread.ManagedThreadId == _mainThreadId) { long num = Stopwatch.GetTimestamp() - state.StartTicks; if (num >= 0) { long num2 = Math.Max(0L, _activeMeasuredTicks); long num3 = Math.Max(0L, num - num2); instance.RecordTopLevel(state.Kind, (double)num * MsPerTick, (double)num2 * MsPerTick, (double)num3 * MsPerTick, Time.frameCount, Time.realtimeSinceStartup); } } } catch { } finally { _topLevelActive = state.PreviousActive; _activeTopLevelKind = state.PreviousKind; _activeMeasuredTicks = state.PreviousMeasuredTicks; } return exception; } private void StartProfiling() { if (_profilingActive) { return; } try { ResetAllStats(); PatchProfilerTargets(); _profilingActive = true; _statsFrozen = false; _status = "Profiling enabled. MonoUpdatersExtra and WearNTearUpdater are instrumented."; } catch (Exception ex) { _harmony.UnpatchSelf(); _profilingActive = false; _status = "Start error: " + ex.GetType().Name + ": " + ex.Message; _logger.LogError((object)ex); } } private void StopProfiling() { try { _profilingActive = false; _harmony.UnpatchSelf(); _frozenRealtime = Time.realtimeSinceStartup; _frozenFrame = Time.frameCount; _statsFrozen = true; ProcessAllStats(_frozenRealtime); _status = "Profiling paused. Data frozen."; } catch (Exception ex) { _status = "Pause error: " + ex.GetType().Name + ": " + ex.Message; _logger.LogError((object)ex); } } private void PatchProfilerTargets() { PatchMethod(AccessTools.Method(typeof(MonoUpdatersExtra), "CustomFixedUpdate", new Type[4] { typeof(List), typeof(List), typeof(string), typeof(float) }, (Type[])null), "CustomFixedPrefix", "CustomFixedFinalizer"); PatchMethod(AccessTools.Method(typeof(MonoUpdatersExtra), "CustomUpdate", new Type[5] { typeof(List), typeof(List), typeof(string), typeof(float), typeof(float) }, (Type[])null), "CustomUpdatePrefix", "CustomUpdateFinalizer"); PatchMethod(AccessTools.Method(typeof(MonoUpdatersExtra), "CustomLateUpdate", new Type[4] { typeof(List), typeof(List), typeof(string), typeof(float) }, (Type[])null), "CustomLatePrefix", "CustomLateFinalizer"); PatchMethod(AccessTools.Method(typeof(MonoUpdatersExtra), "UpdateAI", new Type[4] { typeof(List), typeof(List), typeof(string), typeof(float) }, (Type[])null), "UpdateAIPrefix", "UpdateAIFinalizer"); PatchMethod(AccessTools.Method(typeof(MonoUpdaters), "FixedUpdate", (Type[])null, (Type[])null), "FixedUpdatePrefix", "FixedUpdateFinalizer"); PatchMethod(AccessTools.Method(typeof(MonoUpdaters), "Update", (Type[])null, (Type[])null), "UpdatePrefix", "UpdateFinalizer"); PatchMethod(AccessTools.Method(typeof(MonoUpdaters), "LateUpdate", (Type[])null, (Type[])null), "LateUpdatePrefix", "LateUpdateFinalizer"); PatchMethod(AccessTools.Method(typeof(WearNTearUpdater), "UpdateWearNTear", new Type[2] { typeof(float), typeof(float) }, (Type[])null), "WearPrefix", "WearFinalizer"); } private void PatchMethod(MethodBase original, string prefixName, string finalizerName) { //IL_0034: Unknown result type (might be due to invalid IL or missing references) //IL_0039: Unknown result type (might be due to invalid IL or missing references) //IL_0041: Expected O, but got Unknown //IL_0053: Unknown result type (might be due to invalid IL or missing references) //IL_0058: Unknown result type (might be due to invalid IL or missing references) //IL_0064: Expected O, but got Unknown if (original == null) { throw new MissingMethodException("Required Valheim method for " + prefixName + " was not found."); } HarmonyMethod val = new HarmonyMethod(AccessTools.Method(typeof(ValheimUpdateProfilerTool), prefixName, (Type[])null, (Type[])null)) { priority = 0 }; HarmonyMethod val2 = new HarmonyMethod(AccessTools.Method(typeof(ValheimUpdateProfilerTool), finalizerName, (Type[])null, (Type[])null)) { priority = 800 }; _harmony.Patch(original, val, (HarmonyMethod)null, (HarmonyMethod)null, val2, (HarmonyMethod)null); } internal ValheimUpdateProfilerTool(ValheimProfilerApp app) { //IL_007f: Unknown result type (might be due to invalid IL or missing references) //IL_0089: Expected O, but got Unknown //IL_0149: Unknown result type (might be due to invalid IL or missing references) //IL_014a: Unknown result type (might be due to invalid IL or missing references) //IL_014f: Unknown result type (might be due to invalid IL or missing references) //IL_0161: Unknown result type (might be due to invalid IL or missing references) //IL_0166: Unknown result type (might be due to invalid IL or missing references) //IL_0167: Unknown result type (might be due to invalid IL or missing references) //IL_016c: Unknown result type (might be due to invalid IL or missing references) _app = app ?? throw new ArgumentNullException("app"); _logger = app.Logger; _windows = app.Windows; _theme = app.Theme; _harmony = new Harmony("shudnal.ValheimProfiler.ValheimUpdateProfiler"); _mainThreadId = Thread.CurrentThread.ManagedThreadId; _instance = this; CreateFixedEntries(); ValheimProfilerConfig config = app.Config; _avgSortColumn = ParseSortColumn(config.ValheimUpdateProfilerAvgSortColumn.Value, ValheimUpdateSortColumn.MsOneSecond); _maxSortColumn = ParseSortColumn(config.ValheimUpdateProfilerMaxSortColumn.Value, ValheimUpdateSortColumn.RawMax); foreach (ValheimUpdateKind value in Enum.GetValues(typeof(ValheimUpdateKind))) { _groupExpanded[value] = true; } Vector2 minimumSize = default(Vector2); ((Vector2)(ref minimumSize))..ctor(820f, 440f); Vector2 defaultToolWindowSize = _windows.GetDefaultToolWindowSize(650f, minimumSize); _window = _windows.Register(new ProfilerWindow("ValheimProfiler.ValheimUpdateProfiler", "Valheim Update Profiler", new Rect(ValheimProfilerConfig.DefaultValheimUpdateWindowPosition, defaultToolWindowSize), minimumSize, resizable: true, requestedVisible: false, DrawWindow, config.ValheimUpdateWindowPosition, config.ValheimUpdateWindowSize)); } void IProfilerTool.ShowWindow() { ShowWindow(); } void IProfilerTool.ToggleWindow() { ToggleWindow(); } void IProfilerTool.Update() { Update(); } void IProfilerTool.Shutdown() { Shutdown(); } internal void ShowWindow() { IsWindowVisible = true; _app.ShowUi(); _windows.BringToFront(_window); } internal void ToggleWindow() { IsWindowVisible = !IsWindowVisible; if (IsWindowVisible) { ShowWindow(); } } internal void Update() { if (_profilingActive) { float realtimeSinceStartup = Time.realtimeSinceStartup; if (!(realtimeSinceStartup < _nextAnalyticsUpdate)) { ProcessAllStats(realtimeSinceStartup); _nextAnalyticsUpdate = realtimeSinceStartup + 0.25f; } } } internal void Shutdown() { if (_instance == this) { _instance = null; } _profilingActive = false; try { _harmony.UnpatchSelf(); } catch { } IsWindowVisible = false; } private void CreateFixedEntries() { CreateEntry(ValheimUpdateKind.FixedUpdate, "$total", "Total phase", itemsApplicable: false, 0); CreateEntry(ValheimUpdateKind.FixedUpdate, "$measured", "Measured scopes", itemsApplicable: false, 1); CreateEntry(ValheimUpdateKind.FixedUpdate, "$remainder", "Unattributed remainder", itemsApplicable: false, 2); CreateEntry(ValheimUpdateKind.Update, "$total", "Total phase", itemsApplicable: false, 0); CreateEntry(ValheimUpdateKind.Update, "$measured", "Measured scopes", itemsApplicable: false, 1); CreateEntry(ValheimUpdateKind.Update, "$remainder", "Unattributed remainder", itemsApplicable: false, 2); CreateEntry(ValheimUpdateKind.LateUpdate, "$total", "Total phase", itemsApplicable: false, 0); CreateEntry(ValheimUpdateKind.LateUpdate, "$measured", "Measured scopes", itemsApplicable: false, 1); CreateEntry(ValheimUpdateKind.LateUpdate, "$remainder", "Unattributed remainder", itemsApplicable: false, 2); CreateEntry(ValheimUpdateKind.WearNTear, "WearNTear.Full pass", "Full pass", itemsApplicable: true, 0); CreateEntry(ValheimUpdateKind.WearNTear, "WearNTear.Wear batch", "Wear batch", itemsApplicable: true, 1); CreateEntry(ValheimUpdateKind.WearNTear, "WearNTear.Total", "Total", itemsApplicable: true, 2); foreach (ValheimUpdateScopeEntry value in _entries.Values) { value.RuntimeTypeName = ((value.Kind == ValheimUpdateKind.WearNTear) ? typeof(WearNTearUpdater).FullName : typeof(MonoUpdaters).FullName); } } private ValheimUpdateScopeEntry CreateEntry(ValheimUpdateKind kind, string scope, string displayName, bool itemsApplicable, int fixedOrder = 100) { ValheimUpdateScopeKey key = new ValheimUpdateScopeKey(kind, scope); ValheimUpdateScopeEntry valheimUpdateScopeEntry = new ValheimUpdateScopeEntry(kind, scope, displayName, itemsApplicable, fixedOrder); _entries[key] = valheimUpdateScopeEntry; return valheimUpdateScopeEntry; } private ValheimUpdateScopeEntry GetOrCreateScope(ValheimUpdateKind kind, string scope) { if (scope == null) { scope = string.Empty; } ValheimUpdateScopeKey key = new ValheimUpdateScopeKey(kind, scope); lock (_sync) { if (_entries.TryGetValue(key, out var value)) { return value; } bool isVanilla = IsKnownVanillaScope(kind, scope); return CreateEntry(kind, scope, BuildLocalScopeDisplayName(kind, scope, isVanilla), itemsApplicable: true); } } private void RecordScope(ValheimUpdateKind kind, string scope, int items, object firstItem, double elapsedMs, int frame, float now) { if (_profilingActive) { ValheimUpdateScopeEntry orCreateScope = GetOrCreateScope(kind, scope); ResolveEntrySource(orCreateScope, firstItem); orCreateScope.Add(elapsedMs, items, frame, now); } } private void RecordTopLevel(ValheimUpdateKind kind, double totalMs, double measuredMs, double remainderMs, int frame, float now) { GetOrCreateScope(kind, "$total").Add(totalMs, 0, frame, now); GetOrCreateScope(kind, "$measured").Add(measuredMs, 0, frame, now); GetOrCreateScope(kind, "$remainder").Add(remainderMs, 0, frame, now); } private void RecordWear(WearTimingState state, WearNTearUpdater updater, double elapsedMs, int frame, float now) { string scope = (state.FullPass ? "WearNTear.Full pass" : "WearNTear.Wear batch"); int items = (state.FullPass ? state.InstanceCount : state.PlannedItems); ValheimUpdateScopeEntry orCreateScope = GetOrCreateScope(ValheimUpdateKind.WearNTear, scope); ResolveEntrySource(orCreateScope, state.FirstItem); orCreateScope.Add(elapsedMs, items, frame, now); ValheimUpdateScopeEntry orCreateScope2 = GetOrCreateScope(ValheimUpdateKind.WearNTear, "WearNTear.Total"); ResolveEntrySource(orCreateScope2, state.FirstItem); orCreateScope2.Add(elapsedMs, items, frame, now); lock (_sync) { _wearCurrentInstances = state.InstanceCount; _wearCurrentUpdatesPerFrame = updater?.m_updatesPerFrame ?? state.UpdatesPerFrame; _wearCurrentIndex = updater?.m_index ?? state.StartIndex; if (state.FullPass) { _wearCycleActive = false; return; } if (!_wearCycleActive || state.StartIndex == 0) { _wearCycleActive = true; _wearCycleStartRealtime = now - (float)(elapsedMs * 0.001); } if ((Object)(object)updater != (Object)null && updater.m_index == 0) { double num = Math.Max(0.0, (double)(now - _wearCycleStartRealtime) * 1000.0); double num2 = (double)(state.TimeArgument - state.SleepUntilNext) * 1000.0; _wearLastCycleDurationMs = num; _wearLastCycleLagMs = num2; if (num > _wearMaxCycleDurationMs) { _wearMaxCycleDurationMs = num; } if (num2 > _wearMaxCycleLagMs) { _wearMaxCycleLagMs = num2; } _wearCycleActive = false; } } } private void ResetAllStats() { lock (_sync) { foreach (ValheimUpdateScopeEntry value in _entries.Values) { value.Reset(); } _wearCurrentInstances = 0; _wearCurrentUpdatesPerFrame = 0; _wearCurrentIndex = 0; _wearCycleActive = false; _wearCycleStartRealtime = 0f; _wearLastCycleDurationMs = 0.0; _wearMaxCycleDurationMs = 0.0; _wearLastCycleLagMs = 0.0; _wearMaxCycleLagMs = 0.0; } _statsFrozen = false; } private void ProcessAllStats(float now) { List list; lock (_sync) { list = _entries.Values.ToList(); } for (int i = 0; i < list.Count; i++) { list[i].Timing.ProcessBackgroundAnalytics(now); } } private List BuildRows(ValheimUpdateKind kind) { int displayFrame = DisplayFrame; float displayRealtime = DisplayRealtime; string search = _search?.Trim() ?? string.Empty; List list; lock (_sync) { list = _entries.Values.Where((ValheimUpdateScopeEntry entry) => entry.Kind == kind).ToList(); } List list2 = new List(); List list3 = new List(); for (int num = 0; num < list.Count; num++) { ValheimUpdateScopeEntry valheimUpdateScopeEntry = list[num]; if (MatchesSearch(valheimUpdateScopeEntry, search)) { ValheimUpdateRowSnapshot item = valheimUpdateScopeEntry.Snapshot(displayFrame, displayRealtime); if (valheimUpdateScopeEntry.FixedOrder < 100) { list2.Add(item); } else { list3.Add(item); } } } list2.Sort((ValheimUpdateRowSnapshot left, ValheimUpdateRowSnapshot right) => left.Entry.FixedOrder.CompareTo(right.Entry.FixedOrder)); list3.Sort(CompareRowsDescending); list2.AddRange(list3); return list2; } private int CompareRowsDescending(ValheimUpdateRowSnapshot left, ValheimUpdateRowSnapshot right) { ValheimUpdateSortColumn column = ((_view == ValheimUpdateView.OverOneSecond) ? _avgSortColumn : _maxSortColumn); double sortValue = GetSortValue(left, column); int num = GetSortValue(right, column).CompareTo(sortValue); return (num != 0) ? num : string.Compare(left.Entry.DisplayName, right.Entry.DisplayName, StringComparison.OrdinalIgnoreCase); } private static double GetSortValue(ValheimUpdateRowSnapshot row, ValheimUpdateSortColumn column) { if (1 == 0) { } double result = column switch { ValheimUpdateSortColumn.MsOneSecond => row.MsOneSecond, ValheimUpdateSortColumn.CallsOneSecond => row.CallsOneSecond, ValheimUpdateSortColumn.ItemsOneSecond => row.ItemsOneSecond, ValheimUpdateSortColumn.AverageBatchMs => row.AverageBatchMs, ValheimUpdateSortColumn.AverageItems => row.AverageItems, ValheimUpdateSortColumn.AverageMsPerItem => row.AverageMsPerItem, ValheimUpdateSortColumn.LastMs => row.LastMs, ValheimUpdateSortColumn.LastItems => row.LastItems, ValheimUpdateSortColumn.RawMax => row.MaxSnapshot.RawMaxMs, ValheimUpdateSortColumn.SecondMax => row.MaxSnapshot.SecondMaxMs, ValheimUpdateSortColumn.ThirdMax => row.MaxSnapshot.ThirdMaxMs, ValheimUpdateSortColumn.AboveP99 => row.MaxSnapshot.AboveP99Count, ValheimUpdateSortColumn.AvgAboveP99 => row.MaxSnapshot.AvgAboveP99Ms, ValheimUpdateSortColumn.P99 => row.MaxSnapshot.P99Ms, ValheimUpdateSortColumn.AboveP95 => row.MaxSnapshot.AboveP95Count, ValheimUpdateSortColumn.AvgAboveP95 => row.MaxSnapshot.AvgAboveP95Ms, ValheimUpdateSortColumn.P95 => row.MaxSnapshot.P95Ms, ValheimUpdateSortColumn.MaxItems => row.MaxItems, _ => 0.0, }; if (1 == 0) { } return result; } private static bool MatchesSearch(ValheimUpdateScopeEntry entry, string search) { if (string.IsNullOrEmpty(search)) { return true; } return Contains(entry.DisplayName, search) || Contains(entry.Scope, search) || Contains(entry.RuntimeTypeName, search); } private static bool Contains(string value, string search) { return !string.IsNullOrEmpty(value) && value.IndexOf(search, StringComparison.OrdinalIgnoreCase) >= 0; } private void ResolveEntrySource(ValheimUpdateScopeEntry entry, object firstItem) { if (entry == null || firstItem == null || !string.IsNullOrEmpty(entry.RuntimeTypeName)) { return; } try { Type type = firstItem.GetType(); entry.RuntimeTypeName = type.FullName ?? type.Name; } catch { } } private static bool IsKnownVanillaScope(ValheimUpdateKind kind, string scope) { if (kind == ValheimUpdateKind.WearNTear) { return true; } return !string.IsNullOrEmpty(scope) && KnownVanillaScopes.Contains(scope); } private static string BuildLocalScopeDisplayName(ValheimUpdateKind kind, string scope, bool isVanilla) { string text = scope ?? string.Empty; if (1 == 0) { } string[] array = kind switch { ValheimUpdateKind.FixedUpdate => new string[1] { "MonoUpdaters.FixedUpdate." }, ValheimUpdateKind.Update => new string[1] { "MonoUpdaters.Update." }, ValheimUpdateKind.LateUpdate => new string[1] { "MonoUpdaters.LateUpdate." }, ValheimUpdateKind.AI => new string[2] { "MonoUpdaters.UpdateAI.", "MonoUpdaters.FixedUpdate." }, ValheimUpdateKind.WearNTear => new string[1] { "WearNTear." }, _ => Array.Empty(), }; if (1 == 0) { } string[] array2 = array; for (int i = 0; i < array2.Length; i++) { if (text.StartsWith(array2[i], StringComparison.Ordinal)) { text = text.Substring(array2[i].Length); break; } } if (string.IsNullOrWhiteSpace(text)) { text = scope ?? "Unknown"; } return isVanilla ? text : ("Mod | " + text); } private static ValheimUpdateSortColumn ParseSortColumn(string value, ValheimUpdateSortColumn fallback) { ValheimUpdateSortColumn result; return Enum.TryParse(value, ignoreCase: true, out result) ? result : fallback; } private void SetSortColumn(ValheimUpdateSortColumn column) { if (_view == ValheimUpdateView.OverOneSecond) { _avgSortColumn = column; _app.Config.ValheimUpdateProfilerAvgSortColumn.Value = column.ToString(); } else { _maxSortColumn = column; _app.Config.ValheimUpdateProfilerMaxSortColumn.Value = column.ToString(); } } private bool IsSortColumn(ValheimUpdateSortColumn column) { return ((_view == ValheimUpdateView.OverOneSecond) ? _avgSortColumn : _maxSortColumn) == column; } private void DrawWindow(int id) { //IL_0008: 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_0139: 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) EnsureStyles(); Color contentColor = GUI.contentColor; try { GUI.contentColor = _theme.TextColor; GUILayout.BeginVertical(Array.Empty()); DrawToolbar(); GUILayout.Space(4f); GUILayout.BeginHorizontal(Array.Empty()); MainTab mainTab = (MainTab)GUILayout.Toolbar((int)_mainTab, new string[2] { "Profiler", "Help" }, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(210f) }); if (mainTab != _mainTab) { _mainTab = mainTab; } if (_mainTab == MainTab.Profiler) { GUILayout.Space(12f); ValheimUpdateView valheimUpdateView = (ValheimUpdateView)GUILayout.Toolbar((int)_view, new string[2] { "Over 1 sec", "Max over 60 sec" }, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(290f) }); if (valheimUpdateView != _view) { _view = valheimUpdateView; } } GUILayout.EndHorizontal(); GUILayout.Space(4f); if (_mainTab == MainTab.Profiler) { DrawProfilerTab(); } else { DrawHelpTab(); } GUILayout.EndVertical(); } finally { GUI.contentColor = contentColor; } } private void DrawToolbar() { GUILayout.BeginHorizontal(Array.Empty()); string text = (_profilingActive ? "Profiling: ON" : (_statsFrozen ? "Profiling: PAUSED" : "Profiling: OFF")); GUILayout.Label(text + " | Status: " + _status, _labelStyle, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.ExpandWidth(true) }); string text2 = (_profilingActive ? "Pause profiling" : "Start profiling"); if (GUILayout.Button(text2, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(125f) })) { if (_profilingActive) { StopProfiling(); } else { StartProfiling(); } } if (GUILayout.Button("Reset stats", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(100f) })) { ResetAllStats(); _status = "Statistics reset."; } if (GUILayout.Button("Close", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(70f) })) { IsWindowVisible = false; } GUILayout.EndHorizontal(); } private void DrawProfilerTab() { //IL_0125: Unknown result type (might be due to invalid IL or missing references) //IL_0131: Unknown result type (might be due to invalid IL or missing references) //IL_0136: Unknown result type (might be due to invalid IL or missing references) GUILayout.BeginHorizontal(Array.Empty()); if (GUILayout.Button("Expand all", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(88f) })) { SetAllGroupsExpanded(expanded: true); } if (GUILayout.Button("Collapse all", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(88f) })) { SetAllGroupsExpanded(expanded: false); } GUILayout.Space(12f); GUILayout.Label("Search:", _labelStyle, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(48f) }); string text = GUILayout.TextField(_search ?? string.Empty, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(260f) }); if (!string.Equals(text, _search, StringComparison.Ordinal)) { _search = text; } if (GUILayout.Button("Clear", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(55f) })) { _search = string.Empty; } GUILayout.FlexibleSpace(); GUILayout.EndHorizontal(); GUILayout.Space(3f); DrawWearStatus(); DrawHeader(); _scroll = GUILayout.BeginScrollView(_scroll, true, true, Array.Empty()); GUILayout.BeginVertical((GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(GetTableWidth()) }); DrawGroup(ValheimUpdateKind.FixedUpdate, "MonoUpdaters.FixedUpdate"); DrawGroup(ValheimUpdateKind.Update, "MonoUpdaters.Update"); DrawGroup(ValheimUpdateKind.LateUpdate, "MonoUpdaters.LateUpdate"); DrawGroup(ValheimUpdateKind.AI, "MonoUpdaters.UpdateAI"); DrawGroup(ValheimUpdateKind.WearNTear, "WearNTearUpdater.UpdateWearNTear"); GUILayout.EndVertical(); GUILayout.EndScrollView(); } private void DrawWearStatus() { //IL_0110: Unknown result type (might be due to invalid IL or missing references) //IL_012f: Expected O, but got Unknown int wearCurrentInstances; int wearCurrentUpdatesPerFrame; int wearCurrentIndex; double wearLastCycleDurationMs; double wearMaxCycleDurationMs; double wearLastCycleLagMs; double wearMaxCycleLagMs; lock (_sync) { wearCurrentInstances = _wearCurrentInstances; wearCurrentUpdatesPerFrame = _wearCurrentUpdatesPerFrame; wearCurrentIndex = _wearCurrentIndex; wearLastCycleDurationMs = _wearLastCycleDurationMs; wearMaxCycleDurationMs = _wearMaxCycleDurationMs; wearLastCycleLagMs = _wearLastCycleLagMs; wearMaxCycleLagMs = _wearMaxCycleLagMs; } string arg = ((wearCurrentInstances > 0) ? $"{Math.Min(wearCurrentIndex, wearCurrentInstances)}/{wearCurrentInstances}" : "0/0"); string text = "WearNTearUpdater performs one Full pass per cycle, then processes incremental Wear batches over subsequent frames.\nInstances is the current WearNTear list size. Updates/frame is the adaptive batch limit used by the game.\nCycle progress is the current list index. Positive lag means the completed cycle finished after its scheduled time; negative lag means it finished early."; GUILayout.BeginHorizontal(GUI.skin.box, Array.Empty()); GUILayout.Label(new GUIContent($"WearNTear state | Instances: {wearCurrentInstances} | Updates/frame: {wearCurrentUpdatesPerFrame} | Cycle progress: {arg} | " + "Last cycle: " + FormatMs(wearLastCycleDurationMs) + " | Max cycle: " + FormatMs(wearMaxCycleDurationMs) + " | Last lag: " + FormatSignedMs(wearLastCycleLagMs) + " | Max lag: " + FormatSignedMs(wearMaxCycleLagMs), text), _labelStyle, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.ExpandWidth(true) }); GUILayout.EndHorizontal(); GUILayout.Space(3f); } private void DrawHeader() { GUILayout.BeginHorizontal((GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(GetTableWidth()) }); GUILayout.Label("Scope", _headerStyle, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(420f) }); if (_view == ValheimUpdateView.OverOneSecond) { HeaderButton("ms / 1 sec", ValheimUpdateSortColumn.MsOneSecond, 112f, "Total measured milliseconds in the rolling one-second window."); HeaderButton("calls / 1 sec", ValheimUpdateSortColumn.CallsOneSecond, 112f, "Number of batch or phase invocations in the rolling one-second window."); HeaderButton("items / 1 sec", ValheimUpdateSortColumn.ItemsOneSecond, 112f, "Scheduled list items in the rolling one-second window. A scheduled item may be skipped by the game when disabled."); HeaderButton("avg batch", ValheimUpdateSortColumn.AverageBatchMs, 92f, "Average milliseconds per batch call in the rolling one-second window."); HeaderButton("avg items", ValheimUpdateSortColumn.AverageItems, 92f, "Average scheduled items per call in the rolling one-second window."); HeaderButton("avg ms/item", ValheimUpdateSortColumn.AverageMsPerItem, 112f, "Average inclusive milliseconds per scheduled item."); HeaderButton("last ms", ValheimUpdateSortColumn.LastMs, 92f, "Duration of the latest batch or phase call."); HeaderButton("last items", ValheimUpdateSortColumn.LastItems, 92f, "Scheduled items in the latest call."); } else { HeaderButton("raw max", ValheimUpdateSortColumn.RawMax, 92f, "Slowest call in the rolling 60-second window."); HeaderButton("2nd max", ValheimUpdateSortColumn.SecondMax, 92f, "Second-slowest call in the rolling 60-second window."); HeaderButton("3rd max", ValheimUpdateSortColumn.ThirdMax, 92f, "Third-slowest call in the rolling 60-second window."); HeaderButton("> p99", ValheimUpdateSortColumn.AboveP99, 92f, "Number of calls slower than the approximate p99 threshold in the rolling 60-second window."); HeaderButton("avg >p99", ValheimUpdateSortColumn.AvgAboveP99, 112f, "Average duration of calls slower than the approximate p99 threshold."); HeaderButton("p99", ValheimUpdateSortColumn.P99, 92f, "Approximate 99th percentile in the rolling 60-second window."); HeaderButton("> p95", ValheimUpdateSortColumn.AboveP95, 92f, "Number of calls slower than the approximate p95 threshold in the rolling 60-second window."); HeaderButton("avg >p95", ValheimUpdateSortColumn.AvgAboveP95, 112f, "Average duration of calls slower than the approximate p95 threshold."); HeaderButton("p95", ValheimUpdateSortColumn.P95, 92f, "Approximate 95th percentile in the rolling 60-second window."); HeaderButton("max items", ValheimUpdateSortColumn.MaxItems, 92f, "Largest scheduled batch in the rolling 60-second window."); HeaderButton("last ms", ValheimUpdateSortColumn.LastMs, 92f, "Duration of the latest call."); HeaderButton("last items", ValheimUpdateSortColumn.LastItems, 92f, "Scheduled items in the latest call."); } GUILayout.EndHorizontal(); GUILayout.Space(2f); } private void HeaderButton(string text, ValheimUpdateSortColumn column, float width, string tooltip) { //IL_001c: Unknown result type (might be due to invalid IL or missing references) //IL_0036: Expected O, but got Unknown GUIStyle val = (IsSortColumn(column) ? _activeHeaderStyle : _headerStyle); if (GUILayout.Button(new GUIContent(text, tooltip), val, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(width) })) { SetSortColumn(column); } } private void DrawGroup(ValheimUpdateKind kind, string title) { List list = BuildRows(kind); if (list.Count == 0 && !string.IsNullOrEmpty(_search)) { return; } bool value; bool flag = _groupExpanded.TryGetValue(kind, out value) && value; GUILayout.BeginHorizontal(GUI.skin.box, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(GetTableWidth()) }); if (GUILayout.Button(flag ? "▼" : "▶", _compactButtonStyle, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(16f) })) { flag = !flag; } if (GUILayout.Button($"{title} ({list.Count})", _groupStyle, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(404f) })) { flag = !flag; } double num = 0.0; for (int i = 0; i < list.Count; i++) { if (list[i].Entry.Scope == "$total" || list[i].Entry.Scope == "WearNTear.Total") { num = list[i].MsOneSecond; break; } if (list[i].Entry.FixedOrder >= 100) { num += list[i].MsOneSecond; } } GUILayout.Label($"{num:0.000} ms / 1 sec", _groupStyle, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(150f) }); GUILayout.FlexibleSpace(); GUILayout.EndHorizontal(); _groupExpanded[kind] = flag; if (flag) { for (int j = 0; j < list.Count; j++) { DrawRow(list[j]); } } } private void DrawRow(ValheimUpdateRowSnapshot row) { //IL_00aa: Unknown result type (might be due to invalid IL or missing references) //IL_00cd: Expected O, but got Unknown ValheimUpdateScopeEntry entry = row.Entry; string text = (entry.DisplayName.StartsWith("Mod | ", StringComparison.Ordinal) ? "Third-party or unknown scope" : "Built-in Valheim 0.219.14 scope"); string text2 = (string.IsNullOrEmpty(entry.RuntimeTypeName) ? ("Full profileScope: " + entry.Scope + "\nClassification: " + text) : ("Full profileScope: " + entry.Scope + "\nClassification: " + text + "\nFirst observed runtime type: " + entry.RuntimeTypeName)); GUILayout.BeginHorizontal((GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(GetTableWidth()) }); GUILayout.Label(new GUIContent(entry.DisplayName, text2), _labelStyle, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(420f) }); if (_view == ValheimUpdateView.OverOneSecond) { ValueLabel(row.MsOneSecond, 112f); ValueLabel(row.CallsOneSecond, 112f, "0.0"); ItemLabel(entry, row.ItemsOneSecond, 112f); ValueLabel(row.AverageBatchMs, 92f); ItemValueLabel(entry, row.AverageItems, 92f, "0.0"); ItemValueLabel(entry, row.AverageMsPerItem, 112f, "0.000000"); ValueLabel(row.LastMs, 92f); ItemLabel(entry, row.LastItems, 92f); } else { ValueLabel(row.MaxSnapshot.RawMaxMs, 92f); ValueLabel(row.MaxSnapshot.SecondMaxMs, 92f); ValueLabel(row.MaxSnapshot.ThirdMaxMs, 92f); CountLabel(row.MaxSnapshot.AboveP99Count, 92f); ValueLabel(row.MaxSnapshot.AvgAboveP99Ms, 112f); ValueLabel(row.MaxSnapshot.P99Ms, 92f); CountLabel(row.MaxSnapshot.AboveP95Count, 92f); ValueLabel(row.MaxSnapshot.AvgAboveP95Ms, 112f); ValueLabel(row.MaxSnapshot.P95Ms, 92f); ItemLabel(entry, row.MaxItems, 92f); ValueLabel(row.LastMs, 92f); ItemLabel(entry, row.LastItems, 92f); } GUILayout.EndHorizontal(); } private void ValueLabel(double value, float width, string format = "0.000") { GUILayout.Label(value.ToString(format), _labelStyle, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(width) }); } private void ItemValueLabel(ValheimUpdateScopeEntry entry, double value, float width, string format) { GUILayout.Label(entry.ItemsApplicable ? value.ToString(format) : "-", _labelStyle, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(width) }); } private void ItemLabel(ValheimUpdateScopeEntry entry, long value, float width) { GUILayout.Label(entry.ItemsApplicable ? value.ToString() : "-", _labelStyle, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(width) }); } private void CountLabel(long value, float width) { GUILayout.Label(value.ToString(), _labelStyle, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(width) }); } private void SetAllGroupsExpanded(bool expanded) { foreach (ValheimUpdateKind value in Enum.GetValues(typeof(ValheimUpdateKind))) { _groupExpanded[value] = expanded; } } private float GetTableWidth() { if (_view == ValheimUpdateView.OverOneSecond) { return 1256f; } return 1584f; } private void DrawHelpTab() { //IL_0003: 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_0012: Unknown result type (might be due to invalid IL or missing references) _helpScroll = GUILayout.BeginScrollView(_helpScroll, Array.Empty()); HeaderLabel("Purpose"); Label("Valheim Update Profiler measures fixed centralized update loops that Valheim uses instead of individual MonoBehaviour callbacks."); Label("It is intended for mod developers measuring managed execution time, not as an FPS estimator."); GUILayout.Space(6f); HeaderLabel("MonoUpdatersExtra"); Label("The profiler instruments CustomFixedUpdate, CustomUpdate, CustomLateUpdate and UpdateAI once per batch."); Label("Rows use the profileScope string passed by Valheim or another mod. Third-party scopes are discovered automatically when they use the same extension methods."); Label("Scheduled item counts are container.Count + source.Count before AddRange. They describe attempted list traversal, not guaranteed successful callbacks when an exception occurs."); Label("Known Valheim 0.219.14 profileScope values are shown without a prefix. Other scopes are prefixed with Mod |. Runtime type is captured best-effort from the first non-empty batch and is available in the row tooltip."); GUILayout.Space(6f); HeaderLabel("Top-level phases"); Label("MonoUpdaters.FixedUpdate, Update and LateUpdate are measured as complete phases."); Label("Measured scopes is the sum of MonoUpdatersExtra batches observed inside each top-level invocation."); Label("Unattributed remainder is the top-level phase time minus those measured batches. It includes WaterVolume work and other code outside the centralized extension methods."); Label("Nested or replacement update loops can produce overlapping inclusive measurements; do not add unrelated profiler rows as exclusive cost."); GUILayout.Space(6f); HeaderLabel("WearNTear"); Label("UpdateWearNTear is measured once per invocation and split into Full pass and Wear batch without patching individual building pieces."); Label("Full pass contains UpdateCover for enabled pieces and UpdateAshlandsMaterialValues for all pieces. Its item count is the total instance count, not the number of inner method calls."); Label("Wear batch reports scheduled list slots, current updates/frame, cycle progress, wall-clock cycle duration and schedule lag."); Label("Detailed UpdateCover, UpdateAshlandsMaterialValues or UpdateWear investigation can be performed manually through MonoBehaviour Frame Profiler when necessary."); GUILayout.Space(6f); HeaderLabel("Views"); Label("Over 1 sec reports total milliseconds, calls and scheduled items in a rolling one-second window plus per-call and per-item averages."); Label("Max over 60 sec reports slowest calls, approximate p95/p99 values, counts above those thresholds and the average duration of the slower samples. All times are inclusive wall-clock measurements on the Unity main thread."); Label("Click a column header to sort descending. Sort selection is stored in the BepInEx config."); GUILayout.Space(6f); HeaderLabel("Limitations"); Label("Direct calls to IMonoUpdater methods that bypass MonoUpdatersExtra are not visible here."); Label("A mod that fully replaces the Valheim loops may also bypass these measurements. Use MonoBehaviour Frame Profiler or Patch Profiler for those cases."); Label("The profiler itself adds a small Prefix/Finalizer cost per centralized batch, not per subordinate object."); GUILayout.EndScrollView(); } private void EnsureStyles() { //IL_003e: Unknown result type (might be due to invalid IL or missing references) //IL_0043: 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_0053: Unknown result type (might be due to invalid IL or missing references) //IL_0060: Expected O, but got Unknown //IL_0071: Unknown result type (might be due to invalid IL or missing references) //IL_0087: Unknown result type (might be due to invalid IL or missing references) //IL_008c: 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_009c: Unknown result type (might be due to invalid IL or missing references) //IL_00a4: Unknown result type (might be due to invalid IL or missing references) //IL_00b1: Expected O, but got Unknown //IL_00c2: Unknown result type (might be due to invalid IL or missing references) //IL_00d4: Unknown result type (might be due to invalid IL or missing references) //IL_00de: Expected O, but got Unknown //IL_00e4: Unknown result type (might be due to invalid IL or missing references) //IL_00ef: Unknown result type (might be due to invalid IL or missing references) //IL_00f9: Unknown result type (might be due to invalid IL or missing references) //IL_00fe: Unknown result type (might be due to invalid IL or missing references) //IL_010a: Unknown result type (might be due to invalid IL or missing references) //IL_011c: Unknown result type (might be due to invalid IL or missing references) //IL_012e: Unknown result type (might be due to invalid IL or missing references) //IL_013c: Unknown result type (might be due to invalid IL or missing references) //IL_0141: Unknown result type (might be due to invalid IL or missing references) //IL_014e: Expected O, but got Unknown //IL_0159: Unknown result type (might be due to invalid IL or missing references) //IL_015e: Unknown result type (might be due to invalid IL or missing references) //IL_0163: Unknown result type (might be due to invalid IL or missing references) //IL_016d: Expected O, but got Unknown //IL_016e: Unknown result type (might be due to invalid IL or missing references) //IL_0173: Unknown result type (might be due to invalid IL or missing references) //IL_017d: Expected O, but got Unknown //IL_0183: Expected O, but got Unknown if (!((Object)(object)_styleSkin == (Object)(object)GUI.skin) || _labelStyle == null) { _styleSkin = GUI.skin; _labelStyle = new GUIStyle(GUI.skin.label) { wordWrap = false, clipping = (TextClipping)1, alignment = (TextAnchor)3 }; _labelStyle.normal.textColor = _theme.TextColor; _headerStyle = new GUIStyle(GUI.skin.label) { wordWrap = false, clipping = (TextClipping)1, alignment = (TextAnchor)3, fontStyle = (FontStyle)1 }; _headerStyle.normal.textColor = _theme.HeaderTextColor; _activeHeaderStyle = new GUIStyle(_headerStyle); Color textColor = Color.Lerp(_theme.HeaderTextColor, _theme.AccentColor, 0.5f); _activeHeaderStyle.normal.textColor = textColor; _activeHeaderStyle.hover.textColor = textColor; _activeHeaderStyle.active.textColor = textColor; _groupStyle = new GUIStyle(_headerStyle) { alignment = (TextAnchor)3 }; _compactButtonStyle = new GUIStyle(GUI.skin.button) { margin = new RectOffset(1, 1, 1, 1), padding = new RectOffset(2, 3, 1, 1) }; } } private static string FormatMs(double value) { return $"{value:0.000} ms"; } private static string FormatSignedMs(double value) { return $"{value:+0.000;-0.000;0.000} ms"; } private void Label(string text, params GUILayoutOption[] options) { GUILayout.Label(text ?? string.Empty, _labelStyle, options); } private void HeaderLabel(string text, params GUILayoutOption[] options) { GUILayout.Label(text ?? string.Empty, _headerStyle, options); } } internal enum ValheimUpdateKind { FixedUpdate, Update, LateUpdate, AI, WearNTear } internal enum ValheimUpdateView { OverOneSecond, MaxOverSixtySeconds } internal enum ValheimUpdateSortColumn { MsOneSecond, CallsOneSecond, ItemsOneSecond, AverageBatchMs, AverageItems, AverageMsPerItem, LastMs, LastItems, RawMax, SecondMax, ThirdMax, AboveP99, AvgAboveP99, P99, AboveP95, AvgAboveP95, P95, MaxItems } internal readonly struct ValheimUpdateScopeKey : IEquatable { internal ValheimUpdateKind Kind { get; } internal string Scope { get; } internal ValheimUpdateScopeKey(ValheimUpdateKind kind, string scope) { Kind = kind; Scope = scope ?? string.Empty; } public bool Equals(ValheimUpdateScopeKey other) { return Kind == other.Kind && string.Equals(Scope, other.Scope, StringComparison.Ordinal); } public override bool Equals(object obj) { return obj is ValheimUpdateScopeKey other && Equals(other); } public override int GetHashCode() { return ((int)Kind * 397) ^ StringComparer.Ordinal.GetHashCode(Scope ?? string.Empty); } } internal sealed class ValheimUpdateScopeEntry { internal ValheimUpdateKind Kind { get; } internal string Scope { get; } internal string DisplayName { get; } internal bool ItemsApplicable { get; } internal int FixedOrder { get; } internal string RuntimeTypeName { get; set; } = string.Empty; internal RollingProfilerStat Timing { get; } = new RollingProfilerStat(); internal RollingItemStat Items { get; } = new RollingItemStat(); internal double LastMs { get; private set; } internal int LastItems { get; private set; } internal float LastSeenRealtime { get; private set; } internal ValheimUpdateScopeEntry(ValheimUpdateKind kind, string scope, string displayName, bool itemsApplicable, int fixedOrder = 100) { Kind = kind; Scope = scope ?? string.Empty; DisplayName = displayName ?? Scope; ItemsApplicable = itemsApplicable; FixedOrder = fixedOrder; } internal void Add(double elapsedMs, int items, int frame, float now) { Timing.Add(elapsedMs, frame, now, gcSample: false); LastMs = elapsedMs; LastSeenRealtime = now; if (ItemsApplicable) { LastItems = Math.Max(0, items); Items.Add(LastItems, now); } } internal ValheimUpdateRowSnapshot Snapshot(int frame, float now) { Timing.ProcessBackgroundAnalytics(now); RollingProfilerSnapshot snapshot = Timing.GetSnapshot(frame, now); RollingItemSnapshot rollingItemSnapshot = (ItemsApplicable ? Items.GetSnapshot(now) : default(RollingItemSnapshot)); double num = snapshot.Avg1sCallsPerFrame * (double)snapshot.Avg1sFrames; double num2 = snapshot.Avg1sMsPerFrame * (double)snapshot.Avg1sFrames; return new ValheimUpdateRowSnapshot { Entry = this, MsOneSecond = num2, CallsOneSecond = num, ItemsOneSecond = rollingItemSnapshot.ItemsOneSecond, AverageBatchMs = ((num > 0.0) ? (num2 / num) : 0.0), AverageItems = ((rollingItemSnapshot.CallsOneSecond > 0) ? ((double)rollingItemSnapshot.ItemsOneSecond / (double)rollingItemSnapshot.CallsOneSecond) : 0.0), AverageMsPerItem = ((rollingItemSnapshot.ItemsOneSecond > 0) ? (num2 / (double)rollingItemSnapshot.ItemsOneSecond) : 0.0), LastMs = LastMs, LastItems = LastItems, MaxItems = rollingItemSnapshot.MaxItemsSixtySeconds, MaxSnapshot = snapshot.MaxSnapshot }; } internal void Reset() { Timing.Reset(); Items.Reset(); LastMs = 0.0; LastItems = 0; LastSeenRealtime = 0f; } } internal struct ValheimUpdateRowSnapshot { internal ValheimUpdateScopeEntry Entry; internal double MsOneSecond; internal double CallsOneSecond; internal long ItemsOneSecond; internal double AverageBatchMs; internal double AverageItems; internal double AverageMsPerItem; internal double LastMs; internal int LastItems; internal int MaxItems; internal RollingMaxSnapshot MaxSnapshot; } internal sealed class RollingItemStat { private readonly struct ItemSample { internal float Time { get; } internal int Items { get; } internal ItemSample(float time, int items) { Time = time; Items = items; } } private struct ItemSecondBucket { internal int Second; internal int MaxItems; } private const int MaxWindowSeconds = 60; private readonly object _sync = new object(); private readonly Queue _oneSecondSamples = new Queue(256); private readonly ItemSecondBucket[] _maxBuckets = new ItemSecondBucket[60]; private long _itemsOneSecond; private int _callsOneSecond; internal RollingItemStat() { for (int i = 0; i < _maxBuckets.Length; i++) { _maxBuckets[i].Second = int.MinValue; } } internal void Add(int items, float now) { lock (_sync) { TrimOneSecond(now); _oneSecondSamples.Enqueue(new ItemSample(now, items)); _itemsOneSecond += items; _callsOneSecond++; int num = Mathf.FloorToInt(now); int num2 = PositiveMod(num, 60); ref ItemSecondBucket reference = ref _maxBuckets[num2]; if (reference.Second != num) { reference.Second = num; reference.MaxItems = items; } else if (items > reference.MaxItems) { reference.MaxItems = items; } } } internal RollingItemSnapshot GetSnapshot(float now) { lock (_sync) { TrimOneSecond(now); int num = Mathf.FloorToInt(now); int num2 = 0; for (int i = 0; i < _maxBuckets.Length; i++) { ItemSecondBucket itemSecondBucket = _maxBuckets[i]; if (itemSecondBucket.Second != int.MinValue && num - itemSecondBucket.Second < 60 && itemSecondBucket.MaxItems > num2) { num2 = itemSecondBucket.MaxItems; } } return new RollingItemSnapshot { ItemsOneSecond = _itemsOneSecond, CallsOneSecond = _callsOneSecond, MaxItemsSixtySeconds = num2 }; } } internal void Reset() { lock (_sync) { _oneSecondSamples.Clear(); _itemsOneSecond = 0L; _callsOneSecond = 0; for (int i = 0; i < _maxBuckets.Length; i++) { _maxBuckets[i].Second = int.MinValue; _maxBuckets[i].MaxItems = 0; } } } private void TrimOneSecond(float now) { while (_oneSecondSamples.Count > 0 && now - _oneSecondSamples.Peek().Time > 1f) { _itemsOneSecond -= _oneSecondSamples.Dequeue().Items; _callsOneSecond--; } if (_itemsOneSecond < 0) { _itemsOneSecond = 0L; } if (_callsOneSecond < 0) { _callsOneSecond = 0; } } private static int PositiveMod(int value, int divisor) { int num = value % divisor; return (num < 0) ? (num + divisor) : num; } } internal struct RollingItemSnapshot { internal long ItemsOneSecond; internal int CallsOneSecond; internal int MaxItemsSixtySeconds; } } namespace ValheimProfiler.Tools.ServerLogMonitor { internal sealed class ServerLogMonitorTool : IProfilerTool, IProfilerToolAvailability { private enum MainTab { Stream, Issues, Help } private enum IssueSortColumn { Count, FirstSeen, LastSeen, Level, Source, Message } private sealed class LogEntry { internal long Sequence; internal DateTime Timestamp; internal LogLevel Level; internal string Source; internal string RawMessage; internal string Message; internal string Details; internal string Scene; internal int ThreadId; internal bool IsHistorical; internal long FileOffset; internal string TimeText => (Timestamp == default(DateTime)) ? "--:--:--.---" : Timestamp.ToString("HH:mm:ss.fff"); internal string LevelText => LogMonitorText.GetLevelText(Level); internal string Fingerprint => LogMonitorText.BuildFingerprint(Level, Source, Message, Details); internal string GetClipboardText(bool includeMetadata) { string text = "[" + LevelText.PadRight(7) + ": " + Source + "]"; string text2 = (string.IsNullOrEmpty(RawMessage) ? (Message ?? string.Empty) : RawMessage); if (!includeMetadata) { string text3 = text + " " + text2; return string.IsNullOrEmpty(Details) ? text3 : (text3 + Environment.NewLine + Details); } string text4 = ((Timestamp == default(DateTime)) ? "unknown time" : Timestamp.ToString("yyyy-MM-dd HH:mm:ss.fff")); string text5 = text4 + " " + text + $" [thread {ThreadId}]"; if (!string.IsNullOrEmpty(Scene)) { text5 = text5 + " [scene " + Scene + "]"; } text5 = ((!IsHistorical) ? (text5 + $" [server sequence {Sequence}]") : (text5 + $" [server history offset {FileOffset}]")); if (string.IsNullOrEmpty(Details)) { return text5 + Environment.NewLine + text2; } return text5 + Environment.NewLine + text2 + Environment.NewLine + Details; } } private sealed class IssueGroup { internal string Key; internal LogLevel Level; internal string Source; internal string Message; internal string Details; internal string Scene; internal int LastThreadId; internal int Count; internal DateTime FirstSeen; internal DateTime LastSeen; internal long FirstSequence; internal long LastSequence; internal string LevelText => LogMonitorText.GetLevelText(Level); internal string GetClipboardText() { string text = ((FirstSeen == default(DateTime)) ? "unknown" : FirstSeen.ToString("yyyy-MM-dd HH:mm:ss.fff")); string text2 = ((LastSeen == default(DateTime)) ? "unknown" : LastSeen.ToString("yyyy-MM-dd HH:mm:ss.fff")); string text3 = $"[{LevelText}:{Source}] Count: {Count} | First: {text} | Last: {text2}"; if (!string.IsNullOrEmpty(Scene)) { text3 = text3 + " | Last scene: " + Scene; } text3 += $" | Last thread: {LastThreadId}"; if (string.IsNullOrEmpty(Details)) { return text3 + Environment.NewLine + Message; } return text3 + Environment.NewLine + Message + Environment.NewLine + Details; } } private enum AvailabilityState { NoDedicatedConnection, Detecting, ServerModUnavailable, AccessDenied, Available, ProtocolMismatch } internal const string ToolId = "ServerLogMonitor"; internal const string DisplayTitle = "Server Log Monitor"; private const float VirtualizationOverscan = 60f; private const float ProbeTimeoutSeconds = 3f; private const float ProbeRetrySeconds = 10f; private const float RequestTimeoutSeconds = 6f; private readonly ValheimProfilerApp _app; private readonly WindowManager _windows; private readonly ThemeManager _theme; private readonly ProfilerWindow _mainWindow; private readonly List _entries = new List(); private readonly List _filteredStream = new List(); private readonly Dictionary _issuesByKey = new Dictionary(StringComparer.Ordinal); private readonly List _issues = new List(); private readonly List _filteredIssues = new List(); private GUIStyle _labelStyle; private GUIStyle _headerLabelStyle; private GUIStyle _activeHeaderLabelStyle; private GUIStyle _detailsStyle; private GUISkin _styleSkin; private Vector2 _streamScroll; private Vector2 _issuesScroll; private Vector2 _streamDetailsScroll; private Vector2 _issueDetailsScroll; private Vector2 _helpScroll; private MainTab _mainTab; private LogLevel _streamLevelFilter = (LogLevel)63; private IssueSortColumn _issueSortColumn = IssueSortColumn.Count; private string _streamSearch = string.Empty; private string _issuesSearch = string.Empty; private bool _includeWarningsInIssues; private bool _followStream = true; private bool _scrollStreamToEnd; private bool _streamViewDirty = true; private bool _issuesViewDirty = true; private readonly HashSet _selectedEntries = new HashSet(); private LogEntry _selectedEntry; private LogEntry _selectionAnchorEntry; private IssueGroup _selectedIssue; private AvailabilityState _availability = AvailabilityState.NoDedicatedConnection; private string _availabilityStatus = "Connect to a dedicated server to use Server Log Monitor."; private string _serverVersion = string.Empty; private string _sessionId = string.Empty; private bool _subscribed; private bool _subscriptionRequestPending; private bool _historyRequestPending; private bool _serverAuthorized; private bool _probePending; private float _probeDeadlineRealtime; private float _requestDeadlineRealtime; private float _nextProbeRealtime; private ZRoutedRpc _networkRpc; private long _lastLiveSequence; private long _serverDroppedCount; private long _historyCursor; private long _historyStartCursor; private long _historyFileCreationUtcTicks; private bool _historyHasMore; private int _loadedHistoryEntries; private int _detectedGaps; private string _historyStatus = string.Empty; private string _startupHistoryRequestedSession = string.Empty; string IProfilerTool.Id => "ServerLogMonitor"; string IProfilerTool.DisplayName => "Server Log"; bool IProfilerTool.IsWindowVisible => IsWindowVisible; bool IProfilerTool.IsActive => _subscribed; bool IProfilerToolAvailability.IsAvailable => IsAvailable; bool IProfilerToolAvailability.CanOpenWhenUnavailable => true; string IProfilerToolAvailability.AvailabilityTooltip => AvailabilityTooltip; internal bool IsWindowVisible { get { return _mainWindow.RequestedVisible; } set { _mainWindow.RequestedVisible = value; } } internal bool IsAvailable => _availability == AvailabilityState.Available; internal string AvailabilityTooltip { get { string text = "Requires a compatible Valheim Profiler on the connected headless dedicated server and the current player in the server admin list."; return string.IsNullOrWhiteSpace(_availabilityStatus) ? text : (_availabilityStatus + "\n" + text); } } private float MainWindowHeight { get { //IL_0006: 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) Rect rect = _mainWindow.Rect; return ((Rect)(ref rect)).height; } } private float RowHeight { get { GUIStyle labelStyle = _labelStyle; return Mathf.Max(20f, ((labelStyle != null) ? labelStyle.lineHeight : 16f) + 4f); } } private float HeaderHeight { get { GUIStyle headerLabelStyle = _headerLabelStyle; return Mathf.Max(20f, ((headerLabelStyle != null) ? headerLabelStyle.lineHeight : 16f) + 4f); } } private void DrawHeaderCell(ref float x, float y, float width, float height, string text, string tooltip) { //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_0022: Unknown result type (might be due to invalid IL or missing references) //IL_0032: Expected O, but got Unknown GUI.Label(new Rect(x, y, width, height), new GUIContent(text ?? string.Empty, tooltip ?? string.Empty), _headerLabelStyle); x += width; } private static void DrawCell(ref float x, float y, float width, float height, string text, GUIStyle style) { //IL_0006: Unknown result type (might be due to invalid IL or missing references) GUI.Label(new Rect(x, y, width, height), text ?? string.Empty, style); x += width; } private Color GetLevelColor(LogLevel level) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0003: Unknown result type (might be due to invalid IL or missing references) //IL_0005: Invalid comparison between Unknown and I4 //IL_003f: Unknown result type (might be due to invalid IL or missing references) //IL_0041: Unknown result type (might be due to invalid IL or missing references) //IL_0043: Invalid comparison between Unknown and I4 //IL_0011: 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_0034: Unknown result type (might be due to invalid IL or missing references) //IL_0039: Unknown result type (might be due to invalid IL or missing references) //IL_007d: Unknown result type (might be due to invalid IL or missing references) //IL_007f: Unknown result type (might be due to invalid IL or missing references) //IL_0081: Invalid comparison between Unknown and I4 //IL_004f: 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_0072: Unknown result type (might be due to invalid IL or missing references) //IL_0077: Unknown result type (might be due to invalid IL or missing references) //IL_0104: Unknown result type (might be due to invalid IL or missing references) //IL_00b8: Unknown result type (might be due to invalid IL or missing references) //IL_00ba: Unknown result type (might be due to invalid IL or missing references) //IL_00bc: Invalid comparison between Unknown and I4 //IL_008d: Unknown result type (might be due to invalid IL or missing references) //IL_00a6: Unknown result type (might be due to invalid IL or missing references) //IL_00b0: Unknown result type (might be due to invalid IL or missing references) //IL_00b5: Unknown result type (might be due to invalid IL or missing references) //IL_00cc: Unknown result type (might be due to invalid IL or missing references) //IL_00cf: Unknown result type (might be due to invalid IL or missing references) //IL_00d1: Invalid comparison between Unknown and I4 //IL_00c4: Unknown result type (might be due to invalid IL or missing references) //IL_00c9: Unknown result type (might be due to invalid IL or missing references) //IL_00fc: Unknown result type (might be due to invalid IL or missing references) //IL_0101: Unknown result type (might be due to invalid IL or missing references) //IL_00df: Unknown result type (might be due to invalid IL or missing references) //IL_00e4: Unknown result type (might be due to invalid IL or missing references) //IL_00ee: Unknown result type (might be due to invalid IL or missing references) //IL_00f3: Unknown result type (might be due to invalid IL or missing references) if ((level & 1) > 0) { return Color.Lerp(_theme.TextColor, new Color(1f, 0.18f, 0.18f, 1f), 0.78f); } if ((level & 2) > 0) { return Color.Lerp(_theme.TextColor, new Color(1f, 0.3f, 0.3f, 1f), 0.68f); } if ((level & 4) > 0) { return Color.Lerp(_theme.TextColor, new Color(1f, 0.76f, 0.18f, 1f), 0.62f); } if ((level & 8) > 0) { return Color.white; } if ((level & 0x20) > 0) { return Color.Lerp(_theme.TextColor, Color.gray, 0.48f); } return _theme.TextColor; } private static bool Contains(string value, string search) { return !string.IsNullOrEmpty(value) && !string.IsNullOrEmpty(search) && value.IndexOf(search, StringComparison.OrdinalIgnoreCase) >= 0; } private void DrawIssuesTab() { DrawIssueFilters(); EnsureIssueView(); float num = ((_selectedIssue != null) ? 146f : 0f); float height = Mathf.Max(130f, MainWindowHeight - 166f - num); DrawIssueHeader(); DrawIssueRows(height); if (_selectedIssue != null) { GUILayout.Space(3f); DrawIssueDetails(num); } } private void DrawIssueFilters() { //IL_0022: Unknown result type (might be due to invalid IL or missing references) //IL_003c: Expected O, but got Unknown GUILayout.BeginHorizontal(Array.Empty()); bool flag = ProfilerGui.ToggleLayout(_theme, _includeWarningsInIssues, new GUIContent("Include warnings", "Include Warning groups alongside Error and Fatal groups. Warning entries are always retained in the raw Stream while their level filter is enabled."), 135f, _labelStyle); if (flag != _includeWarningsInIssues) { _includeWarningsInIssues = flag; _issuesViewDirty = true; } GUILayout.Space(8f); Label("Search:", GUILayout.Width(50f)); string text = GUILayout.TextField(_issuesSearch ?? string.Empty, (GUILayoutOption[])(object)new GUILayoutOption[3] { GUILayout.MinWidth(160f), GUILayout.MaxWidth(440f), GUILayout.ExpandWidth(true) }); if (!string.Equals(text, _issuesSearch, StringComparison.Ordinal)) { _issuesSearch = text; _issuesViewDirty = true; } bool enabled = GUI.enabled; GUI.enabled = enabled && !string.IsNullOrEmpty(_issuesSearch); if (GUILayout.Button("Clear", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(54f) })) { _issuesSearch = string.Empty; _issuesViewDirty = true; } GUI.enabled = enabled; GUILayout.EndHorizontal(); GUILayout.Space(2f); } private void EnsureIssueView() { if (_issuesViewDirty) { IEnumerable source = _issues.Where((IssueGroup group) => (_includeWarningsInIssues || !IsWarningLevel(group.Level)) && MatchesSearch(group, _issuesSearch)); _filteredIssues.Clear(); List filteredIssues = _filteredIssues; IssueSortColumn issueSortColumn = _issueSortColumn; if (1 == 0) { } IOrderedEnumerable collection = issueSortColumn switch { IssueSortColumn.FirstSeen => source.OrderByDescending((IssueGroup group) => group.FirstSeen), IssueSortColumn.LastSeen => source.OrderByDescending((IssueGroup group) => group.LastSeen), IssueSortColumn.Level => from @group in source orderby SeverityRank(@group.Level) descending, @group.LastSeen descending select @group, IssueSortColumn.Source => source.OrderByDescending((IssueGroup group) => group.Source, StringComparer.OrdinalIgnoreCase).ThenByDescending((IssueGroup group) => group.Count), IssueSortColumn.Message => source.OrderByDescending((IssueGroup group) => group.Message, StringComparer.OrdinalIgnoreCase).ThenByDescending((IssueGroup group) => group.Count), _ => from @group in source orderby @group.Count descending, @group.LastSeen descending select @group, }; if (1 == 0) { } filteredIssues.AddRange(collection); _issuesViewDirty = false; } } private void DrawIssueHeader() { //IL_0029: Unknown result type (might be due to invalid IL or missing references) //IL_002e: Unknown result type (might be due to invalid IL or missing references) Rect rect = GUILayoutUtility.GetRect(1f, HeaderHeight, (GUILayoutOption[])(object)new GUILayoutOption[2] { GUILayout.ExpandWidth(true), GUILayout.Height(HeaderHeight) }); GetIssueColumnWidths(((Rect)(ref rect)).width, out var levelWidth, out var countWidth, out var firstWidth, out var lastWidth, out var sourceWidth, out var messageWidth); float x = ((Rect)(ref rect)).x; DrawIssueHeaderCell(ref x, ((Rect)(ref rect)).y, levelWidth, ((Rect)(ref rect)).height, "Level", "Highest severity represented by this exact issue group.", IssueSortColumn.Level); DrawIssueHeaderCell(ref x, ((Rect)(ref rect)).y, countWidth, ((Rect)(ref rect)).height, "Count", "Number of identical captured events since Clear or group creation.", IssueSortColumn.Count); DrawIssueHeaderCell(ref x, ((Rect)(ref rect)).y, firstWidth, ((Rect)(ref rect)).height, "First", "Time this exact group was first observed.", IssueSortColumn.FirstSeen); DrawIssueHeaderCell(ref x, ((Rect)(ref rect)).y, lastWidth, ((Rect)(ref rect)).height, "Last", "Time this exact group was most recently observed.", IssueSortColumn.LastSeen); DrawIssueHeaderCell(ref x, ((Rect)(ref rect)).y, sourceWidth, ((Rect)(ref rect)).height, "Source", "BepInEx logger source.", IssueSortColumn.Source); DrawIssueHeaderCell(ref x, ((Rect)(ref rect)).y, messageWidth, ((Rect)(ref rect)).height, "Message", "First line of the grouped event.", IssueSortColumn.Message); } private void DrawIssueRows(float height) { //IL_0003: Unknown result type (might be due to invalid IL or missing references) //IL_0019: Unknown result type (might be due to invalid IL or missing references) //IL_001e: 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_0064: Unknown result type (might be due to invalid IL or missing references) //IL_0155: Unknown result type (might be due to invalid IL or missing references) //IL_013e: Unknown result type (might be due to invalid IL or missing references) //IL_018a: Unknown result type (might be due to invalid IL or missing references) //IL_018f: Unknown result type (might be due to invalid IL or missing references) //IL_0194: Unknown result type (might be due to invalid IL or missing references) //IL_0199: Unknown result type (might be due to invalid IL or missing references) //IL_01c3: Unknown result type (might be due to invalid IL or missing references) _issuesScroll = GUILayout.BeginScrollView(_issuesScroll, false, false, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Height(height) }); float num = Mathf.Max(1f, (float)_filteredIssues.Count * RowHeight); Rect rect = GUILayoutUtility.GetRect(1f, num, (GUILayoutOption[])(object)new GUILayoutOption[2] { GUILayout.ExpandWidth(true), GUILayout.Height(num) }); float num2 = _issuesScroll.y - 60f; float num3 = _issuesScroll.y + height + 60f; int num4 = Mathf.Clamp(Mathf.FloorToInt(num2 / RowHeight), 0, _filteredIssues.Count); int num5 = Mathf.Clamp(Mathf.CeilToInt(num3 / RowHeight), 0, _filteredIssues.Count); GetIssueColumnWidths(((Rect)(ref rect)).width, out var levelWidth, out var countWidth, out var firstWidth, out var lastWidth, out var sourceWidth, out var messageWidth); Rect val = default(Rect); for (int i = num4; i < num5; i++) { IssueGroup issueGroup = _filteredIssues[i]; float num6 = ((Rect)(ref rect)).y + (float)i * RowHeight; ((Rect)(ref val))..ctor(((Rect)(ref rect)).x, num6, ((Rect)(ref rect)).width, RowHeight); if (_selectedIssue == issueGroup) { GUI.Box(val, GUIContent.none, GUI.skin.box); } if (GUI.Button(val, GUIContent.none, GUIStyle.none)) { _selectedIssue = ((_selectedIssue == issueGroup) ? null : issueGroup); } float x = ((Rect)(ref val)).x; Color contentColor = GUI.contentColor; GUI.contentColor = GetLevelColor(issueGroup.Level); DrawCell(ref x, num6, levelWidth, RowHeight, issueGroup.LevelText, _labelStyle); GUI.contentColor = contentColor; DrawCell(ref x, num6, countWidth, RowHeight, issueGroup.Count.ToString(), _labelStyle); DrawCell(ref x, num6, firstWidth, RowHeight, (issueGroup.FirstSeen == default(DateTime)) ? "--:--:--" : issueGroup.FirstSeen.ToString("HH:mm:ss"), _labelStyle); DrawCell(ref x, num6, lastWidth, RowHeight, (issueGroup.LastSeen == default(DateTime)) ? "--:--:--" : issueGroup.LastSeen.ToString("HH:mm:ss"), _labelStyle); DrawCell(ref x, num6, sourceWidth, RowHeight, issueGroup.Source, _labelStyle); DrawCell(ref x, num6, messageWidth, RowHeight, issueGroup.Message, _labelStyle); } GUILayout.EndScrollView(); } private void DrawIssueDetails(float height) { //IL_0196: Unknown result type (might be due to invalid IL or missing references) //IL_01aa: Unknown result type (might be due to invalid IL or missing references) //IL_01af: Unknown result type (might be due to invalid IL or missing references) IssueGroup selectedIssue = _selectedIssue; if (selectedIssue != null) { GUILayout.BeginVertical(GUI.skin.box, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Height(height) }); GUILayout.BeginHorizontal(Array.Empty()); HeaderLabel($"[{selectedIssue.LevelText}:{selectedIssue.Source}] Count: {selectedIssue.Count}", GUILayout.ExpandWidth(true)); if (GUILayout.Button("Copy", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(58f) })) { GUIUtility.systemCopyBuffer = selectedIssue.GetClipboardText(); } if (GUILayout.Button("Close", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(58f) })) { _selectedIssue = null; } GUILayout.EndHorizontal(); Label("First: " + ((selectedIssue.FirstSeen == default(DateTime)) ? "unknown" : selectedIssue.FirstSeen.ToString("HH:mm:ss.fff")) + " | Last: " + ((selectedIssue.LastSeen == default(DateTime)) ? "unknown" : selectedIssue.LastSeen.ToString("HH:mm:ss.fff")) + " | " + string.Format("Last thread: {0} | Last scene: {1}", selectedIssue.LastThreadId, string.IsNullOrEmpty(selectedIssue.Scene) ? "(none)" : selectedIssue.Scene)); _issueDetailsScroll = GUILayout.BeginScrollView(_issueDetailsScroll, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.ExpandHeight(true) }); GUILayout.Label(BuildFullText(selectedIssue.Message, selectedIssue.Details), _detailsStyle, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.ExpandWidth(true) }); GUILayout.EndScrollView(); GUILayout.EndVertical(); } } private void DrawIssueHeaderCell(ref float x, float y, float width, float height, string text, string tooltip, IssueSortColumn column) { //IL_0027: Unknown result type (might be due to invalid IL or missing references) //IL_003e: Unknown result type (might be due to invalid IL or missing references) //IL_0049: Expected O, but got Unknown Rect val = default(Rect); ((Rect)(ref val))..ctor(x, y, width, height); GUIStyle val2 = ((_issueSortColumn == column) ? _activeHeaderLabelStyle : _headerLabelStyle); if (GUI.Button(val, new GUIContent(text ?? string.Empty, tooltip ?? string.Empty), val2)) { _issueSortColumn = column; _app.Config.ServerLogIssueSortColumn.Value = column.ToString(); _issuesViewDirty = true; } x += width; } private static bool MatchesSearch(IssueGroup group, string search) { if (string.IsNullOrWhiteSpace(search)) { return true; } return Contains(group.Source, search) || Contains(group.Message, search) || Contains(group.Details, search) || Contains(group.Scene, search) || Contains(group.LevelText, search); } private static int SeverityRank(LogLevel level) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0003: Unknown result type (might be due to invalid IL or missing references) //IL_0005: Invalid comparison between Unknown and I4 //IL_000f: Unknown result type (might be due to invalid IL or missing references) //IL_0011: Unknown result type (might be due to invalid IL or missing references) //IL_0013: Invalid comparison between Unknown and I4 //IL_001d: Unknown result type (might be due to invalid IL or missing references) //IL_001f: Unknown result type (might be due to invalid IL or missing references) //IL_0021: Invalid comparison between Unknown and I4 if ((level & 1) > 0) { return 3; } if ((level & 2) > 0) { return 2; } if ((level & 4) > 0) { return 1; } return 0; } private static void GetIssueColumnWidths(float availableWidth, out float levelWidth, out float countWidth, out float firstWidth, out float lastWidth, out float sourceWidth, out float messageWidth) { levelWidth = 72f; countWidth = 66f; firstWidth = 76f; lastWidth = 76f; sourceWidth = Mathf.Clamp(availableWidth * 0.22f, 125f, 240f); messageWidth = Mathf.Max(160f, availableWidth - levelWidth - countWidth - firstWidth - lastWidth - sourceWidth); } private void DrawStreamTab() { DrawStreamFilters(); DrawHistoryControls(); EnsureStreamView(); float num = ((_selectedEntries.Count == 1 && _selectedEntry != null) ? 132f : 0f); float height = Mathf.Max(130f, MainWindowHeight - 212f - num); DrawStreamHeader(); DrawStreamRows(height); if (_selectedEntries.Count == 1 && _selectedEntry != null) { GUILayout.Space(3f); DrawStreamDetails(num); } } private void DrawStreamFilters() { //IL_009b: Unknown result type (might be due to invalid IL or missing references) //IL_00b5: Expected O, but got Unknown //IL_0218: Unknown result type (might be due to invalid IL or missing references) //IL_0235: Expected O, but got Unknown //IL_028b: Unknown result type (might be due to invalid IL or missing references) //IL_02a8: Expected O, but got Unknown //IL_02d9: Unknown result type (might be due to invalid IL or missing references) //IL_02f8: Expected O, but got Unknown //IL_036f: Unknown result type (might be due to invalid IL or missing references) //IL_0389: Expected O, but got Unknown GUILayout.BeginHorizontal(Array.Empty()); DrawLevelFilterButton("D", "Debug", (LogLevel)32); DrawLevelFilterButton("I", "Info", (LogLevel)16); DrawLevelFilterButton("M", "Message", (LogLevel)8); DrawLevelFilterButton("W", "Warning", (LogLevel)4); DrawLevelFilterButton("E", "Error", (LogLevel)2); DrawLevelFilterButton("F", "Fatal", (LogLevel)1); GUILayout.Space(6f); bool flag = ProfilerGui.ToggleLayout(_theme, _followStream, new GUIContent("Follow", "Keep the stream scrolled to the newest matching entry."), 82f, _labelStyle); if (flag != _followStream) { _followStream = flag; if (_followStream) { _scrollStreamToEnd = true; } } GUILayout.Space(6f); Label("Search:", GUILayout.Width(50f)); string text = GUILayout.TextField(_streamSearch ?? string.Empty, (GUILayoutOption[])(object)new GUILayoutOption[3] { GUILayout.MinWidth(130f), GUILayout.MaxWidth(360f), GUILayout.ExpandWidth(true) }); if (!string.Equals(text, _streamSearch, StringComparison.Ordinal)) { _streamSearch = text; _streamViewDirty = true; if (_followStream) { _scrollStreamToEnd = true; } } bool enabled = GUI.enabled; GUI.enabled = enabled && !string.IsNullOrEmpty(_streamSearch); if (GUILayout.Button("Clear", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(54f) })) { _streamSearch = string.Empty; _streamViewDirty = true; } GUI.enabled = enabled; GUI.enabled = enabled && (_filteredStream.Count > 0 || _entries.Count > 0); if (GUILayout.Button(new GUIContent("Copy filtered", "Copy all currently filtered Stream rows in chronological order."), (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(94f) })) { EnsureStreamView(); CopyFilteredStream(); } GUI.enabled = enabled; GUI.enabled = enabled && _selectedEntries.Count > 0; if (GUILayout.Button(new GUIContent("Copy selected", $"Copy {_selectedEntries.Count} selected Stream row(s) in chronological order. Use Ctrl-click to toggle rows and Shift-click to select a range."), (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(100f) })) { CopySelectedStream(); } GUI.enabled = enabled; GUILayout.EndHorizontal(); GUILayout.BeginHorizontal(Array.Empty()); GUILayout.Label(new GUIContent("Selection: Shift-click selects a range | Ctrl-click toggles individual rows", "Click a row for a single selection. Hold Shift to select a continuous range, Ctrl to add or remove individual rows, or Ctrl+Shift to add a range."), _labelStyle, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.ExpandWidth(true) }); bool enabled2 = GUI.enabled; GUI.enabled = enabled2 && _selectedEntries.Count > 0; if (GUILayout.Button("Clear selection", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(104f) })) { ClearStreamSelection(); } GUI.enabled = enabled2; bool flag2 = ProfilerGui.ToggleLayout(_theme, _app.Config.ServerLogCopyMetadata.Value, new GUIContent("Copy metadata", "Include timestamp, thread, scene and history/sequence metadata and use the expanded two-line clipboard format. Disabled preserves the compact BepInEx LogOutput-style header and raw message."), 125f, _labelStyle); if (flag2 != _app.Config.ServerLogCopyMetadata.Value) { _app.Config.ServerLogCopyMetadata.Value = flag2; } GUILayout.EndHorizontal(); GUILayout.Space(2f); } private void DrawHistoryControls() { GUILayout.BeginHorizontal(Array.Empty()); bool historyRequestPending = _historyRequestPending; bool enabled = GUI.enabled; GUI.enabled = enabled && _subscribed && !historyRequestPending && _historyHasMore; if (GUILayout.Button(historyRequestPending ? "Loading..." : "Load older", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(88f) })) { RequestOlderHistory(); } GUI.enabled = enabled; GUI.enabled = enabled && _loadedHistoryEntries > 0 && !historyRequestPending; if (GUILayout.Button("Unload history", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(104f) })) { UnloadHistory(); } GUI.enabled = enabled; Label(string.Format("History: {0} | More: {1}", _loadedHistoryEntries, _historyHasMore ? "yes" : "no"), GUILayout.Width(150f)); Label(_historyStatus, GUILayout.ExpandWidth(true)); GUILayout.EndHorizontal(); GUILayout.Space(2f); } private void DrawLevelFilterButton(string text, string levelName, LogLevel level) { //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_0008: Unknown result type (might be due to invalid IL or missing references) //IL_000a: Invalid comparison between Unknown and I4 //IL_0039: Unknown result type (might be due to invalid IL or missing references) //IL_0057: Expected O, but got Unknown //IL_0078: Unknown result type (might be due to invalid IL or missing references) //IL_007d: Unknown result type (might be due to invalid IL or missing references) //IL_007e: Unknown result type (might be due to invalid IL or missing references) //IL_007f: Unknown result type (might be due to invalid IL or missing references) //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_006d: Unknown result type (might be due to invalid IL or missing references) //IL_006e: Unknown result type (might be due to invalid IL or missing references) //IL_006f: Unknown result type (might be due to invalid IL or missing references) bool flag = (_streamLevelFilter & level) > 0; GUIStyle val = (flag ? _theme.AccentButtonStyle : GUI.skin.button); if (GUILayout.Button(new GUIContent(text, "Show or hide " + levelName + " log entries."), val, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(30f) })) { if (flag) { _streamLevelFilter &= ~level; } else { _streamLevelFilter |= level; } _streamViewDirty = true; } } private void EnsureStreamView() { //IL_002f: Unknown result type (might be due to invalid IL or missing references) //IL_0035: 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_003c: Invalid comparison between Unknown and I4 if (!_streamViewDirty) { return; } _filteredStream.Clear(); for (int i = 0; i < _entries.Count; i++) { LogEntry logEntry = _entries[i]; if ((_streamLevelFilter & logEntry.Level) != 0 && MatchesSearch(logEntry, _streamSearch)) { _filteredStream.Add(logEntry); } } _streamViewDirty = false; PruneStreamSelectionToFilteredView(); } private void CopyFilteredStream() { StringBuilder stringBuilder = new StringBuilder(Math.Min(1048576, Math.Max(256, _filteredStream.Count * 128))); for (int i = 0; i < _filteredStream.Count; i++) { if (i > 0) { stringBuilder.AppendLine(); if (_app.Config.ServerLogCopyMetadata.Value) { stringBuilder.AppendLine(); } } stringBuilder.Append(_filteredStream[i].GetClipboardText(_app.Config.ServerLogCopyMetadata.Value)); } GUIUtility.systemCopyBuffer = stringBuilder.ToString(); } private void CopySelectedStream() { EnsureStreamView(); StringBuilder stringBuilder = new StringBuilder(Math.Min(1048576, Math.Max(256, _selectedEntries.Count * 128))); int num = 0; for (int i = 0; i < _filteredStream.Count; i++) { LogEntry logEntry = _filteredStream[i]; if (!_selectedEntries.Contains(logEntry)) { continue; } if (num++ > 0) { stringBuilder.AppendLine(); if (_app.Config.ServerLogCopyMetadata.Value) { stringBuilder.AppendLine(); } } stringBuilder.Append(logEntry.GetClipboardText(_app.Config.ServerLogCopyMetadata.Value)); } if (num > 0) { GUIUtility.systemCopyBuffer = stringBuilder.ToString(); } } private void HandleStreamRowClick(int index, LogEntry entry, bool control, bool shift) { if (shift) { int num = ((_selectionAnchorEntry == null) ? (-1) : _filteredStream.IndexOf(_selectionAnchorEntry)); if (num < 0) { num = index; } if (!control) { _selectedEntries.Clear(); } int num2 = Math.Min(num, index); int num3 = Math.Max(num, index); for (int i = num2; i <= num3; i++) { _selectedEntries.Add(_filteredStream[i]); } _selectedEntry = entry; if (_selectionAnchorEntry == null) { _selectionAnchorEntry = entry; } } else if (control) { if (!_selectedEntries.Add(entry)) { _selectedEntries.Remove(entry); } _selectionAnchorEntry = entry; _selectedEntry = (_selectedEntries.Contains(entry) ? entry : FindFirstSelectedEntryInView()); if (_selectedEntries.Count == 0) { ClearStreamSelection(); } } else if (_selectedEntries.Count == 1 && _selectedEntries.Contains(entry)) { ClearStreamSelection(); } else { _selectedEntries.Clear(); _selectedEntries.Add(entry); _selectedEntry = entry; _selectionAnchorEntry = entry; } } private void ClearStreamSelection() { //IL_0021: Unknown result type (might be due to invalid IL or missing references) _selectedEntries.Clear(); _selectedEntry = null; _selectionAnchorEntry = null; _streamDetailsScroll = default(Vector2); } private void RemoveEntryFromStreamSelection(LogEntry entry) { if (entry != null) { _selectedEntries.Remove(entry); if (_selectionAnchorEntry == entry) { _selectionAnchorEntry = null; } if (_selectedEntry == entry) { _selectedEntry = FindFirstSelectedEntryInView(); } if (_selectedEntries.Count == 0) { ClearStreamSelection(); } else if (_selectionAnchorEntry == null) { _selectionAnchorEntry = _selectedEntry; } } } private LogEntry FindFirstSelectedEntryInView() { for (int i = 0; i < _filteredStream.Count; i++) { if (_selectedEntries.Contains(_filteredStream[i])) { return _filteredStream[i]; } } return null; } private void PruneStreamSelectionToFilteredView() { if (_selectedEntries.Count == 0) { return; } HashSet visible = new HashSet(_filteredStream); _selectedEntries.RemoveWhere((LogEntry entry) => !visible.Contains(entry)); if (_selectedEntries.Count == 0) { ClearStreamSelection(); return; } if (_selectedEntry == null || !_selectedEntries.Contains(_selectedEntry)) { _selectedEntry = FindFirstSelectedEntryInView(); } if (_selectionAnchorEntry == null || !_selectedEntries.Contains(_selectionAnchorEntry)) { _selectionAnchorEntry = _selectedEntry; } } private void DrawStreamHeader() { //IL_0029: Unknown result type (might be due to invalid IL or missing references) //IL_002e: Unknown result type (might be due to invalid IL or missing references) Rect rect = GUILayoutUtility.GetRect(1f, HeaderHeight, (GUILayoutOption[])(object)new GUILayoutOption[2] { GUILayout.ExpandWidth(true), GUILayout.Height(HeaderHeight) }); GetStreamColumnWidths(((Rect)(ref rect)).width, out var timeWidth, out var levelWidth, out var sourceWidth, out var messageWidth); float x = ((Rect)(ref rect)).x; DrawHeaderCell(ref x, ((Rect)(ref rect)).y, timeWidth, ((Rect)(ref rect)).height, "Time", "Server event time converted to the client local time zone. Unity timestamps are removed from Message and kept here."); DrawHeaderCell(ref x, ((Rect)(ref rect)).y, levelWidth, ((Rect)(ref rect)).height, "Level", "BepInEx log level."); DrawHeaderCell(ref x, ((Rect)(ref rect)).y, sourceWidth, ((Rect)(ref rect)).height, "Source", "Dedicated-server BepInEx logger source. Unity messages are normally forwarded through a Unity Log source."); DrawHeaderCell(ref x, ((Rect)(ref rect)).y, messageWidth, ((Rect)(ref rect)).height, "Message", "First line of the captured log event. Click to select one row, Ctrl-click to toggle rows, or Shift-click to select a range."); } private void DrawStreamRows(float height) { //IL_001f: Unknown result type (might be due to invalid IL or missing references) //IL_0035: 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_007b: Unknown result type (might be due to invalid IL or missing references) //IL_0080: Unknown result type (might be due to invalid IL or missing references) //IL_02a8: Unknown result type (might be due to invalid IL or missing references) //IL_02ae: Invalid comparison between Unknown and I4 //IL_01b2: Unknown result type (might be due to invalid IL or missing references) //IL_017d: Unknown result type (might be due to invalid IL or missing references) //IL_0200: Unknown result type (might be due to invalid IL or missing references) //IL_0205: Unknown result type (might be due to invalid IL or missing references) //IL_020a: Unknown result type (might be due to invalid IL or missing references) //IL_020f: Unknown result type (might be due to invalid IL or missing references) //IL_0239: Unknown result type (might be due to invalid IL or missing references) if (_scrollStreamToEnd) { _streamScroll.y = float.MaxValue; } _streamScroll = GUILayout.BeginScrollView(_streamScroll, false, false, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Height(height) }); float num = Mathf.Max(1f, (float)_filteredStream.Count * RowHeight); Rect rect = GUILayoutUtility.GetRect(1f, num, (GUILayoutOption[])(object)new GUILayoutOption[2] { GUILayout.ExpandWidth(true), GUILayout.Height(num) }); float num2 = _streamScroll.y - 60f; float num3 = _streamScroll.y + height + 60f; int num4 = Mathf.Clamp(Mathf.FloorToInt(num2 / RowHeight), 0, _filteredStream.Count); int num5 = Mathf.Clamp(Mathf.CeilToInt(num3 / RowHeight), 0, _filteredStream.Count); GetStreamColumnWidths(((Rect)(ref rect)).width, out var timeWidth, out var levelWidth, out var sourceWidth, out var messageWidth); Rect val = default(Rect); for (int i = num4; i < num5; i++) { LogEntry logEntry = _filteredStream[i]; float num6 = ((Rect)(ref rect)).y + (float)i * RowHeight; ((Rect)(ref val))..ctor(((Rect)(ref rect)).x, num6, ((Rect)(ref rect)).width, RowHeight); if (_selectedEntries.Contains(logEntry)) { GUIStyle val2 = ((_selectedEntry == logEntry) ? _theme.AccentButtonStyle : GUI.skin.box); GUI.Box(val, GUIContent.none, val2); } Event current = Event.current; bool control = current.control || current.command; bool shift = current.shift; if (GUI.Button(val, GUIContent.none, GUIStyle.none)) { HandleStreamRowClick(i, logEntry, control, shift); } float x = ((Rect)(ref val)).x; DrawCell(ref x, num6, timeWidth, RowHeight, logEntry.TimeText, _labelStyle); Color contentColor = GUI.contentColor; GUI.contentColor = GetLevelColor(logEntry.Level); DrawCell(ref x, num6, levelWidth, RowHeight, logEntry.LevelText, _labelStyle); GUI.contentColor = contentColor; DrawCell(ref x, num6, sourceWidth, RowHeight, logEntry.Source, _labelStyle); DrawCell(ref x, num6, messageWidth, RowHeight, logEntry.Message, _labelStyle); } GUILayout.EndScrollView(); if (_scrollStreamToEnd && (int)Event.current.type == 7) { _scrollStreamToEnd = false; } } private void DrawStreamDetails(float height) { //IL_01f7: Unknown result type (might be due to invalid IL or missing references) //IL_020b: Unknown result type (might be due to invalid IL or missing references) //IL_0210: Unknown result type (might be due to invalid IL or missing references) LogEntry selectedEntry = _selectedEntry; if (selectedEntry == null) { return; } GUILayout.BeginVertical(GUI.skin.box, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Height(height) }); GUILayout.BeginHorizontal(Array.Empty()); string text = ((_selectedEntries.Count > 1) ? $"{_selectedEntries.Count} selected | " : string.Empty); HeaderLabel(text + selectedEntry.TimeText + " [" + selectedEntry.LevelText + ":" + selectedEntry.Source + "]", GUILayout.ExpandWidth(true)); if (GUILayout.Button((_selectedEntries.Count > 1) ? "Copy selected" : "Copy", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width((_selectedEntries.Count > 1) ? 100f : 58f) })) { if (_selectedEntries.Count > 1) { CopySelectedStream(); } else { GUIUtility.systemCopyBuffer = selectedEntry.GetClipboardText(_app.Config.ServerLogCopyMetadata.Value); } } if (GUILayout.Button("Close", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(58f) })) { ClearStreamSelection(); } GUILayout.EndHorizontal(); string arg = (selectedEntry.IsHistorical ? $"History offset: {selectedEntry.FileOffset}" : $"Sequence: {selectedEntry.Sequence}"); Label(string.Format("{0} | Thread: {1} | Scene: {2}", arg, selectedEntry.ThreadId, string.IsNullOrEmpty(selectedEntry.Scene) ? "(none)" : selectedEntry.Scene)); _streamDetailsScroll = GUILayout.BeginScrollView(_streamDetailsScroll, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.ExpandHeight(true) }); GUILayout.Label(BuildFullText(selectedEntry.Message, selectedEntry.Details), _detailsStyle, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.ExpandWidth(true) }); GUILayout.EndScrollView(); GUILayout.EndVertical(); } private static bool MatchesSearch(LogEntry entry, string search) { if (string.IsNullOrWhiteSpace(search)) { return true; } return Contains(entry.Source, search) || Contains(entry.Message, search) || Contains(entry.RawMessage, search) || Contains(entry.Details, search) || Contains(entry.Scene, search) || Contains(entry.LevelText, search); } private static string BuildFullText(string message, string details) { if (string.IsNullOrEmpty(details)) { return message ?? string.Empty; } if (string.IsNullOrEmpty(message)) { return details; } return message + Environment.NewLine + details; } private static void GetStreamColumnWidths(float availableWidth, out float timeWidth, out float levelWidth, out float sourceWidth, out float messageWidth) { timeWidth = 92f; levelWidth = 72f; sourceWidth = Mathf.Clamp(availableWidth * 0.24f, 130f, 260f); messageWidth = Mathf.Max(160f, availableWidth - timeWidth - levelWidth - sourceWidth); } internal ServerLogMonitorTool(ValheimProfilerApp app) { //IL_003f: Unknown result type (might be due to invalid IL or missing references) //IL_012c: Unknown result type (might be due to invalid IL or missing references) //IL_012d: Unknown result type (might be due to invalid IL or missing references) //IL_0132: Unknown result type (might be due to invalid IL or missing references) //IL_0144: Unknown result type (might be due to invalid IL or missing references) //IL_0149: Unknown result type (might be due to invalid IL or missing references) //IL_014a: Unknown result type (might be due to invalid IL or missing references) //IL_014f: Unknown result type (might be due to invalid IL or missing references) _app = app ?? throw new ArgumentNullException("app"); _windows = app.Windows; _theme = app.Theme; _issueSortColumn = ParseIssueSortColumn(app.Config.ServerLogIssueSortColumn.Value); Vector2 minimumSize = default(Vector2); ((Vector2)(ref minimumSize))..ctor(760f, 460f); Vector2 defaultToolWindowSize = _windows.GetDefaultToolWindowSize(680f, minimumSize); _mainWindow = _windows.Register(new ProfilerWindow("ValheimProfiler.ServerLogMonitor", "Server Log Monitor", new Rect(ValheimProfilerConfig.DefaultServerLogMonitorWindowPosition, defaultToolWindowSize), minimumSize, resizable: true, requestedVisible: false, DrawWindow, app.Config.ServerLogMonitorWindowPosition, app.Config.ServerLogMonitorWindowSize)); } void IProfilerTool.ShowWindow() { ShowWindow(); } void IProfilerTool.ToggleWindow() { ToggleWindow(); } void IProfilerTool.Update() { Update(); } void IProfilerTool.Shutdown() { Shutdown(); } internal void ToggleWindow() { IsWindowVisible = !IsWindowVisible; if (IsWindowVisible) { ShowWindow(); } } internal void ShowWindow() { IsWindowVisible = true; _app.ShowUi(); _windows.BringToFront(_mainWindow); if (!IsAvailable) { _mainTab = MainTab.Help; } } internal void Update() { UpdateAvailability(); UpdateRequestTimeouts(); } internal void Shutdown() { if (_subscribed && IsDedicatedServerConnectionDetected()) { SendRequest(ServerLogRequestKind.Unsubscribe, "", 0L, 0L, 0L); } IsWindowVisible = false; _subscribed = false; ClearLocalView(resetSession: true); } internal void OnNetworkDestroyed() { _networkRpc = null; _probePending = false; _subscriptionRequestPending = false; _historyRequestPending = false; _serverAuthorized = false; _startupHistoryRequestedSession = string.Empty; _subscribed = false; _sessionId = string.Empty; _serverVersion = string.Empty; _availability = AvailabilityState.NoDedicatedConnection; _availabilityStatus = "Connect to a dedicated server to use Server Log Monitor."; _nextProbeRealtime = 0f; if (IsWindowVisible) { _mainTab = MainTab.Help; } ClearLocalView(resetSession: true); } internal bool IsDedicatedServerConnectionDetected() { //IL_0008: Unknown result type (might be due to invalid IL or missing references) //IL_000e: Invalid comparison between Unknown and I4 try { ZNet instance = ZNet.instance; return (int)SystemInfo.graphicsDeviceType != 4 && (Object)(object)instance != (Object)null && !instance.IsServer() && !instance.IsDedicated() && instance.IsCurrentServerDedicated() && ZRoutedRpc.instance != null; } catch { return false; } } private void UpdateAvailability() { if (!IsDedicatedServerConnectionDetected()) { if (_availability != AvailabilityState.NoDedicatedConnection || _networkRpc != null) { OnNetworkDestroyed(); } return; } ZRoutedRpc instance = ZRoutedRpc.instance; if (_networkRpc != instance) { _networkRpc = instance; _subscribed = false; _sessionId = string.Empty; _availability = AvailabilityState.Detecting; _availabilityStatus = "Checking whether the dedicated server supports remote log monitoring..."; _probePending = false; _nextProbeRealtime = Time.realtimeSinceStartup + 0.15f; if (IsWindowVisible) { _mainTab = MainTab.Help; } ClearLocalView(resetSession: true); } float realtimeSinceStartup = Time.realtimeSinceStartup; if (_probePending && realtimeSinceStartup >= _probeDeadlineRealtime) { _probePending = false; _availability = AvailabilityState.ServerModUnavailable; _availabilityStatus = "The dedicated server did not answer. Valheim Profiler is probably not installed there, is too old, or its RPC backend is unavailable."; _nextProbeRealtime = realtimeSinceStartup + 10f; if (IsWindowVisible) { _mainTab = MainTab.Help; } } if (!_probePending && _availability != AvailabilityState.Available && realtimeSinceStartup >= _nextProbeRealtime) { ProbeServer(); } } private void UpdateRequestTimeouts() { float realtimeSinceStartup = Time.realtimeSinceStartup; if (_subscriptionRequestPending && realtimeSinceStartup >= _requestDeadlineRealtime) { _subscriptionRequestPending = false; _subscribed = false; _availability = AvailabilityState.ServerModUnavailable; _availabilityStatus = "The dedicated server stopped responding to Server Log Monitor requests."; _nextProbeRealtime = realtimeSinceStartup + 10f; if (IsWindowVisible) { _mainTab = MainTab.Help; } } if (_historyRequestPending && realtimeSinceStartup >= _requestDeadlineRealtime) { _historyRequestPending = false; _historyStatus = "The server log history request timed out."; } } private static IssueSortColumn ParseIssueSortColumn(string value) { IssueSortColumn result; return (Enum.TryParse(value, ignoreCase: true, out result) && Enum.IsDefined(typeof(IssueSortColumn), result)) ? result : IssueSortColumn.Count; } private void ProbeServer() { if (IsDedicatedServerConnectionDetected() && !_probePending) { _availability = AvailabilityState.Detecting; _availabilityStatus = "Checking whether the dedicated server supports remote log monitoring..."; if (!SendRequest(ServerLogRequestKind.Probe, "", 0L, 0L, 0L)) { _availability = AvailabilityState.ServerModUnavailable; _availabilityStatus = "The dedicated server RPC connection is not ready."; _nextProbeRealtime = Time.realtimeSinceStartup + 10f; } else { _probePending = true; _probeDeadlineRealtime = Time.realtimeSinceStartup + 3f; } } } private void Subscribe() { if (IsAvailable && !_subscribed && !_subscriptionRequestPending && SendRequest(ServerLogRequestKind.Subscribe, "", 0L, 0L, 0L)) { _subscriptionRequestPending = true; _requestDeadlineRealtime = Time.realtimeSinceStartup + 6f; _availabilityStatus = "Requesting the recent dedicated-server log snapshot..."; _mainTab = MainTab.Stream; } } private void Unsubscribe() { if (_subscribed) { SendRequest(ServerLogRequestKind.Unsubscribe, "", 0L, 0L, 0L); } _subscribed = false; _subscriptionRequestPending = false; _historyRequestPending = false; _availabilityStatus = "Dedicated server log access is available. Subscribe to receive entries."; } private void RequestOlderHistory(bool startupBackfill = false) { if (_subscribed && !_historyRequestPending && _historyHasMore && SendRequest(ServerLogRequestKind.RequestOlder, _sessionId, _historyCursor, _lastLiveSequence, _historyFileCreationUtcTicks)) { _historyRequestPending = true; _requestDeadlineRealtime = Time.realtimeSinceStartup + 6f; _historyStatus = (startupBackfill ? "Loading server startup history..." : "Requesting older server log entries..."); } } private bool SendRequest(ServerLogRequestKind kind, string sessionId = "", long cursor = 0L, long lastSequence = 0L, long fileCreationUtcTicks = 0L) { try { ZRoutedRpc instance = ZRoutedRpc.instance; if (instance == null) { return false; } long serverPeerID = instance.GetServerPeerID(); if (serverPeerID == 0) { return false; } ZPackage val = ServerLogProtocol.CreateRequest(kind, sessionId, cursor, lastSequence, fileCreationUtcTicks); if (val.Size() > 16384) { return false; } string error; return ServerLogTransport.Send(serverPeerID, "shudnal.ValheimProfiler.ServerLog.Request", val, out error); } catch { return false; } } internal void HandleResponse(long sender, ZPackage package) { try { ZRoutedRpc instance = ZRoutedRpc.instance; if (instance == null) { return; } long serverPeerID = instance.GetServerPeerID(); if (serverPeerID != 0L && sender != serverPeerID) { return; } ZPackage payload; string error; switch (ServerLogTransport.TryReceive(sender, package, out payload, out error)) { case ServerLogTransportReceiveResult.WaitingForFragments: return; case ServerLogTransportReceiveResult.Rejected: throw new InvalidOperationException("Invalid Server Log Monitor transport payload: " + error); } if (payload.Size() > 4194304) { throw new InvalidOperationException("Oversized Server Log Monitor response payload."); } ServerLogResponse serverLogResponse = ServerLogProtocol.ReadResponse(payload); _probePending = false; _serverVersion = serverLogResponse.ServerVersion ?? string.Empty; _serverDroppedCount = serverLogResponse.DroppedCount; _serverAuthorized = serverLogResponse.Authorized; ApplyCapabilityState(serverLogResponse); switch (serverLogResponse.Kind) { case ServerLogResponseKind.Capabilities: break; case ServerLogResponseKind.Status: { bool historyRequestPending = _historyRequestPending; _subscriptionRequestPending = false; _historyRequestPending = false; _availabilityStatus = (string.IsNullOrWhiteSpace(serverLogResponse.Status) ? _availabilityStatus : serverLogResponse.Status); if (historyRequestPending) { _historyStatus = _availabilityStatus; } break; } case ServerLogResponseKind.Error: _subscriptionRequestPending = false; _historyRequestPending = false; if (!serverLogResponse.Authorized) { _subscribed = false; if (IsWindowVisible) { _mainTab = MainTab.Help; } } _availabilityStatus = (string.IsNullOrWhiteSpace(serverLogResponse.Status) ? "The dedicated server rejected the log request." : serverLogResponse.Status); _historyStatus = _availabilityStatus; break; case ServerLogResponseKind.Snapshot: ApplySnapshot(serverLogResponse); break; case ServerLogResponseKind.LiveBatch: ApplyLiveBatch(serverLogResponse); break; case ServerLogResponseKind.OlderPage: ApplyOlderPage(serverLogResponse); break; } } catch (Exception ex) { _probePending = false; _subscriptionRequestPending = false; _historyRequestPending = false; _subscribed = false; _availability = AvailabilityState.ProtocolMismatch; _availabilityStatus = "Server Log Monitor protocol error: " + ex.Message; _nextProbeRealtime = Time.realtimeSinceStartup + 10f; if (IsWindowVisible) { _mainTab = MainTab.Help; } } } private void ApplyCapabilityState(ServerLogResponse response) { if (!response.Authorized) { _subscribed = false; _subscriptionRequestPending = false; _availability = AvailabilityState.AccessDenied; _availabilityStatus = (string.IsNullOrWhiteSpace(response.Status) ? "Valheim Profiler is installed, but the current player is not in the server admin list." : response.Status); _nextProbeRealtime = Time.realtimeSinceStartup + 10f; } else { _availability = AvailabilityState.Available; _availabilityStatus = (string.IsNullOrWhiteSpace(response.Status) ? "Dedicated server log access is available. Subscribe to start a private stream." : response.Status); _nextProbeRealtime = float.MaxValue; } } private void ApplySnapshot(ServerLogResponse response) { _subscriptionRequestPending = false; _historyRequestPending = false; bool flag = !string.Equals(_sessionId, response.SessionId, StringComparison.Ordinal); bool flag2 = response.ResetHistory || (_historyFileCreationUtcTicks != 0L && response.FileCreationUtcTicks != 0L && _historyFileCreationUtcTicks != response.FileCreationUtcTicks); long historyCursor = _historyCursor; long historyStartCursor = _historyStartCursor; bool flag3 = !flag && !flag2 && (_loadedHistoryEntries > 0 || historyCursor != historyStartCursor); if (flag || flag2) { ClearLocalView(resetSession: true); } else { RemoveLiveEntries(); } _sessionId = response.SessionId ?? string.Empty; _serverDroppedCount = response.DroppedCount; _subscribed = true; _historyStartCursor = response.HistoryCursor; if (flag3) { _historyCursor = historyCursor; _historyHasMore = historyCursor > 0; } else { _historyCursor = response.HistoryCursor; _historyHasMore = response.HasMoreHistory; } _historyFileCreationUtcTicks = response.FileCreationUtcTicks; _availabilityStatus = (string.IsNullOrWhiteSpace(response.Status) ? "Subscribed to the dedicated server log." : response.Status); for (int i = 0; i < response.Entries.Count; i++) { AddLiveEntry(Convert(response.Entries[i], historicalOverride: false)); } _lastLiveSequence = response.LastSequence; MarkViewsDirty(); if (_followStream) { _scrollStreamToEnd = true; } if (_historyHasMore && !_historyRequestPending && !string.Equals(_startupHistoryRequestedSession, _sessionId, StringComparison.Ordinal)) { _startupHistoryRequestedSession = _sessionId; RequestOlderHistory(startupBackfill: true); } } private void ApplyLiveBatch(ServerLogResponse response) { if (!_subscribed || !string.Equals(_sessionId, response.SessionId, StringComparison.Ordinal)) { _subscribed = false; Subscribe(); } else { if (response.Entries.Count == 0) { return; } long num = ((response.FirstSequence != 0L) ? response.FirstSequence : response.Entries[0].Sequence); if (_lastLiveSequence > 0 && num > _lastLiveSequence + 1) { if (!_subscriptionRequestPending) { _detectedGaps++; _availabilityStatus = $"Detected a server log sequence gap after {_lastLiveSequence}; requesting a recent snapshot."; if (SendRequest(ServerLogRequestKind.Resync, _sessionId, 0L, _lastLiveSequence, _historyFileCreationUtcTicks)) { _subscriptionRequestPending = true; _requestDeadlineRealtime = Time.realtimeSinceStartup + 6f; } } return; } bool flag = false; for (int i = 0; i < response.Entries.Count; i++) { ServerLogWireEntry serverLogWireEntry = response.Entries[i]; if (serverLogWireEntry.Sequence > _lastLiveSequence) { AddLiveEntry(Convert(serverLogWireEntry, historicalOverride: false)); _lastLiveSequence = serverLogWireEntry.Sequence; flag = true; } } if (flag) { MarkViewsDirty(); if (_followStream) { _scrollStreamToEnd = true; } } } } private void ApplyOlderPage(ServerLogResponse response) { _historyRequestPending = false; if (!_subscribed || !string.Equals(_sessionId, response.SessionId, StringComparison.Ordinal)) { _historyStatus = "The server log session changed; subscribe again before loading history."; return; } if (_historyFileCreationUtcTicks != 0L && response.FileCreationUtcTicks != 0L && _historyFileCreationUtcTicks != response.FileCreationUtcTicks) { UnloadHistory(); _historyStatus = "The server LogOutput.log file changed; loaded history was removed."; return; } _historyFileCreationUtcTicks = response.FileCreationUtcTicks; _historyCursor = response.HistoryCursor; _historyHasMore = response.HasMoreHistory; if (response.Entries.Count == 0) { _historyStatus = (string.IsNullOrWhiteSpace(response.Status) ? "No older server log entries." : response.Status); return; } List list = new List(response.Entries.Count); for (int i = 0; i < response.Entries.Count; i++) { list.Add(Convert(response.Entries[i], historicalOverride: true)); } int num = list.Count; if (_entries.Count > 0) { int num2 = LogHistoryMerge.FindOverlap(list, _entries, (LogEntry item) => item.Fingerprint, (LogEntry item) => item.Fingerprint); if (num2 >= 0) { num = num2; } } if (num > 0) { if (num < list.Count) { list.RemoveRange(num, list.Count - num); } _entries.InsertRange(0, list); _loadedHistoryEntries += list.Count; RebuildIssues(); MarkViewsDirty(); } _historyStatus = $"Loaded {num} older server entries. Total history: {_loadedHistoryEntries}."; } private LogEntry Convert(ServerLogWireEntry wire, bool historicalOverride) { //IL_0020: Unknown result type (might be due to invalid IL or missing references) //IL_0025: Unknown result type (might be due to invalid IL or missing references) return new LogEntry { Sequence = wire.Sequence, Timestamp = wire.Timestamp, Level = wire.Level, Source = (wire.Source ?? string.Empty), RawMessage = (wire.RawMessage ?? string.Empty), Message = (wire.Message ?? string.Empty), Details = (wire.Details ?? string.Empty), Scene = (wire.Scene ?? string.Empty), ThreadId = wire.ThreadId, IsHistorical = (historicalOverride || wire.IsHistorical), FileOffset = wire.FileOffset }; } private void AddLiveEntry(LogEntry entry) { //IL_0024: Unknown result type (might be due to invalid IL or missing references) _entries.Add(entry); if (TrimLiveEntriesToLimit()) { RebuildIssues(); } else if (IsIssueLevel(entry.Level)) { AddIssue(entry); } } private bool TrimLiveEntriesToLimit() { int num = Math.Max(500, _app.Config.ServerLogClientMaxEntries.Value); int num2 = 0; for (int i = 0; i < _entries.Count; i++) { if (!_entries[i].IsHistorical) { num2++; } } if (num2 <= num) { return false; } int val = Math.Max(num2 - num, Math.Max(1, num / 10)); int j; for (j = 0; j < _entries.Count && _entries[j].IsHistorical; j++) { } int num3 = Math.Min(val, _entries.Count - j); if (num3 <= 0) { return false; } for (int k = j; k < j + num3; k++) { RemoveEntryFromStreamSelection(_entries[k]); } _entries.RemoveRange(j, num3); return true; } private void RemoveLiveEntries() { int i; for (i = 0; i < _entries.Count && _entries[i].IsHistorical; i++) { } if (i < _entries.Count) { for (int j = i; j < _entries.Count; j++) { RemoveEntryFromStreamSelection(_entries[j]); } _entries.RemoveRange(i, _entries.Count - i); RebuildIssues(); } } private void AddIssue(LogEntry entry) { //IL_014e: Unknown result type (might be due to invalid IL or missing references) //IL_0153: Unknown result type (might be due to invalid IL or missing references) string fingerprint = entry.Fingerprint; if (_issuesByKey.TryGetValue(fingerprint, out var value)) { value.Count++; if (value.FirstSeen == default(DateTime) || (entry.Timestamp != default(DateTime) && entry.Timestamp < value.FirstSeen)) { value.FirstSeen = entry.Timestamp; value.FirstSequence = entry.Sequence; } if (value.LastSeen == default(DateTime) || entry.Timestamp >= value.LastSeen) { value.LastSeen = entry.Timestamp; value.LastSequence = entry.Sequence; value.Scene = entry.Scene; value.LastThreadId = entry.ThreadId; value.Details = entry.Details; } return; } int num = Math.Max(100, _app.Config.ServerLogMaxIssueGroups.Value); if (_issues.Count >= num) { RemoveOldestIssueGroup(); } value = new IssueGroup { Key = fingerprint, Level = entry.Level, Source = entry.Source, Message = entry.Message, Details = entry.Details, Scene = entry.Scene, LastThreadId = entry.ThreadId, Count = 1, FirstSeen = entry.Timestamp, LastSeen = entry.Timestamp, FirstSequence = entry.Sequence, LastSequence = entry.Sequence }; _issuesByKey[fingerprint] = value; _issues.Add(value); } private void RebuildIssues() { //IL_0031: Unknown result type (might be due to invalid IL or missing references) _issues.Clear(); _issuesByKey.Clear(); _selectedIssue = null; for (int i = 0; i < _entries.Count; i++) { if (IsIssueLevel(_entries[i].Level)) { AddIssue(_entries[i]); } } _issuesViewDirty = true; } private void RemoveOldestIssueGroup() { if (_issues.Count == 0) { return; } int index = 0; DateTime lastSeen = _issues[0].LastSeen; for (int i = 1; i < _issues.Count; i++) { if (!(_issues[i].LastSeen >= lastSeen)) { lastSeen = _issues[i].LastSeen; index = i; } } IssueGroup issueGroup = _issues[index]; if (_selectedIssue == issueGroup) { _selectedIssue = null; } _issues.RemoveAt(index); _issuesByKey.Remove(issueGroup.Key); } private void ClearLocalView(bool resetSession = false) { //IL_0051: 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) //IL_0069: 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) _entries.Clear(); _issues.Clear(); _issuesByKey.Clear(); _filteredStream.Clear(); _filteredIssues.Clear(); ClearStreamSelection(); _selectedIssue = null; _streamScroll = default(Vector2); _issuesScroll = default(Vector2); _streamDetailsScroll = default(Vector2); _issueDetailsScroll = default(Vector2); _loadedHistoryEntries = 0; _historyStatus = string.Empty; if (resetSession) { _sessionId = string.Empty; _lastLiveSequence = 0L; _historyCursor = 0L; _historyStartCursor = 0L; _historyFileCreationUtcTicks = 0L; _historyHasMore = false; _detectedGaps = 0; _serverDroppedCount = 0L; _startupHistoryRequestedSession = string.Empty; } else { _historyCursor = _historyStartCursor; _historyHasMore = _historyCursor > 0; } MarkViewsDirty(); } private void UnloadHistory() { bool flag = false; for (int num = _entries.Count - 1; num >= 0; num--) { if (_entries[num].IsHistorical) { RemoveEntryFromStreamSelection(_entries[num]); _entries.RemoveAt(num); flag = true; } } _loadedHistoryEntries = 0; _historyCursor = _historyStartCursor; _historyHasMore = _historyCursor > 0; _historyStatus = (flag ? "Loaded server history removed." : "No loaded server history."); if (flag) { RebuildIssues(); MarkViewsDirty(); } } private void MarkViewsDirty() { _streamViewDirty = true; _issuesViewDirty = true; } private static bool IsIssueLevel(LogLevel level) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_0004: Invalid comparison between Unknown and I4 return (level & 7) > 0; } private static bool IsWarningLevel(LogLevel level) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_0009: Invalid comparison between Unknown and I4 return (level & 4) != 0 && (level & 3) == 0; } private void DrawWindow(int id) { //IL_0008: 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_0014: Unknown result type (might be due to invalid IL or missing references) //IL_012b: Unknown result type (might be due to invalid IL or missing references) EnsureStyles(); Color contentColor = GUI.contentColor; GUI.contentColor = _theme.TextColor; try { GUILayout.BeginVertical(Array.Empty()); DrawToolbar(); GUILayout.Space(2f); MainTab mainTab = (MainTab)GUILayout.Toolbar((int)_mainTab, new string[3] { "Stream", "Issues", "Help" }, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(330f) }); if (mainTab != _mainTab) { _mainTab = mainTab; if (_mainTab == MainTab.Stream && _followStream) { _scrollStreamToEnd = true; } } GUILayout.Space(3f); bool enabled = GUI.enabled; if (_mainTab != MainTab.Help) { GUI.enabled = enabled && _subscribed; } switch (_mainTab) { case MainTab.Stream: DrawStreamTab(); break; case MainTab.Issues: DrawIssuesTab(); break; default: DrawHelpTab(); break; } GUI.enabled = enabled; GUILayout.EndVertical(); } finally { GUI.contentColor = contentColor; } } private void DrawToolbar() { GUILayout.BeginHorizontal(Array.Empty()); string text = (string.IsNullOrEmpty(_serverVersion) ? "unknown" : _serverVersion); string text2 = (_subscribed ? "SUBSCRIBED" : (_subscriptionRequestPending ? "CONNECTING" : "OFF")); GUILayout.Label($"Server v{text} | {text2} | Entries: {_entries.Count} | Issues: {_issues.Count} | Last seq: {_lastLiveSequence} | Gaps: {_detectedGaps} | Server dropped: {_serverDroppedCount}", _labelStyle, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.ExpandWidth(true) }); bool enabled = GUI.enabled; GUI.enabled = enabled && IsAvailable && !_subscriptionRequestPending; if (GUILayout.Button(_subscribed ? "Unsubscribe" : "Subscribe", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(94f) })) { if (_subscribed) { Unsubscribe(); } else { Subscribe(); } } GUI.enabled = enabled && _entries.Count > 0 && !_historyRequestPending; if (GUILayout.Button("Clear local", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(82f) })) { ClearLocalView(); } GUI.enabled = enabled; if (GUILayout.Button("Close", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(62f) })) { IsWindowVisible = false; } GUILayout.EndHorizontal(); GUILayout.BeginHorizontal(Array.Empty()); Label(_availabilityStatus, GUILayout.ExpandWidth(true)); if (!_subscribed && IsAvailable) { Label("Press Subscribe to start a private admin stream.", GUILayout.Width(300f)); } GUILayout.EndHorizontal(); } private void DrawHelpTab() { //IL_0003: 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_0012: Unknown result type (might be due to invalid IL or missing references) _helpScroll = GUILayout.BeginScrollView(_helpScroll, Array.Empty()); HeaderLabel("Server Log Monitor requirements"); BodyLabel("The connected server must be a headless dedicated Valheim server running a compatible Valheim Profiler. The current player must be listed as a server administrator."); BodyLabel("Press Subscribe to create a private stream for the current authenticated connection. The server targets snapshots and live batches only to peers that explicitly subscribed. Unsubscribe or disconnect removes that peer from the subscriber list."); GUILayout.Space(6f); HeaderLabel("Current state"); BodyLabel(_availabilityStatus); if (!string.IsNullOrEmpty(_serverVersion)) { BodyLabel("Detected server mod version: " + _serverVersion); } GUILayout.Space(6f); HeaderLabel("Transport"); BodyLabel("Subscribe requests a bounded recent snapshot and then receives live entries in small batches. Every live event carries a monotonic sequence number. Server timestamps are transferred as UTC and displayed in the client local time zone for easier comparison with Client Log Monitor. A detected gap requests a fresh recent snapshot instead of silently presenting an incomplete stream."); BodyLabel("The server only runs the log backend on a headless instance. It does not initialize profiler discovery, IMGUI, input, cursor or pause systems."); GUILayout.Space(6f); HeaderLabel("Stream selection"); BodyLabel("Click selects one row, Ctrl-click toggles individual rows and Shift-click selects a continuous filtered range. Copy selected copies the chosen server log entries in chronological order with their full details and stack traces. Copy filtered remains available for the complete current filter result."); GUILayout.Space(6f); HeaderLabel("History"); BodyLabel("After the first snapshot, one server LogOutput.log page is loaded automatically and merged before the live stream, so normal startup logs are visible even though Valheim Profiler initialized later. Load older requests additional bounded pages on demand. A server restart or log-file replacement invalidates the history cursor."); GUILayout.Space(6f); HeaderLabel("Privacy and limits"); BodyLabel("Server logs can contain file paths, player names, network details and data written by third-party mods. Access is admin-only, requires an explicit subscription, is rate-limited and never transfers the whole file automatically."); BodyLabel("Clear local only removes the client-side view. It does not modify the server log file or the server's recent ring buffer."); GUILayout.EndScrollView(); } private void EnsureStyles() { //IL_003e: Unknown result type (might be due to invalid IL or missing references) //IL_0043: 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_0053: Unknown result type (might be due to invalid IL or missing references) //IL_0060: Expected O, but got Unknown //IL_0071: Unknown result type (might be due to invalid IL or missing references) //IL_0083: Unknown result type (might be due to invalid IL or missing references) //IL_0088: Unknown result type (might be due to invalid IL or missing references) //IL_0095: Expected O, but got Unknown //IL_00a6: Unknown result type (might be due to invalid IL or missing references) //IL_00b8: Unknown result type (might be due to invalid IL or missing references) //IL_00c2: Expected O, but got Unknown //IL_00c8: Unknown result type (might be due to invalid IL or missing references) //IL_00d3: Unknown result type (might be due to invalid IL or missing references) //IL_00dd: Unknown result type (might be due to invalid IL or missing references) //IL_00e2: Unknown result type (might be due to invalid IL or missing references) //IL_00e9: Unknown result type (might be due to invalid IL or missing references) //IL_00fb: Unknown result type (might be due to invalid IL or missing references) //IL_0100: Unknown result type (might be due to invalid IL or missing references) //IL_0108: Unknown result type (might be due to invalid IL or missing references) //IL_0110: Unknown result type (might be due to invalid IL or missing references) //IL_011d: Expected O, but got Unknown //IL_012e: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)_styleSkin == (Object)(object)GUI.skin) || _labelStyle == null) { _styleSkin = GUI.skin; _labelStyle = new GUIStyle(GUI.skin.label) { wordWrap = false, clipping = (TextClipping)1, alignment = (TextAnchor)3 }; _labelStyle.normal.textColor = _theme.TextColor; _headerLabelStyle = new GUIStyle(_labelStyle) { fontStyle = (FontStyle)1 }; _headerLabelStyle.normal.textColor = _theme.HeaderTextColor; _activeHeaderLabelStyle = new GUIStyle(_headerLabelStyle); Color color = Color.Lerp(_theme.HeaderTextColor, _theme.AccentColor, 0.5f); SetStyleTextColor(_activeHeaderLabelStyle, color); _detailsStyle = new GUIStyle(GUI.skin.label) { wordWrap = true, clipping = (TextClipping)1, alignment = (TextAnchor)0 }; _detailsStyle.normal.textColor = _theme.TextColor; } } private static void SetStyleTextColor(GUIStyle style, Color color) { //IL_0007: 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_0021: Unknown result type (might be due to invalid IL or missing references) //IL_002e: Unknown result type (might be due to invalid IL or missing references) //IL_003b: Unknown result type (might be due to invalid IL or missing references) //IL_0048: Unknown result type (might be due to invalid IL or missing references) //IL_0055: 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) style.normal.textColor = color; style.hover.textColor = color; style.active.textColor = color; style.focused.textColor = color; style.onNormal.textColor = color; style.onHover.textColor = color; style.onActive.textColor = color; style.onFocused.textColor = color; } private void Label(string text, params GUILayoutOption[] options) { GUILayout.Label(text ?? string.Empty, _labelStyle, options); } private void HeaderLabel(string text, params GUILayoutOption[] options) { GUILayout.Label(text ?? string.Empty, _headerLabelStyle, options); } private void BodyLabel(string text, params GUILayoutOption[] options) { GUILayout.Label(text ?? string.Empty, _detailsStyle, options); } } } namespace ValheimProfiler.Tools.PatchProfiler { internal sealed class PatchProfilerTool : IProfilerTool { private struct TimingState { public long StartTicks; public MethodBase ActiveTranspiledTarget; public int TranspilerCallStackDepth; } private sealed class PatchStat { private struct Bucket { public float Time; public double Ms; public int Calls; } private struct TimedSample { public int RealtimeSecond; public double Ms; public bool GcSample; } private struct HistogramEntry { public int Count; public double SumMs; } private sealed class AnalyticsBucket { public int RealtimeSecond = int.MinValue; public int SampleCount; public int GcSampleCount; public double SumMs; public double Top1Ms; public double Top2Ms; public double Top3Ms; public readonly Dictionary Histogram = new Dictionary(); public void Reset() { RealtimeSecond = int.MinValue; SampleCount = 0; GcSampleCount = 0; SumMs = 0.0; Top1Ms = 0.0; Top2Ms = 0.0; Top3Ms = 0.0; Histogram.Clear(); } public void AddTop(double value) { if (!(value <= 0.0)) { if (value > Top1Ms) { Top3Ms = Top2Ms; Top2Ms = Top1Ms; Top1Ms = value; } else if (value > Top2Ms) { Top3Ms = Top2Ms; Top2Ms = value; } else if (value > Top3Ms) { Top3Ms = value; } } } } private const float AvgWindowSec = 1f; private const int MaxWindowSeconds = 60; private const double HistogramMinMs = 0.0005; private const double HistogramBase = 1.15; private readonly object _sync = new object(); private int _frame = -1; private double _frameMs; private int _frameCalls; private readonly Queue _avgWindow = new Queue(); private double _avgWindowMs; private int _avgWindowCalls; private int _avgWindowFrames; private readonly Queue _pendingSamples = new Queue(); private readonly AnalyticsBucket[] _analyticsBuckets; private readonly SortedDictionary _totalHistogram = new SortedDictionary(); private long _totalSampleCount; private int _windowSampleCount; private int _gcWindowSampleCount; private bool _analyticsDirty; private MaxAnalyticsSnapshot _maxSnapshot = MaxAnalyticsSnapshot.Empty; public bool HasAnyAnalyticsData { get { lock (_sync) { return _windowSampleCount > 0 || _pendingSamples.Count > 0; } } } public PatchStat() { _analyticsBuckets = new AnalyticsBucket[60]; for (int i = 0; i < _analyticsBuckets.Length; i++) { _analyticsBuckets[i] = new AnalyticsBucket(); } } public bool Add(double ms, int frame, float now, bool gcSample) { lock (_sync) { SyncFrameInternal(frame, now); _frameMs += ms; _frameCalls++; _pendingSamples.Enqueue(new TimedSample { RealtimeSecond = Mathf.FloorToInt(now), Ms = ms, GcSample = gcSample }); _totalSampleCount++; _analyticsDirty = true; return _pendingSamples.Count == 1; } } public void ProcessBackgroundAnalytics(float now) { lock (_sync) { int num = Mathf.FloorToInt(now); bool flag = false; while (_pendingSamples.Count > 0) { TimedSample sample = _pendingSamples.Dequeue(); if (sample.RealtimeSecond >= num - 59) { AddSampleToAnalyticsWindow(sample); flag = true; } } if (ExpireOldAnalyticsBuckets(num)) { flag = true; } if (flag || _analyticsDirty) { RebuildMaxSnapshot(); _analyticsDirty = false; } } } public PatchStatSnapshot GetSnapshot(int frame, float now) { lock (_sync) { SyncFrameInternal(frame, now); return new PatchStatSnapshot { Avg1sMsPerFrame = ((_avgWindowFrames > 0) ? (_avgWindowMs / (double)_avgWindowFrames) : 0.0), Avg1sCallsPerFrame = ((_avgWindowFrames > 0) ? ((double)_avgWindowCalls / (double)_avgWindowFrames) : 0.0), Avg1sFrames = _avgWindowFrames, MaxSnapshot = _maxSnapshot }; } } public void Reset() { lock (_sync) { _frame = -1; _frameMs = 0.0; _frameCalls = 0; _avgWindow.Clear(); _avgWindowMs = 0.0; _avgWindowCalls = 0; _avgWindowFrames = 0; _pendingSamples.Clear(); _totalHistogram.Clear(); _totalSampleCount = 0L; _windowSampleCount = 0; _gcWindowSampleCount = 0; _analyticsDirty = false; _maxSnapshot = MaxAnalyticsSnapshot.Empty; for (int i = 0; i < _analyticsBuckets.Length; i++) { _analyticsBuckets[i].Reset(); } } } private void SyncFrameInternal(int frame, float now) { if (_frame == -1) { _frame = frame; } if (frame != _frame) { CommitFrame(now); _frame = frame; _frameMs = 0.0; _frameCalls = 0; } while (_avgWindow.Count > 0 && now - _avgWindow.Peek().Time > 1f) { Bucket bucket = _avgWindow.Dequeue(); _avgWindowMs -= bucket.Ms; _avgWindowCalls -= bucket.Calls; _avgWindowFrames--; } if (_avgWindowFrames < 0) { _avgWindowFrames = 0; } } private void CommitFrame(float now) { Bucket item = new Bucket { Time = now, Ms = _frameMs, Calls = _frameCalls }; _avgWindow.Enqueue(item); _avgWindowMs += item.Ms; _avgWindowCalls += item.Calls; _avgWindowFrames++; } private void AddSampleToAnalyticsWindow(TimedSample sample) { int num = Mod(sample.RealtimeSecond, 60); AnalyticsBucket analyticsBucket = _analyticsBuckets[num]; if (analyticsBucket.RealtimeSecond != sample.RealtimeSecond) { ReplaceAnalyticsBucket(analyticsBucket, sample.RealtimeSecond); } int key = MsToHistogramBin(sample.Ms); analyticsBucket.SampleCount++; analyticsBucket.SumMs += sample.Ms; analyticsBucket.AddTop(sample.Ms); if (sample.GcSample) { analyticsBucket.GcSampleCount++; } if (analyticsBucket.Histogram.TryGetValue(key, out var value)) { value.Count++; value.SumMs += sample.Ms; analyticsBucket.Histogram[key] = value; } else { analyticsBucket.Histogram[key] = new HistogramEntry { Count = 1, SumMs = sample.Ms }; } if (_totalHistogram.TryGetValue(key, out var value2)) { value2.Count++; value2.SumMs += sample.Ms; _totalHistogram[key] = value2; } else { _totalHistogram[key] = new HistogramEntry { Count = 1, SumMs = sample.Ms }; } _windowSampleCount++; if (sample.GcSample) { _gcWindowSampleCount++; } } private void ReplaceAnalyticsBucket(AnalyticsBucket bucket, int newSecond) { if (bucket.RealtimeSecond != int.MinValue && bucket.SampleCount > 0) { RemoveBucketFromTotals(bucket); } bucket.Reset(); bucket.RealtimeSecond = newSecond; } private void RemoveBucketFromTotals(AnalyticsBucket bucket) { _windowSampleCount -= bucket.SampleCount; if (_windowSampleCount < 0) { _windowSampleCount = 0; } _gcWindowSampleCount -= bucket.GcSampleCount; if (_gcWindowSampleCount < 0) { _gcWindowSampleCount = 0; } foreach (KeyValuePair item in bucket.Histogram) { if (_totalHistogram.TryGetValue(item.Key, out var value)) { value.Count -= item.Value.Count; value.SumMs -= item.Value.SumMs; if (value.Count <= 0) { _totalHistogram.Remove(item.Key); } else { _totalHistogram[item.Key] = value; } } } } private bool ExpireOldAnalyticsBuckets(int currentSecond) { bool result = false; for (int i = 0; i < _analyticsBuckets.Length; i++) { AnalyticsBucket analyticsBucket = _analyticsBuckets[i]; if (analyticsBucket.RealtimeSecond != int.MinValue && currentSecond - analyticsBucket.RealtimeSecond >= 60) { RemoveBucketFromTotals(analyticsBucket); analyticsBucket.Reset(); result = true; } } return result; } private void RebuildMaxSnapshot() { if (_windowSampleCount <= 0 || _totalHistogram.Count == 0) { _maxSnapshot = new MaxAnalyticsSnapshot { RawMaxMs = 0.0, SecondMaxMs = 0.0, ThirdMaxMs = 0.0, P95Ms = 0.0, P99Ms = 0.0, AboveP95Count = 0, AboveP99Count = 0, AvgAboveP95Ms = 0.0, AvgAboveP99Ms = 0.0, TotalSampleCount = _totalSampleCount, WindowSampleCount = 0, GcSampleCount = 0 }; return; } double top = 0.0; double top2 = 0.0; double top3 = 0.0; for (int i = 0; i < _analyticsBuckets.Length; i++) { AnalyticsBucket analyticsBucket = _analyticsBuckets[i]; AddTop(ref top, ref top2, ref top3, analyticsBucket.Top1Ms); AddTop(ref top, ref top2, ref top3, analyticsBucket.Top2Ms); AddTop(ref top, ref top2, ref top3, analyticsBucket.Top3Ms); } int num = Math.Max(1, Mathf.CeilToInt((float)_windowSampleCount * 0.95f)); int num2 = Math.Max(1, Mathf.CeilToInt((float)_windowSampleCount * 0.99f)); int num3 = 0; int num4 = 0; int num5 = 0; bool flag = false; bool flag2 = false; foreach (KeyValuePair item in _totalHistogram) { num3 += item.Value.Count; if (!flag && num3 >= num) { num4 = item.Key; flag = true; } if (!flag2 && num3 >= num2) { num5 = item.Key; flag2 = true; break; } } int num6 = 0; int num7 = 0; double num8 = 0.0; double num9 = 0.0; foreach (KeyValuePair item2 in _totalHistogram) { if (item2.Key > num4) { num6 += item2.Value.Count; num8 += item2.Value.SumMs; } if (item2.Key > num5) { num7 += item2.Value.Count; num9 += item2.Value.SumMs; } } _maxSnapshot = new MaxAnalyticsSnapshot { RawMaxMs = top, SecondMaxMs = top2, ThirdMaxMs = top3, P95Ms = HistogramBinToMs(num4), P99Ms = HistogramBinToMs(num5), AboveP95Count = num6, AboveP99Count = num7, AvgAboveP95Ms = ((num6 > 0) ? (num8 / (double)num6) : 0.0), AvgAboveP99Ms = ((num7 > 0) ? (num9 / (double)num7) : 0.0), TotalSampleCount = _totalSampleCount, WindowSampleCount = _windowSampleCount, GcSampleCount = _gcWindowSampleCount }; } private static void AddTop(ref double top1, ref double top2, ref double top3, double value) { if (!(value <= 0.0)) { if (value > top1) { top3 = top2; top2 = top1; top1 = value; } else if (value > top2) { top3 = top2; top2 = value; } else if (value > top3) { top3 = value; } } } private static int MsToHistogramBin(double ms) { if (ms <= 0.0005) { return 0; } double num = Math.Log(ms / 0.0005, 1.15); if (double.IsNaN(num) || double.IsInfinity(num) || num < 0.0) { return 0; } return (int)Math.Floor(num); } private static double HistogramBinToMs(int bin) { return (bin <= 0) ? 0.0005 : (0.0005 * Math.Pow(1.15, bin)); } private static int Mod(int x, int m) { int num = x % m; return (num < 0) ? (num + m) : num; } } private struct PatchStatSnapshot { public double Avg1sMsPerFrame; public double Avg1sCallsPerFrame; public int Avg1sFrames; public MaxAnalyticsSnapshot MaxSnapshot; } private struct MaxAnalyticsSnapshot { public double RawMaxMs; public double SecondMaxMs; public double ThirdMaxMs; public double P95Ms; public double P99Ms; public int AboveP95Count; public int AboveP99Count; public double AvgAboveP95Ms; public double AvgAboveP99Ms; public long TotalSampleCount; public int WindowSampleCount; public int GcSampleCount; public static readonly MaxAnalyticsSnapshot Empty = new MaxAnalyticsSnapshot { RawMaxMs = 0.0, SecondMaxMs = 0.0, ThirdMaxMs = 0.0, P95Ms = 0.0, P99Ms = 0.0, AboveP95Count = 0, AboveP99Count = 0, AvgAboveP95Ms = 0.0, AvgAboveP99Ms = 0.0, TotalSampleCount = 0L, WindowSampleCount = 0, GcSampleCount = 0 }; public static MaxAnalyticsSnapshot Aggregate(IEnumerable snapshots) { double top = 0.0; double top2 = 0.0; double top3 = 0.0; int num = 0; int num2 = 0; double num3 = 0.0; double num4 = 0.0; long num5 = 0L; int num6 = 0; int num7 = 0; double num8 = 0.0; double num9 = 0.0; foreach (MaxAnalyticsSnapshot snapshot in snapshots) { AddTop(ref top, ref top2, ref top3, snapshot.RawMaxMs); AddTop(ref top, ref top2, ref top3, snapshot.SecondMaxMs); AddTop(ref top, ref top2, ref top3, snapshot.ThirdMaxMs); if (snapshot.P95Ms > num8) { num8 = snapshot.P95Ms; } if (snapshot.P99Ms > num9) { num9 = snapshot.P99Ms; } num += snapshot.AboveP95Count; num2 += snapshot.AboveP99Count; num3 += snapshot.AvgAboveP95Ms * (double)snapshot.AboveP95Count; num4 += snapshot.AvgAboveP99Ms * (double)snapshot.AboveP99Count; num5 += snapshot.TotalSampleCount; num6 += snapshot.WindowSampleCount; num7 += snapshot.GcSampleCount; } return new MaxAnalyticsSnapshot { RawMaxMs = top, SecondMaxMs = top2, ThirdMaxMs = top3, P95Ms = num8, P99Ms = num9, AboveP95Count = num, AboveP99Count = num2, AvgAboveP95Ms = ((num > 0) ? (num3 / (double)num) : 0.0), AvgAboveP99Ms = ((num2 > 0) ? (num4 / (double)num2) : 0.0), TotalSampleCount = num5, WindowSampleCount = num6, GcSampleCount = num7 }; } private static void AddTop(ref double top1, ref double top2, ref double top3, double value) { if (!(value <= 0.0)) { if (value > top1) { top3 = top2; top2 = top1; top1 = value; } else if (value > top2) { top3 = top2; top2 = value; } else if (value > top3) { top3 = value; } } } } private struct GroupSummary { public double Avg1sMsPerFrame; public double Avg1sCallsPerFrame; public int Avg1sFrames; public MaxAnalyticsSnapshot Max; } private sealed class RuntimeProfileEntry { public readonly PatchContext Context; public readonly PatchStat Stat; public RuntimeProfileEntry(PatchContext context, PatchStat stat) { Context = context; Stat = stat; } public bool AcceptsActiveTranspiledTarget(MethodBase activeTarget) { return Context == null || Context.AcceptsActiveTranspiledTarget(activeTarget, _activeTranspiledTargetStack); } } private sealed class TranspilerDetail { public string ModGuid; public string ModName; public string OwnerHarmonyId; public string TranspilerMethodIdentity; public string TranspilerMethodDisplay; } private sealed class PatchContext { private readonly List _patchTypes = new List(); private readonly List _targets = new List(); private readonly HashSet _requiredActiveTranspiledTargets = new HashSet(); private readonly HashSet _injectedCallOrdinals = new HashSet(); public int EntryId; public MethodBase InstrumentedMethod; public string PatchType = "?"; public string TargetDisplay = "(unknown target)"; public string ModName = "Unknown"; public string ModGuid = "(unknown guid)"; public string OwnerHarmonyId = "(no owner)"; public bool IsTranspiledTargetEntry; public bool IsTranspilerCallEntry; public MethodBase TranspiledTargetMethod; public int InjectedCallSiteCount; public readonly HashSet RelatedModGuids = new HashSet(); public readonly List TranspilerDetails = new List(); public void AddPatchType(string patchType) { if (string.IsNullOrWhiteSpace(patchType)) { patchType = "?"; } if (!_patchTypes.Contains(patchType)) { _patchTypes.Add(patchType); } if (patchType == "Transpiled target") { IsTranspiledTargetEntry = true; } PatchType = string.Join("/", _patchTypes.ToArray()); } public void AddTarget(string target) { if (string.IsNullOrWhiteSpace(target)) { target = "(unknown target)"; } if (!_targets.Contains(target)) { _targets.Add(target); } if (_targets.Count == 1) { TargetDisplay = _targets[0]; return; } string arg = string.Join("; ", _targets.Take(3).ToArray()); string arg2 = ((_targets.Count > 3) ? "; …" : string.Empty); TargetDisplay = $"Multiple targets ({_targets.Count}): {arg}{arg2}"; } public void AddRelatedModGuid(string modGuid) { if (!string.IsNullOrWhiteSpace(modGuid)) { RelatedModGuids.Add(modGuid); } } public void AddTranspilerDetail(string modGuid, string modName, string ownerId, MethodBase transpiler) { string methodIdentity = GetMethodIdentity(transpiler); for (int i = 0; i < TranspilerDetails.Count; i++) { TranspilerDetail transpilerDetail = TranspilerDetails[i]; if (transpilerDetail.TranspilerMethodIdentity == methodIdentity && transpilerDetail.ModGuid == modGuid) { return; } } TranspilerDetails.Add(new TranspilerDetail { ModGuid = (string.IsNullOrWhiteSpace(modGuid) ? "(unknown guid)" : modGuid), ModName = (string.IsNullOrWhiteSpace(modName) ? "Unknown" : modName), OwnerHarmonyId = (string.IsNullOrWhiteSpace(ownerId) ? "(no owner)" : ownerId), TranspilerMethodIdentity = methodIdentity, TranspilerMethodDisplay = GetMethodDisplay(transpiler) }); } public void AddRequiredActiveTranspiledTarget(MethodBase target) { if (target != null) { _requiredActiveTranspiledTargets.Add(target); } } public void AddInjectedCallOrdinal(int ordinal) { if (ordinal >= 0) { _injectedCallOrdinals.Add(ordinal); } } public int[] GetInjectedCallOrdinals() { return _injectedCallOrdinals.OrderBy((int value) => value).ToArray(); } public void AddRequiredActiveTranspiledTargetsTo(HashSet targets) { if (targets == null || _requiredActiveTranspiledTargets.Count == 0) { return; } foreach (MethodBase requiredActiveTranspiledTarget in _requiredActiveTranspiledTargets) { if (requiredActiveTranspiledTarget != null) { targets.Add(requiredActiveTranspiledTarget); } } } public bool AcceptsActiveTranspiledTarget(MethodBase activeTarget, List activeTargetStack) { if (_requiredActiveTranspiledTargets.Count == 0) { return true; } if (activeTarget != null && _requiredActiveTranspiledTargets.Contains(activeTarget)) { return true; } if (activeTargetStack == null) { return false; } for (int num = activeTargetStack.Count - 1; num >= 0; num--) { MethodBase methodBase = activeTargetStack[num]; if (methodBase != null && _requiredActiveTranspiledTargets.Contains(methodBase)) { return true; } } return false; } } private sealed class FlatRowView { public int EntryId; public MethodBase InstrumentedMethod; public PatchStatSnapshot Snapshot; public PatchContext Context; } private sealed class ModGroupView { public string ModGuid; public string ModName; public List Rows; public double AggregateScore; public GroupSummary Summary; } private sealed class ModSelectionPolicy { private readonly string _path; private readonly Dictionary _overrides = new Dictionary(StringComparer.Ordinal); internal ModSelectionPolicy(string path) { _path = path; Reload(); } internal bool Resolve(string modGuid, bool defaultValue) { bool value; return (!string.IsNullOrEmpty(modGuid) && _overrides.TryGetValue(modGuid, out value)) ? value : defaultValue; } internal void Reload() { _overrides.Clear(); try { if (!File.Exists(_path)) { return; } string[] array = File.ReadAllLines(_path); for (int i = 0; i < array.Length; i++) { string text = array[i]?.Trim(); if (string.IsNullOrEmpty(text) || text.StartsWith("#", StringComparison.Ordinal)) { continue; } bool value; if (text.StartsWith("+", StringComparison.Ordinal)) { value = true; } else { if (!text.StartsWith("-", StringComparison.Ordinal)) { continue; } value = false; } string text2 = text.Substring(1).Trim(); if (!string.IsNullOrEmpty(text2)) { _overrides[text2] = value; } } } catch { } } internal void Save(IReadOnlyDictionary selection) { try { string directoryName = Path.GetDirectoryName(_path); if (!string.IsNullOrEmpty(directoryName)) { Directory.CreateDirectory(directoryName); } List list = new List { "# Valheim Profiler Patch Profiler mod selection overrides", "# + mod-guid = enabled, - mod-guid = disabled", "# Missing entries are enabled by default.", string.Empty }; foreach (KeyValuePair item in selection.OrderBy, string>((KeyValuePair pair) => pair.Key, StringComparer.Ordinal)) { if (!item.Value) { list.Add("- " + item.Key); } } File.WriteAllLines(_path, list); Reload(); } catch { } } } private enum MainTab { Profiler, ModsToProfile, Help } private enum ProfilerView { Avg1s, MaxOver60Sec } private enum TableSortColumn { Mod, AvgMsPerFrame, AvgCallsPerFrame, RawMax, SecondMax, ThirdMax, AboveP99, AvgAboveP99, P99, AboveP95, AvgAboveP95, P95, Samples, GcSamples, PatchType, Target, PatchMethod, CombinedMethod } private sealed class InjectedRuntimeCall { public MethodBase CalledMethod; public int AddedCallSites; public readonly HashSet FinalMethodOrdinals = new HashSet(); public readonly List SourcePatches = new List(); } private sealed class DirectCallSite { public MethodBase Method; public string Identity; public int MethodOrdinal; } private sealed class RuntimeTranspilerCallPlan { public int EntryId; public string CalledMethodIdentity; public HashSet MethodOrdinals; } private struct ActiveTranspilerCallTiming { public int EntryId; public PatchStat Stat; public long StartTicks; } internal const string ToolId = "PatchProfiler"; internal const string PluginGuid = "shudnal.ValheimProfiler.PatchProfiler"; internal const string PluginName = "Patch Profiler"; internal const string PluginVersion = "1.0.0"; private const string TranspiledTargetsGuid = "shudnal.ValheimProfiler.PatchProfiler.TranspiledTargets"; private const string TranspiledTargetsName = "Transpiled methods"; private const float MinModColumnWidth = 140f; private const float MaxModColumnWidth = 460f; private const float TimeColumnWidth = 82f; private const float CountColumnWidth = 82f; private const float AvgTimeColumnWidth = 104f; private const float AvgCountColumnWidth = 112f; private const float MethodColumnMinWidth = 260f; private const float TargetColumnMinWidth = 180f; private const float TargetColumnMaxWidth = 900f; private const float PatchMethodColumnMaxWidth = 1200f; private const float CombinedMethodColumnMaxWidth = 1800f; private const float WindowHorizontalPadding = 28f; private const float GroupToggleWidth = 14f; private const int MaxGroupedModNameCharsOnMax60 = 50; private const float RowHeightDefault = 20f; private const float RowHeightMax60 = 20f; private const float GroupRowHeight = 22f; private const float TotalRowHeight = 22f; private const float VirtualizationOverscan = 80f; private const int MaxDisplayedCount = 9999999; private const double MaxDisplayedMs = 999.999; private const double IsolatedRawMaxMinMs = 8.0; private const double IsolatedRawMaxMultiplier = 4.0; private const double GcCheckMinSampleMs = 1.0; private readonly ValheimProfilerApp _app; private readonly ManualLogSource _logger; private readonly WindowManager _windows; private readonly ThemeManager _theme; private readonly Harmony _harmony; private readonly ProfilerWindow _mainWindow; private readonly ProfilerWindow _detailsWindow; private readonly ModSelectionPolicy _modSelectionPolicy; private Vector2 _scroll; private Vector2 _modsScroll; private Vector2 _transpilerDetailsScroll; private PatchContext _selectedTranspilerDetailsContext; private string _selectedTranspilerDetailsTitle = string.Empty; private GUIStyle _labelStyle; private GUIStyle _headerLabelStyle; private GUIStyle _activeHeaderLabelStyle; private GUIStyle _groupLabelStyle; private GUIStyle _compactButtonStyle; private GUISkin _styleSkin; private string _status = "Closed"; private readonly object _lock = new object(); private bool _listReady; private bool _modsSelectionDirty; private volatile bool _profilingActive; private bool _groupByMod = true; private int _currentFrame; private int _currentRealtimeMs; private int _frozenFrame; private float _frozenRealtime; private volatile bool _statsFrozen; private int _lastObservedGc0; private int _lastObservedGc1; private int _lastObservedGc2; private float _drawModColumnWidth; private float _drawPatchTypeColumnWidth; private float _drawTargetColumnWidth; private float _drawPatchMethodColumnWidth; private float _drawMethodColumnWidth; private float _drawContentWidth; private float _drawTimeColumnWidth; private float _drawCountColumnWidth; private float _drawAvgTimeColumnWidth; private float _drawAvgCountColumnWidth; private bool _layoutMetricsDirty = true; private float _lastLayoutWindowWidth = -1f; private ProfilerView _lastLayoutProfilerView = (ProfilerView)(-1); private bool _lastLayoutGroupByMod; private GUISkin _lastLayoutSkin; private int _lastLayoutRowsSignature = int.MinValue; private Dictionary _assemblyToPluginName = new Dictionary(); private Dictionary _assemblyToPluginGuid = new Dictionary(); private Dictionary _pluginGuidToName = new Dictionary(); private int _nextProfileEntryId; private readonly Dictionary _stats = new Dictionary(256); private readonly Dictionary _context = new Dictionary(256); private readonly Dictionary _profileEntryIds = new Dictionary(256); private readonly Dictionary> _entriesByInstrumentedMethod = new Dictionary>(256); private Dictionary _runtimeEntriesByInstrumentedMethod = new Dictionary(0); private HashSet _runtimeTranspiledTargets = new HashSet(); private readonly HashSet _instrumented = new HashSet(); private readonly HashSet _transpiledTargets = new HashSet(); private readonly Dictionary _modExpanded = new Dictionary(); private readonly Dictionary _modsToProfile = new Dictionary(); private readonly Dictionary _modPatchCounts = new Dictionary(); private readonly Dictionary _modGuidToName = new Dictionary(); private ProfilerView _lastRenderedProfilerView = (ProfilerView)(-1); private bool _lastRenderedGroupByMod; private float _nextViewRefreshTime; private bool _viewDirty = true; private List _cachedFlatRows = new List(); private List _cachedGroupedRows = new List(); private GroupSummary _cachedTotalSummary; private readonly object _analyticsQueueLock = new object(); private readonly object _analyticsProcessingLock = new object(); private readonly Queue _dirtyAnalyticsQueue = new Queue(); private readonly HashSet _queuedAnalyticsStats = new HashSet(); private readonly HashSet _activeAnalyticsStats = new HashSet(); private AutoResetEvent _analyticsWakeEvent; private Thread _analyticsThread; private volatile bool _analyticsThreadRunning; private float _lastAnalyticsSweepRealtime; private static readonly double MsPerTick = 1000.0 / (double)Stopwatch.Frequency; private static int _mainThreadId; private static PatchProfilerTool _instance; [ThreadStatic] private static List _activeTranspiledTargetStack; private MainTab _mainTab = MainTab.Profiler; private ProfilerView _profilerView = ProfilerView.MaxOver60Sec; private TableSortColumn _avgSortColumn = TableSortColumn.AvgMsPerFrame; private TableSortColumn _maxSortColumn = TableSortColumn.ThirdMax; private Dictionary _runtimeTranspilerCallPlans = new Dictionary(0); private Dictionary _runtimeTranspilerCallStats = new Dictionary(0); private readonly object _transpilerCallWrapperLock = new object(); private readonly Dictionary _transpilerCallWrappers = new Dictionary(StringComparer.Ordinal); private static int _nextTranspilerCallWrapperId; [ThreadStatic] private static List _activeTranspilerCallTimings; internal bool IsWindowVisible { get { return _mainWindow.RequestedVisible; } set { _mainWindow.RequestedVisible = value; if (!value) { _detailsWindow.RequestedVisible = false; } } } internal bool IsProfilingActive => _profilingActive; string IProfilerTool.Id => "PatchProfiler"; string IProfilerTool.DisplayName => "Patch Profiler"; bool IProfilerTool.IsWindowVisible => IsWindowVisible; bool IProfilerTool.IsActive => IsProfilingActive; private float CurrentRealtime => (float)Volatile.Read(in _currentRealtimeMs) * 0.001f; private int DisplayFrame => _statsFrozen ? _frozenFrame : _currentFrame; private float DisplayRealtime => _statsFrozen ? _frozenRealtime : CurrentRealtime; private float CurrentRowHeight { get { float num = ((_profilerView == ProfilerView.MaxOver60Sec) ? 20f : 20f); GUIStyle labelStyle = _labelStyle; return Mathf.Max(num, ((labelStyle != null) ? labelStyle.lineHeight : 20f) + 4f); } } private float CurrentHeaderHeight { get { GUIStyle headerLabelStyle = _headerLabelStyle; return Mathf.Max(20f, ((headerLabelStyle != null) ? headerLabelStyle.lineHeight : 20f) + 4f); } } private float CurrentGroupRowHeight { get { GUIStyle groupLabelStyle = _groupLabelStyle; return Mathf.Max(22f, ((groupLabelStyle != null) ? groupLabelStyle.lineHeight : 22f) + 4f); } } private float CurrentTotalRowHeight { get { GUIStyle groupLabelStyle = _groupLabelStyle; return Mathf.Max(22f, ((groupLabelStyle != null) ? groupLabelStyle.lineHeight : 22f) + 4f); } } private float MainWindowWidth { get { //IL_0006: 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) Rect rect = _mainWindow.Rect; return ((Rect)(ref rect)).width; } } private float MainWindowHeight { get { //IL_0006: 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) Rect rect = _mainWindow.Rect; return ((Rect)(ref rect)).height; } } private TableSortColumn CurrentSortColumn => (_profilerView == ProfilerView.Avg1s) ? _avgSortColumn : _maxSortColumn; private void StartAnalyticsThread() { if (!_analyticsThreadRunning) { if (_analyticsWakeEvent == null) { _analyticsWakeEvent = new AutoResetEvent(initialState: false); } _analyticsThreadRunning = true; _analyticsThread = new Thread(AnalyticsThreadLoop) { IsBackground = true, Name = "PatchProfilerAnalytics" }; _analyticsThread.Start(); } } private void StopAnalyticsThread() { _analyticsThreadRunning = false; try { _analyticsWakeEvent?.Set(); } catch { } if (_analyticsThread != null && _analyticsThread.IsAlive) { try { _analyticsThread.Join(1000); } catch { } } _analyticsThread = null; try { _analyticsWakeEvent?.Dispose(); } catch { } _analyticsWakeEvent = null; } private void AnalyticsThreadLoop() { while (_analyticsThreadRunning) { try { _analyticsWakeEvent.WaitOne(200); lock (_analyticsProcessingLock) { if (_profilingActive) { ProcessQueuedAnalytics(CurrentRealtime, forceSweep: false); } } } catch { } } } private void ProcessQueuedAnalytics(float now, bool forceSweep) { List list; List list2; lock (_analyticsQueueLock) { list = new List(_dirtyAnalyticsQueue.Count); while (_dirtyAnalyticsQueue.Count > 0) { PatchStat item = _dirtyAnalyticsQueue.Dequeue(); _queuedAnalyticsStats.Remove(item); list.Add(item); } list2 = _activeAnalyticsStats.ToList(); } bool flag = forceSweep || now - _lastAnalyticsSweepRealtime >= 1f || now < _lastAnalyticsSweepRealtime; if (flag) { _lastAnalyticsSweepRealtime = now; } foreach (PatchStat item2 in list) { item2.ProcessBackgroundAnalytics(now); if (item2.HasAnyAnalyticsData) { lock (_analyticsQueueLock) { _activeAnalyticsStats.Add(item2); } } } if (!flag) { return; } foreach (PatchStat item3 in list2) { item3.ProcessBackgroundAnalytics(now); } lock (_analyticsQueueLock) { _activeAnalyticsStats.RemoveWhere((PatchStat stat) => !stat.HasAnyAnalyticsData); } } private void QueueAnalyticsWork(PatchStat stat) { lock (_analyticsQueueLock) { if (_queuedAnalyticsStats.Add(stat)) { _dirtyAnalyticsQueue.Enqueue(stat); } } try { _analyticsWakeEvent?.Set(); } catch { } } private void ClearAnalyticsQueues() { lock (_analyticsProcessingLock) { lock (_analyticsQueueLock) { _dirtyAnalyticsQueue.Clear(); _queuedAnalyticsStats.Clear(); _activeAnalyticsStats.Clear(); } } } private void FlushAnalyticsAt(float now) { lock (_analyticsProcessingLock) { ProcessQueuedAnalytics(now, forceSweep: true); } } private void MarkViewDirty() { _viewDirty = true; _layoutMetricsDirty = true; _nextViewRefreshTime = 0f; } private float GetRefreshInterval(ProfilerView view) { return (view == ProfilerView.Avg1s) ? 0.1f : 1f; } private void RefreshCachedViewIfNeeded() { bool flag = _lastRenderedProfilerView != _profilerView || _lastRenderedGroupByMod != _groupByMod; float displayRealtime = DisplayRealtime; float refreshInterval = GetRefreshInterval(_profilerView); if (!flag && !_viewDirty && displayRealtime < _nextViewRefreshTime) { return; } lock (_lock) { List list = BuildRowsLocked(DisplayFrame, DisplayRealtime); _cachedTotalSummary = BuildSummary(list); if (_groupByMod) { _cachedGroupedRows = BuildGroupedRowsFromRowsLocked(list); _cachedFlatRows.Clear(); } else { _cachedFlatRows = list.Take(500).ToList(); _cachedGroupedRows.Clear(); } } int num = CalculateLayoutRowsSignature(); if (_lastLayoutRowsSignature != num) { _lastLayoutRowsSignature = num; _layoutMetricsDirty = true; } _lastRenderedProfilerView = _profilerView; _lastRenderedGroupByMod = _groupByMod; _viewDirty = false; _nextViewRefreshTime = displayRealtime + refreshInterval; } private int CalculateLayoutRowsSignature() { int num = 17; int num2 = 0; if (_groupByMod) { foreach (ModGroupView cachedGroupedRow in _cachedGroupedRows) { foreach (FlatRowView row in cachedGroupedRow.Rows) { num ^= row.EntryId * 397; num2++; } } } else { foreach (FlatRowView cachedFlatRow in _cachedFlatRows) { num ^= cachedFlatRow.EntryId * 397; num2++; } } return num * 31 + num2; } private void RefreshPatchList() { try { StopProfilingInternal(); _detailsWindow.RequestedVisible = false; _selectedTranspilerDetailsContext = null; _selectedTranspilerDetailsTitle = string.Empty; _status = "Refreshing patch list…"; BuildAssemblyPluginMap(); int num = 0; int profileEntriesFound = 0; Dictionary dictionary; lock (_lock) { dictionary = _modsToProfile.ToDictionary((KeyValuePair kv) => kv.Key, (KeyValuePair kv) => kv.Value); } if (dictionary.Count == 0) { _modSelectionPolicy.Reload(); } lock (_lock) { _instrumented.Clear(); _transpiledTargets.Clear(); _stats.Clear(); _context.Clear(); _profileEntryIds.Clear(); _entriesByInstrumentedMethod.Clear(); _runtimeEntriesByInstrumentedMethod = new Dictionary(0); _runtimeTranspiledTargets = new HashSet(); _runtimeTranspilerCallPlans = new Dictionary(0); _runtimeTranspilerCallStats = new Dictionary(0); lock (_transpilerCallWrapperLock) { _transpilerCallWrappers.Clear(); } _nextProfileEntryId = 0; _modExpanded.Clear(); _modsToProfile.Clear(); _modPatchCounts.Clear(); _modGuidToName.Clear(); _statsFrozen = false; } ClearAnalyticsQueues(); foreach (MethodBase allPatchedMethod in Harmony.GetAllPatchedMethods()) { num++; Patches patchInfo = Harmony.GetPatchInfo(allPatchedMethod); if (patchInfo == null) { continue; } foreach (Patch prefix in patchInfo.Prefixes) { RegisterPatchMethod(prefix, "Prefix", allPatchedMethod, ref profileEntriesFound, dictionary); } foreach (Patch postfix in patchInfo.Postfixes) { RegisterPatchMethod(postfix, "Postfix", allPatchedMethod, ref profileEntriesFound, dictionary); } foreach (Patch finalizer in patchInfo.Finalizers) { RegisterPatchMethod(finalizer, "Finalizer", allPatchedMethod, ref profileEntriesFound, dictionary); } if (patchInfo.Transpilers != null && patchInfo.Transpilers.Any()) { RegisterTranspilers(patchInfo.Transpilers, allPatchedMethod, ref profileEntriesFound, dictionary); } } int num2; int num3; lock (_lock) { num2 = _context.Values.Count((PatchContext ctx) => ctx.IsTranspiledTargetEntry); num3 = _context.Values.Count((PatchContext ctx) => (ctx.PatchType ?? string.Empty).Contains("Transpiler call")); } _listReady = true; _modsSelectionDirty = false; _profilingActive = false; _status = $"Patch list ready. Targets: {num}. Profile entries: {profileEntriesFound}. Transpiled targets: {num2}. Transpiler calls: {num3}."; MarkViewDirty(); } catch (Exception ex) { _status = "Refresh error: " + ex.GetType().Name + ": " + ex.Message; _logger.LogError((object)ex); } } private void RegisterPatchMethod(Patch p, string patchType, MethodBase target, ref int profileEntriesFound, Dictionary oldSelection) { MethodInfo methodInfo = ((p != null) ? p.PatchMethod : null); if (methodInfo == null || methodInfo.DeclaringType?.Assembly == typeof(ValheimProfilerPlugin).Assembly) { return; } string ownerId = (string.IsNullOrWhiteSpace(p.owner) ? "(no owner)" : p.owner); string modGuid = GuessModGuid(methodInfo, ownerId); string modName = GuessModName(methodInfo, modGuid); string methodDisplay = GetMethodDisplay(target); string text = "patch|" + GetMethodIdentity(methodInfo); lock (_lock) { bool flag = !_profileEntryIds.ContainsKey(text); int orCreateProfileEntryLocked = GetOrCreateProfileEntryLocked(text, methodInfo, () => new PatchContext { ModName = modName, ModGuid = modGuid, OwnerHarmonyId = ownerId }); RegisterModLocked(modGuid, modName, oldSelection); _modPatchCounts[modGuid]++; PatchContext patchContext = _context[orCreateProfileEntryLocked]; patchContext.AddPatchType(patchType); patchContext.AddTarget(methodDisplay); if (flag) { profileEntriesFound++; } } } private void RegisterTranspilers(IReadOnlyList transpilers, MethodBase target, ref int profileEntriesFound, Dictionary oldSelection) { if (target == null || transpilers == null || transpilers.Count == 0) { return; } List list = transpilers.Where((Patch patch) => ((patch != null) ? patch.PatchMethod : null) != null && patch.PatchMethod.DeclaringType?.Assembly != typeof(ValheimProfilerPlugin).Assembly).ToList(); if (list.Count == 0) { return; } foreach (Patch item in list) { MethodBase patchMethod = item.PatchMethod; string ownerId = (string.IsNullOrWhiteSpace(item.owner) ? "(no owner)" : item.owner); string text = GuessModGuid(patchMethod, ownerId); string text2 = GuessModName(patchMethod, text); lock (_lock) { RegisterModLocked(text, text2, oldSelection); } RegisterTranspiledTarget(target, patchMethod, text, text2, ownerId, oldSelection, ref profileEntriesFound); } foreach (InjectedRuntimeCall item2 in FindInjectedRuntimeCalls(target, list)) { RegisterTranspilerRuntimeCall(item2, target, oldSelection, ref profileEntriesFound); } } private void RegisterTranspiledTarget(MethodBase target, MethodBase transpiler, string sourceModGuid, string sourceModName, string ownerId, Dictionary oldSelection, ref int profileEntriesFound) { if (target == null) { return; } string text = "transpiled-target|" + GetMethodIdentity(target); string methodDisplay = GetMethodDisplay(target); lock (_lock) { bool flag = !_profileEntryIds.ContainsKey(text); int orCreateProfileEntryLocked = GetOrCreateProfileEntryLocked(text, target, () => new PatchContext { ModName = "Transpiled methods", ModGuid = "shudnal.ValheimProfiler.PatchProfiler.TranspiledTargets", OwnerHarmonyId = "(multiple transpilers)" }); _transpiledTargets.Add(target); RegisterModLocked("shudnal.ValheimProfiler.PatchProfiler.TranspiledTargets", "Transpiled methods", oldSelection); if (flag) { _modPatchCounts["shudnal.ValheimProfiler.PatchProfiler.TranspiledTargets"]++; profileEntriesFound++; } PatchContext patchContext = _context[orCreateProfileEntryLocked]; patchContext.AddPatchType("Transpiled target"); patchContext.AddTarget(methodDisplay); patchContext.AddRelatedModGuid(sourceModGuid); patchContext.AddTranspilerDetail(sourceModGuid, sourceModName, ownerId, transpiler); } } private void RegisterTranspilerRuntimeCall(InjectedRuntimeCall call, MethodBase target, Dictionary oldSelection, ref int profileEntriesFound) { MethodBase methodBase = call?.CalledMethod; if (methodBase == null || target == null) { return; } List<(MethodBase, string, string, string)> list = new List<(MethodBase, string, string, string)>(); foreach (Patch sourcePatch in call.SourcePatches) { MethodBase methodBase2 = ((sourcePatch != null) ? sourcePatch.PatchMethod : null); if (!(methodBase2 == null)) { string text = (string.IsNullOrWhiteSpace(sourcePatch.owner) ? "(no owner)" : sourcePatch.owner); string text2 = GuessModGuid(methodBase2, text); string item = GuessModName(methodBase2, text2); list.Add((methodBase2, text2, item, text)); } } List list2 = (from detail in list select detail.ModGuid into value where !string.IsNullOrWhiteSpace(value) select value).Distinct(StringComparer.Ordinal).ToList(); string modGuid; string modName; string ownerHarmonyId; if (list2.Count == 1) { modGuid = list2[0]; modName = list.First<(MethodBase, string, string, string)>(((MethodBase Method, string ModGuid, string ModName, string OwnerId) detail) => detail.ModGuid == modGuid).Item3; ownerHarmonyId = ((list.Count == 1) ? list[0].Item4 : "(multiple transpilers)"); } else { modGuid = "shudnal.ValheimProfiler.PatchProfiler.TranspiledTargets"; modName = "Transpiled methods"; ownerHarmonyId = "(multiple transpilers)"; } string text3 = "transpiler-call|" + GetMethodIdentity(target) + "|" + modGuid + "|" + GetMethodIdentity(methodBase); lock (_lock) { bool flag = !_profileEntryIds.ContainsKey(text3); int orCreateProfileEntryLocked = GetOrCreateProfileEntryLocked(text3, methodBase, () => new PatchContext { ModName = modName, ModGuid = modGuid, OwnerHarmonyId = ownerHarmonyId }); RegisterModLocked(modGuid, modName, oldSelection); if (flag) { _modPatchCounts[modGuid]++; profileEntriesFound++; } PatchContext patchContext = _context[orCreateProfileEntryLocked]; patchContext.IsTranspilerCallEntry = true; patchContext.TranspiledTargetMethod = target; patchContext.InjectedCallSiteCount = Math.Max(patchContext.InjectedCallSiteCount, call.AddedCallSites); patchContext.AddPatchType("Transpiler call"); patchContext.AddTarget(GetMethodDisplay(target)); patchContext.AddRequiredActiveTranspiledTarget(target); foreach (int finalMethodOrdinal in call.FinalMethodOrdinals) { patchContext.AddInjectedCallOrdinal(finalMethodOrdinal); } foreach (var item2 in list) { patchContext.AddRelatedModGuid(item2.Item2); patchContext.AddTranspilerDetail(item2.Item2, item2.Item3, item2.Item4, item2.Item1); } } } private int GetOrCreateProfileEntryLocked(string entryKey, MethodBase instrumentedMethod, Func createContext) { if (_profileEntryIds.TryGetValue(entryKey, out var value)) { return value; } int num = _nextProfileEntryId++; _profileEntryIds[entryKey] = num; _stats[num] = new PatchStat(); PatchContext patchContext = ((createContext != null) ? createContext() : new PatchContext()); patchContext.EntryId = num; patchContext.InstrumentedMethod = instrumentedMethod; _context[num] = patchContext; if (!_entriesByInstrumentedMethod.TryGetValue(instrumentedMethod, out var value2)) { value2 = new List(1); _entriesByInstrumentedMethod[instrumentedMethod] = value2; } value2.Add(num); return num; } private void RegisterModLocked(string modGuid, string modName, Dictionary oldSelection) { if (string.IsNullOrWhiteSpace(modGuid)) { modGuid = "(unknown guid)"; } if (string.IsNullOrWhiteSpace(modName)) { modName = "Unknown"; } if (!_modExpanded.ContainsKey(modGuid)) { _modExpanded[modGuid] = false; } if (!_modsToProfile.ContainsKey(modGuid)) { _modsToProfile[modGuid] = ((oldSelection != null && oldSelection.TryGetValue(modGuid, out var value)) ? value : _modSelectionPolicy.Resolve(modGuid, defaultValue: true)); } if (!_modPatchCounts.ContainsKey(modGuid)) { _modPatchCounts[modGuid] = 0; } if (!_modGuidToName.ContainsKey(modGuid)) { _modGuidToName[modGuid] = modName; } } private void StartProfiling() { //IL_014c: Unknown result type (might be due to invalid IL or missing references) //IL_0151: Unknown result type (might be due to invalid IL or missing references) //IL_015a: Expected O, but got Unknown //IL_0170: Unknown result type (might be due to invalid IL or missing references) //IL_0175: Unknown result type (might be due to invalid IL or missing references) //IL_0182: Expected O, but got Unknown //IL_022e: Unknown result type (might be due to invalid IL or missing references) //IL_0233: Unknown result type (might be due to invalid IL or missing references) //IL_023a: Unknown result type (might be due to invalid IL or missing references) //IL_0244: Expected O, but got Unknown try { RefreshPatchList(); if (!_listReady) { _status = "Patch list is empty."; return; } if (_profilingActive) { _status = "Profiling is already active."; return; } SaveModSelection(); _statsFrozen = false; int num = 0; int num2 = 0; int num3 = 0; List list; lock (_lock) { RebuildRuntimeProfileMapLocked(); list = (from method in _runtimeEntriesByInstrumentedMethod.Keys.Concat(_runtimeTranspiledTargets) where method != null select method).Distinct().ToList(); } foreach (MethodBase item in list) { lock (_lock) { if (_instrumented.Contains(item)) { continue; } _instrumented.Add(item); goto IL_0135; } IL_0135: try { HarmonyMethod val = new HarmonyMethod(AccessTools.Method(typeof(PatchProfilerTool), "TimingPrefix", (Type[])null, (Type[])null)) { priority = 0 }; HarmonyMethod val2 = new HarmonyMethod(AccessTools.Method(typeof(PatchProfilerTool), "TimingFinalizer", (Type[])null, (Type[])null)) { priority = 800 }; HarmonyMethod val3 = null; if (_runtimeTranspilerCallPlans.ContainsKey(item)) { string[] after = (from patch in Harmony.GetPatchInfo(item)?.Transpilers?.Where((Patch patch) => patch != null && !string.IsNullOrWhiteSpace(patch.owner) && !string.Equals(patch.owner, "shudnal.ValheimProfiler.PatchProfiler", StringComparison.Ordinal)) select patch.owner).Distinct(StringComparer.Ordinal).ToArray() ?? Array.Empty(); val3 = new HarmonyMethod(AccessTools.Method(typeof(PatchProfilerTool), "InjectedCallTimingTranspiler", (Type[])null, (Type[])null)) { priority = 0, after = after }; } _harmony.Patch(item, val, (HarmonyMethod)null, (HarmonyMethod)null, val2, (HarmonyMethod)null); num++; if (val3 != null) { try { _harmony.Patch(item, (HarmonyMethod)null, (HarmonyMethod)null, val3, (HarmonyMethod)null, (HarmonyMethod)null); } catch (Exception ex) { num3++; _logger.LogWarning((object)("Could not instrument exact transpiler call sites in " + GetMethodDisplay(item) + ": " + ex.GetType().Name + ": " + ex.Message)); } } } catch (Exception ex2) { num2++; lock (_lock) { _instrumented.Remove(item); } _logger.LogWarning((object)("Could not instrument " + GetMethodDisplay(item) + ": " + ex2.GetType().Name + ": " + ex2.Message)); } } _profilingActive = true; _status = $"Profiling enabled. Instrumented: {num}."; if (num2 > 0) { _status += $" Failed: {num2}."; } if (num3 > 0) { _status += $" Transpiler call-site failures: {num3}."; } MarkViewDirty(); } catch (Exception ex3) { _status = "Start error: " + ex3.GetType().Name + ": " + ex3.Message; _logger.LogError((object)ex3); } } private void StopProfiling() { try { StopProfilingInternal(freezeData: true); _status = "Profiling paused. Data frozen."; MarkViewDirty(); } catch (Exception ex) { _status = "Pause error: " + ex.GetType().Name + ": " + ex.Message; _logger.LogError((object)ex); } } private void StopProfilingInternal(bool freezeData = false) { bool profilingActive = _profilingActive; _profilingActive = false; _harmony.UnpatchSelf(); lock (_lock) { _instrumented.Clear(); _runtimeEntriesByInstrumentedMethod = new Dictionary(0); _runtimeTranspiledTargets = new HashSet(); _runtimeTranspilerCallPlans = new Dictionary(0); _runtimeTranspilerCallStats = new Dictionary(0); } if (profilingActive && freezeData) { float realtimeSinceStartup = Time.realtimeSinceStartup; int frameCount = Time.frameCount; SetCurrentRealtime(realtimeSinceStartup); FlushAnalyticsAt(realtimeSinceStartup); _frozenRealtime = realtimeSinceStartup; _frozenFrame = frameCount; _statsFrozen = true; } } private void ResetProfilingSelection() { if (!_listReady) { _status = "Patch list is empty. Load patch list first."; return; } SaveModSelection(); bool profilingActive = _profilingActive; StopProfilingInternal(); if (profilingActive) { StartProfiling(); return; } _status = "Profiling selection updated. Start profiling to apply."; MarkViewDirty(); } private static void TimingPrefix(MethodBase __originalMethod, ref TimingState __state) { __state.StartTicks = Stopwatch.GetTimestamp(); __state.ActiveTranspiledTarget = null; __state.TranspilerCallStackDepth = -1; try { if (Thread.CurrentThread.ManagedThreadId == _mainThreadId) { PatchProfilerTool instance = _instance; if (instance != null && instance.IsTranspiledTarget(__originalMethod)) { __state.TranspilerCallStackDepth = CurrentInjectedTranspilerCallDepth(); PushActiveTranspiledTarget(__originalMethod); __state.ActiveTranspiledTarget = __originalMethod; } } } catch { } } private static Exception TimingFinalizer(Exception __exception, TimingState __state, MethodBase __originalMethod) { try { if (__state.StartTicks <= 0) { return __exception; } if (Thread.CurrentThread.ManagedThreadId != _mainThreadId) { return __exception; } PatchProfilerTool instance = _instance; if (instance == null) { return __exception; } long timestamp = Stopwatch.GetTimestamp(); long num = timestamp - __state.StartTicks; if (num < 0) { return __exception; } double num2 = (double)num * MsPerTick; bool gcSample = false; if (num2 >= 1.0) { gcSample = GC.CollectionCount(0) != instance._lastObservedGc0 || GC.CollectionCount(1) != instance._lastObservedGc1 || GC.CollectionCount(2) != instance._lastObservedGc2; } int frameCount = Time.frameCount; float realtimeSinceStartup = Time.realtimeSinceStartup; MethodBase activeTranspiledTarget = CurrentActiveTranspiledTarget(); instance.SetCurrentRealtime(realtimeSinceStartup); instance.Record(__originalMethod, num2, frameCount, realtimeSinceStartup, gcSample, activeTranspiledTarget); } catch { } finally { if (__state.ActiveTranspiledTarget != null) { PopActiveTranspiledTarget(__state.ActiveTranspiledTarget); } TrimInjectedTranspilerCallStack(__state.TranspilerCallStackDepth); } return __exception; } private void Record(MethodBase instrumentedMethod, double elapsedMs, int frame, float now, bool gcSample, MethodBase activeTranspiledTarget) { if (!_profilingActive) { return; } Dictionary runtimeEntriesByInstrumentedMethod = _runtimeEntriesByInstrumentedMethod; if (runtimeEntriesByInstrumentedMethod == null || !runtimeEntriesByInstrumentedMethod.TryGetValue(instrumentedMethod, out var value) || value == null || value.Length == 0) { return; } foreach (RuntimeProfileEntry runtimeProfileEntry in value) { if (runtimeProfileEntry.AcceptsActiveTranspiledTarget(activeTranspiledTarget)) { PatchStat stat = runtimeProfileEntry.Stat; if (stat != null && stat.Add(elapsedMs, frame, now, gcSample)) { QueueAnalyticsWork(stat); } } } } private void RebuildRuntimeProfileMapLocked() { Dictionary> dictionary = new Dictionary>(Math.Max(16, _entriesByInstrumentedMethod.Count)); HashSet hashSet = new HashSet(); Dictionary> dictionary2 = new Dictionary>(); Dictionary dictionary3 = new Dictionary(); foreach (KeyValuePair item in _context) { PatchContext value = item.Value; if (!IsContextEnabledLocked(value) || !_stats.TryGetValue(item.Key, out var value2)) { continue; } if (value.IsTranspilerCallEntry) { MethodBase transpiledTargetMethod = value.TranspiledTargetMethod; MethodBase instrumentedMethod = value.InstrumentedMethod; int[] injectedCallOrdinals = value.GetInjectedCallOrdinals(); if (transpiledTargetMethod != null && instrumentedMethod != null && injectedCallOrdinals.Length != 0 && IsCallSiteTimingSafe(instrumentedMethod)) { if (!dictionary2.TryGetValue(transpiledTargetMethod, out var value3)) { value3 = (dictionary2[transpiledTargetMethod] = new List()); } value3.Add(new RuntimeTranspilerCallPlan { EntryId = item.Key, CalledMethodIdentity = GetMethodIdentity(instrumentedMethod), MethodOrdinals = new HashSet(injectedCallOrdinals) }); dictionary3[item.Key] = value2; hashSet.Add(transpiledTargetMethod); continue; } } MethodBase instrumentedMethod2 = value.InstrumentedMethod; if (!(instrumentedMethod2 == null)) { if (!dictionary.TryGetValue(instrumentedMethod2, out var value4)) { value4 = (dictionary[instrumentedMethod2] = new List(1)); } value4.Add(new RuntimeProfileEntry(value, value2)); if (value.IsTranspiledTargetEntry) { hashSet.Add(instrumentedMethod2); } value.AddRequiredActiveTranspiledTargetsTo(hashSet); } } _runtimeEntriesByInstrumentedMethod = dictionary.ToDictionary((KeyValuePair> kv) => kv.Key, (KeyValuePair> kv) => kv.Value.ToArray()); _runtimeTranspiledTargets = hashSet; _runtimeTranspilerCallPlans = dictionary2.ToDictionary((KeyValuePair> kv) => kv.Key, (KeyValuePair> kv) => kv.Value.ToArray()); _runtimeTranspilerCallStats = dictionary3; } private bool IsTranspiledTarget(MethodBase method) { if (method == null) { return false; } return _runtimeTranspiledTargets?.Contains(method) ?? false; } private bool IsContextEnabledLocked(PatchContext ctx) { if (ctx == null) { return false; } if (!_modsToProfile.TryGetValue(ctx.ModGuid, out var value) || !value) { return false; } if (ctx.ModGuid != "shudnal.ValheimProfiler.PatchProfiler.TranspiledTargets") { return true; } if (ctx.RelatedModGuids.Count == 0) { return true; } foreach (string relatedModGuid in ctx.RelatedModGuids) { if (_modsToProfile.TryGetValue(relatedModGuid, out var value2) && value2) { return true; } } return false; } private static void PushActiveTranspiledTarget(MethodBase method) { if (_activeTranspiledTargetStack == null) { _activeTranspiledTargetStack = new List(4); } _activeTranspiledTargetStack.Add(method); } private static MethodBase CurrentActiveTranspiledTarget() { List activeTranspiledTargetStack = _activeTranspiledTargetStack; if (activeTranspiledTargetStack == null || activeTranspiledTargetStack.Count == 0) { return null; } return activeTranspiledTargetStack[activeTranspiledTargetStack.Count - 1]; } private static void PopActiveTranspiledTarget(MethodBase method) { List activeTranspiledTargetStack = _activeTranspiledTargetStack; if (activeTranspiledTargetStack == null || activeTranspiledTargetStack.Count == 0) { return; } int num = activeTranspiledTargetStack.Count - 1; if ((object)activeTranspiledTargetStack[num] == method || object.Equals(activeTranspiledTargetStack[num], method)) { activeTranspiledTargetStack.RemoveAt(num); return; } for (int num2 = num; num2 >= 0; num2--) { if ((object)activeTranspiledTargetStack[num2] == method || object.Equals(activeTranspiledTargetStack[num2], method)) { activeTranspiledTargetStack.RemoveAt(num2); break; } } } private void PrepareProfilerLayoutWidths() { if (!_layoutMetricsDirty && Mathf.Abs(_lastLayoutWindowWidth - MainWindowWidth) < 0.1f && _lastLayoutProfilerView == _profilerView && _lastLayoutGroupByMod == _groupByMod && (Object)(object)_lastLayoutSkin == (Object)(object)GUI.skin) { return; } _drawTimeColumnWidth = CalculateHeaderAwareColumnWidth(82f, "raw max", "2nd max", "3rd max", "avg >p99", "p99", "avg >p95", "p95", "000.000!"); _drawCountColumnWidth = CalculateHeaderAwareColumnWidth(82f, "> p99", "> p95", "samples", "gc samples", "9999999+"); _drawAvgTimeColumnWidth = CalculateHeaderAwareColumnWidth(104f, "ms per frame", "000.000"); _drawAvgCountColumnWidth = CalculateHeaderAwareColumnWidth(112f, "calls per frame", "9999999+"); List layoutRows = GetLayoutRows(); _drawModColumnWidth = CalculateModColumnWidth(layoutRows); _drawPatchTypeColumnWidth = CalculatePatchTypeColumnWidth(); float num = CalculateFixedProfilerColumnsWidth(_drawModColumnWidth, _drawPatchTypeColumnWidth); float num2 = Mathf.Max(300f, MainWindowWidth - 28f); float num3 = Mathf.Max(260f, num2 - num); if (_profilerView == ProfilerView.Avg1s) { _drawTargetColumnWidth = CalculateTargetColumnWidth(layoutRows); _drawPatchMethodColumnWidth = CalculatePatchMethodColumnWidth(layoutRows); float num4 = _drawTargetColumnWidth + _drawPatchMethodColumnWidth; if (num4 < num3) { _drawPatchMethodColumnWidth += num3 - num4; } _drawMethodColumnWidth = _drawTargetColumnWidth + _drawPatchMethodColumnWidth; } else { _drawTargetColumnWidth = 0f; _drawPatchMethodColumnWidth = 0f; _drawMethodColumnWidth = Mathf.Max(num3, CalculateCombinedMethodColumnWidth(layoutRows)); } _drawContentWidth = num + _drawMethodColumnWidth; _lastLayoutWindowWidth = MainWindowWidth; _lastLayoutProfilerView = _profilerView; _lastLayoutGroupByMod = _groupByMod; _lastLayoutSkin = GUI.skin; _layoutMetricsDirty = false; } private float CalculateHeaderAwareColumnWidth(float nominalWidth, params string[] texts) { //IL_002b: Unknown result type (might be due to invalid IL or missing references) //IL_0035: Expected O, but got Unknown //IL_0030: Unknown result type (might be due to invalid IL or missing references) if (_headerLabelStyle == null) { return nominalWidth; } float num = nominalWidth; foreach (string text in texts) { num = Mathf.Max(num, _headerLabelStyle.CalcSize(new GUIContent(text)).x + 6f); } return Mathf.Ceil(num); } private void DrawHeader() { //IL_0029: Unknown result type (might be due to invalid IL or missing references) //IL_002e: 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) Rect rect = GUILayoutUtility.GetRect(1f, CurrentHeaderHeight, (GUILayoutOption[])(object)new GUILayoutOption[2] { GUILayout.ExpandWidth(true), GUILayout.Height(CurrentHeaderHeight) }); GUI.BeginGroup(rect); try { float x = 0f - _scroll.x; float y = 0f; float height = ((Rect)(ref rect)).height; DrawSortableHeaderCell(ref x, y, _drawModColumnWidth, height, _groupByMod ? "Mod" : "BepInEx mod", "BepInEx mod associated with the patch entry. Transpiled target rows are grouped under Transpiled methods.\nClick to sort ascending by this text column.", TableSortColumn.Mod); if (_profilerView == ProfilerView.Avg1s) { DrawSortableHeaderCell(ref x, y, _drawAvgTimeColumnWidth, height, "ms per frame", "Average measured patch execution time contributed per rendered frame in the rolling one-second window.\nClick to sort descending by this column.", TableSortColumn.AvgMsPerFrame); DrawSortableHeaderCell(ref x, y, _drawAvgCountColumnWidth, height, "calls per frame", "Average number of measured patch invocations per rendered frame in the rolling one-second window.\nClick to sort descending by this column.", TableSortColumn.AvgCallsPerFrame); } else { DrawSortableHeaderCell(ref x, y, _drawTimeColumnWidth, height, "raw max", "Slowest individual measured call in the rolling 60-second window.\nA trailing ! marks an isolated spike or a high GC-associated sample.\nClick to sort descending by this column.", TableSortColumn.RawMax); DrawSortableHeaderCell(ref x, y, _drawTimeColumnWidth, height, "2nd max", "Second-slowest individual measured call in the rolling 60-second window.\nClick to sort descending by this column.", TableSortColumn.SecondMax); DrawSortableHeaderCell(ref x, y, _drawTimeColumnWidth, height, "3rd max", "Third-slowest individual measured call in the rolling 60-second window.\nClick to sort descending by this column.", TableSortColumn.ThirdMax); DrawSortableHeaderCell(ref x, y, _drawCountColumnWidth, height, "> p99", "Number of samples above the approximate p99 histogram boundary.\nClick to sort descending by this column.", TableSortColumn.AboveP99); DrawSortableHeaderCell(ref x, y, _drawTimeColumnWidth, height, "avg >p99", "Average execution time of samples above the approximate p99 boundary.\nClick to sort descending by this column.", TableSortColumn.AvgAboveP99); DrawSortableHeaderCell(ref x, y, _drawTimeColumnWidth, height, "p99", "Approximate 99th percentile: about 99% of samples completed at or below this duration.\nClick to sort descending by this column.", TableSortColumn.P99); DrawSortableHeaderCell(ref x, y, _drawCountColumnWidth, height, "> p95", "Number of samples above the approximate p95 histogram boundary.\nClick to sort descending by this column.", TableSortColumn.AboveP95); DrawSortableHeaderCell(ref x, y, _drawTimeColumnWidth, height, "avg >p95", "Average execution time of samples above the approximate p95 boundary.\nClick to sort descending by this column.", TableSortColumn.AvgAboveP95); DrawSortableHeaderCell(ref x, y, _drawTimeColumnWidth, height, "p95", "Approximate 95th percentile: about 95% of samples completed at or below this duration.\nClick to sort descending by this column.", TableSortColumn.P95); DrawSortableHeaderCell(ref x, y, _drawCountColumnWidth, height, "samples", "Number of measured calls retained in the rolling 60-second window.\nClick to sort descending by this column.", TableSortColumn.Samples); DrawSortableHeaderCell(ref x, y, _drawCountColumnWidth, height, "gc samples", "Slow measured calls observed in a frame where a managed GC collection counter changed.\nThis is correlation, not proof that the patch caused the collection.\nClick to sort descending by this column.", TableSortColumn.GcSamples); } DrawSortableHeaderCell(ref x, y, _drawPatchTypeColumnWidth, height, "PatchType", "Harmony patch role or transpiler-derived runtime entry type.\nClick to sort ascending by this text column.", TableSortColumn.PatchType); if (_profilerView == ProfilerView.Avg1s) { DrawSortableHeaderCell(ref x, y, _drawTargetColumnWidth, height, "Patched target method", "Original game or mod method whose Harmony patch chain contains this entry.\nClick to sort ascending by this text column.", TableSortColumn.Target); DrawSortableHeaderCell(ref x, y, _drawPatchMethodColumnWidth, height, "Patch method", "Managed patch method or transpiler-injected call being measured.\nClick to sort ascending by this text column.", TableSortColumn.PatchMethod); } else { DrawSortableHeaderCell(ref x, y, _drawMethodColumnWidth, height, "Patched target method │ Patch method", "Original patched target followed by the measured patch method or transpiler-injected call.\nClick to sort ascending by this text column.", TableSortColumn.CombinedMethod); } } finally { GUI.EndGroup(); } } private void DrawSortableHeaderCell(ref float x, float y, float width, float height, string text, string tooltip, TableSortColumn column) { //IL_005a: Unknown result type (might be due to invalid IL or missing references) //IL_0067: Unknown result type (might be due to invalid IL or missing references) //IL_0072: Expected O, but got Unknown Rect val = default(Rect); ((Rect)(ref val))..ctor(x, y, width, height); bool flag = CurrentSortColumn == column; GUIStyle val2 = (flag ? _activeHeaderLabelStyle : _headerLabelStyle); string text2 = text ?? string.Empty; if (flag) { text2 += (IsTextSortColumn(column) ? " ▲" : " ▼"); } if (GUI.Button(val, new GUIContent(text2, tooltip ?? string.Empty), val2)) { SetSortColumn(column); } x += width; } private void DrawTotalSummaryRow() { //IL_0029: Unknown result type (might be due to invalid IL or missing references) //IL_002e: 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) Rect rect = GUILayoutUtility.GetRect(1f, CurrentTotalRowHeight, (GUILayoutOption[])(object)new GUILayoutOption[2] { GUILayout.ExpandWidth(true), GUILayout.Height(CurrentTotalRowHeight) }); GUI.BeginGroup(rect); try { float x = 0f - _scroll.x; float y = 0f; float height = ((Rect)(ref rect)).height; DrawGuiLabel(ref x, y, _drawModColumnWidth, height, "Total", _groupLabelStyle); DrawGuiLabel(ref x, y, _drawAvgTimeColumnWidth, height, FormatMs(_cachedTotalSummary.Avg1sMsPerFrame), _labelStyle); DrawGuiLabel(ref x, y, _drawAvgCountColumnWidth, height, FormatCount(_cachedTotalSummary.Avg1sCallsPerFrame), _labelStyle); DrawGuiLabel(ref x, y, _drawPatchTypeColumnWidth, height, string.Empty, _labelStyle); DrawGuiLabel(ref x, y, _drawTargetColumnWidth, height, string.Empty, _labelStyle); DrawGuiLabel(ref x, y, _drawPatchMethodColumnWidth, height, string.Empty, _labelStyle); } finally { GUI.EndGroup(); } } private float CalculateModColumnWidth(List layoutRows) { //IL_0035: Unknown result type (might be due to invalid IL or missing references) //IL_003f: Expected O, but got Unknown //IL_003a: Unknown result type (might be due to invalid IL or missing references) //IL_0115: Unknown result type (might be due to invalid IL or missing references) //IL_011f: Expected O, but got Unknown //IL_011a: 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) //IL_00b8: Expected O, but got Unknown //IL_00b3: Unknown result type (might be due to invalid IL or missing references) if (_labelStyle == null) { return 240f; } string text = (_groupByMod ? "Mod" : "BepInEx mod"); float num = _headerLabelStyle.CalcSize(new GUIContent(text)).x + 16f; if (_groupByMod) { foreach (ModGroupView cachedGroupedRow in _cachedGroupedRows) { string text2 = cachedGroupedRow.ModName ?? "Unknown"; if (_profilerView == ProfilerView.MaxOver60Sec) { text2 = Truncate(text2, 50); } float num2 = 14f + _groupLabelStyle.CalcSize(new GUIContent(text2)).x + 16f; num = Mathf.Max(num, num2); } } else { foreach (FlatRowView layoutRow in layoutRows) { string rowModName = GetRowModName(layoutRow); float num3 = _labelStyle.CalcSize(new GUIContent(rowModName)).x + 16f; num = Mathf.Max(num, num3); } } return Mathf.Clamp(Mathf.Ceil(num), 140f, 460f); } private float CalculatePatchTypeColumnWidth() { //IL_0022: Unknown result type (might be due to invalid IL or missing references) //IL_002c: Expected O, but got Unknown //IL_0027: Unknown result type (might be due to invalid IL or missing references) //IL_0043: Unknown result type (might be due to invalid IL or missing references) //IL_004d: Expected O, but got Unknown //IL_0048: Unknown result type (might be due to invalid IL or missing references) //IL_0064: Unknown result type (might be due to invalid IL or missing references) //IL_006e: Expected O, but got Unknown //IL_0069: Unknown result type (might be due to invalid IL or missing references) if (_labelStyle == null) { return 70f; } float num = _headerLabelStyle.CalcSize(new GUIContent("PatchType")).x + 12f; float num2 = _labelStyle.CalcSize(new GUIContent("Transpiled target")).x + 12f; float num3 = _labelStyle.CalcSize(new GUIContent("Transpiler call")).x + 12f; return Mathf.Ceil(Mathf.Max(num, Mathf.Max(num2, num3))); } private float CalculateTargetColumnWidth(List layoutRows) { //IL_0024: Unknown result type (might be due to invalid IL or missing references) //IL_002e: Expected O, but got Unknown //IL_0029: 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_007f: Expected O, but got Unknown //IL_007a: Unknown result type (might be due to invalid IL or missing references) if (_labelStyle == null) { return 180f; } float num = _headerLabelStyle.CalcSize(new GUIContent("Patched target method")).x + 16f; foreach (FlatRowView layoutRow in layoutRows) { string text = layoutRow.Context?.TargetDisplay ?? "(unknown target)"; num = Mathf.Max(num, _labelStyle.CalcSize(new GUIContent(text)).x + 16f); } return Mathf.Clamp(Mathf.Ceil(num), 180f, 900f); } private float CalculatePatchMethodColumnWidth(List layoutRows) { //IL_0024: Unknown result type (might be due to invalid IL or missing references) //IL_002e: Expected O, but got Unknown //IL_0029: Unknown result type (might be due to invalid IL or missing references) //IL_0065: Unknown result type (might be due to invalid IL or missing references) //IL_006f: Expected O, but got Unknown //IL_006a: Unknown result type (might be due to invalid IL or missing references) if (_labelStyle == null) { return 260f; } float num = _headerLabelStyle.CalcSize(new GUIContent("Patch method")).x + 16f; foreach (FlatRowView layoutRow in layoutRows) { string methodDisplay = GetMethodDisplay(layoutRow.InstrumentedMethod); num = Mathf.Max(num, _labelStyle.CalcSize(new GUIContent(methodDisplay)).x + 16f); } return Mathf.Clamp(Mathf.Ceil(num), 260f, 1200f); } private float CalculateCombinedMethodColumnWidth(List layoutRows) { //IL_0024: Unknown result type (might be due to invalid IL or missing references) //IL_002e: Expected O, but got Unknown //IL_0029: Unknown result type (might be due to invalid IL or missing references) //IL_00c6: Unknown result type (might be due to invalid IL or missing references) //IL_00d0: Expected O, but got Unknown //IL_00cb: Unknown result type (might be due to invalid IL or missing references) if (_labelStyle == null) { return 260f; } float num = _headerLabelStyle.CalcSize(new GUIContent("Patched target method │ Patch method")).x + 16f; foreach (FlatRowView layoutRow in layoutRows) { PatchContext context = layoutRow.Context; string text = context?.TargetDisplay ?? "(unknown target)"; string methodDisplay = GetMethodDisplay(layoutRow.InstrumentedMethod); string text2 = text + " │ " + methodDisplay; float num2 = ((context != null && context.IsTranspiledTargetEntry && context.TranspilerDetails.Count > 0) ? 68f : 0f); num = Mathf.Max(num, num2 + _labelStyle.CalcSize(new GUIContent(text2)).x + 16f); } return Mathf.Clamp(Mathf.Ceil(num), 260f, 1800f); } private List GetLayoutRows() { if (!_groupByMod) { return _cachedFlatRows; } List list = new List(); foreach (ModGroupView cachedGroupedRow in _cachedGroupedRows) { bool flag; lock (_lock) { flag = _modExpanded.TryGetValue(cachedGroupedRow.ModGuid, out var value) && value; } if (flag) { list.AddRange(cachedGroupedRow.Rows); } } return list; } private float CalculateFixedProfilerColumnsWidth(float modColumnWidth, float patchTypeColumnWidth) { float num = modColumnWidth; if (_profilerView == ProfilerView.Avg1s) { num += _drawAvgTimeColumnWidth; num += _drawAvgCountColumnWidth; } else { num += _drawTimeColumnWidth * 7f; num += _drawCountColumnWidth * 4f; } return num + patchTypeColumnWidth; } private float CalculateProfilerContentWidth() { return _drawContentWidth; } private float CalculateProfilerContentHeight() { if (!_groupByMod) { return (float)_cachedFlatRows.Count * CurrentRowHeight; } float num = 0f; foreach (ModGroupView cachedGroupedRow in _cachedGroupedRows) { num += CurrentGroupRowHeight; bool flag; lock (_lock) { flag = _modExpanded.TryGetValue(cachedGroupedRow.ModGuid, out var value) && value; } if (flag) { num += (float)cachedGroupedRow.Rows.Count * CurrentRowHeight; } } return num; } private void SaveModSelection() { Dictionary selection; lock (_lock) { selection = _modsToProfile.ToDictionary, string, bool>((KeyValuePair pair) => pair.Key, (KeyValuePair pair) => pair.Value, StringComparer.Ordinal); } _modSelectionPolicy.Save(selection); _modsSelectionDirty = false; } private void DrawProfilerVirtualContent(Rect contentRect) { //IL_0143: Unknown result type (might be due to invalid IL or missing references) //IL_006e: Unknown result type (might be due to invalid IL or missing references) //IL_00e0: Unknown result type (might be due to invalid IL or missing references) float visibleTop = _scroll.y - 80f; float visibleBottom = _scroll.y + Mathf.Max(100f, MainWindowHeight - 150f) + 80f; float y = 0f; if (_groupByMod) { foreach (ModGroupView cachedGroupedRow in _cachedGroupedRows) { DrawVirtualGroupRow(contentRect, ref y, cachedGroupedRow, visibleTop, visibleBottom); bool flag; lock (_lock) { flag = _modExpanded.TryGetValue(cachedGroupedRow.ModGuid, out var value) && value; } if (flag) { foreach (FlatRowView row in cachedGroupedRow.Rows) { DrawVirtualGroupedPatchRow(contentRect, ref y, row, visibleTop, visibleBottom); } } } return; } foreach (FlatRowView cachedFlatRow in _cachedFlatRows) { DrawVirtualFlatPatchRow(contentRect, ref y, cachedFlatRow, visibleTop, visibleBottom); } } private static bool IsVirtualRowVisible(float y, float height, float visibleTop, float visibleBottom) { return y + height >= visibleTop && y <= visibleBottom; } private void DrawVirtualGroupRow(Rect contentRect, ref float y, ModGroupView group, float visibleTop, float visibleBottom) { //IL_007f: Unknown result type (might be due to invalid IL or missing references) //IL_0084: Unknown result type (might be due to invalid IL or missing references) //IL_0089: Unknown result type (might be due to invalid IL or missing references) //IL_008b: Unknown result type (might be due to invalid IL or missing references) //IL_00f1: Unknown result type (might be due to invalid IL or missing references) float currentGroupRowHeight = CurrentGroupRowHeight; if (!IsVirtualRowVisible(y, currentGroupRowHeight, visibleTop, visibleBottom)) { y += currentGroupRowHeight; return; } bool flag; lock (_lock) { flag = _modExpanded.TryGetValue(group.ModGuid, out var value) && value; } float x = ((Rect)(ref contentRect)).x; float num = ((Rect)(ref contentRect)).y + y; Rect val = InsetGroupToggleRect(new Rect(x, num, 14f, currentGroupRowHeight)); bool flag2 = GUI.Button(val, flag ? "▼" : "▶", _compactButtonStyle); x += 14f; string text = group.ModName ?? "Unknown"; if (_profilerView == ProfilerView.MaxOver60Sec) { text = Truncate(text, 50); } float num2 = _drawModColumnWidth - 14f; flag2 |= GUI.Button(new Rect(x, num, num2, currentGroupRowHeight), text, _groupLabelStyle); x += num2; if (flag2) { lock (_lock) { _modExpanded[group.ModGuid] = !flag; } MarkViewDirty(); } DrawGuiGroupSummaryColumns(ref x, num, currentGroupRowHeight, group.Summary); y += currentGroupRowHeight; } private void DrawVirtualGroupedPatchRow(Rect contentRect, ref float y, FlatRowView row, float visibleTop, float visibleBottom) { float currentRowHeight = CurrentRowHeight; if (!IsVirtualRowVisible(y, currentRowHeight, visibleTop, visibleBottom)) { y += currentRowHeight; return; } MethodBase instrumentedMethod = row.InstrumentedMethod; PatchStatSnapshot snapshot = row.Snapshot; PatchContext context = row.Context; string text = context.PatchType ?? "?"; string target = context.TargetDisplay ?? "(unknown target)"; string methodDisplay = GetMethodDisplay(instrumentedMethod); float x = ((Rect)(ref contentRect)).x; float y2 = ((Rect)(ref contentRect)).y + y; DrawGuiLabel(ref x, y2, _drawModColumnWidth, currentRowHeight, string.Empty, _labelStyle); DrawGuiStatColumns(ref x, y2, currentRowHeight, snapshot); DrawGuiLabel(ref x, y2, _drawPatchTypeColumnWidth, currentRowHeight, text, _labelStyle); DrawGuiMethodColumns(ref x, y2, currentRowHeight, target, methodDisplay, context); y += currentRowHeight; } private void DrawVirtualFlatPatchRow(Rect contentRect, ref float y, FlatRowView row, float visibleTop, float visibleBottom) { float currentRowHeight = CurrentRowHeight; if (!IsVirtualRowVisible(y, currentRowHeight, visibleTop, visibleBottom)) { y += currentRowHeight; return; } MethodBase instrumentedMethod = row.InstrumentedMethod; PatchStatSnapshot snapshot = row.Snapshot; PatchContext context = row.Context; string text = context.ModName ?? GuessModName(instrumentedMethod, context.ModGuid); string text2 = context.PatchType ?? "?"; string target = context.TargetDisplay ?? "(unknown target)"; string methodDisplay = GetMethodDisplay(instrumentedMethod); float x = ((Rect)(ref contentRect)).x; float y2 = ((Rect)(ref contentRect)).y + y; DrawGuiLabel(ref x, y2, _drawModColumnWidth, currentRowHeight, text, _labelStyle); DrawGuiStatColumns(ref x, y2, currentRowHeight, snapshot); DrawGuiLabel(ref x, y2, _drawPatchTypeColumnWidth, currentRowHeight, text2, _labelStyle); DrawGuiMethodColumns(ref x, y2, currentRowHeight, target, methodDisplay, context); y += currentRowHeight; } private void DrawGuiMethodColumns(ref float x, float y, float height, string target, string patchDisplay, PatchContext context) { //IL_016f: Unknown result type (might be due to invalid IL or missing references) //IL_0174: Unknown result type (might be due to invalid IL or missing references) //IL_0179: Unknown result type (might be due to invalid IL or missing references) //IL_017b: Unknown result type (might be due to invalid IL or missing references) //IL_0189: Unknown result type (might be due to invalid IL or missing references) //IL_0199: Expected O, but got Unknown //IL_0071: Unknown result type (might be due to invalid IL or missing references) //IL_0076: Unknown result type (might be due to invalid IL or missing references) //IL_007b: Unknown result type (might be due to invalid IL or missing references) //IL_007d: Unknown result type (might be due to invalid IL or missing references) //IL_008a: Unknown result type (might be due to invalid IL or missing references) //IL_009a: Expected O, but got Unknown bool flag = context != null && context.IsTranspiledTargetEntry && context.TranspilerDetails.Count > 0; if (_profilerView == ProfilerView.Avg1s) { if (flag && _drawTargetColumnWidth > 108f) { bool flag2 = _detailsWindow.RequestedVisible && _selectedTranspilerDetailsContext == context; Rect val = InsetButtonRect(new Rect(x, y, 64f, height)); bool flag3 = GUI.Toggle(val, flag2, new GUIContent("Calls", "Show transpilers and runtime calls detected in the transpiled method."), _compactButtonStyle); if (flag3 != flag2) { ToggleTranspilerDetails(context); } x += 68f; DrawGuiLabel(ref x, y, _drawTargetColumnWidth - 64f - 4f, height, target, _labelStyle); } else { DrawGuiLabel(ref x, y, _drawTargetColumnWidth, height, target, _labelStyle); } DrawGuiLabel(ref x, y, _drawPatchMethodColumnWidth, height, patchDisplay, _labelStyle); return; } string text = target + " │ " + patchDisplay; if (flag && _drawMethodColumnWidth > 148f) { bool flag4 = _detailsWindow.RequestedVisible && _selectedTranspilerDetailsContext == context; Rect val2 = InsetButtonRect(new Rect(x, y, 64f, height)); bool flag5 = GUI.Toggle(val2, flag4, new GUIContent("Calls", "Show transpilers and runtime calls detected in the transpiled method."), _compactButtonStyle); if (flag5 != flag4) { ToggleTranspilerDetails(context); } x += 68f; DrawGuiLabel(ref x, y, _drawMethodColumnWidth - 64f - 4f, height, text, _labelStyle); } else { DrawGuiLabel(ref x, y, _drawMethodColumnWidth, height, text, _labelStyle); } } private void DrawGuiStatColumns(ref float x, float y, float height, PatchStatSnapshot snapshot) { if (_profilerView == ProfilerView.Avg1s) { DrawGuiLabel(ref x, y, _drawAvgTimeColumnWidth, height, FormatMs(snapshot.Avg1sMsPerFrame), _labelStyle); DrawGuiLabel(ref x, y, _drawAvgCountColumnWidth, height, FormatCount(snapshot.Avg1sCallsPerFrame), _labelStyle); } else { DrawGuiMaxColumns(ref x, y, height, snapshot.MaxSnapshot); } } private void DrawGuiGroupSummaryColumns(ref float x, float y, float height, GroupSummary summary) { if (_profilerView == ProfilerView.Avg1s) { DrawGuiLabel(ref x, y, _drawAvgTimeColumnWidth, height, FormatMs(summary.Avg1sMsPerFrame), _labelStyle); DrawGuiLabel(ref x, y, _drawAvgCountColumnWidth, height, FormatCount(summary.Avg1sCallsPerFrame), _labelStyle); } else { DrawGuiMaxColumns(ref x, y, height, summary.Max); } DrawGuiLabel(ref x, y, _drawPatchTypeColumnWidth, height, string.Empty, _labelStyle); if (_profilerView == ProfilerView.Avg1s) { DrawGuiLabel(ref x, y, _drawTargetColumnWidth, height, string.Empty, _labelStyle); DrawGuiLabel(ref x, y, _drawPatchMethodColumnWidth, height, string.Empty, _labelStyle); } else { DrawGuiLabel(ref x, y, _drawMethodColumnWidth, height, string.Empty, _labelStyle); } } private void DrawGuiMaxColumns(ref float x, float y, float height, MaxAnalyticsSnapshot max) { DrawGuiLabel(ref x, y, _drawTimeColumnWidth, height, FormatRawMax(max), _labelStyle); DrawGuiLabel(ref x, y, _drawTimeColumnWidth, height, FormatMs(max.SecondMaxMs), _labelStyle); DrawGuiLabel(ref x, y, _drawTimeColumnWidth, height, FormatMs(max.ThirdMaxMs), _labelStyle); DrawGuiLabel(ref x, y, _drawCountColumnWidth, height, FormatCount(max.AboveP99Count), _labelStyle); DrawGuiLabel(ref x, y, _drawTimeColumnWidth, height, FormatMs(max.AvgAboveP99Ms), _labelStyle); DrawGuiLabel(ref x, y, _drawTimeColumnWidth, height, FormatMs(max.P99Ms), _labelStyle); DrawGuiLabel(ref x, y, _drawCountColumnWidth, height, FormatCount(max.AboveP95Count), _labelStyle); DrawGuiLabel(ref x, y, _drawTimeColumnWidth, height, FormatMs(max.AvgAboveP95Ms), _labelStyle); DrawGuiLabel(ref x, y, _drawTimeColumnWidth, height, FormatMs(max.P95Ms), _labelStyle); DrawGuiLabel(ref x, y, _drawCountColumnWidth, height, FormatCount(max.WindowSampleCount), _labelStyle); DrawGuiLabel(ref x, y, _drawCountColumnWidth, height, FormatCount(max.GcSampleCount), _labelStyle); } private static Rect InsetGroupToggleRect(Rect rect) { //IL_0067: 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_006b: Unknown result type (might be due to invalid IL or missing references) ((Rect)(ref rect)).x = ((Rect)(ref rect)).x + 1f; ((Rect)(ref rect)).y = ((Rect)(ref rect)).y + 3f; ((Rect)(ref rect)).width = Mathf.Max(1f, ((Rect)(ref rect)).width - 3f); ((Rect)(ref rect)).height = Mathf.Max(1f, ((Rect)(ref rect)).height - 6f); return rect; } private static Rect InsetButtonRect(Rect rect) { //IL_0067: 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_006b: Unknown result type (might be due to invalid IL or missing references) ((Rect)(ref rect)).x = ((Rect)(ref rect)).x + 1f; ((Rect)(ref rect)).y = ((Rect)(ref rect)).y + 1f; ((Rect)(ref rect)).width = Mathf.Max(1f, ((Rect)(ref rect)).width - 2f); ((Rect)(ref rect)).height = Mathf.Max(1f, ((Rect)(ref rect)).height - 2f); return rect; } private static void DrawGuiLabel(ref float x, float y, float width, float height, string text, GUIStyle style, string tooltip = null) { //IL_0006: 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) //IL_002d: Expected O, but got Unknown GUI.Label(new Rect(x, y, width, height), new GUIContent(text ?? string.Empty, tooltip ?? string.Empty), style); x += width; } private void DrawProfilerTab() { //IL_0214: Unknown result type (might be due to invalid IL or missing references) //IL_0220: Unknown result type (might be due to invalid IL or missing references) //IL_0225: Unknown result type (might be due to invalid IL or missing references) //IL_0244: Unknown result type (might be due to invalid IL or missing references) //IL_0249: Unknown result type (might be due to invalid IL or missing references) //IL_024b: Unknown result type (might be due to invalid IL or missing references) RefreshCachedViewIfNeeded(); PrepareProfilerLayoutWidths(); DrawHeader(); if (_profilerView == ProfilerView.Avg1s) { DrawTotalSummaryRow(); } GUILayout.Space(2f); Label("The table shows sampled entries only. Selected targets and detected calls stay hidden until their code path executes after profiling starts."); GUILayout.Space(2f); if (!(_groupByMod ? (_cachedGroupedRows.Count > 0) : (_cachedFlatRows.Count > 0))) { Label("No data yet. Run profiling and trigger patched code paths."); return; } if (_groupByMod) { GUILayout.BeginHorizontal(Array.Empty()); if (GUILayout.Button("Expand all", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(88f) })) { lock (_lock) { foreach (string item in _modExpanded.Keys.ToList()) { _modExpanded[item] = true; } } MarkViewDirty(); } if (GUILayout.Button("Collapse all", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(88f) })) { lock (_lock) { foreach (string item2 in _modExpanded.Keys.ToList()) { _modExpanded[item2] = false; } } MarkViewDirty(); } GUILayout.EndHorizontal(); GUILayout.Space(2f); } float num = CalculateProfilerContentHeight(); float num2 = CalculateProfilerContentWidth(); _scroll = GUILayout.BeginScrollView(_scroll, false, false, Array.Empty()); Rect rect = GUILayoutUtility.GetRect(num2, num, (GUILayoutOption[])(object)new GUILayoutOption[2] { GUILayout.Width(num2), GUILayout.Height(num) }); DrawProfilerVirtualContent(rect); GUILayout.EndScrollView(); } private void DrawModsTab() { //IL_0507: Unknown result type (might be due to invalid IL or missing references) //IL_0511: Unknown result type (might be due to invalid IL or missing references) //IL_0516: Unknown result type (might be due to invalid IL or missing references) //IL_05ac: Unknown result type (might be due to invalid IL or missing references) //IL_05c6: Expected O, but got Unknown if (!_listReady) { Label("Patch list could not be loaded. See the status line for details."); return; } GUILayout.BeginHorizontal(Array.Empty()); if (GUILayout.Button("Enable all", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(88f) })) { bool flag = false; lock (_lock) { foreach (string item in _modsToProfile.Keys.ToList()) { if (!_modsToProfile[item]) { _modsToProfile[item] = true; flag = true; } } } _modsSelectionDirty |= flag; } if (GUILayout.Button("Disable all", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(88f) })) { bool flag2 = false; lock (_lock) { foreach (string item2 in _modsToProfile.Keys.ToList()) { if (_modsToProfile[item2]) { _modsToProfile[item2] = false; flag2 = true; } } } _modsSelectionDirty |= flag2; } GUI.enabled = _modsSelectionDirty; GUIStyle val = (_modsSelectionDirty ? _theme.AccentButtonStyle : GUI.skin.button); if (GUILayout.Button("Apply selection", val, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(110f) })) { ResetProfilingSelection(); } GUI.enabled = true; if (_modsSelectionDirty) { GUILayout.Space(6f); GUILayout.Label("Selection has changed", _theme.AccentLabelStyle, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.ExpandWidth(true) }); } else { GUILayout.FlexibleSpace(); } GUILayout.EndHorizontal(); GUILayout.Space(3f); bool flag3; lock (_lock) { flag3 = _modsToProfile.TryGetValue("shudnal.ValheimProfiler.PatchProfiler.TranspiledTargets", out var value) && value; } GUILayout.BeginVertical(GUI.skin.box, Array.Empty()); if (!flag3) { GUILayout.BeginHorizontal(Array.Empty()); GUILayout.Label("Whole transpiled-target timing is disabled", _theme.AccentLabelStyle, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.ExpandWidth(true) }); if (GUILayout.Button("Enable Transpiled methods", _theme.AccentButtonStyle, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(190f) })) { lock (_lock) { _modsToProfile["shudnal.ValheimProfiler.PatchProfiler.TranspiledTargets"] = true; } _modsSelectionDirty = true; flag3 = true; } GUILayout.EndHorizontal(); } else { HeaderLabel("Whole transpiled-target timing is enabled"); } Label("The separate Transpiled methods entry controls timing of complete transpiled target methods. Enabling a source mod does not enable it automatically. Transpiler call rows still follow their source mod selection."); GUILayout.EndVertical(); GUILayout.Space(3f); List list; Dictionary dictionary; Dictionary dictionary2; Dictionary dictionary3; lock (_lock) { list = _modsToProfile.Keys.OrderBy((string text3) => (!(text3 == "shudnal.ValheimProfiler.PatchProfiler.TranspiledTargets")) ? 1 : 0).ThenBy((string text3) => _modGuidToName.TryGetValue(text3, out var value5) ? value5 : text3, StringComparer.OrdinalIgnoreCase).ToList(); dictionary = _modsToProfile.ToDictionary((KeyValuePair pair) => pair.Key, (KeyValuePair pair) => pair.Value); dictionary2 = _modPatchCounts.ToDictionary((KeyValuePair pair) => pair.Key, (KeyValuePair pair) => pair.Value); dictionary3 = _modGuidToName.ToDictionary((KeyValuePair pair) => pair.Key, (KeyValuePair pair) => pair.Value); } _modsScroll = GUILayout.BeginScrollView(_modsScroll, Array.Empty()); foreach (string item3 in list) { bool value2; bool flag4 = dictionary.TryGetValue(item3, out value2) && value2; int value3; int num = (dictionary2.TryGetValue(item3, out value3) ? value3 : 0); string value4; string text = (dictionary3.TryGetValue(item3, out value4) ? value4 : "Unknown"); GUILayout.BeginHorizontal(Array.Empty()); GUILayout.Space(8f); string text2 = ((item3 == "shudnal.ValheimProfiler.PatchProfiler.TranspiledTargets") ? "Separate switch for measuring complete transpiled target methods. Source-mod selection controls Transpiler call rows, not this switch." : "Enable Patch Profiler entries attributed to this BepInEx mod."); bool flag5 = ProfilerGui.ToggleLayout(_theme, flag4, new GUIContent(item3, text2), 390f, _labelStyle); if (item3 == "shudnal.ValheimProfiler.PatchProfiler.TranspiledTargets") { GUILayout.Label("Transpiled methods (whole targets, separate switch)", _theme.AccentLabelStyle, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(300f) }); } else { Label(text, GUILayout.Width(300f)); } Label($"entries: {num}", GUILayout.Width(110f)); GUILayout.EndHorizontal(); if (flag5 != flag4) { lock (_lock) { _modsToProfile[item3] = flag5; } _modsSelectionDirty = true; } } GUILayout.EndScrollView(); } private void DrawHelpTab() { //IL_0003: 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_0012: Unknown result type (might be due to invalid IL or missing references) _modsScroll = GUILayout.BeginScrollView(_modsScroll, Array.Empty()); Label("Patch Profiler measures execution time of Harmony patch methods on the main thread."); Label("It instruments patch methods with a timing prefix and finalizer, then aggregates measured wall-clock duration in milliseconds."); Label("Hover important controls and column headers for contextual explanations."); GUILayout.Space(6f); GroupLabel("Transpilers"); Label("Transpiler methods themselves usually run while Harmony applies patches, not every frame."); Label("For every transpiled target method, the profiler can measure the whole modified target method as a Transpiled target row."); Label("Whole-target timing is controlled by the separate Transpiled methods selection entry and is not enabled automatically when a source mod is selected."); Label("The profiler compares the original target IL with the final IL after Harmony transpilers and registers net-new direct call/callvirt instructions as Transpiler call rows."); Label("Duplicate call sites to the same method are aggregated into one row for the target and mod, while the details window lists the associated source transpilers."); Label("Transpiler call rows are timed directly around the detected net-new call/callvirt instructions after all source transpilers have run."); Label("The timing remains inclusive of work executed inside the called method, but unrelated nested calls to the same method are no longer counted."); Label("The main table shows only entries that received samples. A detected target or call remains absent until that code path executes after profiling starts."); GUILayout.Space(6f); GroupLabel("Over 1 sec"); Label("This view uses a rolling 1 second window."); Label("\"ms per frame\" is the average amount of frame time spent inside measured patch methods."); Label("\"calls per frame\" is the average number of calls per frame during the same window."); Label("The Total row is the sum for all currently measured patch methods."); Label("Click a table column header to sort. Text columns use ascending order; numeric columns use descending order. The active header shows ▲ or ▼."); GUILayout.Space(6f); GroupLabel("Max over 60 sec"); Label("This view uses a rolling 60 second window."); Label("\"raw max\" is the single slowest measured call in the window. A trailing ! means it looks like an isolated spike or a GC-contaminated spike."); Label("\"2nd max\" and \"3rd max\" are the second and third slowest measured calls. They help confirm whether a spike repeated."); Label("If raw max is high but 2nd/3rd max are low, the row is likely affected by a one-off stall, GC, OS scheduling, Unity pause, or measurement contamination."); GUILayout.Space(6f); GroupLabel("Percentiles"); Label("p99 ms means that 99% of calls completed faster than the shown time."); Label("For example, p99 ms = 0.010 means almost all calls completed faster than 0.010 ms."); Label("p95 ms means that 95% of calls completed faster than the shown time."); Label("\"> p99\" and \"> p95\" show how many samples were slower than the corresponding percentile threshold."); Label("\"avg >p99\" and \"avg >p95\" show the average duration of those slower samples."); GUILayout.Space(6f); GroupLabel("GC samples"); Label("\"gc samples\" counts slow measured calls where a GC collection counter changed during the current frame before the sample ended."); Label("Only samples >= 1 ms are checked to keep profiler overhead low."); Label("Such samples are not automatically fake: the profiled code may allocate enough to trigger GC."); Label("But if a row has high raw max and GC samples, interpret the raw max carefully."); GUILayout.Space(6f); GroupLabel("Frame budget"); Label("A 60 FPS target gives about 16.6 ms for the whole frame."); Label("A 30 FPS target gives about 33.3 ms."); Label("Patch time is only one part of the frame, so small values can still matter if they happen very often."); GUILayout.Space(6f); GroupLabel("Notes"); Label("The profiler itself adds overhead. Use it for comparison and diagnosis, not as an exact production benchmark."); Label("Realtime windows continue to move and new measured calls are recorded while profiling is active."); Label("Pause profiling if you want to inspect the current data without changing it."); GUILayout.EndScrollView(); } internal PatchProfilerTool(ValheimProfilerApp app) { //IL_01f8: Unknown result type (might be due to invalid IL or missing references) //IL_0202: Expected O, but got Unknown //IL_0295: Unknown result type (might be due to invalid IL or missing references) //IL_0296: Unknown result type (might be due to invalid IL or missing references) //IL_029b: Unknown result type (might be due to invalid IL or missing references) //IL_02a7: Unknown result type (might be due to invalid IL or missing references) //IL_02a8: Unknown result type (might be due to invalid IL or missing references) //IL_02ad: Unknown result type (might be due to invalid IL or missing references) //IL_02c0: Unknown result type (might be due to invalid IL or missing references) //IL_02c5: Unknown result type (might be due to invalid IL or missing references) //IL_02c6: Unknown result type (might be due to invalid IL or missing references) //IL_02cb: Unknown result type (might be due to invalid IL or missing references) //IL_0308: Unknown result type (might be due to invalid IL or missing references) //IL_030d: Unknown result type (might be due to invalid IL or missing references) //IL_030f: Unknown result type (might be due to invalid IL or missing references) //IL_0314: Unknown result type (might be due to invalid IL or missing references) _app = app ?? throw new ArgumentNullException("app"); _logger = app.Logger; _windows = app.Windows; _theme = app.Theme; _harmony = new Harmony("shudnal.ValheimProfiler.PatchProfiler"); _modSelectionPolicy = new ModSelectionPolicy(ProfilerPaths.GetConfigFilePath("PatchProfilerSelection.cfg")); _mainThreadId = Thread.CurrentThread.ManagedThreadId; _status = "Loaded. Press F8 or use the launcher to open."; ValheimProfilerConfig config = app.Config; _avgSortColumn = ParseSortColumn(config.PatchProfilerAvgSortColumn.Value, ProfilerView.Avg1s, TableSortColumn.AvgMsPerFrame); _maxSortColumn = ParseSortColumn(config.PatchProfilerMaxSortColumn.Value, ProfilerView.MaxOver60Sec, TableSortColumn.ThirdMax); Vector2 minimumSize = default(Vector2); ((Vector2)(ref minimumSize))..ctor(720f, 360f); Vector2 minimumSize2 = default(Vector2); ((Vector2)(ref minimumSize2))..ctor(520f, 280f); Vector2 defaultToolWindowSize = _windows.GetDefaultToolWindowSize(600f, minimumSize); Vector2 defaultCompactWindowSize = _windows.GetDefaultCompactWindowSize(520f, minimumSize2); _mainWindow = _windows.Register(new ProfilerWindow("ValheimProfiler.PatchProfiler", "Patch Profiler", new Rect(ValheimProfilerConfig.DefaultPatchWindowPosition, defaultToolWindowSize), minimumSize, resizable: true, requestedVisible: false, DrawWindow, config.PatchWindowPosition, config.PatchWindowSize)); _detailsWindow = _windows.Register(new ProfilerWindow("ValheimProfiler.PatchProfiler.TranspilerDetails", "Transpiled method details", new Rect(ValheimProfilerConfig.DefaultTranspilerWindowPosition, defaultCompactWindowSize), minimumSize2, resizable: true, requestedVisible: false, DrawTranspilerDetailsWindow, config.TranspilerWindowPosition, config.TranspilerWindowSize)); SetCurrentRealtime(Time.realtimeSinceStartup); UpdateGcBaseline(); _analyticsWakeEvent = new AutoResetEvent(initialState: false); StartAnalyticsThread(); _instance = this; } void IProfilerTool.ShowWindow() { ShowWindow(); } void IProfilerTool.ToggleWindow() { ToggleWindow(); } void IProfilerTool.Update() { Update(); } void IProfilerTool.Shutdown() { Shutdown(); } internal void ToggleWindow() { IsWindowVisible = !IsWindowVisible; if (IsWindowVisible) { ShowWindow(); } } internal void ShowWindow() { IsWindowVisible = true; _app.ShowUi(); _windows.BringToFront(_mainWindow); } internal void Update() { _currentFrame = Time.frameCount; SetCurrentRealtime(Time.realtimeSinceStartup); UpdateGcBaseline(); } internal void Shutdown() { if (_instance == this) { _instance = null; } try { StopProfilingInternal(); } catch { } StopAnalyticsThread(); IsWindowVisible = false; } private void SetCurrentRealtime(float realtime) { Interlocked.Exchange(ref _currentRealtimeMs, Mathf.RoundToInt(realtime * 1000f)); } private void UpdateGcBaseline() { _lastObservedGc0 = GC.CollectionCount(0); _lastObservedGc1 = GC.CollectionCount(1); _lastObservedGc2 = GC.CollectionCount(2); } private void BuildAssemblyPluginMap() { Dictionary dictionary = new Dictionary(); Dictionary dictionary2 = new Dictionary(); Dictionary dictionary3 = new Dictionary(); foreach (KeyValuePair pluginInfo in Chainloader.PluginInfos) { PluginInfo value = pluginInfo.Value; if (!((Object)(object)((value != null) ? value.Instance : null) == (Object)null)) { Assembly assembly = ((object)value.Instance).GetType().Assembly; BepInPlugin metadata = value.Metadata; string text = ((metadata != null) ? metadata.GUID : null); if (string.IsNullOrWhiteSpace(text)) { text = pluginInfo.Key ?? "Unknown"; } BepInPlugin metadata2 = value.Metadata; string value2 = ((metadata2 != null) ? metadata2.Name : null); if (string.IsNullOrWhiteSpace(value2)) { value2 = text; } dictionary[assembly] = value2; dictionary2[assembly] = text; if (!dictionary3.ContainsKey(text)) { dictionary3[text] = value2; } } } lock (_lock) { _assemblyToPluginName = dictionary; _assemblyToPluginGuid = dictionary2; _pluginGuidToName = dictionary3; } } private string GuessModName(MethodBase method, string knownGuid = null) { try { lock (_lock) { if (!string.IsNullOrWhiteSpace(knownGuid) && _pluginGuidToName.TryGetValue(knownGuid, out var value)) { return value; } } if (method == null) { return "Unknown"; } Assembly assembly = method.DeclaringType?.Assembly; if (assembly == null) { return "Unknown"; } lock (_lock) { if (_assemblyToPluginName.TryGetValue(assembly, out var value2)) { return value2; } } return assembly.GetName().Name ?? "Unknown"; } catch { return "Unknown"; } } private string GuessModGuid(MethodBase method, string ownerId) { try { lock (_lock) { if (!string.IsNullOrWhiteSpace(ownerId) && _pluginGuidToName.ContainsKey(ownerId)) { return ownerId; } } if (method == null) { return (!string.IsNullOrWhiteSpace(ownerId)) ? ownerId : "(unknown guid)"; } Assembly assembly = method.DeclaringType?.Assembly; if (assembly == null) { return (!string.IsNullOrWhiteSpace(ownerId)) ? ownerId : "(unknown guid)"; } lock (_lock) { if (_assemblyToPluginGuid.TryGetValue(assembly, out var value)) { return value; } } return assembly.GetName().Name ?? ((!string.IsNullOrWhiteSpace(ownerId)) ? ownerId : "(unknown guid)"); } catch { return (!string.IsNullOrWhiteSpace(ownerId)) ? ownerId : "(unknown guid)"; } } private static string GetMethodDisplay(MethodBase m) { if (m == null) { return "(null)"; } try { Type declaringType = m.DeclaringType; string text = ((declaringType != null) ? declaringType.FullName : "(no type)"); return text + "::" + m.Name; } catch { return "(unprintable)"; } } private static string GetMethodIdentity(MethodBase method) { if (method == null) { return "(null)"; } try { return method.Module.ModuleVersionId.ToString("N") + ":" + method.MetadataToken; } catch { return GetMethodDisplay(method) + "#" + method.GetHashCode(); } } private static string Truncate(string value, int maxChars) { if (string.IsNullOrEmpty(value) || maxChars <= 0 || value.Length <= maxChars) { return value ?? string.Empty; } if (maxChars == 1) { return "…"; } return value.Substring(0, maxChars - 1) + "…"; } private static string FormatMs(double value) { if (value <= 0.0) { return "0.000"; } if (value > 999.999) { return $"{999.999:0.000}+"; } return $"{value:0.000}"; } private static string FormatRawMax(MaxAnalyticsSnapshot max) { string text = FormatMs(max.RawMaxMs); return ShouldMarkRawMax(max) ? (text + "!") : text; } private static string FormatCount(int value) { return FormatCount((long)value); } private static string FormatCount(long value) { if (value <= 0) { return "0"; } if (value > 9999999) { return 9999999 + "+"; } return value.ToString(); } private static string FormatCount(double value) { if (value <= 0.0) { return "0"; } if (value > 9999999.0) { return 9999999 + "+"; } return $"{value:0.##}"; } private static bool ShouldMarkRawMax(MaxAnalyticsSnapshot max) { return IsIsolatedRawMax(max) || IsGcContaminatedRawMax(max); } private static bool IsIsolatedRawMax(MaxAnalyticsSnapshot max) { if (max.RawMaxMs < 8.0) { return false; } if (max.WindowSampleCount < 3) { return false; } if (max.ThirdMaxMs <= 0.0) { return false; } return max.RawMaxMs >= max.ThirdMaxMs * 4.0; } private static bool IsGcContaminatedRawMax(MaxAnalyticsSnapshot max) { if (max.RawMaxMs < 8.0) { return false; } return max.GcSampleCount > 0; } private void SetSortColumn(TableSortColumn column) { if (_profilerView == ProfilerView.Avg1s) { _avgSortColumn = column; _app.Config.PatchProfilerAvgSortColumn.Value = column.ToString(); } else { _maxSortColumn = column; _app.Config.PatchProfilerMaxSortColumn.Value = column.ToString(); } MarkViewDirty(); } private static TableSortColumn ParseSortColumn(string value, ProfilerView view, TableSortColumn fallback) { if (!Enum.TryParse(value, ignoreCase: true, out var result)) { return fallback; } bool flag2; if (view == ProfilerView.Avg1s) { bool flag = (((uint)result <= 2u || (uint)(result - 14) <= 2u) ? true : false); flag2 = flag; } else { bool flag; switch (result) { case TableSortColumn.Mod: case TableSortColumn.RawMax: case TableSortColumn.SecondMax: case TableSortColumn.ThirdMax: case TableSortColumn.AboveP99: case TableSortColumn.AvgAboveP99: case TableSortColumn.P99: case TableSortColumn.AboveP95: case TableSortColumn.AvgAboveP95: case TableSortColumn.P95: case TableSortColumn.Samples: case TableSortColumn.GcSamples: case TableSortColumn.PatchType: case TableSortColumn.CombinedMethod: flag = true; break; default: flag = false; break; } flag2 = flag; } return flag2 ? result : fallback; } private List BuildRowsLocked(int frame, float now) { IEnumerable source = from row in _stats.Select(delegate(KeyValuePair kv) { _context.TryGetValue(kv.Key, out var value); return (value == null) ? null : new FlatRowView { EntryId = kv.Key, InstrumentedMethod = value.InstrumentedMethod, Snapshot = kv.Value.GetSnapshot(frame, now), Context = value }; }) where row != null select row; source = ((_profilerView == ProfilerView.Avg1s) ? source.Where((FlatRowView row) => row.Snapshot.Avg1sFrames > 0 && row.Snapshot.Avg1sMsPerFrame > 0.0) : source.Where((FlatRowView row) => row.Snapshot.MaxSnapshot.WindowSampleCount > 0 && row.Snapshot.MaxSnapshot.RawMaxMs > 0.0)); return SortRows(source); } private List BuildGroupedRowsFromRowsLocked(List rows) { List groups = (from row in rows group row by row.Context.ModGuid ?? "(unknown guid)").Select(delegate(IGrouping group) { List list = SortRows(group.ToList()); FlatRowView flatRowView = list[0]; GroupSummary summary = BuildSummary(list); return new ModGroupView { ModGuid = group.Key, ModName = (flatRowView.Context.ModName ?? "Unknown"), Rows = list, AggregateScore = CalculateGroupScore(summary), Summary = summary }; }).ToList(); groups = SortGroups(groups); foreach (ModGroupView item in groups) { if (!_modExpanded.ContainsKey(item.ModGuid)) { _modExpanded[item.ModGuid] = false; } } return groups; } private List SortRows(IEnumerable rows) { TableSortColumn currentSortColumn = CurrentSortColumn; if (1 == 0) { } IOrderedEnumerable orderedEnumerable = currentSortColumn switch { TableSortColumn.Mod => rows.OrderBy(GetRowModName, StringComparer.OrdinalIgnoreCase), TableSortColumn.AvgMsPerFrame => rows.OrderByDescending((FlatRowView row) => row.Snapshot.Avg1sMsPerFrame), TableSortColumn.AvgCallsPerFrame => rows.OrderByDescending((FlatRowView row) => row.Snapshot.Avg1sCallsPerFrame), TableSortColumn.RawMax => rows.OrderByDescending((FlatRowView row) => row.Snapshot.MaxSnapshot.RawMaxMs), TableSortColumn.SecondMax => rows.OrderByDescending((FlatRowView row) => row.Snapshot.MaxSnapshot.SecondMaxMs), TableSortColumn.ThirdMax => rows.OrderByDescending((FlatRowView row) => row.Snapshot.MaxSnapshot.ThirdMaxMs), TableSortColumn.AboveP99 => rows.OrderByDescending((FlatRowView row) => row.Snapshot.MaxSnapshot.AboveP99Count), TableSortColumn.AvgAboveP99 => rows.OrderByDescending((FlatRowView row) => row.Snapshot.MaxSnapshot.AvgAboveP99Ms), TableSortColumn.P99 => rows.OrderByDescending((FlatRowView row) => row.Snapshot.MaxSnapshot.P99Ms), TableSortColumn.AboveP95 => rows.OrderByDescending((FlatRowView row) => row.Snapshot.MaxSnapshot.AboveP95Count), TableSortColumn.AvgAboveP95 => rows.OrderByDescending((FlatRowView row) => row.Snapshot.MaxSnapshot.AvgAboveP95Ms), TableSortColumn.P95 => rows.OrderByDescending((FlatRowView row) => row.Snapshot.MaxSnapshot.P95Ms), TableSortColumn.Samples => rows.OrderByDescending((FlatRowView row) => row.Snapshot.MaxSnapshot.WindowSampleCount), TableSortColumn.GcSamples => rows.OrderByDescending((FlatRowView row) => row.Snapshot.MaxSnapshot.GcSampleCount), TableSortColumn.PatchType => rows.OrderBy((FlatRowView row) => row.Context.PatchType ?? string.Empty, StringComparer.OrdinalIgnoreCase), TableSortColumn.Target => rows.OrderBy((FlatRowView row) => row.Context.TargetDisplay ?? string.Empty, StringComparer.OrdinalIgnoreCase), TableSortColumn.PatchMethod => rows.OrderBy((FlatRowView row) => GetMethodDisplay(row.InstrumentedMethod), StringComparer.OrdinalIgnoreCase), TableSortColumn.CombinedMethod => rows.OrderBy((FlatRowView row) => BuildCombinedMethodSortText(row), StringComparer.OrdinalIgnoreCase), _ => rows.OrderByDescending((FlatRowView row) => row.Snapshot.MaxSnapshot.ThirdMaxMs), }; if (1 == 0) { } IOrderedEnumerable source = orderedEnumerable; return source.ThenByDescending((FlatRowView row) => row.Snapshot.MaxSnapshot.RawMaxMs).ThenBy(GetRowModName, StringComparer.OrdinalIgnoreCase).ThenBy((FlatRowView row) => row.Context.TargetDisplay ?? string.Empty, StringComparer.OrdinalIgnoreCase) .ThenBy((FlatRowView row) => GetMethodDisplay(row.InstrumentedMethod), StringComparer.OrdinalIgnoreCase) .ToList(); } private List SortGroups(IEnumerable groups) { TableSortColumn currentSortColumn = CurrentSortColumn; if (1 == 0) { } IOrderedEnumerable orderedEnumerable = currentSortColumn switch { TableSortColumn.Mod => groups.OrderBy((ModGroupView group) => group.ModName, StringComparer.OrdinalIgnoreCase), TableSortColumn.AvgMsPerFrame => groups.OrderByDescending((ModGroupView group) => group.Summary.Avg1sMsPerFrame), TableSortColumn.AvgCallsPerFrame => groups.OrderByDescending((ModGroupView group) => group.Summary.Avg1sCallsPerFrame), TableSortColumn.RawMax => groups.OrderByDescending((ModGroupView group) => group.Summary.Max.RawMaxMs), TableSortColumn.SecondMax => groups.OrderByDescending((ModGroupView group) => group.Summary.Max.SecondMaxMs), TableSortColumn.ThirdMax => groups.OrderByDescending((ModGroupView group) => group.Summary.Max.ThirdMaxMs), TableSortColumn.AboveP99 => groups.OrderByDescending((ModGroupView group) => group.Summary.Max.AboveP99Count), TableSortColumn.AvgAboveP99 => groups.OrderByDescending((ModGroupView group) => group.Summary.Max.AvgAboveP99Ms), TableSortColumn.P99 => groups.OrderByDescending((ModGroupView group) => group.Summary.Max.P99Ms), TableSortColumn.AboveP95 => groups.OrderByDescending((ModGroupView group) => group.Summary.Max.AboveP95Count), TableSortColumn.AvgAboveP95 => groups.OrderByDescending((ModGroupView group) => group.Summary.Max.AvgAboveP95Ms), TableSortColumn.P95 => groups.OrderByDescending((ModGroupView group) => group.Summary.Max.P95Ms), TableSortColumn.Samples => groups.OrderByDescending((ModGroupView group) => group.Summary.Max.WindowSampleCount), TableSortColumn.GcSamples => groups.OrderByDescending((ModGroupView group) => group.Summary.Max.GcSampleCount), TableSortColumn.PatchType => groups.OrderBy((ModGroupView group) => GetGroupMinText(group, (FlatRowView row) => row.Context.PatchType), StringComparer.OrdinalIgnoreCase), TableSortColumn.Target => groups.OrderBy((ModGroupView group) => GetGroupMinText(group, (FlatRowView row) => row.Context.TargetDisplay), StringComparer.OrdinalIgnoreCase), TableSortColumn.PatchMethod => groups.OrderBy((ModGroupView group) => GetGroupMinText(group, (FlatRowView row) => GetMethodDisplay(row.InstrumentedMethod)), StringComparer.OrdinalIgnoreCase), TableSortColumn.CombinedMethod => groups.OrderBy((ModGroupView group) => GetGroupMinText(group, BuildCombinedMethodSortText), StringComparer.OrdinalIgnoreCase), _ => groups.OrderByDescending((ModGroupView group) => group.AggregateScore), }; if (1 == 0) { } IOrderedEnumerable source = orderedEnumerable; return source.ThenByDescending((ModGroupView group) => group.Summary.Max.RawMaxMs).ThenBy((ModGroupView group) => group.ModName, StringComparer.OrdinalIgnoreCase).ToList(); } private string GetRowModName(FlatRowView row) { return row?.Context?.ModName ?? GuessModName(row?.InstrumentedMethod, row?.Context?.ModGuid) ?? "Unknown"; } private static string BuildCombinedMethodSortText(FlatRowView row) { return (row?.Context?.TargetDisplay ?? string.Empty) + " | " + GetMethodDisplay(row?.InstrumentedMethod); } private static string GetGroupMinText(ModGroupView group, Func selector) { if (group?.Rows == null || group.Rows.Count == 0) { return string.Empty; } string text = selector(group.Rows[0]) ?? string.Empty; for (int i = 1; i < group.Rows.Count; i++) { string text2 = selector(group.Rows[i]) ?? string.Empty; if (string.Compare(text2, text, StringComparison.OrdinalIgnoreCase) < 0) { text = text2; } } return text; } private static bool IsTextSortColumn(TableSortColumn column) { if (column == TableSortColumn.Mod || (uint)(column - 14) <= 3u) { return true; } return false; } private double CalculateGroupScore(GroupSummary summary) { if (_profilerView == ProfilerView.Avg1s) { return summary.Avg1sMsPerFrame; } return summary.Max.ThirdMaxMs; } private GroupSummary BuildSummary(List rows) { GroupSummary result = default(GroupSummary); if (rows == null || rows.Count == 0) { return result; } if (_profilerView == ProfilerView.Avg1s) { result.Avg1sMsPerFrame = rows.Sum((FlatRowView row) => row.Snapshot.Avg1sMsPerFrame); result.Avg1sCallsPerFrame = rows.Sum((FlatRowView row) => row.Snapshot.Avg1sCallsPerFrame); result.Avg1sFrames = rows.Max((FlatRowView row) => row.Snapshot.Avg1sFrames); return result; } result.Max = MaxAnalyticsSnapshot.Aggregate(rows.Select((FlatRowView row) => row.Snapshot.MaxSnapshot)); return result; } private void DrawWindow(int id) { //IL_0008: 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_0014: Unknown result type (might be due to invalid IL or missing references) //IL_020d: Unknown result type (might be due to invalid IL or missing references) //IL_017d: Unknown result type (might be due to invalid IL or missing references) //IL_0197: Expected O, but got Unknown EnsureStyles(); Color contentColor = GUI.contentColor; GUI.contentColor = _theme.TextColor; try { GUILayout.BeginVertical(Array.Empty()); DrawToolbar(); GUILayout.Space(2f); GUILayout.BeginHorizontal(Array.Empty()); MainTab mainTab = (MainTab)GUILayout.Toolbar((int)_mainTab, new string[3] { "Profiler", "Mods to profile", "Help" }, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(390f) }); if (mainTab != _mainTab) { _mainTab = mainTab; if (_mainTab == MainTab.ModsToProfile && !_listReady) { RefreshPatchList(); } MarkViewDirty(); } if (_mainTab == MainTab.Profiler) { GUILayout.Space(10f); Label("View:", GUILayout.Width(40f)); ProfilerView profilerView = (ProfilerView)GUILayout.Toolbar((int)_profilerView, new string[2] { "Over 1 sec", "Max over 60 sec" }, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(290f) }); if (profilerView != _profilerView) { _profilerView = profilerView; MarkViewDirty(); } GUILayout.Space(10f); bool flag = ProfilerGui.ToggleLayout(_theme, _groupByMod, new GUIContent("Group by Mod", "Group measured entries by their BepInEx mod.\nTranspiled target entries are grouped under Transpiled methods."), 135f, _labelStyle); if (flag != _groupByMod) { _groupByMod = flag; MarkViewDirty(); } } GUILayout.EndHorizontal(); GUILayout.Space(3f); switch (_mainTab) { case MainTab.Profiler: DrawProfilerTab(); break; case MainTab.ModsToProfile: DrawModsTab(); break; default: DrawHelpTab(); break; } GUILayout.EndVertical(); } finally { GUI.contentColor = contentColor; } } private void ToggleTranspilerDetails(PatchContext ctx) { //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) if (ctx == null) { return; } bool flag = _selectedTranspilerDetailsContext == ctx; if (flag && _detailsWindow.RequestedVisible) { _detailsWindow.RequestedVisible = false; return; } if (!flag) { _transpilerDetailsScroll = Vector2.zero; } _selectedTranspilerDetailsContext = ctx; _selectedTranspilerDetailsTitle = ctx.TargetDisplay ?? GetMethodDisplay(ctx.InstrumentedMethod); _detailsWindow.RequestedVisible = true; _app.ShowUi(); _windows.BringToFront(_detailsWindow); } private void DrawTranspilerDetailsWindow(int id) { //IL_0008: 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_0014: Unknown result type (might be due to invalid IL or missing references) //IL_0758: Unknown result type (might be due to invalid IL or missing references) //IL_021f: Unknown result type (might be due to invalid IL or missing references) //IL_0229: Unknown result type (might be due to invalid IL or missing references) //IL_022e: Unknown result type (might be due to invalid IL or missing references) EnsureStyles(); Color contentColor = GUI.contentColor; GUI.contentColor = _theme.TextColor; try { GUILayout.BeginVertical(Array.Empty()); GUILayout.BeginHorizontal(Array.Empty()); HeaderLabel(_selectedTranspilerDetailsTitle, GUILayout.ExpandWidth(true)); if (GUILayout.Button("Close", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(70f) })) { _detailsWindow.RequestedVisible = false; } GUILayout.EndHorizontal(); GUILayout.Space(4f); PatchContext selectedTranspilerDetailsContext = _selectedTranspilerDetailsContext; if (selectedTranspilerDetailsContext == null) { Label("No transpiler details are available for this row."); GUILayout.EndVertical(); return; } int displayFrame = DisplayFrame; float displayRealtime = DisplayRealtime; List<(PatchContext, PatchStatSnapshot)> list = new List<(PatchContext, PatchStatSnapshot)>(); lock (_lock) { foreach (PatchContext value2 in _context.Values) { if (value2 != null && value2.IsTranspilerCallEntry && string.Equals(GetMethodIdentity(value2.TranspiledTargetMethod), GetMethodIdentity(selectedTranspilerDetailsContext.InstrumentedMethod), StringComparison.Ordinal) && _stats.TryGetValue(value2.EntryId, out var value)) { list.Add((value2, value.GetSnapshot(displayFrame, displayRealtime))); } } } int num = list.Count<(PatchContext, PatchStatSnapshot)>(((PatchContext Context, PatchStatSnapshot Snapshot) tuple) => tuple.Snapshot.Avg1sFrames > 0 || tuple.Snapshot.MaxSnapshot.WindowSampleCount > 0); Label($"Transpilers: {selectedTranspilerDetailsContext.TranspilerDetails.Count} | Detected runtime calls: {list.Count} | Sampled: {num}"); Label("Detection is static. A call with 0 samples has not been observed since profiling started; only sampled entries appear in the main table."); GUILayout.Space(3f); _transpilerDetailsScroll = GUILayout.BeginScrollView(_transpilerDetailsScroll, Array.Empty()); HeaderLabel("Transpilers"); if (selectedTranspilerDetailsContext.TranspilerDetails.Count == 0) { Label("No transpiler registrations were retained for this target."); } else { foreach (TranspilerDetail transpilerDetail in selectedTranspilerDetailsContext.TranspilerDetails) { GUILayout.BeginVertical(GUI.skin.box, Array.Empty()); HeaderLabel(transpilerDetail.ModName ?? "Unknown"); Label("GUID: " + (transpilerDetail.ModGuid ?? "(unknown guid)")); Label("Owner: " + (transpilerDetail.OwnerHarmonyId ?? "(no owner)")); Label("Transpiler: " + (transpilerDetail.TranspilerMethodDisplay ?? "(unknown transpiler)")); GUILayout.EndVertical(); GUILayout.Space(2f); } } GUILayout.Space(5f); HeaderLabel("Detected calls injected by transpilers"); Label("These rows are net new direct call/callvirt instructions found by comparing the final transpiled target IL with its original IL."); Label("Timing is injected around the exact detected direct call sites in this transpiled target and updates live on the main thread."); GUILayout.Space(2f); if (list.Count == 0) { Label("No profileable net-new direct calls were detected in the final transpiled target IL."); } else { foreach (var item3 in list.OrderByDescending<(PatchContext, PatchStatSnapshot), double>(((PatchContext Context, PatchStatSnapshot Snapshot) tuple) => tuple.Snapshot.MaxSnapshot.RawMaxMs).ThenBy<(PatchContext, PatchStatSnapshot), string>(((PatchContext Context, PatchStatSnapshot Snapshot) tuple) => GetMethodDisplay(tuple.Context.InstrumentedMethod), StringComparer.OrdinalIgnoreCase)) { PatchContext item = item3.Item1; PatchStatSnapshot item2 = item3.Item2; MaxAnalyticsSnapshot maxSnapshot = item2.MaxSnapshot; GUILayout.BeginVertical(GUI.skin.box, Array.Empty()); HeaderLabel(GetMethodDisplay(item.InstrumentedMethod)); Label("Mod: " + (item.ModName ?? "Unknown") + " | GUID: " + (item.ModGuid ?? "(unknown guid)")); Label("Transpiled target: " + GetMethodDisplay(item.TranspiledTargetMethod)); Label("Net added call sites: " + Math.Max(1, item.InjectedCallSiteCount)); if (item.TranspilerDetails.Count == 0) { Label("Source transpilers: attribution unavailable"); } else { Label("Source transpilers:"); foreach (TranspilerDetail transpilerDetail2 in item.TranspilerDetails) { Label(" " + (transpilerDetail2.ModName ?? "Unknown") + " | " + (transpilerDetail2.TranspilerMethodDisplay ?? "(unknown transpiler)")); } } if (item2.Avg1sFrames == 0 && maxSnapshot.WindowSampleCount == 0) { Label("Status: no runtime samples yet. The target or this branch has not executed since profiling started."); } Label("Over 1 sec: " + FormatMs(item2.Avg1sMsPerFrame) + " ms/frame | " + FormatCount(item2.Avg1sCallsPerFrame) + " calls/frame"); Label("Max over 60 sec: raw " + FormatMs(maxSnapshot.RawMaxMs) + " | 2nd " + FormatMs(maxSnapshot.SecondMaxMs) + " | 3rd " + FormatMs(maxSnapshot.ThirdMaxMs) + " | " + $">p99 {maxSnapshot.AboveP99Count} avg {FormatMs(maxSnapshot.AvgAboveP99Ms)} | p99 {FormatMs(maxSnapshot.P99Ms)} | " + $">p95 {maxSnapshot.AboveP95Count} avg {FormatMs(maxSnapshot.AvgAboveP95Ms)} | p95 {FormatMs(maxSnapshot.P95Ms)} | samples {maxSnapshot.WindowSampleCount}"); GUILayout.EndVertical(); GUILayout.Space(2f); } } GUILayout.EndScrollView(); GUILayout.EndVertical(); } finally { GUI.contentColor = contentColor; } } private void EnsureStyles() { //IL_0045: Unknown result type (might be due to invalid IL or missing references) //IL_004a: Unknown result type (might be due to invalid IL or missing references) //IL_0052: Unknown result type (might be due to invalid IL or missing references) //IL_005a: Unknown result type (might be due to invalid IL or missing references) //IL_0067: Expected O, but got Unknown //IL_0078: Unknown result type (might be due to invalid IL or missing references) //IL_008e: Unknown result type (might be due to invalid IL or missing references) //IL_0093: Unknown result type (might be due to invalid IL or missing references) //IL_009b: Unknown result type (might be due to invalid IL or missing references) //IL_00a3: Unknown result type (might be due to invalid IL or missing references) //IL_00ab: Unknown result type (might be due to invalid IL or missing references) //IL_00b8: Expected O, but got Unknown //IL_00c9: Unknown result type (might be due to invalid IL or missing references) //IL_00db: Unknown result type (might be due to invalid IL or missing references) //IL_00e5: Expected O, but got Unknown //IL_00eb: Unknown result type (might be due to invalid IL or missing references) //IL_00f6: Unknown result type (might be due to invalid IL or missing references) //IL_0100: Unknown result type (might be due to invalid IL or missing references) //IL_0105: Unknown result type (might be due to invalid IL or missing references) //IL_0111: Unknown result type (might be due to invalid IL or missing references) //IL_0123: Unknown result type (might be due to invalid IL or missing references) //IL_0135: Unknown result type (might be due to invalid IL or missing references) //IL_0147: Unknown result type (might be due to invalid IL or missing references) //IL_0159: Unknown result type (might be due to invalid IL or missing references) //IL_016b: Unknown result type (might be due to invalid IL or missing references) //IL_017d: Unknown result type (might be due to invalid IL or missing references) //IL_018f: Unknown result type (might be due to invalid IL or missing references) //IL_01a1: Unknown result type (might be due to invalid IL or missing references) //IL_01a6: Unknown result type (might be due to invalid IL or missing references) //IL_01ae: Unknown result type (might be due to invalid IL or missing references) //IL_01b6: Unknown result type (might be due to invalid IL or missing references) //IL_01be: Unknown result type (might be due to invalid IL or missing references) //IL_01cb: Expected O, but got Unknown //IL_01dc: Unknown result type (might be due to invalid IL or missing references) //IL_01f2: Unknown result type (might be due to invalid IL or missing references) //IL_01f7: Unknown result type (might be due to invalid IL or missing references) //IL_01fc: Unknown result type (might be due to invalid IL or missing references) //IL_0206: Expected O, but got Unknown //IL_0207: Unknown result type (might be due to invalid IL or missing references) //IL_020c: Unknown result type (might be due to invalid IL or missing references) //IL_0216: Expected O, but got Unknown //IL_021c: Expected O, but got Unknown if (!((Object)(object)_styleSkin == (Object)(object)GUI.skin) || _labelStyle == null) { _styleSkin = GUI.skin; _layoutMetricsDirty = true; _labelStyle = new GUIStyle(GUI.skin.label) { wordWrap = false, clipping = (TextClipping)1, alignment = (TextAnchor)0 }; _labelStyle.normal.textColor = _theme.TextColor; _headerLabelStyle = new GUIStyle(GUI.skin.label) { wordWrap = false, clipping = (TextClipping)1, alignment = (TextAnchor)0, fontStyle = (FontStyle)1 }; _headerLabelStyle.normal.textColor = _theme.HeaderTextColor; _activeHeaderLabelStyle = new GUIStyle(_headerLabelStyle); Color textColor = Color.Lerp(_theme.HeaderTextColor, _theme.AccentColor, 0.5f); _activeHeaderLabelStyle.normal.textColor = textColor; _activeHeaderLabelStyle.hover.textColor = textColor; _activeHeaderLabelStyle.active.textColor = textColor; _activeHeaderLabelStyle.focused.textColor = textColor; _activeHeaderLabelStyle.onNormal.textColor = textColor; _activeHeaderLabelStyle.onHover.textColor = textColor; _activeHeaderLabelStyle.onActive.textColor = textColor; _activeHeaderLabelStyle.onFocused.textColor = textColor; _groupLabelStyle = new GUIStyle(GUI.skin.label) { wordWrap = false, clipping = (TextClipping)1, alignment = (TextAnchor)3, fontStyle = (FontStyle)1 }; _groupLabelStyle.normal.textColor = _theme.HeaderTextColor; _compactButtonStyle = new GUIStyle(GUI.skin.button) { margin = new RectOffset(1, 1, 1, 1), padding = new RectOffset(2, 2, 0, 0) }; } } private void Label(string text, params GUILayoutOption[] options) { GUILayout.Label(text ?? string.Empty, _labelStyle, options); } private void HeaderLabel(string text, params GUILayoutOption[] options) { GUILayout.Label(text ?? string.Empty, _headerLabelStyle, options); } private void GroupLabel(string text, params GUILayoutOption[] options) { GUILayout.Label(text ?? string.Empty, _groupLabelStyle, options); } private void DrawToolbar() { GUILayout.BeginHorizontal(Array.Empty()); string text = (_listReady ? $"List: ready ({_stats.Count})" : "List: empty"); string text2 = (_profilingActive ? "Profiling: ON" : (_statsFrozen ? "Profiling: PAUSED" : "Profiling: OFF")); Label(text + " | " + text2 + " | Status: " + _status, GUILayout.ExpandWidth(true)); string text3 = (_profilingActive ? "Pause profiling" : "Start profiling"); if (GUILayout.Button(text3, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(125f) })) { if (_profilingActive) { StopProfiling(); } else { StartProfiling(); } } if (GUILayout.Button("Reset stats", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(100f) })) { lock (_lock) { foreach (PatchStat value in _stats.Values) { value.Reset(); } } ClearAnalyticsQueues(); _statsFrozen = false; MarkViewDirty(); } if (GUILayout.Button("Close", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(70f) })) { IsWindowVisible = false; } GUILayout.EndHorizontal(); } private List FindInjectedRuntimeCalls(MethodBase target, IReadOnlyList transpilers) { List list = new List(); if (target == null || transpilers == null || transpilers.Count == 0) { return list; } List originalInstructions; List currentInstructions; try { originalInstructions = PatchProcessor.GetOriginalInstructions(target, (ILGenerator)null); currentInstructions = PatchProcessor.GetCurrentInstructions(target, int.MaxValue, (ILGenerator)null); } catch (Exception ex) { _logger.LogDebug((object)("Could not compare transpiled IL for " + GetMethodDisplay(target) + ": " + ex.GetType().Name + ": " + ex.Message)); return list; } List directRuntimeCalls = GetDirectRuntimeCalls(originalInstructions); List directRuntimeCalls2 = GetDirectRuntimeCalls(currentInstructions); bool[] array = FindAddedFinalCalls(directRuntimeCalls, directRuntimeCalls2); Dictionary dictionary = new Dictionary(StringComparer.Ordinal); for (int i = 0; i < directRuntimeCalls2.Count; i++) { if (!array[i]) { continue; } DirectCallSite directCallSite = directRuntimeCalls2[i]; MethodBase method = directCallSite.Method; if (IsProfileableInjectedRuntimeCall(method, target)) { if (!dictionary.TryGetValue(directCallSite.Identity, out var value)) { value = new InjectedRuntimeCall { CalledMethod = method }; dictionary[directCallSite.Identity] = value; } value.AddedCallSites++; value.FinalMethodOrdinals.Add(directCallSite.MethodOrdinal); } } foreach (InjectedRuntimeCall value2 in dictionary.Values) { foreach (Patch item in ResolveLikelySourceTranspilers(value2.CalledMethod, target, transpilers)) { value2.SourcePatches.Add(item); } list.Add(value2); } return list.OrderBy((InjectedRuntimeCall call) => GetMethodDisplay(call.CalledMethod), StringComparer.OrdinalIgnoreCase).ToList(); } private static List GetDirectRuntimeCalls(IEnumerable instructions) { List list = new List(); Dictionary dictionary = new Dictionary(StringComparer.Ordinal); if (instructions == null) { return list; } foreach (CodeInstruction instruction in instructions) { if (instruction != null && (!(instruction.opcode != OpCodes.Call) || !(instruction.opcode != OpCodes.Callvirt)) && instruction.operand is MethodBase method) { string methodIdentity = GetMethodIdentity(method); dictionary.TryGetValue(methodIdentity, out var value); dictionary[methodIdentity] = value + 1; list.Add(new DirectCallSite { Method = method, Identity = methodIdentity, MethodOrdinal = value }); } } return list; } private static bool[] FindAddedFinalCalls(IReadOnlyList originalCalls, IReadOnlyList finalCalls) { bool[] array = new bool[finalCalls.Count]; if (finalCalls.Count == 0) { return array; } if (originalCalls.Count == 0) { for (int i = 0; i < array.Length; i++) { array[i] = true; } return array; } long num = (long)(originalCalls.Count + 1) * (long)(finalCalls.Count + 1); if (num <= 2000000) { return FindAddedFinalCallsWithLcs(originalCalls, finalCalls); } Dictionary dictionary = new Dictionary(StringComparer.Ordinal); for (int j = 0; j < originalCalls.Count; j++) { string identity = originalCalls[j].Identity; dictionary.TryGetValue(identity, out var value); dictionary[identity] = value + 1; } for (int k = 0; k < finalCalls.Count; k++) { string identity2 = finalCalls[k].Identity; if (dictionary.TryGetValue(identity2, out var value2) && value2 > 0) { dictionary[identity2] = value2 - 1; } else { array[k] = true; } } return array; } private static bool[] FindAddedFinalCallsWithLcs(IReadOnlyList originalCalls, IReadOnlyList finalCalls) { int count = originalCalls.Count; int count2 = finalCalls.Count; int[,] array = new int[count + 1, count2 + 1]; for (int num = count - 1; num >= 0; num--) { string identity = originalCalls[num].Identity; for (int num2 = count2 - 1; num2 >= 0; num2--) { if (string.Equals(identity, finalCalls[num2].Identity, StringComparison.Ordinal)) { array[num, num2] = array[num + 1, num2 + 1] + 1; } else { array[num, num2] = Math.Max(array[num + 1, num2], array[num, num2 + 1]); } } } bool[] array2 = new bool[count2]; int num3 = 0; int num4 = 0; while (num3 < count && num4 < count2) { if (string.Equals(originalCalls[num3].Identity, finalCalls[num4].Identity, StringComparison.Ordinal)) { array2[num4] = true; num3++; num4++; } else if (array[num3 + 1, num4] >= array[num3, num4 + 1]) { num3++; } else { num4++; } } bool[] array3 = new bool[count2]; for (int i = 0; i < count2; i++) { array3[i] = !array2[i]; } return array3; } private bool IsProfileableInjectedRuntimeCall(MethodBase calledMethod, MethodBase target) { if (calledMethod == null || target == null) { return false; } if (GetMethodIdentity(calledMethod) == GetMethodIdentity(target)) { return false; } if (!(calledMethod is MethodInfo methodInfo)) { return false; } if (calledMethod.DeclaringType == null) { return false; } if (calledMethod.DeclaringType.Assembly == typeof(ValheimProfilerPlugin).Assembly) { return false; } if (calledMethod.ContainsGenericParameters || calledMethod.IsAbstract) { return false; } string text = calledMethod.DeclaringType.FullName ?? string.Empty; if (text.StartsWith("HarmonyLib.", StringComparison.Ordinal) || text.StartsWith("System.", StringComparison.Ordinal) || text.StartsWith("Microsoft.", StringComparison.Ordinal) || text.StartsWith("UnityEngine.", StringComparison.Ordinal)) { return false; } try { if (methodInfo.GetMethodBody() == null) { return false; } } catch { return false; } return true; } private List ResolveLikelySourceTranspilers(MethodBase calledMethod, MethodBase target, IReadOnlyList transpilers) { List list = transpilers.Where((Patch patch) => ((patch != null) ? patch.PatchMethod : null) != null && patch.PatchMethod.DeclaringType?.Assembly != typeof(ValheimProfilerPlugin).Assembly).ToList(); if (list.Count <= 1) { return list; } Assembly calledAssembly = calledMethod?.DeclaringType?.Assembly; if (calledAssembly != null) { List list2 = list.Where((Patch patch) => patch.PatchMethod.DeclaringType?.Assembly == calledAssembly).ToList(); if (list2.Count > 0) { return list2; } } string calledGuid = GuessModGuid(calledMethod, null); List list3 = list.Where((Patch patch) => string.Equals(GuessModGuid(patch.PatchMethod, patch.owner), calledGuid, StringComparison.Ordinal)).ToList(); if (list3.Count > 0) { return list3; } return list; } private static bool IsCallSiteTimingSafe(MethodBase calledMethod) { if (!(calledMethod is MethodInfo methodInfo)) { return false; } Type returnType = methodInfo.ReturnType; return returnType != null && !returnType.IsByRef && !returnType.IsPointer && returnType != typeof(TypedReference) && (methodInfo.CallingConvention & CallingConventions.VarArgs) == 0; } private static IEnumerable InjectedCallTimingTranspiler(IEnumerable instructions, MethodBase __originalMethod) { List list = instructions?.ToList() ?? new List(); PatchProfilerTool instance = _instance; if (instance == null || __originalMethod == null) { return list; } Dictionary runtimeTranspilerCallPlans = instance._runtimeTranspilerCallPlans; if (runtimeTranspilerCallPlans == null || !runtimeTranspilerCallPlans.TryGetValue(__originalMethod, out var value) || value == null || value.Length == 0) { return list; } try { return InjectCallSiteTiming(list, value, instance); } catch (Exception ex) { instance._logger.LogDebug((object)("Could not inject exact transpiler-call timing into " + GetMethodDisplay(__originalMethod) + ": " + ex.GetType().Name + ": " + ex.Message)); return list; } } private static List InjectCallSiteTiming(List source, IReadOnlyList plans, PatchProfilerTool instance) { //IL_02c1: Unknown result type (might be due to invalid IL or missing references) //IL_02c6: Unknown result type (might be due to invalid IL or missing references) //IL_02d1: Unknown result type (might be due to invalid IL or missing references) //IL_02db: Expected O, but got Unknown Dictionary dictionary = plans.Where((RuntimeTranspilerCallPlan plan) => plan != null && plan.EntryId >= 0 && !string.IsNullOrWhiteSpace(plan.CalledMethodIdentity) && plan.MethodOrdinals != null && plan.MethodOrdinals.Count > 0).GroupBy((RuntimeTranspilerCallPlan plan) => plan.CalledMethodIdentity, StringComparer.Ordinal).ToDictionary, string, RuntimeTranspilerCallPlan[]>((IGrouping group) => group.Key, (IGrouping group) => group.ToArray(), StringComparer.Ordinal); if (dictionary.Count == 0) { return source; } Dictionary dictionary2 = new Dictionary(StringComparer.Ordinal); Dictionary> dictionary3 = new Dictionary>(); for (int num = 0; num < source.Count; num++) { CodeInstruction val = source[num]; if (val == null || (val.opcode != OpCodes.Call && val.opcode != OpCodes.Callvirt) || !(val.operand is MethodBase method)) { continue; } string methodIdentity = GetMethodIdentity(method); dictionary2.TryGetValue(methodIdentity, out var value); dictionary2[methodIdentity] = value + 1; if (!dictionary.TryGetValue(methodIdentity, out var value2)) { continue; } foreach (RuntimeTranspilerCallPlan runtimeTranspilerCallPlan in value2) { if (runtimeTranspilerCallPlan.MethodOrdinals.Contains(value)) { if (!dictionary3.TryGetValue(num, out var value3)) { value3 = (dictionary3[num] = new List(1)); } if (!value3.Contains(runtimeTranspilerCallPlan.EntryId)) { value3.Add(runtimeTranspilerCallPlan.EntryId); } } } } foreach (KeyValuePair> item in dictionary3) { int key = item.Key; CodeInstruction val2 = source[key]; if (val2?.operand is MethodInfo calledMethod && !HasUnsupportedCallPrefix(source, key)) { int[] entryIds = (from result in item.Value.Distinct() orderby result select result).ToArray(); MethodInfo orCreateTranspilerCallWrapper = instance.GetOrCreateTranspilerCallWrapper(calledMethod, val2.opcode, entryIds); if (!(orCreateTranspilerCallWrapper == null)) { CodeInstruction value4 = new CodeInstruction(val2) { opcode = OpCodes.Call, operand = orCreateTranspilerCallWrapper }; source[key] = value4; } } } return source; } private MethodInfo GetOrCreateTranspilerCallWrapper(MethodInfo calledMethod, OpCode originalCallOpcode, IReadOnlyList entryIds) { if (calledMethod == null || entryIds == null || entryIds.Count == 0) { return null; } if (!IsCallSiteTimingSafe(calledMethod)) { return null; } string key = GetMethodIdentity(calledMethod) + "|" + originalCallOpcode.Value + "|" + string.Join(",", entryIds); lock (_transpilerCallWrapperLock) { if (_transpilerCallWrappers.TryGetValue(key, out var value)) { return value; } try { MethodInfo methodInfo = CreateTranspilerCallWrapper(calledMethod, originalCallOpcode, entryIds); if (methodInfo != null) { _transpilerCallWrappers[key] = methodInfo; } return methodInfo; } catch (Exception ex) { _logger.LogDebug((object)("Could not create transpiler-call wrapper for " + GetMethodDisplay(calledMethod) + ": " + ex.GetType().Name + ": " + ex.Message)); return null; } } } private static MethodInfo CreateTranspilerCallWrapper(MethodInfo calledMethod, OpCode originalCallOpcode, IReadOnlyList entryIds) { if (calledMethod == null || calledMethod.DeclaringType == null) { return null; } if (calledMethod.ContainsGenericParameters || calledMethod.DeclaringType.ContainsGenericParameters) { return null; } ParameterInfo[] parameters = calledMethod.GetParameters(); int num = ((!calledMethod.IsStatic) ? 1 : 0); Type[] array = new Type[parameters.Length + num]; if (!calledMethod.IsStatic) { Type declaringType = calledMethod.DeclaringType; array[0] = (declaringType.IsValueType ? declaringType.MakeByRefType() : declaringType); } for (int i = 0; i < parameters.Length; i++) { array[i + num] = parameters[i].ParameterType; } string name = "VP_TranspilerCall_" + Interlocked.Increment(ref _nextTranspilerCallWrapperId); DynamicMethod dynamicMethod = new DynamicMethod(name, calledMethod.ReturnType, array, calledMethod.Module, skipVisibility: true); ILGenerator iLGenerator = dynamicMethod.GetILGenerator(); MethodInfo methodInfo = AccessTools.Method(typeof(PatchProfilerTool), "BeginInjectedTranspilerCall", (Type[])null, (Type[])null); MethodInfo methodInfo2 = AccessTools.Method(typeof(PatchProfilerTool), "EndInjectedTranspilerCall", (Type[])null, (Type[])null); if (methodInfo == null || methodInfo2 == null) { return null; } for (int j = 0; j < entryIds.Count; j++) { EmitLoadInt32(iLGenerator, entryIds[j]); iLGenerator.Emit(OpCodes.Call, methodInfo); } LocalBuilder localBuilder = ((calledMethod.ReturnType == typeof(void)) ? null : iLGenerator.DeclareLocal(calledMethod.ReturnType)); Label label = iLGenerator.BeginExceptionBlock(); for (int k = 0; k < array.Length; k++) { EmitLoadArgument(iLGenerator, k); } iLGenerator.Emit(originalCallOpcode, calledMethod); if (localBuilder != null) { iLGenerator.Emit(OpCodes.Stloc, localBuilder); } iLGenerator.Emit(OpCodes.Leave, label); iLGenerator.BeginFinallyBlock(); for (int num2 = entryIds.Count - 1; num2 >= 0; num2--) { EmitLoadInt32(iLGenerator, entryIds[num2]); iLGenerator.Emit(OpCodes.Call, methodInfo2); } iLGenerator.EndExceptionBlock(); if (localBuilder != null) { iLGenerator.Emit(OpCodes.Ldloc, localBuilder); } iLGenerator.Emit(OpCodes.Ret); return dynamicMethod; } private static void EmitLoadArgument(ILGenerator il, int index) { switch (index) { case 0: il.Emit(OpCodes.Ldarg_0); return; case 1: il.Emit(OpCodes.Ldarg_1); return; case 2: il.Emit(OpCodes.Ldarg_2); return; case 3: il.Emit(OpCodes.Ldarg_3); return; } if (index <= 255) { il.Emit(OpCodes.Ldarg_S, (byte)index); } else { il.Emit(OpCodes.Ldarg, (short)index); } } private static void EmitLoadInt32(ILGenerator il, int value) { switch (value) { case -1: il.Emit(OpCodes.Ldc_I4_M1); return; case 0: il.Emit(OpCodes.Ldc_I4_0); return; case 1: il.Emit(OpCodes.Ldc_I4_1); return; case 2: il.Emit(OpCodes.Ldc_I4_2); return; case 3: il.Emit(OpCodes.Ldc_I4_3); return; case 4: il.Emit(OpCodes.Ldc_I4_4); return; case 5: il.Emit(OpCodes.Ldc_I4_5); return; case 6: il.Emit(OpCodes.Ldc_I4_6); return; case 7: il.Emit(OpCodes.Ldc_I4_7); return; case 8: il.Emit(OpCodes.Ldc_I4_8); return; } if (value >= -128 && value <= 127) { il.Emit(OpCodes.Ldc_I4_S, (sbyte)value); } else { il.Emit(OpCodes.Ldc_I4, value); } } private static bool HasUnsupportedCallPrefix(IReadOnlyList instructions, int callIndex) { int num = callIndex - 1; while (num >= 0 && IsCallPrefix(instructions[num]?.opcode ?? default(OpCode))) { OpCode opcode = instructions[num].opcode; if (opcode == OpCodes.Constrained || opcode == OpCodes.Tailcall) { return true; } num--; } return false; } private static bool IsCallPrefix(OpCode opcode) { return opcode == OpCodes.Constrained || opcode == OpCodes.Tailcall || opcode == OpCodes.Readonly || opcode == OpCodes.Unaligned || opcode == OpCodes.Volatile; } private static void BeginInjectedTranspilerCall(int entryId) { try { if (Thread.CurrentThread.ManagedThreadId != _mainThreadId) { return; } PatchProfilerTool instance = _instance; if (instance == null || !instance._profilingActive) { return; } Dictionary runtimeTranspilerCallStats = instance._runtimeTranspilerCallStats; if (runtimeTranspilerCallStats != null && runtimeTranspilerCallStats.TryGetValue(entryId, out var value) && value != null) { if (_activeTranspilerCallTimings == null) { _activeTranspilerCallTimings = new List(8); } _activeTranspilerCallTimings.Add(new ActiveTranspilerCallTiming { EntryId = entryId, Stat = value, StartTicks = Stopwatch.GetTimestamp() }); } } catch { } } private static void EndInjectedTranspilerCall(int entryId) { try { if (Thread.CurrentThread.ManagedThreadId != _mainThreadId) { return; } List activeTranspilerCallTimings = _activeTranspilerCallTimings; if (activeTranspilerCallTimings == null || activeTranspilerCallTimings.Count == 0) { return; } int num = activeTranspilerCallTimings.Count - 1; while (num >= 0 && activeTranspilerCallTimings[num].EntryId != entryId) { num--; } if (num < 0) { return; } ActiveTranspilerCallTiming activeTranspilerCallTiming = activeTranspilerCallTimings[num]; activeTranspilerCallTimings.RemoveAt(num); PatchProfilerTool instance = _instance; if (instance == null || !instance._profilingActive || activeTranspilerCallTiming.Stat == null || activeTranspilerCallTiming.StartTicks <= 0) { return; } long num2 = Stopwatch.GetTimestamp() - activeTranspilerCallTiming.StartTicks; if (num2 >= 0) { double num3 = (double)num2 * MsPerTick; bool gcSample = false; if (num3 >= 1.0) { gcSample = GC.CollectionCount(0) != instance._lastObservedGc0 || GC.CollectionCount(1) != instance._lastObservedGc1 || GC.CollectionCount(2) != instance._lastObservedGc2; } int frameCount = Time.frameCount; float realtimeSinceStartup = Time.realtimeSinceStartup; instance.SetCurrentRealtime(realtimeSinceStartup); if (activeTranspilerCallTiming.Stat.Add(num3, frameCount, realtimeSinceStartup, gcSample)) { instance.QueueAnalyticsWork(activeTranspilerCallTiming.Stat); } } } catch { } } private static int CurrentInjectedTranspilerCallDepth() { return _activeTranspilerCallTimings?.Count ?? 0; } private static void TrimInjectedTranspilerCallStack(int depth) { if (depth >= 0) { List activeTranspilerCallTimings = _activeTranspilerCallTimings; if (activeTranspilerCallTimings != null && activeTranspilerCallTimings.Count > depth) { activeTranspilerCallTimings.RemoveRange(depth, activeTranspilerCallTimings.Count - depth); } } } } } namespace ValheimProfiler.Tools.NetworkProfiler { internal sealed class NetworkProfilerTool : IProfilerTool, IProfilerToolAvailability { private enum AvailabilityState { NoDedicatedConnection, Detecting, ServerModUnavailable, AccessDenied, Available, ProtocolMismatch } private enum MainTab { Rpc, Zdo, Peers, RoutingErrors, Help } private enum RpcSortColumn { Layer, Name, IncomingCalls, LocalCalls, OutgoingCalls, IncomingBytes, LocalBytes, OutgoingBytes, PhysicalBytes, PhysicalSends, HandlerMs, AverageHandlerMs, MaxHandlerMs, MaxPayload, MaxCallsFrame, Errors } private enum ZdoPrefabSortColumn { Prefab, Mutations, UniqueChanged, SentCount, SentBytes, ReceivedCount, ReceivedBytes, SerializeMs, DeserializeMs, AverageSize, MaxSize, Creates, Destroys, Ownership } private enum ZdoInstanceSortColumn { ZdoId, Prefab, Owner, Mutations, SentCount, SentBytes, ReceivedCount, ReceivedBytes, MaxSize, Traffic } private enum ZdoKeySortColumn { Prefab, Key, Type, Mutations, Affected } private enum PeerSortColumn { Peer, Socket, Ready, Ping, Out, In, SerializedOut, SerializedIn, SendQueue, MaxQueue, SendRate, InFlight, SendZdoMs, SyncMs, Sent, Candidates, Selected } private enum RoutingErrorSortColumn { Count, Kind, Rpc, Peer, Component, Caller, Details } internal const string ToolId = "NetworkProfiler"; internal const string DisplayTitle = "Network Profiler"; private const float ProbeTimeoutSeconds = 3f; private const float ProbeRetrySeconds = 10f; private const float RequestTimeoutSeconds = 6f; private readonly ValheimProfilerApp _app; private readonly WindowManager _windows; private readonly ThemeManager _theme; private readonly ProfilerWindow _window; private readonly List _rpcRows = new List(); private readonly List _zdoRows = new List(); private readonly List _zdoInstanceRows = new List(); private readonly List _zdoKeyRows = new List(); private readonly List _peerRows = new List(); private readonly List _errorRows = new List(); private readonly List _compatibilityWarnings = new List(); private AvailabilityState _availability = AvailabilityState.NoDedicatedConnection; private string _status = "Connect to a headless dedicated server to use Network Profiler."; private string _serverVersion = string.Empty; private string _sessionId = string.Empty; private string _compatibilitySummary = string.Empty; private bool _authorized; private bool _subscribed; private bool _probePending; private bool _requestPending; private float _probeDeadline; private float _requestDeadline; private float _nextProbe; private long _snapshotSequence; private ZRoutedRpc _networkRpc; private MainTab _tab; private int _zdoView; private string _search = string.Empty; private Vector2 _scroll; private Vector2 _helpScroll; private GUIStyle _labelStyle; private GUIStyle _headerStyle; private GUIStyle _activeHeaderStyle; private GUIStyle _detailsStyle; private GUISkin _styleSkin; private RpcSortColumn _rpcSortColumn; private ZdoPrefabSortColumn _zdoPrefabSortColumn; private ZdoInstanceSortColumn _zdoInstanceSortColumn; private ZdoKeySortColumn _zdoKeySortColumn; private PeerSortColumn _peerSortColumn; private RoutingErrorSortColumn _routingErrorSortColumn; string IProfilerTool.Id => "NetworkProfiler"; string IProfilerTool.DisplayName => "Network"; bool IProfilerTool.IsWindowVisible => IsWindowVisible; bool IProfilerTool.IsActive => _subscribed; bool IProfilerToolAvailability.IsAvailable => IsAvailable; bool IProfilerToolAvailability.CanOpenWhenUnavailable => true; string IProfilerToolAvailability.AvailabilityTooltip => AvailabilityTooltip; internal bool IsWindowVisible { get { return _window.RequestedVisible; } set { _window.RequestedVisible = value; } } internal bool IsAvailable => _availability == AvailabilityState.Available; internal string AvailabilityTooltip => _status + "\nRequires Valheim Profiler on a headless dedicated server and server admin access."; private bool HasDataRows => _rpcRows.Count > 0 || _zdoRows.Count > 0 || _zdoInstanceRows.Count > 0 || _zdoKeyRows.Count > 0 || _peerRows.Count > 0 || _errorRows.Count > 0; internal NetworkProfilerTool(ValheimProfilerApp app) { //IL_0186: Unknown result type (might be due to invalid IL or missing references) //IL_0187: Unknown result type (might be due to invalid IL or missing references) //IL_018c: Unknown result type (might be due to invalid IL or missing references) //IL_019e: Unknown result type (might be due to invalid IL or missing references) //IL_01a3: Unknown result type (might be due to invalid IL or missing references) //IL_01a4: Unknown result type (might be due to invalid IL or missing references) //IL_01a9: Unknown result type (might be due to invalid IL or missing references) _app = app ?? throw new ArgumentNullException("app"); _windows = app.Windows; _theme = app.Theme; _rpcSortColumn = ParseSort(app.Config.NetworkRpcSortColumn.Value, RpcSortColumn.HandlerMs); _zdoPrefabSortColumn = ParseSort(app.Config.NetworkZdoPrefabSortColumn.Value, ZdoPrefabSortColumn.SentBytes); _zdoInstanceSortColumn = ParseSort(app.Config.NetworkZdoInstanceSortColumn.Value, ZdoInstanceSortColumn.SentBytes); _zdoKeySortColumn = ParseSort(app.Config.NetworkZdoKeySortColumn.Value, ZdoKeySortColumn.Mutations); _peerSortColumn = ParseSort(app.Config.NetworkPeerSortColumn.Value, PeerSortColumn.SendQueue); _routingErrorSortColumn = ParseSort(app.Config.NetworkRoutingErrorSortColumn.Value, RoutingErrorSortColumn.Count); Vector2 minimumSize = default(Vector2); ((Vector2)(ref minimumSize))..ctor(900f, 500f); Vector2 defaultToolWindowSize = _windows.GetDefaultToolWindowSize(700f, minimumSize); _window = _windows.Register(new ProfilerWindow("ValheimProfiler.NetworkProfiler", "Network Profiler", new Rect(ValheimProfilerConfig.DefaultNetworkProfilerWindowPosition, defaultToolWindowSize), minimumSize, resizable: true, requestedVisible: false, DrawWindow, app.Config.NetworkProfilerWindowPosition, app.Config.NetworkProfilerWindowSize)); } void IProfilerTool.ShowWindow() { ShowWindow(); } void IProfilerTool.ToggleWindow() { ToggleWindow(); } void IProfilerTool.Update() { Update(); } void IProfilerTool.Shutdown() { Shutdown(); } internal void ShowWindow() { IsWindowVisible = true; _app.ShowUi(); _windows.BringToFront(_window); if (!IsAvailable) { _tab = MainTab.Help; } } internal void ToggleWindow() { IsWindowVisible = !IsWindowVisible; if (IsWindowVisible) { ShowWindow(); } } internal void Update() { UpdateAvailability(); float realtimeSinceStartup = Time.realtimeSinceStartup; if (_requestPending && realtimeSinceStartup >= _requestDeadline) { _requestPending = false; _status = "Network Profiler request timed out."; } } internal void Shutdown() { if (_subscribed && IsDedicatedServerConnectionDetected()) { SendRequest(NetworkProfilerRequestKind.Unsubscribe); } _subscribed = false; IsWindowVisible = false; ClearRows(); } internal void OnNetworkDestroyed() { _networkRpc = null; _probePending = false; _requestPending = false; _authorized = false; _subscribed = false; _sessionId = string.Empty; _serverVersion = string.Empty; _snapshotSequence = 0L; _availability = AvailabilityState.NoDedicatedConnection; _status = "Connect to a headless dedicated server to use Network Profiler."; _nextProbe = 0f; ClearRows(); if (IsWindowVisible) { _tab = MainTab.Help; } } internal bool IsDedicatedServerConnectionDetected() { //IL_0008: Unknown result type (might be due to invalid IL or missing references) //IL_000e: Invalid comparison between Unknown and I4 try { ZNet instance = ZNet.instance; return (int)SystemInfo.graphicsDeviceType != 4 && (Object)(object)instance != (Object)null && !instance.IsServer() && !instance.IsDedicated() && instance.IsCurrentServerDedicated() && ZRoutedRpc.instance != null; } catch { return false; } } private void UpdateAvailability() { if (!IsDedicatedServerConnectionDetected()) { if (_availability != AvailabilityState.NoDedicatedConnection || _networkRpc != null) { OnNetworkDestroyed(); } return; } ZRoutedRpc instance = ZRoutedRpc.instance; if (instance != _networkRpc) { _networkRpc = instance; _subscribed = false; _availability = AvailabilityState.Detecting; _status = "Checking whether the dedicated server supports Network Profiler..."; _probePending = false; _nextProbe = Time.realtimeSinceStartup + 0.15f; ClearRows(); } float realtimeSinceStartup = Time.realtimeSinceStartup; if (_probePending && realtimeSinceStartup >= _probeDeadline) { _probePending = false; _availability = AvailabilityState.ServerModUnavailable; _status = "The dedicated server did not answer. Valheim Profiler is probably missing, too old, or its Network Profiler backend is unavailable."; _nextProbe = realtimeSinceStartup + 10f; if (IsWindowVisible) { _tab = MainTab.Help; } } if (!_probePending && _availability != AvailabilityState.Available && realtimeSinceStartup >= _nextProbe) { Probe(); } } private void Probe() { if (!SendRequest(NetworkProfilerRequestKind.Probe)) { _availability = AvailabilityState.ServerModUnavailable; _status = "The dedicated-server RPC connection is not ready."; _nextProbe = Time.realtimeSinceStartup + 10f; } else { _availability = AvailabilityState.Detecting; _status = "Checking dedicated-server Network Profiler capabilities..."; _probePending = true; _probeDeadline = Time.realtimeSinceStartup + 3f; } } private void Subscribe() { if (IsAvailable && !_subscribed && !_requestPending && SendRequest(NetworkProfilerRequestKind.Subscribe)) { _requestPending = true; _requestDeadline = Time.realtimeSinceStartup + 6f; _status = "Requesting the first dedicated-server network snapshot..."; _tab = MainTab.Rpc; } } private void Unsubscribe() { if (_subscribed) { SendRequest(NetworkProfilerRequestKind.Unsubscribe); } _subscribed = false; _requestPending = false; _status = "Unsubscribed. The retained local snapshot remains visible; use Reset to clear it or Subscribe to resume private admin snapshots."; } private bool SendRequest(NetworkProfilerRequestKind kind) { try { ZRoutedRpc instance = ZRoutedRpc.instance; if (instance == null) { return false; } long serverPeerID = instance.GetServerPeerID(); if (serverPeerID == 0) { return false; } ZPackage payload = NetworkProfilerProtocol.CreateRequest(kind); string error; return NetworkProfilerTransport.Send(serverPeerID, "shudnal.ValheimProfiler.Network.Request", payload, out error); } catch { return false; } } internal void HandleResponse(long sender, ZPackage package) { try { ZRoutedRpc instance = ZRoutedRpc.instance; if (instance == null) { return; } long serverPeerID = instance.GetServerPeerID(); if (serverPeerID != 0L && sender != serverPeerID) { return; } ZPackage payload; string error; switch (NetworkProfilerTransport.TryReceive(sender, package, out payload, out error)) { case NetworkProfilerTransportReceiveResult.WaitingForFragments: return; case NetworkProfilerTransportReceiveResult.Rejected: throw new InvalidOperationException("Invalid Network Profiler transport payload: " + error); } NetworkProfilerResponse networkProfilerResponse = NetworkProfilerProtocol.ReadResponse(payload); _probePending = false; _requestPending = false; _serverVersion = networkProfilerResponse.ServerVersion ?? string.Empty; _authorized = networkProfilerResponse.Authorized; _compatibilitySummary = networkProfilerResponse.CompatibilitySummary ?? string.Empty; _compatibilityWarnings.Clear(); _compatibilityWarnings.AddRange(networkProfilerResponse.CompatibilityWarnings); if (!networkProfilerResponse.Authorized) { _subscribed = false; _availability = AvailabilityState.AccessDenied; _status = (string.IsNullOrWhiteSpace(networkProfilerResponse.Status) ? "The server rejected Network Profiler access." : networkProfilerResponse.Status); _nextProbe = Time.realtimeSinceStartup + 10f; if (IsWindowVisible) { _tab = MainTab.Help; } return; } _availability = AvailabilityState.Available; _nextProbe = float.MaxValue; _status = (string.IsNullOrWhiteSpace(networkProfilerResponse.Status) ? "Dedicated-server Network Profiler is available." : networkProfilerResponse.Status); if (networkProfilerResponse.Kind == NetworkProfilerResponseKind.Error) { _subscribed = false; } else if (networkProfilerResponse.Kind == NetworkProfilerResponseKind.Status) { if (_status.IndexOf("Unsubscribed", StringComparison.OrdinalIgnoreCase) >= 0) { _subscribed = false; } } else if (networkProfilerResponse.Kind != NetworkProfilerResponseKind.Capabilities) { _subscribed = true; _sessionId = networkProfilerResponse.SessionId ?? string.Empty; _snapshotSequence = networkProfilerResponse.SnapshotSequence; ReplaceRows(networkProfilerResponse); } } catch (Exception ex) { _probePending = false; _requestPending = false; _subscribed = false; _availability = AvailabilityState.ProtocolMismatch; _status = "Network Profiler protocol error: " + ex.Message; _nextProbe = Time.realtimeSinceStartup + 10f; if (IsWindowVisible) { _tab = MainTab.Help; } } } private void ReplaceRows(NetworkProfilerResponse response) { if (response.FullRegistry) { _rpcRows.Clear(); _rpcRows.AddRange(response.RpcRows); } else { for (int i = 0; i < _rpcRows.Count; i++) { ResetRpcInterval(_rpcRows[i]); } Dictionary dictionary = new Dictionary(StringComparer.Ordinal); for (int j = 0; j < _rpcRows.Count; j++) { dictionary[RpcIdentity(_rpcRows[j])] = _rpcRows[j]; } for (int k = 0; k < response.RpcRows.Count; k++) { NetworkRpcWireRow networkRpcWireRow = response.RpcRows[k]; string key = RpcIdentity(networkRpcWireRow); if (dictionary.TryGetValue(key, out var value)) { CopyRpcInterval(networkRpcWireRow, value); } else { _rpcRows.Add(networkRpcWireRow); } } } _zdoRows.Clear(); _zdoRows.AddRange(response.ZdoRows); _zdoInstanceRows.Clear(); _zdoInstanceRows.AddRange(response.ZdoInstanceRows); _zdoKeyRows.Clear(); _zdoKeyRows.AddRange(response.ZdoKeyRows); _peerRows.Clear(); _peerRows.AddRange(response.PeerRows); _errorRows.Clear(); _errorRows.AddRange(response.ErrorRows); } private static string RpcIdentity(NetworkRpcWireRow row) { return row.Layer + "|" + row.MethodHash + "|" + row.Component + "|" + row.Handler + "|" + row.Prefab; } private static void ResetRpcInterval(NetworkRpcWireRow row) { row.IncomingCalls = 0L; row.LocalCalls = 0L; row.OutgoingCalls = 0L; row.IncomingBytes = 0L; row.LocalBytes = 0L; row.OutgoingBytes = 0L; row.PhysicalSends = 0L; row.PhysicalBytes = 0L; row.HandlerMs = 0.0; row.AverageHandlerMs = 0.0; row.MaxHandlerMs = 0.0; row.MaxPayloadBytes = 0; row.MaxCallsPerFrame = 0; row.Errors = 0L; } private static void CopyRpcInterval(NetworkRpcWireRow source, NetworkRpcWireRow target) { target.Name = source.Name; target.Mod = source.Mod; target.Component = source.Component; target.Handler = source.Handler; target.Prefab = source.Prefab; target.Registrations = source.Registrations; target.IncomingCalls = source.IncomingCalls; target.LocalCalls = source.LocalCalls; target.OutgoingCalls = source.OutgoingCalls; target.IncomingBytes = source.IncomingBytes; target.LocalBytes = source.LocalBytes; target.OutgoingBytes = source.OutgoingBytes; target.PhysicalSends = source.PhysicalSends; target.PhysicalBytes = source.PhysicalBytes; target.HandlerMs = source.HandlerMs; target.AverageHandlerMs = source.AverageHandlerMs; target.MaxHandlerMs = source.MaxHandlerMs; target.MaxPayloadBytes = source.MaxPayloadBytes; target.MaxCallsPerFrame = source.MaxCallsPerFrame; target.Errors = source.Errors; } private void ClearDataRows() { _rpcRows.Clear(); _zdoRows.Clear(); _zdoInstanceRows.Clear(); _zdoKeyRows.Clear(); _peerRows.Clear(); _errorRows.Clear(); _snapshotSequence = 0L; } private void ClearRows() { ClearDataRows(); _compatibilityWarnings.Clear(); _compatibilitySummary = string.Empty; } private static T ParseSort(string value, T fallback) where T : struct, Enum { T result; return Enum.TryParse(value, ignoreCase: true, out result) ? result : fallback; } private static string FormatRpcIdentity(string name, int hash) { string text = hash.ToString(); string text2 = "#" + text; string text3 = (name ?? string.Empty).Trim(); if (text3.Length == 0 || string.Equals(text3, text, StringComparison.Ordinal) || string.Equals(text3, text2, StringComparison.Ordinal) || string.Equals(text3, "Unknown RPC", StringComparison.OrdinalIgnoreCase)) { return text2; } if (text3.IndexOf("(#" + text + ")", StringComparison.Ordinal) >= 0 || text3.EndsWith(text2, StringComparison.Ordinal)) { return text3; } return text3 + " (" + text2 + ")"; } private static bool Matches(string search, params string[] values) { if (string.IsNullOrWhiteSpace(search)) { return true; } for (int i = 0; i < values.Length; i++) { if (!string.IsNullOrEmpty(values[i]) && values[i].IndexOf(search, StringComparison.OrdinalIgnoreCase) >= 0) { return true; } } return false; } private static string Bytes(long value) { if (value >= 1048576) { return ((double)value / 1048576.0).ToString("0.00") + " MB"; } if (value >= 1024) { return ((double)value / 1024.0).ToString("0.0") + " KB"; } return value + " B"; } private static string Ms(double value) { return (value < 0.001) ? "0" : value.ToString("0.###"); } private void DrawWindow(int id) { //IL_008b: Unknown result type (might be due to invalid IL or missing references) //IL_0090: Unknown result type (might be due to invalid IL or missing references) EnsureStyles(); GUILayout.BeginVertical(Array.Empty()); DrawToolbar(); GUILayout.Space(3f); MainTab mainTab = (MainTab)GUILayout.Toolbar((int)_tab, new string[5] { "RPC", "ZDO", "Peers / Transport", "Routing errors", "Help" }, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(620f) }); if (mainTab != _tab) { _tab = mainTab; _scroll = Vector2.zero; } GUILayout.Space(3f); if (_tab != MainTab.Help) { GUILayout.BeginHorizontal(Array.Empty()); GUILayout.Label("Search:", _labelStyle, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(48f) }); _search = GUILayout.TextField(_search ?? string.Empty, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(300f) }); if (GUILayout.Button("Clear", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(55f) })) { _search = string.Empty; } if (_tab == MainTab.Zdo) { _zdoView = GUILayout.Toolbar(_zdoView, new string[3] { "By prefab", "Top ZDOs", "Keys" }, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(260f) }); } GUILayout.FlexibleSpace(); GUILayout.EndHorizontal(); GUILayout.Space(3f); } switch (_tab) { case MainTab.Rpc: DrawRpc(); break; case MainTab.Zdo: DrawZdo(); break; case MainTab.Peers: DrawPeers(); break; case MainTab.RoutingErrors: DrawErrors(); break; default: DrawHelp(); break; } GUILayout.EndVertical(); } private void DrawToolbar() { //IL_0134: Unknown result type (might be due to invalid IL or missing references) //IL_0151: Expected O, but got Unknown //IL_01af: Unknown result type (might be due to invalid IL or missing references) //IL_01cc: Expected O, but got Unknown GUILayout.BeginHorizontal(Array.Empty()); string text = (string.IsNullOrEmpty(_serverVersion) ? "unknown" : _serverVersion); GUILayout.Label("Server v" + text + " | " + (_subscribed ? "SUBSCRIBED" : (_requestPending ? "CONNECTING" : "OFF")) + " | " + $"Snapshot: {_snapshotSequence} | RPC: {_rpcRows.Count} | ZDO: {_zdoRows.Count} | Errors: {_errorRows.Count}", _labelStyle, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.ExpandWidth(true) }); bool enabled = GUI.enabled; GUI.enabled = enabled && IsAvailable && !_requestPending; if (GUILayout.Button(new GUIContent(_subscribed ? "Unsubscribe" : "Subscribe", _subscribed ? "Stop this connection-scoped private admin stream. The current local snapshot remains visible until Reset." : "Subscribe this administrator connection to private dedicated-server network snapshots."), (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(94f) })) { if (_subscribed) { Unsubscribe(); } else { Subscribe(); } } GUI.enabled = enabled && !_requestPending && (_subscribed || HasDataRows); if (GUILayout.Button(new GUIContent("Reset", _subscribed ? "Reset the dedicated-server Network Profiler counters and clear the current local snapshot." : "Clear the retained local snapshot after unsubscribing."), (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(62f) })) { if (_subscribed) { if (SendRequest(NetworkProfilerRequestKind.Reset)) { _requestPending = true; _requestDeadline = Time.realtimeSinceStartup + 6f; } } else { ClearDataRows(); _status = "Retained local Network Profiler data cleared."; } } GUI.enabled = enabled; if (GUILayout.Button("Close", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(62f) })) { IsWindowVisible = false; } GUILayout.EndHorizontal(); GUILayout.Label(_status, _labelStyle, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.ExpandWidth(true) }); if (_compatibilityWarnings.Count > 0) { GUILayout.Label("Compatibility warning: " + _compatibilityWarnings[0], _detailsStyle, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.ExpandWidth(true) }); } } private void DrawRpc() { //IL_01ab: Unknown result type (might be due to invalid IL or missing references) //IL_01b7: Unknown result type (might be due to invalid IL or missing references) //IL_01bc: Unknown result type (might be due to invalid IL or missing references) GUILayout.BeginHorizontal(Array.Empty()); SortHeader("Layer", 65f, RpcSortColumn.Layer, "RPC layer: Direct ZRpc, global routed RPC, or object/ZNetView routed RPC."); SortHeader("Mod / component / RPC", 440f, RpcSortColumn.Name, "Registered protocol owner, component, RPC name/hash, handler method and prefab when known. Registration ownership is not always the outgoing call site."); SortHeader("in/s", 78f, RpcSortColumn.IncomingCalls, "Remote calls handled by the server during the latest snapshot interval."); SortHeader("local/s", 78f, RpcSortColumn.LocalCalls, "Calls dispatched locally on the server during the latest interval."); SortHeader("out/s", 78f, RpcSortColumn.OutgoingCalls, "Logical outgoing RPC calls initiated by the server during the latest interval."); SortHeader("in data", 105f, RpcSortColumn.IncomingBytes, "Logical serialized payload received for this RPC during the latest interval."); SortHeader("local data", 105f, RpcSortColumn.LocalBytes, "Logical payload dispatched locally on the server."); SortHeader("out data", 105f, RpcSortColumn.OutgoingBytes, "Logical outgoing payload before routed broadcast fanout."); SortHeader("physical out", 105f, RpcSortColumn.PhysicalBytes, "Serialized routed payload after actual peer fanout. This is more representative for broadcasts."); SortHeader("sends", 78f, RpcSortColumn.PhysicalSends, "Number of physical peer sends after routed fanout."); SortHeader("CPU ms/s", 78f, RpcSortColumn.HandlerMs, "Inclusive server handler CPU time accumulated in the latest interval."); SortHeader("avg ms", 78f, RpcSortColumn.AverageHandlerMs, "Average server handler duration per invocation."); SortHeader("max ms", 78f, RpcSortColumn.MaxHandlerMs, "Slowest server handler invocation in the latest interval."); SortHeader("max payload", 105f, RpcSortColumn.MaxPayload, "Largest logical payload observed for one invocation."); SortHeader("max/frame", 78f, RpcSortColumn.MaxCallsFrame, "Largest number of calls observed in one server frame."); SortHeader("errors", 78f, RpcSortColumn.Errors, "Handler exceptions or routing failures associated with this RPC."); GUILayout.EndHorizontal(); IEnumerable rows = _rpcRows.Where((NetworkRpcWireRow r) => Matches(_search, r.Name, r.Mod, r.Component, r.Handler, r.Prefab)); rows = SortRpcRows(rows); _scroll = GUILayout.BeginScrollView(_scroll, true, true, Array.Empty()); foreach (NetworkRpcWireRow item in rows) { GUILayout.BeginHorizontal(Array.Empty()); Cell(item.Layer, 65f); Cell(BuildRpcName(item), 440f); Cell(item.IncomingCalls.ToString(), 78f); Cell(item.LocalCalls.ToString(), 78f); Cell(item.OutgoingCalls.ToString(), 78f); Cell(Bytes(item.IncomingBytes), 105f); Cell(Bytes(item.LocalBytes), 105f); Cell(Bytes(item.OutgoingBytes), 105f); Cell(Bytes(item.PhysicalBytes), 105f); Cell(item.PhysicalSends.ToString(), 78f); Cell(Ms(item.HandlerMs), 78f); Cell(Ms(item.AverageHandlerMs), 78f); Cell(Ms(item.MaxHandlerMs), 78f); Cell(Bytes(item.MaxPayloadBytes), 105f); Cell(item.MaxCallsPerFrame.ToString(), 78f); Cell(item.Errors.ToString(), 78f); GUILayout.EndHorizontal(); } GUILayout.EndScrollView(); } private static string BuildRpcName(NetworkRpcWireRow row) { string text = string.Empty; if (!string.IsNullOrEmpty(row.Mod)) { text = row.Mod; } if (!string.IsNullOrEmpty(row.Component)) { text = text + ((text.Length == 0) ? string.Empty : " | ") + row.Component; } text = text + ((text.Length == 0) ? string.Empty : " | ") + FormatRpcIdentity(row.Name, row.MethodHash); if (!string.IsNullOrEmpty(row.Handler)) { text = text + " -> " + row.Handler; } if (!string.IsNullOrEmpty(row.Prefab)) { text = text + " | prefab " + row.Prefab; } return text; } private void DrawZdo() { //IL_00b8: Unknown result type (might be due to invalid IL or missing references) //IL_00c4: Unknown result type (might be due to invalid IL or missing references) //IL_00c9: Unknown result type (might be due to invalid IL or missing references) //IL_0595: Unknown result type (might be due to invalid IL or missing references) //IL_05a1: Unknown result type (might be due to invalid IL or missing references) //IL_05a6: Unknown result type (might be due to invalid IL or missing references) //IL_02ca: Unknown result type (might be due to invalid IL or missing references) //IL_02d6: Unknown result type (might be due to invalid IL or missing references) //IL_02db: Unknown result type (might be due to invalid IL or missing references) if (_zdoView == 2) { GUILayout.BeginHorizontal(Array.Empty()); SortHeader("Prefab", 250f, ZdoKeySortColumn.Prefab, "Prefab whose ZDO values were mutated."); SortHeader("Key", 260f, ZdoKeySortColumn.Key, "Best-effort ZDO key name and stable hash. Runtime string Set overloads and known vanilla ZDOVars improve name resolution."); SortHeader("Type", 100f, ZdoKeySortColumn.Type, "Observed value type for this ZDO key."); SortHeader("mutations/s", 100f, ZdoKeySortColumn.Mutations, "Successful Set operations that triggered a ZDO revision in the latest interval."); SortHeader("affected", 90f, ZdoKeySortColumn.Affected, "Distinct ZDO instances affected by this key in the latest interval. Full payload bytes are intentionally not assigned to one key."); GUILayout.EndHorizontal(); IEnumerable rows = _zdoKeyRows.Where((NetworkZdoKeyWireRow r) => Matches(_search, r.Prefab, r.KeyName, r.ValueType)); rows = SortZdoKeyRows(rows); _scroll = GUILayout.BeginScrollView(_scroll, true, true, Array.Empty()); foreach (NetworkZdoKeyWireRow item in rows) { GUILayout.BeginHorizontal(Array.Empty()); Cell(item.Prefab, 250f); Cell((string.IsNullOrEmpty(item.KeyName) ? "Unknown" : item.KeyName) + " (#" + item.KeyHash + ")", 260f); Cell(item.ValueType, 100f); Cell(item.Mutations.ToString(), 100f); Cell(item.AffectedZdos.ToString(), 90f); GUILayout.EndHorizontal(); } GUILayout.EndScrollView(); return; } if (_zdoView == 1) { GUILayout.BeginHorizontal(Array.Empty()); SortHeader("ZDOID", 210f, ZdoInstanceSortColumn.ZdoId, "Specific network object identifier."); SortHeader("Prefab", 240f, ZdoInstanceSortColumn.Prefab, "Prefab name/hash for this ZDO."); SortHeader("Owner", 125f, ZdoInstanceSortColumn.Owner, "Current ZDO owner peer ID."); SortHeader("mut/s", 75f, ZdoInstanceSortColumn.Mutations, "Revision-triggering mutations in the latest interval."); SortHeader("sent/s", 75f, ZdoInstanceSortColumn.SentCount, "Full ZDO serializations sent in the latest interval."); SortHeader("sent", 95f, ZdoInstanceSortColumn.SentBytes, "Full serialized ZDO bytes sent across peers."); SortHeader("recv/s", 75f, ZdoInstanceSortColumn.ReceivedCount, "Full ZDO updates received in the latest interval."); SortHeader("recv", 95f, ZdoInstanceSortColumn.ReceivedBytes, "Full serialized ZDO bytes received."); SortHeader("max size", 90f, ZdoInstanceSortColumn.MaxSize, "Largest complete serialized form observed for this ZDO."); GUILayout.EndHorizontal(); IEnumerable rows2 = _zdoInstanceRows.Where((NetworkZdoInstanceWireRow r) => Matches(_search, r.ZdoId, r.Prefab, r.Owner.ToString())); rows2 = SortZdoInstanceRows(rows2); _scroll = GUILayout.BeginScrollView(_scroll, true, true, Array.Empty()); foreach (NetworkZdoInstanceWireRow item2 in rows2) { GUILayout.BeginHorizontal(Array.Empty()); Cell(item2.ZdoId, 210f); Cell(item2.Prefab + " (#" + item2.PrefabHash + ")", 240f); Cell(item2.Owner.ToString(), 125f); Cell(item2.Mutations.ToString(), 75f); Cell(item2.SentCount.ToString(), 75f); Cell(Bytes(item2.SentBytes), 95f); Cell(item2.ReceivedCount.ToString(), 75f); Cell(Bytes(item2.ReceivedBytes), 95f); Cell(Bytes(item2.MaxSize), 90f); GUILayout.EndHorizontal(); } GUILayout.EndScrollView(); return; } GUILayout.BeginHorizontal(Array.Empty()); SortHeader("Prefab", 270f, ZdoPrefabSortColumn.Prefab, "ZDO prefab name and stable hash."); SortHeader("mut/s", 75f, ZdoPrefabSortColumn.Mutations, "Successful ZDO Set operations that changed values and incremented revisions."); SortHeader("unique", 75f, ZdoPrefabSortColumn.UniqueChanged, "Distinct changed ZDO instances in the latest interval."); SortHeader("sent/s", 75f, ZdoPrefabSortColumn.SentCount, "Complete ZDO serializations sent in the latest interval."); SortHeader("sent", 95f, ZdoPrefabSortColumn.SentBytes, "Complete serialized ZDO bytes sent to all peers, including amplification."); SortHeader("recv/s", 75f, ZdoPrefabSortColumn.ReceivedCount, "Complete ZDO updates received in the latest interval."); SortHeader("recv", 95f, ZdoPrefabSortColumn.ReceivedBytes, "Complete serialized ZDO bytes received."); SortHeader("ser ms", 75f, ZdoPrefabSortColumn.SerializeMs, "CPU time spent serializing complete ZDO payloads."); SortHeader("deser ms", 80f, ZdoPrefabSortColumn.DeserializeMs, "CPU time spent deserializing complete ZDO payloads."); SortHeader("avg size", 90f, ZdoPrefabSortColumn.AverageSize, "Average complete serialized ZDO size."); SortHeader("max size", 90f, ZdoPrefabSortColumn.MaxSize, "Largest complete serialized ZDO size."); SortHeader("create", 70f, ZdoPrefabSortColumn.Creates, "ZDO instances created in the latest interval."); SortHeader("destroy", 70f, ZdoPrefabSortColumn.Destroys, "ZDO instances destroyed in the latest interval."); SortHeader("owner", 70f, ZdoPrefabSortColumn.Ownership, "Ownership changes in the latest interval."); GUILayout.EndHorizontal(); IEnumerable rows3 = _zdoRows.Where((NetworkZdoWireRow r) => Matches(_search, r.Prefab, r.PrefabHash.ToString())); rows3 = SortZdoPrefabRows(rows3); _scroll = GUILayout.BeginScrollView(_scroll, true, true, Array.Empty()); foreach (NetworkZdoWireRow item3 in rows3) { GUILayout.BeginHorizontal(Array.Empty()); Cell(item3.Prefab + " (#" + item3.PrefabHash + ")", 270f); Cell(item3.Mutations.ToString(), 75f); Cell(item3.UniqueChanged.ToString(), 75f); Cell(item3.SentCount.ToString(), 75f); Cell(Bytes(item3.SentBytes), 95f); Cell(item3.ReceivedCount.ToString(), 75f); Cell(Bytes(item3.ReceivedBytes), 95f); Cell(Ms(item3.SerializeMs), 75f); Cell(Ms(item3.DeserializeMs), 80f); Cell(Bytes(item3.AverageSize), 90f); Cell(Bytes(item3.MaxSize), 90f); Cell(item3.Creates.ToString(), 70f); Cell(item3.Destroys.ToString(), 70f); Cell(item3.OwnershipChanges.ToString(), 70f); GUILayout.EndHorizontal(); } GUILayout.EndScrollView(); } private void DrawPeers() { //IL_01c3: Unknown result type (might be due to invalid IL or missing references) //IL_01cf: Unknown result type (might be due to invalid IL or missing references) //IL_01d4: Unknown result type (might be due to invalid IL or missing references) GUILayout.BeginHorizontal(Array.Empty()); SortHeader("Peer", 210f, PeerSortColumn.Peer, "Connected peer name, host and peer ID."); SortHeader("Socket", 135f, PeerSortColumn.Socket, "Runtime socket backend. Network mods may replace or wrap the vanilla backend."); SortHeader("ready", 55f, PeerSortColumn.Ready, "Whether the peer is ready for normal game traffic."); SortHeader("ping", 55f, PeerSortColumn.Ping, "Reported peer ping in milliseconds when available."); SortHeader("out/s", 85f, PeerSortColumn.Out, "Transport-reported outgoing bytes per second."); SortHeader("in/s", 85f, PeerSortColumn.In, "Transport-reported incoming bytes per second."); SortHeader("serialized out", 105f, PeerSortColumn.SerializedOut, "Game package bytes serialized for this peer in the latest interval."); SortHeader("serialized in", 105f, PeerSortColumn.SerializedIn, "Game package bytes deserialized from this peer in the latest interval."); SortHeader("queue", 80f, PeerSortColumn.SendQueue, "Current game-reported socket send queue. Backend semantics differ; see Help compatibility notes."); SortHeader("max queue", 90f, PeerSortColumn.MaxQueue, "Maximum send queue observed during the latest interval."); SortHeader("send rate", 85f, PeerSortColumn.SendRate, "Current configured or measured send rate exposed by the socket backend."); SortHeader("actual flight", 95f, PeerSortColumn.InFlight, "Best-effort actual in-flight bytes. Primarily available for PlayFab; n/a for unsupported backends."); SortHeader("ZDO ms", 75f, PeerSortColumn.SendZdoMs, "CPU time spent in SendZDOs for this peer."); SortHeader("sync ms", 75f, PeerSortColumn.SyncMs, "CPU time spent building and sorting the ZDO synchronization candidate list."); SortHeader("sent", 65f, PeerSortColumn.Sent, "ZDO records selected and serialized for this peer."); SortHeader("candidates", 85f, PeerSortColumn.Candidates, "ZDO candidates considered for this peer before filtering and budget limits."); SortHeader("selected", 75f, PeerSortColumn.Selected, "ZDO candidates selected for synchronization."); GUILayout.EndHorizontal(); IEnumerable rows = _peerRows.Where((NetworkPeerWireRow r) => Matches(_search, r.Name, r.Host, r.Socket, r.PeerId.ToString())); rows = SortPeerRows(rows); _scroll = GUILayout.BeginScrollView(_scroll, true, true, Array.Empty()); foreach (NetworkPeerWireRow item in rows) { GUILayout.BeginHorizontal(Array.Empty()); Cell((string.IsNullOrEmpty(item.Name) ? item.PeerId.ToString() : item.Name) + " | " + item.Host, 210f); Cell(item.Socket, 135f); Cell(item.Ready ? "yes" : "no", 55f); Cell(item.Ping.ToString(), 55f); Cell(Bytes((long)item.ReportedOutBytesPerSecond), 85f); Cell(Bytes((long)item.ReportedInBytesPerSecond), 85f); Cell(Bytes(item.SerializedOutBytesPerSecond), 105f); Cell(Bytes(item.SerializedInBytesPerSecond), 105f); Cell(Bytes(item.SendQueue), 80f); Cell(Bytes(item.MaximumSendQueue), 90f); Cell(Bytes(item.CurrentSendRate), 85f); Cell((item.ActualInFlightBytes < 0) ? "n/a" : Bytes(item.ActualInFlightBytes), 95f); Cell(Ms(item.SendZdoMs), 75f); Cell(Ms(item.CreateSyncListMs), 75f); Cell(item.ZdosSent.ToString(), 65f); Cell(item.SyncCandidates.ToString(), 85f); Cell(item.SyncSelected.ToString(), 75f); GUILayout.EndHorizontal(); } GUILayout.EndScrollView(); } private void DrawErrors() { //IL_00d5: Unknown result type (might be due to invalid IL or missing references) //IL_00e1: Unknown result type (might be due to invalid IL or missing references) //IL_00e6: Unknown result type (might be due to invalid IL or missing references) GUILayout.BeginHorizontal(Array.Empty()); SortHeader("Count", 70f, RoutingErrorSortColumn.Count, "Occurrences of this bounded routing error identity."); SortHeader("Kind", 185f, RoutingErrorSortColumn.Kind, "Missing handler/peer/ZDO/ZNetView, handler exception, or another routing failure category."); SortHeader("RPC", 230f, RoutingErrorSortColumn.Rpc, "Registered RPC name and stable hash. Unknown hashes are shown once as #hash."); SortHeader("Peer", 180f, RoutingErrorSortColumn.Peer, "Source or target peer known at the failing server-side routing point."); SortHeader("Component / handler", 300f, RoutingErrorSortColumn.Component, "Component and registered handler resolved from Register calls or the current handler dictionaries."); SortHeader("Source caller", 300f, RoutingErrorSortColumn.Caller, "Best-effort server-local initiating method and mod. Incoming client call sites cannot be reconstructed on the server; use Peer plus registered handler context."); SortHeader("Target / details", 390f, RoutingErrorSortColumn.Details, "Target ZDO/prefab and the latest error or exception text."); GUILayout.EndHorizontal(); IEnumerable rows = _errorRows.Where((NetworkRoutingErrorWireRow r) => Matches(_search, r.Kind, r.Rpc, r.Peer, r.Component, r.Handler, r.Prefab, r.Caller, r.CallerMod, r.Target, r.LastDetails)); rows = SortErrorRows(rows); _scroll = GUILayout.BeginScrollView(_scroll, true, true, Array.Empty()); foreach (NetworkRoutingErrorWireRow item in rows) { GUILayout.BeginHorizontal(Array.Empty()); Cell(item.Count.ToString(), 70f); Cell(item.Kind, 185f); Cell(FormatRpcIdentity(item.Rpc, item.MethodHash), 230f); Cell(item.Peer + ((item.PeerId == 0L) ? string.Empty : (" | " + item.PeerId)), 180f); Cell((item.Component + " | " + item.Handler).Trim(' ', '|'), 300f); Cell((item.CallerMod + " | " + item.Caller).Trim(' ', '|'), 300f); string text = (item.Target + " | " + item.Prefab + " | " + item.LastDetails).Trim(' ', '|'); Cell(text, 390f); GUILayout.EndHorizontal(); } GUILayout.EndScrollView(); } private void DrawHelp() { //IL_0003: 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_0012: Unknown result type (might be due to invalid IL or missing references) _helpScroll = GUILayout.BeginScrollView(_helpScroll, Array.Empty()); HeaderLabel("Network Profiler requirements"); Body("This tool intentionally works only with a remote headless dedicated server. Self-hosted and open-server instances are excluded because host gameplay, rendering and local loopback make the measurements harder to interpret consistently."); Body("The same compatible Valheim Profiler build must be installed on the dedicated server and the current player must be in its admin list. Subscribe creates a private connection-scoped stream; no network metrics are broadcast to other administrators."); GUILayout.Space(6f); HeaderLabel("RPC"); Body("Registry rows attribute registered handlers to delegate methods, component types, assemblies and BepInEx plugins. Runtime rows show incoming/outgoing calls, logical payload, routed physical fanout, handler CPU time and peaks. Attribution means protocol/handler owner; the exact sender-side call site is not captured in the default low-overhead mode."); Body("Routing errors report handler exceptions, missing direct/global/object handlers, unavailable peers, missing ZDOs and missing ZNetViews. Register calls and current handler dictionaries map names/hashes to components and handler methods. For server-local failed sends the initiating caller is captured only when the error occurs; an incoming client RPC can expose the source peer but not its original client-side method without client instrumentation."); GUILayout.Space(6f); HeaderLabel("ZDO"); Body("A successful ZDO mutation increments the whole ZDO revision, and vanilla synchronization serializes the complete ZDO. The profiler therefore reports mutation triggers, full serialized bytes and per-prefab traffic separately. Key rows show mutation frequency and must not be interpreted as exact byte ownership."); GUILayout.Space(6f); HeaderLabel("Compatibility"); Body(string.IsNullOrEmpty(_compatibilitySummary) ? "No server compatibility report received yet." : _compatibilitySummary); for (int i = 0; i < _compatibilityWarnings.Count; i++) { Body("- " + _compatibilityWarnings[i]); } GUILayout.Space(6f); HeaderLabel("Current state"); Body(_status); GUILayout.EndScrollView(); } private IEnumerable SortRpcRows(IEnumerable rows) { RpcSortColumn rpcSortColumn = _rpcSortColumn; if (1 == 0) { } IOrderedEnumerable result = rpcSortColumn switch { RpcSortColumn.Layer => rows.OrderByDescending((NetworkRpcWireRow r) => r.Layer, StringComparer.OrdinalIgnoreCase), RpcSortColumn.Name => rows.OrderByDescending(BuildRpcName, StringComparer.OrdinalIgnoreCase), RpcSortColumn.IncomingCalls => rows.OrderByDescending((NetworkRpcWireRow r) => r.IncomingCalls), RpcSortColumn.LocalCalls => rows.OrderByDescending((NetworkRpcWireRow r) => r.LocalCalls), RpcSortColumn.OutgoingCalls => rows.OrderByDescending((NetworkRpcWireRow r) => r.OutgoingCalls), RpcSortColumn.IncomingBytes => rows.OrderByDescending((NetworkRpcWireRow r) => r.IncomingBytes), RpcSortColumn.LocalBytes => rows.OrderByDescending((NetworkRpcWireRow r) => r.LocalBytes), RpcSortColumn.OutgoingBytes => rows.OrderByDescending((NetworkRpcWireRow r) => r.OutgoingBytes), RpcSortColumn.PhysicalBytes => rows.OrderByDescending((NetworkRpcWireRow r) => r.PhysicalBytes), RpcSortColumn.PhysicalSends => rows.OrderByDescending((NetworkRpcWireRow r) => r.PhysicalSends), RpcSortColumn.AverageHandlerMs => rows.OrderByDescending((NetworkRpcWireRow r) => r.AverageHandlerMs), RpcSortColumn.MaxHandlerMs => rows.OrderByDescending((NetworkRpcWireRow r) => r.MaxHandlerMs), RpcSortColumn.MaxPayload => rows.OrderByDescending((NetworkRpcWireRow r) => r.MaxPayloadBytes), RpcSortColumn.MaxCallsFrame => rows.OrderByDescending((NetworkRpcWireRow r) => r.MaxCallsPerFrame), RpcSortColumn.Errors => rows.OrderByDescending((NetworkRpcWireRow r) => r.Errors), _ => rows.OrderByDescending((NetworkRpcWireRow r) => r.HandlerMs), }; if (1 == 0) { } return result; } private IEnumerable SortZdoPrefabRows(IEnumerable rows) { ZdoPrefabSortColumn zdoPrefabSortColumn = _zdoPrefabSortColumn; if (1 == 0) { } IOrderedEnumerable result = zdoPrefabSortColumn switch { ZdoPrefabSortColumn.Prefab => rows.OrderByDescending((NetworkZdoWireRow r) => r.Prefab, StringComparer.OrdinalIgnoreCase), ZdoPrefabSortColumn.Mutations => rows.OrderByDescending((NetworkZdoWireRow r) => r.Mutations), ZdoPrefabSortColumn.UniqueChanged => rows.OrderByDescending((NetworkZdoWireRow r) => r.UniqueChanged), ZdoPrefabSortColumn.SentCount => rows.OrderByDescending((NetworkZdoWireRow r) => r.SentCount), ZdoPrefabSortColumn.ReceivedCount => rows.OrderByDescending((NetworkZdoWireRow r) => r.ReceivedCount), ZdoPrefabSortColumn.ReceivedBytes => rows.OrderByDescending((NetworkZdoWireRow r) => r.ReceivedBytes), ZdoPrefabSortColumn.SerializeMs => rows.OrderByDescending((NetworkZdoWireRow r) => r.SerializeMs), ZdoPrefabSortColumn.DeserializeMs => rows.OrderByDescending((NetworkZdoWireRow r) => r.DeserializeMs), ZdoPrefabSortColumn.AverageSize => rows.OrderByDescending((NetworkZdoWireRow r) => r.AverageSize), ZdoPrefabSortColumn.MaxSize => rows.OrderByDescending((NetworkZdoWireRow r) => r.MaxSize), ZdoPrefabSortColumn.Creates => rows.OrderByDescending((NetworkZdoWireRow r) => r.Creates), ZdoPrefabSortColumn.Destroys => rows.OrderByDescending((NetworkZdoWireRow r) => r.Destroys), ZdoPrefabSortColumn.Ownership => rows.OrderByDescending((NetworkZdoWireRow r) => r.OwnershipChanges), _ => rows.OrderByDescending((NetworkZdoWireRow r) => r.SentBytes), }; if (1 == 0) { } return result; } private IEnumerable SortZdoInstanceRows(IEnumerable rows) { ZdoInstanceSortColumn zdoInstanceSortColumn = _zdoInstanceSortColumn; if (1 == 0) { } IOrderedEnumerable result = zdoInstanceSortColumn switch { ZdoInstanceSortColumn.ZdoId => rows.OrderByDescending((NetworkZdoInstanceWireRow r) => r.ZdoId, StringComparer.OrdinalIgnoreCase), ZdoInstanceSortColumn.Prefab => rows.OrderByDescending((NetworkZdoInstanceWireRow r) => r.Prefab, StringComparer.OrdinalIgnoreCase), ZdoInstanceSortColumn.Owner => rows.OrderByDescending((NetworkZdoInstanceWireRow r) => r.Owner), ZdoInstanceSortColumn.Mutations => rows.OrderByDescending((NetworkZdoInstanceWireRow r) => r.Mutations), ZdoInstanceSortColumn.SentCount => rows.OrderByDescending((NetworkZdoInstanceWireRow r) => r.SentCount), ZdoInstanceSortColumn.SentBytes => rows.OrderByDescending((NetworkZdoInstanceWireRow r) => r.SentBytes), ZdoInstanceSortColumn.ReceivedCount => rows.OrderByDescending((NetworkZdoInstanceWireRow r) => r.ReceivedCount), ZdoInstanceSortColumn.ReceivedBytes => rows.OrderByDescending((NetworkZdoInstanceWireRow r) => r.ReceivedBytes), ZdoInstanceSortColumn.MaxSize => rows.OrderByDescending((NetworkZdoInstanceWireRow r) => r.MaxSize), _ => rows.OrderByDescending((NetworkZdoInstanceWireRow r) => r.SentBytes + r.ReceivedBytes + r.Mutations * 16), }; if (1 == 0) { } return result; } private IEnumerable SortZdoKeyRows(IEnumerable rows) { ZdoKeySortColumn zdoKeySortColumn = _zdoKeySortColumn; if (1 == 0) { } IOrderedEnumerable result = zdoKeySortColumn switch { ZdoKeySortColumn.Prefab => rows.OrderByDescending((NetworkZdoKeyWireRow r) => r.Prefab, StringComparer.OrdinalIgnoreCase), ZdoKeySortColumn.Key => rows.OrderByDescending((NetworkZdoKeyWireRow r) => r.KeyName, StringComparer.OrdinalIgnoreCase), ZdoKeySortColumn.Type => rows.OrderByDescending((NetworkZdoKeyWireRow r) => r.ValueType, StringComparer.OrdinalIgnoreCase), ZdoKeySortColumn.Affected => rows.OrderByDescending((NetworkZdoKeyWireRow r) => r.AffectedZdos), _ => rows.OrderByDescending((NetworkZdoKeyWireRow r) => r.Mutations), }; if (1 == 0) { } return result; } private IEnumerable SortPeerRows(IEnumerable rows) { PeerSortColumn peerSortColumn = _peerSortColumn; if (1 == 0) { } IOrderedEnumerable result = peerSortColumn switch { PeerSortColumn.Peer => rows.OrderByDescending((NetworkPeerWireRow r) => r.Name + "|" + r.Host, StringComparer.OrdinalIgnoreCase), PeerSortColumn.Socket => rows.OrderByDescending((NetworkPeerWireRow r) => r.Socket, StringComparer.OrdinalIgnoreCase), PeerSortColumn.Ready => rows.OrderByDescending((NetworkPeerWireRow r) => r.Ready), PeerSortColumn.Ping => rows.OrderByDescending((NetworkPeerWireRow r) => r.Ping), PeerSortColumn.Out => rows.OrderByDescending((NetworkPeerWireRow r) => r.ReportedOutBytesPerSecond), PeerSortColumn.In => rows.OrderByDescending((NetworkPeerWireRow r) => r.ReportedInBytesPerSecond), PeerSortColumn.SerializedOut => rows.OrderByDescending((NetworkPeerWireRow r) => r.SerializedOutBytesPerSecond), PeerSortColumn.SerializedIn => rows.OrderByDescending((NetworkPeerWireRow r) => r.SerializedInBytesPerSecond), PeerSortColumn.MaxQueue => rows.OrderByDescending((NetworkPeerWireRow r) => r.MaximumSendQueue), PeerSortColumn.SendRate => rows.OrderByDescending((NetworkPeerWireRow r) => r.CurrentSendRate), PeerSortColumn.InFlight => rows.OrderByDescending((NetworkPeerWireRow r) => r.ActualInFlightBytes), PeerSortColumn.SendZdoMs => rows.OrderByDescending((NetworkPeerWireRow r) => r.SendZdoMs), PeerSortColumn.SyncMs => rows.OrderByDescending((NetworkPeerWireRow r) => r.CreateSyncListMs), PeerSortColumn.Sent => rows.OrderByDescending((NetworkPeerWireRow r) => r.ZdosSent), PeerSortColumn.Candidates => rows.OrderByDescending((NetworkPeerWireRow r) => r.SyncCandidates), PeerSortColumn.Selected => rows.OrderByDescending((NetworkPeerWireRow r) => r.SyncSelected), _ => rows.OrderByDescending((NetworkPeerWireRow r) => r.SendQueue), }; if (1 == 0) { } return result; } private IEnumerable SortErrorRows(IEnumerable rows) { RoutingErrorSortColumn routingErrorSortColumn = _routingErrorSortColumn; if (1 == 0) { } IOrderedEnumerable result = routingErrorSortColumn switch { RoutingErrorSortColumn.Kind => rows.OrderByDescending((NetworkRoutingErrorWireRow r) => r.Kind, StringComparer.OrdinalIgnoreCase), RoutingErrorSortColumn.Rpc => rows.OrderByDescending((NetworkRoutingErrorWireRow r) => FormatRpcIdentity(r.Rpc, r.MethodHash), StringComparer.OrdinalIgnoreCase), RoutingErrorSortColumn.Peer => rows.OrderByDescending((NetworkRoutingErrorWireRow r) => r.Peer + r.PeerId, StringComparer.OrdinalIgnoreCase), RoutingErrorSortColumn.Component => rows.OrderByDescending((NetworkRoutingErrorWireRow r) => r.Component + r.Handler, StringComparer.OrdinalIgnoreCase), RoutingErrorSortColumn.Caller => rows.OrderByDescending((NetworkRoutingErrorWireRow r) => r.CallerMod + r.Caller, StringComparer.OrdinalIgnoreCase), RoutingErrorSortColumn.Details => rows.OrderByDescending((NetworkRoutingErrorWireRow r) => r.Target + r.LastDetails, StringComparer.OrdinalIgnoreCase), _ => rows.OrderByDescending((NetworkRoutingErrorWireRow r) => r.Count), }; if (1 == 0) { } return result; } private void SetSortColumn(RpcSortColumn column) { _rpcSortColumn = column; _app.Config.NetworkRpcSortColumn.Value = column.ToString(); } private void SetSortColumn(ZdoPrefabSortColumn column) { _zdoPrefabSortColumn = column; _app.Config.NetworkZdoPrefabSortColumn.Value = column.ToString(); } private void SetSortColumn(ZdoInstanceSortColumn column) { _zdoInstanceSortColumn = column; _app.Config.NetworkZdoInstanceSortColumn.Value = column.ToString(); } private void SetSortColumn(ZdoKeySortColumn column) { _zdoKeySortColumn = column; _app.Config.NetworkZdoKeySortColumn.Value = column.ToString(); } private void SetSortColumn(PeerSortColumn column) { _peerSortColumn = column; _app.Config.NetworkPeerSortColumn.Value = column.ToString(); } private void SetSortColumn(RoutingErrorSortColumn column) { _routingErrorSortColumn = column; _app.Config.NetworkRoutingErrorSortColumn.Value = column.ToString(); } private void SortHeader(string text, float width, RpcSortColumn column, string tooltip) { SortHeaderCore(text, width, _rpcSortColumn.Equals(column), tooltip, delegate { SetSortColumn(column); }); } private void SortHeader(string text, float width, ZdoPrefabSortColumn column, string tooltip) { SortHeaderCore(text, width, _zdoPrefabSortColumn.Equals(column), tooltip, delegate { SetSortColumn(column); }); } private void SortHeader(string text, float width, ZdoInstanceSortColumn column, string tooltip) { SortHeaderCore(text, width, _zdoInstanceSortColumn.Equals(column), tooltip, delegate { SetSortColumn(column); }); } private void SortHeader(string text, float width, ZdoKeySortColumn column, string tooltip) { SortHeaderCore(text, width, _zdoKeySortColumn.Equals(column), tooltip, delegate { SetSortColumn(column); }); } private void SortHeader(string text, float width, PeerSortColumn column, string tooltip) { SortHeaderCore(text, width, _peerSortColumn.Equals(column), tooltip, delegate { SetSortColumn(column); }); } private void SortHeader(string text, float width, RoutingErrorSortColumn column, string tooltip) { SortHeaderCore(text, width, _routingErrorSortColumn.Equals(column), tooltip, delegate { SetSortColumn(column); }); } private void SortHeaderCore(string text, float width, bool active, string tooltip, Action setSort) { //IL_0020: Unknown result type (might be due to invalid IL or missing references) //IL_003a: Expected O, but got Unknown GUIStyle val = (active ? _activeHeaderStyle : _headerStyle); if (GUILayout.Button(new GUIContent(text, tooltip + " Click to sort descending."), val, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(width) })) { setSort(); } } private void EnsureStyles() { //IL_003e: Unknown result type (might be due to invalid IL or missing references) //IL_0043: 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_0053: Unknown result type (might be due to invalid IL or missing references) //IL_0060: Expected O, but got Unknown //IL_0071: Unknown result type (might be due to invalid IL or missing references) //IL_0083: Unknown result type (might be due to invalid IL or missing references) //IL_0088: Unknown result type (might be due to invalid IL or missing references) //IL_0095: Expected O, but got Unknown //IL_00a6: Unknown result type (might be due to invalid IL or missing references) //IL_00b8: Unknown result type (might be due to invalid IL or missing references) //IL_00c2: Expected O, but got Unknown //IL_00c8: Unknown result type (might be due to invalid IL or missing references) //IL_00d3: Unknown result type (might be due to invalid IL or missing references) //IL_00dd: Unknown result type (might be due to invalid IL or missing references) //IL_00e2: Unknown result type (might be due to invalid IL or missing references) //IL_00ee: Unknown result type (might be due to invalid IL or missing references) //IL_0100: Unknown result type (might be due to invalid IL or missing references) //IL_0112: Unknown result type (might be due to invalid IL or missing references) //IL_0124: Unknown result type (might be due to invalid IL or missing references) //IL_0136: Unknown result type (might be due to invalid IL or missing references) //IL_013b: Unknown result type (might be due to invalid IL or missing references) //IL_0143: Unknown result type (might be due to invalid IL or missing references) //IL_014b: Unknown result type (might be due to invalid IL or missing references) //IL_0158: Expected O, but got Unknown //IL_0169: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)_styleSkin == (Object)(object)GUI.skin) || _labelStyle == null) { _styleSkin = GUI.skin; _labelStyle = new GUIStyle(GUI.skin.label) { wordWrap = false, clipping = (TextClipping)1, alignment = (TextAnchor)3 }; _labelStyle.normal.textColor = _theme.TextColor; _headerStyle = new GUIStyle(_labelStyle) { fontStyle = (FontStyle)1 }; _headerStyle.normal.textColor = _theme.HeaderTextColor; _activeHeaderStyle = new GUIStyle(_headerStyle); Color textColor = Color.Lerp(_theme.HeaderTextColor, _theme.AccentColor, 0.5f); _activeHeaderStyle.normal.textColor = textColor; _activeHeaderStyle.hover.textColor = textColor; _activeHeaderStyle.active.textColor = textColor; _activeHeaderStyle.focused.textColor = textColor; _detailsStyle = new GUIStyle(GUI.skin.label) { wordWrap = true, clipping = (TextClipping)1, alignment = (TextAnchor)0 }; _detailsStyle.normal.textColor = _theme.TextColor; } } private void Cell(string text, float width) { GUILayout.Label(text ?? string.Empty, _labelStyle, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(width) }); } private void HeaderLabel(string text) { GUILayout.Label(text, _headerStyle, Array.Empty()); } private void Body(string text) { GUILayout.Label(text ?? string.Empty, _detailsStyle, Array.Empty()); } } } namespace ValheimProfiler.Tools.MonoBehaviourProfiler { internal sealed class MonoBehaviourProfilerTool : IProfilerTool { private enum MainTab { Profiler, BehavioursToProfile, Help } private enum ProfilerView { Avg1s, MaxOver60Sec } private enum TableSortColumn { Source, AvgMsPerFrame, AvgCallsPerFrame, RawMax, SecondMax, ThirdMax, AboveP99, AvgAboveP99, P99, AboveP95, AvgAboveP95, P95, Samples, GcSamples, MonoBehaviour } private enum BehaviourSource { Mod, Valheim, Other } private enum ProfileMethodKind { Update, FixedUpdate, LateUpdate, OnGUI } private enum SelectionRowKind { Group, Type, Method } private sealed class BehaviourTypeEntry { public Type Type; public string GroupId; public string GroupName; public string AssemblyName; public string TypeName; public BehaviourSource Source; public bool Expanded; public readonly List Methods = new List(); } private sealed class BehaviourMethodEntry { public BehaviourTypeEntry TypeEntry; public MethodInfo Method; public string Key; public string DisplayName; public ProfileMethodKind Kind; public bool DefaultSelected; public bool Selected; public readonly RollingProfilerStat Stat = new RollingProfilerStat(); } private sealed class RuntimeBehaviourEntry { public readonly Type RuntimeType; public readonly BehaviourMethodEntry Entry; public RuntimeBehaviourEntry(Type runtimeType, BehaviourMethodEntry entry) { RuntimeType = runtimeType; Entry = entry; } } private sealed class FlatRowView { public BehaviourMethodEntry Entry; public RollingProfilerSnapshot Snapshot; } private sealed class GroupRowView { public string GroupId; public string GroupName; public List Rows; public GroupSummary Summary; public double AggregateScore; } private struct GroupSummary { public double Avg1sMsPerFrame; public double Avg1sCallsPerFrame; public RollingMaxSnapshot Max; } private sealed class SelectionRow { public SelectionRowKind Kind; public string GroupId; public string Text; public BehaviourTypeEntry TypeEntry; public BehaviourMethodEntry MethodEntry; public int Indent; } private sealed class SelectionPolicy { private readonly string _path; private readonly Dictionary _overrides = new Dictionary(StringComparer.Ordinal); public SelectionPolicy(string path) { _path = path; Load(); } public bool Resolve(string key, bool defaultValue) { bool value; return (key != null && _overrides.TryGetValue(key, out value)) ? value : defaultValue; } public void Reload() { Load(); } public void Save(IEnumerable entries) { try { string directoryName = Path.GetDirectoryName(_path); if (!string.IsNullOrEmpty(directoryName)) { Directory.CreateDirectory(directoryName); } List list = new List { "# Valheim Profiler MonoBehaviour Frame selection overrides", "# + entry = enabled, - entry = disabled", "# Missing entries use defaults: mod Update/FixedUpdate/LateUpdate/OnGUI callbacks are enabled; Valheim and Other callbacks are disabled.", string.Empty }; foreach (BehaviourMethodEntry item in entries.OrderBy((BehaviourMethodEntry x) => x.Key, StringComparer.Ordinal)) { if (item.Selected != item.DefaultSelected) { list.Add((item.Selected ? "+ " : "- ") + item.Key); } } File.WriteAllLines(_path, list); Load(); } catch { } } private void Load() { _overrides.Clear(); try { if (!File.Exists(_path)) { return; } string[] array = File.ReadAllLines(_path); for (int i = 0; i < array.Length; i++) { string text = array[i]?.Trim(); if (string.IsNullOrEmpty(text) || text.StartsWith("#", StringComparison.Ordinal)) { continue; } bool value; if (text.StartsWith("+", StringComparison.Ordinal)) { value = true; } else { if (!text.StartsWith("-", StringComparison.Ordinal)) { continue; } value = false; } string text2 = text.Substring(1).Trim(); if (!string.IsNullOrEmpty(text2)) { _overrides[text2] = value; } } } catch { } } } private static readonly string[] DefaultCallbackNames = new string[4] { "Update", "FixedUpdate", "LateUpdate", "OnGUI" }; internal const string ToolId = "MonoBehaviourFrameProfiler"; internal const string HarmonyId = "shudnal.ValheimProfiler.MonoBehaviourFrameProfiler"; internal const string DisplayTitle = "MonoBehaviour Frame Profiler"; private const float GcCheckMinSampleMs = 1f; private const float VirtualizationOverscan = 80f; private const float GroupToggleWidth = 14f; private const int MaxDisplayedCount = 9999999; private const double MaxDisplayedMs = 999.999; private const double IsolatedRawMaxMinMs = 8.0; private const double IsolatedRawMaxMultiplier = 4.0; private const float MinSourceColumnWidth = 150f; private const float MaxSourceColumnWidth = 900f; private const float TimeColumnWidth = 82f; private const float CountColumnWidth = 82f; private const float AvgTimeColumnWidth = 104f; private const float AvgCountColumnWidth = 112f; private const float TypeColumnMinWidth = 260f; private const float WindowHorizontalPadding = 18f; private readonly ValheimProfilerApp _app; private readonly ManualLogSource _logger; private readonly WindowManager _windows; private readonly ThemeManager _theme; private readonly Harmony _harmony; private readonly ProfilerWindow _mainWindow; private readonly SelectionPolicy _selectionPolicy; private readonly object _lock = new object(); private readonly List _types = new List(); private readonly Dictionary _methodsByKey = new Dictionary(StringComparer.Ordinal); private readonly HashSet _instrumentedMethods = new HashSet(); private Dictionary _runtimeEntries = new Dictionary(); private readonly Dictionary _groupExpanded = new Dictionary(StringComparer.Ordinal); private readonly Dictionary _selectionGroupExpanded = new Dictionary(StringComparer.Ordinal); private readonly HashSet _typesPresentInActiveScene = new HashSet(); private List _cachedFlatRows = new List(); private List _cachedGroupedRows = new List(); private readonly List _selectionRows = new List(); private readonly object _analyticsQueueLock = new object(); private readonly object _analyticsProcessingLock = new object(); private readonly Queue _dirtyAnalyticsQueue = new Queue(); private readonly HashSet _queuedAnalyticsStats = new HashSet(); private readonly HashSet _activeAnalyticsStats = new HashSet(); private AutoResetEvent _analyticsWakeEvent; private Thread _analyticsThread; private volatile bool _analyticsThreadRunning; private float _lastAnalyticsSweepRealtime; private GUIStyle _labelStyle; private GUIStyle _headerLabelStyle; private GUIStyle _activeHeaderLabelStyle; private GUIStyle _groupLabelStyle; private GUIStyle _compactButtonStyle; private GUISkin _styleSkin; private Vector2 _profilerScroll; private Vector2 _selectionScroll; private Vector2 _helpScroll; private string _search = string.Empty; private string _lastSelectionSearch = string.Empty; private BehaviourSource _selectionSource = BehaviourSource.Mod; private bool _presentInActiveScene; private int _activeSceneHandle = int.MinValue; private bool _selectionRowsDirty = true; private bool _selectionDirty; private bool _selectionExpansionInitialized; private bool _listReady; private volatile bool _profilingActive; private bool _groupByMod = true; private bool _viewDirty = true; private bool _statsFrozen; private int _currentFrame; private int _currentRealtimeMs; private int _frozenFrame; private float _frozenRealtime; private int _lastObservedGc0; private int _lastObservedGc1; private int _lastObservedGc2; private MainTab _mainTab = MainTab.Profiler; private ProfilerView _profilerView = ProfilerView.MaxOver60Sec; private TableSortColumn _avgSortColumn = TableSortColumn.AvgMsPerFrame; private TableSortColumn _maxSortColumn = TableSortColumn.ThirdMax; private ProfilerView _lastRenderedProfilerView = (ProfilerView)(-1); private bool _lastRenderedGroupByMod; private float _nextViewRefreshTime; private float _drawSourceColumnWidth; private float _drawTimeColumnWidth; private float _drawCountColumnWidth; private float _drawAvgTimeColumnWidth; private float _drawAvgCountColumnWidth; private float _drawTypeColumnWidth; private float _drawContentWidth; private bool _layoutMetricsDirty = true; private float _lastLayoutWindowWidth = -1f; private ProfilerView _lastLayoutProfilerView = (ProfilerView)(-1); private bool _lastLayoutGroupByMod; private GUISkin _lastLayoutSkin; private string _status = "Not loaded."; private static readonly double MsPerTick = 1000.0 / (double)Stopwatch.Frequency; private static int _mainThreadId; private static MonoBehaviourProfilerTool _instance; string IProfilerTool.Id => "MonoBehaviourFrameProfiler"; string IProfilerTool.DisplayName => "MonoBehaviour Frame Profiler"; bool IProfilerTool.IsWindowVisible => IsWindowVisible; bool IProfilerTool.IsActive => _profilingActive; internal bool IsWindowVisible { get { return _mainWindow.RequestedVisible; } set { _mainWindow.RequestedVisible = value; } } private float CurrentRealtime => (float)Volatile.Read(in _currentRealtimeMs) * 0.001f; private int DisplayFrame => _statsFrozen ? _frozenFrame : _currentFrame; private float DisplayRealtime => _statsFrozen ? _frozenRealtime : CurrentRealtime; private float MainWindowWidth { get { //IL_0006: 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) Rect rect = _mainWindow.Rect; return ((Rect)(ref rect)).width; } } private float MainWindowHeight { get { //IL_0006: 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) Rect rect = _mainWindow.Rect; return ((Rect)(ref rect)).height; } } private float CurrentRowHeight { get { GUIStyle labelStyle = _labelStyle; return Mathf.Max(20f, ((labelStyle != null) ? labelStyle.lineHeight : 16f) + 4f); } } private float CurrentHeaderHeight { get { GUIStyle headerLabelStyle = _headerLabelStyle; return Mathf.Max(20f, ((headerLabelStyle != null) ? headerLabelStyle.lineHeight : 16f) + 4f); } } private float CurrentGroupHeight { get { GUIStyle groupLabelStyle = _groupLabelStyle; return Mathf.Max(22f, ((groupLabelStyle != null) ? groupLabelStyle.lineHeight : 16f) + 4f); } } private TableSortColumn CurrentSortColumn => (_profilerView == ProfilerView.Avg1s) ? _avgSortColumn : _maxSortColumn; private void StartAnalyticsThread() { if (!_analyticsThreadRunning) { if (_analyticsWakeEvent == null) { _analyticsWakeEvent = new AutoResetEvent(initialState: false); } _analyticsThreadRunning = true; _analyticsThread = new Thread(AnalyticsThreadLoop) { IsBackground = true, Name = "MonoBehaviourProfilerAnalytics" }; _analyticsThread.Start(); } } private void StopAnalyticsThread() { _analyticsThreadRunning = false; try { _analyticsWakeEvent?.Set(); } catch { } if (_analyticsThread != null && _analyticsThread.IsAlive) { try { _analyticsThread.Join(1000); } catch { } } _analyticsThread = null; try { _analyticsWakeEvent?.Dispose(); } catch { } _analyticsWakeEvent = null; } private void AnalyticsThreadLoop() { while (_analyticsThreadRunning) { try { _analyticsWakeEvent.WaitOne(200); lock (_analyticsProcessingLock) { if (_profilingActive) { ProcessQueuedAnalytics(CurrentRealtime, forceSweep: false); } } } catch { } } } private void ProcessQueuedAnalytics(float now, bool forceSweep) { List list; List list2; lock (_analyticsQueueLock) { list = new List(_dirtyAnalyticsQueue.Count); while (_dirtyAnalyticsQueue.Count > 0) { RollingProfilerStat item = _dirtyAnalyticsQueue.Dequeue(); _queuedAnalyticsStats.Remove(item); list.Add(item); } list2 = _activeAnalyticsStats.ToList(); } bool flag = forceSweep || now - _lastAnalyticsSweepRealtime >= 1f || now < _lastAnalyticsSweepRealtime; if (flag) { _lastAnalyticsSweepRealtime = now; } foreach (RollingProfilerStat item2 in list) { item2.ProcessBackgroundAnalytics(now); if (item2.HasAnyAnalyticsData) { lock (_analyticsQueueLock) { _activeAnalyticsStats.Add(item2); } } } if (!flag) { return; } foreach (RollingProfilerStat item3 in list2) { item3.ProcessBackgroundAnalytics(now); } lock (_analyticsQueueLock) { _activeAnalyticsStats.RemoveWhere((RollingProfilerStat stat) => !stat.HasAnyAnalyticsData); } } private void QueueAnalyticsWork(RollingProfilerStat stat) { lock (_analyticsQueueLock) { if (_queuedAnalyticsStats.Add(stat)) { _dirtyAnalyticsQueue.Enqueue(stat); } } try { _analyticsWakeEvent?.Set(); } catch { } } private void ClearAnalyticsQueues() { lock (_analyticsProcessingLock) { lock (_analyticsQueueLock) { _dirtyAnalyticsQueue.Clear(); _queuedAnalyticsStats.Clear(); _activeAnalyticsStats.Clear(); } } } private void FlushAnalyticsAt(float now) { lock (_analyticsProcessingLock) { ProcessQueuedAnalytics(now, forceSweep: true); } } private float GetRefreshInterval(ProfilerView view) { return (view == ProfilerView.Avg1s) ? 0.1f : 1f; } private void RefreshCachedViewIfNeeded() { bool flag = _lastRenderedProfilerView != _profilerView || _lastRenderedGroupByMod != _groupByMod; float displayRealtime = DisplayRealtime; float refreshInterval = GetRefreshInterval(_profilerView); if (flag || _viewDirty || !(displayRealtime < _nextViewRefreshTime)) { List list = BuildRows(DisplayFrame, DisplayRealtime); if (_groupByMod) { _cachedGroupedRows = BuildGroupedRows(list); _cachedFlatRows.Clear(); } else { _cachedFlatRows = list.Take(750).ToList(); _cachedGroupedRows.Clear(); } _lastRenderedProfilerView = _profilerView; _lastRenderedGroupByMod = _groupByMod; _viewDirty = false; _nextViewRefreshTime = displayRealtime + refreshInterval; } } private void RefreshBehaviourList() { if (_profilingActive) { return; } try { _status = "Scanning loaded MonoBehaviours..."; _selectionPolicy.Reload(); Dictionary dictionary = new Dictionary(); foreach (KeyValuePair pluginInfo in Chainloader.PluginInfos) { PluginInfo value = pluginInfo.Value; if (!((Object)(object)((value != null) ? value.Instance : null) == (Object)null)) { Assembly assembly = ((object)value.Instance).GetType().Assembly; BepInPlugin metadata = value.Metadata; string text = ((metadata != null) ? metadata.Name : null); if (string.IsNullOrWhiteSpace(text)) { BepInPlugin metadata2 = value.Metadata; text = ((metadata2 != null) ? metadata2.GUID : null); } if (string.IsNullOrWhiteSpace(text)) { text = assembly.GetName().Name; } dictionary[assembly] = text ?? "Unknown mod"; } } HashSet hashSet = new HashSet(); hashSet.Add(typeof(Player).Assembly); hashSet.Add(typeof(ZInput).Assembly); hashSet.Add(typeof(GuiScaler).Assembly); HashSet gameAssemblies = hashSet; string pluginRoot = NormalizeDirectoryPath(Paths.PluginPath); List list = new List(); Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies(); foreach (Assembly assembly2 in assemblies) { if (!ShouldInspectAssembly(assembly2, gameAssemblies, _app.Config.MonoBehaviourIncludeValheimProfilerCallbacks.Value)) { continue; } ClassifyAssembly(assembly2, gameAssemblies, dictionary, pluginRoot, out var source, out var groupName, out var groupId); Type[] loadableTypes = GetLoadableTypes(assembly2); foreach (Type type in loadableTypes) { if (!IsProfileableMonoBehaviourType(type)) { continue; } BehaviourTypeEntry behaviourTypeEntry = new BehaviourTypeEntry { Type = type, GroupId = groupId, GroupName = groupName, AssemblyName = (assembly2.GetName().Name ?? "Unknown assembly"), TypeName = (type.FullName ?? type.Name), Source = source, Expanded = false }; HashSet hashSet2 = new HashSet(); for (int k = 0; k < DefaultCallbackNames.Length; k++) { string methodName = DefaultCallbackNames[k]; MethodInfo methodInfo = FindEffectiveCallback(type, methodName); if (!(methodInfo == null) && hashSet2.Add(methodInfo)) { bool defaultSelected = source == BehaviourSource.Mod && IsModAssembly(methodInfo.DeclaringType?.Assembly, gameAssemblies, dictionary, pluginRoot); AddMethodEntry(behaviourTypeEntry, methodInfo, ParseCallbackKind(methodName), defaultSelected); } } if (behaviourTypeEntry.Methods.Count > 0) { list.Add(behaviourTypeEntry); } } } list.Sort(delegate(BehaviourTypeEntry left, BehaviourTypeEntry right) { int num3 = string.Compare(left.GroupName, right.GroupName, StringComparison.OrdinalIgnoreCase); return (num3 != 0) ? num3 : string.Compare(left.TypeName, right.TypeName, StringComparison.OrdinalIgnoreCase); }); lock (_lock) { _types.Clear(); _types.AddRange(list); _methodsByKey.Clear(); foreach (BehaviourTypeEntry type2 in _types) { foreach (BehaviourMethodEntry method in type2.Methods) { _methodsByKey[method.Key] = method; } } _runtimeEntries = new Dictionary(); _instrumentedMethods.Clear(); _groupExpanded.Clear(); _selectionGroupExpanded.Clear(); _typesPresentInActiveScene.Clear(); } _listReady = true; _selectionDirty = false; _selectionExpansionInitialized = false; if (_presentInActiveScene) { RefreshActiveScenePresence(); } ExpandPathsToSelected(collapseUnselected: true); _selectionExpansionInitialized = true; MarkSelectionRowsDirty(); MarkViewDirty(); int num = list.Sum((BehaviourTypeEntry x) => x.Methods.Count); int num2 = list.Sum((BehaviourTypeEntry x) => x.Methods.Count((BehaviourMethodEntry behaviourMethodEntry) => behaviourMethodEntry.Selected)); _status = $"List ready. Types: {list.Count}. Frame callbacks: {num}. Selected: {num2}."; } catch (Exception ex) { _status = "Scan error: " + ex.GetType().Name + ": " + ex.Message; _logger.LogError((object)ex); } } private void AddMethodEntry(BehaviourTypeEntry typeEntry, MethodInfo method, ProfileMethodKind kind, bool defaultSelected) { string key = BuildSelectionKey(typeEntry.Type, method); bool selected = _selectionPolicy.Resolve(key, defaultSelected); typeEntry.Methods.Add(new BehaviourMethodEntry { TypeEntry = typeEntry, Method = method, Key = key, DisplayName = (method?.Name ?? "(unknown method)"), Kind = kind, DefaultSelected = defaultSelected, Selected = selected }); } private void RefreshActiveScenePresence() { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_00c7: Unknown result type (might be due to invalid IL or missing references) //IL_00cc: Unknown result type (might be due to invalid IL or missing references) Scene activeScene = SceneManager.GetActiveScene(); int handle = ((Scene)(ref activeScene)).handle; List list; lock (_lock) { list = _types.ToList(); } HashSet hashSet = new HashSet(); foreach (BehaviourTypeEntry item in list) { if (item?.Type == null) { continue; } try { Object[] array = Resources.FindObjectsOfTypeAll(item.Type); foreach (Object obj in array) { Component val = (Component)(object)((obj is Component) ? obj : null); if (!((Object)(object)val == (Object)null) && !((Object)(object)val.gameObject == (Object)null)) { Scene scene = val.gameObject.scene; if (((Scene)(ref scene)).IsValid() && ((Scene)(ref scene)).handle == handle) { hashSet.Add(item.Type); break; } } } } catch { } } lock (_lock) { _typesPresentInActiveScene.Clear(); foreach (Type item2 in hashSet) { _typesPresentInActiveScene.Add(item2); } } _activeSceneHandle = handle; } private bool IsPresentInActiveScene(BehaviourTypeEntry typeEntry) { if (!_presentInActiveScene) { return true; } if (typeEntry?.Type == null) { return false; } lock (_lock) { return _typesPresentInActiveScene.Contains(typeEntry.Type); } } private static bool ShouldInspectAssembly(Assembly assembly, HashSet gameAssemblies, bool includeProfilerMonoBehaviours) { if (assembly == null || assembly.IsDynamic) { return false; } if (assembly == typeof(ValheimProfilerPlugin).Assembly && !includeProfilerMonoBehaviours) { return false; } string text; try { text = assembly.GetName().Name ?? string.Empty; } catch { return false; } if (gameAssemblies != null && gameAssemblies.Contains(assembly)) { return true; } if (text.StartsWith("UnityEngine", StringComparison.Ordinal) || text.StartsWith("System", StringComparison.Ordinal) || text.StartsWith("Microsoft", StringComparison.Ordinal) || text.StartsWith("Mono.", StringComparison.Ordinal) || text.StartsWith("netstandard", StringComparison.OrdinalIgnoreCase) || text.StartsWith("mscorlib", StringComparison.OrdinalIgnoreCase) || text.StartsWith("BepInEx", StringComparison.Ordinal) || text.StartsWith("0Harmony", StringComparison.Ordinal) || text.StartsWith("Harmony", StringComparison.Ordinal)) { return false; } return true; } private static void ClassifyAssembly(Assembly assembly, HashSet gameAssemblies, Dictionary directPluginNames, string pluginRoot, out BehaviourSource source, out string groupName, out string groupId) { string text = assembly.GetName().Name ?? "Unknown assembly"; if (gameAssemblies != null && gameAssemblies.Contains(assembly)) { source = BehaviourSource.Valheim; groupName = "Valheim"; groupId = "valheim|" + text; return; } if (directPluginNames.TryGetValue(assembly, out var value)) { source = BehaviourSource.Mod; groupName = value; groupId = "mod|" + text; return; } string assemblyLocation = GetAssemblyLocation(assembly); if (!string.IsNullOrEmpty(pluginRoot) && !string.IsNullOrEmpty(assemblyLocation) && assemblyLocation.StartsWith(pluginRoot, StringComparison.OrdinalIgnoreCase)) { source = BehaviourSource.Mod; groupName = text; groupId = "mod|" + text; } else { source = BehaviourSource.Other; groupName = text; groupId = "other|" + text; } } private static bool IsProfileableMonoBehaviourType(Type type) { if (type == null || type.IsAbstract || type.IsInterface || type.ContainsGenericParameters) { return false; } try { return type != typeof(MonoBehaviour) && typeof(MonoBehaviour).IsAssignableFrom(type); } catch { return false; } } private static MethodInfo FindEffectiveCallback(Type type, string methodName) { Type type2 = type; while (type2 != null && type2 != typeof(MonoBehaviour)) { MethodInfo methodInfo; try { methodInfo = type2.GetMethod(methodName, BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, Type.EmptyTypes, null); } catch { methodInfo = null; } if (!(methodInfo == null) && !(methodInfo.ReturnType != typeof(void)) && HasManagedBody(methodInfo)) { return methodInfo; } type2 = type2.BaseType; } return null; } private static bool IsModAssembly(Assembly assembly, HashSet gameAssemblies, Dictionary directPluginNames, string pluginRoot) { if (assembly == null || (gameAssemblies != null && gameAssemblies.Contains(assembly))) { return false; } if (directPluginNames.ContainsKey(assembly)) { return true; } string assemblyLocation = GetAssemblyLocation(assembly); return !string.IsNullOrEmpty(pluginRoot) && !string.IsNullOrEmpty(assemblyLocation) && assemblyLocation.StartsWith(pluginRoot, StringComparison.OrdinalIgnoreCase); } private static bool HasManagedBody(MethodBase method) { try { return method != null && method.GetMethodBody() != null; } catch { return false; } } private static ProfileMethodKind ParseCallbackKind(string methodName) { return methodName switch { "FixedUpdate" => ProfileMethodKind.FixedUpdate, "LateUpdate" => ProfileMethodKind.LateUpdate, "OnGUI" => ProfileMethodKind.OnGUI, _ => ProfileMethodKind.Update, }; } private static Type[] GetLoadableTypes(Assembly assembly) { try { return assembly.GetTypes(); } catch (ReflectionTypeLoadException ex) { return ex.Types?.Where((Type type) => type != null).ToArray() ?? Array.Empty(); } catch { return Array.Empty(); } } private static string BuildSelectionKey(Type type, MethodInfo method) { string text = type.Assembly.GetName().Name ?? "Unknown"; string text2 = type.FullName ?? type.Name; string text3; try { text3 = string.Join(",", (from parameter in method.GetParameters() select parameter.ParameterType.FullName ?? parameter.ParameterType.Name).ToArray()); } catch { text3 = "?"; } return text + "|" + text2 + "|" + method.Name + "(" + text3 + ")"; } private static string GetAssemblyLocation(Assembly assembly) { try { return NormalizeFilePath(assembly.Location); } catch { return string.Empty; } } private static string NormalizeDirectoryPath(string path) { string text = NormalizeFilePath(path); if (string.IsNullOrEmpty(text)) { return string.Empty; } string text2 = text.TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar); char directorySeparatorChar = Path.DirectorySeparatorChar; return text2 + directorySeparatorChar; } private static string NormalizeFilePath(string path) { if (string.IsNullOrEmpty(path)) { return string.Empty; } try { return Path.GetFullPath(path); } catch { return path; } } private static void TimingPrefix(ref long __state) { __state = Stopwatch.GetTimestamp(); } private static Exception TimingFinalizer(Exception __exception, long __state, MethodBase __originalMethod, object __instance) { try { if (__state <= 0 || Thread.CurrentThread.ManagedThreadId != _mainThreadId) { return __exception; } MonoBehaviourProfilerTool instance = _instance; if (instance == null || !instance._profilingActive) { return __exception; } long num = Stopwatch.GetTimestamp() - __state; if (num < 0) { return __exception; } double num2 = (double)num * MsPerTick; bool gcSample = false; if (num2 >= 1.0) { gcSample = GC.CollectionCount(0) != instance._lastObservedGc0 || GC.CollectionCount(1) != instance._lastObservedGc1 || GC.CollectionCount(2) != instance._lastObservedGc2; } int frameCount = Time.frameCount; float realtimeSinceStartup = Time.realtimeSinceStartup; Type runtimeType = __instance?.GetType(); instance.SetCurrentRealtime(realtimeSinceStartup); instance.Record(__originalMethod, runtimeType, num2, frameCount, realtimeSinceStartup, gcSample); } catch { } return __exception; } private void StartProfiling() { if (_profilingActive) { return; } if (!_listReady) { RefreshBehaviourList(); } if (!_listReady) { return; } try { ApplySelectionPolicy(); ResetAllStats(); StartProfilingCore(); } catch (Exception ex) { _status = "Start error: " + ex.GetType().Name + ": " + ex.Message; _logger.LogError((object)ex); } } private void StartProfilingCore() { //IL_0193: Unknown result type (might be due to invalid IL or missing references) //IL_0198: Unknown result type (might be due to invalid IL or missing references) //IL_01a0: Expected O, but got Unknown //IL_01b6: Unknown result type (might be due to invalid IL or missing references) //IL_01bb: Unknown result type (might be due to invalid IL or missing references) //IL_01c7: Expected O, but got Unknown Dictionary> dictionary = new Dictionary>(); int num = 0; lock (_lock) { foreach (BehaviourTypeEntry type in _types) { foreach (BehaviourMethodEntry method in type.Methods) { if (method.Selected && !(method.Method == null)) { num++; if (!dictionary.TryGetValue(method.Method, out var value)) { value = new List(); dictionary[method.Method] = value; } value.Add(new RuntimeBehaviourEntry(type.Type, method)); } } } _runtimeEntries = dictionary.ToDictionary((KeyValuePair> pair) => pair.Key, (KeyValuePair> pair) => pair.Value.ToArray()); _instrumentedMethods.Clear(); } if (num == 0) { _status = "No MonoBehaviour methods are selected."; return; } HarmonyMethod val = new HarmonyMethod(AccessTools.Method(typeof(MonoBehaviourProfilerTool), "TimingPrefix", (Type[])null, (Type[])null)) { priority = 0 }; HarmonyMethod val2 = new HarmonyMethod(AccessTools.Method(typeof(MonoBehaviourProfilerTool), "TimingFinalizer", (Type[])null, (Type[])null)) { priority = 800 }; int num2 = 0; foreach (MethodBase key in dictionary.Keys) { try { _harmony.Patch(key, val, (HarmonyMethod)null, (HarmonyMethod)null, val2, (HarmonyMethod)null); _instrumentedMethods.Add(key); num2++; } catch (Exception ex) { _logger.LogDebug((object)$"Could not instrument MonoBehaviour method {key}: {ex.GetType().Name}: {ex.Message}"); } } if (num2 == 0) { lock (_lock) { _runtimeEntries = new Dictionary(); _instrumentedMethods.Clear(); } _status = "No selected MonoBehaviour methods could be instrumented."; } else { _statsFrozen = false; _profilingActive = true; _status = $"Profiling enabled. Selected entries: {num}. Instrumented methods: {num2}."; MarkViewDirty(); } } private void StopProfiling() { try { StopProfilingInternal(freezeData: true); _status = "Profiling paused. Data frozen."; MarkViewDirty(); } catch (Exception ex) { _status = "Pause error: " + ex.GetType().Name + ": " + ex.Message; _logger.LogError((object)ex); } } private void StopProfilingInternal(bool freezeData) { bool profilingActive = _profilingActive; _profilingActive = false; _harmony.UnpatchSelf(); lock (_lock) { _instrumentedMethods.Clear(); _runtimeEntries = new Dictionary(); } if (profilingActive && freezeData) { float realtimeSinceStartup = Time.realtimeSinceStartup; int frameCount = Time.frameCount; SetCurrentRealtime(realtimeSinceStartup); FlushAnalyticsAt(realtimeSinceStartup); _frozenRealtime = realtimeSinceStartup; _frozenFrame = frameCount; _statsFrozen = true; } } private void ApplySelection() { bool profilingActive = _profilingActive; if (profilingActive) { StopProfilingInternal(freezeData: false); } ApplySelectionPolicy(); ResetAllStats(); if (profilingActive) { StartProfilingCore(); } else { _status = "Selection applied. Start profiling to collect data."; } _selectionDirty = false; MarkSelectionRowsDirty(); MarkViewDirty(); } private void ApplySelectionPolicy() { List entries; lock (_lock) { entries = _types.SelectMany((BehaviourTypeEntry type) => type.Methods).ToList(); } _selectionPolicy.Save(entries); } private void ResetAllStats() { lock (_lock) { foreach (BehaviourTypeEntry type in _types) { foreach (BehaviourMethodEntry method in type.Methods) { method.Stat.Reset(); } } } ClearAnalyticsQueues(); _statsFrozen = false; MarkViewDirty(); } private void Record(MethodBase instrumentedMethod, Type runtimeType, double elapsedMs, int frame, float now, bool gcSample) { if (!_profilingActive || instrumentedMethod == null) { return; } Dictionary runtimeEntries = _runtimeEntries; if (!runtimeEntries.TryGetValue(instrumentedMethod, out var value) || value == null || value.Length == 0) { return; } bool flag = false; if (runtimeType != null) { for (int i = 0; i < value.Length; i++) { if (value[i] != null && value[i].RuntimeType == runtimeType) { flag = true; break; } } } foreach (RuntimeBehaviourEntry runtimeBehaviourEntry in value) { if (runtimeBehaviourEntry == null || runtimeBehaviourEntry.Entry == null) { continue; } if (runtimeType != null) { if (flag) { if (runtimeBehaviourEntry.RuntimeType != runtimeType) { continue; } } else if (runtimeBehaviourEntry.RuntimeType == null || !runtimeBehaviourEntry.RuntimeType.IsAssignableFrom(runtimeType)) { continue; } } RollingProfilerStat stat = runtimeBehaviourEntry.Entry.Stat; if (stat.Add(elapsedMs, frame, now, gcSample)) { QueueAnalyticsWork(stat); } } } private void PrepareTableLayout() { if (_layoutMetricsDirty || !(Mathf.Abs(_lastLayoutWindowWidth - MainWindowWidth) < 0.1f) || _lastLayoutProfilerView != _profilerView || _lastLayoutGroupByMod != _groupByMod || !((Object)(object)_lastLayoutSkin == (Object)(object)GUI.skin)) { _drawTimeColumnWidth = CalculateHeaderAwareWidth(82f, "raw max", "2nd max", "3rd max", "avg >p99", "p99", "avg >p95", "p95", "000.000!"); _drawCountColumnWidth = CalculateHeaderAwareWidth(82f, "> p99", "> p95", "samples", "gc samples", "9999999+"); _drawAvgTimeColumnWidth = CalculateHeaderAwareWidth(104f, "ms per frame", "000.000"); _drawAvgCountColumnWidth = CalculateHeaderAwareWidth(112f, "calls per frame", "9999999+"); _drawSourceColumnWidth = CalculateSourceColumnWidth(); _drawTypeColumnWidth = CalculateTypeColumnWidth(); float num = ((_profilerView == ProfilerView.Avg1s) ? (_drawAvgTimeColumnWidth + _drawAvgCountColumnWidth) : (_drawTimeColumnWidth * 7f + _drawCountColumnWidth * 4f)); _drawContentWidth = _drawSourceColumnWidth + num + _drawTypeColumnWidth; _lastLayoutWindowWidth = MainWindowWidth; _lastLayoutProfilerView = _profilerView; _lastLayoutGroupByMod = _groupByMod; _lastLayoutSkin = GUI.skin; _layoutMetricsDirty = false; } } private float CalculateHeaderAwareWidth(float minimum, params string[] values) { //IL_002b: Unknown result type (might be due to invalid IL or missing references) //IL_0035: Expected O, but got Unknown //IL_0030: Unknown result type (might be due to invalid IL or missing references) float num = minimum; if (_headerLabelStyle == null) { return num; } foreach (string text in values) { num = Mathf.Max(num, _headerLabelStyle.CalcSize(new GUIContent(text)).x + 6f); } return Mathf.Ceil(num); } private float CalculateSourceColumnWidth() { //IL_002c: Unknown result type (might be due to invalid IL or missing references) //IL_0036: Expected O, but got Unknown //IL_0031: Unknown result type (might be due to invalid IL or missing references) //IL_009e: Unknown result type (might be due to invalid IL or missing references) //IL_00a8: Expected O, but got Unknown //IL_00a3: Unknown result type (might be due to invalid IL or missing references) //IL_0156: Unknown result type (might be due to invalid IL or missing references) //IL_0160: Expected O, but got Unknown //IL_015b: Unknown result type (might be due to invalid IL or missing references) //IL_00eb: Unknown result type (might be due to invalid IL or missing references) //IL_00f5: Expected O, but got Unknown //IL_00f0: Unknown result type (might be due to invalid IL or missing references) string text = (_groupByMod ? "Mod / callback" : "Mod:MonoBehaviour:Callback"); float num = ((_headerLabelStyle != null) ? (_headerLabelStyle.CalcSize(new GUIContent(text)).x + 12f) : 150f); lock (_lock) { foreach (BehaviourTypeEntry type in _types) { if (_groupByMod) { float num2 = 14f + _groupLabelStyle.CalcSize(new GUIContent(type.GroupName ?? "Unknown")).x + 12f; num = Mathf.Max(num, num2); foreach (BehaviourMethodEntry method in type.Methods) { string text2 = BuildGroupedEntryName(method); float num3 = _labelStyle.CalcSize(new GUIContent(text2)).x + 12f; num = Mathf.Max(num, num3); } continue; } foreach (BehaviourMethodEntry method2 in type.Methods) { string text3 = BuildUngroupedEntryName(method2); float num4 = _labelStyle.CalcSize(new GUIContent(text3)).x + 12f; num = Mathf.Max(num, num4); } } } float num5 = 900f; if (_groupByMod) { num5 = Mathf.Min(num5, CalculateUngroupedSourceColumnWidth()); } return Mathf.Clamp(Mathf.Ceil(num), 150f, num5); } private float CalculateUngroupedSourceColumnWidth() { //IL_001b: Unknown result type (might be due to invalid IL or missing references) //IL_0025: Expected O, but got Unknown //IL_0020: Unknown result type (might be due to invalid IL or missing references) //IL_0089: Unknown result type (might be due to invalid IL or missing references) //IL_0093: Expected O, but got Unknown //IL_008e: Unknown result type (might be due to invalid IL or missing references) float num = ((_headerLabelStyle != null) ? (_headerLabelStyle.CalcSize(new GUIContent("Mod:MonoBehaviour:Callback")).x + 12f) : 150f); lock (_lock) { foreach (BehaviourTypeEntry type in _types) { foreach (BehaviourMethodEntry method in type.Methods) { string text = BuildUngroupedEntryName(method); float num2 = _labelStyle.CalcSize(new GUIContent(text)).x + 12f; num = Mathf.Max(num, num2); } } } return Mathf.Clamp(Mathf.Ceil(num), 150f, 900f); } private float CalculateTypeColumnWidth() { //IL_001b: Unknown result type (might be due to invalid IL or missing references) //IL_0025: Expected O, but got Unknown //IL_0020: 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_007d: Expected O, but got Unknown //IL_0078: Unknown result type (might be due to invalid IL or missing references) float num = ((_headerLabelStyle != null) ? (_headerLabelStyle.CalcSize(new GUIContent("MonoBehaviour")).x + 12f) : 260f); lock (_lock) { foreach (BehaviourTypeEntry type in _types) { num = Mathf.Max(num, _labelStyle.CalcSize(new GUIContent(type.TypeName ?? "Unknown")).x + 12f); } } return Mathf.Clamp(Mathf.Ceil(num), 260f, 720f); } private static string BuildGroupedEntryName(BehaviourMethodEntry entry) { if (entry == null) { return "Unknown.Unknown"; } string text = entry.TypeEntry?.Type?.Name ?? GetShortTypeName(entry.TypeEntry?.TypeName); string text2 = entry.DisplayName ?? "Unknown"; return text + "." + text2; } private static string BuildUngroupedEntryName(BehaviourMethodEntry entry) { if (entry == null) { return "Unknown:Unknown:Unknown"; } string text = entry.TypeEntry?.GroupName ?? "Unknown"; string text2 = entry.TypeEntry?.Type?.Name ?? GetShortTypeName(entry.TypeEntry?.TypeName); string text3 = entry.DisplayName ?? "Unknown"; return text + ":" + text2 + ":" + text3; } private static string GetShortTypeName(string fullName) { if (string.IsNullOrEmpty(fullName)) { return "Unknown"; } int val = fullName.LastIndexOf('.'); int val2 = fullName.LastIndexOf('+'); int num = Math.Max(val, val2); return (num >= 0 && num + 1 < fullName.Length) ? fullName.Substring(num + 1) : fullName; } private void DrawTableHeader() { //IL_0029: Unknown result type (might be due to invalid IL or missing references) //IL_002e: 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) Rect rect = GUILayoutUtility.GetRect(1f, CurrentHeaderHeight, (GUILayoutOption[])(object)new GUILayoutOption[2] { GUILayout.ExpandWidth(true), GUILayout.Height(CurrentHeaderHeight) }); GUI.BeginGroup(rect); try { float x = 0f - _profilerScroll.x; float y = 0f; float height = ((Rect)(ref rect)).height; DrawSortableHeaderCell(ref x, y, _drawSourceColumnWidth, height, _groupByMod ? "Mod / callback" : "Mod:MonoBehaviour:Callback", _groupByMod ? "Grouped view: mod or assembly names are shown in group rows, and short MonoBehaviour type plus callback are shown in child rows.\nClick to sort descending by this column." : "Ungrouped view: Mod:short MonoBehaviour name:callback name.\nClick to sort descending by this column.", TableSortColumn.Source); if (_profilerView == ProfilerView.Avg1s) { DrawSortableHeaderCell(ref x, y, _drawAvgTimeColumnWidth, height, "ms per frame", "Average inclusive execution time contributed by this callback per rendered frame in the rolling one-second window.\nClick to sort descending by this column.", TableSortColumn.AvgMsPerFrame); DrawSortableHeaderCell(ref x, y, _drawAvgCountColumnWidth, height, "calls per frame", "Average number of callback invocations per rendered frame in the rolling one-second window.\nClick to sort descending by this column.", TableSortColumn.AvgCallsPerFrame); } else { DrawSortableHeaderCell(ref x, y, _drawTimeColumnWidth, height, "raw max", "Slowest individual callback invocation in the rolling 60-second window.\nA trailing ! marks an isolated spike or a high GC-associated sample.\nClick to sort descending by this column.", TableSortColumn.RawMax); DrawSortableHeaderCell(ref x, y, _drawTimeColumnWidth, height, "2nd max", "Second-slowest individual callback invocation in the rolling 60-second window.\nClick to sort descending by this column.", TableSortColumn.SecondMax); DrawSortableHeaderCell(ref x, y, _drawTimeColumnWidth, height, "3rd max", "Third-slowest individual callback invocation in the rolling 60-second window.\nClick to sort descending by this column.", TableSortColumn.ThirdMax); DrawSortableHeaderCell(ref x, y, _drawCountColumnWidth, height, "> p99", "Number of samples above the approximate p99 histogram boundary.\nClick to sort descending by this column.", TableSortColumn.AboveP99); DrawSortableHeaderCell(ref x, y, _drawTimeColumnWidth, height, "avg >p99", "Average execution time of samples above the approximate p99 boundary.\nClick to sort descending by this column.", TableSortColumn.AvgAboveP99); DrawSortableHeaderCell(ref x, y, _drawTimeColumnWidth, height, "p99", "Approximate 99th percentile: about 99% of callback invocations completed at or below this duration.\nClick to sort descending by this column.", TableSortColumn.P99); DrawSortableHeaderCell(ref x, y, _drawCountColumnWidth, height, "> p95", "Number of samples above the approximate p95 histogram boundary.\nClick to sort descending by this column.", TableSortColumn.AboveP95); DrawSortableHeaderCell(ref x, y, _drawTimeColumnWidth, height, "avg >p95", "Average execution time of samples above the approximate p95 boundary.\nClick to sort descending by this column.", TableSortColumn.AvgAboveP95); DrawSortableHeaderCell(ref x, y, _drawTimeColumnWidth, height, "p95", "Approximate 95th percentile: about 95% of callback invocations completed at or below this duration.\nClick to sort descending by this column.", TableSortColumn.P95); DrawSortableHeaderCell(ref x, y, _drawCountColumnWidth, height, "samples", "Number of callback invocations currently retained in the rolling 60-second window.\nClick to sort descending by this column.", TableSortColumn.Samples); DrawSortableHeaderCell(ref x, y, _drawCountColumnWidth, height, "gc samples", "Slow callback invocations observed in a frame where a managed GC collection counter changed.\nThis is correlation, not proof that the callback caused the collection.\nClick to sort descending by this column.", TableSortColumn.GcSamples); } DrawSortableHeaderCell(ref x, y, _drawTypeColumnWidth, height, "MonoBehaviour", "Managed MonoBehaviour runtime type being measured.\nClick to sort descending by this column.", TableSortColumn.MonoBehaviour); } finally { GUI.EndGroup(); } } private void DrawSortableHeaderCell(ref float x, float y, float width, float height, string text, string tooltip, TableSortColumn column) { //IL_0027: Unknown result type (might be due to invalid IL or missing references) //IL_003e: Unknown result type (might be due to invalid IL or missing references) //IL_0049: Expected O, but got Unknown Rect val = default(Rect); ((Rect)(ref val))..ctor(x, y, width, height); GUIStyle val2 = ((CurrentSortColumn == column) ? _activeHeaderLabelStyle : _headerLabelStyle); if (GUI.Button(val, new GUIContent(text ?? string.Empty, tooltip ?? string.Empty), val2)) { SetSortColumn(column); } x += width; } private float CalculateTableContentHeight() { if (!_groupByMod) { return Mathf.Max(1f, (float)_cachedFlatRows.Count * CurrentRowHeight); } float num = 0f; lock (_lock) { foreach (GroupRowView cachedGroupedRow in _cachedGroupedRows) { num += CurrentGroupHeight; if (_groupExpanded.TryGetValue(cachedGroupedRow.GroupId, out var value) && value) { num += (float)cachedGroupedRow.Rows.Count * CurrentRowHeight; } } } return Mathf.Max(1f, num); } private void DrawTableContent(Rect contentRect) { //IL_0144: Unknown result type (might be due to invalid IL or missing references) //IL_006e: Unknown result type (might be due to invalid IL or missing references) //IL_00e0: Unknown result type (might be due to invalid IL or missing references) float visibleTop = _profilerScroll.y - 80f; float visibleBottom = _profilerScroll.y + Mathf.Max(100f, MainWindowHeight - 145f) + 80f; float y = 0f; if (_groupByMod) { foreach (GroupRowView cachedGroupedRow in _cachedGroupedRows) { DrawGroupRow(contentRect, ref y, cachedGroupedRow, visibleTop, visibleBottom); bool flag; lock (_lock) { flag = _groupExpanded.TryGetValue(cachedGroupedRow.GroupId, out var value) && value; } if (flag) { foreach (FlatRowView row in cachedGroupedRow.Rows) { DrawDataRow(contentRect, ref y, row, grouped: true, visibleTop, visibleBottom); } } } return; } foreach (FlatRowView cachedFlatRow in _cachedFlatRows) { DrawDataRow(contentRect, ref y, cachedFlatRow, grouped: false, visibleTop, visibleBottom); } } private void DrawGroupRow(Rect contentRect, ref float y, GroupRowView group, float visibleTop, float visibleBottom) { //IL_007f: Unknown result type (might be due to invalid IL or missing references) //IL_0084: Unknown result type (might be due to invalid IL or missing references) //IL_0089: Unknown result type (might be due to invalid IL or missing references) //IL_008b: Unknown result type (might be due to invalid IL or missing references) //IL_00c6: Unknown result type (might be due to invalid IL or missing references) float currentGroupHeight = CurrentGroupHeight; if (!RowVisible(y, currentGroupHeight, visibleTop, visibleBottom)) { y += currentGroupHeight; return; } bool flag; lock (_lock) { flag = _groupExpanded.TryGetValue(group.GroupId, out var value) && value; } float x = ((Rect)(ref contentRect)).x; float num = ((Rect)(ref contentRect)).y + y; Rect val = InsetButtonRect(new Rect(x, num, 14f, currentGroupHeight)); bool flag2 = GUI.Button(val, flag ? "▼" : "▶", _compactButtonStyle); x += 14f; float num2 = _drawSourceColumnWidth - 14f; flag2 |= GUI.Button(new Rect(x, num, num2, currentGroupHeight), group.GroupName, _groupLabelStyle); x += num2; if (flag2) { lock (_lock) { _groupExpanded[group.GroupId] = !flag; } MarkViewDirty(); } if (_profilerView == ProfilerView.Avg1s) { DrawCell(ref x, num, _drawAvgTimeColumnWidth, currentGroupHeight, FormatMs(group.Summary.Avg1sMsPerFrame), _labelStyle); DrawCell(ref x, num, _drawAvgCountColumnWidth, currentGroupHeight, FormatCount(group.Summary.Avg1sCallsPerFrame), _labelStyle); } else { DrawMaxColumns(ref x, num, currentGroupHeight, group.Summary.Max); } DrawCell(ref x, num, _drawTypeColumnWidth, currentGroupHeight, string.Empty, _labelStyle); y += currentGroupHeight; } private void DrawDataRow(Rect contentRect, ref float y, FlatRowView row, bool grouped, float visibleTop, float visibleBottom) { float currentRowHeight = CurrentRowHeight; if (!RowVisible(y, currentRowHeight, visibleTop, visibleBottom)) { y += currentRowHeight; return; } BehaviourMethodEntry entry = row.Entry; float x = ((Rect)(ref contentRect)).x; float y2 = ((Rect)(ref contentRect)).y + y; string text = (grouped ? BuildGroupedEntryName(entry) : BuildUngroupedEntryName(entry)); DrawCell(ref x, y2, _drawSourceColumnWidth, currentRowHeight, text, _labelStyle); if (_profilerView == ProfilerView.Avg1s) { DrawCell(ref x, y2, _drawAvgTimeColumnWidth, currentRowHeight, FormatMs(row.Snapshot.Avg1sMsPerFrame), _labelStyle); DrawCell(ref x, y2, _drawAvgCountColumnWidth, currentRowHeight, FormatCount(row.Snapshot.Avg1sCallsPerFrame), _labelStyle); } else { DrawMaxColumns(ref x, y2, currentRowHeight, row.Snapshot.MaxSnapshot); } DrawCell(ref x, y2, _drawTypeColumnWidth, currentRowHeight, entry.TypeEntry.TypeName, _labelStyle); y += currentRowHeight; } private void DrawMaxColumns(ref float x, float y, float height, RollingMaxSnapshot max) { DrawCell(ref x, y, _drawTimeColumnWidth, height, FormatRawMax(max), _labelStyle); DrawCell(ref x, y, _drawTimeColumnWidth, height, FormatMs(max.SecondMaxMs), _labelStyle); DrawCell(ref x, y, _drawTimeColumnWidth, height, FormatMs(max.ThirdMaxMs), _labelStyle); DrawCell(ref x, y, _drawCountColumnWidth, height, FormatCount(max.AboveP99Count), _labelStyle); DrawCell(ref x, y, _drawTimeColumnWidth, height, FormatMs(max.AvgAboveP99Ms), _labelStyle); DrawCell(ref x, y, _drawTimeColumnWidth, height, FormatMs(max.P99Ms), _labelStyle); DrawCell(ref x, y, _drawCountColumnWidth, height, FormatCount(max.AboveP95Count), _labelStyle); DrawCell(ref x, y, _drawTimeColumnWidth, height, FormatMs(max.AvgAboveP95Ms), _labelStyle); DrawCell(ref x, y, _drawTimeColumnWidth, height, FormatMs(max.P95Ms), _labelStyle); DrawCell(ref x, y, _drawCountColumnWidth, height, FormatCount(max.WindowSampleCount), _labelStyle); DrawCell(ref x, y, _drawCountColumnWidth, height, FormatCount(max.GcSampleCount), _labelStyle); } private static bool RowVisible(float y, float height, float visibleTop, float visibleBottom) { return y + height >= visibleTop && y <= visibleBottom; } private static Rect InsetButtonRect(Rect rect) { //IL_0067: 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_006b: Unknown result type (might be due to invalid IL or missing references) ((Rect)(ref rect)).x = ((Rect)(ref rect)).x + 1f; ((Rect)(ref rect)).y = ((Rect)(ref rect)).y + 3f; ((Rect)(ref rect)).width = Mathf.Max(1f, ((Rect)(ref rect)).width - 3f); ((Rect)(ref rect)).height = Mathf.Max(1f, ((Rect)(ref rect)).height - 6f); return rect; } private static void DrawCell(ref float x, float y, float width, float height, string text, GUIStyle style, string tooltip = null) { //IL_0006: 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) //IL_002d: Expected O, but got Unknown GUI.Label(new Rect(x, y, width, height), new GUIContent(text ?? string.Empty, tooltip ?? string.Empty), style); x += width; } internal MonoBehaviourProfilerTool(ValheimProfilerApp app) { //IL_018b: Unknown result type (might be due to invalid IL or missing references) //IL_0195: Expected O, but got Unknown //IL_020c: Unknown result type (might be due to invalid IL or missing references) //IL_020d: Unknown result type (might be due to invalid IL or missing references) //IL_0212: Unknown result type (might be due to invalid IL or missing references) //IL_0224: Unknown result type (might be due to invalid IL or missing references) //IL_0229: Unknown result type (might be due to invalid IL or missing references) //IL_022a: Unknown result type (might be due to invalid IL or missing references) //IL_022f: Unknown result type (might be due to invalid IL or missing references) _app = app ?? throw new ArgumentNullException("app"); _logger = app.Logger; _windows = app.Windows; _theme = app.Theme; _harmony = new Harmony("shudnal.ValheimProfiler.MonoBehaviourFrameProfiler"); _selectionPolicy = new SelectionPolicy(ProfilerPaths.GetConfigFilePath("MonoBehaviourFrameSelection.cfg")); _mainThreadId = Thread.CurrentThread.ManagedThreadId; ValheimProfilerConfig config = app.Config; _avgSortColumn = ParseSortColumn(config.MonoBehaviourProfilerAvgSortColumn.Value, ProfilerView.Avg1s, TableSortColumn.AvgMsPerFrame); _maxSortColumn = ParseSortColumn(config.MonoBehaviourProfilerMaxSortColumn.Value, ProfilerView.MaxOver60Sec, TableSortColumn.ThirdMax); Vector2 minimumSize = default(Vector2); ((Vector2)(ref minimumSize))..ctor(760f, 420f); Vector2 defaultToolWindowSize = _windows.GetDefaultToolWindowSize(650f, minimumSize); _mainWindow = _windows.Register(new ProfilerWindow("ValheimProfiler.MonoBehaviourFrameProfiler", "MonoBehaviour Frame Profiler", new Rect(ValheimProfilerConfig.DefaultMonoBehaviourWindowPosition, defaultToolWindowSize), minimumSize, resizable: true, requestedVisible: false, DrawWindow, config.MonoBehaviourWindowPosition, config.MonoBehaviourWindowSize)); SetCurrentRealtime(Time.realtimeSinceStartup); UpdateGcBaseline(); _analyticsWakeEvent = new AutoResetEvent(initialState: false); StartAnalyticsThread(); _instance = this; } void IProfilerTool.ShowWindow() { ShowWindow(); } void IProfilerTool.ToggleWindow() { ToggleWindow(); } void IProfilerTool.Update() { Update(); } void IProfilerTool.Shutdown() { Shutdown(); } internal void ToggleWindow() { IsWindowVisible = !IsWindowVisible; if (IsWindowVisible) { ShowWindow(); } } internal void ShowWindow() { IsWindowVisible = true; _app.ShowUi(); _windows.BringToFront(_mainWindow); if (!_listReady) { RefreshBehaviourList(); } } internal void Update() { //IL_002a: 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) _currentFrame = Time.frameCount; SetCurrentRealtime(Time.realtimeSinceStartup); UpdateGcBaseline(); if (_presentInActiveScene) { Scene activeScene = SceneManager.GetActiveScene(); int handle = ((Scene)(ref activeScene)).handle; if (handle != _activeSceneHandle) { RefreshActiveScenePresence(); MarkSelectionRowsDirty(); } } } internal void Shutdown() { if (_instance == this) { _instance = null; } try { StopProfilingInternal(freezeData: false); } catch { } StopAnalyticsThread(); IsWindowVisible = false; } private void SetCurrentRealtime(float realtime) { Interlocked.Exchange(ref _currentRealtimeMs, Mathf.RoundToInt(realtime * 1000f)); } private void UpdateGcBaseline() { _lastObservedGc0 = GC.CollectionCount(0); _lastObservedGc1 = GC.CollectionCount(1); _lastObservedGc2 = GC.CollectionCount(2); } private void MarkViewDirty() { _viewDirty = true; _layoutMetricsDirty = true; _nextViewRefreshTime = 0f; } private void MarkSelectionRowsDirty() { _selectionRowsDirty = true; } private void DrawWindow(int id) { //IL_0008: 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_0014: Unknown result type (might be due to invalid IL or missing references) //IL_0212: Unknown result type (might be due to invalid IL or missing references) //IL_0182: Unknown result type (might be due to invalid IL or missing references) //IL_019c: Expected O, but got Unknown EnsureStyles(); Color contentColor = GUI.contentColor; GUI.contentColor = _theme.TextColor; try { GUILayout.BeginVertical(Array.Empty()); DrawToolbar(); GUILayout.Space(2f); GUILayout.BeginHorizontal(Array.Empty()); MainTab mainTab = (MainTab)GUILayout.Toolbar((int)_mainTab, new string[3] { "Profiler", "Behaviours to profile", "Help" }, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(470f) }); if (mainTab != _mainTab) { _mainTab = mainTab; MarkViewDirty(); if (_mainTab == MainTab.BehavioursToProfile) { ExpandPathsToSelected(collapseUnselected: false); _selectionExpansionInitialized = true; MarkSelectionRowsDirty(); } } if (_mainTab == MainTab.Profiler) { GUILayout.Space(10f); Label("View:", GUILayout.Width(40f)); ProfilerView profilerView = (ProfilerView)GUILayout.Toolbar((int)_profilerView, new string[2] { "Over 1 sec", "Max over 60 sec" }, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(290f) }); if (profilerView != _profilerView) { _profilerView = profilerView; MarkViewDirty(); } GUILayout.Space(10f); bool flag = ProfilerGui.ToggleLayout(_theme, _groupByMod, new GUIContent("Group by Mod", "Group callbacks by their BepInEx mod or assembly.\nGroup summaries contain inclusive values and can overlap when inherited callbacks are selected for multiple runtime types."), 135f, _labelStyle, 0f); if (flag != _groupByMod) { _groupByMod = flag; MarkViewDirty(); } } GUILayout.EndHorizontal(); GUILayout.Space(3f); switch (_mainTab) { case MainTab.Profiler: DrawProfilerTab(); break; case MainTab.BehavioursToProfile: DrawSelectionTab(); break; default: DrawHelpTab(); break; } GUILayout.EndVertical(); } finally { GUI.contentColor = contentColor; } } private void DrawToolbar() { GUILayout.BeginHorizontal(Array.Empty()); string text = (_listReady ? $"List: ready ({_methodsByKey.Count})" : "List: empty"); string text2 = (_profilingActive ? "Profiling: ON" : (_statsFrozen ? "Profiling: PAUSED" : "Profiling: OFF")); Label(text + " | " + text2 + " | Status: " + _status, GUILayout.ExpandWidth(true)); string text3 = (_profilingActive ? "Pause profiling" : "Start profiling"); if (GUILayout.Button(text3, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(125f) })) { if (_profilingActive) { StopProfiling(); } else { StartProfiling(); } } if (GUILayout.Button("Reset stats", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(100f) })) { ResetAllStats(); _status = "Statistics reset."; } if (GUILayout.Button("Close", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(70f) })) { IsWindowVisible = false; } GUILayout.EndHorizontal(); } private void DrawProfilerTab() { //IL_01c0: Unknown result type (might be due to invalid IL or missing references) //IL_01cc: Unknown result type (might be due to invalid IL or missing references) //IL_01d1: Unknown result type (might be due to invalid IL or missing references) //IL_01fa: Unknown result type (might be due to invalid IL or missing references) //IL_01ff: Unknown result type (might be due to invalid IL or missing references) //IL_0201: Unknown result type (might be due to invalid IL or missing references) RefreshCachedViewIfNeeded(); PrepareTableLayout(); DrawTableHeader(); if (!(_groupByMod ? (_cachedGroupedRows.Count > 0) : (_cachedFlatRows.Count > 0))) { Label("No data yet. Start profiling and trigger selected MonoBehaviour frame callbacks."); return; } if (_groupByMod) { GUILayout.BeginHorizontal(Array.Empty()); if (GUILayout.Button("Expand all", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(88f) })) { lock (_lock) { foreach (string item in _groupExpanded.Keys.ToList()) { _groupExpanded[item] = true; } } } if (GUILayout.Button("Collapse all", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(88f) })) { lock (_lock) { foreach (string item2 in _groupExpanded.Keys.ToList()) { _groupExpanded[item2] = false; } } } GUILayout.EndHorizontal(); GUILayout.Space(2f); } float num = CalculateTableContentHeight(); _profilerScroll = GUILayout.BeginScrollView(_profilerScroll, false, true, Array.Empty()); Rect rect = GUILayoutUtility.GetRect(_drawContentWidth, num, (GUILayoutOption[])(object)new GUILayoutOption[2] { GUILayout.Width(_drawContentWidth), GUILayout.Height(num) }); DrawTableContent(rect); GUILayout.EndScrollView(); } private void DrawHelpTab() { //IL_0003: 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_0012: Unknown result type (might be due to invalid IL or missing references) _helpScroll = GUILayout.BeginScrollView(_helpScroll, Array.Empty()); HeaderLabel("Purpose"); Label("MonoBehaviour Frame Profiler measures selected managed frame callbacks on the Unity main thread."); Label("Mod Update, FixedUpdate, LateUpdate and OnGUI callbacks are selected by default. Valheim and Other callbacks are disabled by default."); GUILayout.Space(6f); HeaderLabel("Inclusive time"); Label("All reported times are inclusive: a callback includes time spent in methods and Harmony patches called from inside it."); Label("Inherited callbacks can be represented by more than one selected runtime type, and nested work can overlap with measurements from other profiler tools."); Label("Do not add arbitrary rows together as exclusive frame cost. Group summaries are diagnostic aggregates and can contain overlapping inclusive time."); GUILayout.Space(6f); HeaderLabel("Over 1 sec"); Label("\"ms per frame\" is the average inclusive callback time per frame in a rolling one-second window."); Label("\"calls per frame\" is the average number of callback invocations per frame in the same window."); GUILayout.Space(6f); HeaderLabel("Max over 60 sec"); Label("raw max, 2nd max and 3rd max are the three slowest callback invocations in the rolling 60-second window."); Label("p95 and p99 are approximate histogram percentiles. GC samples mark slow calls observed in a frame where a GC collection counter changed."); Label("A trailing ! marks an isolated high spike or a high GC-associated sample."); Label("Click a table column header to sort descending by that column. The active sort column is highlighted."); GUILayout.Space(6f); HeaderLabel("Selection"); Label("Use Behaviours to profile to enable or disable individual frame callbacks."); Label("Mods, Valheim and Other are exclusive view tabs. Present in active scene is an optional view filter and never deletes saved selections."); Label("Selection changes take effect after Apply selection. Active profiling is restarted and statistics are reset."); Label("Selection overrides are stored in BepInEx/config/shudnal.ValheimProfiler/MonoBehaviourFrameSelection.cfg."); Label("The Include Valheim Profiler callbacks config option makes this profiler available in its own behaviour list for development measurements."); GUILayout.Space(6f); HeaderLabel("Notes"); Label("Pause profiling freezes rolling windows so the captured values can be inspected."); Label("The profiler adds overhead. Profile only the callbacks needed for the current investigation."); Label("Rare lifecycle and one-time methods will be handled by a separate Call/Lifecycle Profiler in a future version."); GUILayout.EndScrollView(); } private void EnsureStyles() { //IL_0045: Unknown result type (might be due to invalid IL or missing references) //IL_004a: Unknown result type (might be due to invalid IL or missing references) //IL_0052: Unknown result type (might be due to invalid IL or missing references) //IL_005a: Unknown result type (might be due to invalid IL or missing references) //IL_0067: Expected O, but got Unknown //IL_0078: Unknown result type (might be due to invalid IL or missing references) //IL_008e: Unknown result type (might be due to invalid IL or missing references) //IL_0093: Unknown result type (might be due to invalid IL or missing references) //IL_009b: Unknown result type (might be due to invalid IL or missing references) //IL_00a3: Unknown result type (might be due to invalid IL or missing references) //IL_00ab: Unknown result type (might be due to invalid IL or missing references) //IL_00b8: Expected O, but got Unknown //IL_00c9: Unknown result type (might be due to invalid IL or missing references) //IL_00db: Unknown result type (might be due to invalid IL or missing references) //IL_00e5: Expected O, but got Unknown //IL_00eb: Unknown result type (might be due to invalid IL or missing references) //IL_00f6: Unknown result type (might be due to invalid IL or missing references) //IL_0100: Unknown result type (might be due to invalid IL or missing references) //IL_0105: Unknown result type (might be due to invalid IL or missing references) //IL_0111: Unknown result type (might be due to invalid IL or missing references) //IL_0123: Unknown result type (might be due to invalid IL or missing references) //IL_0135: Unknown result type (might be due to invalid IL or missing references) //IL_0147: Unknown result type (might be due to invalid IL or missing references) //IL_0159: Unknown result type (might be due to invalid IL or missing references) //IL_016b: Unknown result type (might be due to invalid IL or missing references) //IL_017d: Unknown result type (might be due to invalid IL or missing references) //IL_018f: Unknown result type (might be due to invalid IL or missing references) //IL_019d: Unknown result type (might be due to invalid IL or missing references) //IL_01a2: Unknown result type (might be due to invalid IL or missing references) //IL_01af: Expected O, but got Unknown //IL_01ba: Unknown result type (might be due to invalid IL or missing references) //IL_01bf: Unknown result type (might be due to invalid IL or missing references) //IL_01c4: Unknown result type (might be due to invalid IL or missing references) //IL_01ce: Expected O, but got Unknown //IL_01cf: Unknown result type (might be due to invalid IL or missing references) //IL_01d4: Unknown result type (might be due to invalid IL or missing references) //IL_01de: Expected O, but got Unknown //IL_01e4: Expected O, but got Unknown if (!((Object)(object)_styleSkin == (Object)(object)GUI.skin) || _labelStyle == null) { _styleSkin = GUI.skin; _layoutMetricsDirty = true; _labelStyle = new GUIStyle(GUI.skin.label) { wordWrap = false, clipping = (TextClipping)1, alignment = (TextAnchor)3 }; _labelStyle.normal.textColor = _theme.TextColor; _headerLabelStyle = new GUIStyle(GUI.skin.label) { wordWrap = false, clipping = (TextClipping)1, alignment = (TextAnchor)3, fontStyle = (FontStyle)1 }; _headerLabelStyle.normal.textColor = _theme.HeaderTextColor; _activeHeaderLabelStyle = new GUIStyle(_headerLabelStyle); Color textColor = Color.Lerp(_theme.HeaderTextColor, _theme.AccentColor, 0.5f); _activeHeaderLabelStyle.normal.textColor = textColor; _activeHeaderLabelStyle.hover.textColor = textColor; _activeHeaderLabelStyle.active.textColor = textColor; _activeHeaderLabelStyle.focused.textColor = textColor; _activeHeaderLabelStyle.onNormal.textColor = textColor; _activeHeaderLabelStyle.onHover.textColor = textColor; _activeHeaderLabelStyle.onActive.textColor = textColor; _activeHeaderLabelStyle.onFocused.textColor = textColor; _groupLabelStyle = new GUIStyle(_headerLabelStyle) { alignment = (TextAnchor)3 }; _compactButtonStyle = new GUIStyle(GUI.skin.button) { margin = new RectOffset(1, 1, 1, 1), padding = new RectOffset(2, 2, 0, 0) }; } } private void Label(string text, params GUILayoutOption[] options) { GUILayout.Label(text ?? string.Empty, _labelStyle, options); } private void HeaderLabel(string text, params GUILayoutOption[] options) { GUILayout.Label(text ?? string.Empty, _headerLabelStyle, options); } private void DrawSelectionTab() { //IL_00f1: Unknown result type (might be due to invalid IL or missing references) //IL_00fb: Unknown result type (might be due to invalid IL or missing references) //IL_0100: Unknown result type (might be due to invalid IL or missing references) //IL_0134: Unknown result type (might be due to invalid IL or missing references) //IL_0139: Unknown result type (might be due to invalid IL or missing references) //IL_013b: Unknown result type (might be due to invalid IL or missing references) if (!_listReady) { GUILayout.BeginHorizontal(Array.Empty()); Label("MonoBehaviour list is not loaded.", GUILayout.ExpandWidth(true)); GUI.enabled = !_profilingActive; if (GUILayout.Button("Load list", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(110f) })) { RefreshBehaviourList(); } GUI.enabled = true; GUILayout.EndHorizontal(); return; } if (!_selectionExpansionInitialized) { ExpandPathsToSelected(collapseUnselected: true); _selectionExpansionInitialized = true; } DrawSelectionBulkActions(); GUILayout.Space(3f); DrawSelectionSourceRow(); GUILayout.Space(2f); RebuildSelectionRowsIfNeeded(); float currentRowHeight = CurrentRowHeight; float num = Mathf.Max(1f, (float)_selectionRows.Count * currentRowHeight); _selectionScroll = GUILayout.BeginScrollView(_selectionScroll, Array.Empty()); Rect rect = GUILayoutUtility.GetRect(Mathf.Max(1f, MainWindowWidth - 30f), num, (GUILayoutOption[])(object)new GUILayoutOption[2] { GUILayout.ExpandWidth(true), GUILayout.Height(num) }); DrawSelectionRows(rect, currentRowHeight); GUILayout.EndScrollView(); } private void DrawSelectionBulkActions() { //IL_0027: Unknown result type (might be due to invalid IL or missing references) //IL_0044: Expected O, but got Unknown //IL_0062: Unknown result type (might be due to invalid IL or missing references) //IL_007f: Expected O, but got Unknown //IL_0096: Unknown result type (might be due to invalid IL or missing references) //IL_00b3: Expected O, but got Unknown //IL_0170: Unknown result type (might be due to invalid IL or missing references) //IL_018d: Expected O, but got Unknown //IL_023a: Unknown result type (might be due to invalid IL or missing references) //IL_0257: Expected O, but got Unknown //IL_037d: Unknown result type (might be due to invalid IL or missing references) //IL_039b: Expected O, but got Unknown GUILayout.BeginHorizontal(Array.Empty()); GUI.enabled = !_profilingActive; if (GUILayout.Button(new GUIContent("Refresh list", "Rescan loaded assemblies and rebuild the callback list.\nUnsaved selection changes are discarded."), (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(100f) })) { RefreshBehaviourList(); } GUI.enabled = true; if (GUILayout.Button(new GUIContent("Enable all", "Enable every callback visible through the current Source, Present in active scene and Search filters.\nCallbacks hidden by the current filters are not changed."), (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(88f) })) { EnableFilteredSelection(); } if (GUILayout.Button(new GUIContent("Enable default callbacks", "Enable the default frame callbacks added by mods: Update, FixedUpdate, LateUpdate and OnGUI.\nExisting manually enabled callbacks remain selected."), (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(170f) })) { lock (_lock) { foreach (BehaviourMethodEntry item in _types.SelectMany((BehaviourTypeEntry type) => type.Methods)) { if (item.DefaultSelected) { item.Selected = true; } } } _selectionDirty = true; ExpandPathsToSelected(collapseUnselected: false); MarkSelectionRowsDirty(); } if (GUILayout.Button(new GUIContent("Disable all", "Clear every selected callback and collapse the selection tree."), (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(88f) })) { lock (_lock) { foreach (BehaviourMethodEntry item2 in _types.SelectMany((BehaviourTypeEntry type) => type.Methods)) { item2.Selected = false; } } _selectionDirty = true; CollapseSelectionTree(); MarkSelectionRowsDirty(); } if (GUILayout.Button(new GUIContent("Reset to defaults", "Restore the default selection: mod frame callbacks enabled, Valheim and Other callbacks disabled."), (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(120f) })) { lock (_lock) { foreach (BehaviourMethodEntry item3 in _types.SelectMany((BehaviourTypeEntry type) => type.Methods)) { item3.Selected = item3.DefaultSelected; } } _selectionDirty = true; ExpandPathsToSelected(collapseUnselected: true); MarkSelectionRowsDirty(); } GUILayout.Space(8f); int selectedMethodCount = GetSelectedMethodCount(); GUILayout.Box($"Selected: {selectedMethodCount}", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(105f) }); GUILayout.Space(16f); GUI.enabled = _selectionDirty; GUIStyle val = (_selectionDirty ? _theme.AccentButtonStyle : GUI.skin.button); if (GUILayout.Button(new GUIContent("Apply selection", "Apply the current callback selection.\nIf profiling is active, instrumentation is restarted and statistics are reset."), val, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(132f) })) { ApplySelection(); } GUI.enabled = true; if (_selectionDirty) { GUILayout.Space(6f); GUILayout.Label("Selection has changed", _theme.AccentLabelStyle, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.ExpandWidth(true) }); } else { GUILayout.FlexibleSpace(); } GUILayout.EndHorizontal(); } private void DrawSelectionSourceRow() { //IL_0016: Unknown result type (might be due to invalid IL or missing references) //IL_0039: Expected O, but got Unknown //IL_0052: Unknown result type (might be due to invalid IL or missing references) //IL_0058: Expected O, but got Unknown //IL_0064: Unknown result type (might be due to invalid IL or missing references) //IL_006a: Expected O, but got Unknown //IL_0076: Unknown result type (might be due to invalid IL or missing references) //IL_007c: Expected O, but got Unknown //IL_00e9: Unknown result type (might be due to invalid IL or missing references) //IL_0103: Expected O, but got Unknown //IL_00ae: Unknown result type (might be due to invalid IL or missing references) //IL_00b3: Unknown result type (might be due to invalid IL or missing references) //IL_0132: Unknown result type (might be due to invalid IL or missing references) //IL_0137: Unknown result type (might be due to invalid IL or missing references) //IL_0284: Unknown result type (might be due to invalid IL or missing references) //IL_0289: Unknown result type (might be due to invalid IL or missing references) //IL_0242: Unknown result type (might be due to invalid IL or missing references) //IL_0247: Unknown result type (might be due to invalid IL or missing references) GUILayout.BeginHorizontal(Array.Empty()); GUILayout.Label(new GUIContent("Source:", "Choose one source category to display. The tabs only filter the view and do not change saved selection."), _labelStyle, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(52f) }); BehaviourSource behaviourSource = (BehaviourSource)GUILayout.Toolbar((int)_selectionSource, (GUIContent[])(object)new GUIContent[3] { new GUIContent("Mods", "Show MonoBehaviour frame callbacks discovered in BepInEx plugin assemblies."), new GUIContent("Valheim", "Show MonoBehaviour frame callbacks declared by Valheim game assemblies."), new GUIContent("Other", "Show callbacks from managed assemblies that are neither Valheim nor identifiable BepInEx plugins.") }, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(245f) }); if (behaviourSource != _selectionSource) { _selectionSource = behaviourSource; _selectionScroll = Vector2.zero; ExpandPathsToSelected(collapseUnselected: false); MarkSelectionRowsDirty(); } GUILayout.Space(8f); bool flag = ProfilerGui.ToggleLayout(_theme, _presentInActiveScene, new GUIContent("Present in active scene", "Show only MonoBehaviour types that currently have at least one loaded instance in the active Unity scene.\nInactive GameObjects and disabled components are still considered present.\nThis is a view filter only and does not remove saved selections."), 190f, _labelStyle, 0f); if (flag != _presentInActiveScene) { _presentInActiveScene = flag; if (_presentInActiveScene) { RefreshActiveScenePresence(); } _selectionScroll = Vector2.zero; MarkSelectionRowsDirty(); } GUILayout.Space(10f); if (GUILayout.Button("Expand all", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(78f) })) { SetVisibleSelectionExpansion(expanded: true); } if (GUILayout.Button("Collapse all", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(78f) })) { SetVisibleSelectionExpansion(expanded: false); } GUILayout.Space(8f); GUILayout.Label("Search:", _labelStyle, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(48f) }); string text = GUILayout.TextField(_search ?? string.Empty, (GUILayoutOption[])(object)new GUILayoutOption[2] { GUILayout.Width(245f), GUILayout.Height(Mathf.Max(18f, CurrentRowHeight - 2f)) }); if (!string.Equals(text, _search, StringComparison.Ordinal)) { _search = text ?? string.Empty; _selectionScroll = Vector2.zero; MarkSelectionRowsDirty(); } if (GUILayout.Button("Clear", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(55f) })) { _search = string.Empty; _selectionScroll = Vector2.zero; GUI.FocusControl((string)null); MarkSelectionRowsDirty(); } GUILayout.FlexibleSpace(); GUILayout.EndHorizontal(); } private void EnableFilteredSelection() { string text = (_search ?? string.Empty).Trim(); bool flag = text.Length > 0; bool flag2 = false; lock (_lock) { foreach (BehaviourTypeEntry type in _types) { if (!TypePassesSourceFilter(type) || !IsPresentInActiveScene(type) || !TypeMatchesSearch(type, text, flag)) { continue; } bool flag3 = flag && (ContainsIgnoreCase(type.GroupName, text) || ContainsIgnoreCase(type.AssemblyName, text) || ContainsIgnoreCase(type.TypeName, text)); foreach (BehaviourMethodEntry visibleMethod in GetVisibleMethods(type, text, flag && !flag3)) { if (!visibleMethod.Selected) { visibleMethod.Selected = true; flag2 = true; } } } } if (flag2) { _selectionDirty = true; ExpandPathsToSelected(collapseUnselected: false); MarkSelectionRowsDirty(); } } private int GetSelectedMethodCount() { lock (_lock) { return _types.Sum((BehaviourTypeEntry type) => type.Methods.Count((BehaviourMethodEntry method) => method.Selected)); } } private void ExpandPathsToSelected(bool collapseUnselected) { lock (_lock) { if (collapseUnselected) { _selectionGroupExpanded.Clear(); } foreach (BehaviourTypeEntry type in _types) { bool flag = type.Methods.Any((BehaviourMethodEntry method) => method.Selected); if (collapseUnselected || flag) { type.Expanded = flag; } if (flag) { _selectionGroupExpanded[type.GroupId] = true; } else if (collapseUnselected && !_selectionGroupExpanded.ContainsKey(type.GroupId)) { _selectionGroupExpanded[type.GroupId] = false; } } } } private void CollapseSelectionTree() { lock (_lock) { foreach (BehaviourTypeEntry type in _types) { type.Expanded = false; } foreach (string item in _types.Select((BehaviourTypeEntry type) => type.GroupId).Distinct().ToList()) { _selectionGroupExpanded[item] = false; } } } private void SetVisibleSelectionExpansion(bool expanded) { string text = (_search ?? string.Empty).Trim(); bool hasSearch = text.Length > 0; lock (_lock) { foreach (BehaviourTypeEntry type in _types) { if (TypePassesSourceFilter(type) && IsPresentInActiveScene(type) && TypeMatchesSearch(type, text, hasSearch)) { type.Expanded = expanded; _selectionGroupExpanded[type.GroupId] = expanded; } } } MarkSelectionRowsDirty(); } private void RebuildSelectionRowsIfNeeded() { if (!_selectionRowsDirty && string.Equals(_lastSelectionSearch, _search, StringComparison.Ordinal)) { return; } _selectionRowsDirty = false; _lastSelectionSearch = _search ?? string.Empty; _selectionRows.Clear(); string search = (_search ?? string.Empty).Trim(); bool hasSearch = search.Length > 0; List source; lock (_lock) { source = (from type in _types.Where(TypePassesSourceFilter).Where(IsPresentInActiveScene) where TypeMatchesSearch(type, search, hasSearch) select type).ToList(); } foreach (IGrouping item in (from type in source group type by type.GroupId into @group where @group.Any() select @group).OrderBy, string>((IGrouping group) => group.First().GroupName, StringComparer.OrdinalIgnoreCase)) { BehaviourTypeEntry behaviourTypeEntry = item.First(); if (!_selectionGroupExpanded.TryGetValue(item.Key, out var value)) { value = item.Any((BehaviourTypeEntry type) => type.Methods.Any((BehaviourMethodEntry method) => method.Selected)); _selectionGroupExpanded[item.Key] = value; } _selectionRows.Add(new SelectionRow { Kind = SelectionRowKind.Group, GroupId = item.Key, Text = (behaviourTypeEntry.GroupName ?? behaviourTypeEntry.AssemblyName ?? "Unknown"), Indent = 0 }); if (!value) { continue; } foreach (BehaviourTypeEntry item2 in item.OrderBy((BehaviourTypeEntry item) => item.TypeName, StringComparer.OrdinalIgnoreCase)) { bool flag = hasSearch && (ContainsIgnoreCase(item2.GroupName, search) || ContainsIgnoreCase(item2.AssemblyName, search) || ContainsIgnoreCase(item2.TypeName, search)); List visibleMethods = GetVisibleMethods(item2, search, hasSearch && !flag); if (visibleMethods.Count == 0) { continue; } _selectionRows.Add(new SelectionRow { Kind = SelectionRowKind.Type, GroupId = item.Key, Text = item2.TypeName, TypeEntry = item2, Indent = 1 }); if (!item2.Expanded && !hasSearch) { continue; } foreach (BehaviourMethodEntry item3 in visibleMethods) { _selectionRows.Add(new SelectionRow { Kind = SelectionRowKind.Method, GroupId = item.Key, Text = item3.DisplayName, TypeEntry = item2, MethodEntry = item3, Indent = 2 }); } } } } private bool TypePassesSourceFilter(BehaviourTypeEntry type) { return type != null && type.Source == _selectionSource; } private bool TypeMatchesSearch(BehaviourTypeEntry type, string search, bool hasSearch) { if (!hasSearch) { return true; } if (ContainsIgnoreCase(type.GroupName, search) || ContainsIgnoreCase(type.AssemblyName, search) || ContainsIgnoreCase(type.TypeName, search)) { return true; } return GetVisibleMethods(type, search, filterBySearch: true).Count > 0; } private static List GetVisibleMethods(BehaviourTypeEntry type, string search, bool filterBySearch) { IEnumerable source = type.Methods; if (filterBySearch) { source = source.Where((BehaviourMethodEntry method) => ContainsIgnoreCase(method.DisplayName, search) || ContainsIgnoreCase(method.Kind.ToString(), search)); } return source.OrderBy((BehaviourMethodEntry method) => method.Kind).ThenBy((BehaviourMethodEntry method) => method.DisplayName, StringComparer.OrdinalIgnoreCase).ToList(); } private void DrawSelectionRows(Rect contentRect, float rowHeight) { //IL_00da: Unknown result type (might be due to invalid IL or missing references) //IL_00df: Unknown result type (might be due to invalid IL or missing references) //IL_00e4: Unknown result type (might be due to invalid IL or missing references) //IL_00e6: Unknown result type (might be due to invalid IL or missing references) //IL_02c9: Unknown result type (might be due to invalid IL or missing references) //IL_02d9: Unknown result type (might be due to invalid IL or missing references) //IL_02ee: Expected O, but got Unknown //IL_0128: Unknown result type (might be due to invalid IL or missing references) //IL_0194: Unknown result type (might be due to invalid IL or missing references) //IL_0199: Unknown result type (might be due to invalid IL or missing references) //IL_019e: Unknown result type (might be due to invalid IL or missing references) //IL_01a0: Unknown result type (might be due to invalid IL or missing references) //IL_024c: Unknown result type (might be due to invalid IL or missing references) float visibleTop = _selectionScroll.y - 80f; float visibleBottom = _selectionScroll.y + Mathf.Max(100f, MainWindowHeight - 230f) + 80f; Rect rect = default(Rect); for (int i = 0; i < _selectionRows.Count; i++) { float num = (float)i * rowHeight; if (!RowVisible(num, rowHeight, visibleTop, visibleBottom)) { continue; } SelectionRow selectionRow = _selectionRows[i]; float num2 = ((Rect)(ref contentRect)).y + num; float num3 = ((Rect)(ref contentRect)).x + (float)selectionRow.Indent * 17f; switch (selectionRow.Kind) { case SelectionRowKind.Group: { bool value; bool flag4 = _selectionGroupExpanded.TryGetValue(selectionRow.GroupId, out value) && value; Rect val2 = InsetButtonRect(new Rect(num3, num2, 14f, rowHeight)); bool flag5 = GUI.Button(val2, flag4 ? "▼" : "▶", _compactButtonStyle); num3 += 14f; if (flag5 | GUI.Button(new Rect(num3, num2, ((Rect)(ref contentRect)).width - num3 + ((Rect)(ref contentRect)).x, rowHeight), selectionRow.Text, _groupLabelStyle)) { _selectionGroupExpanded[selectionRow.GroupId] = !flag4; MarkSelectionRowsDirty(); } break; } case SelectionRowKind.Type: { BehaviourTypeEntry typeEntry = selectionRow.TypeEntry; bool flag2 = typeEntry?.Expanded ?? false; Rect val = InsetButtonRect(new Rect(num3, num2, 14f, rowHeight)); bool flag3 = GUI.Button(val, flag2 ? "▼" : "▶", _compactButtonStyle); num3 += 14f; int num4 = typeEntry?.Methods.Count((BehaviourMethodEntry method) => method.Selected) ?? 0; int num5 = typeEntry?.Methods.Count ?? 0; string text = $"{selectionRow.Text} ({num4}/{num5})"; if (flag3 | GUI.Button(new Rect(num3, num2, ((Rect)(ref contentRect)).width - num3 + ((Rect)(ref contentRect)).x, rowHeight), text, _headerLabelStyle)) { if (typeEntry != null) { typeEntry.Expanded = !flag2; } MarkSelectionRowsDirty(); } break; } case SelectionRowKind.Method: { BehaviourMethodEntry methodEntry = selectionRow.MethodEntry; if (methodEntry != null) { ((Rect)(ref rect))..ctor(num3, num2, ((Rect)(ref contentRect)).width - (num3 - ((Rect)(ref contentRect)).x), rowHeight); bool flag = ProfilerGui.Toggle(_theme, rect, methodEntry.Selected, new GUIContent(methodEntry.DisplayName), _labelStyle, 0f); if (flag != methodEntry.Selected) { methodEntry.Selected = flag; _selectionDirty = true; MarkSelectionRowsDirty(); } } break; } } } } private static bool ContainsIgnoreCase(string value, string search) { return !string.IsNullOrEmpty(value) && value.IndexOf(search, StringComparison.OrdinalIgnoreCase) >= 0; } private void SetSortColumn(TableSortColumn column) { if (_profilerView == ProfilerView.Avg1s) { _avgSortColumn = column; _app.Config.MonoBehaviourProfilerAvgSortColumn.Value = column.ToString(); } else { _maxSortColumn = column; _app.Config.MonoBehaviourProfilerMaxSortColumn.Value = column.ToString(); } MarkViewDirty(); } private static TableSortColumn ParseSortColumn(string value, ProfilerView view, TableSortColumn fallback) { if (!Enum.TryParse(value, ignoreCase: true, out var result)) { return fallback; } bool flag2; if (view == ProfilerView.Avg1s) { bool flag = (((uint)result <= 2u || result == TableSortColumn.MonoBehaviour) ? true : false); flag2 = flag; } else { bool flag = ((result == TableSortColumn.Source || (uint)(result - 3) <= 11u) ? true : false); flag2 = flag; } return flag2 ? result : fallback; } private List BuildRows(int frame, float now) { List list = new List(); lock (_lock) { foreach (BehaviourTypeEntry type in _types) { foreach (BehaviourMethodEntry method in type.Methods) { RollingProfilerSnapshot snapshot = method.Stat.GetSnapshot(frame, now); bool num; if (_profilerView != ProfilerView.Avg1s) { if (snapshot.MaxSnapshot.WindowSampleCount <= 0) { continue; } num = snapshot.MaxSnapshot.RawMaxMs > 0.0; } else { if (snapshot.Avg1sFrames <= 0) { continue; } num = snapshot.Avg1sMsPerFrame > 0.0; } if (num) { list.Add(new FlatRowView { Entry = method, Snapshot = snapshot }); } } } } return SortRows(list); } private List BuildGroupedRows(List rows) { List groups = (from row in rows group row by row.Entry.TypeEntry.GroupId ?? "(unknown)").Select(delegate(IGrouping group) { List list = SortRows(group.ToList()); GroupSummary summary = BuildSummary(list); BehaviourTypeEntry typeEntry = list[0].Entry.TypeEntry; return new GroupRowView { GroupId = group.Key, GroupName = (typeEntry.GroupName ?? typeEntry.AssemblyName ?? "Unknown"), Rows = list, Summary = summary, AggregateScore = ((_profilerView == ProfilerView.Avg1s) ? summary.Avg1sMsPerFrame : summary.Max.ThirdMaxMs) }; }).ToList(); groups = SortGroups(groups); lock (_lock) { foreach (GroupRowView item in groups) { if (!_groupExpanded.ContainsKey(item.GroupId)) { _groupExpanded[item.GroupId] = false; } } } return groups; } private List SortRows(IEnumerable rows) { TableSortColumn currentSortColumn = CurrentSortColumn; if (1 == 0) { } IOrderedEnumerable orderedEnumerable = currentSortColumn switch { TableSortColumn.Source => rows.OrderByDescending((FlatRowView row) => _groupByMod ? row.Entry.DisplayName : BuildUngroupedEntryName(row.Entry), StringComparer.OrdinalIgnoreCase), TableSortColumn.AvgMsPerFrame => rows.OrderByDescending((FlatRowView row) => row.Snapshot.Avg1sMsPerFrame), TableSortColumn.AvgCallsPerFrame => rows.OrderByDescending((FlatRowView row) => row.Snapshot.Avg1sCallsPerFrame), TableSortColumn.RawMax => rows.OrderByDescending((FlatRowView row) => row.Snapshot.MaxSnapshot.RawMaxMs), TableSortColumn.SecondMax => rows.OrderByDescending((FlatRowView row) => row.Snapshot.MaxSnapshot.SecondMaxMs), TableSortColumn.ThirdMax => rows.OrderByDescending((FlatRowView row) => row.Snapshot.MaxSnapshot.ThirdMaxMs), TableSortColumn.AboveP99 => rows.OrderByDescending((FlatRowView row) => row.Snapshot.MaxSnapshot.AboveP99Count), TableSortColumn.AvgAboveP99 => rows.OrderByDescending((FlatRowView row) => row.Snapshot.MaxSnapshot.AvgAboveP99Ms), TableSortColumn.P99 => rows.OrderByDescending((FlatRowView row) => row.Snapshot.MaxSnapshot.P99Ms), TableSortColumn.AboveP95 => rows.OrderByDescending((FlatRowView row) => row.Snapshot.MaxSnapshot.AboveP95Count), TableSortColumn.AvgAboveP95 => rows.OrderByDescending((FlatRowView row) => row.Snapshot.MaxSnapshot.AvgAboveP95Ms), TableSortColumn.P95 => rows.OrderByDescending((FlatRowView row) => row.Snapshot.MaxSnapshot.P95Ms), TableSortColumn.Samples => rows.OrderByDescending((FlatRowView row) => row.Snapshot.MaxSnapshot.WindowSampleCount), TableSortColumn.GcSamples => rows.OrderByDescending((FlatRowView row) => row.Snapshot.MaxSnapshot.GcSampleCount), TableSortColumn.MonoBehaviour => rows.OrderByDescending((FlatRowView row) => row.Entry.TypeEntry.TypeName, StringComparer.OrdinalIgnoreCase), _ => rows.OrderByDescending((FlatRowView row) => row.Snapshot.MaxSnapshot.ThirdMaxMs), }; if (1 == 0) { } IOrderedEnumerable source = orderedEnumerable; return source.ThenByDescending((FlatRowView row) => row.Snapshot.MaxSnapshot.RawMaxMs).ThenBy((FlatRowView row) => row.Entry.TypeEntry.GroupName, StringComparer.OrdinalIgnoreCase).ThenBy((FlatRowView row) => row.Entry.TypeEntry.TypeName, StringComparer.OrdinalIgnoreCase) .ThenBy((FlatRowView row) => row.Entry.DisplayName, StringComparer.OrdinalIgnoreCase) .ToList(); } private List SortGroups(IEnumerable groups) { TableSortColumn currentSortColumn = CurrentSortColumn; if (1 == 0) { } IOrderedEnumerable orderedEnumerable = currentSortColumn switch { TableSortColumn.Source => groups.OrderByDescending((GroupRowView group) => group.GroupName, StringComparer.OrdinalIgnoreCase), TableSortColumn.AvgMsPerFrame => groups.OrderByDescending((GroupRowView group) => group.Summary.Avg1sMsPerFrame), TableSortColumn.AvgCallsPerFrame => groups.OrderByDescending((GroupRowView group) => group.Summary.Avg1sCallsPerFrame), TableSortColumn.RawMax => groups.OrderByDescending((GroupRowView group) => group.Summary.Max.RawMaxMs), TableSortColumn.SecondMax => groups.OrderByDescending((GroupRowView group) => group.Summary.Max.SecondMaxMs), TableSortColumn.ThirdMax => groups.OrderByDescending((GroupRowView group) => group.Summary.Max.ThirdMaxMs), TableSortColumn.AboveP99 => groups.OrderByDescending((GroupRowView group) => group.Summary.Max.AboveP99Count), TableSortColumn.AvgAboveP99 => groups.OrderByDescending((GroupRowView group) => group.Summary.Max.AvgAboveP99Ms), TableSortColumn.P99 => groups.OrderByDescending((GroupRowView group) => group.Summary.Max.P99Ms), TableSortColumn.AboveP95 => groups.OrderByDescending((GroupRowView group) => group.Summary.Max.AboveP95Count), TableSortColumn.AvgAboveP95 => groups.OrderByDescending((GroupRowView group) => group.Summary.Max.AvgAboveP95Ms), TableSortColumn.P95 => groups.OrderByDescending((GroupRowView group) => group.Summary.Max.P95Ms), TableSortColumn.Samples => groups.OrderByDescending((GroupRowView group) => group.Summary.Max.WindowSampleCount), TableSortColumn.GcSamples => groups.OrderByDescending((GroupRowView group) => group.Summary.Max.GcSampleCount), TableSortColumn.MonoBehaviour => groups.OrderByDescending(GetGroupMaxTypeName, StringComparer.OrdinalIgnoreCase), _ => groups.OrderByDescending((GroupRowView group) => group.AggregateScore), }; if (1 == 0) { } IOrderedEnumerable source = orderedEnumerable; return source.ThenByDescending((GroupRowView group) => group.Summary.Max.RawMaxMs).ThenBy((GroupRowView group) => group.GroupName, StringComparer.OrdinalIgnoreCase).ToList(); } private static string GetGroupMaxTypeName(GroupRowView group) { if (group?.Rows == null || group.Rows.Count == 0) { return string.Empty; } string text = string.Empty; for (int i = 0; i < group.Rows.Count; i++) { string text2 = group.Rows[i].Entry?.TypeEntry?.TypeName ?? string.Empty; if (string.Compare(text2, text, StringComparison.OrdinalIgnoreCase) > 0) { text = text2; } } return text; } private static GroupSummary BuildSummary(List rows) { return new GroupSummary { Avg1sMsPerFrame = rows.Sum((FlatRowView row) => row.Snapshot.Avg1sMsPerFrame), Avg1sCallsPerFrame = rows.Sum((FlatRowView row) => row.Snapshot.Avg1sCallsPerFrame), Max = RollingMaxSnapshot.Aggregate(rows.Select((FlatRowView row) => row.Snapshot.MaxSnapshot)) }; } private static string FormatMs(double value) { if (value <= 0.0) { return "0.000"; } if (value > 999.999) { return $"{999.999:0.000}+"; } return $"{value:0.000}"; } private static string FormatCount(long value) { if (value <= 0) { return "0"; } if (value > 9999999) { return 9999999 + "+"; } return value.ToString(); } private static string FormatCount(double value) { if (value <= 0.0) { return "0"; } if (value > 9999999.0) { return 9999999 + "+"; } return $"{value:0.##}"; } private static string FormatRawMax(RollingMaxSnapshot max) { string text = FormatMs(max.RawMaxMs); return ShouldMarkRawMax(max) ? (text + "!") : text; } private static bool ShouldMarkRawMax(RollingMaxSnapshot max) { bool flag = max.RawMaxMs >= 8.0 && max.WindowSampleCount >= 3 && max.ThirdMaxMs > 0.0 && max.RawMaxMs >= max.ThirdMaxMs * 4.0; bool flag2 = max.RawMaxMs >= 8.0 && max.GcSampleCount > 0; return flag || flag2; } } } namespace ValheimProfiler.Tools.MonoBehaviourCallProfiler { internal sealed class MonoBehaviourCallProfilerTool : IProfilerTool { private enum MainTab { Profiler, CallsToProfile, Help } private enum TableSortColumn { Source, Calls, Total, Average, Max, Last, P95, P99, FirstAt, LastAt, MonoBehaviour } private enum BehaviourSource { Mod, Valheim, Other } private enum ProfileMethodKind { Lifecycle, Declared } private enum SelectionRowKind { Group, Type, Method } private sealed class BehaviourTypeEntry { public Type Type; public string GroupId; public string GroupName; public string AssemblyName; public string TypeName; public BehaviourSource Source; public bool Expanded; public readonly List Methods = new List(); } private sealed class BehaviourMethodEntry { public BehaviourTypeEntry TypeEntry; public MethodInfo Method; public string Key; public string DisplayName; public string Tooltip; public ProfileMethodKind Kind; public bool IsAsyncOrIterator; public bool DefaultSelected; public bool Selected; public readonly LifetimeProfilerStat Stat = new LifetimeProfilerStat(); } private sealed class RuntimeBehaviourEntry { public readonly Type RuntimeType; public readonly BehaviourMethodEntry Entry; public RuntimeBehaviourEntry(Type runtimeType, BehaviourMethodEntry entry) { RuntimeType = runtimeType; Entry = entry; } } private sealed class FlatRowView { public BehaviourMethodEntry Entry; public LifetimeProfilerSnapshot Snapshot; } private sealed class GroupRowView { public string GroupId; public string GroupName; public List Rows; public GroupSummary Summary; } private struct GroupSummary { public long Calls; public double TotalMs; public double AverageMs; public double MaxMs; public double LastMs; public double P95Ms; public double P99Ms; public float FirstCallAtSeconds; public float LastCallAtSeconds; } private sealed class SelectionRow { public SelectionRowKind Kind; public string GroupId; public string Text; public string Tooltip; public BehaviourTypeEntry TypeEntry; public BehaviourMethodEntry MethodEntry; public int Indent; } private sealed class SelectionPolicy { private readonly string _path; private readonly Dictionary _overrides = new Dictionary(StringComparer.Ordinal); public SelectionPolicy(string path) { _path = path; Load(); } public bool Resolve(string key, bool defaultValue) { bool value; return (key != null && _overrides.TryGetValue(key, out value)) ? value : defaultValue; } public void Reload() { Load(); } public void Save(IEnumerable entries) { try { string directoryName = Path.GetDirectoryName(_path); if (!string.IsNullOrEmpty(directoryName)) { Directory.CreateDirectory(directoryName); } List list = new List { "# Valheim Profiler MonoBehaviour Call selection overrides", "# + entry = enabled, - entry = disabled", "# Missing entries use defaults: synchronous mod lifecycle callbacks are enabled; declared methods, Valheim and Other callbacks are disabled.", string.Empty }; foreach (BehaviourMethodEntry item in entries.OrderBy((BehaviourMethodEntry x) => x.Key, StringComparer.Ordinal)) { if (item.Selected != item.DefaultSelected) { list.Add((item.Selected ? "+ " : "- ") + item.Key); } } File.WriteAllLines(_path, list); Load(); } catch { } } private void Load() { _overrides.Clear(); try { if (!File.Exists(_path)) { return; } string[] array = File.ReadAllLines(_path); for (int i = 0; i < array.Length; i++) { string text = array[i]?.Trim(); if (string.IsNullOrEmpty(text) || text.StartsWith("#", StringComparison.Ordinal)) { continue; } bool value; if (text.StartsWith("+", StringComparison.Ordinal)) { value = true; } else { if (!text.StartsWith("-", StringComparison.Ordinal)) { continue; } value = false; } string text2 = text.Substring(1).Trim(); if (!string.IsNullOrEmpty(text2)) { _overrides[text2] = value; } } } catch { } } } private static readonly string[] LifecycleCallbackNames = new string[5] { "Awake", "Start", "OnEnable", "OnDisable", "OnDestroy" }; private static readonly HashSet FrameCallbackNames = new HashSet(StringComparer.Ordinal) { "Update", "FixedUpdate", "LateUpdate", "OnGUI" }; internal const string ToolId = "MonoBehaviourCallProfiler"; internal const string HarmonyId = "shudnal.ValheimProfiler.MonoBehaviourCallProfiler"; internal const string DisplayTitle = "MonoBehaviour Call Profiler"; private const float VirtualizationOverscan = 80f; private const float GroupToggleWidth = 14f; private const int MaxDisplayedCount = 999999999; private const double MaxDisplayedMs = 999999.999; private const float MinSourceColumnWidth = 170f; private const float MaxSourceColumnWidth = 900f; private const float TimeColumnWidth = 86f; private const float CountColumnWidth = 78f; private const float TimelineColumnWidth = 82f; private const float TypeColumnMinWidth = 280f; private const float WindowHorizontalPadding = 18f; private readonly ValheimProfilerApp _app; private readonly ManualLogSource _logger; private readonly WindowManager _windows; private readonly ThemeManager _theme; private readonly Harmony _harmony; private readonly ProfilerWindow _mainWindow; private readonly SelectionPolicy _selectionPolicy; private readonly object _lock = new object(); private readonly List _types = new List(); private readonly Dictionary _methodsByKey = new Dictionary(StringComparer.Ordinal); private readonly HashSet _instrumentedMethods = new HashSet(); private Dictionary _runtimeEntries = new Dictionary(); private readonly Dictionary _groupExpanded = new Dictionary(StringComparer.Ordinal); private readonly Dictionary _selectionGroupExpanded = new Dictionary(StringComparer.Ordinal); private readonly HashSet _typesPresentInActiveScene = new HashSet(); private List _cachedFlatRows = new List(); private List _cachedGroupedRows = new List(); private readonly List _selectionRows = new List(); private GUIStyle _labelStyle; private GUIStyle _headerLabelStyle; private GUIStyle _activeHeaderLabelStyle; private GUIStyle _groupLabelStyle; private GUIStyle _compactButtonStyle; private GUISkin _styleSkin; private Vector2 _profilerScroll; private Vector2 _selectionScroll; private Vector2 _helpScroll; private string _search = string.Empty; private string _lastSelectionSearch = string.Empty; private BehaviourSource _selectionSource = BehaviourSource.Mod; private bool _showDeclaredMethods; private bool _presentInActiveScene; private int _activeSceneHandle = int.MinValue; private bool _selectionRowsDirty = true; private bool _selectionDirty; private bool _selectionExpansionInitialized; private bool _listReady; private volatile bool _profilingActive; private bool _groupByMod = true; private bool _viewDirty = true; private MainTab _mainTab = MainTab.Profiler; private TableSortColumn _sortColumn = TableSortColumn.Total; private float _nextViewRefreshTime; private float _sessionStartRealtime; private float _drawSourceColumnWidth; private float _drawTimeColumnWidth; private float _drawCountColumnWidth; private float _drawTimelineColumnWidth; private float _drawTypeColumnWidth; private float _drawContentWidth; private bool _layoutMetricsDirty = true; private float _lastLayoutWindowWidth = -1f; private bool _lastLayoutGroupByMod; private GUISkin _lastLayoutSkin; private string _status = "Not loaded."; private static readonly double MsPerTick = 1000.0 / (double)Stopwatch.Frequency; private static int _mainThreadId; private static MonoBehaviourCallProfilerTool _instance; string IProfilerTool.Id => "MonoBehaviourCallProfiler"; string IProfilerTool.DisplayName => "MonoBehaviour Call Profiler"; bool IProfilerTool.IsWindowVisible => IsWindowVisible; bool IProfilerTool.IsActive => _profilingActive; internal bool IsWindowVisible { get { return _mainWindow.RequestedVisible; } set { _mainWindow.RequestedVisible = value; } } private float MainWindowWidth { get { //IL_0006: 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) Rect rect = _mainWindow.Rect; return ((Rect)(ref rect)).width; } } private float MainWindowHeight { get { //IL_0006: 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) Rect rect = _mainWindow.Rect; return ((Rect)(ref rect)).height; } } private float CurrentRowHeight { get { GUIStyle labelStyle = _labelStyle; return Mathf.Max(20f, ((labelStyle != null) ? labelStyle.lineHeight : 16f) + 4f); } } private float CurrentHeaderHeight { get { GUIStyle headerLabelStyle = _headerLabelStyle; return Mathf.Max(20f, ((headerLabelStyle != null) ? headerLabelStyle.lineHeight : 16f) + 4f); } } private float CurrentGroupHeight { get { GUIStyle groupLabelStyle = _groupLabelStyle; return Mathf.Max(22f, ((groupLabelStyle != null) ? groupLabelStyle.lineHeight : 16f) + 4f); } } private TableSortColumn CurrentSortColumn => _sortColumn; private void RefreshBehaviourList() { if (_profilingActive) { return; } try { _status = "Scanning loaded MonoBehaviours..."; _selectionPolicy.Reload(); Dictionary dictionary = new Dictionary(); foreach (KeyValuePair pluginInfo in Chainloader.PluginInfos) { PluginInfo value = pluginInfo.Value; if (!((Object)(object)((value != null) ? value.Instance : null) == (Object)null)) { Assembly assembly = ((object)value.Instance).GetType().Assembly; BepInPlugin metadata = value.Metadata; string text = ((metadata != null) ? metadata.Name : null); if (string.IsNullOrWhiteSpace(text)) { BepInPlugin metadata2 = value.Metadata; text = ((metadata2 != null) ? metadata2.GUID : null); } if (string.IsNullOrWhiteSpace(text)) { text = assembly.GetName().Name; } dictionary[assembly] = text ?? "Unknown mod"; } } HashSet hashSet = new HashSet(); hashSet.Add(typeof(Player).Assembly); hashSet.Add(typeof(ZInput).Assembly); hashSet.Add(typeof(GuiScaler).Assembly); HashSet gameAssemblies = hashSet; string pluginRoot = NormalizeDirectoryPath(Paths.PluginPath); List list = new List(); Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies(); foreach (Assembly assembly2 in assemblies) { if (!ShouldInspectAssembly(assembly2, gameAssemblies, _app.Config.MonoBehaviourCallIncludeValheimProfilerCallbacks.Value)) { continue; } ClassifyAssembly(assembly2, gameAssemblies, dictionary, pluginRoot, out var source, out var groupName, out var groupId); Type[] loadableTypes = GetLoadableTypes(assembly2); foreach (Type type in loadableTypes) { if (!IsProfileableMonoBehaviourType(type)) { continue; } BehaviourTypeEntry behaviourTypeEntry = new BehaviourTypeEntry { Type = type, GroupId = groupId, GroupName = groupName, AssemblyName = (assembly2.GetName().Name ?? "Unknown assembly"), TypeName = (type.FullName ?? type.Name), Source = source, Expanded = false }; HashSet hashSet2 = new HashSet(); for (int k = 0; k < LifecycleCallbackNames.Length; k++) { MethodInfo methodInfo = FindEffectiveLifecycleMethod(type, LifecycleCallbackNames[k]); if (!(methodInfo == null) && hashSet2.Add(methodInfo)) { bool flag = IsAsyncOrIterator(methodInfo); bool defaultSelected = !flag && source == BehaviourSource.Mod && IsModAssembly(methodInfo.DeclaringType?.Assembly, gameAssemblies, dictionary, pluginRoot); AddMethodEntry(behaviourTypeEntry, methodInfo, ProfileMethodKind.Lifecycle, flag, defaultSelected); } } foreach (MethodInfo declaredProfileableMethod in GetDeclaredProfileableMethods(type)) { if (!(declaredProfileableMethod == null) && hashSet2.Add(declaredProfileableMethod)) { AddMethodEntry(behaviourTypeEntry, declaredProfileableMethod, ProfileMethodKind.Declared, IsAsyncOrIterator(declaredProfileableMethod), defaultSelected: false); } } if (behaviourTypeEntry.Methods.Count > 0) { list.Add(behaviourTypeEntry); } } } list.Sort(delegate(BehaviourTypeEntry left, BehaviourTypeEntry right) { int num4 = string.Compare(left.GroupName, right.GroupName, StringComparison.OrdinalIgnoreCase); return (num4 != 0) ? num4 : string.Compare(left.TypeName, right.TypeName, StringComparison.OrdinalIgnoreCase); }); lock (_lock) { _types.Clear(); _types.AddRange(list); _methodsByKey.Clear(); foreach (BehaviourTypeEntry type2 in _types) { foreach (BehaviourMethodEntry method in type2.Methods) { _methodsByKey[method.Key] = method; } } _runtimeEntries = new Dictionary(); _instrumentedMethods.Clear(); _groupExpanded.Clear(); _selectionGroupExpanded.Clear(); _typesPresentInActiveScene.Clear(); } _listReady = true; _selectionDirty = false; _selectionExpansionInitialized = false; if (_presentInActiveScene) { RefreshActiveScenePresence(); } ExpandPathsToSelected(collapseUnselected: true); _selectionExpansionInitialized = true; MarkSelectionRowsDirty(); MarkViewDirty(); int num = list.Sum((BehaviourTypeEntry behaviourTypeEntry2) => behaviourTypeEntry2.Methods.Count((BehaviourMethodEntry method) => method.Kind == ProfileMethodKind.Lifecycle)); int num2 = list.Sum((BehaviourTypeEntry behaviourTypeEntry2) => behaviourTypeEntry2.Methods.Count((BehaviourMethodEntry method) => method.Kind == ProfileMethodKind.Declared)); int num3 = list.Sum((BehaviourTypeEntry behaviourTypeEntry2) => behaviourTypeEntry2.Methods.Count((BehaviourMethodEntry method) => method.Selected)); _status = $"List ready. Types: {list.Count}. Lifecycle: {num}. Declared: {num2}. Selected: {num3}."; } catch (Exception ex) { _status = "Scan error: " + ex.GetType().Name + ": " + ex.Message; _logger.LogError((object)ex); } } private void AddMethodEntry(BehaviourTypeEntry typeEntry, MethodInfo method, ProfileMethodKind kind, bool asyncOrIterator, bool defaultSelected) { string key = BuildSelectionKey(typeEntry.Type, method); bool selected = _selectionPolicy.Resolve(key, defaultSelected); typeEntry.Methods.Add(new BehaviourMethodEntry { TypeEntry = typeEntry, Method = method, Key = key, DisplayName = (method?.Name ?? "(unknown method)"), Tooltip = BuildMethodTooltip(method, asyncOrIterator), Kind = kind, IsAsyncOrIterator = asyncOrIterator, DefaultSelected = defaultSelected, Selected = selected }); } private void RefreshActiveScenePresence() { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_00c7: Unknown result type (might be due to invalid IL or missing references) //IL_00cc: Unknown result type (might be due to invalid IL or missing references) Scene activeScene = SceneManager.GetActiveScene(); int handle = ((Scene)(ref activeScene)).handle; List list; lock (_lock) { list = _types.ToList(); } HashSet hashSet = new HashSet(); foreach (BehaviourTypeEntry item in list) { if (item?.Type == null) { continue; } try { Object[] array = Resources.FindObjectsOfTypeAll(item.Type); foreach (Object obj in array) { Component val = (Component)(object)((obj is Component) ? obj : null); if (!((Object)(object)val == (Object)null) && !((Object)(object)val.gameObject == (Object)null)) { Scene scene = val.gameObject.scene; if (((Scene)(ref scene)).IsValid() && ((Scene)(ref scene)).handle == handle) { hashSet.Add(item.Type); break; } } } } catch { } } lock (_lock) { _typesPresentInActiveScene.Clear(); foreach (Type item2 in hashSet) { _typesPresentInActiveScene.Add(item2); } } _activeSceneHandle = handle; } private bool IsPresentInActiveScene(BehaviourTypeEntry typeEntry) { if (!_presentInActiveScene) { return true; } if (typeEntry?.Type == null) { return false; } lock (_lock) { return _typesPresentInActiveScene.Contains(typeEntry.Type); } } private static bool ShouldInspectAssembly(Assembly assembly, HashSet gameAssemblies, bool includeProfilerMonoBehaviours) { if (assembly == null || assembly.IsDynamic) { return false; } if (assembly == typeof(ValheimProfilerPlugin).Assembly && !includeProfilerMonoBehaviours) { return false; } string text; try { text = assembly.GetName().Name ?? string.Empty; } catch { return false; } if (gameAssemblies != null && gameAssemblies.Contains(assembly)) { return true; } if (text.StartsWith("UnityEngine", StringComparison.Ordinal) || text.StartsWith("System", StringComparison.Ordinal) || text.StartsWith("Microsoft", StringComparison.Ordinal) || text.StartsWith("Mono.", StringComparison.Ordinal) || text.StartsWith("netstandard", StringComparison.OrdinalIgnoreCase) || text.StartsWith("mscorlib", StringComparison.OrdinalIgnoreCase) || text.StartsWith("BepInEx", StringComparison.Ordinal) || text.StartsWith("0Harmony", StringComparison.Ordinal) || text.StartsWith("Harmony", StringComparison.Ordinal)) { return false; } return true; } private static void ClassifyAssembly(Assembly assembly, HashSet gameAssemblies, Dictionary directPluginNames, string pluginRoot, out BehaviourSource source, out string groupName, out string groupId) { string text = assembly.GetName().Name ?? "Unknown assembly"; if (gameAssemblies != null && gameAssemblies.Contains(assembly)) { source = BehaviourSource.Valheim; groupName = "Valheim"; groupId = "valheim|" + text; return; } if (directPluginNames.TryGetValue(assembly, out var value)) { source = BehaviourSource.Mod; groupName = value; groupId = "mod|" + text; return; } string assemblyLocation = GetAssemblyLocation(assembly); if (!string.IsNullOrEmpty(pluginRoot) && !string.IsNullOrEmpty(assemblyLocation) && assemblyLocation.StartsWith(pluginRoot, StringComparison.OrdinalIgnoreCase)) { source = BehaviourSource.Mod; groupName = text; groupId = "mod|" + text; } else { source = BehaviourSource.Other; groupName = text; groupId = "other|" + text; } } private static bool IsProfileableMonoBehaviourType(Type type) { if (type == null || type.IsAbstract || type.IsInterface || type.ContainsGenericParameters) { return false; } try { return type != typeof(MonoBehaviour) && typeof(MonoBehaviour).IsAssignableFrom(type); } catch { return false; } } private static MethodInfo FindEffectiveLifecycleMethod(Type type, string methodName) { Type type2 = type; while (type2 != null && type2 != typeof(MonoBehaviour)) { MethodInfo methodInfo; try { methodInfo = type2.GetMethod(methodName, BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, Type.EmptyTypes, null); } catch { methodInfo = null; } if (IsProfileableMethod(methodInfo)) { return methodInfo; } type2 = type2.BaseType; } return null; } private static IEnumerable GetDeclaredProfileableMethods(Type type) { MethodInfo[] methods; try { methods = type.GetMethods(BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); } catch { return Array.Empty(); } return (from method in methods.Where(IsProfileableMethod) where !IsCompilerGeneratedMethod(method) where !LifecycleCallbackNames.Contains(method.Name, StringComparer.Ordinal) where !FrameCallbackNames.Contains(method.Name) select method).OrderBy((MethodInfo method) => method.Name, StringComparer.OrdinalIgnoreCase).ThenBy((MethodInfo method) => GetParameterCount(method)); } private static bool IsCompilerGeneratedMethod(MethodInfo method) { if (method == null || method.Name.StartsWith("<", StringComparison.Ordinal)) { return true; } try { return method.IsDefined(typeof(CompilerGeneratedAttribute), inherit: false); } catch { return false; } } private static bool IsProfileableMethod(MethodInfo method) { if (method == null || method.IsStatic || method.IsAbstract || method.IsGenericMethodDefinition || method.ContainsGenericParameters || method.IsSpecialName) { return false; } return HasManagedBody(method); } private static bool IsModAssembly(Assembly assembly, HashSet gameAssemblies, Dictionary directPluginNames, string pluginRoot) { if (assembly == null || (gameAssemblies != null && gameAssemblies.Contains(assembly))) { return false; } if (directPluginNames.ContainsKey(assembly)) { return true; } string assemblyLocation = GetAssemblyLocation(assembly); return !string.IsNullOrEmpty(pluginRoot) && !string.IsNullOrEmpty(assemblyLocation) && assemblyLocation.StartsWith(pluginRoot, StringComparison.OrdinalIgnoreCase); } private static bool HasManagedBody(MethodBase method) { try { return method != null && method.GetMethodBody() != null; } catch { return false; } } private static bool IsAsyncOrIterator(MethodInfo method) { if (method == null) { return false; } try { if (method.IsDefined(typeof(AsyncStateMachineAttribute), inherit: false) || method.IsDefined(typeof(IteratorStateMachineAttribute), inherit: false)) { return true; } Type returnType = method.ReturnType; return returnType != null && (typeof(IEnumerator).IsAssignableFrom(returnType) || typeof(IEnumerable).IsAssignableFrom(returnType)); } catch { return false; } } private static string BuildMethodTooltip(MethodInfo method, bool asyncOrIterator) { string methodSignature = GetMethodSignature(method); if (!asyncOrIterator) { return methodSignature; } return methodSignature + "\nCoroutine/async warning: only the synchronous call that creates or advances the state machine is measured, not the complete asynchronous lifetime."; } private static string GetMethodSignature(MethodInfo method) { if (method == null) { return "Unknown method"; } string text = method.DeclaringType?.FullName ?? "UnknownType"; string text2; try { text2 = string.Join(", ", (from parameter in method.GetParameters() select (parameter.ParameterType.FullName ?? parameter.ParameterType.Name) + " " + parameter.Name).ToArray()); } catch { text2 = "?"; } return text + "." + method.Name + "(" + text2 + ")"; } private static Type[] GetLoadableTypes(Assembly assembly) { try { return assembly.GetTypes(); } catch (ReflectionTypeLoadException ex) { return ex.Types?.Where((Type type) => type != null).ToArray() ?? Array.Empty(); } catch { return Array.Empty(); } } private static string BuildSelectionKey(Type type, MethodInfo method) { string text = type.Assembly.GetName().Name ?? "Unknown"; string text2 = type.FullName ?? type.Name; string text3; try { text3 = string.Join(",", (from parameter in method.GetParameters() select parameter.ParameterType.FullName ?? parameter.ParameterType.Name).ToArray()); } catch { text3 = "?"; } return text + "|" + text2 + "|" + method.Name + "(" + text3 + ")"; } private static int GetParameterCount(MethodInfo method) { try { return ((object)method != null) ? method.GetParameters().Length : 0; } catch { return 0; } } private static string GetAssemblyLocation(Assembly assembly) { try { return NormalizeFilePath(assembly.Location); } catch { return string.Empty; } } private static string NormalizeDirectoryPath(string path) { string text = NormalizeFilePath(path); if (string.IsNullOrEmpty(text)) { return string.Empty; } string text2 = text.TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar); char directorySeparatorChar = Path.DirectorySeparatorChar; return text2 + directorySeparatorChar; } private static string NormalizeFilePath(string path) { if (string.IsNullOrEmpty(path)) { return string.Empty; } try { return Path.GetFullPath(path); } catch { return path; } } private static void TimingPrefix(ref long __state) { __state = Stopwatch.GetTimestamp(); } private static Exception TimingFinalizer(Exception __exception, long __state, MethodBase __originalMethod, object __instance) { try { if (__state <= 0 || Thread.CurrentThread.ManagedThreadId != _mainThreadId) { return __exception; } MonoBehaviourCallProfilerTool instance = _instance; if (instance == null || !instance._profilingActive) { return __exception; } long num = Stopwatch.GetTimestamp() - __state; if (num < 0) { return __exception; } double elapsedMs = (double)num * MsPerTick; float realtimeSinceStartup = Time.realtimeSinceStartup; Type runtimeType = __instance?.GetType(); instance.Record(__originalMethod, runtimeType, elapsedMs, realtimeSinceStartup); } catch { } return __exception; } private void StartProfiling() { if (_profilingActive) { return; } if (!_listReady) { RefreshBehaviourList(); } if (!_listReady) { return; } try { ApplySelectionPolicy(); ResetAllStats(); StartProfilingCore(); } catch (Exception ex) { _status = "Start error: " + ex.GetType().Name + ": " + ex.Message; _logger.LogError((object)ex); } } private void StartProfilingCore() { //IL_01ac: Unknown result type (might be due to invalid IL or missing references) //IL_01b1: Unknown result type (might be due to invalid IL or missing references) //IL_01b9: Expected O, but got Unknown //IL_01cf: Unknown result type (might be due to invalid IL or missing references) //IL_01d4: Unknown result type (might be due to invalid IL or missing references) //IL_01e1: Expected O, but got Unknown Dictionary> dictionary = new Dictionary>(); int num = 0; int num2 = 0; lock (_lock) { foreach (BehaviourTypeEntry type in _types) { foreach (BehaviourMethodEntry method in type.Methods) { if (method.Selected && !(method.Method == null)) { num++; if (method.IsAsyncOrIterator) { num2++; } if (!dictionary.TryGetValue(method.Method, out var value)) { value = new List(); dictionary[method.Method] = value; } value.Add(new RuntimeBehaviourEntry(type.Type, method)); } } } _runtimeEntries = dictionary.ToDictionary((KeyValuePair> pair) => pair.Key, (KeyValuePair> pair) => pair.Value.ToArray()); _instrumentedMethods.Clear(); } if (num == 0) { _status = "No MonoBehaviour methods are selected."; return; } HarmonyMethod val = new HarmonyMethod(AccessTools.Method(typeof(MonoBehaviourCallProfilerTool), "TimingPrefix", (Type[])null, (Type[])null)) { priority = 0 }; HarmonyMethod val2 = new HarmonyMethod(AccessTools.Method(typeof(MonoBehaviourCallProfilerTool), "TimingFinalizer", (Type[])null, (Type[])null)) { priority = 800 }; int num3 = 0; foreach (MethodBase key in dictionary.Keys) { try { _harmony.Patch(key, val, (HarmonyMethod)null, (HarmonyMethod)null, val2, (HarmonyMethod)null); _instrumentedMethods.Add(key); num3++; } catch (Exception ex) { _logger.LogDebug((object)$"Could not instrument MonoBehaviour call {key}: {ex.GetType().Name}: {ex.Message}"); } } if (num3 == 0) { lock (_lock) { _runtimeEntries = new Dictionary(); _instrumentedMethods.Clear(); } _status = "No selected MonoBehaviour methods could be instrumented."; return; } _sessionStartRealtime = Time.realtimeSinceStartup; _profilingActive = true; _status = $"Profiling enabled. Selected entries: {num}. Instrumented methods: {num3}."; if (num2 > 0) { _status += $" Coroutine/async entries: {num2} (synchronous portion only)."; } MarkViewDirty(); } private void StopProfiling() { try { StopProfilingInternal(); _status = "Profiling paused. Lifetime data retained."; MarkViewDirty(); } catch (Exception ex) { _status = "Pause error: " + ex.GetType().Name + ": " + ex.Message; _logger.LogError((object)ex); } } private void StopProfilingInternal() { _profilingActive = false; _harmony.UnpatchSelf(); lock (_lock) { _instrumentedMethods.Clear(); _runtimeEntries = new Dictionary(); } } private void ApplySelection() { bool profilingActive = _profilingActive; if (profilingActive) { StopProfilingInternal(); } ApplySelectionPolicy(); ResetAllStats(); if (profilingActive) { StartProfilingCore(); } else { _status = "Selection applied. Start profiling to collect lifetime data."; } _selectionDirty = false; MarkSelectionRowsDirty(); MarkViewDirty(); } private void ApplySelectionPolicy() { List entries; lock (_lock) { entries = _types.SelectMany((BehaviourTypeEntry type) => type.Methods).ToList(); } _selectionPolicy.Save(entries); } private void ResetAllStats() { lock (_lock) { foreach (BehaviourTypeEntry type in _types) { foreach (BehaviourMethodEntry method in type.Methods) { method.Stat.Reset(); } } } _sessionStartRealtime = Time.realtimeSinceStartup; MarkViewDirty(); } private void Record(MethodBase instrumentedMethod, Type runtimeType, double elapsedMs, float now) { if (!_profilingActive || instrumentedMethod == null) { return; } Dictionary runtimeEntries = _runtimeEntries; if (!runtimeEntries.TryGetValue(instrumentedMethod, out var value) || value == null || value.Length == 0) { return; } bool flag = false; if (runtimeType != null) { for (int i = 0; i < value.Length; i++) { if (value[i] != null && value[i].RuntimeType == runtimeType) { flag = true; break; } } } float elapsedSinceStart = Mathf.Max(0f, now - _sessionStartRealtime); foreach (RuntimeBehaviourEntry runtimeBehaviourEntry in value) { if (runtimeBehaviourEntry == null || runtimeBehaviourEntry.Entry == null) { continue; } if (runtimeType != null) { if (flag) { if (runtimeBehaviourEntry.RuntimeType != runtimeType) { continue; } } else if (runtimeBehaviourEntry.RuntimeType == null || !runtimeBehaviourEntry.RuntimeType.IsAssignableFrom(runtimeType)) { continue; } } runtimeBehaviourEntry.Entry.Stat.Add(elapsedMs, elapsedSinceStart); } _viewDirty = true; } private void DrawSelectionTab() { //IL_0101: Unknown result type (might be due to invalid IL or missing references) //IL_010b: Unknown result type (might be due to invalid IL or missing references) //IL_0110: Unknown result type (might be due to invalid IL or missing references) //IL_0144: Unknown result type (might be due to invalid IL or missing references) //IL_0149: Unknown result type (might be due to invalid IL or missing references) //IL_014b: Unknown result type (might be due to invalid IL or missing references) if (!_listReady) { GUILayout.BeginHorizontal(Array.Empty()); Label("MonoBehaviour call list is not loaded.", GUILayout.ExpandWidth(true)); GUI.enabled = !_profilingActive; if (GUILayout.Button("Load list", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(110f) })) { RefreshBehaviourList(); } GUI.enabled = true; GUILayout.EndHorizontal(); return; } if (!_selectionExpansionInitialized) { ExpandPathsToSelected(collapseUnselected: true); _selectionExpansionInitialized = true; } DrawSelectionBulkActions(); GUILayout.Space(3f); DrawSelectionFilterRow(); GUILayout.Space(2f); DrawSelectionSearchRow(); GUILayout.Space(2f); RebuildSelectionRowsIfNeeded(); float currentRowHeight = CurrentRowHeight; float num = Mathf.Max(1f, (float)_selectionRows.Count * currentRowHeight); _selectionScroll = GUILayout.BeginScrollView(_selectionScroll, Array.Empty()); Rect rect = GUILayoutUtility.GetRect(Mathf.Max(1f, MainWindowWidth - 30f), num, (GUILayoutOption[])(object)new GUILayoutOption[2] { GUILayout.ExpandWidth(true), GUILayout.Height(num) }); DrawSelectionRows(rect, currentRowHeight); GUILayout.EndScrollView(); } private void DrawSelectionBulkActions() { //IL_0027: Unknown result type (might be due to invalid IL or missing references) //IL_0044: Expected O, but got Unknown //IL_0062: Unknown result type (might be due to invalid IL or missing references) //IL_007f: Expected O, but got Unknown //IL_0096: Unknown result type (might be due to invalid IL or missing references) //IL_00b3: Expected O, but got Unknown //IL_0170: Unknown result type (might be due to invalid IL or missing references) //IL_018d: Expected O, but got Unknown //IL_023a: Unknown result type (might be due to invalid IL or missing references) //IL_0257: Expected O, but got Unknown //IL_037d: Unknown result type (might be due to invalid IL or missing references) //IL_039b: Expected O, but got Unknown GUILayout.BeginHorizontal(Array.Empty()); GUI.enabled = !_profilingActive; if (GUILayout.Button(new GUIContent("Refresh list", "Rescan loaded assemblies and rebuild lifecycle and declared method discovery.\nUnsaved selection changes are discarded."), (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(100f) })) { RefreshBehaviourList(); } GUI.enabled = true; if (GUILayout.Button(new GUIContent("Enable all", "Enable every method visible through the current Source, Show declared methods, Present in active scene and Search filters.\nMethods hidden by the current filters are not changed."), (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(88f) })) { EnableFilteredSelection(); } if (GUILayout.Button(new GUIContent("Enable lifecycle", "Enable default synchronous lifecycle callbacks added by mods: Awake, Start, OnEnable, OnDisable and OnDestroy.\nExisting manually enabled methods remain selected."), (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(120f) })) { lock (_lock) { foreach (BehaviourMethodEntry item in _types.SelectMany((BehaviourTypeEntry type) => type.Methods)) { if (item.DefaultSelected) { item.Selected = true; } } } _selectionDirty = true; ExpandPathsToSelected(collapseUnselected: false); MarkSelectionRowsDirty(); } if (GUILayout.Button(new GUIContent("Disable all", "Clear every selected call and collapse the selection tree."), (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(88f) })) { lock (_lock) { foreach (BehaviourMethodEntry item2 in _types.SelectMany((BehaviourTypeEntry type) => type.Methods)) { item2.Selected = false; } } _selectionDirty = true; CollapseSelectionTree(); MarkSelectionRowsDirty(); } if (GUILayout.Button(new GUIContent("Reset to defaults", "Restore the default selection: synchronous mod lifecycle callbacks enabled; declared, Valheim and Other methods disabled."), (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(120f) })) { lock (_lock) { foreach (BehaviourMethodEntry item3 in _types.SelectMany((BehaviourTypeEntry type) => type.Methods)) { item3.Selected = item3.DefaultSelected; } } _selectionDirty = true; ExpandPathsToSelected(collapseUnselected: true); MarkSelectionRowsDirty(); } GUILayout.Space(8f); int selectedMethodCount = GetSelectedMethodCount(); GUILayout.Box($"Selected: {selectedMethodCount}", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(105f) }); GUILayout.Space(16f); GUI.enabled = _selectionDirty; GUIStyle val = (_selectionDirty ? _theme.AccentButtonStyle : GUI.skin.button); if (GUILayout.Button(new GUIContent("Apply selection", "Apply the current call selection.\nIf profiling is active, instrumentation is restarted and lifetime statistics are reset."), val, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(132f) })) { ApplySelection(); } GUI.enabled = true; if (_selectionDirty) { GUILayout.Space(6f); GUILayout.Label("Selection has changed", _theme.AccentLabelStyle, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.ExpandWidth(true) }); } else { GUILayout.FlexibleSpace(); } GUILayout.EndHorizontal(); } private void DrawSelectionFilterRow() { //IL_0016: Unknown result type (might be due to invalid IL or missing references) //IL_0039: Expected O, but got Unknown //IL_0052: Unknown result type (might be due to invalid IL or missing references) //IL_0058: Expected O, but got Unknown //IL_0064: Unknown result type (might be due to invalid IL or missing references) //IL_006a: Expected O, but got Unknown //IL_0076: Unknown result type (might be due to invalid IL or missing references) //IL_007c: Expected O, but got Unknown //IL_00e9: Unknown result type (might be due to invalid IL or missing references) //IL_0103: Expected O, but got Unknown //IL_00ae: Unknown result type (might be due to invalid IL or missing references) //IL_00b3: Unknown result type (might be due to invalid IL or missing references) //IL_0152: Unknown result type (might be due to invalid IL or missing references) //IL_016c: Expected O, but got Unknown //IL_011f: Unknown result type (might be due to invalid IL or missing references) //IL_0124: Unknown result type (might be due to invalid IL or missing references) //IL_019b: Unknown result type (might be due to invalid IL or missing references) //IL_01a0: Unknown result type (might be due to invalid IL or missing references) GUILayout.BeginHorizontal(Array.Empty()); GUILayout.Label(new GUIContent("Source:", "Choose one source category to display. These tabs only filter the view and do not change saved selection."), _labelStyle, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(52f) }); BehaviourSource behaviourSource = (BehaviourSource)GUILayout.Toolbar((int)_selectionSource, (GUIContent[])(object)new GUIContent[3] { new GUIContent("Mods", "Show MonoBehaviour calls discovered in BepInEx plugin assemblies."), new GUIContent("Valheim", "Show MonoBehaviour calls declared by Valheim game assemblies."), new GUIContent("Other", "Show calls from managed assemblies that are neither Valheim nor identifiable BepInEx plugins.") }, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(245f) }); if (behaviourSource != _selectionSource) { _selectionSource = behaviourSource; _selectionScroll = Vector2.zero; ExpandPathsToSelected(collapseUnselected: false); MarkSelectionRowsDirty(); } GUILayout.Space(8f); bool flag = ProfilerGui.ToggleLayout(_theme, _showDeclaredMethods, new GUIContent("Show declared methods", "Show managed instance methods declared directly by each MonoBehaviour type in addition to lifecycle callbacks.\nSelected declared methods remain visible when this filter is disabled.\nFrame callbacks Update, FixedUpdate, LateUpdate and OnGUI are intentionally excluded."), 185f, _labelStyle, 0f); if (flag != _showDeclaredMethods) { _showDeclaredMethods = flag; _selectionScroll = Vector2.zero; MarkSelectionRowsDirty(); } GUILayout.Space(8f); bool flag2 = ProfilerGui.ToggleLayout(_theme, _presentInActiveScene, new GUIContent("Present in active scene", "Show only MonoBehaviour types that currently have at least one loaded instance in the active Unity scene.\nInactive GameObjects and disabled components are still considered present.\nThis is a view filter only and does not remove saved selections."), 190f, _labelStyle, 0f); if (flag2 != _presentInActiveScene) { _presentInActiveScene = flag2; if (_presentInActiveScene) { RefreshActiveScenePresence(); } _selectionScroll = Vector2.zero; MarkSelectionRowsDirty(); } GUILayout.FlexibleSpace(); GUILayout.EndHorizontal(); } private void DrawSelectionSearchRow() { //IL_013b: Unknown result type (might be due to invalid IL or missing references) //IL_0140: Unknown result type (might be due to invalid IL or missing references) //IL_00f9: Unknown result type (might be due to invalid IL or missing references) //IL_00fe: Unknown result type (might be due to invalid IL or missing references) GUILayout.BeginHorizontal(Array.Empty()); if (GUILayout.Button("Expand all", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(78f) })) { SetVisibleSelectionExpansion(expanded: true); } if (GUILayout.Button("Collapse all", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(78f) })) { SetVisibleSelectionExpansion(expanded: false); } GUILayout.Space(8f); GUILayout.Label("Search:", _labelStyle, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(48f) }); string text = GUILayout.TextField(_search ?? string.Empty, (GUILayoutOption[])(object)new GUILayoutOption[2] { GUILayout.Width(245f), GUILayout.Height(Mathf.Max(18f, CurrentRowHeight - 2f)) }); if (!string.Equals(text, _search, StringComparison.Ordinal)) { _search = text ?? string.Empty; _selectionScroll = Vector2.zero; MarkSelectionRowsDirty(); } if (GUILayout.Button("Clear", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(55f) })) { _search = string.Empty; _selectionScroll = Vector2.zero; GUI.FocusControl((string)null); MarkSelectionRowsDirty(); } GUILayout.FlexibleSpace(); GUILayout.EndHorizontal(); } private void EnableFilteredSelection() { string text = (_search ?? string.Empty).Trim(); bool flag = text.Length > 0; bool flag2 = false; lock (_lock) { foreach (BehaviourTypeEntry type in _types) { if (!TypePassesSourceFilter(type) || !IsPresentInActiveScene(type) || !TypeMatchesSearch(type, text, flag)) { continue; } bool flag3 = flag && (ContainsIgnoreCase(type.GroupName, text) || ContainsIgnoreCase(type.AssemblyName, text) || ContainsIgnoreCase(type.TypeName, text)); foreach (BehaviourMethodEntry visibleMethod in GetVisibleMethods(type, text, flag && !flag3)) { if (!visibleMethod.Selected) { visibleMethod.Selected = true; flag2 = true; } } } } if (flag2) { _selectionDirty = true; ExpandPathsToSelected(collapseUnselected: false); MarkSelectionRowsDirty(); } } private int GetSelectedMethodCount() { lock (_lock) { return _types.Sum((BehaviourTypeEntry type) => type.Methods.Count((BehaviourMethodEntry method) => method.Selected)); } } private void ExpandPathsToSelected(bool collapseUnselected) { lock (_lock) { if (collapseUnselected) { _selectionGroupExpanded.Clear(); } foreach (BehaviourTypeEntry type in _types) { bool flag = type.Methods.Any((BehaviourMethodEntry method) => method.Selected); if (collapseUnselected || flag) { type.Expanded = flag; } if (flag) { _selectionGroupExpanded[type.GroupId] = true; } else if (collapseUnselected && !_selectionGroupExpanded.ContainsKey(type.GroupId)) { _selectionGroupExpanded[type.GroupId] = false; } } } } private void CollapseSelectionTree() { lock (_lock) { foreach (BehaviourTypeEntry type in _types) { type.Expanded = false; } foreach (string item in _types.Select((BehaviourTypeEntry type) => type.GroupId).Distinct().ToList()) { _selectionGroupExpanded[item] = false; } } } private void SetVisibleSelectionExpansion(bool expanded) { string text = (_search ?? string.Empty).Trim(); bool hasSearch = text.Length > 0; lock (_lock) { foreach (BehaviourTypeEntry type in _types) { if (TypePassesSourceFilter(type) && IsPresentInActiveScene(type) && TypeMatchesSearch(type, text, hasSearch)) { type.Expanded = expanded; _selectionGroupExpanded[type.GroupId] = expanded; } } } MarkSelectionRowsDirty(); } private void RebuildSelectionRowsIfNeeded() { if (!_selectionRowsDirty && string.Equals(_lastSelectionSearch, _search, StringComparison.Ordinal)) { return; } _selectionRowsDirty = false; _lastSelectionSearch = _search ?? string.Empty; _selectionRows.Clear(); string search = (_search ?? string.Empty).Trim(); bool hasSearch = search.Length > 0; List source; lock (_lock) { source = (from type in _types.Where(TypePassesSourceFilter).Where(IsPresentInActiveScene) where TypeMatchesSearch(type, search, hasSearch) select type).ToList(); } foreach (IGrouping item in (from type in source group type by type.GroupId into @group where @group.Any() select @group).OrderBy, string>((IGrouping group) => group.First().GroupName, StringComparer.OrdinalIgnoreCase)) { BehaviourTypeEntry behaviourTypeEntry = item.First(); if (!_selectionGroupExpanded.TryGetValue(item.Key, out var value)) { value = item.Any((BehaviourTypeEntry type) => type.Methods.Any((BehaviourMethodEntry method) => method.Selected)); _selectionGroupExpanded[item.Key] = value; } _selectionRows.Add(new SelectionRow { Kind = SelectionRowKind.Group, GroupId = item.Key, Text = (behaviourTypeEntry.GroupName ?? behaviourTypeEntry.AssemblyName ?? "Unknown"), Indent = 0 }); if (!value) { continue; } foreach (BehaviourTypeEntry item2 in item.OrderBy((BehaviourTypeEntry item) => item.TypeName, StringComparer.OrdinalIgnoreCase)) { bool flag = hasSearch && (ContainsIgnoreCase(item2.GroupName, search) || ContainsIgnoreCase(item2.AssemblyName, search) || ContainsIgnoreCase(item2.TypeName, search)); List visibleMethods = GetVisibleMethods(item2, search, hasSearch && !flag); if (visibleMethods.Count == 0) { continue; } _selectionRows.Add(new SelectionRow { Kind = SelectionRowKind.Type, GroupId = item.Key, Text = item2.TypeName, TypeEntry = item2, Indent = 1 }); if (!item2.Expanded && !hasSearch) { continue; } foreach (BehaviourMethodEntry item3 in visibleMethods) { _selectionRows.Add(new SelectionRow { Kind = SelectionRowKind.Method, GroupId = item.Key, Text = item3.DisplayName, Tooltip = item3.Tooltip, TypeEntry = item2, MethodEntry = item3, Indent = 2 }); } } } } private bool TypePassesSourceFilter(BehaviourTypeEntry type) { return type != null && type.Source == _selectionSource; } private bool TypeMatchesSearch(BehaviourTypeEntry type, string search, bool hasSearch) { if (!hasSearch) { return GetVisibleMethods(type, search, filterBySearch: false).Count > 0; } if (ContainsIgnoreCase(type.GroupName, search) || ContainsIgnoreCase(type.AssemblyName, search) || ContainsIgnoreCase(type.TypeName, search)) { return GetVisibleMethods(type, search, filterBySearch: false).Count > 0; } return GetVisibleMethods(type, search, filterBySearch: true).Count > 0; } private List GetVisibleMethods(BehaviourTypeEntry type, string search, bool filterBySearch) { IEnumerable methods = type.Methods; methods = methods.Where((BehaviourMethodEntry method) => method.Kind == ProfileMethodKind.Lifecycle || _showDeclaredMethods || method.Selected); if (filterBySearch) { methods = methods.Where((BehaviourMethodEntry method) => ContainsIgnoreCase(method.DisplayName, search) || ContainsIgnoreCase(method.Tooltip, search) || ContainsIgnoreCase(method.Kind.ToString(), search)); } return methods.OrderBy((BehaviourMethodEntry method) => method.Kind).ThenBy((BehaviourMethodEntry method) => method.DisplayName, StringComparer.OrdinalIgnoreCase).ThenBy((BehaviourMethodEntry method) => method.Key, StringComparer.Ordinal) .ToList(); } private void DrawSelectionRows(Rect contentRect, float rowHeight) { //IL_00da: Unknown result type (might be due to invalid IL or missing references) //IL_00df: Unknown result type (might be due to invalid IL or missing references) //IL_00e4: Unknown result type (might be due to invalid IL or missing references) //IL_00e6: Unknown result type (might be due to invalid IL or missing references) //IL_0128: Unknown result type (might be due to invalid IL or missing references) //IL_0194: Unknown result type (might be due to invalid IL or missing references) //IL_0199: Unknown result type (might be due to invalid IL or missing references) //IL_019e: Unknown result type (might be due to invalid IL or missing references) //IL_01a0: Unknown result type (might be due to invalid IL or missing references) //IL_02f0: Unknown result type (might be due to invalid IL or missing references) //IL_030b: Unknown result type (might be due to invalid IL or missing references) //IL_0320: Expected O, but got Unknown //IL_024b: Unknown result type (might be due to invalid IL or missing references) float visibleTop = _selectionScroll.y - 80f; float visibleBottom = _selectionScroll.y + Mathf.Max(100f, MainWindowHeight - 255f) + 80f; Rect rect = default(Rect); for (int i = 0; i < _selectionRows.Count; i++) { float num = (float)i * rowHeight; if (!RowVisible(num, rowHeight, visibleTop, visibleBottom)) { continue; } SelectionRow selectionRow = _selectionRows[i]; float num2 = ((Rect)(ref contentRect)).y + num; float num3 = ((Rect)(ref contentRect)).x + (float)selectionRow.Indent * 17f; switch (selectionRow.Kind) { case SelectionRowKind.Group: { bool value; bool flag2 = _selectionGroupExpanded.TryGetValue(selectionRow.GroupId, out value) && value; Rect val = InsetButtonRect(new Rect(num3, num2, 14f, rowHeight)); bool flag3 = GUI.Button(val, flag2 ? "▼" : "▶", _compactButtonStyle); num3 += 14f; if (flag3 | GUI.Button(new Rect(num3, num2, ((Rect)(ref contentRect)).width - num3 + ((Rect)(ref contentRect)).x, rowHeight), selectionRow.Text, _groupLabelStyle)) { _selectionGroupExpanded[selectionRow.GroupId] = !flag2; MarkSelectionRowsDirty(); } break; } case SelectionRowKind.Type: { BehaviourTypeEntry typeEntry = selectionRow.TypeEntry; bool flag4 = typeEntry?.Expanded ?? false; Rect val2 = InsetButtonRect(new Rect(num3, num2, 14f, rowHeight)); bool flag5 = GUI.Button(val2, flag4 ? "▼" : "▶", _compactButtonStyle); num3 += 14f; List list = ((typeEntry == null) ? new List() : GetVisibleMethods(typeEntry, string.Empty, filterBySearch: false)); int num4 = list.Count((BehaviourMethodEntry method) => method.Selected); string text2 = $"{selectionRow.Text} ({num4}/{list.Count})"; if (flag5 | GUI.Button(new Rect(num3, num2, ((Rect)(ref contentRect)).width - num3 + ((Rect)(ref contentRect)).x, rowHeight), text2, _headerLabelStyle)) { if (typeEntry != null) { typeEntry.Expanded = !flag4; } MarkSelectionRowsDirty(); } break; } case SelectionRowKind.Method: { BehaviourMethodEntry methodEntry = selectionRow.MethodEntry; if (methodEntry != null) { ((Rect)(ref rect))..ctor(num3, num2, ((Rect)(ref contentRect)).width - (num3 - ((Rect)(ref contentRect)).x), rowHeight); string text = (methodEntry.IsAsyncOrIterator ? (methodEntry.DisplayName + " !") : methodEntry.DisplayName); bool flag = ProfilerGui.Toggle(_theme, rect, methodEntry.Selected, new GUIContent(text, selectionRow.Tooltip ?? string.Empty), _labelStyle, 0f); if (flag != methodEntry.Selected) { methodEntry.Selected = flag; _selectionDirty = true; MarkSelectionRowsDirty(); } } break; } } } } private static bool ContainsIgnoreCase(string value, string search) { return !string.IsNullOrEmpty(value) && value.IndexOf(search, StringComparison.OrdinalIgnoreCase) >= 0; } private void PrepareTableLayout() { if (_layoutMetricsDirty || (Object)(object)_lastLayoutSkin != (Object)(object)GUI.skin || Mathf.Abs(_lastLayoutWindowWidth - MainWindowWidth) > 0.01f || _lastLayoutGroupByMod != _groupByMod) { _lastLayoutSkin = GUI.skin; _lastLayoutWindowWidth = MainWindowWidth; _lastLayoutGroupByMod = _groupByMod; _layoutMetricsDirty = false; _drawTimeColumnWidth = 86f; _drawCountColumnWidth = 78f; _drawTimelineColumnWidth = 82f; _drawSourceColumnWidth = CalculateSourceColumnWidth(); _drawTypeColumnWidth = CalculateTypeColumnWidth(); float num = _drawCountColumnWidth + _drawTimeColumnWidth * 6f + _drawTimelineColumnWidth * 2f; float num2 = MainWindowWidth - 18f - _drawSourceColumnWidth - num; _drawTypeColumnWidth = Mathf.Max(_drawTypeColumnWidth, num2); _drawContentWidth = _drawSourceColumnWidth + num + _drawTypeColumnWidth; } } private float CalculateSourceColumnWidth() { //IL_0035: Unknown result type (might be due to invalid IL or missing references) //IL_003f: Expected O, but got Unknown //IL_003a: Unknown result type (might be due to invalid IL or missing references) //IL_01b4: Unknown result type (might be due to invalid IL or missing references) //IL_01be: Expected O, but got Unknown //IL_01b9: Unknown result type (might be due to invalid IL or missing references) //IL_0093: Unknown result type (might be due to invalid IL or missing references) //IL_009d: Expected O, but got Unknown //IL_0098: Unknown result type (might be due to invalid IL or missing references) //IL_012b: Unknown result type (might be due to invalid IL or missing references) //IL_0135: Expected O, but got Unknown //IL_0130: Unknown result type (might be due to invalid IL or missing references) if (_labelStyle == null) { return 240f; } string text = (_groupByMod ? "Method" : "Mod │ Method"); float num = _headerLabelStyle.CalcSize(new GUIContent(text)).x + 16f; if (_groupByMod) { foreach (GroupRowView cachedGroupedRow in _cachedGroupedRows) { float num2 = 14f + _groupLabelStyle.CalcSize(new GUIContent(cachedGroupedRow.GroupName ?? "Unknown")).x + 16f; num = Mathf.Max(num, num2); bool flag; lock (_lock) { flag = _groupExpanded.TryGetValue(cachedGroupedRow.GroupId, out var value) && value; } if (!flag) { continue; } foreach (FlatRowView row in cachedGroupedRow.Rows) { float num3 = _labelStyle.CalcSize(new GUIContent(BuildEntryMethodName(row.Entry))).x + 12f; num = Mathf.Max(num, num3); } } } else { foreach (FlatRowView cachedFlatRow in _cachedFlatRows) { float num4 = _labelStyle.CalcSize(new GUIContent(BuildUngroupedEntryName(cachedFlatRow.Entry))).x + 12f; num = Mathf.Max(num, num4); } } return Mathf.Clamp(Mathf.Ceil(num), 170f, 900f); } private float CalculateTypeColumnWidth() { //IL_0024: Unknown result type (might be due to invalid IL or missing references) //IL_002e: Expected O, but got Unknown //IL_0029: Unknown result type (might be due to invalid IL or missing references) //IL_00c1: Unknown result type (might be due to invalid IL or missing references) //IL_00cb: Expected O, but got Unknown //IL_00c6: Unknown result type (might be due to invalid IL or missing references) if (_labelStyle == null) { return 280f; } float num = _headerLabelStyle.CalcSize(new GUIContent("MonoBehaviour")).x + 16f; IEnumerable enumerable; if (!_groupByMod) { IEnumerable cachedFlatRows = _cachedFlatRows; enumerable = cachedFlatRows; } else { enumerable = _cachedGroupedRows.SelectMany((GroupRowView group) => group.Rows); } IEnumerable enumerable2 = enumerable; foreach (FlatRowView item in enumerable2) { string text = item.Entry?.TypeEntry?.TypeName ?? "Unknown"; num = Mathf.Max(num, _labelStyle.CalcSize(new GUIContent(text)).x + 12f); } return Mathf.Max(280f, Mathf.Ceil(num)); } private void DrawTableHeader() { //IL_0029: Unknown result type (might be due to invalid IL or missing references) //IL_002e: 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) Rect rect = GUILayoutUtility.GetRect(1f, CurrentHeaderHeight, (GUILayoutOption[])(object)new GUILayoutOption[2] { GUILayout.ExpandWidth(true), GUILayout.Height(CurrentHeaderHeight) }); GUI.BeginGroup(rect); try { float x = 0f - _profilerScroll.x; float y = 0f; float height = ((Rect)(ref rect)).height; DrawSortableHeaderCell(ref x, y, _drawSourceColumnWidth, height, _groupByMod ? "Method" : "Mod │ Method", "Measured method. In grouped mode the group name is shown in its own expandable row.\nClick to sort descending by this column.", TableSortColumn.Source); DrawSortableHeaderCell(ref x, y, _drawCountColumnWidth, height, "calls", "Total calls observed since the current profiling session started.\nClick to sort descending by this column.", TableSortColumn.Calls); DrawSortableHeaderCell(ref x, y, _drawTimeColumnWidth, height, "total ms", "Total inclusive CPU time accumulated by this method during the current profiling session.\nClick to sort descending by this column.", TableSortColumn.Total); DrawSortableHeaderCell(ref x, y, _drawTimeColumnWidth, height, "avg ms", "Average inclusive duration per call over the complete profiling session.\nClick to sort descending by this column.", TableSortColumn.Average); DrawSortableHeaderCell(ref x, y, _drawTimeColumnWidth, height, "max ms", "Slowest individual call observed during the complete profiling session.\nClick to sort descending by this column.", TableSortColumn.Max); DrawSortableHeaderCell(ref x, y, _drawTimeColumnWidth, height, "last ms", "Duration of the most recently observed call.\nClick to sort descending by this column.", TableSortColumn.Last); DrawSortableHeaderCell(ref x, y, _drawTimeColumnWidth, height, "p95", "Approximate lifetime 95th percentile based on a logarithmic histogram.\nGrouped rows show the highest child percentile, not a combined percentile.\nClick to sort descending by this column.", TableSortColumn.P95); DrawSortableHeaderCell(ref x, y, _drawTimeColumnWidth, height, "p99", "Approximate lifetime 99th percentile based on a logarithmic histogram.\nGrouped rows show the highest child percentile, not a combined percentile.\nClick to sort descending by this column.", TableSortColumn.P99); DrawSortableHeaderCell(ref x, y, _drawTimelineColumnWidth, height, "first at", "Elapsed profiling-session time when the first call was observed.\nClick to sort descending by this column.", TableSortColumn.FirstAt); DrawSortableHeaderCell(ref x, y, _drawTimelineColumnWidth, height, "last at", "Elapsed profiling-session time when the latest call was observed.\nClick to sort descending by this column.", TableSortColumn.LastAt); DrawSortableHeaderCell(ref x, y, _drawTypeColumnWidth, height, "MonoBehaviour", "Managed MonoBehaviour runtime type represented by the row.\nClick to sort descending by this column.", TableSortColumn.MonoBehaviour); } finally { GUI.EndGroup(); } } private void DrawSortableHeaderCell(ref float x, float y, float width, float height, string text, string tooltip, TableSortColumn column) { //IL_0027: Unknown result type (might be due to invalid IL or missing references) //IL_003e: Unknown result type (might be due to invalid IL or missing references) //IL_0049: Expected O, but got Unknown Rect val = default(Rect); ((Rect)(ref val))..ctor(x, y, width, height); GUIStyle val2 = ((CurrentSortColumn == column) ? _activeHeaderLabelStyle : _headerLabelStyle); if (GUI.Button(val, new GUIContent(text ?? string.Empty, tooltip ?? string.Empty), val2)) { SetSortColumn(column); } x += width; } private float CalculateTableContentHeight() { if (!_groupByMod) { return Mathf.Max(1f, (float)_cachedFlatRows.Count * CurrentRowHeight); } float num = 0f; lock (_lock) { foreach (GroupRowView cachedGroupedRow in _cachedGroupedRows) { num += CurrentGroupHeight; if (_groupExpanded.TryGetValue(cachedGroupedRow.GroupId, out var value) && value) { num += (float)cachedGroupedRow.Rows.Count * CurrentRowHeight; } } } return Mathf.Max(1f, num); } private void DrawTableContent(Rect contentRect) { //IL_0144: Unknown result type (might be due to invalid IL or missing references) //IL_006e: Unknown result type (might be due to invalid IL or missing references) //IL_00e0: Unknown result type (might be due to invalid IL or missing references) float visibleTop = _profilerScroll.y - 80f; float visibleBottom = _profilerScroll.y + Mathf.Max(100f, MainWindowHeight - 145f) + 80f; float y = 0f; if (_groupByMod) { foreach (GroupRowView cachedGroupedRow in _cachedGroupedRows) { DrawGroupRow(contentRect, ref y, cachedGroupedRow, visibleTop, visibleBottom); bool flag; lock (_lock) { flag = _groupExpanded.TryGetValue(cachedGroupedRow.GroupId, out var value) && value; } if (flag) { foreach (FlatRowView row in cachedGroupedRow.Rows) { DrawDataRow(contentRect, ref y, row, grouped: true, visibleTop, visibleBottom); } } } return; } foreach (FlatRowView cachedFlatRow in _cachedFlatRows) { DrawDataRow(contentRect, ref y, cachedFlatRow, grouped: false, visibleTop, visibleBottom); } } private void DrawGroupRow(Rect contentRect, ref float y, GroupRowView group, float visibleTop, float visibleBottom) { //IL_007f: Unknown result type (might be due to invalid IL or missing references) //IL_0084: Unknown result type (might be due to invalid IL or missing references) //IL_0089: Unknown result type (might be due to invalid IL or missing references) //IL_008b: Unknown result type (might be due to invalid IL or missing references) //IL_00c6: Unknown result type (might be due to invalid IL or missing references) float currentGroupHeight = CurrentGroupHeight; if (!RowVisible(y, currentGroupHeight, visibleTop, visibleBottom)) { y += currentGroupHeight; return; } bool flag; lock (_lock) { flag = _groupExpanded.TryGetValue(group.GroupId, out var value) && value; } float x = ((Rect)(ref contentRect)).x; float num = ((Rect)(ref contentRect)).y + y; Rect val = InsetButtonRect(new Rect(x, num, 14f, currentGroupHeight)); bool flag2 = GUI.Button(val, flag ? "▼" : "▶", _compactButtonStyle); x += 14f; float num2 = _drawSourceColumnWidth - 14f; flag2 |= GUI.Button(new Rect(x, num, num2, currentGroupHeight), group.GroupName, _groupLabelStyle); x += num2; if (flag2) { lock (_lock) { _groupExpanded[group.GroupId] = !flag; } _layoutMetricsDirty = true; } DrawSummaryCells(ref x, num, currentGroupHeight, group.Summary); DrawCell(ref x, num, _drawTypeColumnWidth, currentGroupHeight, string.Empty, _labelStyle); y += currentGroupHeight; } private void DrawDataRow(Rect contentRect, ref float y, FlatRowView row, bool grouped, float visibleTop, float visibleBottom) { float currentRowHeight = CurrentRowHeight; if (!RowVisible(y, currentRowHeight, visibleTop, visibleBottom)) { y += currentRowHeight; return; } BehaviourMethodEntry entry = row.Entry; float x = ((Rect)(ref contentRect)).x; float y2 = ((Rect)(ref contentRect)).y + y; string text = (grouped ? BuildEntryMethodName(entry) : BuildUngroupedEntryName(entry)); DrawCell(ref x, y2, _drawSourceColumnWidth, currentRowHeight, text, _labelStyle, entry.Tooltip); DrawSnapshotCells(ref x, y2, currentRowHeight, row.Snapshot); DrawCell(ref x, y2, _drawTypeColumnWidth, currentRowHeight, entry.TypeEntry.TypeName, _labelStyle); y += currentRowHeight; } private void DrawSummaryCells(ref float x, float y, float height, GroupSummary summary) { DrawCell(ref x, y, _drawCountColumnWidth, height, FormatCount(summary.Calls), _labelStyle); DrawCell(ref x, y, _drawTimeColumnWidth, height, FormatMs(summary.TotalMs), _labelStyle); DrawCell(ref x, y, _drawTimeColumnWidth, height, FormatMs(summary.AverageMs), _labelStyle); DrawCell(ref x, y, _drawTimeColumnWidth, height, FormatMs(summary.MaxMs), _labelStyle); DrawCell(ref x, y, _drawTimeColumnWidth, height, FormatMs(summary.LastMs), _labelStyle); DrawCell(ref x, y, _drawTimeColumnWidth, height, FormatMs(summary.P95Ms), _labelStyle); DrawCell(ref x, y, _drawTimeColumnWidth, height, FormatMs(summary.P99Ms), _labelStyle); DrawCell(ref x, y, _drawTimelineColumnWidth, height, FormatTimeline(summary.FirstCallAtSeconds), _labelStyle); DrawCell(ref x, y, _drawTimelineColumnWidth, height, FormatTimeline(summary.LastCallAtSeconds), _labelStyle); } private void DrawSnapshotCells(ref float x, float y, float height, LifetimeProfilerSnapshot snapshot) { DrawCell(ref x, y, _drawCountColumnWidth, height, FormatCount(snapshot.Calls), _labelStyle); DrawCell(ref x, y, _drawTimeColumnWidth, height, FormatMs(snapshot.TotalMs), _labelStyle); DrawCell(ref x, y, _drawTimeColumnWidth, height, FormatMs(snapshot.AverageMs), _labelStyle); DrawCell(ref x, y, _drawTimeColumnWidth, height, FormatMs(snapshot.MaxMs), _labelStyle); DrawCell(ref x, y, _drawTimeColumnWidth, height, FormatMs(snapshot.LastMs), _labelStyle); DrawCell(ref x, y, _drawTimeColumnWidth, height, FormatMs(snapshot.P95Ms), _labelStyle); DrawCell(ref x, y, _drawTimeColumnWidth, height, FormatMs(snapshot.P99Ms), _labelStyle); DrawCell(ref x, y, _drawTimelineColumnWidth, height, FormatTimeline(snapshot.FirstCallAtSeconds), _labelStyle); DrawCell(ref x, y, _drawTimelineColumnWidth, height, FormatTimeline(snapshot.LastCallAtSeconds), _labelStyle); } private static bool RowVisible(float y, float height, float visibleTop, float visibleBottom) { return y + height >= visibleTop && y <= visibleBottom; } private static Rect InsetButtonRect(Rect rect) { //IL_0067: 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_006b: Unknown result type (might be due to invalid IL or missing references) ((Rect)(ref rect)).x = ((Rect)(ref rect)).x + 1f; ((Rect)(ref rect)).y = ((Rect)(ref rect)).y + 3f; ((Rect)(ref rect)).width = Mathf.Max(1f, ((Rect)(ref rect)).width - 3f); ((Rect)(ref rect)).height = Mathf.Max(1f, ((Rect)(ref rect)).height - 6f); return rect; } private static void DrawCell(ref float x, float y, float width, float height, string text, GUIStyle style, string tooltip = null) { //IL_0006: 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) //IL_002d: Expected O, but got Unknown GUI.Label(new Rect(x, y, width, height), new GUIContent(text ?? string.Empty, tooltip ?? string.Empty), style); x += width; } internal MonoBehaviourCallProfilerTool(ValheimProfilerApp app) { //IL_0138: Unknown result type (might be due to invalid IL or missing references) //IL_0142: Expected O, but got Unknown //IL_01a0: Unknown result type (might be due to invalid IL or missing references) //IL_01a1: Unknown result type (might be due to invalid IL or missing references) //IL_01a6: Unknown result type (might be due to invalid IL or missing references) //IL_01b8: Unknown result type (might be due to invalid IL or missing references) //IL_01bd: Unknown result type (might be due to invalid IL or missing references) //IL_01be: Unknown result type (might be due to invalid IL or missing references) //IL_01c3: Unknown result type (might be due to invalid IL or missing references) _app = app ?? throw new ArgumentNullException("app"); _logger = app.Logger; _windows = app.Windows; _theme = app.Theme; _harmony = new Harmony("shudnal.ValheimProfiler.MonoBehaviourCallProfiler"); _selectionPolicy = new SelectionPolicy(ProfilerPaths.GetConfigFilePath("MonoBehaviourCallSelection.cfg")); _mainThreadId = Thread.CurrentThread.ManagedThreadId; ValheimProfilerConfig config = app.Config; _sortColumn = ParseSortColumn(config.MonoBehaviourCallProfilerSortColumn.Value, TableSortColumn.Total); Vector2 minimumSize = default(Vector2); ((Vector2)(ref minimumSize))..ctor(800f, 440f); Vector2 defaultToolWindowSize = _windows.GetDefaultToolWindowSize(660f, minimumSize); _mainWindow = _windows.Register(new ProfilerWindow("ValheimProfiler.MonoBehaviourCallProfiler", "MonoBehaviour Call Profiler", new Rect(ValheimProfilerConfig.DefaultMonoBehaviourCallWindowPosition, defaultToolWindowSize), minimumSize, resizable: true, requestedVisible: false, DrawWindow, config.MonoBehaviourCallWindowPosition, config.MonoBehaviourCallWindowSize)); _instance = this; } void IProfilerTool.ShowWindow() { ShowWindow(); } void IProfilerTool.ToggleWindow() { ToggleWindow(); } void IProfilerTool.Update() { Update(); } void IProfilerTool.Shutdown() { Shutdown(); } internal void ToggleWindow() { IsWindowVisible = !IsWindowVisible; if (IsWindowVisible) { ShowWindow(); } } internal void ShowWindow() { IsWindowVisible = true; _app.ShowUi(); _windows.BringToFront(_mainWindow); if (!_listReady) { RefreshBehaviourList(); } } internal void Update() { //IL_000c: Unknown result type (might be due to invalid IL or missing references) //IL_0011: Unknown result type (might be due to invalid IL or missing references) if (_presentInActiveScene) { Scene activeScene = SceneManager.GetActiveScene(); int handle = ((Scene)(ref activeScene)).handle; if (handle != _activeSceneHandle) { RefreshActiveScenePresence(); MarkSelectionRowsDirty(); } } } internal void Shutdown() { if (_instance == this) { _instance = null; } try { StopProfilingInternal(); } catch { } IsWindowVisible = false; } private void MarkViewDirty() { _viewDirty = true; _layoutMetricsDirty = true; _nextViewRefreshTime = 0f; } private void MarkSelectionRowsDirty() { _selectionRowsDirty = true; } private void SetSortColumn(TableSortColumn column) { _sortColumn = column; _app.Config.MonoBehaviourCallProfilerSortColumn.Value = column.ToString(); MarkViewDirty(); } private static TableSortColumn ParseSortColumn(string value, TableSortColumn fallback) { TableSortColumn result; return Enum.TryParse(value, ignoreCase: true, out result) ? result : fallback; } private void RefreshCachedViewIfNeeded() { bool flag = _lastLayoutGroupByMod != _groupByMod; float realtimeSinceStartup = Time.realtimeSinceStartup; if (flag || _viewDirty || !(realtimeSinceStartup < _nextViewRefreshTime)) { List list = BuildRows(); if (_groupByMod) { _cachedGroupedRows = BuildGroupedRows(list); _cachedFlatRows.Clear(); } else { _cachedFlatRows = list.Take(1000).ToList(); _cachedGroupedRows.Clear(); } _viewDirty = false; _nextViewRefreshTime = realtimeSinceStartup + 0.2f; } } private List BuildRows() { List list = new List(); lock (_lock) { foreach (BehaviourTypeEntry type in _types) { foreach (BehaviourMethodEntry method in type.Methods) { LifetimeProfilerSnapshot snapshot = method.Stat.GetSnapshot(); if (snapshot.Calls > 0) { list.Add(new FlatRowView { Entry = method, Snapshot = snapshot }); } } } } return SortRows(list); } private List BuildGroupedRows(List rows) { List groups = (from row in rows group row by row.Entry.TypeEntry.GroupId ?? "(unknown)").Select(delegate(IGrouping group) { List list = SortRows(group.ToList()); BehaviourTypeEntry typeEntry = list[0].Entry.TypeEntry; return new GroupRowView { GroupId = group.Key, GroupName = (typeEntry.GroupName ?? typeEntry.AssemblyName ?? "Unknown"), Rows = list, Summary = BuildSummary(list) }; }).ToList(); groups = SortGroups(groups); lock (_lock) { foreach (GroupRowView item in groups) { if (!_groupExpanded.ContainsKey(item.GroupId)) { _groupExpanded[item.GroupId] = false; } } } return groups; } private List SortRows(IEnumerable rows) { TableSortColumn currentSortColumn = CurrentSortColumn; if (1 == 0) { } IOrderedEnumerable orderedEnumerable = currentSortColumn switch { TableSortColumn.Source => rows.OrderByDescending((FlatRowView row) => _groupByMod ? BuildEntryMethodName(row.Entry) : BuildUngroupedEntryName(row.Entry), StringComparer.OrdinalIgnoreCase), TableSortColumn.Calls => rows.OrderByDescending((FlatRowView row) => row.Snapshot.Calls), TableSortColumn.Total => rows.OrderByDescending((FlatRowView row) => row.Snapshot.TotalMs), TableSortColumn.Average => rows.OrderByDescending((FlatRowView row) => row.Snapshot.AverageMs), TableSortColumn.Max => rows.OrderByDescending((FlatRowView row) => row.Snapshot.MaxMs), TableSortColumn.Last => rows.OrderByDescending((FlatRowView row) => row.Snapshot.LastMs), TableSortColumn.P95 => rows.OrderByDescending((FlatRowView row) => row.Snapshot.P95Ms), TableSortColumn.P99 => rows.OrderByDescending((FlatRowView row) => row.Snapshot.P99Ms), TableSortColumn.FirstAt => rows.OrderByDescending((FlatRowView row) => row.Snapshot.FirstCallAtSeconds), TableSortColumn.LastAt => rows.OrderByDescending((FlatRowView row) => row.Snapshot.LastCallAtSeconds), TableSortColumn.MonoBehaviour => rows.OrderByDescending((FlatRowView row) => row.Entry.TypeEntry.TypeName, StringComparer.OrdinalIgnoreCase), _ => rows.OrderByDescending((FlatRowView row) => row.Snapshot.TotalMs), }; if (1 == 0) { } IOrderedEnumerable source = orderedEnumerable; return source.ThenByDescending((FlatRowView row) => row.Snapshot.MaxMs).ThenBy((FlatRowView row) => row.Entry.TypeEntry.GroupName, StringComparer.OrdinalIgnoreCase).ThenBy((FlatRowView row) => row.Entry.TypeEntry.TypeName, StringComparer.OrdinalIgnoreCase) .ThenBy((FlatRowView row) => row.Entry.DisplayName, StringComparer.OrdinalIgnoreCase) .ToList(); } private List SortGroups(IEnumerable groups) { TableSortColumn currentSortColumn = CurrentSortColumn; if (1 == 0) { } IOrderedEnumerable orderedEnumerable = currentSortColumn switch { TableSortColumn.Source => groups.OrderByDescending((GroupRowView group) => group.GroupName, StringComparer.OrdinalIgnoreCase), TableSortColumn.Calls => groups.OrderByDescending((GroupRowView group) => group.Summary.Calls), TableSortColumn.Total => groups.OrderByDescending((GroupRowView group) => group.Summary.TotalMs), TableSortColumn.Average => groups.OrderByDescending((GroupRowView group) => group.Summary.AverageMs), TableSortColumn.Max => groups.OrderByDescending((GroupRowView group) => group.Summary.MaxMs), TableSortColumn.Last => groups.OrderByDescending((GroupRowView group) => group.Summary.LastMs), TableSortColumn.P95 => groups.OrderByDescending((GroupRowView group) => group.Summary.P95Ms), TableSortColumn.P99 => groups.OrderByDescending((GroupRowView group) => group.Summary.P99Ms), TableSortColumn.FirstAt => groups.OrderByDescending((GroupRowView group) => group.Summary.FirstCallAtSeconds), TableSortColumn.LastAt => groups.OrderByDescending((GroupRowView group) => group.Summary.LastCallAtSeconds), TableSortColumn.MonoBehaviour => groups.OrderByDescending(GetGroupMaxTypeName, StringComparer.OrdinalIgnoreCase), _ => groups.OrderByDescending((GroupRowView group) => group.Summary.TotalMs), }; if (1 == 0) { } IOrderedEnumerable source = orderedEnumerable; return source.ThenByDescending((GroupRowView group) => group.Summary.MaxMs).ThenBy((GroupRowView group) => group.GroupName, StringComparer.OrdinalIgnoreCase).ToList(); } private static string GetGroupMaxTypeName(GroupRowView group) { if (group?.Rows == null || group.Rows.Count == 0) { return string.Empty; } string text = string.Empty; for (int i = 0; i < group.Rows.Count; i++) { string text2 = group.Rows[i].Entry?.TypeEntry?.TypeName ?? string.Empty; if (string.Compare(text2, text, StringComparison.OrdinalIgnoreCase) > 0) { text = text2; } } return text; } private static GroupSummary BuildSummary(List rows) { long num = rows.Sum((FlatRowView row) => row.Snapshot.Calls); double num2 = rows.Sum((FlatRowView row) => row.Snapshot.TotalMs); FlatRowView flatRowView = rows.OrderByDescending((FlatRowView row) => row.Snapshot.LastCallAtSeconds).FirstOrDefault(); bool flag = false; float num3 = 0f; foreach (FlatRowView row in rows) { float firstCallAtSeconds = row.Snapshot.FirstCallAtSeconds; if (!flag || firstCallAtSeconds < num3) { num3 = firstCallAtSeconds; flag = true; } } return new GroupSummary { Calls = num, TotalMs = num2, AverageMs = ((num > 0) ? (num2 / (double)num) : 0.0), MaxMs = rows.Max((FlatRowView row) => row.Snapshot.MaxMs), LastMs = (flatRowView?.Snapshot.LastMs ?? 0.0), P95Ms = rows.Max((FlatRowView row) => row.Snapshot.P95Ms), P99Ms = rows.Max((FlatRowView row) => row.Snapshot.P99Ms), FirstCallAtSeconds = num3, LastCallAtSeconds = (flatRowView?.Snapshot.LastCallAtSeconds ?? 0f) }; } private static string BuildEntryMethodName(BehaviourMethodEntry entry) { if (entry == null) { return "Unknown"; } string text = entry.TypeEntry?.Type?.Name; if (string.IsNullOrWhiteSpace(text)) { text = entry.TypeEntry?.TypeName ?? string.Empty; int num = Math.Max(text.LastIndexOf('.'), text.LastIndexOf('+')); if (num >= 0 && num + 1 < text.Length) { text = text.Substring(num + 1); } } string text2 = entry.DisplayName ?? "Unknown"; return string.IsNullOrWhiteSpace(text) ? text2 : (text + "." + text2); } private static string BuildUngroupedEntryName(BehaviourMethodEntry entry) { if (entry?.TypeEntry == null) { return BuildEntryMethodName(entry); } string text = entry.TypeEntry.GroupName ?? entry.TypeEntry.AssemblyName ?? "Unknown"; return text + " │ " + BuildEntryMethodName(entry); } private static string FormatMs(double value) { if (value <= 0.0) { return "0.000"; } if (value > 999999.999) { return $"{999999.999:0.000}+"; } return $"{value:0.000}"; } private static string FormatCount(long value) { if (value <= 0) { return "0"; } if (value > 999999999) { return 999999999 + "+"; } return value.ToString(); } private static string FormatTimeline(float seconds) { if (seconds < 0f) { return "-"; } if (seconds < 60f) { return $"{seconds:0.0}s"; } if (seconds < 3600f) { return $"{seconds / 60f:0.0}m"; } return $"{seconds / 3600f:0.0}h"; } private void DrawWindow(int id) { //IL_0008: 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_0014: Unknown result type (might be due to invalid IL or missing references) //IL_018b: Unknown result type (might be due to invalid IL or missing references) //IL_00fb: Unknown result type (might be due to invalid IL or missing references) //IL_0115: Expected O, but got Unknown EnsureStyles(); Color contentColor = GUI.contentColor; GUI.contentColor = _theme.TextColor; try { GUILayout.BeginVertical(Array.Empty()); DrawToolbar(); GUILayout.Space(2f); GUILayout.BeginHorizontal(Array.Empty()); MainTab mainTab = (MainTab)GUILayout.Toolbar((int)_mainTab, new string[3] { "Profiler", "Calls to profile", "Help" }, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(470f) }); if (mainTab != _mainTab) { _mainTab = mainTab; MarkViewDirty(); if (_mainTab == MainTab.CallsToProfile) { ExpandPathsToSelected(collapseUnselected: false); _selectionExpansionInitialized = true; MarkSelectionRowsDirty(); } } if (_mainTab == MainTab.Profiler) { GUILayout.Space(12f); bool flag = ProfilerGui.ToggleLayout(_theme, _groupByMod, new GUIContent("Group by Mod", "Group lifetime call statistics by their BepInEx mod or assembly.\nGroup totals are inclusive and can overlap when inherited methods are selected for multiple runtime types."), 135f, _labelStyle, 0f); if (flag != _groupByMod) { _groupByMod = flag; MarkViewDirty(); } } GUILayout.EndHorizontal(); GUILayout.Space(3f); switch (_mainTab) { case MainTab.Profiler: DrawProfilerTab(); break; case MainTab.CallsToProfile: DrawSelectionTab(); break; default: DrawHelpTab(); break; } GUILayout.EndVertical(); } finally { GUI.contentColor = contentColor; } } private void DrawToolbar() { GUILayout.BeginHorizontal(Array.Empty()); string text = (_listReady ? $"List: ready ({_methodsByKey.Count})" : "List: empty"); string text2 = (_profilingActive ? "Profiling: ON" : "Profiling: OFF"); Label(text + " | " + text2 + " | Status: " + _status, GUILayout.ExpandWidth(true)); string text3 = (_profilingActive ? "Pause profiling" : "Start profiling"); if (GUILayout.Button(text3, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(125f) })) { if (_profilingActive) { StopProfiling(); } else { StartProfiling(); } } if (GUILayout.Button("Reset stats", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(100f) })) { ResetAllStats(); _status = "Lifetime statistics reset."; } if (GUILayout.Button("Close", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(70f) })) { IsWindowVisible = false; } GUILayout.EndHorizontal(); } private void DrawProfilerTab() { //IL_01ce: Unknown result type (might be due to invalid IL or missing references) //IL_01d8: Unknown result type (might be due to invalid IL or missing references) //IL_01dd: Unknown result type (might be due to invalid IL or missing references) //IL_0206: Unknown result type (might be due to invalid IL or missing references) //IL_020b: Unknown result type (might be due to invalid IL or missing references) //IL_020d: Unknown result type (might be due to invalid IL or missing references) RefreshCachedViewIfNeeded(); PrepareTableLayout(); DrawTableHeader(); if (!(_groupByMod ? (_cachedGroupedRows.Count > 0) : (_cachedFlatRows.Count > 0))) { Label("No data yet. Start profiling and trigger selected lifecycle or declared MonoBehaviour methods."); return; } if (_groupByMod) { GUILayout.BeginHorizontal(Array.Empty()); if (GUILayout.Button("Expand all", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(88f) })) { lock (_lock) { foreach (string item in _groupExpanded.Keys.ToList()) { _groupExpanded[item] = true; } } _layoutMetricsDirty = true; } if (GUILayout.Button("Collapse all", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(88f) })) { lock (_lock) { foreach (string item2 in _groupExpanded.Keys.ToList()) { _groupExpanded[item2] = false; } } _layoutMetricsDirty = true; } GUILayout.EndHorizontal(); GUILayout.Space(2f); } float num = CalculateTableContentHeight(); _profilerScroll = GUILayout.BeginScrollView(_profilerScroll, Array.Empty()); Rect rect = GUILayoutUtility.GetRect(_drawContentWidth, num, (GUILayoutOption[])(object)new GUILayoutOption[2] { GUILayout.Width(_drawContentWidth), GUILayout.Height(num) }); DrawTableContent(rect); GUILayout.EndScrollView(); } private void DrawHelpTab() { //IL_0003: 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_0012: Unknown result type (might be due to invalid IL or missing references) _helpScroll = GUILayout.BeginScrollView(_helpScroll, Array.Empty()); HeaderLabel("Purpose"); Label("MonoBehaviour Call Profiler measures selected rare, lifecycle and manually chosen managed instance methods on the Unity main thread."); Label("It uses lifetime statistics. Samples are retained until Reset stats, selection changes, or profiling is started again."); GUILayout.Space(6f); HeaderLabel("Default lifecycle selection"); Label("Synchronous mod Awake, Start, OnEnable, OnDisable and OnDestroy methods are selected by default."); Label("Valheim and Other methods are disabled by default. Declared methods are always opt-in."); GUILayout.Space(6f); HeaderLabel("Timing limits"); Label("All times are inclusive and include nested calls and Harmony patches executed inside the measured method."); Label("Only calls that occur after instrumentation starts are visible. Awake and Start calls that already happened cannot be reconstructed."); Label("Coroutine and async entries measure only the synchronous call that creates or advances the state machine, not the complete asynchronous lifetime."); GUILayout.Space(6f); HeaderLabel("Statistics"); Label("calls, total, average, max and last are exact lifetime counters for the current profiling session."); Label("p95 and p99 are approximate lifetime percentiles maintained in a fixed logarithmic histogram without time-based expiration."); Label("first at and last at are elapsed times relative to the current profiling session start."); Label("Grouped p95 and p99 values show the highest child percentile rather than a mathematically merged percentile."); Label("Click a table column header to sort descending by that column. The active sort column is highlighted and persisted in config."); GUILayout.Space(6f); HeaderLabel("Selection"); Label("Calls to profile reuses the Frame Profiler source, search, grouping and scene-filter workflow."); Label("Show declared methods reveals manually selectable methods declared directly by each MonoBehaviour type. Selected declared methods remain visible when the filter is off."); Label("Selection changes take effect after Apply selection. Active instrumentation is restarted and lifetime statistics are reset."); Label("Selection overrides are stored in BepInEx/config/shudnal.ValheimProfiler/MonoBehaviourCallSelection.cfg."); GUILayout.Space(6f); HeaderLabel("Overhead"); Label("The profiler adds a Harmony prefix and finalizer to every selected method. Select only methods relevant to the current investigation."); Label("Regular Update, FixedUpdate, LateUpdate and OnGUI callbacks are intentionally excluded here and belong in MonoBehaviour Frame Profiler."); GUILayout.EndScrollView(); } private void EnsureStyles() { //IL_0045: Unknown result type (might be due to invalid IL or missing references) //IL_004a: Unknown result type (might be due to invalid IL or missing references) //IL_0052: Unknown result type (might be due to invalid IL or missing references) //IL_005a: Unknown result type (might be due to invalid IL or missing references) //IL_0067: Expected O, but got Unknown //IL_0078: Unknown result type (might be due to invalid IL or missing references) //IL_008e: Unknown result type (might be due to invalid IL or missing references) //IL_0093: Unknown result type (might be due to invalid IL or missing references) //IL_009b: Unknown result type (might be due to invalid IL or missing references) //IL_00a3: Unknown result type (might be due to invalid IL or missing references) //IL_00ab: Unknown result type (might be due to invalid IL or missing references) //IL_00b8: Expected O, but got Unknown //IL_00c9: Unknown result type (might be due to invalid IL or missing references) //IL_00db: Unknown result type (might be due to invalid IL or missing references) //IL_00e5: Expected O, but got Unknown //IL_00eb: Unknown result type (might be due to invalid IL or missing references) //IL_00f6: Unknown result type (might be due to invalid IL or missing references) //IL_0100: Unknown result type (might be due to invalid IL or missing references) //IL_0105: Unknown result type (might be due to invalid IL or missing references) //IL_0111: Unknown result type (might be due to invalid IL or missing references) //IL_0123: Unknown result type (might be due to invalid IL or missing references) //IL_0135: Unknown result type (might be due to invalid IL or missing references) //IL_0147: Unknown result type (might be due to invalid IL or missing references) //IL_0159: Unknown result type (might be due to invalid IL or missing references) //IL_016b: Unknown result type (might be due to invalid IL or missing references) //IL_017d: Unknown result type (might be due to invalid IL or missing references) //IL_018f: Unknown result type (might be due to invalid IL or missing references) //IL_019d: Unknown result type (might be due to invalid IL or missing references) //IL_01a2: Unknown result type (might be due to invalid IL or missing references) //IL_01af: Expected O, but got Unknown //IL_01ba: Unknown result type (might be due to invalid IL or missing references) //IL_01bf: Unknown result type (might be due to invalid IL or missing references) //IL_01c4: Unknown result type (might be due to invalid IL or missing references) //IL_01ce: Expected O, but got Unknown //IL_01cf: Unknown result type (might be due to invalid IL or missing references) //IL_01d4: Unknown result type (might be due to invalid IL or missing references) //IL_01de: Expected O, but got Unknown //IL_01e4: Expected O, but got Unknown if (!((Object)(object)_styleSkin == (Object)(object)GUI.skin) || _labelStyle == null) { _styleSkin = GUI.skin; _layoutMetricsDirty = true; _labelStyle = new GUIStyle(GUI.skin.label) { wordWrap = false, clipping = (TextClipping)1, alignment = (TextAnchor)3 }; _labelStyle.normal.textColor = _theme.TextColor; _headerLabelStyle = new GUIStyle(GUI.skin.label) { wordWrap = false, clipping = (TextClipping)1, alignment = (TextAnchor)3, fontStyle = (FontStyle)1 }; _headerLabelStyle.normal.textColor = _theme.HeaderTextColor; _activeHeaderLabelStyle = new GUIStyle(_headerLabelStyle); Color textColor = Color.Lerp(_theme.HeaderTextColor, _theme.AccentColor, 0.5f); _activeHeaderLabelStyle.normal.textColor = textColor; _activeHeaderLabelStyle.hover.textColor = textColor; _activeHeaderLabelStyle.active.textColor = textColor; _activeHeaderLabelStyle.focused.textColor = textColor; _activeHeaderLabelStyle.onNormal.textColor = textColor; _activeHeaderLabelStyle.onHover.textColor = textColor; _activeHeaderLabelStyle.onActive.textColor = textColor; _activeHeaderLabelStyle.onFocused.textColor = textColor; _groupLabelStyle = new GUIStyle(_headerLabelStyle) { alignment = (TextAnchor)3 }; _compactButtonStyle = new GUIStyle(GUI.skin.button) { margin = new RectOffset(1, 1, 1, 1), padding = new RectOffset(2, 2, 0, 0) }; } } private void Label(string text, params GUILayoutOption[] options) { GUILayout.Label(text ?? string.Empty, _labelStyle, options); } private void HeaderLabel(string text, params GUILayoutOption[] options) { GUILayout.Label(text ?? string.Empty, _headerLabelStyle, options); } } } namespace ValheimProfiler.Tools.LogMonitor { internal sealed class LogMonitorTool : IProfilerTool { private sealed class LogMonitorListener : ILogListener, IDisposable { private LogMonitorTool _owner; internal LogMonitorListener(LogMonitorTool owner) { _owner = owner; } public void LogEvent(object sender, LogEventArgs eventArgs) { _owner?.Enqueue(eventArgs); } public void Dispose() { _owner = null; } } private enum HistoryLoadMode { AutomaticBackfill, ManualOlder } private sealed class HistoryLoadResult { internal HistoryLoadMode Mode; internal LogFilePage Page; } private enum MainTab { Stream, Issues, Help } private enum IssueSortColumn { Count, FirstSeen, LastSeen, Level, Source, Message } private sealed class PendingLogEvent { internal long Sequence; internal DateTime Timestamp; internal LogLevel Level; internal string Source; internal string RawMessage; internal string Message; internal string Details; internal int ThreadId; } private sealed class LogEntry { internal long Sequence; internal DateTime Timestamp; internal LogLevel Level; internal string Source; internal string RawMessage; internal string Message; internal string Details; internal string Scene; internal int ThreadId; internal bool IsHistorical; internal long FileOffset; internal string TimeText => (Timestamp == default(DateTime)) ? "--:--:--.---" : Timestamp.ToString("HH:mm:ss.fff"); internal string LevelText => LogMonitorText.GetLevelText(Level); internal string Fingerprint => LogMonitorText.BuildFingerprint(Level, Source, Message, Details); internal string GetClipboardText(bool includeMetadata) { string text = "[" + LevelText.PadRight(7) + ": " + Source + "]"; string text2 = (string.IsNullOrEmpty(RawMessage) ? (Message ?? string.Empty) : RawMessage); if (!includeMetadata) { string text3 = text + " " + text2; return string.IsNullOrEmpty(Details) ? text3 : (text3 + Environment.NewLine + Details); } string text4 = ((Timestamp == default(DateTime)) ? "unknown time" : Timestamp.ToString("yyyy-MM-dd HH:mm:ss.fff")); string text5 = text4 + " " + text + $" [thread {ThreadId}]"; if (!string.IsNullOrEmpty(Scene)) { text5 = text5 + " [scene " + Scene + "]"; } if (IsHistorical) { text5 += $" [history offset {FileOffset}]"; } if (string.IsNullOrEmpty(Details)) { return text5 + Environment.NewLine + text2; } return text5 + Environment.NewLine + text2 + Environment.NewLine + Details; } } private sealed class IssueGroup { internal string Key; internal LogLevel Level; internal string Source; internal string Message; internal string Details; internal string Scene; internal int LastThreadId; internal int Count; internal DateTime FirstSeen; internal DateTime LastSeen; internal long FirstSequence; internal long LastSequence; internal string LevelText => LogMonitorText.GetLevelText(Level); internal string GetClipboardText() { string text = ((FirstSeen == default(DateTime)) ? "unknown" : FirstSeen.ToString("yyyy-MM-dd HH:mm:ss.fff")); string text2 = ((LastSeen == default(DateTime)) ? "unknown" : LastSeen.ToString("yyyy-MM-dd HH:mm:ss.fff")); string text3 = $"[{LevelText}:{Source}] Count: {Count} | First: {text} | Last: {text2}"; if (!string.IsNullOrEmpty(Scene)) { text3 = text3 + " | Last scene: " + Scene; } text3 += $" | Last thread: {LastThreadId}"; if (string.IsNullOrEmpty(Details)) { return text3 + Environment.NewLine + Message; } return text3 + Environment.NewLine + Message + Environment.NewLine + Details; } } private const int MaxPendingEntries = 20000; private const int MaxDrainPerFrame = 1000; private const int MaxStoredTextLength = 65536; internal const string ToolId = "LogMonitor"; internal const string DisplayTitle = "Client Log Monitor"; private const float VirtualizationOverscan = 60f; private readonly ValheimProfilerApp _app; private readonly WindowManager _windows; private readonly ThemeManager _theme; private readonly ProfilerWindow _mainWindow; private readonly LogMonitorListener _listener; private readonly ConcurrentQueue _pending = new ConcurrentQueue(); private readonly List _entries = new List(); private readonly List _filteredStream = new List(); private readonly Dictionary _issuesByKey = new Dictionary(StringComparer.Ordinal); private readonly List _issues = new List(); private readonly List _filteredIssues = new List(); private readonly object _historyResultSync = new object(); private GUIStyle _labelStyle; private GUIStyle _headerLabelStyle; private GUIStyle _activeHeaderLabelStyle; private GUIStyle _detailsStyle; private GUISkin _styleSkin; private Vector2 _streamScroll; private Vector2 _issuesScroll; private Vector2 _streamDetailsScroll; private Vector2 _issueDetailsScroll; private Vector2 _helpScroll; private MainTab _mainTab; private LogLevel _streamLevelFilter = (LogLevel)63; private IssueSortColumn _issueSortColumn = IssueSortColumn.Count; private string _streamSearch = string.Empty; private string _issuesSearch = string.Empty; private bool _includeWarningsInIssues; private bool _followStream = true; private bool _scrollStreamToEnd; private bool _streamViewDirty = true; private bool _issuesViewDirty = true; private volatile bool _captureEnabled = true; private readonly HashSet _selectedEntries = new HashSet(); private LogEntry _selectedEntry; private LogEntry _selectionAnchorEntry; private IssueGroup _selectedIssue; private int _pendingCount; private long _nextSequence; private long _nextHistoricalSequence; private long _capturedEntries; private long _droppedEntries; private bool _automaticBackfillScheduled; private bool _automaticBackfillAttempted; private float _automaticBackfillDueRealtime; private int _historyLoadInProgress; private HistoryLoadResult _pendingHistoryResult; private long _historyCursor = -1L; private long _historyFileCreationUtcTicks; private bool _historyHasMore = true; private int _loadedHistoryEntries; private string _historyStatus = string.Empty; private string LogFilePath => Path.Combine(Paths.BepInExRootPath, "LogOutput.log"); string IProfilerTool.Id => "LogMonitor"; string IProfilerTool.DisplayName => "Client Log"; bool IProfilerTool.IsWindowVisible => IsWindowVisible; bool IProfilerTool.IsActive => _captureEnabled; internal bool IsWindowVisible { get { return _mainWindow.RequestedVisible; } set { _mainWindow.RequestedVisible = value; } } private float MainWindowWidth { get { //IL_0006: 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) Rect rect = _mainWindow.Rect; return ((Rect)(ref rect)).width; } } private float MainWindowHeight { get { //IL_0006: 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) Rect rect = _mainWindow.Rect; return ((Rect)(ref rect)).height; } } private float RowHeight { get { GUIStyle labelStyle = _labelStyle; return Mathf.Max(20f, ((labelStyle != null) ? labelStyle.lineHeight : 16f) + 4f); } } private float HeaderHeight { get { GUIStyle headerLabelStyle = _headerLabelStyle; return Mathf.Max(20f, ((headerLabelStyle != null) ? headerLabelStyle.lineHeight : 16f) + 4f); } } private long DroppedEntries => Interlocked.Read(in _droppedEntries); private void Enqueue(LogEventArgs eventArgs) { //IL_00da: Unknown result type (might be due to invalid IL or missing references) //IL_00df: Unknown result type (might be due to invalid IL or missing references) if (!_captureEnabled || eventArgs == null) { return; } int num = Interlocked.Increment(ref _pendingCount); if (num > 20000) { Interlocked.Decrement(ref _pendingCount); Interlocked.Increment(ref _droppedEntries); return; } try { ParseData(eventArgs.Data, out var message, out var details); ILogSource source = eventArgs.Source; string source2 = LimitText(((source != null) ? source.SourceName : null) ?? "Unknown", 256); DateTime parsedTimestamp; string message2 = LogMonitorText.NormalizeMessage(source2, message, out parsedTimestamp); DateTime timestamp = ((parsedTimestamp != default(DateTime)) ? parsedTimestamp : DateTime.Now); _pending.Enqueue(new PendingLogEvent { Sequence = Interlocked.Increment(ref _nextSequence), Timestamp = timestamp, Level = eventArgs.Level, Source = source2, RawMessage = message, Message = message2, Details = details, ThreadId = Thread.CurrentThread.ManagedThreadId }); } catch { Interlocked.Decrement(ref _pendingCount); Interlocked.Increment(ref _droppedEntries); } } private void DrainPending() { //IL_0054: 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) string activeSceneName = GetActiveSceneName(); int i; for (i = 0; i < 1000; i++) { if (!_pending.TryDequeue(out var result)) { break; } Interlocked.Decrement(ref _pendingCount); LogEntry logEntry = new LogEntry { Sequence = result.Sequence, Timestamp = result.Timestamp, Level = result.Level, Source = result.Source, RawMessage = result.RawMessage, Message = result.Message, Details = result.Details, Scene = activeSceneName, ThreadId = result.ThreadId, IsHistorical = false, FileOffset = -1L }; AddLiveEntry(logEntry); if (!_automaticBackfillScheduled && LogMonitorText.IsChainloaderStartupComplete(logEntry.Source, logEntry.Message)) { _automaticBackfillScheduled = true; _automaticBackfillDueRealtime = Time.realtimeSinceStartup + 0.5f; } } if (i > 0) { MarkViewsDirty(); if (_followStream) { _scrollStreamToEnd = true; } } } private void AddLiveEntry(LogEntry entry) { //IL_0033: Unknown result type (might be due to invalid IL or missing references) _entries.Add(entry); _capturedEntries++; if (TrimLiveEntriesToLimit()) { RebuildIssues(); } else if (IsIssueLevel(entry.Level)) { AddIssue(entry); } } private bool TrimLiveEntriesToLimit() { int num = Math.Max(500, _app.Config.LogMonitorMaxEntries.Value); int num2 = 0; for (int i = 0; i < _entries.Count; i++) { if (!_entries[i].IsHistorical) { num2++; } } if (num2 <= num) { return false; } int val = Math.Max(num2 - num, Math.Max(1, num / 10)); int j; for (j = 0; j < _entries.Count && _entries[j].IsHistorical; j++) { } int num3 = Math.Min(val, _entries.Count - j); if (num3 <= 0) { return false; } for (int k = j; k < j + num3; k++) { RemoveEntryFromStreamSelection(_entries[k]); } _entries.RemoveRange(j, num3); return true; } private void AddIssue(LogEntry entry) { //IL_014e: Unknown result type (might be due to invalid IL or missing references) //IL_0153: Unknown result type (might be due to invalid IL or missing references) string key = BuildIssueKey(entry); if (_issuesByKey.TryGetValue(key, out var value)) { value.Count++; if (value.FirstSeen == default(DateTime) || (entry.Timestamp != default(DateTime) && entry.Timestamp < value.FirstSeen)) { value.FirstSeen = entry.Timestamp; value.FirstSequence = entry.Sequence; } if (value.LastSeen == default(DateTime) || entry.Timestamp >= value.LastSeen) { value.LastSeen = entry.Timestamp; value.LastSequence = entry.Sequence; value.Scene = entry.Scene; value.LastThreadId = entry.ThreadId; value.Details = entry.Details; } return; } int num = Math.Max(100, _app.Config.LogMonitorMaxIssueGroups.Value); if (_issues.Count >= num) { RemoveOldestIssueGroup(); } value = new IssueGroup { Key = key, Level = entry.Level, Source = entry.Source, Message = entry.Message, Details = entry.Details, Scene = entry.Scene, LastThreadId = entry.ThreadId, Count = 1, FirstSeen = entry.Timestamp, LastSeen = entry.Timestamp, FirstSequence = entry.Sequence, LastSequence = entry.Sequence }; _issuesByKey[key] = value; _issues.Add(value); } private void RebuildIssues() { //IL_0033: Unknown result type (might be due to invalid IL or missing references) _issues.Clear(); _issuesByKey.Clear(); _selectedIssue = null; for (int i = 0; i < _entries.Count; i++) { LogEntry logEntry = _entries[i]; if (IsIssueLevel(logEntry.Level)) { AddIssue(logEntry); } } _issuesViewDirty = true; } private void RemoveOldestIssueGroup() { if (_issues.Count == 0) { return; } int index = 0; DateTime lastSeen = _issues[0].LastSeen; for (int i = 1; i < _issues.Count; i++) { if (!(_issues[i].LastSeen >= lastSeen)) { lastSeen = _issues[i].LastSeen; index = i; } } IssueGroup issueGroup = _issues[index]; if (_selectedIssue == issueGroup) { _selectedIssue = null; } _issues.RemoveAt(index); _issuesByKey.Remove(issueGroup.Key); } private void ClearCapturedData() { //IL_0070: Unknown result type (might be due to invalid IL or missing references) //IL_007c: Unknown result type (might be due to invalid IL or missing references) //IL_0088: Unknown result type (might be due to invalid IL or missing references) //IL_0094: Unknown result type (might be due to invalid IL or missing references) PendingLogEvent result; while (_pending.TryDequeue(out result)) { Interlocked.Decrement(ref _pendingCount); } _entries.Clear(); _issues.Clear(); _issuesByKey.Clear(); _filteredStream.Clear(); _filteredIssues.Clear(); ClearStreamSelection(); _selectedIssue = null; _streamScroll = default(Vector2); _issuesScroll = default(Vector2); _streamDetailsScroll = default(Vector2); _issueDetailsScroll = default(Vector2); _capturedEntries = 0L; _historyCursor = -1L; _historyHasMore = false; _loadedHistoryEntries = 0; _historyStatus = string.Empty; Interlocked.Exchange(ref _droppedEntries, 0L); MarkViewsDirty(); } private void UnloadHistory() { bool flag = false; for (int num = _entries.Count - 1; num >= 0; num--) { if (_entries[num].IsHistorical) { RemoveEntryFromStreamSelection(_entries[num]); _entries.RemoveAt(num); flag = true; } } _loadedHistoryEntries = 0; _historyCursor = -1L; _historyHasMore = true; _historyStatus = (flag ? "Loaded history removed." : "No loaded history."); if (flag) { RebuildIssues(); MarkViewsDirty(); } } private void MarkViewsDirty() { _streamViewDirty = true; _issuesViewDirty = true; } private static void ParseData(object data, out string message, out string details) { if (data is Exception ex) { message = LimitText(ex.GetType().FullName + ": " + ex.Message, 4096); details = LimitText(ex.ToString(), 65536); return; } string text = LimitText(data?.ToString() ?? "NULL", 65536); int num = text.IndexOfAny(new char[2] { '\r', '\n' }); if (num < 0) { message = text; details = string.Empty; return; } message = text.Substring(0, num).TrimEnd(Array.Empty()); int i; for (i = num; i < text.Length && (text[i] == '\r' || text[i] == '\n'); i++) { } details = ((i < text.Length) ? text.Substring(i) : string.Empty); if (string.IsNullOrEmpty(message)) { message = ((details.Length > 0) ? FirstLine(details) : "(empty message)"); } } private static string FirstLine(string value) { if (string.IsNullOrEmpty(value)) { return string.Empty; } int num = value.IndexOfAny(new char[2] { '\r', '\n' }); return (num >= 0) ? value.Substring(0, num) : value; } private static string LimitText(string value, int maxLength) { if (value == null) { value = string.Empty; } if (value.Length <= maxLength) { return value; } return value.Substring(0, Math.Max(0, maxLength - 24)) + "\n... [text truncated]"; } private static string GetActiveSceneName() { //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Unknown result type (might be due to invalid IL or missing references) try { Scene activeScene = SceneManager.GetActiveScene(); return ((Scene)(ref activeScene)).name ?? string.Empty; } catch { return string.Empty; } } private static bool IsIssueLevel(LogLevel level) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_0004: Invalid comparison between Unknown and I4 return (level & 7) > 0; } private static bool IsWarningLevel(LogLevel level) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_0009: Invalid comparison between Unknown and I4 return (level & 4) != 0 && (level & 3) == 0; } private static string BuildIssueKey(LogEntry entry) { return entry.Fingerprint; } private static string GetLevelText(LogLevel level) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) return LogMonitorText.GetLevelText(level); } private void DrawHeaderCell(ref float x, float y, float width, float height, string text, string tooltip) { //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_0022: Unknown result type (might be due to invalid IL or missing references) //IL_0032: Expected O, but got Unknown GUI.Label(new Rect(x, y, width, height), new GUIContent(text ?? string.Empty, tooltip ?? string.Empty), _headerLabelStyle); x += width; } private static void DrawCell(ref float x, float y, float width, float height, string text, GUIStyle style) { //IL_0006: Unknown result type (might be due to invalid IL or missing references) GUI.Label(new Rect(x, y, width, height), text ?? string.Empty, style); x += width; } private Color GetLevelColor(LogLevel level) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0003: Unknown result type (might be due to invalid IL or missing references) //IL_0005: Invalid comparison between Unknown and I4 //IL_003f: Unknown result type (might be due to invalid IL or missing references) //IL_0041: Unknown result type (might be due to invalid IL or missing references) //IL_0043: Invalid comparison between Unknown and I4 //IL_0011: 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_0034: Unknown result type (might be due to invalid IL or missing references) //IL_0039: Unknown result type (might be due to invalid IL or missing references) //IL_007d: Unknown result type (might be due to invalid IL or missing references) //IL_007f: Unknown result type (might be due to invalid IL or missing references) //IL_0081: Invalid comparison between Unknown and I4 //IL_004f: 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_0072: Unknown result type (might be due to invalid IL or missing references) //IL_0077: Unknown result type (might be due to invalid IL or missing references) //IL_0104: Unknown result type (might be due to invalid IL or missing references) //IL_00b8: Unknown result type (might be due to invalid IL or missing references) //IL_00ba: Unknown result type (might be due to invalid IL or missing references) //IL_00bc: Invalid comparison between Unknown and I4 //IL_008d: Unknown result type (might be due to invalid IL or missing references) //IL_00a6: Unknown result type (might be due to invalid IL or missing references) //IL_00b0: Unknown result type (might be due to invalid IL or missing references) //IL_00b5: Unknown result type (might be due to invalid IL or missing references) //IL_00cc: Unknown result type (might be due to invalid IL or missing references) //IL_00cf: Unknown result type (might be due to invalid IL or missing references) //IL_00d1: Invalid comparison between Unknown and I4 //IL_00c4: Unknown result type (might be due to invalid IL or missing references) //IL_00c9: Unknown result type (might be due to invalid IL or missing references) //IL_00fc: Unknown result type (might be due to invalid IL or missing references) //IL_0101: Unknown result type (might be due to invalid IL or missing references) //IL_00df: Unknown result type (might be due to invalid IL or missing references) //IL_00e4: Unknown result type (might be due to invalid IL or missing references) //IL_00ee: Unknown result type (might be due to invalid IL or missing references) //IL_00f3: Unknown result type (might be due to invalid IL or missing references) if ((level & 1) > 0) { return Color.Lerp(_theme.TextColor, new Color(1f, 0.18f, 0.18f, 1f), 0.78f); } if ((level & 2) > 0) { return Color.Lerp(_theme.TextColor, new Color(1f, 0.3f, 0.3f, 1f), 0.68f); } if ((level & 4) > 0) { return Color.Lerp(_theme.TextColor, new Color(1f, 0.76f, 0.18f, 1f), 0.62f); } if ((level & 8) > 0) { return Color.white; } if ((level & 0x20) > 0) { return Color.Lerp(_theme.TextColor, Color.gray, 0.48f); } return _theme.TextColor; } private static bool Contains(string value, string search) { return !string.IsNullOrEmpty(value) && !string.IsNullOrEmpty(search) && value.IndexOf(search, StringComparison.OrdinalIgnoreCase) >= 0; } private void UpdateHistoryLoading() { if (_automaticBackfillScheduled && !_automaticBackfillAttempted && Time.realtimeSinceStartup >= _automaticBackfillDueRealtime && QueueHistoryLoad(HistoryLoadMode.AutomaticBackfill, 0L)) { _automaticBackfillAttempted = true; } HistoryLoadResult historyLoadResult = null; lock (_historyResultSync) { if (_pendingHistoryResult != null) { historyLoadResult = _pendingHistoryResult; _pendingHistoryResult = null; } } if (historyLoadResult != null) { Interlocked.Exchange(ref _historyLoadInProgress, 0); ApplyHistoryResult(historyLoadResult); } } private void RequestOlderHistory() { if (Volatile.Read(in _historyLoadInProgress) == 0) { long beforeOffset = ((_historyCursor > 0) ? _historyCursor : 0); QueueHistoryLoad(HistoryLoadMode.ManualOlder, beforeOffset); } } private bool QueueHistoryLoad(HistoryLoadMode mode, long beforeOffset) { if (Interlocked.CompareExchange(ref _historyLoadInProgress, 1, 0) != 0) { return false; } _historyStatus = ((mode == HistoryLoadMode.AutomaticBackfill) ? "Loading startup history..." : "Loading older entries..."); int maxEntries = ((mode == HistoryLoadMode.AutomaticBackfill) ? Math.Max(2000, _app.Config.LogMonitorHistoryPageEntries.Value * 4) : _app.Config.LogMonitorHistoryPageEntries.Value); int maxBytes = ((mode == HistoryLoadMode.AutomaticBackfill) ? 16777216 : 4194304); string path = LogFilePath; ThreadPool.QueueUserWorkItem(delegate { HistoryLoadResult pendingHistoryResult; try { pendingHistoryResult = new HistoryLoadResult { Mode = mode, Page = LogFileHistoryReader.ReadOlder(path, beforeOffset, maxEntries, maxBytes) }; } catch (Exception ex) { pendingHistoryResult = new HistoryLoadResult { Mode = mode, Page = new LogFilePage { Error = ex.GetType().Name + ": " + ex.Message } }; } lock (_historyResultSync) { _pendingHistoryResult = pendingHistoryResult; } }); return true; } private void ApplyHistoryResult(HistoryLoadResult result) { //IL_011e: Unknown result type (might be due to invalid IL or missing references) //IL_0123: Unknown result type (might be due to invalid IL or missing references) LogFilePage page = result.Page; if (!string.IsNullOrEmpty(page.Error)) { _historyStatus = page.Error; return; } if (_historyFileCreationUtcTicks != 0L && page.FileCreationUtcTicks != 0L && _historyFileCreationUtcTicks != page.FileCreationUtcTicks) { UnloadHistory(); _historyStatus = "LogOutput.log changed; loaded history was reset."; } _historyFileCreationUtcTicks = page.FileCreationUtcTicks; _historyHasMore = page.HasMore; _historyCursor = page.NextCursor; if (page.Entries.Count == 0) { _historyStatus = (page.HasMore ? "No complete entries found in this page." : "No older entries."); return; } List list = new List(page.Entries.Count); for (int i = 0; i < page.Entries.Count; i++) { ParsedLogFileEntry parsedLogFileEntry = page.Entries[i]; list.Add(new LogEntry { Sequence = --_nextHistoricalSequence, Timestamp = parsedLogFileEntry.Timestamp, Level = parsedLogFileEntry.Level, Source = parsedLogFileEntry.Source, RawMessage = parsedLogFileEntry.RawMessage, Message = parsedLogFileEntry.Message, Details = parsedLogFileEntry.Details, Scene = parsedLogFileEntry.Scene, ThreadId = parsedLogFileEntry.ThreadId, IsHistorical = true, FileOffset = parsedLogFileEntry.FileOffset }); } int num = list.Count; bool flag = result.Mode == HistoryLoadMode.AutomaticBackfill || _loadedHistoryEntries == 0; bool flag2 = false; if (flag && _entries.Count > 0) { int num2 = LogHistoryMerge.FindOverlap(list, _entries, (LogEntry item) => item.Fingerprint, (LogEntry item) => item.Fingerprint); if (num2 >= 0) { num = num2; flag2 = true; } } if (num > 0) { if (num < list.Count) { list.RemoveRange(num, list.Count - num); } _entries.InsertRange(0, list); _loadedHistoryEntries += list.Count; RebuildIssues(); MarkViewsDirty(); } if (_followStream && result.Mode != HistoryLoadMode.ManualOlder) { _scrollStreamToEnd = true; } string text = ((flag && !flag2 && _entries.Count > num) ? " Startup overlap was not found; repeated file records may remain visible." : string.Empty); _historyStatus = $"Loaded {num} older entries. Total history: {_loadedHistoryEntries}." + text; } private void DrawIssuesTab() { DrawIssueFilters(); EnsureIssueView(); float num = ((_selectedIssue != null) ? 146f : 0f); float height = Mathf.Max(130f, MainWindowHeight - 166f - num); DrawIssueHeader(); DrawIssueRows(height); if (_selectedIssue != null) { GUILayout.Space(3f); DrawIssueDetails(num); } } private void DrawIssueFilters() { //IL_0022: Unknown result type (might be due to invalid IL or missing references) //IL_003c: Expected O, but got Unknown GUILayout.BeginHorizontal(Array.Empty()); bool flag = ProfilerGui.ToggleLayout(_theme, _includeWarningsInIssues, new GUIContent("Include warnings", "Include Warning groups alongside Error and Fatal groups. Warning entries are always retained in the raw Stream while their level filter is enabled."), 135f, _labelStyle); if (flag != _includeWarningsInIssues) { _includeWarningsInIssues = flag; _issuesViewDirty = true; } GUILayout.Space(8f); Label("Search:", GUILayout.Width(50f)); string text = GUILayout.TextField(_issuesSearch ?? string.Empty, (GUILayoutOption[])(object)new GUILayoutOption[3] { GUILayout.MinWidth(160f), GUILayout.MaxWidth(440f), GUILayout.ExpandWidth(true) }); if (!string.Equals(text, _issuesSearch, StringComparison.Ordinal)) { _issuesSearch = text; _issuesViewDirty = true; } GUI.enabled = !string.IsNullOrEmpty(_issuesSearch); if (GUILayout.Button("Clear", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(54f) })) { _issuesSearch = string.Empty; _issuesViewDirty = true; } GUI.enabled = true; GUILayout.EndHorizontal(); GUILayout.Space(2f); } private void EnsureIssueView() { if (_issuesViewDirty) { IEnumerable source = _issues.Where((IssueGroup group) => (_includeWarningsInIssues || !IsWarningLevel(group.Level)) && MatchesSearch(group, _issuesSearch)); _filteredIssues.Clear(); List filteredIssues = _filteredIssues; IssueSortColumn issueSortColumn = _issueSortColumn; if (1 == 0) { } IOrderedEnumerable collection = issueSortColumn switch { IssueSortColumn.FirstSeen => source.OrderByDescending((IssueGroup group) => group.FirstSeen), IssueSortColumn.LastSeen => source.OrderByDescending((IssueGroup group) => group.LastSeen), IssueSortColumn.Level => from @group in source orderby SeverityRank(@group.Level) descending, @group.LastSeen descending select @group, IssueSortColumn.Source => source.OrderByDescending((IssueGroup group) => group.Source, StringComparer.OrdinalIgnoreCase).ThenByDescending((IssueGroup group) => group.Count), IssueSortColumn.Message => source.OrderByDescending((IssueGroup group) => group.Message, StringComparer.OrdinalIgnoreCase).ThenByDescending((IssueGroup group) => group.Count), _ => from @group in source orderby @group.Count descending, @group.LastSeen descending select @group, }; if (1 == 0) { } filteredIssues.AddRange(collection); _issuesViewDirty = false; } } private void DrawIssueHeader() { //IL_0029: Unknown result type (might be due to invalid IL or missing references) //IL_002e: Unknown result type (might be due to invalid IL or missing references) Rect rect = GUILayoutUtility.GetRect(1f, HeaderHeight, (GUILayoutOption[])(object)new GUILayoutOption[2] { GUILayout.ExpandWidth(true), GUILayout.Height(HeaderHeight) }); GetIssueColumnWidths(((Rect)(ref rect)).width, out var levelWidth, out var countWidth, out var firstWidth, out var lastWidth, out var sourceWidth, out var messageWidth); float x = ((Rect)(ref rect)).x; DrawIssueHeaderCell(ref x, ((Rect)(ref rect)).y, levelWidth, ((Rect)(ref rect)).height, "Level", "Highest severity represented by this exact issue group.", IssueSortColumn.Level); DrawIssueHeaderCell(ref x, ((Rect)(ref rect)).y, countWidth, ((Rect)(ref rect)).height, "Count", "Number of identical captured events since Clear or group creation.", IssueSortColumn.Count); DrawIssueHeaderCell(ref x, ((Rect)(ref rect)).y, firstWidth, ((Rect)(ref rect)).height, "First", "Time this exact group was first observed.", IssueSortColumn.FirstSeen); DrawIssueHeaderCell(ref x, ((Rect)(ref rect)).y, lastWidth, ((Rect)(ref rect)).height, "Last", "Time this exact group was most recently observed.", IssueSortColumn.LastSeen); DrawIssueHeaderCell(ref x, ((Rect)(ref rect)).y, sourceWidth, ((Rect)(ref rect)).height, "Source", "BepInEx logger source.", IssueSortColumn.Source); DrawIssueHeaderCell(ref x, ((Rect)(ref rect)).y, messageWidth, ((Rect)(ref rect)).height, "Message", "First line of the grouped event.", IssueSortColumn.Message); } private void DrawIssueRows(float height) { //IL_0003: Unknown result type (might be due to invalid IL or missing references) //IL_0019: Unknown result type (might be due to invalid IL or missing references) //IL_001e: 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_0064: Unknown result type (might be due to invalid IL or missing references) //IL_0155: Unknown result type (might be due to invalid IL or missing references) //IL_013e: Unknown result type (might be due to invalid IL or missing references) //IL_018a: Unknown result type (might be due to invalid IL or missing references) //IL_018f: Unknown result type (might be due to invalid IL or missing references) //IL_0194: Unknown result type (might be due to invalid IL or missing references) //IL_0199: Unknown result type (might be due to invalid IL or missing references) //IL_01c3: Unknown result type (might be due to invalid IL or missing references) _issuesScroll = GUILayout.BeginScrollView(_issuesScroll, false, false, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Height(height) }); float num = Mathf.Max(1f, (float)_filteredIssues.Count * RowHeight); Rect rect = GUILayoutUtility.GetRect(1f, num, (GUILayoutOption[])(object)new GUILayoutOption[2] { GUILayout.ExpandWidth(true), GUILayout.Height(num) }); float num2 = _issuesScroll.y - 60f; float num3 = _issuesScroll.y + height + 60f; int num4 = Mathf.Clamp(Mathf.FloorToInt(num2 / RowHeight), 0, _filteredIssues.Count); int num5 = Mathf.Clamp(Mathf.CeilToInt(num3 / RowHeight), 0, _filteredIssues.Count); GetIssueColumnWidths(((Rect)(ref rect)).width, out var levelWidth, out var countWidth, out var firstWidth, out var lastWidth, out var sourceWidth, out var messageWidth); Rect val = default(Rect); for (int i = num4; i < num5; i++) { IssueGroup issueGroup = _filteredIssues[i]; float num6 = ((Rect)(ref rect)).y + (float)i * RowHeight; ((Rect)(ref val))..ctor(((Rect)(ref rect)).x, num6, ((Rect)(ref rect)).width, RowHeight); if (_selectedIssue == issueGroup) { GUI.Box(val, GUIContent.none, GUI.skin.box); } if (GUI.Button(val, GUIContent.none, GUIStyle.none)) { _selectedIssue = ((_selectedIssue == issueGroup) ? null : issueGroup); } float x = ((Rect)(ref val)).x; Color contentColor = GUI.contentColor; GUI.contentColor = GetLevelColor(issueGroup.Level); DrawCell(ref x, num6, levelWidth, RowHeight, issueGroup.LevelText, _labelStyle); GUI.contentColor = contentColor; DrawCell(ref x, num6, countWidth, RowHeight, issueGroup.Count.ToString(), _labelStyle); DrawCell(ref x, num6, firstWidth, RowHeight, (issueGroup.FirstSeen == default(DateTime)) ? "--:--:--" : issueGroup.FirstSeen.ToString("HH:mm:ss"), _labelStyle); DrawCell(ref x, num6, lastWidth, RowHeight, (issueGroup.LastSeen == default(DateTime)) ? "--:--:--" : issueGroup.LastSeen.ToString("HH:mm:ss"), _labelStyle); DrawCell(ref x, num6, sourceWidth, RowHeight, issueGroup.Source, _labelStyle); DrawCell(ref x, num6, messageWidth, RowHeight, issueGroup.Message, _labelStyle); } GUILayout.EndScrollView(); } private void DrawIssueDetails(float height) { //IL_0196: Unknown result type (might be due to invalid IL or missing references) //IL_01aa: Unknown result type (might be due to invalid IL or missing references) //IL_01af: Unknown result type (might be due to invalid IL or missing references) IssueGroup selectedIssue = _selectedIssue; if (selectedIssue != null) { GUILayout.BeginVertical(GUI.skin.box, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Height(height) }); GUILayout.BeginHorizontal(Array.Empty()); HeaderLabel($"[{selectedIssue.LevelText}:{selectedIssue.Source}] Count: {selectedIssue.Count}", GUILayout.ExpandWidth(true)); if (GUILayout.Button("Copy", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(58f) })) { GUIUtility.systemCopyBuffer = selectedIssue.GetClipboardText(); } if (GUILayout.Button("Close", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(58f) })) { _selectedIssue = null; } GUILayout.EndHorizontal(); Label("First: " + ((selectedIssue.FirstSeen == default(DateTime)) ? "unknown" : selectedIssue.FirstSeen.ToString("HH:mm:ss.fff")) + " | Last: " + ((selectedIssue.LastSeen == default(DateTime)) ? "unknown" : selectedIssue.LastSeen.ToString("HH:mm:ss.fff")) + " | " + string.Format("Last thread: {0} | Last scene: {1}", selectedIssue.LastThreadId, string.IsNullOrEmpty(selectedIssue.Scene) ? "(none)" : selectedIssue.Scene)); _issueDetailsScroll = GUILayout.BeginScrollView(_issueDetailsScroll, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.ExpandHeight(true) }); GUILayout.Label(BuildFullText(selectedIssue.Message, selectedIssue.Details), _detailsStyle, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.ExpandWidth(true) }); GUILayout.EndScrollView(); GUILayout.EndVertical(); } } private void DrawIssueHeaderCell(ref float x, float y, float width, float height, string text, string tooltip, IssueSortColumn column) { //IL_0027: Unknown result type (might be due to invalid IL or missing references) //IL_003e: Unknown result type (might be due to invalid IL or missing references) //IL_0049: Expected O, but got Unknown Rect val = default(Rect); ((Rect)(ref val))..ctor(x, y, width, height); GUIStyle val2 = ((_issueSortColumn == column) ? _activeHeaderLabelStyle : _headerLabelStyle); if (GUI.Button(val, new GUIContent(text ?? string.Empty, tooltip ?? string.Empty), val2)) { _issueSortColumn = column; _app.Config.LogMonitorIssueSortColumn.Value = column.ToString(); _issuesViewDirty = true; } x += width; } private static bool MatchesSearch(IssueGroup group, string search) { if (string.IsNullOrWhiteSpace(search)) { return true; } return Contains(group.Source, search) || Contains(group.Message, search) || Contains(group.Details, search) || Contains(group.Scene, search) || Contains(group.LevelText, search); } private static int SeverityRank(LogLevel level) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0003: Unknown result type (might be due to invalid IL or missing references) //IL_0005: Invalid comparison between Unknown and I4 //IL_000f: Unknown result type (might be due to invalid IL or missing references) //IL_0011: Unknown result type (might be due to invalid IL or missing references) //IL_0013: Invalid comparison between Unknown and I4 //IL_001d: Unknown result type (might be due to invalid IL or missing references) //IL_001f: Unknown result type (might be due to invalid IL or missing references) //IL_0021: Invalid comparison between Unknown and I4 if ((level & 1) > 0) { return 3; } if ((level & 2) > 0) { return 2; } if ((level & 4) > 0) { return 1; } return 0; } private static void GetIssueColumnWidths(float availableWidth, out float levelWidth, out float countWidth, out float firstWidth, out float lastWidth, out float sourceWidth, out float messageWidth) { levelWidth = 72f; countWidth = 66f; firstWidth = 76f; lastWidth = 76f; sourceWidth = Mathf.Clamp(availableWidth * 0.22f, 125f, 240f); messageWidth = Mathf.Max(160f, availableWidth - levelWidth - countWidth - firstWidth - lastWidth - sourceWidth); } private void DrawStreamTab() { DrawStreamFilters(); DrawHistoryControls(); EnsureStreamView(); float num = ((_selectedEntries.Count == 1 && _selectedEntry != null) ? 132f : 0f); float height = Mathf.Max(130f, MainWindowHeight - 212f - num); DrawStreamHeader(); DrawStreamRows(height); if (_selectedEntries.Count == 1 && _selectedEntry != null) { GUILayout.Space(3f); DrawStreamDetails(num); } } private void DrawStreamFilters() { //IL_009b: Unknown result type (might be due to invalid IL or missing references) //IL_00b5: Expected O, but got Unknown //IL_0206: Unknown result type (might be due to invalid IL or missing references) //IL_0223: Expected O, but got Unknown //IL_0273: Unknown result type (might be due to invalid IL or missing references) //IL_0290: Expected O, but got Unknown //IL_02c1: Unknown result type (might be due to invalid IL or missing references) //IL_02e0: Expected O, but got Unknown //IL_0357: Unknown result type (might be due to invalid IL or missing references) //IL_0371: Expected O, but got Unknown GUILayout.BeginHorizontal(Array.Empty()); DrawLevelFilterButton("D", "Debug", (LogLevel)32); DrawLevelFilterButton("I", "Info", (LogLevel)16); DrawLevelFilterButton("M", "Message", (LogLevel)8); DrawLevelFilterButton("W", "Warning", (LogLevel)4); DrawLevelFilterButton("E", "Error", (LogLevel)2); DrawLevelFilterButton("F", "Fatal", (LogLevel)1); GUILayout.Space(6f); bool flag = ProfilerGui.ToggleLayout(_theme, _followStream, new GUIContent("Follow", "Keep the stream scrolled to the newest matching entry."), 82f, _labelStyle); if (flag != _followStream) { _followStream = flag; if (_followStream) { _scrollStreamToEnd = true; } } GUILayout.Space(6f); Label("Search:", GUILayout.Width(50f)); string text = GUILayout.TextField(_streamSearch ?? string.Empty, (GUILayoutOption[])(object)new GUILayoutOption[3] { GUILayout.MinWidth(130f), GUILayout.MaxWidth(360f), GUILayout.ExpandWidth(true) }); if (!string.Equals(text, _streamSearch, StringComparison.Ordinal)) { _streamSearch = text; _streamViewDirty = true; if (_followStream) { _scrollStreamToEnd = true; } } GUI.enabled = !string.IsNullOrEmpty(_streamSearch); if (GUILayout.Button("Clear", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(54f) })) { _streamSearch = string.Empty; _streamViewDirty = true; } GUI.enabled = true; GUI.enabled = _filteredStream.Count > 0 || _entries.Count > 0; if (GUILayout.Button(new GUIContent("Copy filtered", "Copy all currently filtered Stream rows in chronological order."), (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(94f) })) { EnsureStreamView(); CopyFilteredStream(); } GUI.enabled = true; GUI.enabled = _selectedEntries.Count > 0; if (GUILayout.Button(new GUIContent("Copy selected", $"Copy {_selectedEntries.Count} selected Stream row(s) in chronological order. Use Ctrl-click to toggle rows and Shift-click to select a range."), (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(100f) })) { CopySelectedStream(); } GUI.enabled = true; GUILayout.EndHorizontal(); GUILayout.BeginHorizontal(Array.Empty()); GUILayout.Label(new GUIContent("Selection: Shift-click selects a range | Ctrl-click toggles individual rows", "Click a row for a single selection. Hold Shift to select a continuous range, Ctrl to add or remove individual rows, or Ctrl+Shift to add a range."), _labelStyle, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.ExpandWidth(true) }); bool enabled = GUI.enabled; GUI.enabled = enabled && _selectedEntries.Count > 0; if (GUILayout.Button("Clear selection", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(104f) })) { ClearStreamSelection(); } GUI.enabled = enabled; bool flag2 = ProfilerGui.ToggleLayout(_theme, _app.Config.LogMonitorCopyMetadata.Value, new GUIContent("Copy metadata", "Include timestamp, thread, scene and history/sequence metadata and use the expanded two-line clipboard format. Disabled preserves the compact BepInEx LogOutput-style header and raw message."), 125f, _labelStyle); if (flag2 != _app.Config.LogMonitorCopyMetadata.Value) { _app.Config.LogMonitorCopyMetadata.Value = flag2; } GUILayout.EndHorizontal(); GUILayout.Space(2f); } private void DrawHistoryControls() { GUILayout.BeginHorizontal(Array.Empty()); bool flag = Volatile.Read(in _historyLoadInProgress) != 0; GUI.enabled = !flag && (_historyHasMore || _historyCursor < 0); if (GUILayout.Button(flag ? "Loading..." : "Load older", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(88f) })) { RequestOlderHistory(); } GUI.enabled = true; GUI.enabled = _loadedHistoryEntries > 0 && !flag; if (GUILayout.Button("Unload history", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(104f) })) { UnloadHistory(); } GUI.enabled = true; Label(string.Format("History: {0} | More: {1}", _loadedHistoryEntries, _historyHasMore ? "yes" : "no"), GUILayout.Width(150f)); Label(_historyStatus, GUILayout.ExpandWidth(true)); GUILayout.EndHorizontal(); GUILayout.Space(2f); } private void DrawLevelFilterButton(string text, string levelName, LogLevel level) { //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_0008: Unknown result type (might be due to invalid IL or missing references) //IL_000a: Invalid comparison between Unknown and I4 //IL_0039: Unknown result type (might be due to invalid IL or missing references) //IL_0057: Expected O, but got Unknown //IL_0078: Unknown result type (might be due to invalid IL or missing references) //IL_007d: Unknown result type (might be due to invalid IL or missing references) //IL_007e: Unknown result type (might be due to invalid IL or missing references) //IL_007f: Unknown result type (might be due to invalid IL or missing references) //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_006d: Unknown result type (might be due to invalid IL or missing references) //IL_006e: Unknown result type (might be due to invalid IL or missing references) //IL_006f: Unknown result type (might be due to invalid IL or missing references) bool flag = (_streamLevelFilter & level) > 0; GUIStyle val = (flag ? _theme.AccentButtonStyle : GUI.skin.button); if (GUILayout.Button(new GUIContent(text, "Show or hide " + levelName + " log entries."), val, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(30f) })) { if (flag) { _streamLevelFilter &= ~level; } else { _streamLevelFilter |= level; } _streamViewDirty = true; } } private void EnsureStreamView() { //IL_002f: Unknown result type (might be due to invalid IL or missing references) //IL_0035: 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_003c: Invalid comparison between Unknown and I4 if (!_streamViewDirty) { return; } _filteredStream.Clear(); for (int i = 0; i < _entries.Count; i++) { LogEntry logEntry = _entries[i]; if ((_streamLevelFilter & logEntry.Level) != 0 && MatchesSearch(logEntry, _streamSearch)) { _filteredStream.Add(logEntry); } } _streamViewDirty = false; PruneStreamSelectionToFilteredView(); } private void CopyFilteredStream() { StringBuilder stringBuilder = new StringBuilder(Math.Min(1048576, Math.Max(256, _filteredStream.Count * 128))); for (int i = 0; i < _filteredStream.Count; i++) { if (i > 0) { stringBuilder.AppendLine(); if (_app.Config.LogMonitorCopyMetadata.Value) { stringBuilder.AppendLine(); } } stringBuilder.Append(_filteredStream[i].GetClipboardText(_app.Config.LogMonitorCopyMetadata.Value)); } GUIUtility.systemCopyBuffer = stringBuilder.ToString(); } private void CopySelectedStream() { EnsureStreamView(); StringBuilder stringBuilder = new StringBuilder(Math.Min(1048576, Math.Max(256, _selectedEntries.Count * 128))); int num = 0; for (int i = 0; i < _filteredStream.Count; i++) { LogEntry logEntry = _filteredStream[i]; if (!_selectedEntries.Contains(logEntry)) { continue; } if (num++ > 0) { stringBuilder.AppendLine(); if (_app.Config.LogMonitorCopyMetadata.Value) { stringBuilder.AppendLine(); } } stringBuilder.Append(logEntry.GetClipboardText(_app.Config.LogMonitorCopyMetadata.Value)); } if (num > 0) { GUIUtility.systemCopyBuffer = stringBuilder.ToString(); } } private void HandleStreamRowClick(int index, LogEntry entry, bool control, bool shift) { if (shift) { int num = ((_selectionAnchorEntry == null) ? (-1) : _filteredStream.IndexOf(_selectionAnchorEntry)); if (num < 0) { num = index; } if (!control) { _selectedEntries.Clear(); } int num2 = Math.Min(num, index); int num3 = Math.Max(num, index); for (int i = num2; i <= num3; i++) { _selectedEntries.Add(_filteredStream[i]); } _selectedEntry = entry; if (_selectionAnchorEntry == null) { _selectionAnchorEntry = entry; } } else if (control) { if (!_selectedEntries.Add(entry)) { _selectedEntries.Remove(entry); } _selectionAnchorEntry = entry; _selectedEntry = (_selectedEntries.Contains(entry) ? entry : FindFirstSelectedEntryInView()); if (_selectedEntries.Count == 0) { ClearStreamSelection(); } } else if (_selectedEntries.Count == 1 && _selectedEntries.Contains(entry)) { ClearStreamSelection(); } else { _selectedEntries.Clear(); _selectedEntries.Add(entry); _selectedEntry = entry; _selectionAnchorEntry = entry; } } private void ClearStreamSelection() { //IL_0021: Unknown result type (might be due to invalid IL or missing references) _selectedEntries.Clear(); _selectedEntry = null; _selectionAnchorEntry = null; _streamDetailsScroll = default(Vector2); } private void RemoveEntryFromStreamSelection(LogEntry entry) { if (entry != null) { _selectedEntries.Remove(entry); if (_selectionAnchorEntry == entry) { _selectionAnchorEntry = null; } if (_selectedEntry == entry) { _selectedEntry = FindFirstSelectedEntryInView(); } if (_selectedEntries.Count == 0) { ClearStreamSelection(); } else if (_selectionAnchorEntry == null) { _selectionAnchorEntry = _selectedEntry; } } } private LogEntry FindFirstSelectedEntryInView() { for (int i = 0; i < _filteredStream.Count; i++) { if (_selectedEntries.Contains(_filteredStream[i])) { return _filteredStream[i]; } } return null; } private void PruneStreamSelectionToFilteredView() { if (_selectedEntries.Count == 0) { return; } HashSet visible = new HashSet(_filteredStream); _selectedEntries.RemoveWhere((LogEntry entry) => !visible.Contains(entry)); if (_selectedEntries.Count == 0) { ClearStreamSelection(); return; } if (_selectedEntry == null || !_selectedEntries.Contains(_selectedEntry)) { _selectedEntry = FindFirstSelectedEntryInView(); } if (_selectionAnchorEntry == null || !_selectedEntries.Contains(_selectionAnchorEntry)) { _selectionAnchorEntry = _selectedEntry; } } private void DrawStreamHeader() { //IL_0029: Unknown result type (might be due to invalid IL or missing references) //IL_002e: Unknown result type (might be due to invalid IL or missing references) Rect rect = GUILayoutUtility.GetRect(1f, HeaderHeight, (GUILayoutOption[])(object)new GUILayoutOption[2] { GUILayout.ExpandWidth(true), GUILayout.Height(HeaderHeight) }); GetStreamColumnWidths(((Rect)(ref rect)).width, out var timeWidth, out var levelWidth, out var sourceWidth, out var messageWidth); float x = ((Rect)(ref rect)).x; DrawHeaderCell(ref x, ((Rect)(ref rect)).y, timeWidth, ((Rect)(ref rect)).height, "Time", "Local event time. Unity timestamps are removed from Message and kept here."); DrawHeaderCell(ref x, ((Rect)(ref rect)).y, levelWidth, ((Rect)(ref rect)).height, "Level", "BepInEx log level."); DrawHeaderCell(ref x, ((Rect)(ref rect)).y, sourceWidth, ((Rect)(ref rect)).height, "Source", "BepInEx logger source. Unity messages are normally forwarded through a Unity Log source."); DrawHeaderCell(ref x, ((Rect)(ref rect)).y, messageWidth, ((Rect)(ref rect)).height, "Message", "First line of the captured log event. Click to select one row, Ctrl-click to toggle rows, or Shift-click to select a range."); } private void DrawStreamRows(float height) { //IL_001f: Unknown result type (might be due to invalid IL or missing references) //IL_0035: 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_007b: Unknown result type (might be due to invalid IL or missing references) //IL_0080: Unknown result type (might be due to invalid IL or missing references) //IL_02a8: Unknown result type (might be due to invalid IL or missing references) //IL_02ae: Invalid comparison between Unknown and I4 //IL_01b2: Unknown result type (might be due to invalid IL or missing references) //IL_017d: Unknown result type (might be due to invalid IL or missing references) //IL_0200: Unknown result type (might be due to invalid IL or missing references) //IL_0205: Unknown result type (might be due to invalid IL or missing references) //IL_020a: Unknown result type (might be due to invalid IL or missing references) //IL_020f: Unknown result type (might be due to invalid IL or missing references) //IL_0239: Unknown result type (might be due to invalid IL or missing references) if (_scrollStreamToEnd) { _streamScroll.y = float.MaxValue; } _streamScroll = GUILayout.BeginScrollView(_streamScroll, false, false, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Height(height) }); float num = Mathf.Max(1f, (float)_filteredStream.Count * RowHeight); Rect rect = GUILayoutUtility.GetRect(1f, num, (GUILayoutOption[])(object)new GUILayoutOption[2] { GUILayout.ExpandWidth(true), GUILayout.Height(num) }); float num2 = _streamScroll.y - 60f; float num3 = _streamScroll.y + height + 60f; int num4 = Mathf.Clamp(Mathf.FloorToInt(num2 / RowHeight), 0, _filteredStream.Count); int num5 = Mathf.Clamp(Mathf.CeilToInt(num3 / RowHeight), 0, _filteredStream.Count); GetStreamColumnWidths(((Rect)(ref rect)).width, out var timeWidth, out var levelWidth, out var sourceWidth, out var messageWidth); Rect val = default(Rect); for (int i = num4; i < num5; i++) { LogEntry logEntry = _filteredStream[i]; float num6 = ((Rect)(ref rect)).y + (float)i * RowHeight; ((Rect)(ref val))..ctor(((Rect)(ref rect)).x, num6, ((Rect)(ref rect)).width, RowHeight); if (_selectedEntries.Contains(logEntry)) { GUIStyle val2 = ((_selectedEntry == logEntry) ? _theme.AccentButtonStyle : GUI.skin.box); GUI.Box(val, GUIContent.none, val2); } Event current = Event.current; bool control = current.control || current.command; bool shift = current.shift; if (GUI.Button(val, GUIContent.none, GUIStyle.none)) { HandleStreamRowClick(i, logEntry, control, shift); } float x = ((Rect)(ref val)).x; DrawCell(ref x, num6, timeWidth, RowHeight, logEntry.TimeText, _labelStyle); Color contentColor = GUI.contentColor; GUI.contentColor = GetLevelColor(logEntry.Level); DrawCell(ref x, num6, levelWidth, RowHeight, logEntry.LevelText, _labelStyle); GUI.contentColor = contentColor; DrawCell(ref x, num6, sourceWidth, RowHeight, logEntry.Source, _labelStyle); DrawCell(ref x, num6, messageWidth, RowHeight, logEntry.Message, _labelStyle); } GUILayout.EndScrollView(); if (_scrollStreamToEnd && (int)Event.current.type == 7) { _scrollStreamToEnd = false; } } private void DrawStreamDetails(float height) { //IL_01f7: Unknown result type (might be due to invalid IL or missing references) //IL_020b: Unknown result type (might be due to invalid IL or missing references) //IL_0210: Unknown result type (might be due to invalid IL or missing references) LogEntry selectedEntry = _selectedEntry; if (selectedEntry == null) { return; } GUILayout.BeginVertical(GUI.skin.box, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Height(height) }); GUILayout.BeginHorizontal(Array.Empty()); string text = ((_selectedEntries.Count > 1) ? $"{_selectedEntries.Count} selected | " : string.Empty); HeaderLabel(text + selectedEntry.TimeText + " [" + selectedEntry.LevelText + ":" + selectedEntry.Source + "]", GUILayout.ExpandWidth(true)); if (GUILayout.Button((_selectedEntries.Count > 1) ? "Copy selected" : "Copy", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width((_selectedEntries.Count > 1) ? 100f : 58f) })) { if (_selectedEntries.Count > 1) { CopySelectedStream(); } else { GUIUtility.systemCopyBuffer = selectedEntry.GetClipboardText(_app.Config.LogMonitorCopyMetadata.Value); } } if (GUILayout.Button("Close", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(58f) })) { ClearStreamSelection(); } GUILayout.EndHorizontal(); string arg = (selectedEntry.IsHistorical ? $"History offset: {selectedEntry.FileOffset}" : $"Sequence: {selectedEntry.Sequence}"); Label(string.Format("{0} | Thread: {1} | Scene: {2}", arg, selectedEntry.ThreadId, string.IsNullOrEmpty(selectedEntry.Scene) ? "(none)" : selectedEntry.Scene)); _streamDetailsScroll = GUILayout.BeginScrollView(_streamDetailsScroll, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.ExpandHeight(true) }); GUILayout.Label(BuildFullText(selectedEntry.Message, selectedEntry.Details), _detailsStyle, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.ExpandWidth(true) }); GUILayout.EndScrollView(); GUILayout.EndVertical(); } private static bool MatchesSearch(LogEntry entry, string search) { if (string.IsNullOrWhiteSpace(search)) { return true; } return Contains(entry.Source, search) || Contains(entry.Message, search) || Contains(entry.RawMessage, search) || Contains(entry.Details, search) || Contains(entry.Scene, search) || Contains(entry.LevelText, search); } private static string BuildFullText(string message, string details) { if (string.IsNullOrEmpty(details)) { return message ?? string.Empty; } if (string.IsNullOrEmpty(message)) { return details; } return message + Environment.NewLine + details; } private static void GetStreamColumnWidths(float availableWidth, out float timeWidth, out float levelWidth, out float sourceWidth, out float messageWidth) { timeWidth = 92f; levelWidth = 72f; sourceWidth = Mathf.Clamp(availableWidth * 0.24f, 130f, 260f); messageWidth = Mathf.Max(160f, availableWidth - timeWidth - levelWidth - sourceWidth); } internal LogMonitorTool(ValheimProfilerApp app) { //IL_0055: Unknown result type (might be due to invalid IL or missing references) //IL_0129: Unknown result type (might be due to invalid IL or missing references) //IL_012a: Unknown result type (might be due to invalid IL or missing references) //IL_012f: Unknown result type (might be due to invalid IL or missing references) //IL_0141: Unknown result type (might be due to invalid IL or missing references) //IL_0146: Unknown result type (might be due to invalid IL or missing references) //IL_0147: Unknown result type (might be due to invalid IL or missing references) //IL_014c: Unknown result type (might be due to invalid IL or missing references) _app = app ?? throw new ArgumentNullException("app"); _windows = app.Windows; _theme = app.Theme; ValheimProfilerConfig config = app.Config; _issueSortColumn = ParseIssueSortColumn(config.LogMonitorIssueSortColumn.Value); Vector2 minimumSize = default(Vector2); ((Vector2)(ref minimumSize))..ctor(760f, 460f); Vector2 defaultToolWindowSize = _windows.GetDefaultToolWindowSize(680f, minimumSize); _mainWindow = _windows.Register(new ProfilerWindow("ValheimProfiler.LogMonitor", "Client Log Monitor", new Rect(ValheimProfilerConfig.DefaultLogMonitorWindowPosition, defaultToolWindowSize), minimumSize, resizable: true, requestedVisible: false, DrawWindow, config.LogMonitorWindowPosition, config.LogMonitorWindowSize)); _listener = new LogMonitorListener(this); Logger.Listeners.Add((ILogListener)(object)_listener); } void IProfilerTool.ShowWindow() { ShowWindow(); } void IProfilerTool.ToggleWindow() { ToggleWindow(); } void IProfilerTool.Update() { Update(); } void IProfilerTool.Shutdown() { Shutdown(); } internal void ToggleWindow() { IsWindowVisible = !IsWindowVisible; if (IsWindowVisible) { ShowWindow(); } } internal void ShowWindow() { IsWindowVisible = true; _app.ShowUi(); _windows.BringToFront(_mainWindow); } internal void Update() { DrainPending(); UpdateHistoryLoading(); } internal void Shutdown() { _captureEnabled = false; try { Logger.Listeners.Remove((ILogListener)(object)_listener); _listener.Dispose(); } catch { } IsWindowVisible = false; ClearCapturedData(); } private void ToggleCapture() { _captureEnabled = !_captureEnabled; if (_captureEnabled && _followStream) { _scrollStreamToEnd = true; } } private static IssueSortColumn ParseIssueSortColumn(string value) { IssueSortColumn result; return (Enum.TryParse(value, ignoreCase: true, out result) && Enum.IsDefined(typeof(IssueSortColumn), result)) ? result : IssueSortColumn.Count; } private void DrawWindow(int id) { //IL_0008: 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_0014: Unknown result type (might be due to invalid IL or missing references) //IL_00f8: Unknown result type (might be due to invalid IL or missing references) EnsureStyles(); Color contentColor = GUI.contentColor; GUI.contentColor = _theme.TextColor; try { GUILayout.BeginVertical(Array.Empty()); DrawToolbar(); GUILayout.Space(2f); MainTab mainTab = (MainTab)GUILayout.Toolbar((int)_mainTab, new string[3] { "Stream", "Issues", "Help" }, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(330f) }); if (mainTab != _mainTab) { _mainTab = mainTab; if (_mainTab == MainTab.Stream && _followStream) { _scrollStreamToEnd = true; } } GUILayout.Space(3f); switch (_mainTab) { case MainTab.Stream: DrawStreamTab(); break; case MainTab.Issues: DrawIssuesTab(); break; default: DrawHelpTab(); break; } GUILayout.EndVertical(); } finally { GUI.contentColor = contentColor; } } private void DrawToolbar() { GUILayout.BeginHorizontal(Array.Empty()); string text = (_captureEnabled ? "ON" : "PAUSED"); GUILayout.Label($"Client | Capture: {text} | Captured: {_capturedEntries} | Buffered: {_entries.Count}/{_app.Config.LogMonitorMaxEntries.Value} | " + $"Issues: {_issues.Count} | Pending: {Mathf.Max(0, Volatile.Read(in _pendingCount))} | Dropped: {DroppedEntries}", _labelStyle, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.ExpandWidth(true) }); if (GUILayout.Button(_captureEnabled ? "Pause capture" : "Resume capture", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(110f) })) { ToggleCapture(); } bool enabled = GUI.enabled; GUI.enabled = enabled && Volatile.Read(in _historyLoadInProgress) == 0; if (GUILayout.Button("Clear", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(62f) })) { ClearCapturedData(); } GUI.enabled = enabled; if (GUILayout.Button("Close", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(62f) })) { IsWindowVisible = false; } GUILayout.EndHorizontal(); } private void DrawHelpTab() { //IL_0003: 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_0012: Unknown result type (might be due to invalid IL or missing references) _helpScroll = GUILayout.BeginScrollView(_helpScroll, Array.Empty()); HeaderLabel("Client Log Monitor"); BodyLabel("Captures live BepInEx log events from the current client process. Unity log messages forwarded through BepInEx are included without installing a second Unity callback, and their redundant leading timestamp is removed from Message while the parsed time remains in the Time column."); BodyLabel("Capture continues while the Log Monitor window is hidden. Pause capture stops adding new entries; it does not freeze or alter normal BepInEx and Unity logging."); GUILayout.Space(6f); HeaderLabel("Stream"); BodyLabel("Stream keeps a bounded in-memory history of all captured levels. Filter by level, logger source or any text contained in the message, details, scene or source name."); BodyLabel("Follow keeps the view at the newest matching entry. Click selects one row, Ctrl-click toggles individual rows and Shift-click selects a continuous range. Copy selected copies the chosen rows with full details and stack traces; Copy filtered copies every currently visible filtered row in chronological order."); GUILayout.Space(6f); HeaderLabel("Issues"); BodyLabel("Issues groups identical Warning, Error and Fatal events by severity, source, message and details. Warning groups are hidden by default and can be enabled from the Issues toolbar."); BodyLabel("Grouping is intentionally conservative. Dynamic values are not stripped from messages, so unrelated failures are not silently merged into one issue."); GUILayout.Space(6f); HeaderLabel("Startup and older history"); BodyLabel("After the BepInEx 'Chainloader startup complete' event is observed, the current LogOutput.log is read once and entries written before Valheim Profiler initialized are merged ahead of the live stream. A sequence of normalized fingerprints is used to avoid duplicating the overlap."); BodyLabel("Load older reads previous bounded pages from LogOutput.log on demand. Manually loaded history is stored beyond the automatic live-entry limit and can be removed with Unload history."); GUILayout.Space(6f); HeaderLabel("Memory and limits"); BodyLabel("Live entries and issue groups use bounded collections controlled by the Log Monitor config section. Manually requested history is allowed beyond the live limit because it is an explicit user action. Issue counts always describe the entries currently loaded in the window."); BodyLabel("Very large individual messages are truncated before storage. A bounded pending queue prevents a log storm on background threads from growing memory without limit."); GUILayout.Space(6f); HeaderLabel("Server logs"); BodyLabel("Server Log Monitor is a separate window so client and server events can be compared at the same time. It is available only when the connected dedicated server also runs a compatible Valheim Profiler backend and the current player is a server administrator who explicitly subscribes."); GUILayout.EndScrollView(); } private void EnsureStyles() { //IL_003e: Unknown result type (might be due to invalid IL or missing references) //IL_0043: 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_0053: Unknown result type (might be due to invalid IL or missing references) //IL_0060: Expected O, but got Unknown //IL_0071: Unknown result type (might be due to invalid IL or missing references) //IL_0083: Unknown result type (might be due to invalid IL or missing references) //IL_0088: Unknown result type (might be due to invalid IL or missing references) //IL_0095: Expected O, but got Unknown //IL_00a6: Unknown result type (might be due to invalid IL or missing references) //IL_00b8: Unknown result type (might be due to invalid IL or missing references) //IL_00c2: Expected O, but got Unknown //IL_00c8: Unknown result type (might be due to invalid IL or missing references) //IL_00d3: Unknown result type (might be due to invalid IL or missing references) //IL_00dd: Unknown result type (might be due to invalid IL or missing references) //IL_00e2: Unknown result type (might be due to invalid IL or missing references) //IL_00e9: Unknown result type (might be due to invalid IL or missing references) //IL_00fb: Unknown result type (might be due to invalid IL or missing references) //IL_0100: Unknown result type (might be due to invalid IL or missing references) //IL_0108: Unknown result type (might be due to invalid IL or missing references) //IL_0110: Unknown result type (might be due to invalid IL or missing references) //IL_011d: Expected O, but got Unknown //IL_012e: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)_styleSkin == (Object)(object)GUI.skin) || _labelStyle == null) { _styleSkin = GUI.skin; _labelStyle = new GUIStyle(GUI.skin.label) { wordWrap = false, clipping = (TextClipping)1, alignment = (TextAnchor)3 }; _labelStyle.normal.textColor = _theme.TextColor; _headerLabelStyle = new GUIStyle(_labelStyle) { fontStyle = (FontStyle)1 }; _headerLabelStyle.normal.textColor = _theme.HeaderTextColor; _activeHeaderLabelStyle = new GUIStyle(_headerLabelStyle); Color color = Color.Lerp(_theme.HeaderTextColor, _theme.AccentColor, 0.5f); SetStyleTextColor(_activeHeaderLabelStyle, color); _detailsStyle = new GUIStyle(GUI.skin.label) { wordWrap = true, clipping = (TextClipping)1, alignment = (TextAnchor)0 }; _detailsStyle.normal.textColor = _theme.TextColor; } } private static void SetStyleTextColor(GUIStyle style, Color color) { //IL_0007: 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_0021: Unknown result type (might be due to invalid IL or missing references) //IL_002e: Unknown result type (might be due to invalid IL or missing references) //IL_003b: Unknown result type (might be due to invalid IL or missing references) //IL_0048: Unknown result type (might be due to invalid IL or missing references) //IL_0055: 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) style.normal.textColor = color; style.hover.textColor = color; style.active.textColor = color; style.focused.textColor = color; style.onNormal.textColor = color; style.onHover.textColor = color; style.onActive.textColor = color; style.onFocused.textColor = color; } private void Label(string text, params GUILayoutOption[] options) { GUILayout.Label(text ?? string.Empty, _labelStyle, options); } private void HeaderLabel(string text, params GUILayoutOption[] options) { GUILayout.Label(text ?? string.Empty, _headerLabelStyle, options); } private void BodyLabel(string text, params GUILayoutOption[] options) { GUILayout.Label(text ?? string.Empty, _detailsStyle, options); } } } namespace ValheimProfiler.Server { internal static class NetworkProfilerInstrumentation { private sealed class RegistrationHolder { internal NetworkProfilerService.RpcRegistrationInfo Registration; } private struct HandlerState { internal bool Active; internal long StartTicks; internal int PayloadBytes; internal long Sender; internal NetworkProfilerService.RpcRegistrationInfo Registration; } private struct DirectInvokeState { internal bool Active; internal string Method; internal int SentDataBefore; } private struct RouteState { internal bool Active; internal int Hash; internal int LogicalBytes; internal long TargetPeer; internal ZDOID TargetZdo; internal int PhysicalSends; internal int PhysicalBytes; internal RouteContext Previous; } private sealed class RouteContext { internal int PhysicalSends; internal int PhysicalBytes; } private struct MutationState { internal bool Active; internal uint DataRevision; internal int KeyHash; internal string ValueType; } private struct OwnerState { internal bool Active; internal long Owner; } private struct SerializeState { internal bool Active; internal long StartTicks; internal int StartSize; internal int StartPosition; } private struct SendZdoState { internal bool Active; internal long StartTicks; internal long PeerId; internal int SentBefore; internal int Queue; internal SendContext Previous; } private sealed class SendContext { internal long PeerId; } private struct SyncListState { internal bool Active; internal long StartTicks; internal long PeerId; } private struct ReceiveState { internal bool Active; internal ReceiveContext Previous; } private struct IncomingRoutedState { internal bool Active; internal bool Previous; } private sealed class ReceiveContext { internal long PeerId; } private static readonly ConditionalWeakTable Registrations = new ConditionalWeakTable(); private static readonly HashSet PatchedHandlerInvokes = new HashSet(); private static readonly object HandlerPatchLock = new object(); private static NetworkProfilerService _service; private static Harmony _harmony; [ThreadStatic] private static RouteContext _routeContext; [ThreadStatic] private static SendContext _sendContext; [ThreadStatic] private static ReceiveContext _receiveContext; [ThreadStatic] private static bool _insideIncomingRoutedRpc; internal static void Install(NetworkProfilerService service, Harmony harmony) { _service = service; _harmony = harmony; PatchRegisterMethods(typeof(ZRpc)); PatchRegisterMethods(typeof(ZRoutedRpc)); PatchRegisterMethods(typeof(ZNetView)); Patch(typeof(ZRpc), "Invoke", null, "DirectInvokePrefix", null, "DirectInvokeFinalizer"); Patch(typeof(ZRpc), "HandlePackage", null, "DirectHandlePrefix"); Patch(typeof(ZRoutedRpc), "RouteRPC", null, "RoutePrefix", null, "RouteFinalizer"); Patch(typeof(ZRoutedRpc), "RPC_RoutedRPC", null, "IncomingRoutedPrefix", null, "IncomingRoutedFinalizer"); Patch(typeof(ZRoutedRpc), "HandleRoutedRPC", null, "HandleRoutedPrefix"); PatchZdoMutationMethods(); Patch(typeof(ZDO), "SetOwnerInternal", null, "OwnerPrefix", "OwnerPostfix"); Patch(typeof(ZDOMan), "CreateNewZDO", new Type[3] { typeof(ZDOID), typeof(Vector3), typeof(int) }, null, "CreateZdoPostfix"); Patch(typeof(ZDOMan), "DestroyZDO", null, "DestroyZdoPrefix"); Patch(typeof(ZDOMan), "SendZDOs", null, "SendZdosPrefix", null, "SendZdosFinalizer"); Patch(typeof(ZDOMan), "CreateSyncList", null, "CreateSyncListPrefix", null, "CreateSyncListFinalizer"); Patch(typeof(ZDO), "Serialize", null, "SerializePrefix", null, "SerializeFinalizer"); Patch(typeof(ZDOMan), "RPC_ZDOData", null, "ReceivePrefix", null, "ReceiveFinalizer"); Patch(typeof(ZDO), "Deserialize", null, "DeserializePrefix", null, "DeserializeFinalizer"); } internal static void Uninstall(NetworkProfilerService service) { if (_service != service) { return; } _service = null; _harmony = null; lock (HandlerPatchLock) { PatchedHandlerInvokes.Clear(); } } internal static void BindHandler(object handlerObject, NetworkProfilerService.RpcRegistrationInfo registration) { if (handlerObject == null || registration == null) { return; } try { Registrations.Remove(handlerObject); Registrations.Add(handlerObject, new RegistrationHolder { Registration = registration }); MethodInfo methodInfo = AccessTools.Method(handlerObject.GetType(), "Invoke", (Type[])null, (Type[])null); if (methodInfo == null) { return; } lock (HandlerPatchLock) { if (PatchedHandlerInvokes.Add(methodInfo)) { Harmony harmony = _harmony; if (harmony != null) { harmony.Patch((MethodBase)methodInfo, HarmonyMethod("HandlerPrefix", 800), (HarmonyMethod)null, (HarmonyMethod)null, HarmonyMethod("HandlerFinalizer", 0), (HarmonyMethod)null); } } } } catch { } } private static void PatchRegisterMethods(Type type) { foreach (MethodInfo item in from m in type.GetMethods(BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic) where m.Name == "Register" && m.GetParameters().Length == 2 select m) { try { _harmony.Patch((MethodBase)item, (HarmonyMethod)null, HarmonyMethod("RegisterPostfix", 0), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } catch { } } } private static void RegisterPostfix(object __instance, object[] __args) { try { if (_service == null || __args == null || __args.Length < 2 || !(__args[0] is string text) || !(__args[1] is Delegate handler)) { return; } int stableHashCode = StringExtensionMethods.GetStableHashCode(text); object obj = null; string prefab = string.Empty; ZRpc val = (ZRpc)((__instance is ZRpc) ? __instance : null); string layer; if (val != null) { layer = "Direct"; val.m_functions.TryGetValue(stableHashCode, out var value); obj = value; } else { ZRoutedRpc val2 = (ZRoutedRpc)((__instance is ZRoutedRpc) ? __instance : null); if (val2 != null) { layer = "Global"; val2.m_functions.TryGetValue(stableHashCode, out var value2); obj = value2; } else { ZNetView val3 = (ZNetView)((__instance is ZNetView) ? __instance : null); if (val3 == null) { return; } layer = "Object"; val3.m_functions.TryGetValue(stableHashCode, out var value3); obj = value3; try { prefab = val3.GetPrefabName() ?? string.Empty; } catch { } } } _service.RegisterRpc(obj, layer, text, handler, prefab); } catch { } } private static void HandlerPrefix(object __instance, object[] __args, ref HandlerState __state) { try { NetworkProfilerService service = _service; if (service == null || !service.IsCollecting || __instance == null || !Registrations.TryGetValue(__instance, out var value)) { return; } __state.Active = true; __state.StartTicks = Stopwatch.GetTimestamp(); __state.Registration = value.Registration; if (__args == null) { return; } for (int i = 0; i < __args.Length; i++) { if (__args[i] is long sender) { __state.Sender = sender; continue; } object obj = __args[i]; ZPackage val = (ZPackage)((obj is ZPackage) ? obj : null); if (val != null) { __state.PayloadBytes = Math.Max(0, val.Size()); continue; } object obj2 = __args[i]; ZRpc val2 = (ZRpc)((obj2 is ZRpc) ? obj2 : null); if (val2 != null) { __state.Sender = ResolvePeerId(val2); } } } catch { } } private static Exception HandlerFinalizer(Exception __exception, HandlerState __state) { try { if (__state.Active && _service != null && __state.Registration != null && __state.StartTicks > 0) { double elapsedMs = ElapsedMs(__state.StartTicks); _service.RecordRpcHandler(__state.Registration, __state.Sender, __state.PayloadBytes, elapsedMs); if (__exception != null) { _service.RecordRpcHandlerException(__state.Registration, __state.Sender, __exception); } } } catch { } return __exception; } private static void DirectInvokePrefix(string method, ZRpc __instance, ref DirectInvokeState __state) { NetworkProfilerService service = _service; if (service != null && service.IsCollecting) { __state.Active = true; __state.Method = method ?? string.Empty; __state.SentDataBefore = __instance?.m_sentData ?? 0; } } private static Exception DirectInvokeFinalizer(Exception __exception, ZRpc __instance, DirectInvokeState __state) { try { if (!__state.Active) { return __exception; } int num = Math.Max(0, (__instance?.m_sentData ?? __state.SentDataBefore) - __state.SentDataBefore); if (_routeContext != null && string.Equals(__state.Method, "RoutedRPC", StringComparison.Ordinal)) { if (num > 0) { _routeContext.PhysicalSends++; _routeContext.PhysicalBytes += num; } } else if (_service != null && num > 0 && !string.Equals(__state.Method, "RoutedRPC", StringComparison.Ordinal)) { _service.RecordDirectOutgoing(__state.Method, num, __instance); } } catch { } return __exception; } private static void DirectHandlePrefix(ZRpc __instance, ZPackage package) { //IL_0079: Unknown result type (might be due to invalid IL or missing references) try { NetworkProfilerService service = _service; if (service != null && service.IsCollecting && package != null) { int pos = package.GetPos(); int num = package.ReadInt(); package.SetPos(pos); if (num != 0 && (__instance?.m_functions == null || !__instance.m_functions.ContainsKey(num))) { _service.RecordRoutingError("Direct handler missing", num, ResolvePeerId(__instance), ZDOID.None, "Received direct RPC hash is not registered on this peer."); } } } catch { } } private static void RoutePrefix(RoutedRPCData rpcData, ref RouteState __state) { //IL_005d: 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) NetworkProfilerService service = _service; if (service != null && service.IsCollecting && rpcData != null) { __state.Active = true; __state.Hash = rpcData.m_methodHash; ZPackage parameters = rpcData.m_parameters; __state.LogicalBytes = ((parameters != null) ? parameters.Size() : 0); __state.TargetPeer = rpcData.m_targetPeerID; __state.TargetZdo = rpcData.m_targetZDO; __state.Previous = _routeContext; _routeContext = new RouteContext(); } } private static Exception RouteFinalizer(Exception __exception, RouteState __state) { //IL_0066: Unknown result type (might be due to invalid IL or missing references) //IL_00fe: Unknown result type (might be due to invalid IL or missing references) try { if (!__state.Active) { return __exception; } RouteContext routeContext = _routeContext; __state.PhysicalSends = routeContext?.PhysicalSends ?? 0; __state.PhysicalBytes = routeContext?.PhysicalBytes ?? 0; _service?.RecordRoutedOutgoing(__state.Hash, __state.LogicalBytes, __state.PhysicalSends, __state.PhysicalBytes, __state.TargetZdo); if (__state.TargetPeer != 0L && ZRoutedRpc.instance != null && __state.TargetPeer != ZRoutedRpc.instance.m_id && __state.PhysicalSends == 0) { ZNetPeer peer = ZRoutedRpc.instance.GetPeer(__state.TargetPeer); string details = ((peer == null) ? "Target peer was not found." : ((!peer.IsReady()) ? "Target peer exists but is not ready." : "No physical send was observed. The socket may be disconnected or a networking mod may have suppressed the route.")); CaptureExternalCaller(out var caller, out var callerMod); _service?.RecordRoutingError("Target peer unavailable", __state.Hash, __state.TargetPeer, __state.TargetZdo, details, "", "", "", caller, callerMod); } } catch { } finally { if (__state.Active) { _routeContext = __state.Previous; } } return __exception; } private static void IncomingRoutedPrefix(ZPackage pkg, ref IncomingRoutedState __state) { //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_0080: Unknown result type (might be due to invalid IL or missing references) NetworkProfilerService service = _service; if (service == null || !service.IsCollecting) { return; } __state.Active = true; __state.Previous = _insideIncomingRoutedRpc; _insideIncomingRoutedRpc = true; try { if (_service != null && pkg != null) { int pos = pkg.GetPos(); pkg.ReadLong(); long senderPeerId = pkg.ReadLong(); long targetPeerId = pkg.ReadLong(); ZDOID targetZdo = pkg.ReadZDOID(); int methodHash = pkg.ReadInt(); pkg.SetPos(pos); InspectRoutedTarget(senderPeerId, targetPeerId, targetZdo, methodHash); } } catch { } } private static Exception IncomingRoutedFinalizer(Exception __exception, IncomingRoutedState __state) { if (__state.Active) { _insideIncomingRoutedRpc = __state.Previous; } return __exception; } private static void HandleRoutedPrefix(RoutedRPCData data) { NetworkProfilerService service = _service; if (service == null || !service.IsCollecting || _insideIncomingRoutedRpc) { return; } try { InspectRoutedTarget(data, captureCallerOnError: true); } catch { } } private static void InspectRoutedTarget(RoutedRPCData data, bool captureCallerOnError = false) { //IL_0018: Unknown result type (might be due to invalid IL or missing references) if (data != null) { InspectRoutedTarget(data.m_senderPeerID, data.m_targetPeerID, data.m_targetZDO, data.m_methodHash, captureCallerOnError); } } private static void InspectRoutedTarget(long senderPeerId, long targetPeerId, ZDOID targetZdo, int methodHash, bool captureCallerOnError = false) { //IL_0091: Unknown result type (might be due to invalid IL or missing references) //IL_0069: Unknown result type (might be due to invalid IL or missing references) //IL_00aa: Unknown result type (might be due to invalid IL or missing references) //IL_00e7: Unknown result type (might be due to invalid IL or missing references) //IL_011a: Unknown result type (might be due to invalid IL or missing references) if (_service == null || ZRoutedRpc.instance == null || (targetPeerId != 0L && targetPeerId != ZRoutedRpc.instance.m_id)) { return; } if (((ZDOID)(ref targetZdo)).IsNone()) { if (!ZRoutedRpc.instance.m_functions.ContainsKey(methodHash)) { RecordRoutedTargetError("Global routed handler missing", methodHash, senderPeerId, ZDOID.None, "The routed RPC reached this peer but no global handler is registered.", string.Empty, captureCallerOnError); } return; } ZDOMan instance = ZDOMan.instance; ZDO val = ((instance != null) ? instance.GetZDO(targetZdo) : null); if (val == null) { RecordRoutedTargetError("Target ZDO missing", methodHash, senderPeerId, targetZdo, "The routed object RPC targets a ZDO that is not present on this peer.", string.Empty, captureCallerOnError); return; } ZNetScene instance2 = ZNetScene.instance; ZNetView val2 = ((instance2 != null) ? instance2.FindInstance(val) : null); if ((Object)(object)val2 == (Object)null) { RecordRoutedTargetError("Target ZNetView missing", methodHash, senderPeerId, targetZdo, "The target ZDO exists but its ZNetView is not instantiated.", SafePrefab(val2, val), captureCallerOnError); } else if (!val2.m_functions.ContainsKey(methodHash)) { RecordRoutedTargetError("Object handler missing", methodHash, senderPeerId, targetZdo, "The target ZNetView exists but does not have this RPC registered.", SafePrefab(val2, val), captureCallerOnError); } } private static void RecordRoutedTargetError(string kind, int methodHash, long senderPeerId, ZDOID targetZdo, string details, string prefab, bool captureCaller) { //IL_002b: Unknown result type (might be due to invalid IL or missing references) string caller = string.Empty; string callerMod = string.Empty; if (captureCaller) { CaptureExternalCaller(out caller, out callerMod); } _service?.RecordRoutingError(kind, methodHash, senderPeerId, targetZdo, details, "", "", prefab, caller, callerMod); } private static string SafePrefab(ZNetView view, ZDO zdo) { try { return ((view != null) ? view.GetPrefabName() : null) ?? ((zdo != null) ? zdo.GetPrefab().ToString() : null) ?? string.Empty; } catch { return string.Empty; } } private static void PatchZdoMutationMethods() { MethodInfo[] methods = typeof(ZDO).GetMethods(BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); foreach (MethodInfo methodInfo in methods) { ParameterInfo[] parameters = methodInfo.GetParameters(); if (methodInfo.Name == "Set" && parameters.Length != 0 && parameters[0].ParameterType == typeof(string)) { try { _harmony.Patch((MethodBase)methodInfo, HarmonyMethod("RememberStringKeyPrefix", 800), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } catch { } continue; } bool flag = false; if ((methodInfo.Name == "Set" || methodInfo.Name == "Update") && parameters.Length != 0 && parameters[0].ParameterType == typeof(int) && (!(methodInfo.Name == "Set") || parameters.Length <= 1 || !(parameters[1].ParameterType == typeof(bool)))) { flag = true; } else { bool flag2; switch (methodInfo.Name) { case "SetConnection": case "UpdateConnection": case "SetPosition": case "SetRotation": case "SetType": case "SetDistant": case "SetPrefab": case "SetSector": flag2 = true; break; default: flag2 = false; break; } if (flag2) { flag = true; } } if (flag) { try { _harmony.Patch((MethodBase)methodInfo, HarmonyMethod("MutationPrefix", 800), HarmonyMethod("MutationPostfix", 0), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } catch { } } } } private static void RememberStringKeyPrefix(object[] __args) { try { NetworkProfilerService service = _service; if (service != null && service.IsCollecting && __args != null && __args.Length != 0 && __args[0] is string text) { _service?.RememberKeyName(StringExtensionMethods.GetStableHashCode(text), text); if (__args.Length > 1 && __args[1] is ZDOID) { KeyValuePair hashZDOID = ZDO.GetHashZDOID(text); _service?.RememberKeyName(hashZDOID.Key, text + "_u"); _service?.RememberKeyName(hashZDOID.Value, text + "_i"); } } } catch { } } private static void MutationPrefix(ZDO __instance, MethodBase __originalMethod, object[] __args, ref MutationState __state) { NetworkProfilerService service = _service; if (service == null || !service.IsCollecting || __instance == null) { return; } __state.Active = true; __state.DataRevision = __instance.DataRevision; __state.ValueType = __originalMethod?.Name ?? "Mutation"; if (__args != null && __args.Length != 0) { if (__args[0] is int keyHash) { __state.KeyHash = keyHash; } else if (__args[0] is KeyValuePair keyValuePair) { __state.KeyHash = keyValuePair.Key; } if (__args.Length > 1 && __args[1] != null) { __state.ValueType = __args[1].GetType().Name; } } } private static void MutationPostfix(ZDO __instance, MutationState __state) { try { if (__state.Active && __instance != null && __instance.DataRevision != __state.DataRevision) { _service?.RecordZdoMutation(__instance, __state.KeyHash, __state.ValueType); } } catch { } } private static void OwnerPrefix(ZDO __instance, ref OwnerState __state) { NetworkProfilerService service = _service; if (service != null && service.IsCollecting && __instance != null) { __state.Active = true; __state.Owner = __instance.GetOwner(); } } private static void OwnerPostfix(ZDO __instance, OwnerState __state) { try { if (__state.Active && __instance != null && __instance.GetOwner() != __state.Owner) { _service?.RecordZdoOwnershipChange(__instance); } } catch { } } private static void CreateZdoPostfix(ZDO __result) { try { NetworkProfilerService service = _service; if (service != null && service.IsCollecting) { _service.RecordZdoCreate(__result); } } catch { } } private static void DestroyZdoPrefix(ZDO zdo) { try { NetworkProfilerService service = _service; if (service != null && service.IsCollecting) { _service.RecordZdoDestroy(zdo); } } catch { } } private static void SendZdosPrefix(ZDOPeer peer, ref SendZdoState __state) { NetworkProfilerService service = _service; if (service == null || !service.IsCollecting) { return; } __state.Active = true; __state.StartTicks = Stopwatch.GetTimestamp(); __state.PeerId = (peer?.m_peer?.m_uid).GetValueOrDefault(); __state.SentBefore = ZDOMan.instance?.m_zdosSent ?? 0; try { int? obj; if (peer == null) { obj = null; } else { ZNetPeer peer2 = peer.m_peer; if (peer2 == null) { obj = null; } else { ISocket socket = peer2.m_socket; obj = ((socket != null) ? new int?(socket.GetSendQueueSize()) : ((int?)null)); } } __state.Queue = obj ?? (-1); } catch { __state.Queue = -1; } __state.Previous = _sendContext; _sendContext = new SendContext { PeerId = __state.PeerId }; } private static Exception SendZdosFinalizer(Exception __exception, SendZdoState __state) { try { if (!__state.Active) { return __exception; } int zdoCount = Math.Max(0, (ZDOMan.instance?.m_zdosSent ?? __state.SentBefore) - __state.SentBefore); _service?.RecordSendZdos(__state.PeerId, ElapsedMs(__state.StartTicks), zdoCount, __state.Queue); } catch { } finally { if (__state.Active) { _sendContext = __state.Previous; } } return __exception; } private static void CreateSyncListPrefix(ZDOPeer peer, ref SyncListState __state) { NetworkProfilerService service = _service; if (service != null && service.IsCollecting) { __state.Active = true; __state.StartTicks = Stopwatch.GetTimestamp(); __state.PeerId = (peer?.m_peer?.m_uid).GetValueOrDefault(); } } private static Exception CreateSyncListFinalizer(Exception __exception, ZDOMan __instance, List toSync, SyncListState __state) { try { if (!__state.Active) { return __exception; } int candidates = (__instance?.m_tempSectorObjects?.Count).GetValueOrDefault() + (__instance?.m_tempToSyncDistant?.Count).GetValueOrDefault(); _service?.RecordCreateSyncList(__state.PeerId, ElapsedMs(__state.StartTicks), candidates, toSync?.Count ?? 0); } catch { } return __exception; } private static void SerializePrefix(ZPackage pkg, ref SerializeState __state) { NetworkProfilerService service = _service; if (service != null && service.IsCollecting && _sendContext != null) { __state.Active = true; __state.StartTicks = Stopwatch.GetTimestamp(); __state.StartSize = ((pkg != null) ? pkg.Size() : 0); } } private static Exception SerializeFinalizer(Exception __exception, ZDO __instance, ZPackage pkg, SerializeState __state) { try { if (__state.Active && _sendContext != null) { _service?.RecordZdoSerialize(__instance, Math.Max(0, ((pkg != null) ? pkg.Size() : __state.StartSize) - __state.StartSize), ElapsedMs(__state.StartTicks), _sendContext.PeerId, receiving: false); } } catch { } return __exception; } private static void ReceivePrefix(ZRpc rpc, ref ReceiveState __state) { NetworkProfilerService service = _service; if (service != null && service.IsCollecting) { __state.Active = true; __state.Previous = _receiveContext; _receiveContext = new ReceiveContext { PeerId = ResolvePeerId(rpc) }; } } private static Exception ReceiveFinalizer(Exception __exception, ReceiveState __state) { if (__state.Active) { _receiveContext = __state.Previous; } return __exception; } private static void DeserializePrefix(ZPackage pkg, ref SerializeState __state) { NetworkProfilerService service = _service; if (service != null && service.IsCollecting && _receiveContext != null) { __state.Active = true; __state.StartTicks = Stopwatch.GetTimestamp(); __state.StartPosition = ((pkg != null) ? pkg.GetPos() : 0); } } private static Exception DeserializeFinalizer(Exception __exception, ZDO __instance, ZPackage pkg, SerializeState __state) { try { if (__state.Active && _receiveContext != null) { _service?.RecordZdoSerialize(__instance, Math.Max(0, ((pkg != null) ? pkg.GetPos() : __state.StartPosition) - __state.StartPosition), ElapsedMs(__state.StartTicks), _receiveContext.PeerId, receiving: true); } } catch { } return __exception; } private static void Patch(Type type, string name, Type[] parameters = null, string prefix = null, string postfix = null, string finalizer = null) { try { MethodInfo methodInfo = ((parameters == null) ? AccessTools.Method(type, name, (Type[])null, (Type[])null) : AccessTools.Method(type, name, parameters, (Type[])null)); if (methodInfo == null) { _service?.ReportInstrumentationWarning(type.FullName + "." + name + " was not found; related metrics are unavailable."); } else { _harmony.Patch((MethodBase)methodInfo, (prefix == null) ? null : HarmonyMethod(prefix, 800), (postfix == null) ? null : HarmonyMethod(postfix, 0), (HarmonyMethod)null, (finalizer == null) ? null : HarmonyMethod(finalizer, 0), (HarmonyMethod)null); } } catch (Exception ex) { _service?.ReportInstrumentationWarning(type.FullName + "." + name + " could not be patched: " + ex.GetType().Name + ": " + ex.Message); } } private static HarmonyMethod HarmonyMethod(string methodName, int priority) { //IL_0013: Unknown result type (might be due to invalid IL or missing references) //IL_0019: Expected O, but got Unknown HarmonyMethod val = new HarmonyMethod(AccessTools.Method(typeof(NetworkProfilerInstrumentation), methodName, (Type[])null, (Type[])null)); val.priority = priority; return val; } private static void CaptureExternalCaller(out string caller, out string callerMod) { caller = string.Empty; callerMod = string.Empty; try { StackFrame[] frames = new StackTrace(2, fNeedFileInfo: false).GetFrames(); if (frames == null) { return; } for (int i = 0; i < frames.Length; i++) { MethodBase method = frames[i].GetMethod(); Type type = method?.DeclaringType; if (method == null || type == null) { continue; } Assembly assembly = type.Assembly; string text = assembly.GetName().Name ?? string.Empty; if (assembly == typeof(NetworkProfilerInstrumentation).Assembly || text == "0Harmony" || type == typeof(ZRpc) || type == typeof(ZRoutedRpc) || type == typeof(ZNetView)) { continue; } string? text2 = type.Namespace; if (text2 == null || !text2.StartsWith("HarmonyLib", StringComparison.Ordinal)) { string? text3 = type.Namespace; if (text3 == null || !text3.StartsWith("System", StringComparison.Ordinal)) { caller = type.FullName + "." + method.Name; callerMod = _service?.ResolveMod(assembly) ?? text; break; } } } } catch { } } private static long ResolvePeerId(ZRpc rpc) { try { ZNet instance = ZNet.instance; return ((instance == null) ? ((long?)null) : instance.GetPeer(rpc)?.m_uid).GetValueOrDefault(); } catch { return 0L; } } private static double ElapsedMs(long startTicks) { if (startTicks <= 0) { return 0.0; } return Math.Max(0.0, (double)(Stopwatch.GetTimestamp() - startTicks) * 1000.0 / (double)Stopwatch.Frequency); } } internal enum NetworkProfilerRequestKind : byte { Probe = 1, Subscribe, Unsubscribe, Reset } internal enum NetworkProfilerResponseKind : byte { Capabilities = 1, Snapshot, Status, Error } internal sealed class NetworkRpcWireRow { internal string Layer = string.Empty; internal string Name = string.Empty; internal int MethodHash; internal string Component = string.Empty; internal string Handler = string.Empty; internal string Mod = string.Empty; internal string Prefab = string.Empty; internal long Registrations; internal long IncomingCalls; internal long LocalCalls; internal long OutgoingCalls; internal long IncomingBytes; internal long LocalBytes; internal long OutgoingBytes; internal long PhysicalSends; internal long PhysicalBytes; internal double HandlerMs; internal double AverageHandlerMs; internal double MaxHandlerMs; internal int MaxPayloadBytes; internal int MaxCallsPerFrame; internal long Errors; } internal sealed class NetworkZdoWireRow { internal int PrefabHash; internal string Prefab = string.Empty; internal long Mutations; internal long UniqueChanged; internal long SentCount; internal long SentBytes; internal long ReceivedCount; internal long ReceivedBytes; internal double SerializeMs; internal double DeserializeMs; internal int AverageSize; internal int MaxSize; internal long Creates; internal long Destroys; internal long OwnershipChanges; } internal sealed class NetworkZdoInstanceWireRow { internal string ZdoId = string.Empty; internal int PrefabHash; internal string Prefab = string.Empty; internal long Owner; internal long Mutations; internal long SentCount; internal long SentBytes; internal long ReceivedCount; internal long ReceivedBytes; internal int MaxSize; } internal sealed class NetworkZdoKeyWireRow { internal int KeyHash; internal string KeyName = string.Empty; internal string ValueType = string.Empty; internal string Prefab = string.Empty; internal long Mutations; internal long AffectedZdos; } internal sealed class NetworkPeerWireRow { internal long PeerId; internal string Name = string.Empty; internal string Host = string.Empty; internal string Socket = string.Empty; internal bool Ready; internal int Ping; internal float LocalQuality; internal float RemoteQuality; internal float ReportedOutBytesPerSecond; internal float ReportedInBytesPerSecond; internal int SerializedOutBytesPerSecond; internal int SerializedInBytesPerSecond; internal int SendQueue; internal int MaximumSendQueue; internal int CurrentSendRate; internal long ActualInFlightBytes; internal float LastReceiveAge; internal double SendZdoMs; internal double CreateSyncListMs; internal int ZdosSent; internal int SyncCandidates; internal int SyncSelected; } internal sealed class NetworkRoutingErrorWireRow { internal string Kind = string.Empty; internal string Rpc = string.Empty; internal int MethodHash; internal string Component = string.Empty; internal string Handler = string.Empty; internal string Prefab = string.Empty; internal string Caller = string.Empty; internal string CallerMod = string.Empty; internal long PeerId; internal string Peer = string.Empty; internal string Target = string.Empty; internal long Count; internal string LastDetails = string.Empty; } internal sealed class NetworkProfilerResponse { internal NetworkProfilerResponseKind Kind; internal string SessionId = string.Empty; internal string Status = string.Empty; internal string ServerVersion = string.Empty; internal bool Authorized; internal long SnapshotSequence; internal bool FullRegistry; internal string CompatibilitySummary = string.Empty; internal readonly List CompatibilityWarnings = new List(); internal readonly List RpcRows = new List(); internal readonly List ZdoRows = new List(); internal readonly List ZdoInstanceRows = new List(); internal readonly List ZdoKeyRows = new List(); internal readonly List PeerRows = new List(); internal readonly List ErrorRows = new List(); } internal static class NetworkProfilerProtocol { private delegate void RowWriter(ZPackage package, T row); private delegate T RowReader(ZPackage package); internal const int Version = 1; internal const string RequestRpc = "shudnal.ValheimProfiler.Network.Request"; internal const string ResponseRpc = "shudnal.ValheimProfiler.Network.Response"; internal const int MaxRequestPayloadBytes = 8192; internal const int MaxResponsePayloadBytes = 4194304; internal const int MaxStringLength = 4096; internal const int MaxRowsPerSection = 2000; internal static ZPackage CreateRequest(NetworkProfilerRequestKind kind) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Expected O, but got Unknown ZPackage val = new ZPackage(); val.Write(1); val.Write((byte)kind); return val; } internal static void ReadRequest(ZPackage package, out int version, out NetworkProfilerRequestKind kind) { version = package.ReadInt(); kind = (NetworkProfilerRequestKind)package.ReadByte(); if (!Enum.IsDefined(typeof(NetworkProfilerRequestKind), kind)) { throw new InvalidOperationException($"Unknown Network Profiler request kind {(byte)kind}."); } } internal static ZPackage CreateResponse(NetworkProfilerResponse response) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Expected O, but got Unknown ZPackage val = new ZPackage(); val.Write(1); val.Write((byte)response.Kind); WriteString(val, response.SessionId, 128); WriteString(val, response.Status, 4096); WriteString(val, response.ServerVersion, 128); val.Write(response.Authorized); val.Write(response.SnapshotSequence); val.Write(response.FullRegistry); WriteString(val, response.CompatibilitySummary, 4096); WriteStringList(val, response.CompatibilityWarnings, 128); WriteRows(val, response.RpcRows, WriteRpcRow); WriteRows(val, response.ZdoRows, WriteZdoRow); WriteRows(val, response.ZdoInstanceRows, WriteZdoInstanceRow); WriteRows(val, response.ZdoKeyRows, WriteZdoKeyRow); WriteRows(val, response.PeerRows, WritePeerRow); WriteRows(val, response.ErrorRows, WriteErrorRow); return val; } internal static NetworkProfilerResponse ReadResponse(ZPackage package) { int num = package.ReadInt(); if (num != 1) { throw new InvalidOperationException($"Unsupported Network Profiler protocol {num}; expected {1}."); } NetworkProfilerResponseKind networkProfilerResponseKind = (NetworkProfilerResponseKind)package.ReadByte(); if (!Enum.IsDefined(typeof(NetworkProfilerResponseKind), networkProfilerResponseKind)) { throw new InvalidOperationException($"Unknown Network Profiler response kind {(byte)networkProfilerResponseKind}."); } NetworkProfilerResponse networkProfilerResponse = new NetworkProfilerResponse { Kind = networkProfilerResponseKind, SessionId = ReadString(package, 128, "session id"), Status = ReadString(package, 4096, "status"), ServerVersion = ReadString(package, 128, "server version"), Authorized = package.ReadBool(), SnapshotSequence = package.ReadLong(), FullRegistry = package.ReadBool(), CompatibilitySummary = ReadString(package, 4096, "compatibility summary") }; if (networkProfilerResponse.SnapshotSequence < 0) { throw new InvalidOperationException("Network Profiler response contains a negative snapshot sequence."); } ReadStringList(package, networkProfilerResponse.CompatibilityWarnings, 128); ReadRows(package, networkProfilerResponse.RpcRows, ReadRpcRow); ReadRows(package, networkProfilerResponse.ZdoRows, ReadZdoRow); ReadRows(package, networkProfilerResponse.ZdoInstanceRows, ReadZdoInstanceRow); ReadRows(package, networkProfilerResponse.ZdoKeyRows, ReadZdoKeyRow); ReadRows(package, networkProfilerResponse.PeerRows, ReadPeerRow); ReadRows(package, networkProfilerResponse.ErrorRows, ReadErrorRow); return networkProfilerResponse; } private static void WriteRows(ZPackage package, List rows, RowWriter writer) { int num = Math.Min(rows?.Count ?? 0, 2000); package.Write(num); for (int i = 0; i < num; i++) { writer(package, rows[i]); } } private static void ReadRows(ZPackage package, List rows, RowReader reader) { int num = package.ReadInt(); if (num < 0 || num > 2000) { throw new InvalidOperationException($"Invalid Network Profiler row count {num}."); } for (int i = 0; i < num; i++) { rows.Add(reader(package)); } } private static void WriteStringList(ZPackage package, List values, int maximumCount) { int num = Math.Min(values?.Count ?? 0, maximumCount); package.Write(num); for (int i = 0; i < num; i++) { WriteString(package, values[i], 4096); } } private static void ReadStringList(ZPackage package, List values, int maximumCount) { int num = package.ReadInt(); if (num < 0 || num > maximumCount) { throw new InvalidOperationException($"Invalid Network Profiler string list count {num}."); } for (int i = 0; i < num; i++) { values.Add(ReadString(package, 4096, "list item")); } } private static void WriteRpcRow(ZPackage p, NetworkRpcWireRow r) { WriteString(p, r.Layer, 64); WriteString(p, r.Name, 512); p.Write(r.MethodHash); WriteString(p, r.Component, 512); WriteString(p, r.Handler, 1024); WriteString(p, r.Mod, 512); WriteString(p, r.Prefab, 512); p.Write(r.Registrations); p.Write(r.IncomingCalls); p.Write(r.LocalCalls); p.Write(r.OutgoingCalls); p.Write(r.IncomingBytes); p.Write(r.LocalBytes); p.Write(r.OutgoingBytes); p.Write(r.PhysicalSends); p.Write(r.PhysicalBytes); p.Write(r.HandlerMs); p.Write(r.AverageHandlerMs); p.Write(r.MaxHandlerMs); p.Write(r.MaxPayloadBytes); p.Write(r.MaxCallsPerFrame); p.Write(r.Errors); } private static NetworkRpcWireRow ReadRpcRow(ZPackage p) { return new NetworkRpcWireRow { Layer = ReadString(p, 64, "rpc layer"), Name = ReadString(p, 512, "rpc name"), MethodHash = p.ReadInt(), Component = ReadString(p, 512, "rpc component"), Handler = ReadString(p, 1024, "rpc handler"), Mod = ReadString(p, 512, "rpc mod"), Prefab = ReadString(p, 512, "rpc prefab"), Registrations = ReadNonNegativeLong(p, "registrations"), IncomingCalls = ReadNonNegativeLong(p, "incoming calls"), LocalCalls = ReadNonNegativeLong(p, "local calls"), OutgoingCalls = ReadNonNegativeLong(p, "outgoing calls"), IncomingBytes = ReadNonNegativeLong(p, "incoming bytes"), LocalBytes = ReadNonNegativeLong(p, "local bytes"), OutgoingBytes = ReadNonNegativeLong(p, "outgoing bytes"), PhysicalSends = ReadNonNegativeLong(p, "physical sends"), PhysicalBytes = ReadNonNegativeLong(p, "physical bytes"), HandlerMs = ReadNonNegativeDouble(p, "handler ms"), AverageHandlerMs = ReadNonNegativeDouble(p, "average handler ms"), MaxHandlerMs = ReadNonNegativeDouble(p, "max handler ms"), MaxPayloadBytes = ReadNonNegativeInt(p, "max payload"), MaxCallsPerFrame = ReadNonNegativeInt(p, "max calls per frame"), Errors = ReadNonNegativeLong(p, "errors") }; } private static void WriteZdoRow(ZPackage p, NetworkZdoWireRow r) { p.Write(r.PrefabHash); WriteString(p, r.Prefab, 512); p.Write(r.Mutations); p.Write(r.UniqueChanged); p.Write(r.SentCount); p.Write(r.SentBytes); p.Write(r.ReceivedCount); p.Write(r.ReceivedBytes); p.Write(r.SerializeMs); p.Write(r.DeserializeMs); p.Write(r.AverageSize); p.Write(r.MaxSize); p.Write(r.Creates); p.Write(r.Destroys); p.Write(r.OwnershipChanges); } private static NetworkZdoWireRow ReadZdoRow(ZPackage p) { return new NetworkZdoWireRow { PrefabHash = p.ReadInt(), Prefab = ReadString(p, 512, "prefab"), Mutations = ReadNonNegativeLong(p, "mutations"), UniqueChanged = ReadNonNegativeLong(p, "unique changed"), SentCount = ReadNonNegativeLong(p, "sent count"), SentBytes = ReadNonNegativeLong(p, "sent bytes"), ReceivedCount = ReadNonNegativeLong(p, "received count"), ReceivedBytes = ReadNonNegativeLong(p, "received bytes"), SerializeMs = ReadNonNegativeDouble(p, "serialize ms"), DeserializeMs = ReadNonNegativeDouble(p, "deserialize ms"), AverageSize = ReadNonNegativeInt(p, "average size"), MaxSize = ReadNonNegativeInt(p, "max size"), Creates = ReadNonNegativeLong(p, "creates"), Destroys = ReadNonNegativeLong(p, "destroys"), OwnershipChanges = ReadNonNegativeLong(p, "ownership changes") }; } private static void WriteZdoInstanceRow(ZPackage p, NetworkZdoInstanceWireRow r) { WriteString(p, r.ZdoId, 256); p.Write(r.PrefabHash); WriteString(p, r.Prefab, 512); p.Write(r.Owner); p.Write(r.Mutations); p.Write(r.SentCount); p.Write(r.SentBytes); p.Write(r.ReceivedCount); p.Write(r.ReceivedBytes); p.Write(r.MaxSize); } private static NetworkZdoInstanceWireRow ReadZdoInstanceRow(ZPackage p) { return new NetworkZdoInstanceWireRow { ZdoId = ReadString(p, 256, "zdo id"), PrefabHash = p.ReadInt(), Prefab = ReadString(p, 512, "zdo prefab"), Owner = p.ReadLong(), Mutations = ReadNonNegativeLong(p, "zdo mutations"), SentCount = ReadNonNegativeLong(p, "zdo sent count"), SentBytes = ReadNonNegativeLong(p, "zdo sent bytes"), ReceivedCount = ReadNonNegativeLong(p, "zdo received count"), ReceivedBytes = ReadNonNegativeLong(p, "zdo received bytes"), MaxSize = ReadNonNegativeInt(p, "zdo max size") }; } private static void WriteZdoKeyRow(ZPackage p, NetworkZdoKeyWireRow r) { p.Write(r.KeyHash); WriteString(p, r.KeyName, 512); WriteString(p, r.ValueType, 128); WriteString(p, r.Prefab, 512); p.Write(r.Mutations); p.Write(r.AffectedZdos); } private static NetworkZdoKeyWireRow ReadZdoKeyRow(ZPackage p) { return new NetworkZdoKeyWireRow { KeyHash = p.ReadInt(), KeyName = ReadString(p, 512, "key name"), ValueType = ReadString(p, 128, "value type"), Prefab = ReadString(p, 512, "key prefab"), Mutations = ReadNonNegativeLong(p, "key mutations"), AffectedZdos = ReadNonNegativeLong(p, "affected zdos") }; } private static void WritePeerRow(ZPackage p, NetworkPeerWireRow r) { p.Write(r.PeerId); WriteString(p, r.Name, 512); WriteString(p, r.Host, 512); WriteString(p, r.Socket, 512); p.Write(r.Ready); p.Write(r.Ping); p.Write(r.LocalQuality); p.Write(r.RemoteQuality); p.Write(r.ReportedOutBytesPerSecond); p.Write(r.ReportedInBytesPerSecond); p.Write(r.SerializedOutBytesPerSecond); p.Write(r.SerializedInBytesPerSecond); p.Write(r.SendQueue); p.Write(r.MaximumSendQueue); p.Write(r.CurrentSendRate); p.Write(r.ActualInFlightBytes); p.Write(r.LastReceiveAge); p.Write(r.SendZdoMs); p.Write(r.CreateSyncListMs); p.Write(r.ZdosSent); p.Write(r.SyncCandidates); p.Write(r.SyncSelected); } private static NetworkPeerWireRow ReadPeerRow(ZPackage p) { return new NetworkPeerWireRow { PeerId = p.ReadLong(), Name = ReadString(p, 512, "peer name"), Host = ReadString(p, 512, "peer host"), Socket = ReadString(p, 512, "socket"), Ready = p.ReadBool(), Ping = p.ReadInt(), LocalQuality = p.ReadSingle(), RemoteQuality = p.ReadSingle(), ReportedOutBytesPerSecond = p.ReadSingle(), ReportedInBytesPerSecond = p.ReadSingle(), SerializedOutBytesPerSecond = p.ReadInt(), SerializedInBytesPerSecond = p.ReadInt(), SendQueue = p.ReadInt(), MaximumSendQueue = p.ReadInt(), CurrentSendRate = p.ReadInt(), ActualInFlightBytes = p.ReadLong(), LastReceiveAge = p.ReadSingle(), SendZdoMs = ReadNonNegativeDouble(p, "send zdo ms"), CreateSyncListMs = ReadNonNegativeDouble(p, "sync list ms"), ZdosSent = ReadNonNegativeInt(p, "zdos sent"), SyncCandidates = ReadNonNegativeInt(p, "sync candidates"), SyncSelected = ReadNonNegativeInt(p, "sync selected") }; } private static void WriteErrorRow(ZPackage p, NetworkRoutingErrorWireRow r) { WriteString(p, r.Kind, 512); WriteString(p, r.Rpc, 512); p.Write(r.MethodHash); WriteString(p, r.Component, 512); WriteString(p, r.Handler, 1024); WriteString(p, r.Prefab, 512); WriteString(p, r.Caller, 1024); WriteString(p, r.CallerMod, 512); p.Write(r.PeerId); WriteString(p, r.Peer, 512); WriteString(p, r.Target, 512); p.Write(r.Count); WriteString(p, r.LastDetails, 4096); } private static NetworkRoutingErrorWireRow ReadErrorRow(ZPackage p) { return new NetworkRoutingErrorWireRow { Kind = ReadString(p, 512, "error kind"), Rpc = ReadString(p, 512, "error rpc"), MethodHash = p.ReadInt(), Component = ReadString(p, 512, "error component"), Handler = ReadString(p, 1024, "error handler"), Prefab = ReadString(p, 512, "error prefab"), Caller = ReadString(p, 1024, "error caller"), CallerMod = ReadString(p, 512, "error caller mod"), PeerId = p.ReadLong(), Peer = ReadString(p, 512, "error peer"), Target = ReadString(p, 512, "error target"), Count = ReadNonNegativeLong(p, "error count"), LastDetails = ReadString(p, 4096, "error details") }; } private static void WriteString(ZPackage package, string value, int maximumLength) { package.Write(Limit(value, maximumLength)); } private static string ReadString(ZPackage package, int maximumLength, string field) { string text = package.ReadString() ?? string.Empty; if (text.Length > maximumLength) { throw new InvalidOperationException($"Network Profiler {field} exceeds {maximumLength} characters."); } return text; } private static string Limit(string value, int maximumLength) { if (value == null) { value = string.Empty; } return (value.Length <= maximumLength) ? value : value.Substring(0, maximumLength); } private static long ReadNonNegativeLong(ZPackage p, string field) { long num = p.ReadLong(); if (num < 0) { throw new InvalidOperationException("Network Profiler " + field + " is negative."); } return num; } private static int ReadNonNegativeInt(ZPackage p, string field) { int num = p.ReadInt(); if (num < 0) { throw new InvalidOperationException("Network Profiler " + field + " is negative."); } return num; } private static double ReadNonNegativeDouble(ZPackage p, string field) { double num = p.ReadDouble(); if (double.IsNaN(num) || double.IsInfinity(num) || num < 0.0) { throw new InvalidOperationException("Network Profiler " + field + " is invalid."); } return num; } } internal static class NetworkProfilerRpcBridge { private static ZRoutedRpc _registeredRpc; internal static void RegisterForCurrentNetwork() { ZRoutedRpc instance = ZRoutedRpc.instance; if (instance != null && instance != _registeredRpc) { _registeredRpc = instance; ValheimProfilerPlugin instance2 = ValheimProfilerPlugin.Instance; if (instance2?.NetworkProfilerService != null && instance.m_server) { instance.Register("shudnal.ValheimProfiler.Network.Request", (Action)instance2.NetworkProfilerService.HandleRequest); } if (instance2?.App?.NetworkProfiler != null && !instance.m_server) { instance.Register("shudnal.ValheimProfiler.Network.Response", (Action)instance2.App.NetworkProfiler.HandleResponse); } } } internal static void NetworkDestroyed() { _registeredRpc = null; NetworkProfilerTransport.Clear(); ValheimProfilerPlugin.Instance?.NetworkProfilerService?.OnNetworkDestroyed(); ValheimProfilerPlugin.Instance?.App?.NetworkProfiler?.OnNetworkDestroyed(); } } internal sealed class NetworkProfilerService { private sealed class Subscriber { internal long PeerId; } private sealed class SeenRpcHandler { } internal sealed class RpcRegistrationInfo { internal string Layer = string.Empty; internal string Name = string.Empty; internal int Hash; internal string Component = string.Empty; internal string Handler = string.Empty; internal string Mod = string.Empty; internal string Prefab = string.Empty; internal long Registrations; internal string Key = string.Empty; } private sealed class RpcAggregate { internal RpcRegistrationInfo Registration = new RpcRegistrationInfo(); internal long IncomingCalls; internal long LocalCalls; internal long OutgoingCalls; internal long IncomingBytes; internal long LocalBytes; internal long OutgoingBytes; internal long PhysicalSends; internal long PhysicalBytes; internal double HandlerMs; internal double MaxHandlerMs; internal int MaxPayloadBytes; internal long Errors; internal int Frame = -1; internal int CallsThisFrame; internal int MaxCallsPerFrame; internal void CountFrame(int frame) { if (Frame != frame) { Frame = frame; CallsThisFrame = 0; } CallsThisFrame++; if (CallsThisFrame > MaxCallsPerFrame) { MaxCallsPerFrame = CallsThisFrame; } } internal void ResetInterval() { IncomingCalls = 0L; LocalCalls = 0L; OutgoingCalls = 0L; IncomingBytes = 0L; LocalBytes = 0L; OutgoingBytes = 0L; PhysicalSends = 0L; PhysicalBytes = 0L; HandlerMs = 0.0; MaxHandlerMs = 0.0; MaxPayloadBytes = 0; Errors = 0L; Frame = -1; CallsThisFrame = 0; MaxCallsPerFrame = 0; } } private sealed class ZdoAggregate { internal int PrefabHash; internal string Prefab = string.Empty; internal long Mutations; internal readonly HashSet UniqueChanged = new HashSet(); internal long SentCount; internal long SentBytes; internal long ReceivedCount; internal long ReceivedBytes; internal double SerializeMs; internal double DeserializeMs; internal long SizeSamples; internal long SizeTotal; internal int MaxSize; internal long Creates; internal long Destroys; internal long OwnershipChanges; internal void ResetInterval() { Mutations = 0L; UniqueChanged.Clear(); SentCount = 0L; SentBytes = 0L; ReceivedCount = 0L; ReceivedBytes = 0L; SerializeMs = 0.0; DeserializeMs = 0.0; SizeSamples = 0L; SizeTotal = 0L; MaxSize = 0; Creates = 0L; Destroys = 0L; OwnershipChanges = 0L; } } private readonly struct ZdoKeyId : IEquatable { internal readonly int PrefabHash; internal readonly int KeyHash; internal readonly string ValueType; internal ZdoKeyId(int prefabHash, int keyHash, string valueType) { PrefabHash = prefabHash; KeyHash = keyHash; ValueType = valueType ?? string.Empty; } public bool Equals(ZdoKeyId other) { return PrefabHash == other.PrefabHash && KeyHash == other.KeyHash && string.Equals(ValueType, other.ValueType, StringComparison.Ordinal); } public override bool Equals(object obj) { return obj is ZdoKeyId other && Equals(other); } public override int GetHashCode() { return (((PrefabHash * 397) ^ KeyHash) * 397) ^ StringComparer.Ordinal.GetHashCode(ValueType); } } private sealed class ZdoInstanceAggregate { internal ZDOID Id; internal int PrefabHash; internal string Prefab = string.Empty; internal long Owner; internal long Mutations; internal long SentCount; internal long SentBytes; internal long ReceivedCount; internal long ReceivedBytes; internal int MaxSize; } private sealed class ZdoKeyAggregate { internal int Hash; internal string Name = string.Empty; internal string ValueType = string.Empty; internal string Prefab = string.Empty; internal long Mutations; internal readonly HashSet Affected = new HashSet(); internal void ResetInterval() { Mutations = 0L; Affected.Clear(); } } private sealed class PeerAggregate { internal long PeerId; internal int MaximumSendQueue; internal double SendZdoMs; internal double CreateSyncListMs; internal int ZdosSent; internal int SyncCandidates; internal int SyncSelected; internal int PreviousSentData; internal int PreviousRecvData; internal bool CountersInitialized; internal void ResetInterval() { SendZdoMs = 0.0; CreateSyncListMs = 0.0; ZdosSent = 0; SyncCandidates = 0; SyncSelected = 0; } } private sealed class RoutingErrorAggregate { internal NetworkRoutingErrorWireRow Row = new NetworkRoutingErrorWireRow(); } private const int MaxSubscribers = 8; private const int MaxRpcRows = 600; private const int MaxZdoRows = 400; private const int MaxZdoInstanceRows = 500; private const int MaxTrackedZdoInstancesPerInterval = 10000; private const int MaxKeyRows = 400; private const int MaxTrackedZdoKeyAggregates = 4096; private const int MaxErrorRows = 400; private const int MaxTrackedRoutingErrors = 2048; private const int MaxKnownKeyNames = 4096; private static readonly TimeSpan SnapshotInterval = TimeSpan.FromSeconds(1.0); private static readonly TimeSpan SubscriberValidationInterval = TimeSpan.FromSeconds(1.0); private static readonly TimeSpan RegistryScanInterval = TimeSpan.FromSeconds(15.0); private readonly object _sync = new object(); private readonly Harmony _harmony; private readonly Dictionary _subscribers = new Dictionary(); private readonly Dictionary _rpc = new Dictionary(StringComparer.Ordinal); private readonly Dictionary> _rpcByHash = new Dictionary>(); private readonly Dictionary _zdo = new Dictionary(); private readonly Dictionary _zdoInstances = new Dictionary(); private readonly Dictionary _zdoKeys = new Dictionary(); private readonly Dictionary _peers = new Dictionary(); private readonly Dictionary _errors = new Dictionary(StringComparer.Ordinal); private readonly Dictionary _assemblyMods = new Dictionary(); private readonly Dictionary _knownKeyNames = new Dictionary(); private readonly Dictionary _knownRpcNames = new Dictionary(); private readonly Dictionary _prefabNames = new Dictionary(); private readonly ConditionalWeakTable _seenRpcHandlers = new ConditionalWeakTable(); private readonly string _sessionId = Guid.NewGuid().ToString("N"); private readonly List _compatibilityWarnings = new List(); private string _compatibilitySummary = "Vanilla network stack detected."; private DateTime _nextSnapshotUtc; private DateTime _nextSubscriberValidationUtc; private DateTime _nextRegistryScanUtc; private long _snapshotSequence; private long _untrackedZdoKeyMutations; private long _collapsedRoutingErrors; private volatile bool _collecting; private bool _shutdown; internal string SessionId => _sessionId; internal bool IsCollecting => _collecting; internal NetworkProfilerService() { //IL_00f5: Unknown result type (might be due to invalid IL or missing references) //IL_00ff: Expected O, but got Unknown BuildAssemblyMap(); BuildKnownZdoKeyMap(); DetectNetworkMods(); _harmony = new Harmony("shudnal.ValheimProfiler.NetworkProfiler.Server"); try { NetworkProfilerInstrumentation.Install(this, _harmony); } catch (Exception ex) { ReportInstrumentationWarning("Network instrumentation initialization failed: " + ex.GetType().Name + ": " + ex.Message); } } internal void Update() { if (_shutdown) { return; } DateTime utcNow = DateTime.UtcNow; if (utcNow >= _nextSubscriberValidationUtc) { _nextSubscriberValidationUtc = utcNow + SubscriberValidationInterval; ValidateSubscribers(); } if (_subscribers.Count > 0 && utcNow >= _nextRegistryScanUtc) { _nextRegistryScanUtc = utcNow + RegistryScanInterval; ScanRpcRegistrations(); } if (utcNow < _nextSnapshotUtc) { return; } _nextSnapshotUtc = utcNow + SnapshotInterval; if (_subscribers.Count != 0) { NetworkProfilerResponse response = BuildSnapshot("Dedicated-server network snapshot.", includeRegistry: false); long[] array = _subscribers.Keys.ToArray(); foreach (long target in array) { Send(target, response); } } } internal void HandleRequest(long sender, ZPackage envelope) { try { ZPackage payload; string error; switch (NetworkProfilerTransport.TryReceive(sender, envelope, out payload, out error)) { case NetworkProfilerTransportReceiveResult.WaitingForFragments: return; case NetworkProfilerTransportReceiveResult.Rejected: SendError(sender, "Invalid Network Profiler transport payload: " + error); return; } if (payload.Size() > 8192) { SendError(sender, "Oversized Network Profiler request."); return; } NetworkProfilerProtocol.ReadRequest(payload, out var version, out var kind); if (version != 1) { SendError(sender, $"Unsupported Network Profiler protocol {version}; server uses {1}."); return; } if (kind == NetworkProfilerRequestKind.Probe) { SendCapabilities(sender); return; } if (!IsAdmin(sender)) { _subscribers.Remove(sender); _collecting = _subscribers.Count > 0; SendError(sender, "Network Profiler access denied. The connected peer is not a server administrator."); return; } switch (kind) { case NetworkProfilerRequestKind.Subscribe: { if (!_subscribers.ContainsKey(sender) && _subscribers.Count >= 8) { SendError(sender, $"Network Profiler subscriber limit ({8}) reached."); break; } bool collecting = _collecting; _subscribers[sender] = new Subscriber { PeerId = sender }; BuildAssemblyMap(); DetectNetworkMods(); ScanRpcRegistrations(); if (!collecting) { ResetIntervalMetrics(); } _collecting = true; if (!collecting) { InitializePeerCounters(); } _nextSnapshotUtc = DateTime.UtcNow + SnapshotInterval; Send(sender, BuildSnapshot("Subscribed to the dedicated-server Network Profiler. The first complete one-second interval follows.", includeRegistry: true, includeDynamic: false, consumeInterval: false)); break; } case NetworkProfilerRequestKind.Unsubscribe: _subscribers.Remove(sender); _collecting = _subscribers.Count > 0; SendStatus(sender, "Unsubscribed from Network Profiler updates."); break; case NetworkProfilerRequestKind.Reset: ResetMetrics(); InitializePeerCounters(); Send(sender, BuildSnapshot("Network Profiler interval and error metrics were reset.", includeRegistry: true, includeDynamic: false, consumeInterval: false)); break; default: SendError(sender, $"Unknown Network Profiler request {kind}."); break; } } catch (Exception ex) { SendError(sender, "Invalid Network Profiler request: " + ex.GetType().Name + ": " + ex.Message); } } internal void OnNetworkDestroyed() { _subscribers.Clear(); _collecting = false; } internal void Shutdown() { if (_shutdown) { return; } _shutdown = true; _subscribers.Clear(); _collecting = false; NetworkProfilerInstrumentation.Uninstall(this); try { _harmony.UnpatchSelf(); } catch { } } internal void ReportInstrumentationWarning(string warning) { if (string.IsNullOrWhiteSpace(warning)) { return; } lock (_sync) { string item = "Instrumentation: " + warning; if (!_compatibilityWarnings.Contains(item)) { _compatibilityWarnings.Add(item); } if (_compatibilitySummary.IndexOf("instrumentation warning", StringComparison.OrdinalIgnoreCase) < 0) { _compatibilitySummary += " One or more instrumentation warnings were detected; see Help."; } } } internal string ResolveMod(Assembly assembly) { if (assembly == null) { return string.Empty; } lock (_sync) { if (_assemblyMods.TryGetValue(assembly, out var value)) { return value; } } return assembly.GetName().Name ?? string.Empty; } internal void RegisterRpc(object handlerObject, string layer, string name, Delegate handler, string prefab, int? explicitHash = null) { if (string.IsNullOrEmpty(name)) { return; } int num = explicitHash ?? StringExtensionMethods.GetStableHashCode(name); lock (_sync) { _knownRpcNames[num] = name; } if (IsInternalProfilerRpcName(name) || IsInternalProfilerRpcHash(num) || IsRoutedCarrier(layer, name)) { return; } if (handlerObject != null) { lock (_sync) { if (_seenRpcHandlers.TryGetValue(handlerObject, out var _)) { return; } _seenRpcHandlers.Add(handlerObject, new SeenRpcHandler()); } } Type type = handler?.Target?.GetType(); Type type2 = ((handler?.Target is Component) ? type : (handler?.Method?.DeclaringType ?? type)); string component = type2?.FullName ?? string.Empty; string handler2 = ((handler?.Method == null) ? string.Empty : (handler.Method.DeclaringType?.FullName + "." + handler.Method.Name)); string mod = ResolveMod(handler?.Method?.DeclaringType?.Assembly ?? type2?.Assembly); RpcRegistrationInfo registration = new RpcRegistrationInfo { Layer = (layer ?? string.Empty), Name = name, Hash = num, Component = component, Handler = handler2, Mod = mod, Prefab = (prefab ?? string.Empty), Registrations = 1L }; registration.Key = RpcKey(registration); lock (_sync) { string key = registration.Key; if (!_rpc.TryGetValue(key, out var value2)) { value2 = new RpcAggregate { Registration = registration }; _rpc[key] = value2; } else { value2.Registration.Registrations++; } if (!_rpcByHash.TryGetValue(num, out var value3)) { value3 = new List(); _rpcByHash[num] = value3; } if (!value3.Any((RpcRegistrationInfo item) => item.Layer == registration.Layer && item.Handler == registration.Handler && item.Prefab == registration.Prefab)) { value3.Add(registration); } } NetworkProfilerInstrumentation.BindHandler(handlerObject, registration); } internal void RecordRpcHandler(RpcRegistrationInfo registration, long sender, int payloadBytes, double elapsedMs) { if (!_collecting || registration == null) { return; } lock (_sync) { RpcAggregate rpcAggregate = GetRpcAggregate(registration); if (sender != 0L && ZRoutedRpc.instance != null && sender == ZRoutedRpc.instance.m_id) { rpcAggregate.LocalCalls++; rpcAggregate.LocalBytes += Math.Max(0, payloadBytes); } else { rpcAggregate.IncomingCalls++; rpcAggregate.IncomingBytes += Math.Max(0, payloadBytes); } rpcAggregate.HandlerMs += Math.Max(0.0, elapsedMs); if (elapsedMs > rpcAggregate.MaxHandlerMs) { rpcAggregate.MaxHandlerMs = elapsedMs; } if (payloadBytes > rpcAggregate.MaxPayloadBytes) { rpcAggregate.MaxPayloadBytes = payloadBytes; } rpcAggregate.CountFrame(Time.frameCount); } } internal void RecordRpcHandlerException(RpcRegistrationInfo registration, long sender, Exception exception) { //IL_0060: Unknown result type (might be due to invalid IL or missing references) if (_collecting && registration != null && exception != null) { Exception ex = ((exception is TargetInvocationException { InnerException: not null } ex2) ? ex2.InnerException : exception); string details = ex.GetType().FullName + ": " + ex.Message; RecordRoutingError("Handler exception", registration.Hash, sender, ZDOID.None, details, registration.Component, registration.Handler, registration.Prefab); } } internal void RecordDirectOutgoing(string name, int payloadBytes, ZRpc rpc) { if (!_collecting || IsInternalProfilerRpcName(name)) { return; } RpcRegistrationInfo rpcRegistrationInfo = FindRegistration((name != null) ? StringExtensionMethods.GetStableHashCode(name) : 0, "Direct"); if (rpcRegistrationInfo == null) { rpcRegistrationInfo = UnknownRegistration("Direct", name, string.Empty); } lock (_sync) { RpcAggregate rpcAggregate = GetRpcAggregate(rpcRegistrationInfo); rpcAggregate.OutgoingCalls++; rpcAggregate.OutgoingBytes += Math.Max(0, payloadBytes); rpcAggregate.PhysicalSends++; rpcAggregate.PhysicalBytes += Math.Max(0, payloadBytes); if (payloadBytes > rpcAggregate.MaxPayloadBytes) { rpcAggregate.MaxPayloadBytes = payloadBytes; } rpcAggregate.CountFrame(Time.frameCount); } } internal void RecordRoutedOutgoing(int hash, int logicalBytes, int physicalSends, int physicalBytes, ZDOID targetZdo) { //IL_0047: Unknown result type (might be due to invalid IL or missing references) if (!_collecting || IsInternalProfilerRpcHash(hash)) { return; } string layer = (((ZDOID)(ref targetZdo)).IsNone() ? "Global" : "Object"); string prefab = (((ZDOID)(ref targetZdo)).IsNone() ? string.Empty : ResolveTargetPrefabName(targetZdo)); RpcRegistrationInfo registration = FindRegistration(hash, layer, prefab) ?? FindRegistration(hash, layer) ?? FindRegistration(hash, null) ?? UnknownRegistration(layer, ResolveRpcName(hash), prefab, hash); lock (_sync) { RpcAggregate rpcAggregate = GetRpcAggregate(registration); rpcAggregate.OutgoingCalls++; rpcAggregate.OutgoingBytes += Math.Max(0, logicalBytes); rpcAggregate.PhysicalSends += Math.Max(0, physicalSends); rpcAggregate.PhysicalBytes += Math.Max(0, physicalBytes); if (logicalBytes > rpcAggregate.MaxPayloadBytes) { rpcAggregate.MaxPayloadBytes = logicalBytes; } rpcAggregate.CountFrame(Time.frameCount); } } internal unsafe void RecordRoutingError(string kind, int hash, long peerId, ZDOID targetZdo, string details, string component = "", string handler = "", string prefab = "", string caller = "", string callerMod = "") { //IL_005a: Unknown result type (might be due to invalid IL or missing references) if (!_collecting || IsInternalProfilerRpcHash(hash)) { return; } string text = ResolveRpcName(hash); string peer = DescribePeer(peerId); string text2 = (((ZDOID)(ref targetZdo)).IsNone() ? string.Empty : ((object)(*(ZDOID*)(&targetZdo))/*cast due to .constrained prefix*/).ToString()); RpcRegistrationInfo rpcRegistrationInfo = FindRegistrationForError(hash, kind, targetZdo, component, handler, prefab); component = ((!string.IsNullOrEmpty(component)) ? component : (rpcRegistrationInfo?.Component ?? string.Empty)); handler = ((!string.IsNullOrEmpty(handler)) ? handler : (rpcRegistrationInfo?.Handler ?? string.Empty)); prefab = ((!string.IsNullOrEmpty(prefab)) ? prefab : (rpcRegistrationInfo?.Prefab ?? string.Empty)); string key = kind + "|" + hash + "|" + peerId + "|" + text2 + "|" + component + "|" + handler + "|" + prefab + "|" + caller; lock (_sync) { if (!_errors.TryGetValue(key, out var value)) { if (_errors.Count >= 2048) { if (!_errors.TryGetValue("__overflow__", out value)) { value = new RoutingErrorAggregate { Row = new NetworkRoutingErrorWireRow { Kind = "Tracking limit reached", Rpc = "multiple", Count = 0L, LastDetails = "Additional unique routing-error identities were collapsed to keep memory bounded." } }; _errors["__overflow__"] = value; } _collapsedRoutingErrors++; value.Row.Count++; value.Row.LastDetails = $"Collapsed {_collapsedRoutingErrors} additional unique routing-error identities."; RpcRegistrationInfo registration = rpcRegistrationInfo ?? UnknownRegistration("Unknown", text, prefab, hash); GetRpcAggregate(registration).Errors++; return; } value = new RoutingErrorAggregate { Row = new NetworkRoutingErrorWireRow { Kind = (kind ?? string.Empty), Rpc = text, MethodHash = hash, Component = component, Handler = handler, Prefab = prefab, Caller = (caller ?? string.Empty), CallerMod = (callerMod ?? string.Empty), PeerId = peerId, Peer = peer, Target = text2, Count = 0L } }; _errors[key] = value; } value.Row.Count++; value.Row.LastDetails = details ?? string.Empty; RpcRegistrationInfo registration2 = rpcRegistrationInfo ?? UnknownRegistration("Unknown", text, prefab, hash); GetRpcAggregate(registration2).Errors++; } } internal void RememberKeyName(int hash, string name) { if (!_collecting || string.IsNullOrEmpty(name)) { return; } lock (_sync) { if (_knownKeyNames.ContainsKey(hash) || _knownKeyNames.Count < 4096) { _knownKeyNames[hash] = name; } } } internal void RecordZdoMutation(ZDO zdo, int keyHash, string valueType) { //IL_0069: Unknown result type (might be due to invalid IL or missing references) //IL_0163: Unknown result type (might be due to invalid IL or missing references) if (!_collecting || zdo == null) { return; } int prefab = zdo.GetPrefab(); string text = ResolvePrefabName(prefab); lock (_sync) { ZdoAggregate zdoAggregate = GetZdoAggregate(prefab, text); zdoAggregate.Mutations++; zdoAggregate.UniqueChanged.Add(zdo.m_uid); ZdoInstanceAggregate zdoInstanceAggregate = GetZdoInstanceAggregate(zdo, prefab, text); if (zdoInstanceAggregate != null) { zdoInstanceAggregate.Mutations++; } string value; string name = ((keyHash == 0) ? valueType : (_knownKeyNames.TryGetValue(keyHash, out value) ? value : keyHash.ToString())); ZdoKeyId key = new ZdoKeyId(prefab, keyHash, valueType); if (!_zdoKeys.TryGetValue(key, out var value2)) { if (_zdoKeys.Count >= 4096) { _untrackedZdoKeyMutations++; return; } value2 = new ZdoKeyAggregate { Hash = keyHash, Name = name, ValueType = (valueType ?? string.Empty), Prefab = text }; _zdoKeys[key] = value2; } value2.Mutations++; value2.Affected.Add(zdo.m_uid); } } internal void RecordZdoOwnershipChange(ZDO zdo) { if (!_collecting || zdo == null) { return; } lock (_sync) { GetZdoAggregate(zdo.GetPrefab(), ResolvePrefabName(zdo.GetPrefab())).OwnershipChanges++; } } internal void RecordZdoCreate(ZDO zdo) { if (!_collecting || zdo == null) { return; } lock (_sync) { GetZdoAggregate(zdo.GetPrefab(), ResolvePrefabName(zdo.GetPrefab())).Creates++; } } internal void RecordZdoDestroy(ZDO zdo) { if (!_collecting || zdo == null) { return; } lock (_sync) { GetZdoAggregate(zdo.GetPrefab(), ResolvePrefabName(zdo.GetPrefab())).Destroys++; } } internal void RecordZdoSerialize(ZDO zdo, int bytes, double elapsedMs, long peerId, bool receiving) { if (!_collecting || zdo == null) { return; } lock (_sync) { ZdoAggregate zdoAggregate = GetZdoAggregate(zdo.GetPrefab(), ResolvePrefabName(zdo.GetPrefab())); ZdoInstanceAggregate zdoInstanceAggregate = GetZdoInstanceAggregate(zdo, zdo.GetPrefab(), zdoAggregate.Prefab); if (receiving) { zdoAggregate.ReceivedCount++; zdoAggregate.ReceivedBytes += Math.Max(0, bytes); zdoAggregate.DeserializeMs += Math.Max(0.0, elapsedMs); if (zdoInstanceAggregate != null) { zdoInstanceAggregate.ReceivedCount++; zdoInstanceAggregate.ReceivedBytes += Math.Max(0, bytes); } } else { zdoAggregate.SentCount++; zdoAggregate.SentBytes += Math.Max(0, bytes); zdoAggregate.SerializeMs += Math.Max(0.0, elapsedMs); if (zdoInstanceAggregate != null) { zdoInstanceAggregate.SentCount++; zdoInstanceAggregate.SentBytes += Math.Max(0, bytes); } } if (zdoInstanceAggregate != null && bytes > zdoInstanceAggregate.MaxSize) { zdoInstanceAggregate.MaxSize = bytes; } zdoAggregate.SizeSamples++; zdoAggregate.SizeTotal += Math.Max(0, bytes); if (bytes > zdoAggregate.MaxSize) { zdoAggregate.MaxSize = bytes; } } } internal void RecordSendZdos(long peerId, double elapsedMs, int zdoCount, int queue) { if (!_collecting) { return; } lock (_sync) { PeerAggregate peerAggregate = GetPeerAggregate(peerId); peerAggregate.SendZdoMs += Math.Max(0.0, elapsedMs); peerAggregate.ZdosSent += Math.Max(0, zdoCount); if (queue > peerAggregate.MaximumSendQueue) { peerAggregate.MaximumSendQueue = queue; } } } internal void RecordCreateSyncList(long peerId, double elapsedMs, int candidates, int selected) { if (!_collecting) { return; } lock (_sync) { PeerAggregate peerAggregate = GetPeerAggregate(peerId); peerAggregate.CreateSyncListMs += Math.Max(0.0, elapsedMs); peerAggregate.SyncCandidates += Math.Max(0, candidates); peerAggregate.SyncSelected += Math.Max(0, selected); } } private static bool IsInternalProfilerRpcName(string name) { return !string.IsNullOrEmpty(name) && name.StartsWith("shudnal.ValheimProfiler.", StringComparison.Ordinal); } private static bool IsRoutedCarrier(string layer, string name) { return string.Equals(layer, "Direct", StringComparison.OrdinalIgnoreCase) && string.Equals(name, "RoutedRPC", StringComparison.Ordinal); } private bool IsInternalProfilerRpcHash(int hash) { if (hash == StringExtensionMethods.GetStableHashCode("shudnal.ValheimProfiler.Network.Request") || hash == StringExtensionMethods.GetStableHashCode("shudnal.ValheimProfiler.Network.Response") || hash == StringExtensionMethods.GetStableHashCode("shudnal.ValheimProfiler.ServerLog.Request") || hash == StringExtensionMethods.GetStableHashCode("shudnal.ValheimProfiler.ServerLog.Response")) { return true; } lock (_sync) { string value; return _knownRpcNames.TryGetValue(hash, out value) && IsInternalProfilerRpcName(value); } } internal string ResolveRpcName(int hash) { lock (_sync) { string value; return _knownRpcNames.TryGetValue(hash, out value) ? value : "Unknown RPC"; } } internal RpcRegistrationInfo FindRegistration(int hash, string layer, string prefab = null) { lock (_sync) { if (!_rpcByHash.TryGetValue(hash, out var value) || value.Count == 0) { return null; } if (!string.IsNullOrEmpty(layer) && !string.IsNullOrEmpty(prefab)) { RpcRegistrationInfo rpcRegistrationInfo = value.FirstOrDefault((RpcRegistrationInfo item) => string.Equals(item.Layer, layer, StringComparison.OrdinalIgnoreCase) && string.Equals(item.Prefab, prefab, StringComparison.Ordinal)); if (rpcRegistrationInfo != null) { return rpcRegistrationInfo; } } if (string.IsNullOrEmpty(layer)) { return value[0]; } return value.FirstOrDefault((RpcRegistrationInfo item) => string.Equals(item.Layer, layer, StringComparison.OrdinalIgnoreCase)) ?? value[0]; } } private RpcRegistrationInfo FindRegistrationForError(int hash, string kind, ZDOID targetZdo, string component, string handler, string prefab) { lock (_sync) { if (!_rpcByHash.TryGetValue(hash, out var value) || value.Count == 0) { return null; } if (!string.IsNullOrEmpty(handler) || !string.IsNullOrEmpty(component) || !string.IsNullOrEmpty(prefab)) { RpcRegistrationInfo rpcRegistrationInfo = value.FirstOrDefault((RpcRegistrationInfo item) => (string.IsNullOrEmpty(handler) || string.Equals(item.Handler, handler, StringComparison.Ordinal)) && (string.IsNullOrEmpty(component) || string.Equals(item.Component, component, StringComparison.Ordinal)) && (string.IsNullOrEmpty(prefab) || string.Equals(item.Prefab, prefab, StringComparison.Ordinal))); if (rpcRegistrationInfo != null) { return rpcRegistrationInfo; } } string layer = ((!((ZDOID)(ref targetZdo)).IsNone()) ? "Object" : ((kind != null && kind.IndexOf("Direct", StringComparison.OrdinalIgnoreCase) >= 0) ? "Direct" : "Global")); return value.FirstOrDefault((RpcRegistrationInfo item) => string.Equals(item.Layer, layer, StringComparison.OrdinalIgnoreCase)) ?? value[0]; } } private NetworkProfilerResponse BuildSnapshot(string status, bool includeRegistry, bool includeDynamic = true, bool consumeInterval = true) { NetworkProfilerResponse networkProfilerResponse = new NetworkProfilerResponse { Kind = NetworkProfilerResponseKind.Snapshot, SessionId = _sessionId, Status = status, ServerVersion = "1.0.0", Authorized = true, SnapshotSequence = ++_snapshotSequence, FullRegistry = includeRegistry, CompatibilitySummary = _compatibilitySummary }; networkProfilerResponse.CompatibilityWarnings.AddRange(_compatibilityWarnings); lock (_sync) { RpcAggregate[] array = (from item in _rpc.Values where item.IncomingCalls + item.LocalCalls + item.OutgoingCalls + item.Errors > 0 || (includeRegistry && item.Registration.Registrations > 0) orderby item.HandlerMs + (double)item.PhysicalBytes / 1024.0 descending select item).Take(600).ToArray(); RpcAggregate[] array2 = array; foreach (RpcAggregate rpcAggregate in array2) { long num2 = rpcAggregate.IncomingCalls + rpcAggregate.LocalCalls; networkProfilerResponse.RpcRows.Add(new NetworkRpcWireRow { Layer = rpcAggregate.Registration.Layer, Name = ((_knownRpcNames.TryGetValue(rpcAggregate.Registration.Hash, out var value) && !string.Equals(value, "Unknown RPC", StringComparison.OrdinalIgnoreCase)) ? value : rpcAggregate.Registration.Name), MethodHash = rpcAggregate.Registration.Hash, Component = rpcAggregate.Registration.Component, Handler = rpcAggregate.Registration.Handler, Mod = rpcAggregate.Registration.Mod, Prefab = rpcAggregate.Registration.Prefab, Registrations = rpcAggregate.Registration.Registrations, IncomingCalls = (includeDynamic ? rpcAggregate.IncomingCalls : 0), LocalCalls = (includeDynamic ? rpcAggregate.LocalCalls : 0), OutgoingCalls = (includeDynamic ? rpcAggregate.OutgoingCalls : 0), IncomingBytes = (includeDynamic ? rpcAggregate.IncomingBytes : 0), LocalBytes = (includeDynamic ? rpcAggregate.LocalBytes : 0), OutgoingBytes = (includeDynamic ? rpcAggregate.OutgoingBytes : 0), PhysicalSends = (includeDynamic ? rpcAggregate.PhysicalSends : 0), PhysicalBytes = (includeDynamic ? rpcAggregate.PhysicalBytes : 0), HandlerMs = (includeDynamic ? rpcAggregate.HandlerMs : 0.0), AverageHandlerMs = ((includeDynamic && num2 > 0) ? (rpcAggregate.HandlerMs / (double)num2) : 0.0), MaxHandlerMs = (includeDynamic ? rpcAggregate.MaxHandlerMs : 0.0), MaxPayloadBytes = (includeDynamic ? rpcAggregate.MaxPayloadBytes : 0), MaxCallsPerFrame = (includeDynamic ? rpcAggregate.MaxCallsPerFrame : 0), Errors = (includeDynamic ? rpcAggregate.Errors : 0) }); } if (consumeInterval) { foreach (RpcAggregate value3 in _rpc.Values) { value3.ResetInterval(); } } if (!includeDynamic) { return networkProfilerResponse; } ZdoAggregate[] array3 = (from item in _zdo.Values where item.Mutations + item.SentCount + item.ReceivedCount + item.Creates + item.Destroys + item.OwnershipChanges > 0 orderby item.SentBytes + item.ReceivedBytes descending select item).Take(400).ToArray(); ZdoAggregate[] array4 = array3; foreach (ZdoAggregate zdoAggregate in array4) { networkProfilerResponse.ZdoRows.Add(new NetworkZdoWireRow { PrefabHash = zdoAggregate.PrefabHash, Prefab = zdoAggregate.Prefab, Mutations = zdoAggregate.Mutations, UniqueChanged = zdoAggregate.UniqueChanged.Count, SentCount = zdoAggregate.SentCount, SentBytes = zdoAggregate.SentBytes, ReceivedCount = zdoAggregate.ReceivedCount, ReceivedBytes = zdoAggregate.ReceivedBytes, SerializeMs = zdoAggregate.SerializeMs, DeserializeMs = zdoAggregate.DeserializeMs, AverageSize = (int)((zdoAggregate.SizeSamples > 0) ? Math.Min(2147483647L, zdoAggregate.SizeTotal / zdoAggregate.SizeSamples) : 0), MaxSize = zdoAggregate.MaxSize, Creates = zdoAggregate.Creates, Destroys = zdoAggregate.Destroys, OwnershipChanges = zdoAggregate.OwnershipChanges }); } if (consumeInterval) { foreach (ZdoAggregate value4 in _zdo.Values) { value4.ResetInterval(); } } foreach (ZdoInstanceAggregate item in _zdoInstances.Values.OrderByDescending((ZdoInstanceAggregate item) => item.SentBytes + item.ReceivedBytes + item.Mutations * 16).Take(500)) { networkProfilerResponse.ZdoInstanceRows.Add(new NetworkZdoInstanceWireRow { ZdoId = ((object)Unsafe.As(ref item.Id)/*cast due to .constrained prefix*/).ToString(), PrefabHash = item.PrefabHash, Prefab = item.Prefab, Owner = item.Owner, Mutations = item.Mutations, SentCount = item.SentCount, SentBytes = item.SentBytes, ReceivedCount = item.ReceivedCount, ReceivedBytes = item.ReceivedBytes, MaxSize = item.MaxSize }); } if (consumeInterval) { _zdoInstances.Clear(); } ZdoKeyAggregate[] array5 = (from item in _zdoKeys.Values where item.Mutations > 0 orderby item.Mutations descending select item).Take(400).ToArray(); ZdoKeyAggregate[] array6 = array5; foreach (ZdoKeyAggregate zdoKeyAggregate in array6) { networkProfilerResponse.ZdoKeyRows.Add(new NetworkZdoKeyWireRow { KeyHash = zdoKeyAggregate.Hash, KeyName = zdoKeyAggregate.Name, ValueType = zdoKeyAggregate.ValueType, Prefab = zdoKeyAggregate.Prefab, Mutations = zdoKeyAggregate.Mutations, AffectedZdos = zdoKeyAggregate.Affected.Count }); } if (_untrackedZdoKeyMutations > 0) { networkProfilerResponse.ZdoKeyRows.Add(new NetworkZdoKeyWireRow { KeyName = "(tracking limit reached)", ValueType = "Multiple", Prefab = "Multiple", Mutations = _untrackedZdoKeyMutations }); } if (consumeInterval) { foreach (ZdoKeyAggregate value5 in _zdoKeys.Values) { value5.ResetInterval(); } _untrackedZdoKeyMutations = 0L; } SamplePeers(networkProfilerResponse, consumeInterval); foreach (RoutingErrorAggregate item2 in _errors.Values.OrderByDescending((RoutingErrorAggregate item) => item.Row.Count).Take(400)) { if (_knownRpcNames.TryGetValue(item2.Row.MethodHash, out var value2) && !string.Equals(value2, "Unknown RPC", StringComparison.OrdinalIgnoreCase)) { item2.Row.Rpc = value2; } networkProfilerResponse.ErrorRows.Add(item2.Row); } } return networkProfilerResponse; } private void SamplePeers(NetworkProfilerResponse response, bool consumeInterval) { List list; try { ZNet instance = ZNet.instance; list = ((instance != null) ? instance.GetPeers() : null); } catch { list = null; } if (list == null) { return; } for (int i = 0; i < list.Count; i++) { ZNetPeer peer = list[i]; if (peer?.m_socket != null && peer.m_rpc != null) { long uid = peer.m_uid; PeerAggregate peerAggregate = GetPeerAggregate(uid); int serializedOutBytesPerSecond = 0; int serializedInBytesPerSecond = 0; if (peerAggregate.CountersInitialized) { serializedOutBytesPerSecond = Math.Max(0, peer.m_rpc.m_sentData - peerAggregate.PreviousSentData); serializedInBytesPerSecond = Math.Max(0, peer.m_rpc.m_recvData - peerAggregate.PreviousRecvData); } peerAggregate.PreviousSentData = peer.m_rpc.m_sentData; peerAggregate.PreviousRecvData = peer.m_rpc.m_recvData; peerAggregate.CountersInitialized = true; int num = Safe(() => peer.m_socket.GetSendQueueSize(), -1); if (num > peerAggregate.MaximumSendQueue) { peerAggregate.MaximumSendQueue = num; } float localQuality = 0f; float remoteQuality = 0f; float val = 0f; float val2 = 0f; int ping = 0; try { peer.m_socket.GetConnectionQuality(ref localQuality, ref remoteQuality, ref ping, ref val, ref val2); } catch { } response.PeerRows.Add(new NetworkPeerWireRow { PeerId = uid, Name = (peer.m_playerName ?? string.Empty), Host = Safe(() => peer.m_socket.GetHostName(), string.Empty), Socket = DescribeSocket(peer.m_socket), Ready = peer.IsReady(), Ping = ping, LocalQuality = localQuality, RemoteQuality = remoteQuality, ReportedOutBytesPerSecond = Math.Max(0f, val), ReportedInBytesPerSecond = Math.Max(0f, val2), SerializedOutBytesPerSecond = serializedOutBytesPerSecond, SerializedInBytesPerSecond = serializedInBytesPerSecond, SendQueue = num, MaximumSendQueue = Math.Max(num, peerAggregate.MaximumSendQueue), CurrentSendRate = Safe(() => peer.m_socket.GetCurrentSendRate(), 0), ActualInFlightBytes = TryGetActualInFlight(peer.m_socket), LastReceiveAge = Math.Max(0f, peer.m_rpc.GetTimeSinceLastPing()), SendZdoMs = peerAggregate.SendZdoMs, CreateSyncListMs = peerAggregate.CreateSyncListMs, ZdosSent = peerAggregate.ZdosSent, SyncCandidates = peerAggregate.SyncCandidates, SyncSelected = peerAggregate.SyncSelected }); if (consumeInterval) { peerAggregate.ResetInterval(); } } } } private void SendCapabilities(long target) { bool flag = IsAdmin(target); Send(target, CreateStateResponse(NetworkProfilerResponseKind.Capabilities, target, flag ? "Dedicated-server Network Profiler is available. Subscribe to start private admin snapshots." : "Valheim Profiler is installed, but the connected peer is not a server administrator.")); } private void SendStatus(long target, string status) { Send(target, CreateStateResponse(NetworkProfilerResponseKind.Status, target, status)); } private void SendError(long target, string status) { Send(target, CreateStateResponse(NetworkProfilerResponseKind.Error, target, status)); } private NetworkProfilerResponse CreateStateResponse(NetworkProfilerResponseKind kind, long target, string status) { NetworkProfilerResponse networkProfilerResponse = new NetworkProfilerResponse { Kind = kind, SessionId = _sessionId, Status = (status ?? string.Empty), ServerVersion = "1.0.0", Authorized = IsAdmin(target), SnapshotSequence = _snapshotSequence, CompatibilitySummary = _compatibilitySummary }; networkProfilerResponse.CompatibilityWarnings.AddRange(_compatibilityWarnings); return networkProfilerResponse; } private static bool Send(long target, NetworkProfilerResponse response) { try { if (!TryCreateBoundedPayload(response, out var payload)) { return false; } string error; return NetworkProfilerTransport.Send(target, "shudnal.ValheimProfiler.Network.Response", payload, out error); } catch { return false; } } private static bool TryCreateBoundedPayload(NetworkProfilerResponse response, out ZPackage payload) { payload = NetworkProfilerProtocol.CreateResponse(response); if (payload.Size() <= 4194304) { return true; } if (!response.CompatibilityWarnings.Contains("Snapshot rows were trimmed to fit the Network Profiler payload limit.")) { response.CompatibilityWarnings.Add("Snapshot rows were trimmed to fit the Network Profiler payload limit."); } for (int i = 0; i < 128; i++) { if (!TrimLowestPriorityRows(response)) { return false; } payload = NetworkProfilerProtocol.CreateResponse(response); if (payload.Size() <= 4194304) { return true; } } return false; } private static bool TrimLowestPriorityRows(NetworkProfilerResponse response) { if (TrimTail(response.ZdoInstanceRows, 100)) { return true; } if (TrimTail(response.ZdoKeyRows, 100)) { return true; } if (TrimTail(response.RpcRows, 100)) { return true; } if (TrimTail(response.ZdoRows, 100)) { return true; } if (TrimTail(response.ErrorRows, 100)) { return true; } if (TrimTail(response.ZdoInstanceRows, 0)) { return true; } if (TrimTail(response.ZdoKeyRows, 0)) { return true; } if (TrimTail(response.RpcRows, 0)) { return true; } if (TrimTail(response.ZdoRows, 0)) { return true; } if (TrimTail(response.ErrorRows, 0)) { return true; } if (response.CompatibilityWarnings.Count > 1) { response.CompatibilityWarnings.RemoveAt(response.CompatibilityWarnings.Count - 2); return true; } return TrimTail(response.PeerRows, 0); } private static bool TrimTail(List rows, int minimumToKeep) { if (rows == null || rows.Count <= minimumToKeep) { return false; } int val = rows.Count - minimumToKeep; int num = Math.Max(1, Math.Min(val, Math.Max(1, rows.Count / 4))); rows.RemoveRange(rows.Count - num, num); return true; } private void ValidateSubscribers() { long[] array = _subscribers.Keys.ToArray(); foreach (long num in array) { ZRoutedRpc instance = ZRoutedRpc.instance; ZNetPeer val = ((instance != null) ? instance.GetPeer(num) : null); if (val == null || !val.IsReady() || !IsAdmin(num)) { _subscribers.Remove(num); } } _collecting = _subscribers.Count > 0; } private static bool IsAdmin(long sender) { try { ZRoutedRpc instance = ZRoutedRpc.instance; ZNetPeer val = ((instance != null) ? instance.GetPeer(sender) : null); object obj; if (val == null) { obj = null; } else { ISocket socket = val.m_socket; obj = ((socket != null) ? socket.GetHostName() : null); } string text = (string)obj; return (Object)(object)ZNet.instance != (Object)null && !string.IsNullOrEmpty(text) && ZNet.instance.IsAdmin(text); } catch { return false; } } private void InitializePeerCounters() { List list; try { ZNet instance = ZNet.instance; list = ((instance != null) ? instance.GetPeers() : null); } catch { list = null; } if (list == null) { return; } lock (_sync) { for (int i = 0; i < list.Count; i++) { ZNetPeer val = list[i]; if (val?.m_rpc != null) { PeerAggregate peerAggregate = GetPeerAggregate(val.m_uid); peerAggregate.PreviousSentData = val.m_rpc.m_sentData; peerAggregate.PreviousRecvData = val.m_rpc.m_recvData; peerAggregate.CountersInitialized = true; } } } } private void ResetIntervalMetrics() { lock (_sync) { foreach (RpcAggregate value in _rpc.Values) { value.ResetInterval(); } foreach (ZdoAggregate value2 in _zdo.Values) { value2.ResetInterval(); } foreach (ZdoKeyAggregate value3 in _zdoKeys.Values) { value3.ResetInterval(); } _zdoInstances.Clear(); foreach (PeerAggregate value4 in _peers.Values) { value4.ResetInterval(); } } } private void ResetMetrics() { ResetIntervalMetrics(); lock (_sync) { foreach (PeerAggregate value in _peers.Values) { value.MaximumSendQueue = 0; } _errors.Clear(); _collapsedRoutingErrors = 0L; } } private RpcAggregate GetRpcAggregate(RpcRegistrationInfo registration) { string key = (string.IsNullOrEmpty(registration.Key) ? (registration.Key = RpcKey(registration)) : registration.Key); if (!_rpc.TryGetValue(key, out var value)) { value = new RpcAggregate { Registration = registration }; _rpc[key] = value; } return value; } private static string RpcKey(RpcRegistrationInfo r) { return r.Layer + "|" + r.Hash + "|" + r.Component + "|" + r.Handler + "|" + r.Prefab; } private RpcRegistrationInfo UnknownRegistration(string layer, string name, string prefab, int? explicitHash = null) { RpcRegistrationInfo rpcRegistrationInfo = new RpcRegistrationInfo { Layer = (layer ?? "Unknown"), Name = (string.IsNullOrEmpty(name) ? "unknown" : name), Hash = (explicitHash ?? ((!string.IsNullOrEmpty(name)) ? StringExtensionMethods.GetStableHashCode(name) : 0)), Component = string.Empty, Handler = string.Empty, Mod = string.Empty, Prefab = (prefab ?? string.Empty), Registrations = 0L }; rpcRegistrationInfo.Key = RpcKey(rpcRegistrationInfo); return rpcRegistrationInfo; } private ZdoAggregate GetZdoAggregate(int hash, string name) { if (!_zdo.TryGetValue(hash, out var value)) { value = new ZdoAggregate { PrefabHash = hash, Prefab = (name ?? hash.ToString()) }; _zdo[hash] = value; } return value; } private ZdoInstanceAggregate GetZdoInstanceAggregate(ZDO zdo, int prefabHash, string prefab) { //IL_0017: 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_0063: Unknown result type (might be due to invalid IL or missing references) //IL_0095: Unknown result type (might be due to invalid IL or missing references) if (zdo == null) { return null; } if (_zdoInstances.TryGetValue(zdo.m_uid, out var value)) { value.Owner = zdo.GetOwner(); return value; } if (_zdoInstances.Count >= 10000) { return null; } value = new ZdoInstanceAggregate { Id = zdo.m_uid, PrefabHash = prefabHash, Prefab = (prefab ?? prefabHash.ToString()), Owner = zdo.GetOwner() }; _zdoInstances[zdo.m_uid] = value; return value; } private PeerAggregate GetPeerAggregate(long peerId) { if (!_peers.TryGetValue(peerId, out var value)) { value = new PeerAggregate { PeerId = peerId }; _peers[peerId] = value; } return value; } private string ResolvePrefabName(int hash) { lock (_sync) { if (_prefabNames.TryGetValue(hash, out var value)) { return value; } } string text = ((hash == 0) ? "None" : hash.ToString()); try { ZNetScene instance = ZNetScene.instance; GameObject val = ((instance != null) ? instance.GetPrefab(hash) : null); if ((Object)(object)val != (Object)null) { text = ((Object)val).name; } } catch { } lock (_sync) { _prefabNames[hash] = text; } return text; } private string ResolveTargetPrefabName(ZDOID targetZdo) { //IL_0022: Unknown result type (might be due to invalid IL or missing references) if (((ZDOID)(ref targetZdo)).IsNone()) { return string.Empty; } try { ZDOMan instance = ZDOMan.instance; ZDO val = ((instance != null) ? instance.GetZDO(targetZdo) : null); return (val == null) ? string.Empty : ResolvePrefabName(val.GetPrefab()); } catch { return string.Empty; } } private string DescribePeer(long peerId) { try { ZRoutedRpc instance = ZRoutedRpc.instance; ZNetPeer val = ((instance != null) ? instance.GetPeer(peerId) : null); if (val == null) { return peerId.ToString(); } string arg = (string.IsNullOrEmpty(val.m_playerName) ? "unnamed" : val.m_playerName); ISocket socket = val.m_socket; string arg2 = ((socket != null) ? socket.GetHostName() : null) ?? string.Empty; return $"{arg} ({peerId}, {arg2})"; } catch { return peerId.ToString(); } } private void ScanRpcRegistrations() { try { ZRoutedRpc instance = ZRoutedRpc.instance; if (instance != null) { foreach (KeyValuePair function in instance.m_functions) { RegisterScanned(function.Value, "Global", function.Key, string.Empty); } foreach (ZNetPeer peer in instance.m_peers) { if (peer?.m_rpc?.m_functions == null) { continue; } foreach (KeyValuePair function2 in peer.m_rpc.m_functions) { RegisterScanned(function2.Value, "Direct", function2.Key, string.Empty); } } } ZNetScene instance2 = ZNetScene.instance; if (!((Object)(object)instance2 != (Object)null)) { return; } foreach (ZNetView value in instance2.m_instances.Values) { if (value?.m_functions == null) { continue; } string prefab = string.Empty; try { prefab = value.GetPrefabName() ?? string.Empty; } catch { } foreach (KeyValuePair function3 in value.m_functions) { RegisterScanned(function3.Value, "Object", function3.Key, prefab); } } } catch { } } private bool IsRpcHandlerSeen(object handlerObject) { if (handlerObject == null) { return false; } lock (_sync) { SeenRpcHandler value; return _seenRpcHandlers.TryGetValue(handlerObject, out value); } } private void RegisterScanned(object wrapper, string layer, int hash, string prefab) { if (wrapper != null && !IsRpcHandlerSeen(wrapper)) { Delegate handler = null; try { handler = wrapper.GetType().GetField("m_action", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)?.GetValue(wrapper) as Delegate; } catch { } string name = ResolveRpcName(hash); RegisterRpc(wrapper, layer, name, handler, prefab, hash); } } private void BuildKnownZdoKeyMap() { try { FieldInfo[] fields = typeof(ZDOVars).GetFields(BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); foreach (FieldInfo fieldInfo in fields) { if (fieldInfo.FieldType == typeof(int)) { int key = (int)fieldInfo.GetValue(null); if (!_knownKeyNames.ContainsKey(key) && _knownKeyNames.Count < 4096) { _knownKeyNames[key] = fieldInfo.Name; } } else if (fieldInfo.FieldType == typeof(KeyValuePair)) { KeyValuePair keyValuePair = (KeyValuePair)fieldInfo.GetValue(null); if (!_knownKeyNames.ContainsKey(keyValuePair.Key) && _knownKeyNames.Count < 4096) { _knownKeyNames[keyValuePair.Key] = fieldInfo.Name + "_u"; } if (!_knownKeyNames.ContainsKey(keyValuePair.Value) && _knownKeyNames.Count < 4096) { _knownKeyNames[keyValuePair.Value] = fieldInfo.Name + "_i"; } } } } catch { } } private void BuildAssemblyMap() { foreach (KeyValuePair pluginInfo in Chainloader.PluginInfos) { try { PluginInfo value = pluginInfo.Value; Assembly assembly = ((value == null) ? null : ((object)value.Instance)?.GetType().Assembly); if (assembly != null) { _assemblyMods[assembly] = pluginInfo.Key; } } catch { } } } private void DetectNetworkMods() { List list = new List(); AddCompatibility("com.Fire.FiresGhettoNetworkMod", "VAGhettoNetworking", "Experimental compatibility: custom routed-RPC processing, ZDO delta serialization, ownership and throttling can bypass vanilla paths. RPC registrations, handler timing, physical traffic and peer queues remain useful; ZDO byte/candidate columns may be partial.", list); AddCompatibility("VitByr.VBNetTweaks", "VBNetTweaks", "Partial compatibility: custom routed-RPC filtering changes broadcast fanout and ZDO scheduling. Physical sends remain measured, but vanilla candidate/selection semantics may differ.", list); AddCompatibility("Searica.Valheim.NetworkTweaks", "NetworkTweaks", "Compatible: it changes peers-per-update and Steam limits. Per-peer intervals and queue values reflect the modified runtime.", list); AddCompatibility("CW_Jesse.BetterNetworking", "Better Networking", "Compatible: runtime limits and compression can change queue/transport values; logical RPC and ZDO measurements remain usable.", list); AddCompatibility("org.bepinex.plugins.network", "Network", "Compatible with caveat: it buffers early ZDOData registration; registrations and runtime traffic are still measured after the final handler is installed.", list); _compatibilitySummary = ((list.Count == 0) ? "Vanilla network stack detected." : ("Detected network stack mods: " + string.Join(", ", list) + ". See Help for caveats.")); if (_compatibilityWarnings.Any((string item) => item.StartsWith("Instrumentation:", StringComparison.Ordinal))) { _compatibilitySummary += " One or more instrumentation warnings were detected; see Help."; } } private void AddCompatibility(string guid, string name, string warning, List detected) { if (Chainloader.PluginInfos.ContainsKey(guid)) { if (!detected.Contains(name)) { detected.Add(name); } string item = name + ": " + warning; if (!_compatibilityWarnings.Contains(item)) { _compatibilityWarnings.Add(item); } } } private static string DescribeSocket(ISocket socket) { if (socket == null) { return "null"; } string text = ((object)socket).GetType().Name; object obj = socket; for (int i = 0; i < 8; i++) { if (obj == null) { break; } Type type = obj.GetType(); if (!string.Equals(type.Name, "BufferingSocket", StringComparison.Ordinal)) { break; } object obj2 = type.GetField("Original", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)?.GetValue(obj); if (obj2 == null || obj2 == obj) { break; } text = text + " -> " + obj2.GetType().Name; obj = obj2; } return text; } private static long TryGetActualInFlight(ISocket socket) { try { object obj = socket; for (int i = 0; i < 8; i++) { if (obj == null) { break; } Type type = obj.GetType(); if (type.Name == "ZPlayFabSocket") { object obj2 = type.GetField("m_inFlightQueue", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)?.GetValue(obj); if (obj2 == null) { return -1L; } PropertyInfo property = obj2.GetType().GetProperty("Bytes", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); FieldInfo field = obj2.GetType().GetField("Bytes", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); object obj3 = property?.GetValue(obj2) ?? field?.GetValue(obj2); return (obj3 == null) ? (-1) : Convert.ToInt64(obj3); } object obj4 = type.GetField("Original", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)?.GetValue(obj); if (obj4 == null || obj4 == obj) { break; } obj = obj4; } } catch { } return -1L; } private static T Safe(Func action, T fallback) { try { return action(); } catch { return fallback; } } } internal enum NetworkProfilerTransportReceiveResult { Complete, WaitingForFragments, Rejected } internal static class NetworkProfilerTransport { [Flags] private enum TransportFlags : byte { None = 0, Compressed = 1, Fragmented = 2 } private sealed class FragmentAssembly { internal long Sender; internal int ExpectedFragments; internal int ExpectedBytes; internal TransportFlags Flags; internal long ExpiresUtcTicks; internal int ReceivedBytes; internal readonly SortedDictionary Fragments = new SortedDictionary(); } private const int Magic = 1448103504; private const byte EnvelopeVersion = 1; private const int CompressionThresholdBytes = 8192; private const int FragmentPayloadBytes = 250000; private const int MaxEnvelopeBytes = 300000; private const int MaxFragments = 64; private const int MaxFragmentAssembliesPerSender = 4; private const int MaxFragmentCacheBytesPerSender = 8388608; private const int MaxFragmentCacheBytesGlobal = 33554432; private static readonly TimeSpan FragmentLifetime = TimeSpan.FromSeconds(30.0); internal const int MaxRawPayloadBytes = 4194304; private static readonly object CacheLock = new object(); private static readonly Dictionary FragmentCache = new Dictionary(); private static long _nextPackageId; internal static bool Send(long target, string rpcName, ZPackage payload, out string error) { //IL_01a8: Unknown result type (might be due to invalid IL or missing references) //IL_01af: Expected O, but got Unknown error = string.Empty; try { ZRoutedRpc instance = ZRoutedRpc.instance; if (instance == null) { error = "ZRoutedRpc is not available."; return false; } if (target == 0) { error = "Network Profiler transport refuses broadcast target 0."; return false; } if (payload == null) { error = "Network Profiler payload is null."; return false; } byte[] packageBytes = GetPackageBytes(payload); if (packageBytes.Length == 0 || packageBytes.Length > 4194304) { error = $"Network Profiler payload size {packageBytes.Length} is outside the allowed range 1..{4194304}."; return false; } TransportFlags transportFlags = TransportFlags.None; byte[] array = packageBytes; if (packageBytes.Length >= 8192) { byte[] array2 = Compress(packageBytes); if (array2.Length + 32 < packageBytes.Length) { array = array2; transportFlags |= TransportFlags.Compressed; } } if (array.Length > 4194304) { error = $"Encoded Network Profiler payload exceeds {4194304} bytes."; return false; } int num = (array.Length + 250000 - 1) / 250000; if (num <= 0 || num > 64) { error = $"Network Profiler payload requires invalid fragment count {num}."; return false; } if (num > 1) { transportFlags |= TransportFlags.Fragmented; } long num2 = Interlocked.Increment(ref _nextPackageId); for (int i = 0; i < num; i++) { int num3 = i * 250000; int num4 = Math.Min(250000, array.Length - num3); byte[] array3 = new byte[num4]; Buffer.BlockCopy(array, num3, array3, 0, num4); ZPackage val = new ZPackage(); val.Write(1448103504); val.Write((byte)1); val.Write((byte)transportFlags); val.Write(num2); val.Write(i); val.Write(num); val.Write(array.Length); val.Write(array3); if (val.Size() > 300000) { error = $"Network Profiler fragment {i + 1}/{num} exceeded the envelope limit."; return false; } instance.InvokeRoutedRPC(target, rpcName, new object[1] { val }); } return true; } catch (Exception ex) { error = ex.GetType().Name + ": " + ex.Message; return false; } } internal static NetworkProfilerTransportReceiveResult TryReceive(long sender, ZPackage envelope, out ZPackage payload, out string error) { payload = null; error = string.Empty; string text = null; try { CleanupExpired(); if (envelope == null || envelope.Size() <= 0 || envelope.Size() > 300000) { error = "Invalid or oversized Network Profiler transport envelope."; return NetworkProfilerTransportReceiveResult.Rejected; } int num = envelope.ReadInt(); byte b = envelope.ReadByte(); TransportFlags transportFlags = (TransportFlags)envelope.ReadByte(); long num2 = envelope.ReadLong(); int num3 = envelope.ReadInt(); int num4 = envelope.ReadInt(); int num5 = envelope.ReadInt(); byte[] array = envelope.ReadByteArray(); if (num != 1448103504) { error = "Unsupported Network Profiler transport envelope."; return NetworkProfilerTransportReceiveResult.Rejected; } if (b != 1) { error = $"Unsupported Network Profiler transport version {b}; expected {(byte)1}."; return NetworkProfilerTransportReceiveResult.Rejected; } if ((transportFlags & ~(TransportFlags.Compressed | TransportFlags.Fragmented)) != TransportFlags.None) { error = $"Unknown Network Profiler transport flags {(byte)transportFlags}."; return NetworkProfilerTransportReceiveResult.Rejected; } if (num2 <= 0 || num5 <= 0 || num5 > 4194304 || num4 <= 0 || num4 > 64 || num3 < 0 || num3 >= num4 || array == null || array.Length == 0 || array.Length > 250000 || array.Length > num5) { error = "Invalid Network Profiler fragment metadata."; return NetworkProfilerTransportReceiveResult.Rejected; } if ((transportFlags & TransportFlags.Fragmented) == 0) { if (num4 != 1 || num3 != 0 || array.Length != num5) { error = "Invalid unfragmented Network Profiler envelope."; return NetworkProfilerTransportReceiveResult.Rejected; } return DecodePayload(transportFlags, array, out payload, out error); } int num6 = (num5 + 250000 - 1) / 250000; int num7 = ((num3 == num4 - 1) ? (num5 - 250000 * (num4 - 1)) : 250000); if (num4 <= 1 || num4 != num6 || num7 <= 0 || array.Length != num7) { error = "Invalid fragmented Network Profiler envelope shape."; return NetworkProfilerTransportReceiveResult.Rejected; } text = sender + ":" + num2; byte[] array2 = null; lock (CacheLock) { if (!FragmentCache.TryGetValue(text, out var value)) { if (GetAssemblyCountForSender(sender) >= 4) { error = "Too many pending fragmented Network Profiler payloads from this sender."; return NetworkProfilerTransportReceiveResult.Rejected; } if (GetCacheBytesForSender(sender) + array.Length > 8388608 || GetCacheBytesGlobal() + array.Length > 33554432) { error = "Network Profiler fragment cache limit exceeded."; return NetworkProfilerTransportReceiveResult.Rejected; } value = new FragmentAssembly { Sender = sender, ExpectedFragments = num4, ExpectedBytes = num5, Flags = transportFlags, ExpiresUtcTicks = DateTime.UtcNow.Add(FragmentLifetime).Ticks }; FragmentCache.Add(text, value); } else if (value.ExpectedFragments != num4 || value.ExpectedBytes != num5 || value.Flags != transportFlags) { RemoveAssembly(text); error = "Inconsistent Network Profiler fragment metadata."; return NetworkProfilerTransportReceiveResult.Rejected; } if (value.Fragments.ContainsKey(num3)) { RemoveAssembly(text); error = "Duplicate Network Profiler fragment received."; return NetworkProfilerTransportReceiveResult.Rejected; } if (GetCacheBytesForSender(sender) + array.Length > 8388608 || GetCacheBytesGlobal() + array.Length > 33554432 || value.ReceivedBytes + array.Length > value.ExpectedBytes) { RemoveAssembly(text); error = "Network Profiler fragment cache size limit exceeded."; return NetworkProfilerTransportReceiveResult.Rejected; } value.Fragments.Add(num3, array); value.ReceivedBytes += array.Length; value.ExpiresUtcTicks = DateTime.UtcNow.Add(FragmentLifetime).Ticks; if (value.Fragments.Count < value.ExpectedFragments) { return NetworkProfilerTransportReceiveResult.WaitingForFragments; } if (value.ReceivedBytes != value.ExpectedBytes) { RemoveAssembly(text); error = "Completed Network Profiler fragment assembly has an invalid size."; return NetworkProfilerTransportReceiveResult.Rejected; } array2 = new byte[value.ExpectedBytes]; int num8 = 0; for (int i = 0; i < value.ExpectedFragments; i++) { if (!value.Fragments.TryGetValue(i, out var value2)) { RemoveAssembly(text); error = "Completed Network Profiler fragment assembly is missing a fragment."; return NetworkProfilerTransportReceiveResult.Rejected; } Buffer.BlockCopy(value2, 0, array2, num8, value2.Length); num8 += value2.Length; } RemoveAssembly(text); text = null; } return DecodePayload(transportFlags, array2, out payload, out error); } catch (Exception ex) { if (text != null) { lock (CacheLock) { RemoveAssembly(text); } } error = ex.GetType().Name + ": " + ex.Message; return NetworkProfilerTransportReceiveResult.Rejected; } } internal static void Clear() { lock (CacheLock) { FragmentCache.Clear(); } } private static NetworkProfilerTransportReceiveResult DecodePayload(TransportFlags flags, byte[] encoded, out ZPackage payload, out string error) { //IL_0050: Unknown result type (might be due to invalid IL or missing references) //IL_0056: Expected O, but got Unknown payload = null; error = string.Empty; try { byte[] array = (((flags & TransportFlags.Compressed) != TransportFlags.None) ? DecompressLimited(encoded, 4194304) : encoded); if (array.Length == 0 || array.Length > 4194304) { error = $"Decoded Network Profiler payload size {array.Length} is invalid."; return NetworkProfilerTransportReceiveResult.Rejected; } payload = new ZPackage(array); return NetworkProfilerTransportReceiveResult.Complete; } catch (Exception ex) { error = ex.GetType().Name + ": " + ex.Message; return NetworkProfilerTransportReceiveResult.Rejected; } } private static byte[] GetPackageBytes(ZPackage package) { int num = package.Size(); byte[] array = package.GetArray(); if (array.Length == num) { return array; } if (array.Length < num) { throw new InvalidDataException($"ZPackage returned {array.Length} bytes for declared size {num}."); } byte[] array2 = new byte[num]; Buffer.BlockCopy(array, 0, array2, 0, num); return array2; } private static byte[] Compress(byte[] data) { using MemoryStream memoryStream = new MemoryStream(); using (DeflateStream deflateStream = new DeflateStream(memoryStream, CompressionLevel.Optimal, leaveOpen: true)) { deflateStream.Write(data, 0, data.Length); } return memoryStream.ToArray(); } private static byte[] DecompressLimited(byte[] data, int maximumBytes) { using MemoryStream stream = new MemoryStream(data, writable: false); using DeflateStream deflateStream = new DeflateStream(stream, CompressionMode.Decompress); using MemoryStream memoryStream = new MemoryStream(); byte[] array = new byte[81920]; while (true) { int num = deflateStream.Read(array, 0, array.Length); if (num <= 0) { break; } if (memoryStream.Length + num > maximumBytes) { throw new InvalidDataException($"Decompressed Network Profiler payload exceeds {maximumBytes} bytes."); } memoryStream.Write(array, 0, num); } return memoryStream.ToArray(); } private static void CleanupExpired() { long now = DateTime.UtcNow.Ticks; lock (CacheLock) { string[] array = (from pair in FragmentCache where pair.Value.ExpiresUtcTicks <= now select pair.Key).ToArray(); foreach (string key in array) { FragmentCache.Remove(key); } } } private static int GetAssemblyCountForSender(long sender) { return FragmentCache.Values.Count((FragmentAssembly assembly) => assembly.Sender == sender); } private static int GetCacheBytesForSender(long sender) { return FragmentCache.Values.Where((FragmentAssembly assembly) => assembly.Sender == sender).Sum((FragmentAssembly assembly) => assembly.ReceivedBytes); } private static int GetCacheBytesGlobal() { return FragmentCache.Values.Sum((FragmentAssembly assembly) => assembly.ReceivedBytes); } private static void RemoveAssembly(string cacheKey) { if (!string.IsNullOrEmpty(cacheKey)) { FragmentCache.Remove(cacheKey); } } } internal enum ServerLogRequestKind : byte { Probe = 1, Subscribe, Unsubscribe, RequestOlder, Resync, SetRemoteAccess } internal enum ServerLogResponseKind : byte { Capabilities = 1, Status, Snapshot, LiveBatch, OlderPage, Error } internal sealed class ServerLogWireEntry { internal long Sequence; internal DateTime Timestamp; internal LogLevel Level; internal string Source; internal string RawMessage; internal string Message; internal string Details; internal string Scene; internal int ThreadId; internal bool IsHistorical; internal long FileOffset; internal string Fingerprint => LogMonitorText.BuildFingerprint(Level, Source, Message, Details); } internal sealed class ServerLogResponse { internal ServerLogResponseKind Kind; internal string SessionId = string.Empty; internal string Status = string.Empty; internal string ServerVersion = string.Empty; internal bool RemoteEnabled; internal bool Authorized; internal long DroppedCount; internal long FirstSequence; internal long LastSequence; internal long HistoryCursor; internal bool HasMoreHistory; internal long FileCreationUtcTicks; internal bool ResetHistory; internal readonly List Entries = new List(); } internal static class ServerLogProtocol { internal const int Version = 4; internal const string RequestRpc = "shudnal.ValheimProfiler.ServerLog.Request"; internal const string ResponseRpc = "shudnal.ValheimProfiler.ServerLog.Response"; internal const int MaxRequestPayloadBytes = 16384; internal const int MaxResponsePayloadBytes = 4194304; internal const int MaxEntryPayloadBytes = 131072; internal const int MaxWireStringLength = 8192; internal static ZPackage CreateRequest(ServerLogRequestKind kind, string sessionId = "", long cursor = 0L, long lastSequence = 0L, long fileCreationUtcTicks = 0L, bool requestedRemoteEnabled = false) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Expected O, but got Unknown ZPackage val = new ZPackage(); val.Write(4); val.Write((byte)kind); val.Write(LimitTo(sessionId, 128)); val.Write(cursor); val.Write(lastSequence); val.Write(fileCreationUtcTicks); val.Write(requestedRemoteEnabled); return val; } internal static void ReadRequest(ZPackage package, out int version, out ServerLogRequestKind kind, out string sessionId, out long cursor, out long lastSequence, out long fileCreationUtcTicks, out bool requestedRemoteEnabled) { version = package.ReadInt(); kind = (ServerLogRequestKind)package.ReadByte(); if (!Enum.IsDefined(typeof(ServerLogRequestKind), kind)) { throw new InvalidOperationException($"Unknown server log request kind {(byte)kind}."); } sessionId = ReadBoundedString(package, 128, "request session id"); cursor = package.ReadLong(); lastSequence = package.ReadLong(); fileCreationUtcTicks = package.ReadLong(); requestedRemoteEnabled = package.ReadBool(); if (cursor < 0 || lastSequence < 0 || fileCreationUtcTicks < 0) { throw new InvalidOperationException("Server log request contains a negative cursor, sequence or file timestamp."); } } internal static ZPackage CreateResponse(ServerLogResponse response) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Expected O, but got Unknown //IL_00e8: Unknown result type (might be due to invalid IL or missing references) //IL_00ee: Expected O, but got Unknown ZPackage val = new ZPackage(); val.Write(4); val.Write((byte)response.Kind); val.Write(LimitTo(response.SessionId, 128)); val.Write(Limit(response.Status)); val.Write(LimitTo(response.ServerVersion, 128)); val.Write(response.RemoteEnabled); val.Write(response.Authorized); val.Write(response.DroppedCount); val.Write(response.FirstSequence); val.Write(response.LastSequence); val.Write(response.HistoryCursor); val.Write(response.HasMoreHistory); val.Write(response.FileCreationUtcTicks); val.Write(response.ResetHistory); val.Write(response.Entries.Count); for (int i = 0; i < response.Entries.Count; i++) { ZPackage val2 = new ZPackage(); WriteEntry(val2, response.Entries[i]); byte[] array = val2.GetArray(); if (array.Length > 131072) { throw new InvalidOperationException($"Server log entry payload exceeds {131072} bytes."); } val.Write(array); } return val; } internal static ServerLogResponse ReadResponse(ZPackage package) { //IL_021d: Unknown result type (might be due to invalid IL or missing references) //IL_0227: Expected O, but got Unknown int num = package.ReadInt(); if (num != 4) { throw new InvalidOperationException($"Unsupported server log protocol {num}; expected {4}."); } ServerLogResponseKind serverLogResponseKind = (ServerLogResponseKind)package.ReadByte(); if (!Enum.IsDefined(typeof(ServerLogResponseKind), serverLogResponseKind)) { throw new InvalidOperationException($"Unknown server log response kind {(byte)serverLogResponseKind}."); } ServerLogResponse serverLogResponse = new ServerLogResponse { Kind = serverLogResponseKind, SessionId = ReadBoundedString(package, 128, "response session id"), Status = ReadBoundedString(package, 8192, "response status"), ServerVersion = ReadBoundedString(package, 128, "server version"), RemoteEnabled = package.ReadBool(), Authorized = package.ReadBool(), DroppedCount = package.ReadLong(), FirstSequence = package.ReadLong(), LastSequence = package.ReadLong(), HistoryCursor = package.ReadLong(), HasMoreHistory = package.ReadBool(), FileCreationUtcTicks = package.ReadLong(), ResetHistory = package.ReadBool() }; if (serverLogResponse.DroppedCount < 0 || serverLogResponse.FirstSequence < 0 || serverLogResponse.LastSequence < 0 || serverLogResponse.HistoryCursor < 0 || serverLogResponse.FileCreationUtcTicks < 0 || (serverLogResponse.FirstSequence > 0 && serverLogResponse.LastSequence > 0 && serverLogResponse.FirstSequence > serverLogResponse.LastSequence)) { throw new InvalidOperationException("Server log response contains invalid range metadata."); } int num2 = package.ReadInt(); if (num2 < 0 || num2 > 10000) { throw new InvalidOperationException($"Invalid server log entry count {num2}."); } long num3 = 0L; long num4 = -1L; for (int i = 0; i < num2; i++) { byte[] array = package.ReadByteArray(); if (array == null || array.Length == 0 || array.Length > 131072) { throw new InvalidOperationException($"Invalid server log entry payload size {((array != null) ? array.Length : 0)}."); } ServerLogWireEntry serverLogWireEntry = ReadEntry(new ZPackage(array)); if (serverLogResponse.Kind == ServerLogResponseKind.OlderPage) { if (!serverLogWireEntry.IsHistorical || serverLogWireEntry.Sequence != 0L || (num4 >= 0 && serverLogWireEntry.FileOffset < num4)) { throw new InvalidOperationException("Server log history page contains invalid entry ordering."); } num4 = serverLogWireEntry.FileOffset; } else if (serverLogResponse.Kind == ServerLogResponseKind.Snapshot || serverLogResponse.Kind == ServerLogResponseKind.LiveBatch) { if (serverLogWireEntry.IsHistorical || serverLogWireEntry.Sequence <= 0 || (num3 > 0 && serverLogWireEntry.Sequence <= num3)) { throw new InvalidOperationException("Server log live response contains invalid entry ordering."); } num3 = serverLogWireEntry.Sequence; } serverLogResponse.Entries.Add(serverLogWireEntry); } if (serverLogResponse.Entries.Count > 0 && (serverLogResponse.Kind == ServerLogResponseKind.Snapshot || serverLogResponse.Kind == ServerLogResponseKind.LiveBatch) && (serverLogResponse.FirstSequence != serverLogResponse.Entries[0].Sequence || serverLogResponse.LastSequence != serverLogResponse.Entries[serverLogResponse.Entries.Count - 1].Sequence)) { throw new InvalidOperationException("Server log response sequence range does not match its entries."); } return serverLogResponse; } internal static int EstimateEntryBytes(ServerLogWireEntry entry) { return 80 + EstimateString(entry.Source) + EstimateString(entry.RawMessage) + EstimateString(entry.Message) + EstimateString(entry.Details) + EstimateString(entry.Scene); } private static int EstimateString(string value) { return Math.Min(8192, value?.Length ?? 0) * 4 + 8; } private static void WriteEntry(ZPackage package, ServerLogWireEntry entry) { //IL_0022: Unknown result type (might be due to invalid IL or missing references) //IL_002c: Expected I4, but got Unknown package.Write(entry.Sequence); package.Write(ToUtcTicks(entry.Timestamp)); package.Write((int)entry.Level); package.Write(Limit(entry.Source)); package.Write(Limit(entry.RawMessage)); package.Write(Limit(entry.Message)); package.Write(Limit(entry.Details)); package.Write(Limit(entry.Scene)); package.Write(entry.ThreadId); package.Write(entry.IsHistorical); package.Write(entry.FileOffset); } private static ServerLogWireEntry ReadEntry(ZPackage package) { //IL_016c: Unknown result type (might be due to invalid IL or missing references) long num = package.ReadLong(); long num2 = package.ReadLong(); int num3 = package.ReadInt(); if ((num3 & -64) != 0) { throw new InvalidOperationException($"Server log entry contains invalid log level {num3}."); } string source = ReadBoundedString(package, 8192, "source"); string rawMessage = ReadBoundedString(package, 8192, "raw message"); string message = ReadBoundedString(package, 8192, "message"); string details = ReadBoundedString(package, 8192, "details"); string scene = ReadBoundedString(package, 8192, "scene"); int num4 = package.ReadInt(); bool flag = package.ReadBool(); long num5 = package.ReadLong(); bool flag2 = ((!flag) ? (num <= 0 || num5 != -1) : (num != 0L || num5 < 0)); int num6; if (num2 >= 0) { DateTime maxValue = DateTime.MaxValue; if (num2 <= maxValue.Ticks) { num6 = ((num4 < 0) ? 1 : 0); goto IL_00ed; } } num6 = 1; goto IL_00ed; IL_00ed: if (((uint)num6 | (flag2 ? 1u : 0u)) != 0) { throw new InvalidOperationException($"Server log entry contains invalid numeric metadata: sequence={num}, ticks={num2}, " + $"thread={num4}, historical={flag}, fileOffset={num5}."); } return new ServerLogWireEntry { Sequence = num, Timestamp = ((num2 > 0) ? new DateTime(num2, DateTimeKind.Utc).ToLocalTime() : default(DateTime)), Level = (LogLevel)num3, Source = source, RawMessage = rawMessage, Message = message, Details = details, Scene = scene, ThreadId = num4, IsHistorical = flag, FileOffset = num5 }; } private static string ReadBoundedString(ZPackage package, int maximumLength, string fieldName) { string text = package.ReadString() ?? string.Empty; if (text.Length > maximumLength) { throw new InvalidOperationException($"Server log {fieldName} exceeds {maximumLength} characters."); } return text; } private static long ToUtcTicks(DateTime value) { if (value == default(DateTime)) { return 0L; } return (value.Kind == DateTimeKind.Utc) ? value.Ticks : value.ToUniversalTime().Ticks; } private static string Limit(string value) { return LimitTo(value, 8192); } private static string LimitTo(string value, int maximumLength) { if (value == null) { value = string.Empty; } if (value.Length <= maximumLength) { return value; } return value[..Math.Max(0, maximumLength - 24)] + "\n... [text truncated]"; } } internal static class ServerLogRpcBridge { private static ZRoutedRpc _registeredRpc; internal static void RegisterForCurrentNetwork() { ZRoutedRpc instance = ZRoutedRpc.instance; if (instance != null && instance != _registeredRpc) { _registeredRpc = instance; ValheimProfilerPlugin instance2 = ValheimProfilerPlugin.Instance; if (instance2?.ServerLogService != null && instance.m_server) { instance.Register("shudnal.ValheimProfiler.ServerLog.Request", (Action)instance2.ServerLogService.HandleRequest); } if (instance2?.App?.ServerLogMonitor != null && !instance.m_server) { instance.Register("shudnal.ValheimProfiler.ServerLog.Response", (Action)instance2.App.ServerLogMonitor.HandleResponse); } } } internal static void NetworkDestroyed() { _registeredRpc = null; ServerLogTransport.Clear(); ValheimProfilerPlugin.Instance?.ServerLogService?.OnNetworkDestroyed(); ValheimProfilerPlugin.Instance?.App?.ServerLogMonitor?.OnNetworkDestroyed(); } } [HarmonyPatch(typeof(ZNet), "Awake")] internal static class ZNetAwakeServerLogPatch { [HarmonyPostfix] internal static void Postfix() { ServerLogRpcBridge.RegisterForCurrentNetwork(); NetworkProfilerRpcBridge.RegisterForCurrentNetwork(); } } [HarmonyPatch(typeof(ZNet), "OnDestroy")] internal static class ZNetOnDestroyServerLogPatch { [HarmonyPrefix] internal static void Prefix() { ServerLogRpcBridge.NetworkDestroyed(); NetworkProfilerRpcBridge.NetworkDestroyed(); } } internal sealed class ServerLogService { private sealed class PendingLogEvent { internal DateTime Timestamp; internal LogLevel Level; internal string Source; internal string RawMessage; internal string Message; internal string Details; internal int ThreadId; } private sealed class Subscriber { internal long PeerId; internal long LastSentSequence; internal DateTime LastHistoryRequestUtc; internal bool HistoryRequestPending; } private sealed class HistoryResult { internal long PeerId; internal string RequestedSessionId; internal LogFilePage Page; } private sealed class Listener : ILogListener, IDisposable { private ServerLogService _owner; internal Listener(ServerLogService owner) { _owner = owner; } public void LogEvent(object sender, LogEventArgs eventArgs) { _owner?.Enqueue(eventArgs); } public void Dispose() { _owner = null; } } private const int MaxPendingEntries = 20000; private const int MaxDrainPerFrame = 2000; private const int MaxStoredTextLength = 8192; private const int MaxLiveBatchEntries = 256; private const int MaxSubscribers = 8; private static readonly TimeSpan LiveBatchInterval = TimeSpan.FromMilliseconds(200.0); private static readonly TimeSpan HistoryRequestInterval = TimeSpan.FromMilliseconds(500.0); private static readonly TimeSpan SubscriberValidationInterval = TimeSpan.FromSeconds(1.0); private readonly ValheimProfilerConfig _config; private readonly Listener _listener; private readonly ConcurrentQueue _pending = new ConcurrentQueue(); private readonly ConcurrentQueue _historyResults = new ConcurrentQueue(); private readonly List _recent = new List(); private readonly Dictionary _subscribers = new Dictionary(); private readonly string _sessionId = Guid.NewGuid().ToString("N"); private int _pendingCount; private long _droppedCount; private long _nextSequence; private DateTime _nextLiveSendUtc; private DateTime _nextSubscriberValidationUtc; private bool _shutdown; internal string SessionId => _sessionId; private static string LogFilePath => Path.Combine(Paths.BepInExRootPath, "LogOutput.log"); internal ServerLogService(ValheimProfilerConfig config) { _config = config ?? throw new ArgumentNullException("config"); _listener = new Listener(this); Logger.Listeners.Add((ILogListener)(object)_listener); } internal void Update() { if (!_shutdown) { DrainPending(); DrainHistoryResults(); DateTime utcNow = DateTime.UtcNow; if (utcNow >= _nextSubscriberValidationUtc) { _nextSubscriberValidationUtc = utcNow + SubscriberValidationInterval; ValidateSubscribers(); } if (!(utcNow < _nextLiveSendUtc)) { _nextLiveSendUtc = utcNow + LiveBatchInterval; SendLiveBatches(); } } } internal void HandleRequest(long sender, ZPackage package) { try { ZPackage payload; string error; switch (ServerLogTransport.TryReceive(sender, package, out payload, out error)) { case ServerLogTransportReceiveResult.WaitingForFragments: return; case ServerLogTransportReceiveResult.Rejected: SendError(sender, "Invalid server log transport payload: " + error); return; } if (payload.Size() > 16384) { SendError(sender, "Oversized server log request payload."); return; } ServerLogProtocol.ReadRequest(payload, out var version, out var kind, out var sessionId, out var cursor, out var lastSequence, out var fileCreationUtcTicks, out var _); if (version != 4) { SendError(sender, $"Unsupported server log protocol {version}; server uses {4}."); return; } if (kind == ServerLogRequestKind.Probe) { SendCapabilities(sender); return; } if (!IsAdmin(sender)) { SendError(sender, "Server log access denied. The connected peer is not a server administrator."); _subscribers.Remove(sender); return; } switch (kind) { case ServerLogRequestKind.SetRemoteAccess: SendError(sender, "The remote access toggle was removed. Use Subscribe to start a private admin log stream."); break; case ServerLogRequestKind.Subscribe: Subscribe(sender); break; case ServerLogRequestKind.Unsubscribe: _subscribers.Remove(sender); SendStatus(sender, "Unsubscribed from server log updates."); break; case ServerLogRequestKind.RequestOlder: RequestOlder(sender, sessionId, cursor, fileCreationUtcTicks); break; case ServerLogRequestKind.Resync: { if (!_subscribers.TryGetValue(sender, out var value)) { if (_subscribers.Count >= 8) { SendError(sender, $"Server log subscriber limit ({8}) reached."); break; } value = new Subscriber { PeerId = sender }; _subscribers[sender] = value; } SendSnapshot(value, $"Server log stream resynchronized after client sequence {lastSequence}."); break; } default: SendError(sender, $"Unknown server log request {kind}."); break; } } catch (Exception ex) { SendError(sender, "Invalid server log request: " + ex.GetType().Name + ": " + ex.Message); } } internal void OnNetworkDestroyed() { _subscribers.Clear(); } internal void Shutdown() { if (!_shutdown) { _shutdown = true; try { Logger.Listeners.Remove((ILogListener)(object)_listener); _listener.Dispose(); } catch { } _subscribers.Clear(); _recent.Clear(); PendingLogEvent result; while (_pending.TryDequeue(out result)) { } HistoryResult result2; while (_historyResults.TryDequeue(out result2)) { } } } private void Subscribe(long sender) { if (!_subscribers.TryGetValue(sender, out var value)) { if (_subscribers.Count >= 8) { SendError(sender, $"Server log subscriber limit ({8}) reached."); return; } value = new Subscriber { PeerId = sender }; _subscribers.Add(sender, value); } SendSnapshot(value, "Subscribed to a private dedicated-server log stream for this admin connection."); } private void RequestOlder(long sender, string requestedSession, long cursor, long expectedFileCreationUtcTicks) { if (!_subscribers.TryGetValue(sender, out var value)) { SendError(sender, "Subscribe to the server log before requesting history."); return; } if (!string.Equals(requestedSession, _sessionId, StringComparison.Ordinal)) { SendSnapshot(value, "The server log session changed; a fresh recent snapshot was sent.", resetHistory: true); return; } long creationUtcTicks; long logFileLength = GetLogFileLength(out creationUtcTicks); if ((expectedFileCreationUtcTicks != 0L && creationUtcTicks != 0L && expectedFileCreationUtcTicks != creationUtcTicks) || cursor > logFileLength) { SendSnapshot(value, "The server LogOutput.log file changed; loaded history was invalidated and a fresh snapshot was sent.", resetHistory: true); return; } DateTime utcNow = DateTime.UtcNow; if (value.HistoryRequestPending || utcNow - value.LastHistoryRequestUtc < HistoryRequestInterval) { SendStatus(sender, "A server log history request is already pending or was requested too quickly."); return; } value.HistoryRequestPending = true; value.LastHistoryRequestUtc = utcNow; int maxEntries = Math.Max(100, _config.ServerLogHistoryPageEntries.Value); string path = LogFilePath; string session = _sessionId; ThreadPool.QueueUserWorkItem(delegate { LogFilePage page; try { page = LogFileHistoryReader.ReadOlder(path, cursor, maxEntries, 4194304); } catch (Exception ex) { page = new LogFilePage { Error = ex.GetType().Name + ": " + ex.Message }; } _historyResults.Enqueue(new HistoryResult { PeerId = sender, RequestedSessionId = session, Page = page }); }); } private void DrainHistoryResults() { //IL_01ea: Unknown result type (might be due to invalid IL or missing references) //IL_01ef: Unknown result type (might be due to invalid IL or missing references) HistoryResult result; while (_historyResults.TryDequeue(out result)) { if (_subscribers.TryGetValue(result.PeerId, out var value)) { value.HistoryRequestPending = false; } if (_shutdown || !string.Equals(result.RequestedSessionId, _sessionId, StringComparison.Ordinal) || !_subscribers.ContainsKey(result.PeerId) || !IsAdmin(result.PeerId)) { continue; } LogFilePage page = result.Page; if (!string.IsNullOrEmpty(page.Error)) { if (page.Error.StartsWith("LogOutput.log changed", StringComparison.OrdinalIgnoreCase) && _subscribers.TryGetValue(result.PeerId, out var value2)) { SendSnapshot(value2, "The server LogOutput.log file changed; loaded history was invalidated and a fresh snapshot was sent.", resetHistory: true); } else { SendError(result.PeerId, page.Error); } continue; } ServerLogResponse serverLogResponse = new ServerLogResponse { Kind = ServerLogResponseKind.OlderPage, SessionId = _sessionId, ServerVersion = "1.0.0", RemoteEnabled = true, Authorized = true, Status = ((page.Entries.Count == 0) ? "No older server log entries." : $"Loaded {page.Entries.Count} older server log entries."), DroppedCount = Interlocked.Read(in _droppedCount), HistoryCursor = page.NextCursor, HasMoreHistory = page.HasMore, FileCreationUtcTicks = page.FileCreationUtcTicks }; List list = new List(page.Entries.Count); for (int i = 0; i < page.Entries.Count; i++) { ParsedLogFileEntry parsedLogFileEntry = page.Entries[i]; list.Add(new ServerLogWireEntry { Sequence = 0L, Timestamp = parsedLogFileEntry.Timestamp, Level = parsedLogFileEntry.Level, Source = parsedLogFileEntry.Source, RawMessage = parsedLogFileEntry.RawMessage, Message = parsedLogFileEntry.Message, Details = parsedLogFileEntry.Details, Scene = parsedLogFileEntry.Scene, ThreadId = parsedLogFileEntry.ThreadId, IsHistorical = true, FileOffset = parsedLogFileEntry.FileOffset }); } AddNewestEntriesWithinBudget(serverLogResponse, list, list.Count); if (serverLogResponse.Entries.Count > 0) { serverLogResponse.HistoryCursor = serverLogResponse.Entries[0].FileOffset; serverLogResponse.HasMoreHistory = serverLogResponse.HistoryCursor > 0; } Send(result.PeerId, serverLogResponse); } } private void SendSnapshot(Subscriber subscriber, string status, bool resetHistory = false) { long creationUtcTicks; long logFileLength = GetLogFileLength(out creationUtcTicks); ServerLogResponse serverLogResponse = new ServerLogResponse { Kind = ServerLogResponseKind.Snapshot, SessionId = _sessionId, ServerVersion = "1.0.0", RemoteEnabled = true, Authorized = true, Status = status, DroppedCount = Interlocked.Read(in _droppedCount), HistoryCursor = logFileLength, HasMoreHistory = (logFileLength > 0), FileCreationUtcTicks = creationUtcTicks, ResetHistory = resetHistory }; int maximumCount = Math.Max(100, _config.ServerLogInitialEntries.Value); AddNewestEntriesWithinBudget(serverLogResponse, _recent, maximumCount); long lastSentSequence; if (serverLogResponse.Entries.Count > 0) { serverLogResponse.FirstSequence = serverLogResponse.Entries[0].Sequence; serverLogResponse.LastSequence = serverLogResponse.Entries[serverLogResponse.Entries.Count - 1].Sequence; lastSentSequence = serverLogResponse.LastSequence; } else { serverLogResponse.FirstSequence = 0L; serverLogResponse.LastSequence = _nextSequence; lastSentSequence = _nextSequence; } if (Send(subscriber.PeerId, serverLogResponse)) { subscriber.LastSentSequence = lastSentSequence; } } private void SendLiveBatches() { if (_subscribers.Count == 0 || _recent.Count == 0) { return; } long sequence = _recent[0].Sequence; long sequence2 = _recent[_recent.Count - 1].Sequence; Subscriber[] array = _subscribers.Values.ToArray(); foreach (Subscriber subscriber in array) { if (subscriber.LastSentSequence >= sequence2) { continue; } if (subscriber.LastSentSequence > 0 && subscriber.LastSentSequence < sequence - 1) { SendSnapshot(subscriber, "Live server log entries were dropped before delivery; a recent snapshot was sent."); continue; } ServerLogResponse serverLogResponse = new ServerLogResponse { Kind = ServerLogResponseKind.LiveBatch, SessionId = _sessionId, ServerVersion = "1.0.0", RemoteEnabled = true, Authorized = true, Status = string.Empty, DroppedCount = Interlocked.Read(in _droppedCount) }; int num = 256; for (int j = 0; j < _recent.Count; j++) { if (serverLogResponse.Entries.Count >= 256) { break; } ServerLogWireEntry serverLogWireEntry = _recent[j]; if (serverLogWireEntry.Sequence > subscriber.LastSentSequence) { int num2 = ServerLogProtocol.EstimateEntryBytes(serverLogWireEntry); if (serverLogResponse.Entries.Count > 0 && num + num2 > 4194304) { break; } serverLogResponse.Entries.Add(serverLogWireEntry); num += num2; } } if (serverLogResponse.Entries.Count != 0) { serverLogResponse.FirstSequence = serverLogResponse.Entries[0].Sequence; serverLogResponse.LastSequence = serverLogResponse.Entries[serverLogResponse.Entries.Count - 1].Sequence; if (Send(subscriber.PeerId, serverLogResponse)) { subscriber.LastSentSequence = serverLogResponse.LastSequence; } } } } private static void AddNewestEntriesWithinBudget(ServerLogResponse response, IReadOnlyList source, int maximumCount) { if (source == null || source.Count == 0 || maximumCount <= 0) { return; } List list = new List(); int num = 256; int num2 = source.Count - 1; while (num2 >= 0 && list.Count < maximumCount) { ServerLogWireEntry serverLogWireEntry = source[num2]; int num3 = ServerLogProtocol.EstimateEntryBytes(serverLogWireEntry); if (list.Count > 0 && num + num3 > 4194304) { break; } list.Add(serverLogWireEntry); num += num3; num2--; } for (int num4 = list.Count - 1; num4 >= 0; num4--) { response.Entries.Add(list[num4]); } } private void SendCapabilities(long target) { bool flag = IsAdmin(target); string status = (flag ? "Dedicated server log access is available. Subscribe to start a private stream for this connection." : "Valheim Profiler is installed, but the connected peer is not a server administrator."); Send(target, new ServerLogResponse { Kind = ServerLogResponseKind.Capabilities, SessionId = _sessionId, Status = status, ServerVersion = "1.0.0", RemoteEnabled = true, Authorized = flag, DroppedCount = Interlocked.Read(in _droppedCount) }); } private void SendStatus(long target, string status) { Send(target, new ServerLogResponse { Kind = ServerLogResponseKind.Status, SessionId = _sessionId, ServerVersion = "1.0.0", RemoteEnabled = true, Authorized = IsAdmin(target), Status = status, DroppedCount = Interlocked.Read(in _droppedCount) }); } private void SendError(long target, string status) { Send(target, new ServerLogResponse { Kind = ServerLogResponseKind.Error, SessionId = _sessionId, ServerVersion = "1.0.0", RemoteEnabled = true, Authorized = IsAdmin(target), Status = status, DroppedCount = Interlocked.Read(in _droppedCount) }); } private static bool Send(long target, ServerLogResponse response) { try { bool flag = true; ZPackage val = ServerLogProtocol.CreateResponse(response); while (val.Size() > 4194304 && response.Entries.Count > 1) { int num = Math.Max(1, response.Entries.Count / 8); if (response.Kind == ServerLogResponseKind.LiveBatch) { response.Entries.RemoveRange(response.Entries.Count - num, num); } else { response.Entries.RemoveRange(0, num); } RefreshResponseRange(response); val = ServerLogProtocol.CreateResponse(response); } if (val.Size() > 4194304) { flag = false; response.Entries.Clear(); response.Kind = ServerLogResponseKind.Error; response.Status = "The requested server log response exceeded the transport limit. Reduce page or snapshot entry limits."; response.FirstSequence = 0L; response.LastSequence = 0L; val = ServerLogProtocol.CreateResponse(response); } string error; bool flag2 = ServerLogTransport.Send(target, "shudnal.ValheimProfiler.ServerLog.Response", val, out error); return flag2 && flag; } catch { return false; } } private static void RefreshResponseRange(ServerLogResponse response) { if (response.Entries.Count != 0) { if (response.Kind == ServerLogResponseKind.Snapshot || response.Kind == ServerLogResponseKind.LiveBatch) { response.FirstSequence = response.Entries[0].Sequence; response.LastSequence = response.Entries[response.Entries.Count - 1].Sequence; } if (response.Kind == ServerLogResponseKind.OlderPage) { response.HistoryCursor = response.Entries[0].FileOffset; response.HasMoreHistory = response.HistoryCursor > 0; } } } private void Enqueue(LogEventArgs eventArgs) { //IL_00c3: Unknown result type (might be due to invalid IL or missing references) //IL_00c8: Unknown result type (might be due to invalid IL or missing references) if (_shutdown || eventArgs == null) { return; } int num = Interlocked.Increment(ref _pendingCount); if (num > 20000) { Interlocked.Decrement(ref _pendingCount); Interlocked.Increment(ref _droppedCount); return; } try { ParseData(eventArgs.Data, out var message, out var details); ILogSource source = eventArgs.Source; string source2 = LimitText(((source != null) ? source.SourceName : null) ?? "Unknown", 256); DateTime parsedTimestamp; string message2 = LogMonitorText.NormalizeMessage(source2, message, out parsedTimestamp); _pending.Enqueue(new PendingLogEvent { Timestamp = ((parsedTimestamp != default(DateTime)) ? parsedTimestamp : DateTime.Now), Level = eventArgs.Level, Source = source2, RawMessage = message, Message = message2, Details = details, ThreadId = Thread.CurrentThread.ManagedThreadId }); } catch { Interlocked.Decrement(ref _pendingCount); Interlocked.Increment(ref _droppedCount); } } private void DrainPending() { //IL_006b: Unknown result type (might be due to invalid IL or missing references) //IL_0070: Unknown result type (might be due to invalid IL or missing references) string activeSceneName = GetActiveSceneName(); for (int i = 0; i < 2000; i++) { if (!_pending.TryDequeue(out var result)) { break; } Interlocked.Decrement(ref _pendingCount); _recent.Add(new ServerLogWireEntry { Sequence = ++_nextSequence, Timestamp = result.Timestamp, Level = result.Level, Source = result.Source, RawMessage = result.RawMessage, Message = result.Message, Details = result.Details, Scene = activeSceneName, ThreadId = result.ThreadId, IsHistorical = false, FileOffset = -1L }); } int num = Math.Max(500, _config.ServerLogRecentEntries.Value); if (_recent.Count > num) { int val = Math.Max(_recent.Count - num, Math.Max(1, num / 10)); _recent.RemoveRange(0, Math.Min(val, _recent.Count)); } } private void ValidateSubscribers() { if (_subscribers.Count == 0) { return; } long[] array = _subscribers.Keys.ToArray(); foreach (long num in array) { ZRoutedRpc instance = ZRoutedRpc.instance; ZNetPeer val = ((instance != null) ? instance.GetPeer(num) : null); if (val == null || !val.IsReady()) { _subscribers.Remove(num); } else if (!IsAdmin(num)) { SendError(num, "Server log access was revoked because the connected peer is no longer a server administrator."); _subscribers.Remove(num); } } } private static bool IsAdmin(long sender) { try { if ((Object)(object)ZNet.instance == (Object)null || ZRoutedRpc.instance == null) { return false; } ZNetPeer peer = ZRoutedRpc.instance.GetPeer(sender); object obj; if (peer == null) { obj = null; } else { ISocket socket = peer.m_socket; obj = ((socket != null) ? socket.GetHostName() : null); } string text = (string)obj; return !string.IsNullOrEmpty(text) && ZNet.instance.IsAdmin(text); } catch { return false; } } private static string GetActiveSceneName() { //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Unknown result type (might be due to invalid IL or missing references) try { Scene activeScene = SceneManager.GetActiveScene(); return ((Scene)(ref activeScene)).name ?? string.Empty; } catch { return string.Empty; } } private static long GetLogFileLength(out long creationUtcTicks) { creationUtcTicks = 0L; try { FileInfo fileInfo = new FileInfo(LogFilePath); if (!fileInfo.Exists) { return 0L; } creationUtcTicks = fileInfo.CreationTimeUtc.Ticks; return fileInfo.Length; } catch { return 0L; } } private static void ParseData(object data, out string message, out string details) { if (data is Exception ex) { message = LimitText(ex.GetType().FullName + ": " + ex.Message, 4096); details = LimitText(ex.ToString(), 8192); return; } string text = LimitText(data?.ToString() ?? "NULL", 8192); int num = text.IndexOfAny(new char[2] { '\r', '\n' }); if (num < 0) { message = text; details = string.Empty; return; } message = text.Substring(0, num).TrimEnd(Array.Empty()); int i; for (i = num; i < text.Length && (text[i] == '\r' || text[i] == '\n'); i++) { } details = ((i < text.Length) ? text.Substring(i) : string.Empty); if (string.IsNullOrEmpty(message)) { message = ((details.Length > 0) ? FirstLine(details) : "(empty message)"); } } private static string FirstLine(string value) { if (string.IsNullOrEmpty(value)) { return string.Empty; } int num = value.IndexOfAny(new char[2] { '\r', '\n' }); return (num >= 0) ? value.Substring(0, num) : value; } private static string LimitText(string value, int maxLength) { if (value == null) { value = string.Empty; } if (value.Length <= maxLength) { return value; } return value.Substring(0, Math.Max(0, maxLength - 24)) + "\n... [text truncated]"; } } internal enum ServerLogTransportReceiveResult { Complete, WaitingForFragments, Rejected } internal static class ServerLogTransport { [Flags] private enum TransportFlags : byte { None = 0, Compressed = 1, Fragmented = 2 } private sealed class FragmentAssembly { internal long Sender; internal int ExpectedFragments; internal int ExpectedBytes; internal TransportFlags Flags; internal long ExpiresUtcTicks; internal int ReceivedBytes; internal readonly SortedDictionary Fragments = new SortedDictionary(); } private const int Magic = 1448102983; private const byte EnvelopeVersion = 1; private const int CompressionThresholdBytes = 8192; private const int FragmentPayloadBytes = 250000; private const int MaxEnvelopeBytes = 300000; private const int MaxFragments = 64; private const int MaxFragmentAssembliesPerSender = 4; private const int MaxFragmentCacheBytesPerSender = 8388608; private const int MaxFragmentCacheBytesGlobal = 33554432; private static readonly TimeSpan FragmentLifetime = TimeSpan.FromSeconds(30.0); internal const int MaxRawPayloadBytes = 4194304; private static readonly object CacheLock = new object(); private static readonly Dictionary FragmentCache = new Dictionary(); private static long _nextPackageId; internal static bool Send(long target, string rpcName, ZPackage payload, out string error) { //IL_01a8: Unknown result type (might be due to invalid IL or missing references) //IL_01af: Expected O, but got Unknown error = string.Empty; try { ZRoutedRpc instance = ZRoutedRpc.instance; if (instance == null) { error = "ZRoutedRpc is not available."; return false; } if (target == 0) { error = "Server Log Monitor transport refuses broadcast target 0."; return false; } if (payload == null) { error = "Server Log Monitor payload is null."; return false; } byte[] packageBytes = GetPackageBytes(payload); if (packageBytes.Length == 0 || packageBytes.Length > 4194304) { error = $"Server Log Monitor payload size {packageBytes.Length} is outside the allowed range 1..{4194304}."; return false; } TransportFlags transportFlags = TransportFlags.None; byte[] array = packageBytes; if (packageBytes.Length >= 8192) { byte[] array2 = Compress(packageBytes); if (array2.Length + 32 < packageBytes.Length) { array = array2; transportFlags |= TransportFlags.Compressed; } } if (array.Length > 4194304) { error = $"Encoded Server Log Monitor payload exceeds {4194304} bytes."; return false; } int num = (array.Length + 250000 - 1) / 250000; if (num <= 0 || num > 64) { error = $"Server Log Monitor payload requires invalid fragment count {num}."; return false; } if (num > 1) { transportFlags |= TransportFlags.Fragmented; } long num2 = Interlocked.Increment(ref _nextPackageId); for (int i = 0; i < num; i++) { int num3 = i * 250000; int num4 = Math.Min(250000, array.Length - num3); byte[] array3 = new byte[num4]; Buffer.BlockCopy(array, num3, array3, 0, num4); ZPackage val = new ZPackage(); val.Write(1448102983); val.Write((byte)1); val.Write((byte)transportFlags); val.Write(num2); val.Write(i); val.Write(num); val.Write(array.Length); val.Write(array3); if (val.Size() > 300000) { error = $"Server Log Monitor fragment {i + 1}/{num} exceeded the envelope limit."; return false; } instance.InvokeRoutedRPC(target, rpcName, new object[1] { val }); } return true; } catch (Exception ex) { error = ex.GetType().Name + ": " + ex.Message; return false; } } internal static ServerLogTransportReceiveResult TryReceive(long sender, ZPackage envelope, out ZPackage payload, out string error) { payload = null; error = string.Empty; string text = null; try { CleanupExpired(); if (envelope == null || envelope.Size() <= 0 || envelope.Size() > 300000) { error = "Invalid or oversized Server Log Monitor transport envelope."; return ServerLogTransportReceiveResult.Rejected; } int num = envelope.ReadInt(); byte b = envelope.ReadByte(); TransportFlags transportFlags = (TransportFlags)envelope.ReadByte(); long num2 = envelope.ReadLong(); int num3 = envelope.ReadInt(); int num4 = envelope.ReadInt(); int num5 = envelope.ReadInt(); byte[] array = envelope.ReadByteArray(); if (num != 1448102983) { error = "Unsupported Server Log Monitor transport envelope."; return ServerLogTransportReceiveResult.Rejected; } if (b != 1) { error = $"Unsupported Server Log Monitor transport version {b}; expected {(byte)1}."; return ServerLogTransportReceiveResult.Rejected; } if ((transportFlags & ~(TransportFlags.Compressed | TransportFlags.Fragmented)) != TransportFlags.None) { error = $"Unknown Server Log Monitor transport flags {(byte)transportFlags}."; return ServerLogTransportReceiveResult.Rejected; } if (num2 <= 0 || num5 <= 0 || num5 > 4194304 || num4 <= 0 || num4 > 64 || num3 < 0 || num3 >= num4 || array == null || array.Length == 0 || array.Length > 250000 || array.Length > num5) { error = "Invalid Server Log Monitor fragment metadata."; return ServerLogTransportReceiveResult.Rejected; } if ((transportFlags & TransportFlags.Fragmented) == 0) { if (num4 != 1 || num3 != 0 || array.Length != num5) { error = "Invalid unfragmented Server Log Monitor envelope."; return ServerLogTransportReceiveResult.Rejected; } return DecodePayload(transportFlags, array, out payload, out error); } int num6 = (num5 + 250000 - 1) / 250000; int num7 = ((num3 == num4 - 1) ? (num5 - 250000 * (num4 - 1)) : 250000); if (num4 <= 1 || num4 != num6 || num7 <= 0 || array.Length != num7) { error = "Invalid fragmented Server Log Monitor envelope shape."; return ServerLogTransportReceiveResult.Rejected; } text = sender + ":" + num2; byte[] array2 = null; lock (CacheLock) { if (!FragmentCache.TryGetValue(text, out var value)) { if (GetAssemblyCountForSender(sender) >= 4) { error = "Too many pending fragmented Server Log Monitor payloads from this sender."; return ServerLogTransportReceiveResult.Rejected; } if (GetCacheBytesForSender(sender) + array.Length > 8388608 || GetCacheBytesGlobal() + array.Length > 33554432) { error = "Server Log Monitor fragment cache limit exceeded."; return ServerLogTransportReceiveResult.Rejected; } value = new FragmentAssembly { Sender = sender, ExpectedFragments = num4, ExpectedBytes = num5, Flags = transportFlags, ExpiresUtcTicks = DateTime.UtcNow.Add(FragmentLifetime).Ticks }; FragmentCache.Add(text, value); } else if (value.ExpectedFragments != num4 || value.ExpectedBytes != num5 || value.Flags != transportFlags) { RemoveAssembly(text); error = "Inconsistent Server Log Monitor fragment metadata."; return ServerLogTransportReceiveResult.Rejected; } if (value.Fragments.ContainsKey(num3)) { RemoveAssembly(text); error = "Duplicate Server Log Monitor fragment received."; return ServerLogTransportReceiveResult.Rejected; } if (GetCacheBytesForSender(sender) + array.Length > 8388608 || GetCacheBytesGlobal() + array.Length > 33554432 || value.ReceivedBytes + array.Length > value.ExpectedBytes) { RemoveAssembly(text); error = "Server Log Monitor fragment cache size limit exceeded."; return ServerLogTransportReceiveResult.Rejected; } value.Fragments.Add(num3, array); value.ReceivedBytes += array.Length; value.ExpiresUtcTicks = DateTime.UtcNow.Add(FragmentLifetime).Ticks; if (value.Fragments.Count < value.ExpectedFragments) { return ServerLogTransportReceiveResult.WaitingForFragments; } if (value.ReceivedBytes != value.ExpectedBytes) { RemoveAssembly(text); error = "Completed Server Log Monitor fragment assembly has an invalid size."; return ServerLogTransportReceiveResult.Rejected; } array2 = new byte[value.ExpectedBytes]; int num8 = 0; for (int i = 0; i < value.ExpectedFragments; i++) { if (!value.Fragments.TryGetValue(i, out var value2)) { RemoveAssembly(text); error = "Completed Server Log Monitor fragment assembly is missing a fragment."; return ServerLogTransportReceiveResult.Rejected; } Buffer.BlockCopy(value2, 0, array2, num8, value2.Length); num8 += value2.Length; } RemoveAssembly(text); text = null; } return DecodePayload(transportFlags, array2, out payload, out error); } catch (Exception ex) { if (text != null) { lock (CacheLock) { RemoveAssembly(text); } } error = ex.GetType().Name + ": " + ex.Message; return ServerLogTransportReceiveResult.Rejected; } } internal static void Clear() { lock (CacheLock) { FragmentCache.Clear(); } } private static ServerLogTransportReceiveResult DecodePayload(TransportFlags flags, byte[] encoded, out ZPackage payload, out string error) { //IL_0050: Unknown result type (might be due to invalid IL or missing references) //IL_0056: Expected O, but got Unknown payload = null; error = string.Empty; try { byte[] array = (((flags & TransportFlags.Compressed) != TransportFlags.None) ? DecompressLimited(encoded, 4194304) : encoded); if (array.Length == 0 || array.Length > 4194304) { error = $"Decoded Server Log Monitor payload size {array.Length} is invalid."; return ServerLogTransportReceiveResult.Rejected; } payload = new ZPackage(array); return ServerLogTransportReceiveResult.Complete; } catch (Exception ex) { error = ex.GetType().Name + ": " + ex.Message; return ServerLogTransportReceiveResult.Rejected; } } private static byte[] GetPackageBytes(ZPackage package) { int num = package.Size(); byte[] array = package.GetArray(); if (array.Length == num) { return array; } if (array.Length < num) { throw new InvalidDataException($"ZPackage returned {array.Length} bytes for declared size {num}."); } byte[] array2 = new byte[num]; Buffer.BlockCopy(array, 0, array2, 0, num); return array2; } private static byte[] Compress(byte[] data) { using MemoryStream memoryStream = new MemoryStream(); using (DeflateStream deflateStream = new DeflateStream(memoryStream, CompressionLevel.Optimal, leaveOpen: true)) { deflateStream.Write(data, 0, data.Length); } return memoryStream.ToArray(); } private static byte[] DecompressLimited(byte[] data, int maximumBytes) { using MemoryStream stream = new MemoryStream(data, writable: false); using DeflateStream deflateStream = new DeflateStream(stream, CompressionMode.Decompress); using MemoryStream memoryStream = new MemoryStream(); byte[] array = new byte[81920]; while (true) { int num = deflateStream.Read(array, 0, array.Length); if (num <= 0) { break; } if (memoryStream.Length + num > maximumBytes) { throw new InvalidDataException($"Decompressed Server Log Monitor payload exceeds {maximumBytes} bytes."); } memoryStream.Write(array, 0, num); } return memoryStream.ToArray(); } private static void CleanupExpired() { long now = DateTime.UtcNow.Ticks; lock (CacheLock) { string[] array = (from pair in FragmentCache where pair.Value.ExpiresUtcTicks <= now select pair.Key).ToArray(); foreach (string key in array) { FragmentCache.Remove(key); } } } private static int GetAssemblyCountForSender(long sender) { return FragmentCache.Values.Count((FragmentAssembly assembly) => assembly.Sender == sender); } private static int GetCacheBytesForSender(long sender) { return FragmentCache.Values.Where((FragmentAssembly assembly) => assembly.Sender == sender).Sum((FragmentAssembly assembly) => assembly.ReceivedBytes); } private static int GetCacheBytesGlobal() { return FragmentCache.Values.Sum((FragmentAssembly assembly) => assembly.ReceivedBytes); } private static void RemoveAssembly(string cacheKey) { if (!string.IsNullOrEmpty(cacheKey)) { FragmentCache.Remove(cacheKey); } } } } namespace ValheimProfiler.Core { internal interface IProfilerTool { string Id { get; } string DisplayName { get; } bool IsWindowVisible { get; } bool IsActive { get; } void ShowWindow(); void ToggleWindow(); void Update(); void Shutdown(); } internal interface IProfilerToolAvailability { bool IsAvailable { get; } bool CanOpenWhenUnavailable { get; } string AvailabilityTooltip { get; } } internal static class ProfilerPaths { internal static string ConfigDirectory => Path.Combine(Paths.ConfigPath, "shudnal.ValheimProfiler"); internal static string GetConfigFilePath(string fileName) { Directory.CreateDirectory(ConfigDirectory); return Path.Combine(ConfigDirectory, fileName); } } internal sealed class ToolRegistry { private readonly List _tools = new List(); internal IReadOnlyList Tools => _tools; internal T Register(T tool) where T : class, IProfilerTool { if (tool == null) { throw new ArgumentNullException("tool"); } for (int i = 0; i < _tools.Count; i++) { if (string.Equals(_tools[i].Id, tool.Id, StringComparison.Ordinal)) { throw new InvalidOperationException("Profiler tool '" + tool.Id + "' is already registered."); } } _tools.Add(tool); return tool; } internal void Update() { for (int i = 0; i < _tools.Count; i++) { _tools[i].Update(); } } internal void Shutdown() { for (int num = _tools.Count - 1; num >= 0; num--) { _tools[num].Shutdown(); } _tools.Clear(); } } internal sealed class ValheimProfilerApp { private readonly ValheimProfilerConfig _config; private readonly ManualLogSource _logger; private readonly GuiScaleController _scale; private readonly ThemeManager _theme; private readonly TooltipManager _tooltips; private readonly WindowManager _windows; private readonly ToolRegistry _tools; private readonly ValheimCursorController _cursor; private readonly ValheimPauseController _pause; private ProfilerWindow _launcherWindow; private PatchProfilerTool _patchProfiler; private MonoBehaviourProfilerTool _monoBehaviourProfiler; private MonoBehaviourCallProfilerTool _monoBehaviourCallProfiler; private ValheimUpdateProfilerTool _valheimUpdateProfiler; private LogMonitorTool _logMonitor; private ServerLogMonitorTool _serverLogMonitor; private NetworkProfilerTool _networkProfiler; private bool _uiVisible; internal bool UiVisible => _uiVisible; internal bool IsDrawingUi { get; private set; } internal bool HasVisibleWindows => _uiVisible && _windows.HasRequestedVisibleWindows; internal bool ShouldBlockGameInput => HasVisibleWindows && _config.BlockGameInput.Value; internal bool ShouldBlockMouseInput => HasVisibleWindows && (_config.BlockGameInput.Value || _config.BlockMouseInput.Value); internal ValheimProfilerConfig Config => _config; internal GuiScaleController Scale => _scale; internal ThemeManager Theme => _theme; internal TooltipManager Tooltips => _tooltips; internal WindowManager Windows => _windows; internal ManualLogSource Logger => _logger; internal ServerLogMonitorTool ServerLogMonitor => _serverLogMonitor; internal NetworkProfilerTool NetworkProfiler => _networkProfiler; internal ValheimProfilerApp(ValheimProfilerConfig config, ManualLogSource logger) { _config = config; _logger = logger; _scale = new GuiScaleController(config); _theme = new ThemeManager(config); _tooltips = new TooltipManager(_theme); _windows = new WindowManager(config, _scale, _theme, _tooltips); _tools = new ToolRegistry(); _cursor = new ValheimCursorController(); _pause = new ValheimPauseController(config); } internal void Initialize() { //IL_0012: 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) //IL_0026: Unknown result type (might be due to invalid IL or missing references) //IL_0035: Unknown result type (might be due to invalid IL or missing references) _launcherWindow = _windows.Register(new ProfilerWindow("ValheimProfiler.Launcher", "Valheim Profiler 1.0.0", new Rect(ValheimProfilerConfig.DefaultLauncherPosition, new Vector2(470f, 48f)), new Vector2(320f, 42f), resizable: false, requestedVisible: true, DrawLauncher, _config.LauncherPosition, null, centerWhenPositionIsNegative: true, allowTooltipOverflow: true)); _logMonitor = new LogMonitorTool(this); _patchProfiler = _tools.Register(new PatchProfilerTool(this)); _monoBehaviourProfiler = _tools.Register(new MonoBehaviourProfilerTool(this)); _monoBehaviourCallProfiler = _tools.Register(new MonoBehaviourCallProfilerTool(this)); _valheimUpdateProfiler = _tools.Register(new ValheimUpdateProfilerTool(this)); _tools.Register(_logMonitor); _serverLogMonitor = _tools.Register(new ServerLogMonitorTool(this)); _networkProfiler = _tools.Register(new NetworkProfilerTool(this)); } internal void MarkGameWindowReady() { _scale.MarkGameWindowReady(); } internal void Update() { //IL_000c: Unknown result type (might be due to invalid IL or missing references) //IL_0011: Unknown result type (might be due to invalid IL or missing references) //IL_0039: Unknown result type (might be due to invalid IL or missing references) //IL_003e: 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) //IL_0062: Unknown result type (might be due to invalid IL or missing references) //IL_00b4: Unknown result type (might be due to invalid IL or missing references) //IL_00b9: Unknown result type (might be due to invalid IL or missing references) //IL_00d8: Unknown result type (might be due to invalid IL or missing references) //IL_00dd: Unknown result type (might be due to invalid IL or missing references) //IL_008f: 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_012f: Unknown result type (might be due to invalid IL or missing references) //IL_0134: Unknown result type (might be due to invalid IL or missing references) //IL_010a: Unknown result type (might be due to invalid IL or missing references) //IL_010f: Unknown result type (might be due to invalid IL or missing references) KeyboardShortcut value = _config.GlobalHotkey.Value; if (((KeyboardShortcut)(ref value)).IsDown()) { SetUiVisible(!_uiVisible); } value = _config.PatchProfilerHotkey.Value; HandleToolHotkey(((KeyboardShortcut)(ref value)).IsDown(), _patchProfiler); value = _config.MonoBehaviourCallProfilerHotkey.Value; bool flag = ((KeyboardShortcut)(ref value)).IsDown(); HandleToolHotkey(flag, _monoBehaviourCallProfiler); if (!flag) { value = _config.MonoBehaviourProfilerHotkey.Value; HandleToolHotkey(((KeyboardShortcut)(ref value)).IsDown(), _monoBehaviourProfiler); } value = _config.ValheimUpdateProfilerHotkey.Value; HandleToolHotkey(((KeyboardShortcut)(ref value)).IsDown(), _valheimUpdateProfiler); value = _config.ServerLogMonitorHotkey.Value; bool flag2 = ((KeyboardShortcut)(ref value)).IsDown(); HandleToolHotkey(flag2, _serverLogMonitor); if (!flag2) { value = _config.LogMonitorHotkey.Value; HandleToolHotkey(((KeyboardShortcut)(ref value)).IsDown(), _logMonitor); } value = _config.NetworkProfilerHotkey.Value; HandleToolHotkey(((KeyboardShortcut)(ref value)).IsDown(), _networkProfiler); _tools.Update(); _windows.UpdatePersistence(); _cursor.Update(HasVisibleWindows); _pause.Update(HasVisibleWindows); } internal void LateUpdate() { _cursor.LateUpdate(HasVisibleWindows); } internal void OnGUI() { //IL_0091: Unknown result type (might be due to invalid IL or missing references) //IL_009f: Unknown result type (might be due to invalid IL or missing references) //IL_001f: Unknown result type (might be due to invalid IL or missing references) //IL_0024: Unknown result type (might be due to invalid IL or missing references) //IL_002b: Unknown result type (might be due to invalid IL or missing references) //IL_0030: Unknown result type (might be due to invalid IL or missing references) //IL_0040: Unknown result type (might be due to invalid IL or missing references) //IL_0062: Unknown result type (might be due to invalid IL or missing references) if (!HasVisibleWindows) { return; } _theme.EnsureStyles(); Matrix4x4 matrix = GUI.matrix; GUISkin skin = GUI.skin; Color contentColor = GUI.contentColor; IsDrawingUi = true; try { GUI.matrix = _scale.Matrix; GUI.skin = _theme.Skin; GUI.contentColor = _theme.TextColor; UpdateLauncherSize(); _cursor.OnGUI(active: true); _windows.DrawAll(); } finally { GUI.contentColor = contentColor; GUI.skin = skin; GUI.matrix = matrix; IsDrawingUi = false; } } internal void ShowUi() { SetUiVisible(visible: true); } internal void SetUiVisible(bool visible) { if (_uiVisible != visible) { _uiVisible = visible; _cursor.Update(HasVisibleWindows); _pause.Update(HasVisibleWindows); } } internal void ReleaseCursorAndPause() { _cursor.Release(); _pause.Release(); } internal void OverrideCursorReleaseState(CursorLockMode lockState, bool visible) { //IL_0016: Unknown result type (might be due to invalid IL or missing references) if (HasVisibleWindows) { _cursor.OverrideReleaseState(lockState, visible); } } internal void Shutdown() { _tools.Shutdown(); _patchProfiler = null; _monoBehaviourProfiler = null; _monoBehaviourCallProfiler = null; _valheimUpdateProfiler = null; _logMonitor = null; _serverLogMonitor = null; _networkProfiler = null; _windows.SaveAll(); _windows.Shutdown(); ReleaseCursorAndPause(); _theme.Shutdown(); } private void HandleToolHotkey(bool pressed, IProfilerTool tool) { if (pressed && tool != null && !(tool is IProfilerToolAvailability { IsAvailable: false, CanOpenWhenUnavailable: false })) { bool uiVisible = _uiVisible; ShowUi(); if (uiVisible) { tool.ToggleWindow(); } else { tool.ShowWindow(); } } } private void DrawLauncher(int id) { //IL_01f3: Unknown result type (might be due to invalid IL or missing references) //IL_020d: Expected O, but got Unknown //IL_0268: Unknown result type (might be due to invalid IL or missing references) //IL_0282: Expected O, but got Unknown //IL_0122: Unknown result type (might be due to invalid IL or missing references) //IL_0133: Expected O, but got Unknown //IL_0135: Unknown result type (might be due to invalid IL or missing references) //IL_013a: Unknown result type (might be due to invalid IL or missing references) //IL_0183: Unknown result type (might be due to invalid IL or missing references) //IL_018c: Unknown result type (might be due to invalid IL or missing references) //IL_019b: Expected O, but got Unknown GUILayout.BeginHorizontal(Array.Empty()); for (int i = 0; i < _tools.Tools.Count; i++) { IProfilerTool profilerTool = _tools.Tools[i]; IProfilerToolAvailability profilerToolAvailability = profilerTool as IProfilerToolAvailability; bool flag = profilerToolAvailability?.IsAvailable ?? true; bool flag2 = profilerToolAvailability?.CanOpenWhenUnavailable ?? false; bool flag3 = flag || flag2; string text = (flag3 ? string.Empty : (profilerToolAvailability?.AvailabilityTooltip ?? string.Empty)); string text2 = (profilerTool.IsActive ? "Currently active and collecting data." : string.Empty); string text3 = (string.IsNullOrEmpty(text) ? text2 : (string.IsNullOrEmpty(text2) ? text : (text2 + "\n" + text))); string text4 = (profilerTool.IsActive ? ("● " + profilerTool.DisplayName) : profilerTool.DisplayName); bool enabled = GUI.enabled; GUI.enabled = enabled && flag3; GUIStyle val = (profilerTool.IsActive ? _theme.AccentButtonStyle : GUI.skin.button); bool flag4 = GUILayout.Toggle(profilerTool.IsWindowVisible, new GUIContent(text4, text3), val, Array.Empty()); Rect lastRect = GUILayoutUtility.GetLastRect(); GUI.enabled = enabled; if (flag3 && flag4 != profilerTool.IsWindowVisible) { profilerTool.ToggleWindow(); } else if (!flag3 && !string.IsNullOrEmpty(text)) { GUI.Label(lastRect, new GUIContent(string.Empty, text), GUIStyle.none); } } GUILayout.FlexibleSpace(); float launcherToggleWidth = GetLauncherToggleWidth("Prevent input"); bool flag5 = ProfilerGui.ToggleLayout(_theme, _config.BlockGameInput.Value, new GUIContent("Prevent input", "Block all Valheim gameplay input while profiler windows are visible. Disable this to keep playing while watching profiler results."), launcherToggleWidth, GUI.skin.label); if (flag5 != _config.BlockGameInput.Value) { _config.BlockGameInput.Value = flag5; } float launcherToggleWidth2 = GetLauncherToggleWidth("Prevent mouse"); bool flag6 = ProfilerGui.ToggleLayout(_theme, _config.BlockMouseInput.Value, new GUIContent("Prevent mouse", "Block gameplay mouse clicks, wheel and camera movement while keeping keyboard movement, inventory and other hotkeys active. Prevent input overrides this setting."), launcherToggleWidth2, GUI.skin.label); if (flag6 != _config.BlockMouseInput.Value) { _config.BlockMouseInput.Value = flag6; } if (GUILayout.Button("Reset layout", Array.Empty())) { _windows.RequestResetLayout(); } if (GUILayout.Button("Hide", Array.Empty())) { SetUiVisible(visible: false); } GUILayout.EndHorizontal(); } private void UpdateLauncherSize() { //IL_00e2: Unknown result type (might be due to invalid IL or missing references) //IL_00ec: Expected O, but got Unknown //IL_00e7: Unknown result type (might be due to invalid IL or missing references) //IL_0109: Unknown result type (might be due to invalid IL or missing references) //IL_0113: Expected O, but got Unknown //IL_010e: Unknown result type (might be due to invalid IL or missing references) //IL_0146: Unknown result type (might be due to invalid IL or missing references) //IL_014b: Unknown result type (might be due to invalid IL or missing references) //IL_0154: Unknown result type (might be due to invalid IL or missing references) //IL_0172: Unknown result type (might be due to invalid IL or missing references) //IL_0194: Unknown result type (might be due to invalid IL or missing references) //IL_0072: Unknown result type (might be due to invalid IL or missing references) //IL_007c: Expected O, but got Unknown //IL_0077: Unknown result type (might be due to invalid IL or missing references) if (_launcherWindow != null && !((Object)(object)GUI.skin == (Object)null)) { float num = 20f; for (int i = 0; i < _tools.Tools.Count; i++) { IProfilerTool profilerTool = _tools.Tools[i]; string text = (profilerTool.IsActive ? ("● " + profilerTool.DisplayName) : profilerTool.DisplayName); num += GUI.skin.button.CalcSize(new GUIContent(text)).x + 8f; } num += GetLauncherToggleWidth("Prevent input") + 8f; num += GetLauncherToggleWidth("Prevent mouse") + 8f; num += GUI.skin.button.CalcSize(new GUIContent("Reset layout")).x + 8f; num += GUI.skin.button.CalcSize(new GUIContent("Hide")).x + 8f; float num2 = Mathf.Max(18f, GUI.skin.button.lineHeight + 6f); Rect rect = _launcherWindow.Rect; ((Rect)(ref rect)).width = Mathf.Max(_launcherWindow.MinimumSize.x, num); ((Rect)(ref rect)).height = Mathf.Max(_launcherWindow.MinimumSize.y, num2 + 22f); _launcherWindow.Rect = rect; } } private float GetLauncherToggleWidth(string text) { //IL_0028: Unknown result type (might be due to invalid IL or missing references) //IL_0032: Expected O, but got Unknown //IL_002d: Unknown result type (might be due to invalid IL or missing references) GUISkin skin = GUI.skin; GUIStyle val = ((skin != null) ? skin.label : null); float num = ((val != null) ? val.CalcSize(new GUIContent(text ?? string.Empty)).x : 80f); return Mathf.Ceil(1f + _theme.CompactToggleSize + 4f + num + 4f); } } } namespace ValheimProfiler.Core.Profiling { internal sealed class LifetimeProfilerStat { private const double HistogramMinMs = 0.0005; private const double HistogramBase = 1.15; private const int HistogramBinCount = 256; private readonly object _sync = new object(); private readonly long[] _histogram = new long[256]; private long _calls; private double _totalMs; private double _maxMs; private double _lastMs; private float _firstCallAtSeconds; private float _lastCallAtSeconds; internal void Add(double elapsedMs, float elapsedSinceStart) { if (elapsedMs < 0.0) { return; } lock (_sync) { if (_calls == 0) { _firstCallAtSeconds = ((elapsedSinceStart < 0f) ? 0f : elapsedSinceStart); } _calls++; _totalMs += elapsedMs; _lastMs = elapsedMs; _lastCallAtSeconds = ((elapsedSinceStart < 0f) ? 0f : elapsedSinceStart); if (elapsedMs > _maxMs) { _maxMs = elapsedMs; } _histogram[MsToHistogramBin(elapsedMs)]++; } } internal LifetimeProfilerSnapshot GetSnapshot() { lock (_sync) { if (_calls <= 0) { return LifetimeProfilerSnapshot.Empty; } return new LifetimeProfilerSnapshot { Calls = _calls, TotalMs = _totalMs, AverageMs = _totalMs / (double)_calls, MaxMs = _maxMs, LastMs = _lastMs, P95Ms = GetPercentile(0.95), P99Ms = GetPercentile(0.99), FirstCallAtSeconds = _firstCallAtSeconds, LastCallAtSeconds = _lastCallAtSeconds }; } } internal void Reset() { lock (_sync) { _calls = 0L; _totalMs = 0.0; _maxMs = 0.0; _lastMs = 0.0; _firstCallAtSeconds = 0f; _lastCallAtSeconds = 0f; Array.Clear(_histogram, 0, _histogram.Length); } } private double GetPercentile(double percentile) { long num = Math.Max(1L, (long)Math.Ceiling((double)_calls * percentile)); long num2 = 0L; for (int i = 0; i < _histogram.Length; i++) { num2 += _histogram[i]; if (num2 >= num) { return HistogramBinToMs(i); } } return _maxMs; } private static int MsToHistogramBin(double ms) { if (ms <= 0.0005) { return 0; } double d = Math.Log(ms / 0.0005, 1.15); if (double.IsNaN(d) || double.IsInfinity(d)) { return 255; } return Math.Max(0, Math.Min(255, (int)Math.Floor(d))); } private static double HistogramBinToMs(int bin) { if (bin <= 0) { return 0.0005; } return 0.0005 * Math.Pow(1.15, bin + 1); } } internal struct LifetimeProfilerSnapshot { internal static readonly LifetimeProfilerSnapshot Empty = default(LifetimeProfilerSnapshot); internal long Calls; internal double TotalMs; internal double AverageMs; internal double MaxMs; internal double LastMs; internal double P95Ms; internal double P99Ms; internal float FirstCallAtSeconds; internal float LastCallAtSeconds; } internal sealed class RollingProfilerStat { private struct Bucket { public float Time; public double Ms; public int Calls; } private struct TimedSample { public int RealtimeSecond; public double Ms; public bool GcSample; } private struct HistogramEntry { public int Count; public double SumMs; } private sealed class AnalyticsBucket { public int RealtimeSecond = int.MinValue; public int SampleCount; public int GcSampleCount; public double SumMs; public double Top1Ms; public double Top2Ms; public double Top3Ms; public readonly Dictionary Histogram = new Dictionary(); public void Reset() { RealtimeSecond = int.MinValue; SampleCount = 0; GcSampleCount = 0; SumMs = 0.0; Top1Ms = 0.0; Top2Ms = 0.0; Top3Ms = 0.0; Histogram.Clear(); } public void AddTop(double value) { if (!(value <= 0.0)) { if (value > Top1Ms) { Top3Ms = Top2Ms; Top2Ms = Top1Ms; Top1Ms = value; } else if (value > Top2Ms) { Top3Ms = Top2Ms; Top2Ms = value; } else if (value > Top3Ms) { Top3Ms = value; } } } } private const float AvgWindowSec = 1f; private const int MaxWindowSeconds = 60; private const double HistogramMinMs = 0.0005; private const double HistogramBase = 1.15; private readonly object _sync = new object(); private int _frame = -1; private double _frameMs; private int _frameCalls; private readonly Queue _avgWindow = new Queue(); private double _avgWindowMs; private int _avgWindowCalls; private int _avgWindowFrames; private readonly Queue _pendingSamples = new Queue(); private readonly AnalyticsBucket[] _analyticsBuckets; private readonly SortedDictionary _totalHistogram = new SortedDictionary(); private long _totalSampleCount; private int _windowSampleCount; private int _gcWindowSampleCount; private bool _analyticsDirty; private RollingMaxSnapshot _maxSnapshot = RollingMaxSnapshot.Empty; public bool HasAnyAnalyticsData { get { lock (_sync) { return _windowSampleCount > 0 || _pendingSamples.Count > 0; } } } public RollingProfilerStat() { _analyticsBuckets = new AnalyticsBucket[60]; for (int i = 0; i < _analyticsBuckets.Length; i++) { _analyticsBuckets[i] = new AnalyticsBucket(); } } public bool Add(double ms, int frame, float now, bool gcSample) { lock (_sync) { SyncFrameInternal(frame, now); _frameMs += ms; _frameCalls++; _pendingSamples.Enqueue(new TimedSample { RealtimeSecond = Mathf.FloorToInt(now), Ms = ms, GcSample = gcSample }); _totalSampleCount++; _analyticsDirty = true; return _pendingSamples.Count == 1; } } public void ProcessBackgroundAnalytics(float now) { lock (_sync) { int num = Mathf.FloorToInt(now); bool flag = false; while (_pendingSamples.Count > 0) { TimedSample sample = _pendingSamples.Dequeue(); if (sample.RealtimeSecond >= num - 59) { AddSampleToAnalyticsWindow(sample); flag = true; } } if (ExpireOldAnalyticsBuckets(num)) { flag = true; } if (flag || _analyticsDirty) { RebuildMaxSnapshot(); _analyticsDirty = false; } } } public RollingProfilerSnapshot GetSnapshot(int frame, float now) { lock (_sync) { SyncFrameInternal(frame, now); return new RollingProfilerSnapshot { Avg1sMsPerFrame = ((_avgWindowFrames > 0) ? (_avgWindowMs / (double)_avgWindowFrames) : 0.0), Avg1sCallsPerFrame = ((_avgWindowFrames > 0) ? ((double)_avgWindowCalls / (double)_avgWindowFrames) : 0.0), Avg1sFrames = _avgWindowFrames, MaxSnapshot = _maxSnapshot }; } } public void Reset() { lock (_sync) { _frame = -1; _frameMs = 0.0; _frameCalls = 0; _avgWindow.Clear(); _avgWindowMs = 0.0; _avgWindowCalls = 0; _avgWindowFrames = 0; _pendingSamples.Clear(); _totalHistogram.Clear(); _totalSampleCount = 0L; _windowSampleCount = 0; _gcWindowSampleCount = 0; _analyticsDirty = false; _maxSnapshot = RollingMaxSnapshot.Empty; for (int i = 0; i < _analyticsBuckets.Length; i++) { _analyticsBuckets[i].Reset(); } } } private void SyncFrameInternal(int frame, float now) { if (_frame == -1) { _frame = frame; } if (frame != _frame) { CommitFrame(now); _frame = frame; _frameMs = 0.0; _frameCalls = 0; } while (_avgWindow.Count > 0 && now - _avgWindow.Peek().Time > 1f) { Bucket bucket = _avgWindow.Dequeue(); _avgWindowMs -= bucket.Ms; _avgWindowCalls -= bucket.Calls; _avgWindowFrames--; } if (_avgWindowFrames < 0) { _avgWindowFrames = 0; } } private void CommitFrame(float now) { Bucket item = new Bucket { Time = now, Ms = _frameMs, Calls = _frameCalls }; _avgWindow.Enqueue(item); _avgWindowMs += item.Ms; _avgWindowCalls += item.Calls; _avgWindowFrames++; } private void AddSampleToAnalyticsWindow(TimedSample sample) { int num = Mod(sample.RealtimeSecond, 60); AnalyticsBucket analyticsBucket = _analyticsBuckets[num]; if (analyticsBucket.RealtimeSecond != sample.RealtimeSecond) { ReplaceAnalyticsBucket(analyticsBucket, sample.RealtimeSecond); } int key = MsToHistogramBin(sample.Ms); analyticsBucket.SampleCount++; analyticsBucket.SumMs += sample.Ms; analyticsBucket.AddTop(sample.Ms); if (sample.GcSample) { analyticsBucket.GcSampleCount++; } if (analyticsBucket.Histogram.TryGetValue(key, out var value)) { value.Count++; value.SumMs += sample.Ms; analyticsBucket.Histogram[key] = value; } else { analyticsBucket.Histogram[key] = new HistogramEntry { Count = 1, SumMs = sample.Ms }; } if (_totalHistogram.TryGetValue(key, out var value2)) { value2.Count++; value2.SumMs += sample.Ms; _totalHistogram[key] = value2; } else { _totalHistogram[key] = new HistogramEntry { Count = 1, SumMs = sample.Ms }; } _windowSampleCount++; if (sample.GcSample) { _gcWindowSampleCount++; } } private void ReplaceAnalyticsBucket(AnalyticsBucket bucket, int newSecond) { if (bucket.RealtimeSecond != int.MinValue && bucket.SampleCount > 0) { RemoveBucketFromTotals(bucket); } bucket.Reset(); bucket.RealtimeSecond = newSecond; } private void RemoveBucketFromTotals(AnalyticsBucket bucket) { _windowSampleCount -= bucket.SampleCount; if (_windowSampleCount < 0) { _windowSampleCount = 0; } _gcWindowSampleCount -= bucket.GcSampleCount; if (_gcWindowSampleCount < 0) { _gcWindowSampleCount = 0; } foreach (KeyValuePair item in bucket.Histogram) { if (_totalHistogram.TryGetValue(item.Key, out var value)) { value.Count -= item.Value.Count; value.SumMs -= item.Value.SumMs; if (value.Count <= 0) { _totalHistogram.Remove(item.Key); } else { _totalHistogram[item.Key] = value; } } } } private bool ExpireOldAnalyticsBuckets(int currentSecond) { bool result = false; for (int i = 0; i < _analyticsBuckets.Length; i++) { AnalyticsBucket analyticsBucket = _analyticsBuckets[i]; if (analyticsBucket.RealtimeSecond != int.MinValue && currentSecond - analyticsBucket.RealtimeSecond >= 60) { RemoveBucketFromTotals(analyticsBucket); analyticsBucket.Reset(); result = true; } } return result; } private void RebuildMaxSnapshot() { if (_windowSampleCount <= 0 || _totalHistogram.Count == 0) { _maxSnapshot = new RollingMaxSnapshot { RawMaxMs = 0.0, SecondMaxMs = 0.0, ThirdMaxMs = 0.0, P95Ms = 0.0, P99Ms = 0.0, AboveP95Count = 0, AboveP99Count = 0, AvgAboveP95Ms = 0.0, AvgAboveP99Ms = 0.0, TotalSampleCount = _totalSampleCount, WindowSampleCount = 0, GcSampleCount = 0 }; return; } double top = 0.0; double top2 = 0.0; double top3 = 0.0; for (int i = 0; i < _analyticsBuckets.Length; i++) { AnalyticsBucket analyticsBucket = _analyticsBuckets[i]; AddTop(ref top, ref top2, ref top3, analyticsBucket.Top1Ms); AddTop(ref top, ref top2, ref top3, analyticsBucket.Top2Ms); AddTop(ref top, ref top2, ref top3, analyticsBucket.Top3Ms); } int num = Math.Max(1, Mathf.CeilToInt((float)_windowSampleCount * 0.95f)); int num2 = Math.Max(1, Mathf.CeilToInt((float)_windowSampleCount * 0.99f)); int num3 = 0; int num4 = 0; int num5 = 0; bool flag = false; bool flag2 = false; foreach (KeyValuePair item in _totalHistogram) { num3 += item.Value.Count; if (!flag && num3 >= num) { num4 = item.Key; flag = true; } if (!flag2 && num3 >= num2) { num5 = item.Key; flag2 = true; break; } } int num6 = 0; int num7 = 0; double num8 = 0.0; double num9 = 0.0; foreach (KeyValuePair item2 in _totalHistogram) { if (item2.Key > num4) { num6 += item2.Value.Count; num8 += item2.Value.SumMs; } if (item2.Key > num5) { num7 += item2.Value.Count; num9 += item2.Value.SumMs; } } _maxSnapshot = new RollingMaxSnapshot { RawMaxMs = top, SecondMaxMs = top2, ThirdMaxMs = top3, P95Ms = HistogramBinToMs(num4), P99Ms = HistogramBinToMs(num5), AboveP95Count = num6, AboveP99Count = num7, AvgAboveP95Ms = ((num6 > 0) ? (num8 / (double)num6) : 0.0), AvgAboveP99Ms = ((num7 > 0) ? (num9 / (double)num7) : 0.0), TotalSampleCount = _totalSampleCount, WindowSampleCount = _windowSampleCount, GcSampleCount = _gcWindowSampleCount }; } private static void AddTop(ref double top1, ref double top2, ref double top3, double value) { if (!(value <= 0.0)) { if (value > top1) { top3 = top2; top2 = top1; top1 = value; } else if (value > top2) { top3 = top2; top2 = value; } else if (value > top3) { top3 = value; } } } private static int MsToHistogramBin(double ms) { if (ms <= 0.0005) { return 0; } double num = Math.Log(ms / 0.0005, 1.15); if (double.IsNaN(num) || double.IsInfinity(num) || num < 0.0) { return 0; } return (int)Math.Floor(num); } private static double HistogramBinToMs(int bin) { return (bin <= 0) ? 0.0005 : (0.0005 * Math.Pow(1.15, bin)); } private static int Mod(int x, int m) { int num = x % m; return (num < 0) ? (num + m) : num; } } internal struct RollingProfilerSnapshot { public double Avg1sMsPerFrame; public double Avg1sCallsPerFrame; public int Avg1sFrames; public RollingMaxSnapshot MaxSnapshot; } internal struct RollingMaxSnapshot { public double RawMaxMs; public double SecondMaxMs; public double ThirdMaxMs; public double P95Ms; public double P99Ms; public int AboveP95Count; public int AboveP99Count; public double AvgAboveP95Ms; public double AvgAboveP99Ms; public long TotalSampleCount; public int WindowSampleCount; public int GcSampleCount; public static readonly RollingMaxSnapshot Empty = new RollingMaxSnapshot { RawMaxMs = 0.0, SecondMaxMs = 0.0, ThirdMaxMs = 0.0, P95Ms = 0.0, P99Ms = 0.0, AboveP95Count = 0, AboveP99Count = 0, AvgAboveP95Ms = 0.0, AvgAboveP99Ms = 0.0, TotalSampleCount = 0L, WindowSampleCount = 0, GcSampleCount = 0 }; public static RollingMaxSnapshot Aggregate(IEnumerable snapshots) { double top = 0.0; double top2 = 0.0; double top3 = 0.0; int num = 0; int num2 = 0; double num3 = 0.0; double num4 = 0.0; long num5 = 0L; int num6 = 0; int num7 = 0; double num8 = 0.0; double num9 = 0.0; foreach (RollingMaxSnapshot snapshot in snapshots) { AddTop(ref top, ref top2, ref top3, snapshot.RawMaxMs); AddTop(ref top, ref top2, ref top3, snapshot.SecondMaxMs); AddTop(ref top, ref top2, ref top3, snapshot.ThirdMaxMs); if (snapshot.P95Ms > num8) { num8 = snapshot.P95Ms; } if (snapshot.P99Ms > num9) { num9 = snapshot.P99Ms; } num += snapshot.AboveP95Count; num2 += snapshot.AboveP99Count; num3 += snapshot.AvgAboveP95Ms * (double)snapshot.AboveP95Count; num4 += snapshot.AvgAboveP99Ms * (double)snapshot.AboveP99Count; num5 += snapshot.TotalSampleCount; num6 += snapshot.WindowSampleCount; num7 += snapshot.GcSampleCount; } return new RollingMaxSnapshot { RawMaxMs = top, SecondMaxMs = top2, ThirdMaxMs = top3, P95Ms = num8, P99Ms = num9, AboveP95Count = num, AboveP99Count = num2, AvgAboveP95Ms = ((num > 0) ? (num3 / (double)num) : 0.0), AvgAboveP99Ms = ((num2 > 0) ? (num4 / (double)num2) : 0.0), TotalSampleCount = num5, WindowSampleCount = num6, GcSampleCount = num7 }; } private static void AddTop(ref double top1, ref double top2, ref double top3, double value) { if (!(value <= 0.0)) { if (value > top1) { top3 = top2; top2 = top1; top1 = value; } else if (value > top2) { top3 = top2; top2 = value; } else if (value > top3) { top3 = value; } } } } } namespace ValheimProfiler.Core.Logging { internal sealed class ParsedLogFileEntry { internal long FileOffset; internal DateTime Timestamp; internal LogLevel Level; internal string Source; internal string RawMessage; internal string Message; internal string Details; internal string Scene; internal int ThreadId; internal string Fingerprint => LogMonitorText.BuildFingerprint(Level, Source, Message, Details); } internal sealed class LogFilePage { internal readonly List Entries = new List(); internal long NextCursor; internal bool HasMore; internal long FileLength; internal long FileCreationUtcTicks; internal string Error; } internal static class LogFileHistoryReader { private sealed class LineInfo { internal long Offset; internal string Text; } private static readonly Regex ExtendedHeaderRegex = new Regex("^(?\\d{4}-\\d{2}-\\d{2} \\d{2}:\\d{2}:\\d{2}(?:\\.\\d{1,7})?)\\s+\\[(?[^:\\]]+):\\s*(?[^\\]]+)\\](?:\\s+\\[thread\\s+(?\\d+)\\])?(?:\\s+\\[scene\\s+(?[^\\]]*)\\])?\\s*(?.*)$", RegexOptions.Compiled | RegexOptions.CultureInvariant); private static readonly Regex StandardHeaderRegex = new Regex("^\\[(?[^:\\]]+):\\s*(?[^\\]]+)\\]\\s*(?.*)$", RegexOptions.Compiled | RegexOptions.CultureInvariant); internal static LogFilePage ReadOlder(string path, long beforeOffset, int maxEntries, int maxBytes) { LogFilePage logFilePage = new LogFilePage(); try { if (string.IsNullOrWhiteSpace(path) || !File.Exists(path)) { logFilePage.Error = "LogOutput.log was not found."; return logFilePage; } FileInfo fileInfo = new FileInfo(path); logFilePage.FileLength = fileInfo.Length; logFilePage.FileCreationUtcTicks = fileInfo.CreationTimeUtc.Ticks; if (beforeOffset > fileInfo.Length) { logFilePage.Error = "LogOutput.log changed while history was being paged."; return logFilePage; } long num = ((beforeOffset > 0) ? beforeOffset : fileInfo.Length); if (num <= 0) { logFilePage.NextCursor = 0L; logFilePage.HasMore = false; return logFilePage; } int num2 = Math.Max(65536, maxBytes); long num3 = Math.Max(0L, num - num2); int num4 = checked((int)(num - num3)); byte[] array = new byte[num4]; using (FileStream fileStream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite | FileShare.Delete)) { fileStream.Position = num3; int i; int num5; for (i = 0; i < num4; i += num5) { num5 = fileStream.Read(array, i, num4 - i); if (num5 <= 0) { break; } } if (i != array.Length) { Array.Resize(ref array, i); } } List lines = DecodeLines(array, num3, num3 > 0); List list = ParseLines(lines, fileInfo.LastWriteTime); if (list.Count == 0) { logFilePage.NextCursor = num3; logFilePage.HasMore = num3 > 0; return logFilePage; } int num6 = Math.Max(1, maxEntries); int num7 = Math.Max(0, list.Count - num6); for (int j = num7; j < list.Count; j++) { logFilePage.Entries.Add(list[j]); } logFilePage.NextCursor = ((logFilePage.Entries.Count > 0) ? logFilePage.Entries[0].FileOffset : num3); logFilePage.HasMore = logFilePage.NextCursor > 0; return logFilePage; } catch (Exception ex) { logFilePage.Error = ex.GetType().Name + ": " + ex.Message; return logFilePage; } } private static List DecodeLines(byte[] data, long absoluteStart, bool discardFirstPartialLine) { List list = new List(); if (data == null || data.Length == 0) { return list; } int i = 0; if (discardFirstPartialLine) { for (; i < data.Length && data[i] != 10; i++) { } if (i < data.Length) { i++; } } int num = i; for (int j = i; j <= data.Length; j++) { if (j >= data.Length || data[j] == 10) { int num2 = j; if (num2 > num && data[num2 - 1] == 13) { num2--; } string text = Encoding.UTF8.GetString(data, num, Math.Max(0, num2 - num)); list.Add(new LineInfo { Offset = absoluteStart + num, Text = text }); num = j + 1; } } return list; } private static List ParseLines(List lines, DateTime fallbackTimestamp) { List entries = new List(); ParsedLogFileEntry current = null; StringBuilder continuation = new StringBuilder(); for (int i = 0; i < lines.Count; i++) { LineInfo lineInfo = lines[i]; if (TryParseHeader(lineInfo, out var entry)) { FinishCurrent(); current = entry; } else if (current != null) { if (continuation.Length > 0) { continuation.Append('\n'); } continuation.Append(lineInfo.Text); } } FinishCurrent(); return entries; void FinishCurrent() { if (current != null) { string text = continuation.ToString(); if (string.IsNullOrEmpty(current.RawMessage) && !string.IsNullOrEmpty(text)) { int num = text.IndexOf('\n'); if (num >= 0) { current.RawMessage = text.Substring(0, num); current.Details = text.Substring(num + 1); } else { current.RawMessage = text; current.Details = string.Empty; } } else { current.Details = text; } ParsedLogFileEntry parsedLogFileEntry = current; if (parsedLogFileEntry.RawMessage == null) { parsedLogFileEntry.RawMessage = string.Empty; } parsedLogFileEntry = current; if (parsedLogFileEntry.Details == null) { parsedLogFileEntry.Details = string.Empty; } current.Message = LogMonitorText.NormalizeMessage(current.Source, current.RawMessage, out var parsedTimestamp); if (current.Timestamp == default(DateTime) && parsedTimestamp != default(DateTime)) { current.Timestamp = parsedTimestamp; } if (current.Timestamp == default(DateTime)) { current.Timestamp = fallbackTimestamp; } entries.Add(current); current = null; continuation.Clear(); } } } private static bool TryParseHeader(LineInfo line, out ParsedLogFileEntry entry) { //IL_0122: Unknown result type (might be due to invalid IL or missing references) //IL_0127: Unknown result type (might be due to invalid IL or missing references) entry = null; string text = line.Text ?? string.Empty; if (line.Offset == 0L && text.Length > 0 && text[0] == '\ufeff') { text = text.TrimStart(new char[1] { '\ufeff' }); } Match match = ExtendedHeaderRegex.Match(text); bool success = match.Success; if (!success) { match = StandardHeaderRegex.Match(text); } if (!match.Success) { return false; } DateTime result = default(DateTime); if (success) { DateTime.TryParse(match.Groups["timestamp"].Value, CultureInfo.InvariantCulture, DateTimeStyles.AssumeLocal, out result); } int result2 = 0; if (success) { int.TryParse(match.Groups["thread"].Value, NumberStyles.Integer, CultureInfo.InvariantCulture, out result2); } entry = new ParsedLogFileEntry { FileOffset = line.Offset, Timestamp = result, Level = LogMonitorText.ParseLevel(match.Groups["level"].Value), Source = match.Groups["source"].Value.Trim(), RawMessage = match.Groups["message"].Value, Scene = (success ? match.Groups["scene"].Value : string.Empty), ThreadId = result2 }; return true; } } internal static class LogHistoryMerge { internal static int FindOverlap(IReadOnlyList older, IReadOnlyList newer, Func olderFingerprint, Func newerFingerprint, int minimumRun = 3) { if (older == null || newer == null || older.Count == 0 || newer.Count == 0) { return -1; } int num = Math.Max(1, Math.Min(minimumRun, Math.Min(older.Count, newer.Count))); int num2 = Math.Max(0, older.Count - 512); int num3 = Math.Min(newer.Count, 128); for (int i = 0; i < num3; i++) { string a = newerFingerprint(newer[i]); for (int j = num2; j < older.Count; j++) { if (string.Equals(a, olderFingerprint(older[j]), StringComparison.Ordinal)) { int k; for (k = 1; j + k < older.Count && i + k < newer.Count && string.Equals(olderFingerprint(older[j + k]), newerFingerprint(newer[i + k]), StringComparison.Ordinal); k++) { } if (k >= num) { return j; } } } } return -1; } } internal static class LogMonitorText { private static readonly Regex UnityTimestampRegex = new Regex("^(?\\d{2}/\\d{2}/\\d{4} \\d{2}:\\d{2}:\\d{2}):\\s*", RegexOptions.Compiled | RegexOptions.CultureInvariant); internal static string NormalizeMessage(string source, string rawMessage, out DateTime parsedTimestamp) { parsedTimestamp = default(DateTime); string text = rawMessage ?? string.Empty; if (!string.Equals(source?.Trim(), "Unity Log", StringComparison.OrdinalIgnoreCase)) { return text; } Match match = UnityTimestampRegex.Match(text); if (!match.Success) { return text; } DateTime.TryParseExact(match.Groups["timestamp"].Value, "MM/dd/yyyy HH:mm:ss", CultureInfo.InvariantCulture, DateTimeStyles.AssumeLocal, out parsedTimestamp); return text.Substring(match.Length); } internal static string BuildFingerprint(LogLevel level, string source, string message, string details) { //IL_0009: Unknown result type (might be due to invalid IL or missing references) return GetLevelText(level) + "\n" + (source ?? string.Empty).Trim() + "\n" + NormalizeFingerprintText(message) + "\n" + NormalizeFingerprintText(details); } internal static string NormalizeFingerprintText(string value) { if (string.IsNullOrWhiteSpace(value)) { return string.Empty; } return value.Replace("\r\n", "\n").Replace('\r', '\n').Trim(); } internal unsafe static string GetLevelText(LogLevel level) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0003: Unknown result type (might be due to invalid IL or missing references) //IL_0005: Invalid comparison between Unknown and I4 //IL_0013: 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_0017: Invalid comparison between Unknown and I4 //IL_0025: Unknown result type (might be due to invalid IL or missing references) //IL_0027: Unknown result type (might be due to invalid IL or missing references) //IL_0029: Invalid comparison between Unknown and I4 //IL_0037: Unknown result type (might be due to invalid IL or missing references) //IL_0039: Unknown result type (might be due to invalid IL or missing references) //IL_003b: Invalid comparison between Unknown and I4 //IL_004b: Unknown result type (might be due to invalid IL or missing references) //IL_004e: Unknown result type (might be due to invalid IL or missing references) //IL_0050: Invalid comparison between Unknown and I4 //IL_0060: Unknown result type (might be due to invalid IL or missing references) //IL_0063: Unknown result type (might be due to invalid IL or missing references) //IL_0065: Invalid comparison between Unknown and I4 if ((level & 1) > 0) { return "Fatal"; } if ((level & 2) > 0) { return "Error"; } if ((level & 4) > 0) { return "Warning"; } if ((level & 8) > 0) { return "Message"; } if ((level & 0x10) > 0) { return "Info"; } if ((level & 0x20) > 0) { return "Debug"; } return ((object)(*(LogLevel*)(&level))/*cast due to .constrained prefix*/).ToString(); } internal static LogLevel ParseLevel(string value) { //IL_0022: Unknown result type (might be due to invalid IL or missing references) //IL_003b: Unknown result type (might be due to invalid IL or missing references) //IL_00d4: Unknown result type (might be due to invalid IL or missing references) //IL_0065: Unknown result type (might be due to invalid IL or missing references) //IL_007b: Unknown result type (might be due to invalid IL or missing references) //IL_0092: Unknown result type (might be due to invalid IL or missing references) //IL_00a9: Unknown result type (might be due to invalid IL or missing references) //IL_00bf: Unknown result type (might be due to invalid IL or missing references) //IL_00d0: Unknown result type (might be due to invalid IL or missing references) //IL_00d1: Unknown result type (might be due to invalid IL or missing references) string text = (value ?? string.Empty).Trim(); if (text.Equals("Fatal", StringComparison.OrdinalIgnoreCase)) { return (LogLevel)1; } if (text.Equals("Error", StringComparison.OrdinalIgnoreCase)) { return (LogLevel)2; } if (text.Equals("Warning", StringComparison.OrdinalIgnoreCase) || text.Equals("Warn", StringComparison.OrdinalIgnoreCase)) { return (LogLevel)4; } if (text.Equals("Message", StringComparison.OrdinalIgnoreCase)) { return (LogLevel)8; } if (text.Equals("Info", StringComparison.OrdinalIgnoreCase)) { return (LogLevel)16; } if (text.Equals("Debug", StringComparison.OrdinalIgnoreCase)) { return (LogLevel)32; } if (text.Equals("None", StringComparison.OrdinalIgnoreCase)) { return (LogLevel)0; } LogLevel result; return (LogLevel)((!Enum.TryParse(text, ignoreCase: true, out result)) ? 8 : ((int)result)); } internal static bool IsChainloaderStartupComplete(string source, string message) { return string.Equals(source?.Trim(), "BepInEx", StringComparison.OrdinalIgnoreCase) && string.Equals(message?.Trim(), "Chainloader startup complete", StringComparison.Ordinal); } } } namespace ValheimProfiler.Configuration { internal sealed class ValheimProfilerConfig { internal static readonly Vector2 DefaultLauncherPosition = new Vector2(-1f, 9f); internal static readonly Vector2 DefaultPatchWindowPosition = new Vector2(80f, 80f); internal static readonly Vector2 DefaultPatchWindowSize = new Vector2(-1f, 600f); internal static readonly Vector2 DefaultTranspilerWindowPosition = new Vector2(120f, 120f); internal static readonly Vector2 DefaultMonoBehaviourWindowPosition = new Vector2(110f, 90f); internal static readonly Vector2 DefaultMonoBehaviourCallWindowPosition = new Vector2(140f, 110f); internal static readonly Vector2 DefaultValheimUpdateWindowPosition = new Vector2(155f, 120f); internal static readonly Vector2 DefaultLogMonitorWindowPosition = new Vector2(170f, 130f); internal static readonly Vector2 DefaultServerLogMonitorWindowPosition = new Vector2(200f, 150f); internal static readonly Vector2 DefaultNetworkProfilerWindowPosition = new Vector2(230f, 170f); internal static readonly Vector2 DefaultTranspilerWindowSize = new Vector2(-1f, 520f); internal static readonly Vector2 DefaultMonoBehaviourWindowSize = new Vector2(-1f, 650f); internal static readonly Vector2 DefaultMonoBehaviourCallWindowSize = new Vector2(-1f, 660f); internal static readonly Vector2 DefaultValheimUpdateWindowSize = new Vector2(-1f, 650f); internal static readonly Vector2 DefaultLogMonitorWindowSize = new Vector2(-1f, 680f); internal static readonly Vector2 DefaultServerLogMonitorWindowSize = new Vector2(-1f, 680f); internal static readonly Vector2 DefaultNetworkProfilerWindowSize = new Vector2(-1f, 700f); internal ConfigFile ConfigFile { get; } internal ConfigEntry GlobalHotkey { get; } internal ConfigEntry PatchProfilerHotkey { get; } internal ConfigEntry MonoBehaviourProfilerHotkey { get; } internal ConfigEntry MonoBehaviourCallProfilerHotkey { get; } internal ConfigEntry LogMonitorHotkey { get; } internal ConfigEntry ServerLogMonitorHotkey { get; } internal ConfigEntry ValheimUpdateProfilerHotkey { get; } internal ConfigEntry NetworkProfilerHotkey { get; } internal ConfigEntry BlockGameInput { get; } internal ConfigEntry BlockMouseInput { get; } internal ConfigEntry PauseGame { get; } internal ConfigEntry UseValheimGuiScale { get; } internal ConfigEntry UiScale { get; } internal ConfigEntry FontSize { get; } internal ConfigEntry MonoBehaviourIncludeValheimProfilerCallbacks { get; } internal ConfigEntry PatchProfilerAvgSortColumn { get; } internal ConfigEntry PatchProfilerMaxSortColumn { get; } internal ConfigEntry MonoBehaviourProfilerAvgSortColumn { get; } internal ConfigEntry MonoBehaviourProfilerMaxSortColumn { get; } internal ConfigEntry MonoBehaviourCallIncludeValheimProfilerCallbacks { get; } internal ConfigEntry MonoBehaviourCallProfilerSortColumn { get; } internal ConfigEntry ValheimUpdateProfilerAvgSortColumn { get; } internal ConfigEntry ValheimUpdateProfilerMaxSortColumn { get; } internal ConfigEntry LogMonitorMaxEntries { get; } internal ConfigEntry LogMonitorMaxIssueGroups { get; } internal ConfigEntry LogMonitorIssueSortColumn { get; } internal ConfigEntry LogMonitorHistoryPageEntries { get; } internal ConfigEntry LogMonitorCopyMetadata { get; } internal ConfigEntry ServerLogRecentEntries { get; } internal ConfigEntry ServerLogInitialEntries { get; } internal ConfigEntry ServerLogHistoryPageEntries { get; } internal ConfigEntry ServerLogClientMaxEntries { get; } internal ConfigEntry ServerLogMaxIssueGroups { get; } internal ConfigEntry ServerLogIssueSortColumn { get; } internal ConfigEntry ServerLogCopyMetadata { get; } internal ConfigEntry NetworkRpcSortColumn { get; } internal ConfigEntry NetworkZdoPrefabSortColumn { get; } internal ConfigEntry NetworkZdoInstanceSortColumn { get; } internal ConfigEntry NetworkZdoKeySortColumn { get; } internal ConfigEntry NetworkPeerSortColumn { get; } internal ConfigEntry NetworkRoutingErrorSortColumn { get; } internal ConfigEntry WindowBackground { get; } internal ConfigEntry WindowBorder { get; } internal ConfigEntry EntryBackground { get; } internal ConfigEntry TextColor { get; } internal ConfigEntry HeaderTextColor { get; } internal ConfigEntry ButtonBackground { get; } internal ConfigEntry ButtonTextColor { get; } internal ConfigEntry AccentColor { get; } internal ConfigEntry LauncherPosition { get; } internal ConfigEntry PatchWindowPosition { get; } internal ConfigEntry PatchWindowSize { get; } internal ConfigEntry TranspilerWindowPosition { get; } internal ConfigEntry TranspilerWindowSize { get; } internal ConfigEntry MonoBehaviourWindowPosition { get; } internal ConfigEntry MonoBehaviourWindowSize { get; } internal ConfigEntry MonoBehaviourCallWindowPosition { get; } internal ConfigEntry MonoBehaviourCallWindowSize { get; } internal ConfigEntry ValheimUpdateWindowPosition { get; } internal ConfigEntry ValheimUpdateWindowSize { get; } internal ConfigEntry LogMonitorWindowPosition { get; } internal ConfigEntry LogMonitorWindowSize { get; } internal ConfigEntry ServerLogMonitorWindowPosition { get; } internal ConfigEntry ServerLogMonitorWindowSize { get; } internal ConfigEntry NetworkProfilerWindowPosition { get; } internal ConfigEntry NetworkProfilerWindowSize { get; } internal ValheimProfilerConfig(ConfigFile config) { //IL_0025: Unknown result type (might be due to invalid IL or missing references) //IL_004f: Unknown result type (might be due to invalid IL or missing references) //IL_0075: Unknown result type (might be due to invalid IL or missing references) //IL_009b: Unknown result type (might be due to invalid IL or missing references) //IL_00c5: Unknown result type (might be due to invalid IL or missing references) //IL_00ef: Unknown result type (might be due to invalid IL or missing references) //IL_0115: Unknown result type (might be due to invalid IL or missing references) //IL_013f: Unknown result type (might be due to invalid IL or missing references) //IL_01ed: Unknown result type (might be due to invalid IL or missing references) //IL_01f7: Expected O, but got Unknown //IL_021d: Unknown result type (might be due to invalid IL or missing references) //IL_0227: Expected O, but got Unknown //IL_036e: Unknown result type (might be due to invalid IL or missing references) //IL_0378: Expected O, but got Unknown //IL_03a4: Unknown result type (might be due to invalid IL or missing references) //IL_03ae: Expected O, but got Unknown //IL_03fa: Unknown result type (might be due to invalid IL or missing references) //IL_0404: Expected O, but got Unknown //IL_044f: Unknown result type (might be due to invalid IL or missing references) //IL_0459: Expected O, but got Unknown //IL_0485: Unknown result type (might be due to invalid IL or missing references) //IL_048f: Expected O, but got Unknown //IL_04bb: Unknown result type (might be due to invalid IL or missing references) //IL_04c5: Expected O, but got Unknown //IL_04f4: Unknown result type (might be due to invalid IL or missing references) //IL_04fe: Expected O, but got Unknown //IL_052a: Unknown result type (might be due to invalid IL or missing references) //IL_0534: Expected O, but got Unknown //IL_0655: Unknown result type (might be due to invalid IL or missing references) //IL_0689: Unknown result type (might be due to invalid IL or missing references) //IL_06bd: Unknown result type (might be due to invalid IL or missing references) //IL_06f1: Unknown result type (might be due to invalid IL or missing references) //IL_0711: Unknown result type (might be due to invalid IL or missing references) //IL_0745: Unknown result type (might be due to invalid IL or missing references) //IL_0765: Unknown result type (might be due to invalid IL or missing references) //IL_0799: Unknown result type (might be due to invalid IL or missing references) //IL_07b9: Unknown result type (might be due to invalid IL or missing references) //IL_07d9: Unknown result type (might be due to invalid IL or missing references) //IL_07f9: Unknown result type (might be due to invalid IL or missing references) //IL_0819: Unknown result type (might be due to invalid IL or missing references) //IL_0839: Unknown result type (might be due to invalid IL or missing references) //IL_0859: Unknown result type (might be due to invalid IL or missing references) //IL_0879: Unknown result type (might be due to invalid IL or missing references) //IL_0899: Unknown result type (might be due to invalid IL or missing references) //IL_08b9: Unknown result type (might be due to invalid IL or missing references) //IL_08d9: Unknown result type (might be due to invalid IL or missing references) //IL_08f9: Unknown result type (might be due to invalid IL or missing references) //IL_0919: Unknown result type (might be due to invalid IL or missing references) //IL_0939: Unknown result type (might be due to invalid IL or missing references) //IL_0959: Unknown result type (might be due to invalid IL or missing references) //IL_0979: Unknown result type (might be due to invalid IL or missing references) //IL_0999: Unknown result type (might be due to invalid IL or missing references) //IL_09b9: Unknown result type (might be due to invalid IL or missing references) ConfigFile = config; GlobalHotkey = config.Bind("General", "Toggle UI", new KeyboardShortcut((KeyCode)288, Array.Empty()), "Show or hide the Valheim Profiler user interface without stopping active profilers."); PatchProfilerHotkey = config.Bind("General", "Toggle Patch Profiler", new KeyboardShortcut((KeyCode)289, Array.Empty()), "Show or hide the Patch Profiler window."); MonoBehaviourProfilerHotkey = config.Bind("General", "Toggle MonoBehaviour Frame Profiler", new KeyboardShortcut((KeyCode)0, Array.Empty()), "Show or hide the MonoBehaviour Frame Profiler window. No default shortcut is assigned."); MonoBehaviourCallProfilerHotkey = config.Bind("General", "Toggle MonoBehaviour Call Profiler", new KeyboardShortcut((KeyCode)0, Array.Empty()), "Show or hide the MonoBehaviour Call Profiler window. No default shortcut is assigned."); LogMonitorHotkey = config.Bind("General", "Toggle Client Log Monitor", new KeyboardShortcut((KeyCode)290, Array.Empty()), "Show or hide the client Log Monitor window."); ServerLogMonitorHotkey = config.Bind("General", "Toggle Server Log Monitor", new KeyboardShortcut((KeyCode)291, Array.Empty()), "Show or hide the remote dedicated-server Log Monitor window."); ValheimUpdateProfilerHotkey = config.Bind("General", "Toggle Valheim Update Profiler", new KeyboardShortcut((KeyCode)0, Array.Empty()), "Show or hide the Valheim Update Profiler window. No default shortcut is assigned."); NetworkProfilerHotkey = config.Bind("General", "Toggle Network Profiler", new KeyboardShortcut((KeyCode)292, Array.Empty()), "Show or hide the dedicated-server Network Profiler window."); BlockGameInput = config.Bind("Valheim", "Block game input", true, "Block all Valheim gameplay input while at least one profiler window is visible. IMGUI input and profiler hotkeys remain available."); BlockMouseInput = config.Bind("Valheim", "Block mouse input", false, "Block gameplay mouse buttons, wheel and camera movement while profiler windows are visible, while leaving keyboard movement, inventory and other hotkeys available. Full input blocking takes precedence."); PauseGame = config.Bind("Valheim", "Pause game", false, "Pause the game, when possible, while profiler windows are visible."); UseValheimGuiScale = config.Bind("Interface", "Use Valheim GUI scaling", true, "Multiply the profiler scale by Valheim's Accessibility - Scale GUI setting."); UiScale = config.Bind("Interface", "Scale", 1f, new ConfigDescription("Additional profiler UI scale.", (AcceptableValueBase)(object)new AcceptableValueRange(0.6f, 2f), Array.Empty())); FontSize = config.Bind("Interface", "Font size", 12, new ConfigDescription("Base IMGUI font size.", (AcceptableValueBase)(object)new AcceptableValueRange(9, 28), Array.Empty())); MonoBehaviourIncludeValheimProfilerCallbacks = config.Bind("MonoBehaviour Frame Profiler", "Include Valheim Profiler callbacks", true, "Include Valheim Profiler plugin callbacks in MonoBehaviour discovery so the profiler can profile itself. Refresh the behaviour list after changing this setting."); PatchProfilerAvgSortColumn = config.Bind("Patch Profiler", "Over 1 sec sort column", "AvgMsPerFrame", "Descending sort column restored when the Patch Profiler opens the Over 1 sec view."); PatchProfilerMaxSortColumn = config.Bind("Patch Profiler", "Max over 60 sec sort column", "ThirdMax", "Descending sort column restored when the Patch Profiler opens the Max over 60 sec view."); MonoBehaviourProfilerAvgSortColumn = config.Bind("MonoBehaviour Frame Profiler", "Over 1 sec sort column", "AvgMsPerFrame", "Descending sort column restored when the MonoBehaviour Frame Profiler opens the Over 1 sec view."); MonoBehaviourProfilerMaxSortColumn = config.Bind("MonoBehaviour Frame Profiler", "Max over 60 sec sort column", "ThirdMax", "Descending sort column restored when the MonoBehaviour Frame Profiler opens the Max over 60 sec view."); MonoBehaviourCallIncludeValheimProfilerCallbacks = config.Bind("MonoBehaviour Call Profiler", "Include Valheim Profiler callbacks", true, "Include Valheim Profiler plugin lifecycle and declared methods in MonoBehaviour Call Profiler discovery so the profiler can profile itself. Refresh the call list after changing this setting."); MonoBehaviourCallProfilerSortColumn = config.Bind("MonoBehaviour Call Profiler", "Sort column", "Total", "Descending sort column restored when the MonoBehaviour Call Profiler opens."); ValheimUpdateProfilerAvgSortColumn = config.Bind("Valheim Update Profiler", "Over 1 sec sort column", "MsOneSecond", "Descending sort column restored when the Valheim Update Profiler opens the Over 1 sec view."); ValheimUpdateProfilerMaxSortColumn = config.Bind("Valheim Update Profiler", "Max over 60 sec sort column", "RawMax", "Descending sort column restored when the Valheim Update Profiler opens the Max over 60 sec view."); LogMonitorMaxEntries = config.Bind("Log Monitor", "Maximum captured entries", 5000, new ConfigDescription("Maximum number of raw client log entries kept in memory. Older entries are discarded in batches.", (AcceptableValueBase)(object)new AcceptableValueRange(500, 50000), Array.Empty())); LogMonitorMaxIssueGroups = config.Bind("Log Monitor", "Maximum issue groups", 1000, new ConfigDescription("Maximum number of distinct Warning, Error and Fatal groups kept since the last Clear.", (AcceptableValueBase)(object)new AcceptableValueRange(100, 10000), Array.Empty())); LogMonitorIssueSortColumn = config.Bind("Log Monitor", "Issue sort column", "Count", "Descending sort column restored when the Log Monitor opens the Issues view."); LogMonitorHistoryPageEntries = config.Bind("Log Monitor", "History page entries", 1000, new ConfigDescription("Maximum number of parsed LogOutput.log entries loaded by one Load older request.", (AcceptableValueBase)(object)new AcceptableValueRange(100, 10000), Array.Empty())); LogMonitorCopyMetadata = config.Bind("Log Monitor", "Copy metadata", false, "Include timestamp, thread, scene and history metadata when copying client log rows. Disabled output stays close to BepInEx LogOutput.log format."); ServerLogRecentEntries = config.Bind("Server Log Monitor", "Recent entries", 5000, new ConfigDescription("Maximum live server log entries retained for remote subscribers.", (AcceptableValueBase)(object)new AcceptableValueRange(500, 50000), Array.Empty())); ServerLogInitialEntries = config.Bind("Server Log Monitor", "Initial snapshot entries", 1000, new ConfigDescription("Maximum recent entries sent when an admin subscribes or a live gap requires resynchronization.", (AcceptableValueBase)(object)new AcceptableValueRange(100, 10000), Array.Empty())); ServerLogHistoryPageEntries = config.Bind("Server Log Monitor", "History page entries", 1000, new ConfigDescription("Maximum historical entries returned by one remote Load older request.", (AcceptableValueBase)(object)new AcceptableValueRange(100, 10000), Array.Empty())); ServerLogClientMaxEntries = config.Bind("Server Log Monitor", "Client live entry limit", 5000, new ConfigDescription("Maximum live server entries retained by the client window. Manually loaded history is stored beyond this limit.", (AcceptableValueBase)(object)new AcceptableValueRange(500, 50000), Array.Empty())); ServerLogMaxIssueGroups = config.Bind("Server Log Monitor", "Maximum issue groups", 1000, new ConfigDescription("Maximum number of distinct server Warning, Error and Fatal groups retained in the client window.", (AcceptableValueBase)(object)new AcceptableValueRange(100, 10000), Array.Empty())); ServerLogIssueSortColumn = config.Bind("Server Log Monitor", "Issue sort column", "Count", "Descending sort column restored when the Server Log Monitor opens the Issues view."); ServerLogCopyMetadata = config.Bind("Server Log Monitor", "Copy metadata", false, "Include timestamp, thread, scene, sequence and history metadata when copying server log rows. Disabled output stays close to BepInEx LogOutput.log format."); NetworkRpcSortColumn = config.Bind("Network Profiler", "RPC sort column", "HandlerMs", "Descending sort column restored for the RPC table."); NetworkZdoPrefabSortColumn = config.Bind("Network Profiler", "ZDO prefab sort column", "SentBytes", "Descending sort column restored for the ZDO By prefab table."); NetworkZdoInstanceSortColumn = config.Bind("Network Profiler", "ZDO instance sort column", "SentBytes", "Descending sort column restored for the Top ZDOs table."); NetworkZdoKeySortColumn = config.Bind("Network Profiler", "ZDO key sort column", "Mutations", "Descending sort column restored for the ZDO Keys table."); NetworkPeerSortColumn = config.Bind("Network Profiler", "Peer sort column", "SendQueue", "Descending sort column restored for the Peers / Transport table."); NetworkRoutingErrorSortColumn = config.Bind("Network Profiler", "Routing error sort column", "Count", "Descending sort column restored for the Routing errors table."); WindowBackground = config.Bind("Style", "Window background", new Color(0.16f, 0.16f, 0.16f, 1f), "Background color of profiler windows."); WindowBorder = config.Bind("Style", "Window border", new Color(0.48f, 0.48f, 0.48f, 1f), "One-pixel border color used to separate overlapping profiler windows."); EntryBackground = config.Bind("Style", "Entry background", new Color(0.12f, 0.12f, 0.12f, 0.95f), "Background color used by boxes and grouped sections."); TextColor = config.Bind("Style", "Text color", new Color(0.92f, 0.92f, 0.92f, 1f), "Normal text color."); HeaderTextColor = config.Bind("Style", "Header text color", Color.white, "Header and emphasized text color."); ButtonBackground = config.Bind("Style", "Button background", new Color(0.24f, 0.24f, 0.24f, 1f), "Normal button background color."); ButtonTextColor = config.Bind("Style", "Button text color", Color.white, "Button text color."); AccentColor = config.Bind("Style", "Accent color", new Color(0.38f, 0.55f, 0.72f, 1f), "Accent used for active controls and important actions."); LauncherPosition = config.Bind("Layout", "Launcher position", DefaultLauncherPosition, "Logical position of the launcher. A negative X centers it on first use."); PatchWindowPosition = config.Bind("Layout", "Patch Profiler position", DefaultPatchWindowPosition, "Logical position of the Patch Profiler window."); PatchWindowSize = config.Bind("Layout", "Patch Profiler size", DefaultPatchWindowSize, "Logical size of the Patch Profiler window. A non-positive width uses 75% of the logical screen width."); TranspilerWindowPosition = config.Bind("Layout", "Transpiler details position", DefaultTranspilerWindowPosition, "Logical position of the transpiler details window."); TranspilerWindowSize = config.Bind("Layout", "Transpiler details size", DefaultTranspilerWindowSize, "Logical size of the transpiler details window. A non-positive width uses 25% of the logical screen width."); MonoBehaviourWindowPosition = config.Bind("Layout", "MonoBehaviour Frame Profiler position", DefaultMonoBehaviourWindowPosition, "Logical position of the MonoBehaviour Frame Profiler window."); MonoBehaviourWindowSize = config.Bind("Layout", "MonoBehaviour Frame Profiler size", DefaultMonoBehaviourWindowSize, "Logical size of the MonoBehaviour Frame Profiler window. A non-positive width uses 75% of the logical screen width."); MonoBehaviourCallWindowPosition = config.Bind("Layout", "MonoBehaviour Call Profiler position", DefaultMonoBehaviourCallWindowPosition, "Logical position of the MonoBehaviour Call Profiler window."); MonoBehaviourCallWindowSize = config.Bind("Layout", "MonoBehaviour Call Profiler size", DefaultMonoBehaviourCallWindowSize, "Logical size of the MonoBehaviour Call Profiler window. A non-positive width uses 75% of the logical screen width."); ValheimUpdateWindowPosition = config.Bind("Layout", "Valheim Update Profiler position", DefaultValheimUpdateWindowPosition, "Logical position of the Valheim Update Profiler window."); ValheimUpdateWindowSize = config.Bind("Layout", "Valheim Update Profiler size", DefaultValheimUpdateWindowSize, "Logical size of the Valheim Update Profiler window. A non-positive width uses 75% of the logical screen width."); LogMonitorWindowPosition = config.Bind("Layout", "Log Monitor position", DefaultLogMonitorWindowPosition, "Logical position of the client Log Monitor window."); LogMonitorWindowSize = config.Bind("Layout", "Log Monitor size", DefaultLogMonitorWindowSize, "Logical size of the client Log Monitor window. A non-positive width uses 75% of the logical screen width."); ServerLogMonitorWindowPosition = config.Bind("Layout", "Server Log Monitor position", DefaultServerLogMonitorWindowPosition, "Logical position of the remote dedicated-server Log Monitor window."); ServerLogMonitorWindowSize = config.Bind("Layout", "Server Log Monitor size", DefaultServerLogMonitorWindowSize, "Logical size of the remote dedicated-server Log Monitor window. A non-positive width uses 75% of the logical screen width."); NetworkProfilerWindowPosition = config.Bind("Layout", "Network Profiler position", DefaultNetworkProfilerWindowPosition, "Logical position of the dedicated-server Network Profiler window."); NetworkProfilerWindowSize = config.Bind("Layout", "Network Profiler size", DefaultNetworkProfilerWindowSize, "Logical size of the dedicated-server Network Profiler window. A non-positive width uses 75% of the logical screen width."); } } }