diff --git a/services/DebugService.cs b/services/DebugService.cs index 6855e4ec..5d42d5e3 100644 --- a/services/DebugService.cs +++ b/services/DebugService.cs @@ -1,45 +1,121 @@ +using System.Diagnostics; using System.Reflection; using HarmonyLib; using NeoModLoader.constants; namespace NeoModLoader.services; +[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class)] +public class DebugAttribute : Attribute{} public static class DebugService { - static void Debugger(MethodBase __originalMethod) + static bool IsDebuggable(MethodBase method) { - LogService.LogInfo(__originalMethod.ToString()); + if (method.IsAbstract || method.ContainsGenericParameters || method.IsSpecialName || method.Name.Contains("<")) + { + return false; + } + return true; } - static readonly Harmony Patcher = new Harmony(Others.harmony_id); - private static readonly HarmonyMethod Hook = new(AccessTools.Method(typeof(DebugService), nameof(Debugger))); - public static void AttachDebugger(Assembly assembly) + public abstract class Debugger { - foreach (var Type in assembly.GetTypes()) + protected HarmonyMethod Prefix; + protected HarmonyMethod Postfix; + protected HarmonyMethod Finalizer; + public void Attach(Assembly assembly, Func predicate = null) { + foreach (var Type in assembly.GetTypes()) + { + Attach(Type, predicate); + } + } + public void Attach(Type Type, Func predicate = null) + { + predicate ??= Default; foreach (var method in Type.GetMethods( BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static | - BindingFlags.DeclaredOnly)) + BindingFlags.DeclaredOnly).Where(m => IsDebuggable(m) && predicate(m))) { - Patcher.Patch(method, Hook); + Attach(method); } - } - } - public static void RemoveDebugger(Assembly assembly) - { - foreach (var Type in assembly.GetTypes()) - { - foreach (var method in Type.GetMethods( + foreach (var method in Type.GetConstructors( BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static | - BindingFlags.DeclaredOnly)) + BindingFlags.DeclaredOnly).Where(m => IsDebuggable(m) && predicate(m))) + { + Attach(method); + } + } + public void Attach(MethodBase method) + { + try { - Patcher.Unpatch(method, HarmonyPatchType.Prefix); + Patcher.Patch(method, Prefix, Postfix, null, Finalizer, null); + } + catch (Exception e) + { + LogService.LogError($"Failed to attach debugger to {method.FullDescription()} due to {e}"); } } } + public class LogDebugger : Debugger + { + static void prefix(MethodBase __originalMethod) + { + LogService.Log(__originalMethod.ToString()); + } + public LogDebugger() + { + Prefix = new HarmonyMethod(AccessTools.Method(typeof(LogDebugger), nameof(prefix))); + } + } + public class ProfilerDebugger : Debugger + { + static Stopwatch stopwatch = new(); + public static void prefix(out long __state) + { + __state = Stopwatch.GetTimestamp(); + } + public static void postfix( + MethodBase __originalMethod, + long __state) + { + LogService.Log( + $"{__originalMethod.Name} took {Stopwatch.GetTimestamp() - __state}"); + } + public ProfilerDebugger() + { + Prefix = new HarmonyMethod(AccessTools.Method(typeof(ProfilerDebugger), nameof(prefix))); + Postfix = new HarmonyMethod(AccessTools.Method(typeof(ProfilerDebugger), nameof(postfix))); + } + } + public class ExceptionDebugger : Debugger + { + static void finalizer(Exception __exception, MethodBase __original) + { + if (__exception is not null) + handler?.Invoke(__exception, __original); + } + public ExceptionDebugger() + { + Finalizer = new HarmonyMethod(AccessTools.Method(typeof(ExceptionDebugger), nameof(finalizer))); + } + public static void AddHandler(ExceptionHandler Handler) + { + handler += Handler; + } + public delegate void ExceptionHandler(Exception Exception, MethodBase Method); + public static event ExceptionHandler handler; + } + static readonly Harmony Patcher = new Harmony(Others.harmony_id); + public static readonly LogDebugger Logger = new(); + public static readonly ProfilerDebugger Profiler = new(); + public static readonly ExceptionDebugger ExceptionHandler = new(); + static readonly Func Default = _ => true; + static readonly Func Attribute = method => method.IsDefined(typeof(DebugAttribute), true) || method.DeclaringType?.IsDefined(typeof(DebugAttribute), true) == true; } \ No newline at end of file diff --git a/services/LogService.cs b/services/LogService.cs index e6e3b485..38679b7c 100644 --- a/services/LogService.cs +++ b/services/LogService.cs @@ -197,6 +197,21 @@ public static void LogInfo(string message) } } /// + /// Log message without [NML] prefix + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void Log(string message) + { + if (Others.unity_player_enabled) + { + UnityEngine.Debug.Log(message); + } + else + { + System.Console.WriteLine(message); + } + } + /// /// Log StackTrace from where call this method with [NML] prefix as Info /// [MethodImpl(MethodImplOptions.AggressiveInlining)]