Decompiled source of LoadTimeProfiler v1.0.0

patchers\LoadTimeProfiler.dll

Decompiled a day ago
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Reflection.Emit;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
using System.Text;
using BepInEx;
using BepInEx.Bootstrap;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using Mono.Cecil;
using Mono.Cecil.Cil;
using UnityEngine;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: AssemblyTitle("LoadTimeProfiler")]
[assembly: AssemblyDescription("Valheim preloader startup and connection profiler")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("sighsorry")]
[assembly: AssemblyProduct("LoadTimeProfiler")]
[assembly: AssemblyCopyright("Copyright 2026")]
[assembly: AssemblyTrademark("")]
[assembly: ComVisible(false)]
[assembly: Guid("1E310F27-0F47-41D7-94D7-4A3FE40B8539")]
[assembly: AssemblyFileVersion("1.0.0")]
[assembly: TargetFramework(".NETFramework,Version=v4.8", FrameworkDisplayName = ".NET Framework 4.8")]
[assembly: AssemblyVersion("1.0.0.0")]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)]
	internal sealed class NullableAttribute : Attribute
	{
		public readonly byte[] NullableFlags;

		public NullableAttribute(byte P_0)
		{
			NullableFlags = new byte[1] { P_0 };
		}

		public NullableAttribute(byte[] P_0)
		{
			NullableFlags = P_0;
		}
	}
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)]
	internal sealed class NullableContextAttribute : Attribute
	{
		public readonly byte Flag;

		public NullableContextAttribute(byte P_0)
		{
			Flag = P_0;
		}
	}
	[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 LoadTimeProfiler
{
	public static class LoadTimeProfilerPatcher
	{
		internal const string ModName = "LoadTimeProfiler";

		internal const string ModVersion = "1.0.0";

		internal const string Author = "sighsorry";

		internal const string ModGUID = "sighsorry.LoadTimeProfiler";

		private static ConfigEntry<bool>? _enabled;

		private static bool _initialized;

		public static IEnumerable<string> TargetDLLs { get; } = new string[1] { "UnityEngine.CoreModule.dll" };

		internal static ManualLogSource? Log { get; private set; }

		internal static bool ProfilingEnabled { get; private set; }

		internal static bool IsDedicatedServer { get; private set; }

		public static void Patch(AssemblyDefinition assembly)
		{
			TimelineProfiler.CapturePatcherStart();
			try
			{
				InjectRuntimeEntrypoint(assembly);
			}
			catch (Exception arg)
			{
				Console.Error.WriteLine(string.Format("[{0}] Could not inject runtime entrypoint: {1}", "LoadTimeProfiler", arg));
			}
		}

		public static void Finish()
		{
		}

		private static void InjectRuntimeEntrypoint(AssemblyDefinition assembly)
		{
			//IL_00c4: 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)
			MethodDefinition obj = ((IEnumerable<MethodDefinition>)((IEnumerable<TypeDefinition>)assembly.MainModule.Types).First((TypeDefinition type) => ((TypeReference)type).Namespace == "UnityEngine" && ((MemberReference)type).Name == "GameObject").Methods).First((MethodDefinition val2) => val2.IsConstructor && val2.IsStatic);
			Instruction val = ((IEnumerable<Instruction>)obj.Body.Instructions).First(delegate(Instruction instruction)
			{
				//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)
				if (instruction.OpCode == OpCodes.Call)
				{
					object operand = instruction.Operand;
					MethodReference val2 = (MethodReference)((operand is MethodReference) ? operand : null);
					if (val2 != null && ((MemberReference)((MemberReference)val2).DeclaringType).FullName == "BepInEx.Bootstrap.Chainloader")
					{
						return ((MemberReference)val2).Name == "Start";
					}
				}
				return false;
			});
			MethodInfo method = typeof(RuntimeEntrypoint).GetMethod("BeforeChainloaderStart", BindingFlags.Static | BindingFlags.Public);
			MethodInfo method2 = typeof(RuntimeEntrypoint).GetMethod("AfterChainloaderStart", BindingFlags.Static | BindingFlags.Public);
			ILProcessor iLProcessor = obj.Body.GetILProcessor();
			iLProcessor.InsertBefore(val, iLProcessor.Create(OpCodes.Call, assembly.MainModule.ImportReference((MethodBase)method)));
			iLProcessor.InsertAfter(val, iLProcessor.Create(OpCodes.Call, assembly.MainModule.ImportReference((MethodBase)method2)));
		}

		internal static void InitializeProfiler()
		{
			//IL_0028: Unknown result type (might be due to invalid IL or missing references)
			if (!_initialized)
			{
				_initialized = true;
				IsDedicatedServer = DetectDedicatedServer();
				try
				{
					_enabled = new ConfigFile(Path.Combine(Paths.ConfigPath, "sighsorry.LoadTimeProfiler.cfg"), true).Bind<bool>("General", "Enabled", true, "Profile client loading or dedicated server startup. Changes apply on the next launch.");
					ProfilingEnabled = _enabled.Value;
				}
				catch (Exception ex)
				{
					ProfilingEnabled = true;
					Console.Error.WriteLine("[LoadTimeProfiler] Could not read config; profiling remains enabled: " + ex.Message);
				}
				ProfilerLog.Initialize();
				if (!ProfilingEnabled)
				{
					ProfilerLog.WriteLine("Profiling is disabled in config.");
					return;
				}
				TimelineProfiler.BeginStartup(IsDedicatedServer);
				ProfilerLog.WriteLine(IsDedicatedServer ? "Preloader profiler active. Waiting for dedicated server startup." : "Preloader profiler active. Waiting for BepInEx chainloader startup.");
			}
		}

		internal static void AttachBepInExLogger()
		{
			if (Log != null)
			{
				return;
			}
			try
			{
				Log = Logger.CreateLogSource("LoadTimeProfiler");
				if (ProfilingEnabled)
				{
					string text = (IsDedicatedServer ? "dedicated server startup" : "startup and connection");
					Log.LogInfo((object)("Writing " + text + " profiles to " + ProfilerLog.FilePath + "."));
				}
				else
				{
					Log.LogInfo((object)("Profiler disabled. Session log reset at " + ProfilerLog.FilePath + "."));
				}
			}
			catch (Exception ex)
			{
				Console.Error.WriteLine("[LoadTimeProfiler] Could not create BepInEx log source: " + ex.Message);
			}
		}

		internal static void LogInfo(string message)
		{
			ManualLogSource? log = Log;
			if (log != null)
			{
				log.LogInfo((object)message);
			}
		}

		internal static void LogWarning(string message)
		{
			if (Log != null)
			{
				Log.LogWarning((object)message);
			}
			else
			{
				Console.Error.WriteLine("[LoadTimeProfiler] " + message);
			}
		}

		internal static void LogError(string message)
		{
			if (Log != null)
			{
				Log.LogError((object)message);
			}
			else
			{
				Console.Error.WriteLine("[LoadTimeProfiler] " + message);
			}
		}

		private static bool DetectDedicatedServer()
		{
			return (Paths.ProcessName ?? string.Empty).IndexOf("valheim_server", StringComparison.OrdinalIgnoreCase) >= 0;
		}
	}
	internal static class ProfilerLog
	{
		private const int MaximumRetainedLogs = 10;

		private static readonly object Lock = new object();

		private static StreamWriter? _writer;

		internal static string FilePath { get; private set; } = Path.Combine(Paths.ConfigPath, "LoadTimeProfiler", "pending.log");

		internal static void Initialize()
		{
			lock (Lock)
			{
				DisposeWriter();
				DateTime now = DateTime.Now;
				string text = Path.Combine(Paths.ConfigPath, "LoadTimeProfiler");
				try
				{
					Directory.CreateDirectory(text);
					RetainNewestLogs(text, 9);
					FilePath = CreateUniqueLogPath(text, now);
					_writer = new StreamWriter(new FileStream(FilePath, FileMode.CreateNew, FileAccess.Write, FileShare.ReadWrite), new UTF8Encoding(encoderShouldEmitUTF8Identifier: false))
					{
						AutoFlush = true
					};
					_writer.WriteLine("LoadTimeProfiler 1.0.0");
					_writer.WriteLine($"Session: {now:yyyy-MM-dd HH:mm:ss zzz}");
					_writer.WriteLine("Process: " + Paths.ProcessName);
					_writer.WriteLine("Mode: " + (LoadTimeProfilerPatcher.IsDedicatedServer ? "Dedicated server" : "Client"));
					_writer.WriteLine($"BepInEx: {typeof(BaseUnityPlugin).Assembly.GetName().Version}");
					_writer.WriteLine(LoadTimeProfilerPatcher.IsDedicatedServer ? "Coverage: BepInEx plugin construction/Awake/OnEnable, plugin Start methods, dedicated server lifecycle execution, and milestone intervals." : "Coverage: BepInEx plugin construction/Awake/OnEnable, plugin Start methods, selected client lifecycle execution, and milestone intervals.");
					_writer.WriteLine("Deep attribution instruments existing synchronous Harmony callbacks in ObjectDB.Awake and ZNetScene.Awake.");
					_writer.WriteLine();
				}
				catch (Exception ex)
				{
					DisposeWriter();
					LoadTimeProfilerPatcher.LogError("Could not create profiler log '" + FilePath + "': " + ex.Message);
				}
			}
		}

		private static string CreateUniqueLogPath(string directory, DateTime timestamp)
		{
			string text = timestamp.ToString("yyyy-MM-dd_HH-mm-ss");
			string text2 = Path.Combine(directory, text + ".log");
			int num = 2;
			while (File.Exists(text2))
			{
				text2 = Path.Combine(directory, text + "_" + num + ".log");
				num++;
			}
			return text2;
		}

		private static void RetainNewestLogs(string directory, int maximumExistingLogs)
		{
			FileInfo[] array = (from file in new DirectoryInfo(directory).GetFiles("*.log", SearchOption.TopDirectoryOnly)
				orderby file.LastWriteTimeUtc
				select file).ThenBy<FileInfo, string>((FileInfo file) => file.Name, StringComparer.Ordinal).ToArray();
			int num = Math.Max(0, array.Length - maximumExistingLogs);
			for (int num2 = 0; num2 < num; num2++)
			{
				try
				{
					array[num2].Delete();
				}
				catch (IOException)
				{
				}
				catch (UnauthorizedAccessException)
				{
				}
			}
		}

		internal static void WriteLine(string text)
		{
			lock (Lock)
			{
				if (_writer == null)
				{
					return;
				}
				try
				{
					_writer.WriteLine(text);
				}
				catch (Exception ex)
				{
					LoadTimeProfilerPatcher.LogWarning("Could not write profiler log: " + ex.Message);
					DisposeWriter();
				}
			}
		}

		internal static void WriteBlock(string text)
		{
			lock (Lock)
			{
				if (_writer == null)
				{
					return;
				}
				try
				{
					_writer.WriteLine(text.TrimEnd(Array.Empty<char>()));
					_writer.WriteLine();
				}
				catch (Exception ex)
				{
					LoadTimeProfilerPatcher.LogWarning("Could not write profiler log: " + ex.Message);
					DisposeWriter();
				}
			}
		}

		private static void DisposeWriter()
		{
			try
			{
				_writer?.Dispose();
			}
			catch
			{
			}
			_writer = null;
		}
	}
	internal enum ProfileSession
	{
		Startup,
		Connection
	}
	[Flags]
	internal enum ProfileSessionMask
	{
		None = 0,
		Startup = 1,
		Connection = 2
	}
	internal static class TimelineProfiler
	{
		private sealed class SessionState
		{
			private readonly List<Milestone> _milestones = new List<Milestone>();

			private readonly HashSet<string> _seenMilestones = new HashSet<string>(StringComparer.Ordinal);

			internal ProfileSession Session { get; }

			internal string Name { get; private set; }

			internal bool Active { get; private set; }

			private double StartMilliseconds { get; set; }

			internal SessionState(ProfileSession session, string name)
			{
				Session = session;
				Name = name;
			}

			internal void Begin(double startMilliseconds, string? name = null)
			{
				if (!string.IsNullOrEmpty(name))
				{
					Name = name;
				}
				Active = true;
				StartMilliseconds = startMilliseconds;
				_milestones.Clear();
				_seenMilestones.Clear();
			}

			internal void AddMilestoneOnce(string label, double absoluteMilliseconds)
			{
				if (_seenMilestones.Add(label))
				{
					AddMilestone(label, absoluteMilliseconds);
				}
			}

			internal void AddMilestone(string label, double absoluteMilliseconds)
			{
				_seenMilestones.Add(label);
				_milestones.Add(new Milestone(label, Math.Max(0.0, absoluteMilliseconds - StartMilliseconds)));
			}

			internal SessionSnapshot Complete(double absoluteMilliseconds, string result)
			{
				Active = false;
				double totalMilliseconds = Math.Max(0.0, absoluteMilliseconds - StartMilliseconds);
				return new SessionSnapshot(Session, Name, result, totalMilliseconds, _milestones.ToArray());
			}
		}

		private readonly struct SessionSnapshot
		{
			internal ProfileSession Session { get; }

			internal string Name { get; }

			internal string Result { get; }

			internal double TotalMilliseconds { get; }

			internal Milestone[] Milestones { get; }

			internal SessionSnapshot(ProfileSession session, string name, string result, double totalMilliseconds, Milestone[] milestones)
			{
				Session = session;
				Name = name;
				Result = result;
				TotalMilliseconds = totalMilliseconds;
				Milestones = milestones;
			}
		}

		private readonly struct Milestone
		{
			internal string Label { get; }

			internal double ElapsedMilliseconds { get; }

			internal Milestone(string label, double elapsedMilliseconds)
			{
				Label = label;
				ElapsedMilliseconds = elapsedMilliseconds;
			}
		}

		private static readonly object Lock = new object();

		private static readonly SessionState Startup = new SessionState(ProfileSession.Startup, "Start To Lobby");

		private static readonly SessionState Connection = new SessionState(ProfileSession.Connection, "Lobby To World");

		private static double _patcherStartMilliseconds;

		internal static void CapturePatcherStart()
		{
			lock (Lock)
			{
				if (_patcherStartMilliseconds <= 0.0)
				{
					_patcherStartMilliseconds = NowMilliseconds();
				}
			}
		}

		internal static void BeginStartup(bool dedicatedServer)
		{
			if (!LoadTimeProfilerPatcher.ProfilingEnabled)
			{
				return;
			}
			LifecyclePhaseProfiler.ResetSession(ProfileSession.Startup);
			ChainloaderProfiler.ResetSession();
			if (dedicatedServer)
			{
				DeepLobbyAttributionProfiler.ResetSession();
			}
			lock (Lock)
			{
				double num = NowMilliseconds();
				double num2 = ((_patcherStartMilliseconds > 0.0) ? _patcherStartMilliseconds : num);
				Startup.Begin(num2, dedicatedServer ? "Server Startup" : "Start To Lobby");
				Startup.AddMilestone("LoadTimeProfiler.Patcher.Finish", num2);
				Startup.AddMilestone("LoadTimeProfiler.Patcher initialized", num);
			}
		}

		internal static void BeginConnection(string label, bool restartActive)
		{
			if (!LoadTimeProfilerPatcher.ProfilingEnabled)
			{
				return;
			}
			if (restartActive)
			{
				AbortConnection("superseded by " + label);
			}
			lock (Lock)
			{
				if (Connection.Active)
				{
					Connection.AddMilestoneOnce(label, NowMilliseconds());
					return;
				}
			}
			LifecyclePhaseProfiler.ResetSession(ProfileSession.Connection);
			DeepLobbyAttributionProfiler.ResetSession();
			lock (Lock)
			{
				double num = NowMilliseconds();
				if (Connection.Active)
				{
					Connection.AddMilestoneOnce(label, num);
					return;
				}
				Connection.Begin(num);
				Connection.AddMilestone(label, num);
			}
		}

		internal static void MarkStartup(string label)
		{
			Mark(Startup, label);
		}

		internal static void MarkConnection(string label)
		{
			Mark(Connection, label);
		}

		internal static bool IsActive(ProfileSession session)
		{
			lock (Lock)
			{
				return GetState(session).Active;
			}
		}

		internal static ProfileSessionMask GetActiveSessionMask()
		{
			lock (Lock)
			{
				ProfileSessionMask profileSessionMask = ProfileSessionMask.None;
				if (Startup.Active)
				{
					profileSessionMask |= ProfileSessionMask.Startup;
				}
				if (Connection.Active)
				{
					profileSessionMask |= ProfileSessionMask.Connection;
				}
				return profileSessionMask;
			}
		}

		internal static double GetElapsedSincePatcherStart()
		{
			lock (Lock)
			{
				return (_patcherStartMilliseconds <= 0.0) ? 0.0 : Math.Max(0.0, NowMilliseconds() - _patcherStartMilliseconds);
			}
		}

		internal static void CompleteStartup(string label)
		{
			Finish(Startup, label, "completed");
		}

		internal static void AbortStartup(string label)
		{
			Finish(Startup, label, "aborted");
		}

		internal static void CompleteConnection(string label)
		{
			Finish(Connection, label, "completed");
		}

		internal static void AbortConnection(string label)
		{
			Finish(Connection, label, "aborted");
		}

		private static void Mark(SessionState state, string label)
		{
			if (!LoadTimeProfilerPatcher.ProfilingEnabled)
			{
				return;
			}
			lock (Lock)
			{
				if (state.Active)
				{
					state.AddMilestoneOnce(label, NowMilliseconds());
				}
			}
		}

		private static void Finish(SessionState state, string label, string result)
		{
			SessionSnapshot sessionSnapshot;
			lock (Lock)
			{
				if (!state.Active)
				{
					return;
				}
				double absoluteMilliseconds = NowMilliseconds();
				state.AddMilestone(label, absoluteMilliseconds);
				sessionSnapshot = state.Complete(absoluteMilliseconds, result);
			}
			StringBuilder stringBuilder = new StringBuilder();
			stringBuilder.AppendLine("=== " + sessionSnapshot.Name + " ===");
			stringBuilder.AppendLine("Result: " + sessionSnapshot.Result);
			stringBuilder.Append("Total: ").AppendLine(FormatDuration(sessionSnapshot.TotalMilliseconds));
			Dictionary<string, double> lifecycleExecutionTimes = LifecyclePhaseProfiler.SnapshotSingleExecutionTimes(sessionSnapshot.Session);
			AppendMilestoneIntervals(stringBuilder, sessionSnapshot.Milestones, lifecycleExecutionTimes);
			if (sessionSnapshot.Session == ProfileSession.Startup)
			{
				ChainloaderProfiler.AppendStartupReport(stringBuilder);
			}
			LifecyclePhaseProfiler.AppendSessionReport(stringBuilder, sessionSnapshot.Session);
			if (sessionSnapshot.Session == ProfileSession.Connection || (sessionSnapshot.Session == ProfileSession.Startup && LoadTimeProfilerPatcher.IsDedicatedServer))
			{
				DeepLobbyAttributionProfiler.AppendReport(stringBuilder);
			}
			ProfilerLog.WriteBlock(stringBuilder.ToString());
			LoadTimeProfilerPatcher.LogInfo(sessionSnapshot.Name + " profile " + sessionSnapshot.Result + ": " + FormatDuration(sessionSnapshot.TotalMilliseconds) + ". See " + ProfilerLog.FilePath + ".");
		}

		private static void AppendMilestoneIntervals(StringBuilder builder, Milestone[] milestones, IReadOnlyDictionary<string, double> lifecycleExecutionTimes)
		{
			builder.AppendLine("Milestone intervals:");
			builder.AppendLine("  Breakdown: lifecycle execution + remaining time until the next milestone.");
			if (milestones.Length < 2)
			{
				builder.AppendLine("  No milestone interval completed.");
				return;
			}
			for (int i = 0; i < milestones.Length - 1; i++)
			{
				Milestone milestone = milestones[i];
				Milestone milestone2 = milestones[i + 1];
				double num = Math.Max(0.0, milestone2.ElapsedMilliseconds - milestone.ElapsedMilliseconds);
				double num2 = Math.Truncate(num);
				builder.Append("  ").Append(FormatSeconds(num2));
				if (lifecycleExecutionTimes.TryGetValue(milestone.Label, out var value) && value <= num)
				{
					double num3 = Math.Min(num2, Math.Truncate(Math.Max(0.0, value)));
					double milliseconds = num2 - num3;
					builder.Append(" (").Append(FormatSeconds(num3)).Append(" + ")
						.Append(FormatSeconds(milliseconds))
						.Append(')');
				}
				builder.Append(": ").AppendLine(milestone.Label);
			}
		}

		private static SessionState GetState(ProfileSession session)
		{
			if (session != ProfileSession.Startup)
			{
				return Connection;
			}
			return Startup;
		}

		private static double NowMilliseconds()
		{
			return (double)Stopwatch.GetTimestamp() * 1000.0 / (double)Stopwatch.Frequency;
		}

		internal static string FormatDuration(double milliseconds)
		{
			if (milliseconds >= 60000.0)
			{
				int num = (int)(milliseconds / 60000.0);
				double num2 = (milliseconds - (double)num * 60000.0) / 1000.0;
				return $"{num} min {num2:00.000} s";
			}
			if (milliseconds >= 1000.0)
			{
				return $"{milliseconds / 1000.0:0.000} s";
			}
			return $"{milliseconds:0.###} ms";
		}

		internal static string FormatSeconds(double milliseconds)
		{
			return (Math.Truncate(Math.Max(0.0, milliseconds)) / 1000.0).ToString("0.000", CultureInfo.InvariantCulture) + " s";
		}
	}
	public static class RuntimeEntrypoint
	{
		private static bool _chainloaderStarted;

		public static void BeforeChainloaderStart()
		{
			try
			{
				LoadTimeProfilerPatcher.InitializeProfiler();
				LoadTimeProfilerPatcher.AttachBepInExLogger();
				if (LoadTimeProfilerPatcher.ProfilingEnabled)
				{
					ProfilerLog.WriteLine("Chainloader runtime entrypoint reached. Installing Unity and Valheim hooks.");
					RuntimeHookInstaller.Install();
					ChainloaderProfiler.BeginChainloader();
					_chainloaderStarted = true;
				}
			}
			catch (Exception ex)
			{
				RuntimeHookInstaller.RemovePluginConstructionHook();
				ProfilerLog.WriteLine("Runtime hook initialization failed: " + ex);
				LoadTimeProfilerPatcher.LogError("Runtime hook initialization failed: " + ex.Message);
			}
		}

		public static void AfterChainloaderStart()
		{
			bool flag = _chainloaderStarted && LoadTimeProfilerPatcher.ProfilingEnabled && LoadTimeProfilerPatcher.IsDedicatedServer;
			try
			{
				if (_chainloaderStarted)
				{
					ChainloaderProfiler.EndChainloader();
				}
			}
			catch (Exception ex)
			{
				ProfilerLog.WriteLine("Chainloader completion measurement failed: " + ex);
			}
			finally
			{
				_chainloaderStarted = false;
				RuntimeHookInstaller.RemovePluginConstructionHook();
			}
			if (flag)
			{
				try
				{
					DeepLobbyAttributionProfiler.PrepareForActiveSession();
				}
				catch (Exception ex2)
				{
					ProfilerLog.WriteLine("Dedicated server attribution preparation failed: " + ex2);
				}
			}
		}
	}
	internal static class RuntimeHookInstaller
	{
		private static readonly object Lock = new object();

		private static readonly Harmony Harmony = new Harmony("sighsorry.LoadTimeProfiler.runtime");

		private static bool _installed;

		private static MethodBase? _pluginConstructionTarget;

		private static bool _pluginConstructionPatched;

		internal static void Install()
		{
			lock (Lock)
			{
				if (!_installed)
				{
					_installed = true;
					int installed = 0;
					int failed = 0;
					if (TryPatchPluginConstruction())
					{
						installed++;
					}
					else
					{
						failed++;
					}
					PatchLifecycleTargets(ref installed, ref failed);
					ProfilerLog.WriteLine($"Unity and Valheim runtime hook installation completed: installed={installed}, failed={failed}.");
				}
			}
		}

		internal static void RemovePluginConstructionHook()
		{
			lock (Lock)
			{
				if (!_pluginConstructionPatched || _pluginConstructionTarget == null)
				{
					return;
				}
				try
				{
					Harmony.Unpatch(_pluginConstructionTarget, (HarmonyPatchType)0, Harmony.Id);
					ProfilerLog.WriteLine("Removed the startup-only GameObject.AddComponent profiling hook.");
				}
				catch (Exception ex)
				{
					ProfilerLog.WriteLine("Runtime hook warning: could not remove the plugin construction hook: " + ex.Message);
				}
				finally
				{
					_pluginConstructionPatched = false;
					_pluginConstructionTarget = null;
				}
			}
		}

		private static bool TryPatchPluginConstruction()
		{
			try
			{
				MethodBase methodBase = AccessTools.Method(typeof(GameObject), "AddComponent", new Type[1] { typeof(Type) }, (Type[])null);
				if (methodBase == null)
				{
					throw new MissingMethodException(typeof(GameObject).FullName, "AddComponent");
				}
				Harmony.Patch(methodBase, Highest(typeof(LoadTimeProfilerPluginInitializationPatch), "Prefix"), Lowest(typeof(LoadTimeProfilerPluginInitializationPatch), "Postfix"), (HarmonyMethod)null, Lowest(typeof(LoadTimeProfilerPluginInitializationPatch), "Finalizer"), (HarmonyMethod)null);
				_pluginConstructionTarget = methodBase;
				_pluginConstructionPatched = true;
				return true;
			}
			catch (Exception ex)
			{
				ProfilerLog.WriteLine("Runtime hook warning: could not profile plugin construction: " + ex.Message);
				return false;
			}
		}

		private static void PatchLifecycleTargets(ref int installed, ref int failed)
		{
			HarmonyMethod val;
			HarmonyMethod val2;
			try
			{
				val = Highest(typeof(LoadTimeProfilerLifecyclePatch), "Prefix");
				val2 = Lowest(typeof(LoadTimeProfilerLifecyclePatch), "Finalizer");
			}
			catch (Exception ex)
			{
				failed++;
				ProfilerLog.WriteLine("Runtime hook warning: could not prepare lifecycle hooks: " + ex.Message);
				return;
			}
			foreach (MethodBase target in LifecyclePatches.GetTargets())
			{
				try
				{
					Harmony.Patch(target, val, (HarmonyMethod)null, (HarmonyMethod)null, val2, (HarmonyMethod)null);
					installed++;
				}
				catch (Exception ex2)
				{
					failed++;
					string text = (target.DeclaringType?.FullName ?? "<unknown>") + "." + target.Name;
					ProfilerLog.WriteLine("Runtime hook warning: could not patch " + text + ": " + ex2.Message);
				}
			}
		}

		private static HarmonyMethod Highest(Type type, string methodName)
		{
			//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_0030: Expected O, but got Unknown
			MethodInfo methodInfo = AccessTools.Method(type, methodName, (Type[])null, (Type[])null);
			if (methodInfo == null)
			{
				throw new MissingMethodException(type.FullName, methodName);
			}
			return new HarmonyMethod(methodInfo)
			{
				priority = int.MaxValue
			};
		}

		private static HarmonyMethod Lowest(Type type, string methodName)
		{
			//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_0030: Expected O, but got Unknown
			MethodInfo methodInfo = AccessTools.Method(type, methodName, (Type[])null, (Type[])null);
			if (methodInfo == null)
			{
				throw new MissingMethodException(type.FullName, methodName);
			}
			return new HarmonyMethod(methodInfo)
			{
				priority = int.MinValue
			};
		}
	}
	internal static class ChainloaderProfiler
	{
		internal sealed class PluginInitializationState
		{
			internal Type ComponentType { get; }

			internal PluginIdentity Identity { get; }

			internal long StartTimestamp { get; }

			internal bool Completed { get; set; }

			internal PluginInitializationState(Type componentType, PluginIdentity identity, long startTimestamp)
			{
				ComponentType = componentType;
				Identity = identity;
				StartTimestamp = startTimestamp;
			}
		}

		internal readonly struct PluginIdentity
		{
			internal string Key { get; }

			internal string DisplayName { get; }

			internal PluginIdentity(string key, string displayName)
			{
				Key = key;
				DisplayName = displayName;
			}
		}

		private sealed class MutableTiming
		{
			private PluginIdentity Identity { get; }

			private int Count { get; set; }

			private double ElapsedMilliseconds { get; set; }

			internal MutableTiming(PluginIdentity identity)
			{
				Identity = identity;
			}

			internal void Add(double elapsedMilliseconds)
			{
				Count++;
				ElapsedMilliseconds += elapsedMilliseconds;
			}

			internal TimingSnapshot Snapshot()
			{
				return new TimingSnapshot(Identity, Count, ElapsedMilliseconds);
			}
		}

		private readonly struct TimingSnapshot
		{
			internal PluginIdentity Identity { get; }

			internal int Count { get; }

			internal double ElapsedMilliseconds { get; }

			internal TimingSnapshot(PluginIdentity identity, int count, double elapsedMilliseconds)
			{
				Identity = identity;
				Count = count;
				ElapsedMilliseconds = elapsedMilliseconds;
			}
		}

		private static readonly object Lock = new object();

		private static readonly Harmony PluginStartHarmony = new Harmony("sighsorry.LoadTimeProfiler.plugin-start");

		private static readonly Dictionary<string, MutableTiming> Initializations = new Dictionary<string, MutableTiming>(StringComparer.Ordinal);

		private static readonly Dictionary<string, MutableTiming> Starts = new Dictionary<string, MutableTiming>(StringComparer.Ordinal);

		private static readonly Dictionary<MethodBase, PluginIdentity> StartMethods = new Dictionary<MethodBase, PluginIdentity>();

		private static readonly HashSet<MethodBase> InstrumentedStartMethods = new HashSet<MethodBase>();

		private static bool _chainloaderActive;

		private static long _chainloaderStarted;

		private static double _preChainloaderMilliseconds;

		private static double _chainloaderMilliseconds;

		internal static void ResetSession()
		{
			lock (Lock)
			{
				Initializations.Clear();
				Starts.Clear();
				_chainloaderActive = false;
				_chainloaderStarted = 0L;
				_preChainloaderMilliseconds = 0.0;
				_chainloaderMilliseconds = 0.0;
			}
		}

		internal static void BeginChainloader()
		{
			if (LoadTimeProfilerPatcher.ProfilingEnabled)
			{
				lock (Lock)
				{
					_chainloaderActive = true;
					_chainloaderStarted = Stopwatch.GetTimestamp();
					_preChainloaderMilliseconds = TimelineProfiler.GetElapsedSincePatcherStart();
				}
				TimelineProfiler.MarkStartup("BepInEx.Chainloader.Start");
			}
		}

		internal static void EndChainloader()
		{
			long timestamp = Stopwatch.GetTimestamp();
			lock (Lock)
			{
				if (_chainloaderStarted > 0)
				{
					_chainloaderMilliseconds = TicksToMilliseconds(timestamp - _chainloaderStarted);
					_chainloaderStarted = 0L;
				}
				_chainloaderActive = false;
			}
			TimelineProfiler.MarkStartup("BepInEx.Chainloader.Start complete");
		}

		internal static PluginInitializationState? BeginPlugin(GameObject gameObject, Type componentType)
		{
			if (!LoadTimeProfilerPatcher.ProfilingEnabled || !typeof(BaseUnityPlugin).IsAssignableFrom(componentType))
			{
				return null;
			}
			lock (Lock)
			{
				if (!_chainloaderActive || gameObject != Chainloader.ManagerObject)
				{
					return null;
				}
			}
			return new PluginInitializationState(componentType, ResolveIdentity(componentType), Stopwatch.GetTimestamp());
		}

		internal static void CompletePlugin(PluginInitializationState? state)
		{
			if (state != null && !state.Completed)
			{
				state.Completed = true;
				double elapsedMilliseconds = TicksToMilliseconds(Stopwatch.GetTimestamp() - state.StartTimestamp);
				lock (Lock)
				{
					Add(Initializations, state.Identity.Key, state.Identity, elapsedMilliseconds);
				}
				InstrumentStart(state.ComponentType, state.Identity);
			}
		}

		internal static void AppendStartupReport(StringBuilder builder)
		{
			TimingSnapshot[] array;
			TimingSnapshot[] timings;
			double preChainloaderMilliseconds;
			double chainloaderMilliseconds;
			lock (Lock)
			{
				array = Initializations.Values.Select((MutableTiming value) => value.Snapshot()).ToArray();
				timings = Starts.Values.Select((MutableTiming value) => value.Snapshot()).ToArray();
				preChainloaderMilliseconds = _preChainloaderMilliseconds;
				chainloaderMilliseconds = _chainloaderMilliseconds;
			}
			builder.AppendLine("BepInEx startup:");
			builder.Append("  Before Chainloader.Start (includes Chainloader.Initialize): ").AppendLine(TimelineProfiler.FormatDuration(preChainloaderMilliseconds));
			builder.Append("  Chainloader.Start: ").AppendLine(TimelineProfiler.FormatDuration(chainloaderMilliseconds));
			builder.AppendLine("Plugin construction/Awake/OnEnable:");
			AppendTimings(builder, array);
			builder.AppendLine("Plugin Start methods:");
			AppendTimings(builder, timings);
			double num = array.Sum((TimingSnapshot value) => value.ElapsedMilliseconds);
			double milliseconds = Math.Max(0.0, chainloaderMilliseconds - num);
			builder.Append("  Chainloader scan/load/dependency remainder: ").AppendLine(TimelineProfiler.FormatDuration(milliseconds));
		}

		private static void AppendTimings(StringBuilder builder, TimingSnapshot[] timings)
		{
			if (timings.Length == 0)
			{
				builder.AppendLine("  No callbacks were measured.");
				return;
			}
			foreach (TimingSnapshot item in timings.OrderByDescending((TimingSnapshot value) => value.ElapsedMilliseconds))
			{
				builder.Append("  ").Append(TimelineProfiler.FormatSeconds(item.ElapsedMilliseconds));
				if (item.Count > 1)
				{
					builder.Append(" x").Append(item.Count);
				}
				builder.Append(": ").AppendLine(item.Identity.DisplayName);
			}
		}

		private static void InstrumentStart(Type componentType, PluginIdentity identity)
		{
			//IL_007a: 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_008b: Expected O, but got Unknown
			//IL_009b: 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_00ad: Expected O, but got Unknown
			MethodInfo method = componentType.GetMethod("Start", BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, Type.EmptyTypes, null);
			if (method == null || method.IsAbstract || method.ContainsGenericParameters)
			{
				return;
			}
			lock (Lock)
			{
				if (!InstrumentedStartMethods.Add(method))
				{
					return;
				}
				StartMethods[method] = identity;
			}
			try
			{
				HarmonyMethod val = new HarmonyMethod(typeof(ChainloaderProfiler), "ProfiledStartPrefix", (Type[])null)
				{
					priority = int.MaxValue
				};
				HarmonyMethod val2 = new HarmonyMethod(typeof(ChainloaderProfiler), "ProfiledStartFinalizer", (Type[])null)
				{
					priority = int.MinValue
				};
				PluginStartHarmony.Patch((MethodBase)method, val, (HarmonyMethod)null, (HarmonyMethod)null, val2, (HarmonyMethod)null);
			}
			catch (Exception ex)
			{
				lock (Lock)
				{
					InstrumentedStartMethods.Remove(method);
					StartMethods.Remove(method);
				}
				ProfilerLog.WriteLine("Instrumentation warning: could not profile plugin Start '" + componentType.FullName + "': " + ex.Message);
			}
		}

		private static void ProfiledStartPrefix(out long __state)
		{
			__state = 0L;
			if (LoadTimeProfilerPatcher.ProfilingEnabled && TimelineProfiler.IsActive(ProfileSession.Startup))
			{
				__state = Stopwatch.GetTimestamp();
			}
		}

		private static Exception? ProfiledStartFinalizer(MethodBase __originalMethod, long __state, Exception? __exception)
		{
			if (__state <= 0)
			{
				return __exception;
			}
			double elapsedMilliseconds = TicksToMilliseconds(Stopwatch.GetTimestamp() - __state);
			lock (Lock)
			{
				if (StartMethods.TryGetValue(__originalMethod, out var value))
				{
					Add(Starts, value.Key, value, elapsedMilliseconds);
				}
			}
			return __exception;
		}

		private static PluginIdentity ResolveIdentity(Type componentType)
		{
			BepInPlugin? obj = componentType.GetCustomAttributes(typeof(BepInPlugin), inherit: false).OfType<BepInPlugin>().FirstOrDefault();
			string key = ((obj != null) ? obj.GUID : null) ?? componentType.FullName ?? componentType.Name;
			string displayName = ((obj != null) ? obj.Name : null) ?? componentType.Name;
			return new PluginIdentity(key, displayName);
		}

		private static void Add(Dictionary<string, MutableTiming> timings, string key, PluginIdentity identity, double elapsedMilliseconds)
		{
			if (!timings.TryGetValue(key, out MutableTiming value))
			{
				value = (timings[key] = new MutableTiming(identity));
			}
			value.Add(elapsedMilliseconds);
		}

		private static double TicksToMilliseconds(long ticks)
		{
			return (double)ticks * 1000.0 / (double)Stopwatch.Frequency;
		}
	}
	internal static class LoadTimeProfilerPluginInitializationPatch
	{
		internal static void Prefix(GameObject __instance, Type componentType, out ChainloaderProfiler.PluginInitializationState? __state)
		{
			__state = ChainloaderProfiler.BeginPlugin(__instance, componentType);
		}

		internal static void Postfix(ChainloaderProfiler.PluginInitializationState? __state)
		{
			ChainloaderProfiler.CompletePlugin(__state);
		}

		internal static Exception? Finalizer(ChainloaderProfiler.PluginInitializationState? __state, Exception? __exception)
		{
			ChainloaderProfiler.CompletePlugin(__state);
			return __exception;
		}
	}
	internal static class LifecyclePhaseProfiler
	{
		private sealed class PhaseContext
		{
			internal MethodBase Target { get; }

			internal string Label { get; }

			internal ProfileSessionMask Sessions { get; }

			internal long StartTimestamp { get; }

			internal PhaseContext(MethodBase target, string label, ProfileSessionMask sessions, long startTimestamp)
			{
				Target = target;
				Label = label;
				Sessions = sessions;
				StartTimestamp = startTimestamp;
			}
		}

		private sealed class SessionAggregate
		{
			private readonly Dictionary<string, MutableTiming> _timings = new Dictionary<string, MutableTiming>(StringComparer.Ordinal);

			internal void Clear()
			{
				_timings.Clear();
			}

			internal void Add(PhaseContext context, double elapsedMilliseconds)
			{
				if (!_timings.TryGetValue(context.Label, out MutableTiming value))
				{
					value = new MutableTiming();
					_timings[context.Label] = value;
				}
				value.Count++;
				value.ElapsedMilliseconds += elapsedMilliseconds;
			}

			internal PhaseTiming[] Snapshot()
			{
				return _timings.Select<KeyValuePair<string, MutableTiming>, PhaseTiming>((KeyValuePair<string, MutableTiming> pair) => new PhaseTiming(pair.Key, pair.Value.Count, pair.Value.ElapsedMilliseconds)).ToArray();
			}

			internal Dictionary<string, double> SnapshotSingleExecutionTimes()
			{
				return _timings.Where<KeyValuePair<string, MutableTiming>>((KeyValuePair<string, MutableTiming> pair) => pair.Value.Count == 1).ToDictionary<KeyValuePair<string, MutableTiming>, string, double>((KeyValuePair<string, MutableTiming> pair) => pair.Key, (KeyValuePair<string, MutableTiming> pair) => pair.Value.ElapsedMilliseconds, StringComparer.Ordinal);
			}
		}

		private sealed class MutableTiming
		{
			internal int Count { get; set; }

			internal double ElapsedMilliseconds { get; set; }
		}

		private readonly struct PhaseTiming
		{
			internal string Label { get; }

			internal int Count { get; }

			internal double ElapsedMilliseconds { get; }

			internal PhaseTiming(string label, int count, double elapsedMilliseconds)
			{
				Label = label;
				Count = count;
				ElapsedMilliseconds = elapsedMilliseconds;
			}
		}

		private static readonly object Lock = new object();

		private static readonly List<PhaseContext> ActivePhases = new List<PhaseContext>();

		private static readonly Dictionary<ProfileSession, SessionAggregate> Sessions = new Dictionary<ProfileSession, SessionAggregate>
		{
			[ProfileSession.Startup] = new SessionAggregate(),
			[ProfileSession.Connection] = new SessionAggregate()
		};

		internal static void BeginTarget(MethodBase target, string label)
		{
			if (!LoadTimeProfilerPatcher.ProfilingEnabled)
			{
				return;
			}
			ProfileSessionMask activeSessionMask = TimelineProfiler.GetActiveSessionMask();
			if (activeSessionMask == ProfileSessionMask.None)
			{
				return;
			}
			lock (Lock)
			{
				ActivePhases.Add(new PhaseContext(target, label, activeSessionMask, Stopwatch.GetTimestamp()));
			}
		}

		internal static void EndTarget(MethodBase target)
		{
			if (!LoadTimeProfilerPatcher.ProfilingEnabled)
			{
				return;
			}
			long timestamp = Stopwatch.GetTimestamp();
			lock (Lock)
			{
				for (int num = ActivePhases.Count - 1; num >= 0; num--)
				{
					PhaseContext phaseContext = ActivePhases[num];
					if (!(phaseContext.Target != target))
					{
						ActivePhases.RemoveAt(num);
						double elapsedMilliseconds = TicksToMilliseconds(timestamp - phaseContext.StartTimestamp);
						if ((phaseContext.Sessions & ProfileSessionMask.Startup) != ProfileSessionMask.None)
						{
							Sessions[ProfileSession.Startup].Add(phaseContext, elapsedMilliseconds);
						}
						if ((phaseContext.Sessions & ProfileSessionMask.Connection) != ProfileSessionMask.None)
						{
							Sessions[ProfileSession.Connection].Add(phaseContext, elapsedMilliseconds);
						}
						break;
					}
				}
			}
		}

		internal static void ResetSession(ProfileSession session)
		{
			lock (Lock)
			{
				Sessions[session].Clear();
			}
		}

		internal static Dictionary<string, double> SnapshotSingleExecutionTimes(ProfileSession session)
		{
			lock (Lock)
			{
				return Sessions[session].SnapshotSingleExecutionTimes();
			}
		}

		internal static void AppendSessionReport(StringBuilder builder, ProfileSession session)
		{
			PhaseTiming[] array;
			lock (Lock)
			{
				array = Sessions[session].Snapshot();
			}
			builder.AppendLine("Measured lifecycle execution times (prefix -> finalizer, inclusive):");
			if (array.Length == 0)
			{
				builder.AppendLine("  No profiled lifecycle phase completed.");
				return;
			}
			foreach (PhaseTiming item in array.OrderByDescending((PhaseTiming value) => value.ElapsedMilliseconds))
			{
				builder.Append("  ").Append(TimelineProfiler.FormatSeconds(item.ElapsedMilliseconds));
				if (item.Count > 1)
				{
					builder.Append(" x").Append(item.Count);
				}
				builder.Append(": ").AppendLine(item.Label);
			}
		}

		private static double TicksToMilliseconds(long ticks)
		{
			return (double)ticks * 1000.0 / (double)Stopwatch.Frequency;
		}
	}
	internal static class LifecyclePatches
	{
		private sealed class LifecycleTarget
		{
			internal MethodBase Method { get; }

			internal string Label { get; }

			internal bool StartupMilestone { get; }

			internal bool ConnectionMilestone { get; }

			internal bool BeginsConnection { get; }

			internal bool RestartsConnection { get; }

			internal bool PreparesDeepLobbyAttribution { get; }

			internal bool CompletesStartup { get; }

			internal bool DedicatedStartupCompletion { get; }

			internal bool CompletesConnection { get; }

			internal bool AbortsConnection { get; }

			internal LifecycleTarget(MethodBase method, string label, bool startupMilestone, bool connectionMilestone, bool beginsConnection, bool restartsConnection, bool preparesDeepLobbyAttribution, bool completesStartup, bool dedicatedStartupCompletion, bool completesConnection, bool abortsConnection)
			{
				Method = method;
				Label = label;
				StartupMilestone = startupMilestone;
				ConnectionMilestone = connectionMilestone;
				BeginsConnection = beginsConnection;
				RestartsConnection = restartsConnection;
				PreparesDeepLobbyAttribution = preparesDeepLobbyAttribution;
				CompletesStartup = completesStartup;
				DedicatedStartupCompletion = dedicatedStartupCompletion;
				CompletesConnection = completesConnection;
				AbortsConnection = abortsConnection;
			}
		}

		private static readonly List<LifecycleTarget> Targets = BuildTargets();

		private static readonly Dictionary<MethodBase, LifecycleTarget> TargetsByMethod = BuildTargetLookup();

		internal static IEnumerable<MethodBase> GetTargets()
		{
			foreach (LifecycleTarget target in Targets)
			{
				if (!target.DedicatedStartupCompletion || LoadTimeProfilerPatcher.IsDedicatedServer)
				{
					yield return target.Method;
				}
			}
		}

		internal static void Enter(MethodBase method)
		{
			if (LoadTimeProfilerPatcher.ProfilingEnabled && TargetsByMethod.TryGetValue(method, out LifecycleTarget value))
			{
				bool isDedicatedServer = LoadTimeProfilerPatcher.IsDedicatedServer;
				if (!isDedicatedServer && value.BeginsConnection)
				{
					TimelineProfiler.BeginConnection(value.Label, value.RestartsConnection);
				}
				if (value.StartupMilestone || (isDedicatedServer && (value.ConnectionMilestone || value.DedicatedStartupCompletion)))
				{
					TimelineProfiler.MarkStartup(value.Label);
				}
				if (!isDedicatedServer && value.ConnectionMilestone)
				{
					TimelineProfiler.MarkConnection(value.Label);
				}
				if (!isDedicatedServer && value.PreparesDeepLobbyAttribution)
				{
					DeepLobbyAttributionProfiler.PrepareForActiveSession();
				}
				LifecyclePhaseProfiler.BeginTarget(method, value.Label);
				DeepLobbyAttributionProfiler.BeginTarget(method);
			}
		}

		internal static void Exit(MethodBase method, Exception? exception)
		{
			//IL_00cf: 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)
			if (!LoadTimeProfilerPatcher.ProfilingEnabled || !TargetsByMethod.TryGetValue(method, out LifecycleTarget value))
			{
				return;
			}
			DeepLobbyAttributionProfiler.EndTarget(method);
			LifecyclePhaseProfiler.EndTarget(method);
			bool isDedicatedServer = LoadTimeProfilerPatcher.IsDedicatedServer;
			if ((!isDedicatedServer && value.CompletesStartup) || (isDedicatedServer && value.DedicatedStartupCompletion))
			{
				if (exception == null)
				{
					TimelineProfiler.CompleteStartup(value.Label + " complete");
				}
				else
				{
					TimelineProfiler.AbortStartup(value.Label + " failed: " + exception.GetType().Name);
				}
			}
			if (!isDedicatedServer && value.CompletesConnection)
			{
				if (exception == null)
				{
					TimelineProfiler.CompleteConnection(value.Label + " local player ready");
				}
				else
				{
					TimelineProfiler.AbortConnection(value.Label + " failed: " + exception.GetType().Name);
				}
			}
			if (!isDedicatedServer && value.AbortsConnection)
			{
				string text;
				try
				{
					text = ((object)ZNet.GetConnectionStatus()/*cast due to .constrained prefix*/).ToString();
				}
				catch
				{
					text = "unknown";
				}
				TimelineProfiler.AbortConnection(value.Label + " status=" + text);
			}
		}

		private static List<LifecycleTarget> BuildTargets()
		{
			List<LifecycleTarget> list = new List<LifecycleTarget>();
			Add(list, typeof(FejdStartup), "Awake", "FejdStartup.Awake", startup: true);
			Add(list, typeof(FejdStartup), "SetupGui", "FejdStartup.SetupGui", startup: true);
			Add(list, typeof(FejdStartup), "SetupObjectDB", "FejdStartup.SetupObjectDB", startup: true);
			Add(list, typeof(FejdStartup), "Start", "FejdStartup.Start", startup: true, connection: false, beginsConnection: false, restartsConnection: false, preparesDeepLobbyAttribution: false, completesStartup: true);
			Add(list, typeof(FejdStartup), "JoinServer", "FejdStartup.JoinServer", startup: false, connection: true, beginsConnection: true, restartsConnection: true);
			Add(list, typeof(FejdStartup), "OnWorldStart", "FejdStartup.OnWorldStart", startup: false, connection: true, beginsConnection: true, restartsConnection: false, preparesDeepLobbyAttribution: true);
			Add(list, typeof(FejdStartup), "TransitionToMainScene", "FejdStartup.TransitionToMainScene", startup: false, connection: true);
			Add(list, typeof(FejdStartup), "LoadMainScene", "FejdStartup.LoadMainScene", startup: false, connection: true);
			Add(list, typeof(FejdStartup), "ShowConnectError", "FejdStartup.ShowConnectError", startup: false, connection: true, beginsConnection: false, restartsConnection: false, preparesDeepLobbyAttribution: false, completesStartup: false, dedicatedStartupCompletion: false, completesConnection: false, abortsConnection: true);
			Add(list, typeof(Game), "Awake", "Game.Awake", startup: false, connection: true);
			Add(list, typeof(ZoneSystem), "Awake", "ZoneSystem.Awake", startup: false, connection: true);
			Add(list, typeof(ZNet), "Awake", "ZNet.Awake", startup: false, connection: true);
			Add(list, typeof(ZNetScene), "Awake", "ZNetScene.Awake", startup: false, connection: true);
			Add(list, typeof(ObjectDB), "Awake", "ObjectDB.Awake", startup: false, connection: true);
			Add(list, typeof(Game), "Start", "Game.Start", startup: false, connection: true);
			Add(list, typeof(ZoneSystem), "Start", "ZoneSystem.Start", startup: false, connection: true);
			Add(list, typeof(DungeonDB), "Start", "DungeonDB.Start", startup: false, connection: true);
			Add(list, typeof(ZNet), "Start", "ZNet.Start", startup: false, connection: true);
			Add(list, typeof(ZNet), "OnGenerationFinished", "ZNet.OnGenerationFinished", startup: false, connection: false, beginsConnection: false, restartsConnection: false, preparesDeepLobbyAttribution: false, completesStartup: false, dedicatedStartupCompletion: true);
			Add(list, typeof(ZNet), "ClientConnect", "ZNet.ClientConnect", startup: false, connection: true);
			Add(list, typeof(ZNet), "OnNewConnection", "ZNet.OnNewConnection", startup: false, connection: true);
			Add(list, typeof(ZNet), "RPC_PeerInfo", "ZNet.RPC_PeerInfo", startup: false, connection: true);
			Add(list, typeof(Game), "RequestRespawn", "Game.RequestRespawn", startup: false, connection: true);
			Add(list, typeof(Game), "SpawnPlayer", "Game.SpawnPlayer", startup: false, connection: true, beginsConnection: false, restartsConnection: false, preparesDeepLobbyAttribution: false, completesStartup: false, dedicatedStartupCompletion: false, completesConnection: true);
			return list;
		}

		private static Dictionary<MethodBase, LifecycleTarget> BuildTargetLookup()
		{
			Dictionary<MethodBase, LifecycleTarget> dictionary = new Dictionary<MethodBase, LifecycleTarget>();
			foreach (LifecycleTarget target in Targets)
			{
				dictionary[target.Method] = target;
			}
			return dictionary;
		}

		private static void Add(List<LifecycleTarget> targets, Type type, string methodName, string label, bool startup = false, bool connection = false, bool beginsConnection = false, bool restartsConnection = false, bool preparesDeepLobbyAttribution = false, bool completesStartup = false, bool dedicatedStartupCompletion = false, bool completesConnection = false, bool abortsConnection = false)
		{
			MethodBase methodBase = AccessTools.Method(type, methodName, (Type[])null, (Type[])null);
			if (methodBase == null)
			{
				ProfilerLog.WriteLine("Target warning: could not find " + type.FullName + "." + methodName + ".");
			}
			else
			{
				targets.Add(new LifecycleTarget(methodBase, label, startup, connection, beginsConnection, restartsConnection, preparesDeepLobbyAttribution, completesStartup, dedicatedStartupCompletion, completesConnection, abortsConnection));
			}
		}
	}
	internal static class LoadTimeProfilerLifecyclePatch
	{
		internal static void Prefix(MethodBase __originalMethod)
		{
			LifecyclePatches.Enter(__originalMethod);
		}

		internal static Exception? Finalizer(MethodBase __originalMethod, Exception? __exception)
		{
			LifecyclePatches.Exit(__originalMethod, __exception);
			return __exception;
		}
	}
	internal static class DeepLobbyAttributionProfiler
	{
		private sealed class PluginResolver
		{
			private readonly Dictionary<Assembly, List<PluginIdentity>> _byAssembly;

			private readonly Dictionary<string, PluginIdentity> _byGuid;

			private PluginResolver(Dictionary<Assembly, List<PluginIdentity>> byAssembly, Dictionary<string, PluginIdentity> byGuid)
			{
				_byAssembly = byAssembly;
				_byGuid = byGuid;
			}

			internal static PluginResolver Create()
			{
				Dictionary<Assembly, List<PluginIdentity>> dictionary = new Dictionary<Assembly, List<PluginIdentity>>();
				Dictionary<string, PluginIdentity> dictionary2 = new Dictionary<string, PluginIdentity>(StringComparer.Ordinal);
				foreach (PluginInfo value2 in Chainloader.PluginInfos.Values)
				{
					try
					{
						string gUID = value2.Metadata.GUID;
						string name = value2.Metadata.Name;
						PluginIdentity item = (dictionary2[gUID] = new PluginIdentity(gUID, name));
						BaseUnityPlugin instance = value2.Instance;
						if (!((Object)(object)instance == (Object)null))
						{
							Assembly assembly = ((object)instance).GetType().Assembly;
							if (!dictionary.TryGetValue(assembly, out var value))
							{
								value = (dictionary[assembly] = new List<PluginIdentity>());
							}
							value.Add(item);
						}
					}
					catch
					{
					}
				}
				return new PluginResolver(dictionary, dictionary2);
			}

			internal PluginIdentity Resolve(MethodBase method, string? owner)
			{
				string normalizedOwner = owner ?? string.Empty;
				if (string.IsNullOrWhiteSpace(normalizedOwner))
				{
					normalizedOwner = string.Empty;
				}
				Assembly assembly = method.Module.Assembly;
				if (_byAssembly.TryGetValue(assembly, out List<PluginIdentity> value))
				{
					if (value.Count == 1)
					{
						return value[0];
					}
					PluginIdentity result = value.FirstOrDefault((PluginIdentity identity) => string.Equals(identity.Key, normalizedOwner, StringComparison.Ordinal));
					if (!string.IsNullOrEmpty(result.Key))
					{
						return result;
					}
					string displayName = assembly.GetName().Name ?? "<unknown assembly>";
					return new PluginIdentity("assembly:" + assembly.FullName, displayName);
				}
				if (!string.IsNullOrEmpty(normalizedOwner) && _byGuid.TryGetValue(normalizedOwner, out var value2))
				{
					return value2;
				}
				string text = assembly.GetName().Name ?? "<unknown assembly>";
				if (!string.IsNullOrEmpty(normalizedOwner))
				{
					return new PluginIdentity("owner:" + normalizedOwner + "|assembly:" + text, normalizedOwner);
				}
				return new PluginIdentity("assembly:" + assembly.FullName, text);
			}
		}

		private sealed class CallbackState
		{
			internal PluginIdentity Identity { get; }

			internal string PhaseLabel { get; }

			internal long StartTimestamp { get; }

			internal long ChildTicks { get; set; }

			internal CallbackState(PluginIdentity identity, string phaseLabel, long startTimestamp)
			{
				Identity = identity;
				PhaseLabel = phaseLabel;
				StartTimestamp = startTimestamp;
			}
		}

		private sealed class PhaseState
		{
			internal MethodBase Target { get; }

			internal string Label { get; }

			internal PhaseState(MethodBase target, string label)
			{
				Target = target;
				Label = label;
			}
		}

		private sealed class PluginAggregate
		{
			private readonly Dictionary<string, double> _phases = new Dictionary<string, double>(StringComparer.Ordinal);

			private PluginIdentity Identity { get; }

			internal PluginAggregate(PluginIdentity identity)
			{
				Identity = identity;
			}

			internal void Add(string phaseLabel, double elapsedMilliseconds)
			{
				_phases.TryGetValue(phaseLabel, out var value);
				_phases[phaseLabel] = value + elapsedMilliseconds;
			}

			internal PluginSnapshot Snapshot()
			{
				return new PluginSnapshot(Identity, _phases.ToDictionary<KeyValuePair<string, double>, string, double>((KeyValuePair<string, double> pair) => pair.Key, (KeyValuePair<string, double> pair) => pair.Value, StringComparer.Ordinal));
			}
		}

		private readonly struct PluginIdentity
		{
			internal string Key { get; }

			internal string DisplayName { get; }

			internal PluginIdentity(string key, string displayName)
			{
				Key = key;
				DisplayName = displayName;
			}
		}

		private readonly struct PluginSnapshot
		{
			private readonly IReadOnlyDictionary<string, double> _phases;

			internal PluginIdentity Identity { get; }

			internal double TotalMilliseconds { get; }

			internal PluginSnapshot(PluginIdentity identity, IReadOnlyDictionary<string, double> phases)
			{
				Identity = identity;
				_phases = phases;
				TotalMilliseconds = phases.Values.Sum();
			}

			internal double GetPhaseMilliseconds(string phaseLabel)
			{
				if (!_phases.TryGetValue(phaseLabel, out var value))
				{
					return 0.0;
				}
				return value;
			}
		}

		private readonly struct PreparationSummary
		{
			internal int Installed { get; }

			internal int AlreadyInstrumented { get; }

			internal int Skipped { get; }

			internal int Failed { get; }

			internal double ElapsedMilliseconds { get; }

			internal PreparationSummary(int installed, int alreadyInstrumented, int skipped, int failed, double elapsedMilliseconds)
			{
				Installed = installed;
				AlreadyInstrumented = alreadyInstrumented;
				Skipped = skipped;
				Failed = failed;
				ElapsedMilliseconds = elapsedMilliseconds;
			}
		}

		private const string ObjectDbLabel = "ObjectDB.Awake";

		private const string ZNetSceneLabel = "ZNetScene.Awake";

		private const int MaximumReportedPlugins = 60;

		private static readonly object Lock = new object();

		private static readonly Harmony InstrumentationHarmony = new Harmony("sighsorry.LoadTimeProfiler.deep-lobby-attribution");

		private static readonly Dictionary<MethodBase, string> TargetLabels = BuildTargetLabels();

		private static readonly HashSet<MethodBase> InstrumentedMethods = new HashSet<MethodBase>();

		private static readonly Dictionary<MethodBase, PluginIdentity> CallbackOwners = new Dictionary<MethodBase, PluginIdentity>();

		private static readonly Dictionary<string, PluginAggregate> PluginTimings = new Dictionary<string, PluginAggregate>(StringComparer.Ordinal);

		[ThreadStatic]
		private static Stack<PhaseState>? _activePhases;

		[ThreadStatic]
		private static Stack<CallbackState>? _activeCallbacks;

		private static PreparationSummary _lastPreparation;

		internal static void ResetSession()
		{
			lock (Lock)
			{
				PluginTimings.Clear();
				_lastPreparation = default(PreparationSummary);
			}
			_activePhases?.Clear();
			_activeCallbacks?.Clear();
		}

		internal static void PrepareForActiveSession()
		{
			//IL_016c: 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_018e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0193: Unknown result type (might be due to invalid IL or missing references)
			//IL_01a4: Expected O, but got Unknown
			//IL_01a4: Expected O, but got Unknown
			if (!LoadTimeProfilerPatcher.ProfilingEnabled)
			{
				return;
			}
			long timestamp = Stopwatch.GetTimestamp();
			HashSet<MethodBase> blockedMethods = (LoadTimeProfilerPatcher.IsDedicatedServer ? new HashSet<MethodBase>() : GetCurrentMethodBlocklist());
			PluginResolver pluginResolver = PluginResolver.Create();
			HashSet<MethodBase> hashSet = new HashSet<MethodBase>();
			int num = 0;
			int num2 = 0;
			int num3 = 0;
			int num4 = 0;
			foreach (MethodBase key in TargetLabels.Keys)
			{
				Patches patchInfo;
				try
				{
					patchInfo = Harmony.GetPatchInfo(key);
				}
				catch (Exception ex)
				{
					num4++;
					ProfilerLog.WriteLine("Deep attribution warning: could not inspect " + FormatMethodName(key) + ": " + ex.Message);
					continue;
				}
				if (patchInfo == null)
				{
					continue;
				}
				foreach (Patch item in EnumerateInvocationPatches(patchInfo))
				{
					MethodBase patchMethod = item.PatchMethod;
					if (patchMethod == null || !hashSet.Add(patchMethod))
					{
						continue;
					}
					if (!CanInstrument(patchMethod, blockedMethods))
					{
						num3++;
						continue;
					}
					PluginIdentity value = pluginResolver.Resolve(patchMethod, item.owner);
					lock (Lock)
					{
						if (InstrumentedMethods.Contains(patchMethod))
						{
							num2++;
							continue;
						}
						InstrumentedMethods.Add(patchMethod);
						CallbackOwners[patchMethod] = value;
					}
					try
					{
						InstrumentationHarmony.Patch(patchMethod, new HarmonyMethod(typeof(DeepLobbyAttributionProfiler), "ProfiledCallbackPrefix", (Type[])null)
						{
							priority = int.MaxValue
						}, (HarmonyMethod)null, (HarmonyMethod)null, new HarmonyMethod(typeof(DeepLobbyAttributionProfiler), "ProfiledCallbackFinalizer", (Type[])null)
						{
							priority = int.MinValue
						}, (HarmonyMethod)null);
						num++;
					}
					catch (Exception ex2)
					{
						num4++;
						lock (Lock)
						{
							InstrumentedMethods.Remove(patchMethod);
							CallbackOwners.Remove(patchMethod);
						}
						ProfilerLog.WriteLine("Deep attribution warning: could not instrument " + FormatMethodName(patchMethod) + ": " + ex2.Message);
					}
				}
			}
			double num5 = TicksToMilliseconds(Stopwatch.GetTimestamp() - timestamp);
			lock (Lock)
			{
				_lastPreparation = new PreparationSummary(num, num2, num3, num4, num5);
			}
			string arg = (LoadTimeProfilerPatcher.IsDedicatedServer ? "server startup" : "deep lobby");
			ProfilerLog.WriteLine($"Scoped {arg} attribution prepared: installed={num}, existing={num2}, " + $"skipped={num3}, failed={num4}, setup={TimelineProfiler.FormatSeconds(num5)}.");
		}

		internal static void BeginTarget(MethodBase target)
		{
			ProfileSession session = ((!LoadTimeProfilerPatcher.IsDedicatedServer) ? ProfileSession.Connection : ProfileSession.Startup);
			if (LoadTimeProfilerPatcher.ProfilingEnabled && TargetLabels.TryGetValue(target, out string value) && TimelineProfiler.IsActive(session))
			{
				(_activePhases ?? (_activePhases = new Stack<PhaseState>())).Push(new PhaseState(target, value));
			}
		}

		internal static void EndTarget(MethodBase target)
		{
			if (_activePhases != null && _activePhases.Count != 0)
			{
				if (_activePhases.Peek().Target != target)
				{
					_activePhases.Clear();
					_activeCallbacks?.Clear();
				}
				else
				{
					_activePhases.Pop();
					_activeCallbacks?.Clear();
				}
			}
		}

		internal static void AppendReport(StringBuilder builder)
		{
			builder.AppendLine(LoadTimeProfilerPatcher.IsDedicatedServer ? "Scoped server startup attribution:" : "Scoped deep lobby attribution:");
			PluginSnapshot[] source;
			PreparationSummary lastPreparation;
			lock (Lock)
			{
				source = PluginTimings.Values.Select((PluginAggregate value) => value.Snapshot()).ToArray();
				lastPreparation = _lastPreparation;
			}
			builder.AppendLine("  Exclusive synchronous Harmony callback time in ObjectDB.Awake and ZNetScene.Awake.");
			builder.AppendLine("  Breakdown order: ObjectDB + ZNetScene.");
			builder.Append("  Prepared callbacks: installed=").Append(lastPreparation.Installed).Append(", existing=")
				.Append(lastPreparation.AlreadyInstrumented)
				.Append(", skipped=")
				.Append(lastPreparation.Skipped)
				.Append(", failed=")
				.Append(lastPreparation.Failed)
				.Append(", setup=")
				.AppendLine(TimelineProfiler.FormatSeconds(lastPreparation.ElapsedMilliseconds));
			PluginSnapshot[] array = (from value in source
				where value.TotalMilliseconds > 0.0
				orderby value.TotalMilliseconds descending
				select value).ThenBy<PluginSnapshot, string>((PluginSnapshot value) => value.Identity.DisplayName, StringComparer.Ordinal).ToArray();
			if (array.Length == 0)
			{
				builder.AppendLine("  No instrumented callbacks were invoked in the two scoped phases.");
				return;
			}
			int num = Math.Min(array.Length, 60);
			for (int num2 = 0; num2 < num; num2++)
			{
				PluginSnapshot pluginSnapshot = array[num2];
				double phaseMilliseconds = pluginSnapshot.GetPhaseMilliseconds("ObjectDB.Awake");
				double phaseMilliseconds2 = pluginSnapshot.GetPhaseMilliseconds("ZNetScene.Awake");
				builder.Append("  ").Append(TimelineProfiler.FormatSeconds(pluginSnapshot.TotalMilliseconds)).Append(" (")
					.Append(TimelineProfiler.FormatSeconds(phaseMilliseconds))
					.Append(" + ")
					.Append(TimelineProfiler.FormatSeconds(phaseMilliseconds2))
					.Append("): ")
					.AppendLine(pluginSnapshot.Identity.DisplayName);
			}
			if (array.Length > num)
			{
				builder.Append("  ... ").Append(array.Length - num).AppendLine(" more plugins omitted.");
			}
		}

		private static void ProfiledCallbackPrefix(MethodBase __originalMethod, out CallbackState? __state)
		{
			__state = null;
			if (_activePhases == null || _activePhases.Count == 0)
			{
				return;
			}
			PluginIdentity value;
			lock (Lock)
			{
				if (!CallbackOwners.TryGetValue(__originalMethod, out value))
				{
					return;
				}
			}
			PhaseState phaseState = _activePhases.Peek();
			CallbackState callbackState = new CallbackState(value, phaseState.Label, Stopwatch.GetTimestamp());
			(_activeCallbacks ?? (_activeCallbacks = new Stack<CallbackState>())).Push(callbackState);
			__state = callbackState;
		}

		private static Exception? ProfiledCallbackFinalizer(CallbackState? __state, Exception? __exception)
		{
			if (__state == null)
			{
				return __exception;
			}
			long num = Math.Max(0L, Stopwatch.GetTimestamp() - __state.StartTimestamp);
			if (TryRemoveActiveCallback(__state) && _activeCallbacks != null && _activeCallbacks.Count > 0)
			{
				_activeCallbacks.Peek().ChildTicks += num;
			}
			double elapsedMilliseconds = TicksToMilliseconds(Math.Max(0L, num - __state.ChildTicks));
			lock (Lock)
			{
				if (!PluginTimings.TryGetValue(__state.Identity.Key, out PluginAggregate value))
				{
					value = new PluginAggregate(__state.Identity);
					PluginTimings[__state.Identity.Key] = value;
				}
				value.Add(__state.PhaseLabel, elapsedMilliseconds);
				return __exception;
			}
		}

		private static bool TryRemoveActiveCallback(CallbackState state)
		{
			if (_activeCallbacks == null || _activeCallbacks.Count == 0)
			{
				return false;
			}
			if (_activeCallbacks.Peek() == state)
			{
				_activeCallbacks.Pop();
				return true;
			}
			CallbackState[] array = _activeCallbacks.ToArray();
			int num = Array.FindIndex(array, (CallbackState candidate) => candidate == state);
			if (num < 0)
			{
				return false;
			}
			_activeCallbacks.Clear();
			for (int num2 = array.Length - 1; num2 > num; num2--)
			{
				_activeCallbacks.Push(array[num2]);
			}
			return true;
		}

		private static Dictionary<MethodBase, string> BuildTargetLabels()
		{
			Dictionary<MethodBase, string> dictionary = new Dictionary<MethodBase, string>();
			AddTarget(dictionary, typeof(ObjectDB), "Awake", "ObjectDB.Awake");
			AddTarget(dictionary, typeof(ZNetScene), "Awake", "ZNetScene.Awake");
			return dictionary;
		}

		private static void AddTarget(Dictionary<MethodBase, string> targets, Type type, string methodName, string label)
		{
			MethodBase methodBase = AccessTools.Method(type, methodName, (Type[])null, (Type[])null);
			if (methodBase != null)
			{
				targets[methodBase] = label;
			}
		}

		private static HashSet<MethodBase> GetCurrentMethodBlocklist()
		{
			HashSet<MethodBase> hashSet = new HashSet<MethodBase>();
			MethodBase methodBase = AccessTools.Method(typeof(FejdStartup), "OnWorldStart", (Type[])null, (Type[])null);
			if (methodBase == null)
			{
				return hashSet;
			}
			hashSet.Add(methodBase);
			try
			{
				Patches patchInfo = Harmony.GetPatchInfo(methodBase);
				if (patchInfo != null)
				{
					foreach (Patch item in EnumerateInvocationPatches(patchInfo))
					{
						if (item.PatchMethod != null)
						{
							hashSet.Add(item.PatchMethod);
						}
					}
				}
			}
			catch
			{
			}
			return hashSet;
		}

		private static IEnumerable<Patch> EnumerateInvocationPatches(Patches patches)
		{
			foreach (Patch prefix in patches.Prefixes)
			{
				yield return prefix;
			}
			foreach (Patch postfix in patches.Postfixes)
			{
				yield return postfix;
			}
			foreach (Patch finalizer in patches.Finalizers)
			{
				yield return finalizer;
			}
		}

		private static bool CanInstrument(MethodBase method, ISet<MethodBase> blockedMethods)
		{
			if (blockedMethods.Contains(method) || !(method is MethodInfo methodInfo) || method is DynamicMethod || method.DeclaringType == null || method.DeclaringType.ContainsGenericParameters || method.ContainsGenericParameters || method.IsAbstract)
			{
				return false;
			}
			Assembly assembly = method.Module.Assembly;
			if (assembly.IsDynamic || assembly == typeof(DeepLobbyAttributionProfiler).Assembly)
			{
				return false;
			}
			string text = assembly.GetName().Name ?? string.Empty;
			if (text.Equals("0Harmony", StringComparison.OrdinalIgnoreCase) || text.StartsWith("BepInEx", StringComparison.OrdinalIgnoreCase) || text.StartsWith("MonoMod", StringComparison.OrdinalIgnoreCase) || text.StartsWith("Mono.Cecil", StringComparison.OrdinalIgnoreCase))
			{
				return false;
			}
			string text2 = method.DeclaringType.FullName ?? string.Empty;
			if (text2.StartsWith("HarmonyLib.", StringComparison.Ordinal) || text2.StartsWith("BepInEx.", StringComparison.Ordinal) || text2.StartsWith("MonoMod.", StringComparison.Ordinal) || text2.StartsWith("Mono.Cecil.", StringComparison.Ordinal) || methodInfo.Name.StartsWith("DMD<", StringComparison.Ordinal))
			{
				return false;
			}
			try
			{
				return methodInfo.GetMethodBody() != null;
			}
			catch
			{
				return false;
			}
		}

		private static string FormatMethodName(MethodBase method)
		{
			return (method.DeclaringType?.FullName ?? "<dynamic>") + "." + method.Name;
		}

		private static double TicksToMilliseconds(long ticks)
		{
			return (double)ticks * 1000.0 / (double)Stopwatch.Frequency;
		}
	}
}