using System;
using System.Collections;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
using System.Text;
using System.Threading;
using BepInEx;
using BepInEx.Bootstrap;
using BepInEx.Logging;
using HarmonyLib;
using UnityEngine;
using ValheimModProfiler.Core;
using ValheimModProfiler.Data;
using ValheimModProfiler.Instrumentation;
using ValheimModProfiler.Overlay;
[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: AssemblyTitle("ProfilerValheim.Zeta")]
[assembly: AssemblyDescription("In-game CPU profiler for Valheim mods")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("ProfilerValheim.Zeta")]
[assembly: AssemblyCopyright("Copyright © 2026")]
[assembly: AssemblyTrademark("")]
[assembly: ComVisible(false)]
[assembly: Guid("78df1c3c-4bc4-4d62-8123-7d5be105a321")]
[assembly: AssemblyFileVersion("1.1.0.0")]
[assembly: TargetFramework(".NETFramework,Version=v4.7.2", FrameworkDisplayName = ".NET Framework 4.7.2")]
[assembly: AssemblyVersion("1.1.0.0")]
namespace ValheimModProfiler.Overlay
{
public class IngameProfiler : MonoBehaviour
{
private Rect _windowRect = new Rect(40f, 40f, 970f, 720f);
private bool _showWindow = true;
private Vector2 _cachedMousePos;
private bool _hookedBefore;
private MetricSnapshot _currentSnapshot;
private float _updateTimer;
public static string StatusText = "Ready.";
private int _currentTab;
private Vector2 _overviewScroll;
private string _searchFilter = "";
private bool _hideSmall;
private HashSet<string> _expandedMods = new HashSet<string>();
private Vector2 _alertsScroll;
private Vector2 _compareScroll;
private List<ModDelta> _cachedDeltas;
private string _exportStatus = "";
private static Texture2D _whiteTex;
private GUIStyle _modBtnStyle;
private GUIStyle _methodLabelStyle;
private bool _stylesReady;
private const float GRAPH_CEILING_MS = 66f;
private void Start()
{
//IL_0002: Unknown result type (might be due to invalid IL or missing references)
//IL_000c: Expected O, but got Unknown
//IL_0013: Unknown result type (might be due to invalid IL or missing references)
_whiteTex = new Texture2D(1, 1);
_whiteTex.SetPixel(0, 0, Color.white);
_whiteTex.Apply();
}
private void Update()
{
if (Input.GetKeyDown((KeyCode)289))
{
_showWindow = !_showWindow;
}
ProfilerDataManager.EndFrame();
if (ProfilerBootstrapper.IsMonitoring)
{
_hookedBefore = true;
}
_updateTimer += Time.unscaledDeltaTime;
if (_updateTimer >= 0.5f)
{
_updateTimer = 0f;
_currentSnapshot = ProfilerDataManager.GetSnapshot();
_cachedDeltas = null;
}
}
private void OnGUI()
{
//IL_000e: Unknown result type (might be due to invalid IL or missing references)
//IL_0014: Invalid comparison between Unknown and I4
//IL_0036: 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_001b: Unknown result type (might be due to invalid IL or missing references)
//IL_0021: Invalid comparison between Unknown and I4
//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)
//IL_0068: Expected O, but got Unknown
//IL_0063: 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_0028: Unknown result type (might be due to invalid IL or missing references)
//IL_002e: Invalid comparison between Unknown and I4
if (_showWindow)
{
if ((int)Event.current.type == 2 || (int)Event.current.type == 3 || (int)Event.current.type == 7)
{
_cachedMousePos = Event.current.mousePosition;
}
EnsureStyles();
_windowRect = GUI.Window(12345, _windowRect, new WindowFunction(DrawWindow), "Valheim Mod Profiler [F8]");
}
}
private void DrawWindow(int id)
{
//IL_01b1: Unknown result type (might be due to invalid IL or missing references)
//IL_0079: 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_00f4: 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)
GUILayout.BeginHorizontal(Array.Empty<GUILayoutOption>());
GUILayout.Label($"<b>FPS: {_currentSnapshot?.Fps ?? 0.0:0.0}</b>", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(110f) });
GUILayout.Label(StatusText, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.ExpandWidth(true) });
GUILayout.EndHorizontal();
GUILayout.BeginHorizontal(Array.Empty<GUILayoutOption>());
DrawScanButton();
GUI.backgroundColor = Color.white;
if (GUILayout.Button("CLEAR GRAPH", (GUILayoutOption[])(object)new GUILayoutOption[2]
{
GUILayout.Height(28f),
GUILayout.Width(100f)
}))
{
Array.Clear(ProfilerDataManager.GlobalHistory, 0, ProfilerDataManager.GlobalHistory.Length);
Array.Clear(ProfilerDataManager.WorstFrameHistory, 0, ProfilerDataManager.WorstFrameHistory.Length);
Array.Clear(ProfilerDataManager.EventMarkerHistory, 0, ProfilerDataManager.EventMarkerHistory.Length);
}
GUI.backgroundColor = new Color(0.5f, 0.8f, 1f);
if (GUILayout.Button("MARK EVENT", (GUILayoutOption[])(object)new GUILayoutOption[2]
{
GUILayout.Height(28f),
GUILayout.Width(100f)
}))
{
ProfilerDataManager.MarkEvent();
}
GUI.backgroundColor = new Color(0.9f, 0.85f, 0.5f);
if (GUILayout.Button("EXPORT", (GUILayoutOption[])(object)new GUILayoutOption[2]
{
GUILayout.Height(28f),
GUILayout.Width(80f)
}))
{
try
{
string path = ReportExporter.Export(_currentSnapshot);
_exportStatus = "Saved: " + Path.GetFileName(path);
}
catch (Exception ex)
{
_exportStatus = "Export error: " + ex.Message;
}
}
GUI.backgroundColor = Color.white;
GUILayout.EndHorizontal();
if (!string.IsNullOrEmpty(_exportStatus))
{
GUILayout.Label(_exportStatus, Array.Empty<GUILayoutOption>());
}
int alertCount = ProfilerDataManager.AlertCount;
string[] array = new string[3]
{
" Overview ",
$" Alerts ({alertCount}) ",
" A/B Compare "
};
GUI.backgroundColor = Color.white;
_currentTab = GUILayout.Toolbar(_currentTab, array, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Height(26f) });
GUILayout.Space(4f);
switch (_currentTab)
{
case 0:
DrawOverviewTab();
break;
case 1:
DrawAlertsTab();
break;
case 2:
DrawCompareTab();
break;
}
GUI.DragWindow();
}
private void DrawScanButton()
{
//IL_0072: 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)
bool isScanning = ProfilerBootstrapper.IsScanning;
bool isMonitoring = ProfilerBootstrapper.IsMonitoring;
if (isScanning || isMonitoring)
{
GUI.backgroundColor = new Color(1f, 0.25f, 0.25f);
if (GUILayout.Button(isScanning ? "STOP SCAN" : "STOP MONITORING", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Height(28f) }))
{
ModEntry.Instance?.StopMonitoring();
}
return;
}
GUI.backgroundColor = new Color(0.3f, 0.85f, 0.3f);
if (GUILayout.Button(_hookedBefore ? "RESUME" : "SCAN MODS", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Height(28f) }))
{
if (_hookedBefore)
{
ProfilerBootstrapper.ResumeMonitoring();
}
else
{
ModEntry.Instance?.StartScanning();
}
}
}
private void DrawOverviewTab()
{
//IL_0032: 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_0038: 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)
//IL_0239: Unknown result type (might be due to invalid IL or missing references)
//IL_023e: 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_006a: Invalid comparison between Unknown and I4
//IL_006d: 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_0089: 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_009d: Unknown result type (might be due to invalid IL or missing references)
GUILayout.Label("CPU Load (0 – 66ms) белый: total оранжевый: worst frame зелёный: событие", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.ExpandWidth(true) });
Rect rect = GUILayoutUtility.GetRect(0f, 150f, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.ExpandWidth(true) });
GUI.Box(rect, "");
if (((Rect)(ref rect)).width > 1f && ((Rect)(ref rect)).height > 1f && (int)Event.current.type == 7)
{
DrawGrid(rect);
if (_currentSnapshot != null)
{
DrawWorstFrameLine(rect, _currentSnapshot);
DrawMultiGraph(rect, _currentSnapshot);
DrawEventMarkers(rect);
DrawTooltip(rect, _currentSnapshot);
}
}
GUILayout.Space(6f);
GUILayout.BeginHorizontal(Array.Empty<GUILayoutOption>());
GUILayout.Label("Filter:", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(42f) });
_searchFilter = GUILayout.TextField(_searchFilter, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(200f) });
if (GUILayout.Button("x", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(22f) }))
{
_searchFilter = "";
}
GUILayout.Space(10f);
_hideSmall = GUILayout.Toggle(_hideSmall, " Hide < 0.1ms", Array.Empty<GUILayoutOption>());
GUILayout.FlexibleSpace();
GUILayout.Label("[click row = show methods]", Array.Empty<GUILayoutOption>());
GUILayout.EndHorizontal();
GUILayout.Space(4f);
GUILayout.BeginHorizontal(Array.Empty<GUILayoutOption>());
GUILayout.Label("", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(16f) });
GUILayout.Label("", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(18f) });
GUILayout.Label("Mod Name", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(326f) });
GUILayout.Label("Avg CPU", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(75f) });
GUILayout.Label("Impact", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(75f) });
GUILayout.Label("Calls/s", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(75f) });
GUILayout.EndHorizontal();
_overviewScroll = GUILayout.BeginScrollView(_overviewScroll, Array.Empty<GUILayoutOption>());
if (_currentSnapshot?.Mods != null)
{
foreach (ModMetric item in FilteredMods(_currentSnapshot.Mods))
{
DrawModRow(item);
}
}
GUILayout.EndScrollView();
}
private IEnumerable<ModMetric> FilteredMods(List<ModMetric> mods)
{
return from m in mods
where !_hideSmall || m.AvgMs >= 0.1
where string.IsNullOrEmpty(_searchFilter) || m.Name.IndexOf(_searchFilter, StringComparison.OrdinalIgnoreCase) >= 0
select m;
}
private void DrawModRow(ModMetric mod)
{
//IL_0080: 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)
//IL_008c: 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_0158: Unknown result type (might be due to invalid IL or missing references)
//IL_0180: 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_013b: Unknown result type (might be due to invalid IL or missing references)
bool flag = _expandedMods.Contains(mod.Name);
bool flag2 = mod.TopMethods != null && mod.TopMethods.Count > 0;
GUILayout.BeginHorizontal(Array.Empty<GUILayoutOption>());
GUILayout.Label((!flag2) ? " " : (flag ? "v" : ">"), (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(16f) });
Rect rect = GUILayoutUtility.GetRect(16f, 16f, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.ExpandWidth(false) });
Color color = GUI.color;
GUI.color = mod.Color;
GUI.DrawTexture(rect, (Texture)(object)_whiteTex);
GUI.color = color;
if (GUILayout.Button(mod.Name, _modBtnStyle, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(326f) }) && !_expandedMods.Remove(mod.Name))
{
_expandedMods.Add(mod.Name);
}
GUILayout.Label(mod.CachedAvgMsString + "ms", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(75f) });
GUI.contentColor = (Color)((mod.ImpactPercent > 5.0) ? new Color(1f, 0.4f, 0.4f) : ((mod.ImpactPercent > 1.0) ? Color.yellow : Color.white));
GUILayout.Label(mod.CachedImpactString, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(75f) });
GUI.contentColor = Color.white;
GUILayout.Label(mod.CachedCallsString, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(75f) });
GUILayout.EndHorizontal();
if (!(flag && flag2))
{
return;
}
foreach (MethodDetail topMethod in mod.TopMethods)
{
GUILayout.BeginHorizontal(Array.Empty<GUILayoutOption>());
GUILayout.Space(34f);
GUILayout.Label("|_", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(18f) });
GUILayout.Label(topMethod.MethodName, _methodLabelStyle, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(308f) });
GUILayout.Label(topMethod.CachedAvgMs + "ms", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(75f) });
GUILayout.Label("", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(75f) });
GUILayout.Label(topMethod.CachedCalls, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(75f) });
GUILayout.EndHorizontal();
}
GUILayout.Space(3f);
}
private void DrawAlertsTab()
{
//IL_0041: 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)
//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_0171: 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)
GUILayout.BeginHorizontal(Array.Empty<GUILayoutOption>());
GUILayout.Label($"Spikes > {16.6f:0.0}ms per frame:", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.ExpandWidth(true) });
GUI.backgroundColor = new Color(0.8f, 0.3f, 0.3f);
if (GUILayout.Button("Clear", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(80f) }))
{
ProfilerDataManager.ClearAlerts();
}
GUI.backgroundColor = Color.white;
GUILayout.EndHorizontal();
GUILayout.Space(4f);
GUILayout.BeginHorizontal(Array.Empty<GUILayoutOption>());
GUILayout.Label("Time", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(65f) });
GUILayout.Label("Mod", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(360f) });
GUILayout.Label("Peak", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(90f) });
GUILayout.EndHorizontal();
_alertsScroll = GUILayout.BeginScrollView(_alertsScroll, Array.Empty<GUILayoutOption>());
List<AlertEntry> alerts = ProfilerDataManager.GetAlerts();
if (alerts.Count == 0)
{
GUILayout.Label("No spikes detected yet. Keep profiling!", Array.Empty<GUILayoutOption>());
}
else
{
foreach (AlertEntry item in alerts)
{
GUILayout.BeginHorizontal(Array.Empty<GUILayoutOption>());
GUILayout.Label(item.Timestamp, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(65f) });
GUI.contentColor = new Color(1f, 0.5f, 0.5f);
GUILayout.Label(item.ModName, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(360f) });
GUI.contentColor = Color.white;
GUILayout.Label(item.PeakMs.ToString("0.0") + " ms", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(90f) });
GUILayout.EndHorizontal();
}
}
GUILayout.EndScrollView();
}
private void DrawCompareTab()
{
//IL_0019: 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_0092: 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_00ca: Unknown result type (might be due to invalid IL or missing references)
//IL_0157: 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_01c4: Unknown result type (might be due to invalid IL or missing references)
//IL_028b: Unknown result type (might be due to invalid IL or missing references)
//IL_0295: Unknown result type (might be due to invalid IL or missing references)
//IL_029a: Unknown result type (might be due to invalid IL or missing references)
//IL_0322: Unknown result type (might be due to invalid IL or missing references)
//IL_034b: Unknown result type (might be due to invalid IL or missing references)
GUILayout.BeginHorizontal(Array.Empty<GUILayoutOption>());
GUI.backgroundColor = new Color(0.4f, 0.6f, 1f);
if (GUILayout.Button("Save as Snapshot A", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Height(28f) }))
{
ProfilerDataManager.SaveSnapshotA();
_cachedDeltas = null;
}
GUI.backgroundColor = new Color(1f, 0.65f, 0.3f);
if (GUILayout.Button("Save as Snapshot B", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Height(28f) }))
{
ProfilerDataManager.SaveSnapshotB();
_cachedDeltas = null;
}
GUI.backgroundColor = Color.white;
GUILayout.EndHorizontal();
GUILayout.Space(4f);
GUILayout.BeginHorizontal(Array.Empty<GUILayoutOption>());
bool flag = ProfilerDataManager.SnapshotA != null;
bool flag2 = ProfilerDataManager.SnapshotB != null;
GUI.color = (flag ? Color.green : Color.gray);
GUILayout.Label(flag ? $"A: {ProfilerDataManager.SnapshotA.Fps:0.0} FPS ({ProfilerDataManager.SnapshotA.Mods?.Count} mods)" : "A: not saved", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(230f) });
GUI.color = (Color)(flag2 ? new Color(1f, 0.65f, 0.3f) : Color.gray);
GUILayout.Label(flag2 ? $"B: {ProfilerDataManager.SnapshotB.Fps:0.0} FPS ({ProfilerDataManager.SnapshotB.Mods?.Count} mods)" : "B: not saved", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(230f) });
GUI.color = Color.white;
GUILayout.EndHorizontal();
GUILayout.Space(6f);
if (!flag || !flag2)
{
GUILayout.Label("Workflow:\n1. Profile your mods in state A -> Save as Snapshot A\n2. Make changes -> Save as Snapshot B\n3. See the delta here.", Array.Empty<GUILayoutOption>());
return;
}
if (_cachedDeltas == null)
{
_cachedDeltas = ProfilerDataManager.ComputeDeltas();
}
GUILayout.BeginHorizontal(Array.Empty<GUILayoutOption>());
GUILayout.Label("Mod", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(330f) });
GUILayout.Label("A (ms)", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(85f) });
GUILayout.Label("B (ms)", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(85f) });
GUILayout.Label("Delta", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(95f) });
GUILayout.EndHorizontal();
_compareScroll = GUILayout.BeginScrollView(_compareScroll, Array.Empty<GUILayoutOption>());
foreach (ModDelta cachedDelta in _cachedDeltas)
{
GUILayout.BeginHorizontal(Array.Empty<GUILayoutOption>());
GUILayout.Label(cachedDelta.ModName, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(330f) });
GUILayout.Label(cachedDelta.CachedA, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(85f) });
GUILayout.Label(cachedDelta.CachedB, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(85f) });
GUI.contentColor = cachedDelta.DeltaColor;
GUILayout.Label(cachedDelta.CachedDelta, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(95f) });
GUI.contentColor = Color.white;
GUILayout.EndHorizontal();
}
GUILayout.EndScrollView();
}
private void DrawGrid(Rect r)
{
//IL_0000: 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_0020: 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_0045: 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_006a: 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_00c6: 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_0127: Unknown result type (might be due to invalid IL or missing references)
Color color = GUI.color;
DrawHLine(r, 66f, new Color(1f, 0.2f, 0.2f, 0.12f));
DrawHLine(r, 33.3f, new Color(1f, 0.8f, 0.2f, 0.2f));
DrawHLine(r, 16.6f, new Color(0.3f, 1f, 0.3f, 0.28f));
GUI.color = new Color(0.3f, 1f, 0.3f, 0.6f);
float num = ((Rect)(ref r)).y + ((Rect)(ref r)).height * 0.74848485f;
GUI.Label(new Rect(((Rect)(ref r)).x + 4f, num - 14f, 45f, 14f), "60fps");
GUI.color = new Color(1f, 0.8f, 0.2f, 0.6f);
float num2 = ((Rect)(ref r)).y + ((Rect)(ref r)).height * 0.49545455f;
GUI.Label(new Rect(((Rect)(ref r)).x + 4f, num2 - 14f, 45f, 14f), "30fps");
GUI.color = color;
}
private void DrawHLine(Rect r, float ms, Color col)
{
//IL_0033: Unknown result type (might be due to invalid IL or missing references)
//IL_0038: 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)
float num = ((Rect)(ref r)).y + ((Rect)(ref r)).height * (1f - ms / 66f);
if (!(num < ((Rect)(ref r)).y) && !(num > ((Rect)(ref r)).yMax))
{
Color color = GUI.color;
GUI.color = col;
GUI.DrawTexture(new Rect(((Rect)(ref r)).x, num, ((Rect)(ref r)).width, 1f), (Texture)(object)_whiteTex);
GUI.color = color;
}
}
private void DrawMultiGraph(Rect r, MetricSnapshot snap)
{
//IL_0043: 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_00b6: 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)
if (snap.TotalHistory == null)
{
return;
}
float num = 0f;
float[] totalHistory = snap.TotalHistory;
foreach (float num2 in totalHistory)
{
if (num2 > num)
{
num = num2;
}
}
float scaleMax = Mathf.Clamp(num, 16f, 66f);
DrawLine(r, snap.TotalHistory, new Color(1f, 1f, 1f, 0.35f), scaleMax, 3f);
foreach (ModMetric item in snap.Mods.Where((ModMetric m) => m.History != null && m.ImpactPercent > 0.5).Take(5).Reverse())
{
DrawLine(r, item.History, item.Color, scaleMax, 2f);
}
}
private void DrawWorstFrameLine(Rect r, MetricSnapshot snap)
{
//IL_004b: 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)
if (snap.WorstFrameHistory == null || snap.TotalHistory == null)
{
return;
}
float num = 0f;
float[] totalHistory = snap.TotalHistory;
foreach (float num2 in totalHistory)
{
if (num2 > num)
{
num = num2;
}
}
float scaleMax = Mathf.Clamp(num, 16f, 66f);
DrawLine(r, snap.WorstFrameHistory, new Color(1f, 0.55f, 0.1f, 0.45f), scaleMax, 1f);
}
private void DrawEventMarkers(Rect r)
{
//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_00d7: 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_0071: 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_00bb: Unknown result type (might be due to invalid IL or missing references)
float[] eventMarkerHistory = ProfilerDataManager.EventMarkerHistory;
if (eventMarkerHistory == null)
{
return;
}
float num = ((Rect)(ref r)).width / (float)(eventMarkerHistory.Length - 1);
Color color = GUI.color;
for (int i = 0; i < eventMarkerHistory.Length; i++)
{
if (!(eventMarkerHistory[i] <= 0f))
{
float num2 = ((Rect)(ref r)).x + (float)i * num;
GUI.color = new Color(0.3f, 1f, 0.5f, 0.75f);
GUI.DrawTexture(new Rect(num2, ((Rect)(ref r)).y, 2f, ((Rect)(ref r)).height), (Texture)(object)_whiteTex);
GUI.color = new Color(0.3f, 1f, 0.5f, 1f);
GUI.DrawTexture(new Rect(num2 - 3f, ((Rect)(ref r)).y + 2f, 8f, 8f), (Texture)(object)_whiteTex);
}
}
GUI.color = color;
}
private void DrawTooltip(Rect r, MetricSnapshot snap)
{
//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_0009: Unknown result type (might be due to invalid IL or missing references)
//IL_0012: Unknown result type (might be due to invalid IL or missing references)
//IL_0046: 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_0069: Unknown result type (might be due to invalid IL or missing references)
//IL_0082: Unknown result type (might be due to invalid IL or missing references)
//IL_01b9: Unknown result type (might be due to invalid IL or missing references)
//IL_01e8: 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_01d5: Unknown result type (might be due to invalid IL or missing references)
Vector2 cachedMousePos = _cachedMousePos;
if (!((Rect)(ref r)).Contains(cachedMousePos))
{
return;
}
int num = Mathf.Clamp((int)((cachedMousePos.x - ((Rect)(ref r)).x) / ((Rect)(ref r)).width * (float)(snap.TotalHistory.Length - 1)), 0, snap.TotalHistory.Length - 1);
Color color = GUI.color;
GUI.color = new Color(1f, 1f, 1f, 0.35f);
GUI.DrawTexture(new Rect(cachedMousePos.x, ((Rect)(ref r)).y, 1f, ((Rect)(ref r)).height), (Texture)(object)_whiteTex);
GUI.color = color;
float num2 = snap.TotalHistory[num];
float num3 = ((snap.WorstFrameHistory != null && num < snap.WorstFrameHistory.Length) ? snap.WorstFrameHistory[num] : 0f);
string arg = "None";
float num4 = 0f;
foreach (ModMetric mod in snap.Mods)
{
if (mod.History != null && num < mod.History.Length && mod.History[num] > num4)
{
num4 = mod.History[num];
arg = mod.Name;
}
}
bool flag = ProfilerDataManager.EventMarkerHistory != null && num < ProfilerDataManager.EventMarkerHistory.Length && ProfilerDataManager.EventMarkerHistory[num] > 0f;
string text = $"Total: {num2:0.0} ms" + $"\nWorst: {num3:0.0} ms" + $"\nTop: {arg} ({num4:0.0} ms)" + (flag ? "\n[EVENT MARKER]" : "");
float num5 = 240f;
float num6 = (flag ? 72 : 58);
float num7 = cachedMousePos.x + 15f;
if (num7 + num5 > ((Rect)(ref r)).xMax)
{
num7 = cachedMousePos.x - num5 - 15f;
}
GUI.Box(new Rect(num7, cachedMousePos.y, num5, num6), text);
}
private void DrawLine(Rect r, float[] data, Color col, float scaleMax, float thickness)
{
//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_001f: 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_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_0039: 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_0046: 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_0049: 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_0054: 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)
if (data != null && data.Length >= 2)
{
float xStep = ((Rect)(ref r)).width / (float)(data.Length - 1);
Color color = GUI.color;
GUI.color = col;
Vector2 a = Pt(r, 0, data[0], xStep, scaleMax);
for (int i = 1; i < data.Length; i++)
{
Vector2 val = Pt(r, i, data[i], xStep, scaleMax);
DrawSeg(a, val, thickness);
a = val;
}
GUI.color = color;
}
}
private Vector2 Pt(Rect r, int i, float v, float xStep, float maxV)
{
//IL_0033: Unknown result type (might be due to invalid IL or missing references)
v = Mathf.Min(v, maxV);
return new Vector2(((Rect)(ref r)).x + (float)i * xStep, ((Rect)(ref r)).y + ((Rect)(ref r)).height - v / maxV * ((Rect)(ref r)).height);
}
public static void DrawSeg(Vector2 a, Vector2 b, float w)
{
//IL_0000: Unknown result type (might be due to invalid IL or missing references)
//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_0007: 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_0022: 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_002f: 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_0043: Unknown result type (might be due to invalid IL or missing references)
//IL_0049: Unknown result type (might be due to invalid IL or missing references)
//IL_0057: 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_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_0067: 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)
Vector2 val = b - a;
if (!(((Vector2)(ref val)).sqrMagnitude < 0.01f))
{
float num = 57.29578f * Mathf.Atan2(b.y - a.y, b.x - a.x);
GUIUtility.RotateAroundPivot(num, a);
float x = a.x;
float num2 = a.y - w * 0.5f;
val = b - a;
GUI.DrawTexture(new Rect(x, num2, ((Vector2)(ref val)).magnitude, w), (Texture)(object)_whiteTex);
GUIUtility.RotateAroundPivot(0f - num, a);
}
}
private void EnsureStyles()
{
//IL_001b: 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_0027: Unknown result type (might be due to invalid IL or missing references)
//IL_002f: Unknown result type (might be due to invalid IL or missing references)
//IL_0034: Unknown result type (might be due to invalid IL or missing references)
//IL_003e: Expected O, but got Unknown
//IL_0043: Expected O, but got Unknown
//IL_004e: 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_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_0084: Unknown result type (might be due to invalid IL or missing references)
//IL_0091: Expected O, but got Unknown
//IL_00ab: Unknown result type (might be due to invalid IL or missing references)
if (!_stylesReady)
{
_stylesReady = true;
_modBtnStyle = new GUIStyle(GUI.skin.button)
{
alignment = (TextAnchor)3,
fontSize = 12,
padding = new RectOffset(4, 4, 2, 2)
};
_modBtnStyle.normal.textColor = Color.white;
_modBtnStyle.hover.textColor = Color.white;
_methodLabelStyle = new GUIStyle(GUI.skin.label)
{
alignment = (TextAnchor)3,
fontSize = 11
};
_methodLabelStyle.normal.textColor = new Color(0.75f, 0.75f, 0.75f);
}
}
}
}
namespace ValheimModProfiler.Instrumentation
{
public static class ProfilerBootstrapper
{
[CompilerGenerated]
private sealed class <ApplyHooksCoroutine>d__13 : IEnumerator<object>, IDisposable, IEnumerator
{
private int <>1__state;
private object <>2__current;
private Harmony <harmony>5__2;
private MethodInfo <myPrefix>5__3;
private MethodInfo <myPostfix>5__4;
private List<PluginInfo> <plugins>5__5;
private int <totalHooks>5__6;
private int <processedPlugins>5__7;
private List<PluginInfo>.Enumerator <>7__wrap7;
private PluginInfo <plugin>5__9;
object IEnumerator<object>.Current
{
[DebuggerHidden]
get
{
return <>2__current;
}
}
object IEnumerator.Current
{
[DebuggerHidden]
get
{
return <>2__current;
}
}
[DebuggerHidden]
public <ApplyHooksCoroutine>d__13(int <>1__state)
{
this.<>1__state = <>1__state;
}
[DebuggerHidden]
void IDisposable.Dispose()
{
int num = <>1__state;
if (num == -3 || num == 2)
{
try
{
}
finally
{
<>m__Finally1();
}
}
<harmony>5__2 = null;
<myPrefix>5__3 = null;
<myPostfix>5__4 = null;
<plugins>5__5 = null;
<>7__wrap7 = default(List<PluginInfo>.Enumerator);
<plugin>5__9 = null;
<>1__state = -2;
}
private bool MoveNext()
{
//IL_0067: Unknown result type (might be due to invalid IL or missing references)
//IL_0071: Expected O, but got Unknown
bool result;
try
{
switch (<>1__state)
{
default:
result = false;
goto end_IL_0000;
case 0:
<>1__state = -1;
_cancelRequested = false;
IsScanning = true;
IsMonitoring = true;
IngameProfiler.StatusText = "Initializing Smart Scan...";
<>2__current = null;
<>1__state = 1;
result = true;
goto end_IL_0000;
case 1:
<>1__state = -1;
<harmony>5__2 = new Harmony("com.debug.valheim_profiler");
<myPrefix>5__3 = typeof(ProfilerRecorder).GetMethod("Prefix");
<myPostfix>5__4 = typeof(ProfilerRecorder).GetMethod("Postfix");
<plugins>5__5 = Chainloader.PluginInfos.Values.ToList();
Logger.LogInfo((object)$"Found {<plugins>5__5.Count} BepInEx plugins to scan.");
<totalHooks>5__6 = 0;
<processedPlugins>5__7 = 0;
<>7__wrap7 = <plugins>5__5.GetEnumerator();
<>1__state = -3;
break;
case 2:
{
<>1__state = -3;
if ((Object)(object)<plugin>5__9.Instance == (Object)null)
{
break;
}
Assembly assembly = ((object)<plugin>5__9.Instance).GetType().Assembly;
if (assembly == null)
{
break;
}
Type[] array;
try
{
array = assembly.GetTypes();
}
catch (ReflectionTypeLoadException ex)
{
array = ex.Types.Where((Type t) => t != null).ToArray();
Logger.LogWarning((object)("[Profiler] Partial type load for " + <plugin>5__9.Metadata.Name + ": " + ex.LoaderExceptions.FirstOrDefault()?.Message));
}
catch (Exception ex2)
{
Logger.LogWarning((object)("[Profiler] Cannot load types from " + <plugin>5__9.Metadata.Name + ": " + ex2.Message));
break;
}
Type[] array2 = array;
foreach (Type type in array2)
{
if (typeof(MonoBehaviour).IsAssignableFrom(type))
{
HookUnityMethod(<harmony>5__2, type, "Update", <myPrefix>5__3, <myPostfix>5__4, ref <totalHooks>5__6);
HookUnityMethod(<harmony>5__2, type, "FixedUpdate", <myPrefix>5__3, <myPostfix>5__4, ref <totalHooks>5__6);
HookUnityMethod(<harmony>5__2, type, "LateUpdate", <myPrefix>5__3, <myPostfix>5__4, ref <totalHooks>5__6);
}
if (!type.GetCustomAttributes(inherit: false).Any((object x) => x.GetType().Name.Contains("Harmony")))
{
continue;
}
MethodInfo[] methods = type.GetMethods(BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
foreach (MethodInfo methodInfo in methods)
{
if (methodInfo.Name == "Prefix" || methodInfo.Name == "Postfix")
{
SafePatch(<harmony>5__2, methodInfo, <myPrefix>5__3, <myPostfix>5__4, ref <totalHooks>5__6);
}
}
}
<plugin>5__9 = null;
break;
}
}
while (true)
{
if (<>7__wrap7.MoveNext())
{
<plugin>5__9 = <>7__wrap7.Current;
if (_cancelRequested)
{
Logger.LogInfo((object)$"[Profiler] Scan cancelled by user. Hooks applied: {<totalHooks>5__6}");
IngameProfiler.StatusText = $"Monitoring stopped. Hooks were: {<totalHooks>5__6}.";
IsScanning = false;
result = false;
<>m__Finally1();
break;
}
<processedPlugins>5__7++;
if (!(<plugin>5__9.Metadata.GUID == "com.debug.valheim_profiler"))
{
IngameProfiler.StatusText = $"Scanning ({<processedPlugins>5__7}/{<plugins>5__5.Count}): {<plugin>5__9.Metadata.Name}";
<>2__current = null;
<>1__state = 2;
result = true;
break;
}
continue;
}
<>m__Finally1();
<>7__wrap7 = default(List<PluginInfo>.Enumerator);
Logger.LogInfo((object)$"Smart Scan Complete. Total Hooks: {<totalHooks>5__6}");
IngameProfiler.StatusText = $"Monitoring {<totalHooks>5__6} methods. Press Stop to pause.";
IsScanning = false;
result = false;
break;
}
end_IL_0000:;
}
catch
{
//try-fault
((IDisposable)this).Dispose();
throw;
}
return result;
}
bool IEnumerator.MoveNext()
{
//ILSpy generated this explicit interface implementation from .override directive in MoveNext
return this.MoveNext();
}
private void <>m__Finally1()
{
<>1__state = -1;
((IDisposable)<>7__wrap7).Dispose();
}
[DebuggerHidden]
void IEnumerator.Reset()
{
throw new NotSupportedException();
}
}
private static readonly ManualLogSource Logger = Logger.CreateLogSource("ProfilerHooks");
private static readonly HashSet<MethodBase> _hookedMethods = new HashSet<MethodBase>();
private static volatile bool _cancelRequested = false;
public static bool IsScanning { get; private set; } = false;
public static bool IsMonitoring { get; private set; } = false;
public static void StopMonitoring()
{
_cancelRequested = true;
IsMonitoring = false;
}
public static void ResumeMonitoring()
{
_cancelRequested = false;
IsMonitoring = true;
IngameProfiler.StatusText = "Monitoring resumed.";
}
[IteratorStateMachine(typeof(<ApplyHooksCoroutine>d__13))]
public static IEnumerator ApplyHooksCoroutine()
{
//yield-return decompiler failed: Unexpected instruction in Iterator.Dispose()
return new <ApplyHooksCoroutine>d__13(0);
}
private static void HookUnityMethod(Harmony harmony, Type type, string methodName, MethodInfo pre, MethodInfo post, ref int counter)
{
try
{
MethodInfo method = type.GetMethod(methodName, BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
if (method != null)
{
SafePatch(harmony, method, pre, post, ref counter);
}
}
catch (Exception ex)
{
Logger.LogWarning((object)("[Profiler] HookUnityMethod(" + methodName + ") on " + type.FullName + ": " + ex.Message));
}
}
private static void SafePatch(Harmony harmony, MethodBase target, MethodInfo pre, MethodInfo post, ref int counter)
{
//IL_002d: 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: Expected O, but got Unknown
//IL_0040: Expected O, but got Unknown
if (_hookedMethods.Contains(target) || target.IsGenericMethod || target.ContainsGenericParameters || target.IsAbstract)
{
return;
}
try
{
harmony.Patch(target, new HarmonyMethod(pre), new HarmonyMethod(post), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
_hookedMethods.Add(target);
counter++;
}
catch (Exception ex)
{
Logger.LogWarning((object)("[Profiler] Cannot patch " + target.DeclaringType?.Name + "." + target.Name + ": " + ex.Message));
}
}
}
public static class ProfilerRecorder
{
private struct FrameData
{
public long StartTick;
public long ChildrenTicks;
public int OwnerHash;
}
[ThreadStatic]
private static Stack<FrameData> _stack;
[ThreadStatic]
private static bool _isRecording;
public static void Prefix(MethodBase __originalMethod, out long __state)
{
if (!ProfilerBootstrapper.IsMonitoring)
{
__state = -1L;
return;
}
if (_isRecording)
{
__state = -1L;
return;
}
_isRecording = true;
try
{
if (_stack == null)
{
_stack = new Stack<FrameData>();
}
int ownerHash = 0;
string name = "Unknown";
string displayName = "Unknown";
if (__originalMethod.DeclaringType != null)
{
ownerHash = __originalMethod.DeclaringType.Assembly.FullName.GetHashCode();
name = __originalMethod.DeclaringType.Assembly.GetName().Name;
displayName = __originalMethod.DeclaringType.Name + "." + __originalMethod.Name;
}
ProfilerDataManager.RegisterModName(ownerHash, name);
ProfilerDataManager.RegisterMethodName(__originalMethod.MetadataToken, displayName);
_stack.Push(new FrameData
{
StartTick = Stopwatch.GetTimestamp(),
ChildrenTicks = 0L,
OwnerHash = ownerHash
});
__state = 1L;
}
catch (Exception ex)
{
ManualLogSource logger = ModEntry.Logger;
if (logger != null)
{
logger.LogWarning((object)("[Profiler] Prefix error in " + __originalMethod?.Name + ": " + ex.Message));
}
__state = -1L;
}
finally
{
_isRecording = false;
}
}
public static void Postfix(MethodBase __originalMethod, long __state)
{
if (__state == -1 || _isRecording)
{
return;
}
_isRecording = true;
try
{
if (_stack != null && _stack.Count != 0)
{
long timestamp = Stopwatch.GetTimestamp();
FrameData frameData = _stack.Pop();
long num = timestamp - frameData.StartTick;
long num2 = num - frameData.ChildrenTicks;
if (num2 < 0)
{
num2 = 0L;
}
if (_stack.Count > 0)
{
FrameData item = _stack.Pop();
item.ChildrenTicks += num;
_stack.Push(item);
}
ProfilerDataManager.RecordSample(__originalMethod.MetadataToken, frameData.OwnerHash, num2);
}
}
catch (Exception ex)
{
ManualLogSource logger = ModEntry.Logger;
if (logger != null)
{
logger.LogWarning((object)("[Profiler] Postfix error in " + __originalMethod?.Name + ": " + ex.Message));
}
}
finally
{
_isRecording = false;
}
}
}
}
namespace ValheimModProfiler.Data
{
public class MetricSnapshot
{
public double Fps { get; set; }
public List<ModMetric> Mods { get; set; } = new List<ModMetric>();
public float[] TotalHistory { get; set; }
public float[] WorstFrameHistory { get; set; }
}
public class ModMetric
{
public string Name { get; set; }
public double TotalMs { get; set; }
public double AvgMs { get; set; }
public int Calls { get; set; }
public double ImpactPercent { get; set; }
public Color Color { get; set; }
public float[] History { get; set; }
public List<MethodDetail> TopMethods { get; set; } = new List<MethodDetail>();
public string CachedAvgMsString { get; set; }
public string CachedImpactString { get; set; }
public string CachedCallsString { get; set; }
}
public class MethodDetail
{
public string MethodName { get; set; }
public double AvgMs { get; set; }
public int Calls { get; set; }
public string CachedAvgMs { get; set; }
public string CachedCalls { get; set; }
}
public class AlertEntry
{
public string ModName { get; set; }
public float PeakMs { get; set; }
public string Timestamp { get; set; }
}
public class ModDelta
{
public string ModName { get; set; }
public double AvgMsA { get; set; }
public double AvgMsB { get; set; }
public double DeltaMs => AvgMsB - AvgMsA;
public Color DeltaColor { get; set; }
public string CachedA { get; set; }
public string CachedB { get; set; }
public string CachedDelta { get; set; }
}
public class MethodData
{
public long Ticks;
public int Calls;
public int OwnerHash;
}
public static class ProfilerDataManager
{
private class MethodAccumEntry
{
public string DisplayName;
public int OwnerHash;
public double TotalMs;
public int Calls;
}
private static ConcurrentDictionary<int, MethodData> _methodRegistry = new ConcurrentDictionary<int, MethodData>();
private static ConcurrentDictionary<int, string> _modNames = new ConcurrentDictionary<int, string>();
private static ConcurrentDictionary<int, string> _methodDisplayNames = new ConcurrentDictionary<int, string>();
private static readonly int _historySize = 300;
public static float[] GlobalHistory = new float[300];
public static float[] WorstFrameHistory = new float[300];
public static float[] EventMarkerHistory = new float[300];
private static float _sessionWorst = 0f;
private static bool _pendingMark = false;
private static Dictionary<string, float[]> _modHistories = new Dictionary<string, float[]>();
private static Dictionary<int, MethodAccumEntry> _methodAccum = new Dictionary<int, MethodAccumEntry>();
private static Dictionary<string, ModMetric> _snapshotAccumulator = new Dictionary<string, ModMetric>();
private static MetricSnapshot _currentSnapshot = new MetricSnapshot();
private static readonly object _snapshotLock = new object();
private static Stopwatch _snapshotTimer = new Stopwatch();
private static int _frameCount = 0;
public const float SpikeThresholdMs = 16.6f;
private static Dictionary<string, float> _spikeTracking = new Dictionary<string, float>();
private static readonly List<AlertEntry> _alerts = new List<AlertEntry>();
private static readonly object _alertsLock = new object();
private const int MaxAlerts = 100;
public static MetricSnapshot SnapshotA { get; private set; }
public static MetricSnapshot SnapshotB { get; private set; }
public static int AlertCount
{
get
{
lock (_alertsLock)
{
return _alerts.Count;
}
}
}
public static void Initialize()
{
_snapshotTimer.Start();
}
public static void MarkEvent()
{
_pendingMark = true;
}
public static void SaveSnapshotA()
{
SnapshotA = GetSnapshot();
}
public static void SaveSnapshotB()
{
SnapshotB = GetSnapshot();
}
public static void ClearAlerts()
{
lock (_alertsLock)
{
_alerts.Clear();
}
}
public static void RegisterModName(int ownerHash, string name)
{
_modNames.TryAdd(ownerHash, name);
}
public static void RegisterMethodName(int methodToken, string displayName)
{
_methodDisplayNames.TryAdd(methodToken, displayName);
}
public static void RecordSample(int methodToken, int ownerHash, long selfTicks)
{
if (!_methodRegistry.TryGetValue(methodToken, out var value))
{
value = new MethodData
{
OwnerHash = ownerHash
};
_methodRegistry.TryAdd(methodToken, value);
}
Interlocked.Add(ref value.Ticks, selfTicks);
Interlocked.Increment(ref value.Calls);
}
public static List<AlertEntry> GetAlerts()
{
lock (_alertsLock)
{
return new List<AlertEntry>(_alerts);
}
}
public static List<ModDelta> ComputeDeltas()
{
MetricSnapshot snapshotA = SnapshotA;
MetricSnapshot snapshotB = SnapshotB;
if (snapshotA == null || snapshotB == null)
{
return null;
}
Dictionary<string, double> dictA = snapshotA.Mods.ToDictionary((ModMetric m) => m.Name, (ModMetric m) => m.AvgMs);
Dictionary<string, double> dictB = snapshotB.Mods.ToDictionary((ModMetric m) => m.Name, (ModMetric m) => m.AvgMs);
HashSet<string> hashSet = new HashSet<string>(dictA.Keys);
hashSet.UnionWith(dictB.Keys);
return (from d in hashSet.Select(delegate(string name)
{
//IL_00a0: 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_0074: Unknown result type (might be due to invalid IL or missing references)
double value;
double num = (dictA.TryGetValue(name, out value) ? value : 0.0);
double value2;
double num2 = (dictB.TryGetValue(name, out value2) ? value2 : 0.0);
double num3 = num2 - num;
return new ModDelta
{
ModName = name,
AvgMsA = num,
AvgMsB = num2,
DeltaColor = (Color)((num3 > 0.5) ? new Color(1f, 0.4f, 0.4f) : ((num3 < -0.5) ? new Color(0.4f, 1f, 0.4f) : Color.white)),
CachedA = num.ToString("0.00") + "ms",
CachedB = num2.ToString("0.00") + "ms",
CachedDelta = ((num3 >= 0.0) ? "+" : "") + num3.ToString("0.00") + "ms"
};
})
orderby Math.Abs(d.DeltaMs) descending
select d).ToList();
}
public static void EndFrame()
{
//IL_00cc: Unknown result type (might be due to invalid IL or missing references)
if (Time.timeScale <= 0.0001f)
{
return;
}
_frameCount++;
double num = 0.0;
Dictionary<string, float> dictionary = new Dictionary<string, float>();
foreach (KeyValuePair<int, MethodData> item in _methodRegistry)
{
MethodData value = item.Value;
long num2 = Interlocked.Exchange(ref value.Ticks, 0L);
int num3 = Interlocked.Exchange(ref value.Calls, 0);
if (num2 != 0L)
{
num += (double)num2;
double num4 = (double)num2 / (double)Stopwatch.Frequency * 1000.0;
string value2;
string text = (_modNames.TryGetValue(value.OwnerHash, out value2) ? value2 : "Unknown");
if (!_snapshotAccumulator.TryGetValue(text, out var value3))
{
value3 = new ModMetric
{
Name = text,
Color = GenerateColor(text)
};
_snapshotAccumulator[text] = value3;
}
value3.TotalMs += num4;
value3.Calls += num3;
if (!dictionary.ContainsKey(text))
{
dictionary[text] = 0f;
}
dictionary[text] += (float)num4;
if (!_methodAccum.TryGetValue(item.Key, out var value4))
{
value4 = new MethodAccumEntry
{
DisplayName = (_methodDisplayNames.TryGetValue(item.Key, out var value5) ? value5 : $"token_{item.Key}"),
OwnerHash = value.OwnerHash
};
_methodAccum[item.Key] = value4;
}
value4.TotalMs += num4;
value4.Calls += num3;
}
}
foreach (KeyValuePair<string, float> item2 in dictionary)
{
float value6;
float num5 = (_spikeTracking.TryGetValue(item2.Key, out value6) ? value6 : 0f);
if (item2.Value > num5)
{
_spikeTracking[item2.Key] = item2.Value;
}
}
float num6 = (float)(num / (double)Stopwatch.Frequency * 1000.0);
ShiftAndSet(GlobalHistory, num6);
if (num6 > _sessionWorst)
{
_sessionWorst = num6;
}
ShiftAndSet(WorstFrameHistory, _sessionWorst);
_sessionWorst *= 0.999f;
foreach (string key in _snapshotAccumulator.Keys)
{
if (!_modHistories.ContainsKey(key))
{
_modHistories[key] = new float[_historySize];
}
ShiftAndSet(_modHistories[key], dictionary.TryGetValue(key, out var value7) ? value7 : 0f);
}
ShiftAndSet(EventMarkerHistory, _pendingMark ? 1f : 0f);
_pendingMark = false;
if (_snapshotTimer.ElapsedMilliseconds >= 500)
{
PublishSnapshot();
}
}
private static void PublishSnapshot()
{
double elapsedSec = _snapshotTimer.Elapsed.TotalSeconds;
double num = (double)_frameCount / elapsedSec;
if (num <= 0.0)
{
num = 1.0;
}
double num2 = 1000.0 / num;
Dictionary<string, List<MethodAccumEntry>> dictionary = new Dictionary<string, List<MethodAccumEntry>>();
foreach (KeyValuePair<int, MethodAccumEntry> item in _methodAccum)
{
if (!(item.Value.TotalMs < 0.001))
{
string value;
string key = (_modNames.TryGetValue(item.Value.OwnerHash, out value) ? value : "Unknown");
if (!dictionary.ContainsKey(key))
{
dictionary[key] = new List<MethodAccumEntry>();
}
dictionary[key].Add(item.Value);
}
}
MetricSnapshot metricSnapshot = new MetricSnapshot
{
Fps = Math.Round(num, 1),
Mods = new List<ModMetric>(),
TotalHistory = (float[])GlobalHistory.Clone(),
WorstFrameHistory = (float[])WorstFrameHistory.Clone()
};
foreach (KeyValuePair<string, ModMetric> item2 in _snapshotAccumulator)
{
ModMetric value2 = item2.Value;
value2.AvgMs = value2.TotalMs / (double)_frameCount;
value2.ImpactPercent = value2.AvgMs / num2 * 100.0;
value2.CachedAvgMsString = value2.AvgMs.ToString("0.00");
value2.CachedImpactString = value2.ImpactPercent.ToString("0.0") + "%";
value2.CachedCallsString = ((double)value2.Calls / elapsedSec).ToString("0") + "/s";
if (_modHistories.TryGetValue(value2.Name, out var value3))
{
value2.History = (float[])value3.Clone();
}
value2.TopMethods = new List<MethodDetail>();
if (dictionary.TryGetValue(value2.Name, out var value4))
{
value2.TopMethods = (from m in value4.OrderByDescending((MethodAccumEntry m) => m.TotalMs).Take(10)
select new MethodDetail
{
MethodName = m.DisplayName,
AvgMs = m.TotalMs / (double)_frameCount,
Calls = m.Calls,
CachedAvgMs = (m.TotalMs / (double)_frameCount).ToString("0.00"),
CachedCalls = ((double)m.Calls / elapsedSec).ToString("0") + "/s"
}).ToList();
}
metricSnapshot.Mods.Add(value2);
}
metricSnapshot.Mods = metricSnapshot.Mods.OrderByDescending((ModMetric m) => m.TotalMs).ToList();
foreach (KeyValuePair<string, float> item3 in _spikeTracking)
{
if (!(item3.Value >= 16.6f))
{
continue;
}
lock (_alertsLock)
{
_alerts.Insert(0, new AlertEntry
{
ModName = item3.Key,
PeakMs = item3.Value,
Timestamp = DateTime.Now.ToString("HH:mm:ss")
});
if (_alerts.Count > 100)
{
_alerts.RemoveAt(_alerts.Count - 1);
}
}
}
_spikeTracking.Clear();
lock (_snapshotLock)
{
_currentSnapshot = metricSnapshot;
}
foreach (ModMetric value5 in _snapshotAccumulator.Values)
{
value5.TotalMs = 0.0;
value5.Calls = 0;
}
foreach (MethodAccumEntry value6 in _methodAccum.Values)
{
value6.TotalMs = 0.0;
value6.Calls = 0;
}
_frameCount = 0;
_snapshotTimer.Restart();
}
public static MetricSnapshot GetSnapshot()
{
lock (_snapshotLock)
{
return _currentSnapshot;
}
}
private static void ShiftAndSet(float[] arr, float newVal)
{
Array.Copy(arr, 1, arr, 0, arr.Length - 1);
arr[^1] = newVal;
}
private static Color GenerateColor(string name)
{
//IL_0022: Unknown result type (might be due to invalid IL or missing references)
return Color.HSVToRGB((float)Mathf.Abs(name.GetHashCode() % 360) / 360f, 0.85f, 1f);
}
}
public static class ReportExporter
{
public static string Export(MetricSnapshot snapshot)
{
if (snapshot == null)
{
throw new InvalidOperationException("No snapshot available yet — wait a moment after starting the scan.");
}
string text = Path.Combine(Paths.BepInExRootPath, "profiler_reports");
Directory.CreateDirectory(text);
string path = $"profiler_{DateTime.Now:yyyy-MM-dd_HH-mm-ss}.txt";
string text2 = Path.Combine(text, path);
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.AppendLine("=================================================");
stringBuilder.AppendLine(" Valheim Mod Profiler -- Performance Report ");
stringBuilder.AppendLine("=================================================");
stringBuilder.AppendLine();
stringBuilder.AppendLine($" Date : {DateTime.Now:yyyy-MM-dd HH:mm:ss}");
stringBuilder.AppendLine($" FPS : {snapshot.Fps:0.0}");
stringBuilder.AppendLine($" Mods : {snapshot.Mods?.Count ?? 0}");
stringBuilder.AppendLine();
stringBuilder.AppendLine(string.Format(" {0,-38} {1,9} {2,9} {3,9}", "Mod", "Avg CPU", "Impact", "Calls/s"));
stringBuilder.AppendLine(" " + new string('-', 68));
if (snapshot.Mods != null)
{
foreach (ModMetric mod in snapshot.Mods)
{
stringBuilder.AppendLine($" {mod.Name,-38} " + string.Format("{0,9} ", mod.CachedAvgMsString + "ms") + $"{mod.CachedImpactString,9} " + $"{mod.CachedCallsString,9}");
if (mod.TopMethods == null || mod.TopMethods.Count <= 0)
{
continue;
}
foreach (MethodDetail topMethod in mod.TopMethods)
{
stringBuilder.AppendLine($" └ {topMethod.MethodName,-36} " + string.Format("{0,9} ", topMethod.CachedAvgMs + "ms") + string.Format("{0,9} ", "") + $"{topMethod.CachedCalls,9}");
}
stringBuilder.AppendLine();
}
}
List<AlertEntry> alerts = ProfilerDataManager.GetAlerts();
if (alerts.Count > 0)
{
stringBuilder.AppendLine();
stringBuilder.AppendLine($" Spikes (>{16.6f:0.0}ms detected):");
stringBuilder.AppendLine(" " + new string('-', 50));
foreach (AlertEntry item in alerts)
{
stringBuilder.AppendLine($" [{item.Timestamp}] {item.ModName,-38} peak {item.PeakMs:0.0} ms");
}
}
File.WriteAllText(text2, stringBuilder.ToString(), Encoding.UTF8);
return text2;
}
}
}
namespace ValheimModProfiler.Core
{
[BepInPlugin("com.debug.valheim_profiler", "Valheim Mod Profiler", "1.0.0")]
[BepInProcess("valheim.exe")]
public class ModEntry : BaseUnityPlugin
{
public static ManualLogSource Logger;
public static ModEntry Instance { get; private set; }
private void Awake()
{
Instance = this;
Logger = ((BaseUnityPlugin)this).Logger;
ProfilerDataManager.Initialize();
}
private void Start()
{
//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_0010: Expected O, but got Unknown
GameObject val = new GameObject("ValheimProfilerOverlay");
Object.DontDestroyOnLoad((Object)val);
val.AddComponent<IngameProfiler>();
Logger.LogInfo((object)"Profiler UI Ready. Press F8 in game.");
}
public void StartScanning()
{
if (!ProfilerBootstrapper.IsScanning)
{
((MonoBehaviour)this).StartCoroutine(ProfilerBootstrapper.ApplyHooksCoroutine());
}
}
public void StopMonitoring()
{
ProfilerBootstrapper.StopMonitoring();
}
}
}