Please disclose if any significant portion of your mod was created using AI tools by adding the 'AI Generated' category. Failing to do so may result in the mod being removed from Thunderstore.
Decompiled source of LoadTimeProfiler v1.0.0
patchers\LoadTimeProfiler.dll
Decompiled a day agousing 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; } } }