diff --git a/BepInEx.Preloader/BepInEx.Preloader.csproj b/BepInEx.Preloader/BepInEx.Preloader.csproj index b06b42f..61a623a 100644 --- a/BepInEx.Preloader/BepInEx.Preloader.csproj +++ b/BepInEx.Preloader/BepInEx.Preloader.csproj @@ -42,6 +42,7 @@ + @@ -59,5 +60,8 @@ Harmony + + + \ No newline at end of file diff --git a/BepInEx.Preloader/Preloader.cs b/BepInEx.Preloader/Preloader.cs index c8a519b..0ac94ef 100644 --- a/BepInEx.Preloader/Preloader.cs +++ b/BepInEx.Preloader/Preloader.cs @@ -22,7 +22,7 @@ namespace BepInEx.Preloader /// /// The log writer that is specific to the preloader. /// - private static PreloaderLogWriter PreloaderLog { get; set; } + private static PreloaderConsoleListener PreloaderLog { get; set; } public static void Run() { @@ -31,19 +31,20 @@ namespace BepInEx.Preloader AllocateConsole(); UnityPatches.Apply(); + + Logger.Sources.Add(TraceLogSource.CreateSource()); - PreloaderLog = - new PreloaderLogWriter( + PreloaderLog = new PreloaderConsoleListener( Utility.SafeParseBool(Config.GetEntry("preloader-logconsole", "false", "BepInEx"))); - PreloaderLog.Enabled = true; + + Logger.Listeners.Add(PreloaderLog); + string consoleTile = $"BepInEx {typeof(Paths).Assembly.GetName().Version} - {Process.GetCurrentProcess().ProcessName}"; ConsoleWindow.Title = consoleTile; - Logger.SetLogger(PreloaderLog); - - PreloaderLog.WriteLine(consoleTile); + Logger.LogMessage(consoleTile); //See BuildInfoAttribute for more information about this section. object[] attributes = typeof(BuildInfoAttribute).Assembly.GetCustomAttributes(typeof(BuildInfoAttribute), false); @@ -52,10 +53,10 @@ namespace BepInEx.Preloader { var attribute = (BuildInfoAttribute)attributes[0]; - PreloaderLog.WriteLine(attribute.Info); + Logger.LogMessage(attribute.Info); } - Logger.Log(LogLevel.Message, "Preloader started"); + Logger.LogMessage("Preloader started"); string entrypointAssembly = Config.GetEntry("entrypoint-assembly", "UnityEngine.dll", "Preloader"); @@ -66,22 +67,33 @@ namespace BepInEx.Preloader AssemblyPatcher.PatchAndLoad(Paths.ManagedPath); AssemblyPatcher.DisposePatchers(); + + Logger.LogMessage("Preloader finished"); + + UnityLogListener.WriteStringToUnityLog?.Invoke(PreloaderLog.ToString()); + + Logger.Listeners.Remove(PreloaderLog); + Logger.Listeners.Add(new ConsoleLogListener()); + + PreloaderLog.Dispose(); } catch (Exception ex) { - Logger.Log(LogLevel.Fatal, "Could not run preloader!"); - Logger.Log(LogLevel.Fatal, ex); - - PreloaderLog.Enabled = false; - try { + Logger.Log(LogLevel.Fatal, "Could not run preloader!"); + Logger.Log(LogLevel.Fatal, ex); + + PreloaderLog?.Dispose(); + if (!ConsoleWindow.IsAttatched) { //if we've already attached the console, then the log will already be written to the console AllocateConsole(); Console.Write(PreloaderLog); } + + PreloaderLog = null; } finally { @@ -89,7 +101,7 @@ namespace BepInEx.Preloader Path.Combine(Paths.GameRootPath, $"preloader_{DateTime.Now:yyyyMMdd_HHmmss_fff}.log"), PreloaderLog + "\r\n" + ex); - PreloaderLog.Dispose(); + PreloaderLog?.Dispose(); PreloaderLog = null; } } @@ -191,8 +203,7 @@ namespace BepInEx.Preloader public static void PatchEntrypoint(ref AssemblyDefinition assembly) { if (assembly.MainModule.AssemblyReferences.Any(x => x.Name.Contains("BepInEx"))) - throw new Exception( - "BepInEx has been detected to be patched! Please unpatch before using a patchless variant!"); + throw new Exception("BepInEx has been detected to be patched! Please unpatch before using a patchless variant!"); string entrypointType = Config.GetEntry("entrypoint-type", "Application", "Preloader"); string entrypointMethod = Config.GetEntry("entrypoint-method", ".cctor", "Preloader"); diff --git a/BepInEx.Preloader/PreloaderLogWriter.cs b/BepInEx.Preloader/PreloaderLogWriter.cs new file mode 100644 index 0000000..de8ac98 --- /dev/null +++ b/BepInEx.Preloader/PreloaderLogWriter.cs @@ -0,0 +1,91 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Text; +using BepInEx.ConsoleUtil; + +namespace BepInEx.Logging +{ + public class PreloaderConsoleListener : ILogListener + { + public List LogEvents { get; } = new List(); + protected StringBuilder LogBuilder = new StringBuilder(); + + public static TextWriter StandardOut { get; set; } + protected PreloaderConsoleSource LoggerSource { get; set; } + + + public PreloaderConsoleListener(bool redirectConsole) + { + StandardOut = Console.Out; + + if (redirectConsole) + { + LoggerSource = new PreloaderConsoleSource(); + + Logger.Sources.Add(LoggerSource); + Console.SetOut(LoggerSource); + } + } + + public void LogEvent(object sender, LogEventArgs eventArgs) + { + LogEvents.Add(eventArgs); + + string log = $"[{eventArgs.Level}:{((ILogSource)sender).SourceName}] {eventArgs.Data}\r\n"; + + LogBuilder.Append(log); + + Kon.ForegroundColor = eventArgs.Level.GetConsoleColor(); + ConsoleDirectWrite(log); + Kon.ForegroundColor = ConsoleColor.Gray; + } + + public void ConsoleDirectWrite(string value) + { + StandardOut.Write(value); + } + + public void ConsoleDirectWriteLine(string value) + { + StandardOut.WriteLine(value); + } + + public override string ToString() => LogBuilder.ToString(); + + public void Dispose() + { + + if (LoggerSource != null) + { + Console.SetOut(StandardOut); + Logger.Sources.Remove(LoggerSource); + LoggerSource.Dispose(); + LoggerSource = null; + } + } + } + + public class PreloaderConsoleSource : TextWriter, ILogSource + { + public override Encoding Encoding { get; } = Console.OutputEncoding; + + public string SourceName { get; } = "BepInEx Preloader"; + + public event EventHandler LogEvent; + + public override void Write(object value) + => LogEvent?.Invoke(this, new LogEventArgs(value, LogLevel.Info, this)); + + public override void Write(string value) + => Write((object)value); + + public override void WriteLine() { } + + public override void WriteLine(object value) + => Write(value); + + public override void WriteLine(string value) + => Write(value); + } +} \ No newline at end of file diff --git a/BepInEx/BepInEx.csproj b/BepInEx/BepInEx.csproj index d9c9821..3c8431d 100644 --- a/BepInEx/BepInEx.csproj +++ b/BepInEx/BepInEx.csproj @@ -57,12 +57,16 @@ - + - - - + + + + + + + diff --git a/BepInEx/Bootstrap/Chainloader.cs b/BepInEx/Bootstrap/Chainloader.cs index 063f704..0f8406e 100644 --- a/BepInEx/Bootstrap/Chainloader.cs +++ b/BepInEx/Bootstrap/Chainloader.cs @@ -8,7 +8,6 @@ using System.Text; using BepInEx.Logging; using UnityEngine; using UnityInjector.ConsoleUtil; -using UnityLogWriter = BepInEx.Logging.UnityLogWriter; namespace BepInEx.Bootstrap { @@ -25,7 +24,7 @@ namespace BepInEx.Bootstrap /// /// The GameObject that all plugins are attached to as components. /// - public static GameObject ManagerObject { get; private set; } = new GameObject("BepInEx_Manager"); + public static GameObject ManagerObject { get; private set; } private static bool _loaded = false; @@ -52,14 +51,23 @@ namespace BepInEx.Bootstrap ConsoleEncoding.ConsoleCodePage = (uint)Encoding.UTF8.CodePage; Console.OutputEncoding = Encoding.UTF8; + Logger.Listeners.Add(new ConsoleLogListener()); } - UnityLogWriter unityLogWriter = new UnityLogWriter(); + //Fix for standard output getting overwritten by UnityLogger + Console.SetOut(ConsoleWindow.StandardOut); - if (Logger.CurrentLogger != null && Logger.CurrentLogger is PreloaderLogWriter preloaderLogger) - unityLogWriter.WriteToLog($"{preloaderLogger}\r\n"); - Logger.SetLogger(unityLogWriter); + Logger.Listeners.Add(new UnityLogListener()); + + if (!TraceLogSource.IsListening) + Logger.Sources.Add(TraceLogSource.CreateSource()); + + if (bool.Parse(Config.GetEntry("chainloader-log-unity-messages", "false", "BepInEx"))) + Logger.Sources.Add(new UnityLogSource()); + + + Logger.Log(LogLevel.Message, "Chainloader ready"); _initialized = true; } @@ -83,10 +91,6 @@ namespace BepInEx.Bootstrap try { - if (bool.Parse(Config.GetEntry("chainloader-log-unity-messages", "false", "BepInEx"))) - UnityLogWriter.ListenUnityLogs(); - - var productNameProp = typeof(Application).GetProperty("productName", BindingFlags.Public | BindingFlags.Static); if (productNameProp != null) ConsoleWindow.Title = @@ -94,6 +98,8 @@ namespace BepInEx.Bootstrap Logger.Log(LogLevel.Message, "Chainloader started"); + ManagerObject = new GameObject("BepInEx_Manager"); + UnityEngine.Object.DontDestroyOnLoad(ManagerObject); diff --git a/BepInEx/ConsoleUtil/ConsoleEncoding/ConsoleEncoding.Buffers.cs b/BepInEx/ConsoleUtil/ConsoleEncoding/ConsoleEncoding.Buffers.cs index e1e977b..0504cd1 100644 --- a/BepInEx/ConsoleUtil/ConsoleEncoding/ConsoleEncoding.Buffers.cs +++ b/BepInEx/ConsoleUtil/ConsoleEncoding/ConsoleEncoding.Buffers.cs @@ -13,7 +13,7 @@ namespace UnityInjector.ConsoleUtil // using only safe (managed) code // -------------------------------------------------- - partial class ConsoleEncoding + internal partial class ConsoleEncoding { private byte[] _byteBuffer = new byte[256]; private char[] _charBuffer = new char[256]; diff --git a/BepInEx/ConsoleUtil/ConsoleEncoding/ConsoleEncoding.PInvoke.cs b/BepInEx/ConsoleUtil/ConsoleEncoding/ConsoleEncoding.PInvoke.cs index 6e5baca..dbd2ed9 100644 --- a/BepInEx/ConsoleUtil/ConsoleEncoding/ConsoleEncoding.PInvoke.cs +++ b/BepInEx/ConsoleUtil/ConsoleEncoding/ConsoleEncoding.PInvoke.cs @@ -15,7 +15,7 @@ namespace UnityInjector.ConsoleUtil // http://jonskeet.uk/csharp/ebcdic/ // using only safe (managed) code // -------------------------------------------------- - partial class ConsoleEncoding + internal partial class ConsoleEncoding { [DllImport("kernel32.dll")] private static extern uint GetConsoleOutputCP(); diff --git a/BepInEx/ConsoleUtil/ConsoleEncoding/ConsoleEncoding.cs b/BepInEx/ConsoleUtil/ConsoleEncoding/ConsoleEncoding.cs index bf69bcb..7be3609 100644 --- a/BepInEx/ConsoleUtil/ConsoleEncoding/ConsoleEncoding.cs +++ b/BepInEx/ConsoleUtil/ConsoleEncoding/ConsoleEncoding.cs @@ -15,7 +15,7 @@ namespace UnityInjector.ConsoleUtil // http://jonskeet.uk/csharp/ebcdic/ // using only safe (managed) code // -------------------------------------------------- - public partial class ConsoleEncoding : Encoding + internal partial class ConsoleEncoding : Encoding { private readonly uint _codePage; public override int CodePage => (int)_codePage; diff --git a/BepInEx/ConsoleUtil/ConsoleWindow.cs b/BepInEx/ConsoleUtil/ConsoleWindow.cs index e80bd2f..39d1bac 100644 --- a/BepInEx/ConsoleUtil/ConsoleWindow.cs +++ b/BepInEx/ConsoleUtil/ConsoleWindow.cs @@ -10,12 +10,14 @@ using System.Text; namespace UnityInjector.ConsoleUtil { - public class ConsoleWindow + internal class ConsoleWindow { public static bool IsAttatched { get; private set; } private static IntPtr _cOut; private static IntPtr _oOut; + public static TextWriter StandardOut { get; private set; } + public static void Attach() { if (IsAttatched) @@ -121,12 +123,13 @@ namespace UnityInjector.ConsoleUtil private static void Init() { var stdOut = Console.OpenStandardOutput(); - var stdWriter = new StreamWriter(stdOut, Encoding.Default) + StandardOut = new StreamWriter(stdOut, Encoding.Default) { AutoFlush = true }; - Console.SetOut(stdWriter); - Console.SetError(stdWriter); + + Console.SetOut(StandardOut); + Console.SetError(StandardOut); } [DllImport("kernel32.dll", SetLastError = true)] diff --git a/BepInEx/ConsoleUtil/SafeConsole.cs b/BepInEx/ConsoleUtil/SafeConsole.cs index 9d5a6cd..50ce40f 100644 --- a/BepInEx/ConsoleUtil/SafeConsole.cs +++ b/BepInEx/ConsoleUtil/SafeConsole.cs @@ -8,7 +8,7 @@ using System.Reflection; namespace UnityInjector.ConsoleUtil { - public static class SafeConsole + internal static class SafeConsole { private static GetColorDelegate _getBackgroundColor; private static GetColorDelegate _getForegroundColor; diff --git a/BepInEx/Logging/BaseLogger.cs b/BepInEx/Logging/BaseLogger.cs deleted file mode 100644 index 279cd4c..0000000 --- a/BepInEx/Logging/BaseLogger.cs +++ /dev/null @@ -1,99 +0,0 @@ -using System; -using System.IO; -using System.Text; - -namespace BepInEx.Logging -{ - /// - /// The base implementation of a logging class. - /// - public abstract class BaseLogger : TextWriter - { - protected BaseLogger() - { - DisplayedLevels = GetDefaultLogLevel(); - } - - /// - /// The encoding that the underlying text writer should use. Defaults to UTF-8 BOM. - /// - public override Encoding Encoding { get; } = new UTF8Encoding(true); - - - /// - /// The handler for a entry logged event. - /// - /// The text element of the log itself. - /// Whether or not it should be dislpayed to the user. - public delegate void EntryLoggedEventHandler(LogLevel level, object entry); - - /// - /// The listener event for an entry being logged. - /// - public event EntryLoggedEventHandler EntryLogged; - - /// - /// Retrieves the default log level to use for this logger. - /// - /// The default log level to use. - protected virtual LogLevel GetDefaultLogLevel() - { - LogLevel lowest; - - try - { - lowest = (LogLevel)Enum.Parse(typeof(LogLevel), Config.GetEntry("logger-displayed-levels", nameof(LogLevel.Info), "BepInEx")); - } - catch - { - return LogLevel.All; - } - - if (lowest == LogLevel.None) - return LogLevel.None; - - LogLevel value = lowest; - - foreach (LogLevel e in Enum.GetValues(typeof(LogLevel))) - { - if (lowest > e) - value |= e; - } - - return value; - } - - /// - /// A filter which is used to specify which log levels are not ignored by the logger. - /// - public LogLevel DisplayedLevels { get; set; } - - private readonly object logLockObj = new object(); - - /// - /// Logs an entry to the Logger instance. - /// - /// The level of the entry. - /// The textual value of the entry. - public virtual void Log(LogLevel level, object entry) - { - if ((DisplayedLevels & level) != LogLevel.None) - { - lock (logLockObj) - { - EntryLogged?.Invoke(level, entry); - WriteLine($"[{level.GetHighestLevel()}] {entry}"); - } - } - } - - /// - /// Logs an entry to the Logger instance, with a of Message. - /// - /// The text value of this log entry. - public virtual void Log(object entry) - { - Log(LogLevel.Message, entry); - } - } -} \ No newline at end of file diff --git a/BepInEx/Logging/ConsoleLogListener.cs b/BepInEx/Logging/ConsoleLogListener.cs new file mode 100644 index 0000000..e1c67c1 --- /dev/null +++ b/BepInEx/Logging/ConsoleLogListener.cs @@ -0,0 +1,22 @@ +using System; +using BepInEx.ConsoleUtil; + +namespace BepInEx.Logging +{ + /// + /// Logs entries using Unity specific outputs. + /// + public class ConsoleLogListener : ILogListener + { + public void LogEvent(object sender, LogEventArgs eventArgs) + { + string log = $"[{eventArgs.Level}:{((ILogSource)sender).SourceName}] {eventArgs.Data}\r\n"; + + Kon.ForegroundColor = eventArgs.Level.GetConsoleColor(); + Console.Write(log); + Kon.ForegroundColor = ConsoleColor.Gray; + } + + public void Dispose() { } + } +} \ No newline at end of file diff --git a/BepInEx/Logging/ILogListener.cs b/BepInEx/Logging/ILogListener.cs new file mode 100644 index 0000000..081f48b --- /dev/null +++ b/BepInEx/Logging/ILogListener.cs @@ -0,0 +1,9 @@ +using System; + +namespace BepInEx.Logging +{ + public interface ILogListener : IDisposable + { + void LogEvent(object sender, LogEventArgs eventArgs); + } +} \ No newline at end of file diff --git a/BepInEx/Logging/ILogSource.cs b/BepInEx/Logging/ILogSource.cs new file mode 100644 index 0000000..84613ab --- /dev/null +++ b/BepInEx/Logging/ILogSource.cs @@ -0,0 +1,11 @@ +using System; + +namespace BepInEx.Logging +{ + public interface ILogSource : IDisposable + { + string SourceName { get; } + + event EventHandler LogEvent; + } +} \ No newline at end of file diff --git a/BepInEx/Logging/LogEventArgs.cs b/BepInEx/Logging/LogEventArgs.cs new file mode 100644 index 0000000..8dd11f7 --- /dev/null +++ b/BepInEx/Logging/LogEventArgs.cs @@ -0,0 +1,20 @@ +using System; + +namespace BepInEx.Logging +{ + public class LogEventArgs : EventArgs + { + public object Data { get; protected set; } + + public LogLevel Level { get; protected set; } + + public ILogSource Source { get; protected set; } + + public LogEventArgs(object data, LogLevel level, ILogSource source) + { + Data = data; + Level = level; + Source = source; + } + } +} \ No newline at end of file diff --git a/BepInEx/Logging/Logger.cs b/BepInEx/Logging/Logger.cs index 028254c..cca6d96 100644 --- a/BepInEx/Logging/Logger.cs +++ b/BepInEx/Logging/Logger.cs @@ -1,4 +1,6 @@ -using BepInEx.Logging; +using System; +using System.Collections.Generic; +using BepInEx.Logging; namespace BepInEx { @@ -7,37 +9,75 @@ namespace BepInEx /// public static class Logger { - /// - /// The current instance of a that is being used. - /// - public static BaseLogger CurrentLogger { get; set; } + private static readonly ManualLogSource InternalLogSource = new ManualLogSource("BepInEx"); - /// - /// The listener event for an entry being logged. - /// - public static event BaseLogger.EntryLoggedEventHandler EntryLogged; + public static ICollection Listeners { get; } = new List(); + + public static ICollection Sources { get; } = new LogSourceCollection() + { + InternalLogSource + }; + + private static void InternalLogEvent(object sender, LogEventArgs eventArgs) + { + foreach (var listener in Listeners) + { + listener?.LogEvent(sender, eventArgs); + } + } /// /// Logs an entry to the current logger instance. /// /// The level of the entry. /// The textual value of the entry. - public static void Log(LogLevel level, object entry) + public static void Log(LogLevel level, object data) { - EntryLogged?.Invoke(level, entry); - - CurrentLogger?.Log(level, entry); + InternalLogEvent(InternalLogSource, new LogEventArgs(data, level, InternalLogSource)); } - /// - /// Sets the instance being used by the static class. - /// - /// The instance to use in the static class. - public static void SetLogger(BaseLogger logger) - { - CurrentLogger?.Dispose(); + public static void LogFatal(object data) => Log(LogLevel.Fatal, data); + public static void LogError(object data) => Log(LogLevel.Error, data); + public static void LogWarning(object data) => Log(LogLevel.Warning, data); + public static void LogMessage(object data) => Log(LogLevel.Message, data); + public static void LogInfo(object data) => Log(LogLevel.Info, data); + public static void LogDebug(object data) => Log(LogLevel.Debug, data); - CurrentLogger = logger; + + private class LogSourceCollection : List, ICollection + { + void ICollection.Add(ILogSource item) + { + if (item == null) + throw new ArgumentNullException(nameof(item), "Log sources cannot be null when added to the source list."); + + item.LogEvent += InternalLogEvent; + + base.Add(item); + } + + void ICollection.Clear() + { + foreach (var item in base.ToArray()) + { + ((ICollection)this).Remove(item); + } + } + + bool ICollection.Remove(ILogSource item) + { + if (item == null) + return false; + + if (!base.Contains(item)) + return false; + + item.LogEvent -= InternalLogEvent; + + base.Remove(item); + + return true; + } } } } \ No newline at end of file diff --git a/BepInEx/Logging/ManualLogSource.cs b/BepInEx/Logging/ManualLogSource.cs new file mode 100644 index 0000000..b71a6c7 --- /dev/null +++ b/BepInEx/Logging/ManualLogSource.cs @@ -0,0 +1,29 @@ +using System; + +namespace BepInEx.Logging +{ + public class ManualLogSource : ILogSource + { + public string SourceName { get; } + public event EventHandler LogEvent; + + public ManualLogSource(string sourceName) + { + SourceName = sourceName; + } + + public void Log(LogLevel level, object data) + { + LogEvent?.Invoke(this, new LogEventArgs(data, level, this)); + } + + public void LogFatal(object data) => Log(LogLevel.Fatal, data); + public void LogError(object data) => Log(LogLevel.Error, data); + public void LogWarning(object data) => Log(LogLevel.Warning, data); + public void LogMessage(object data) => Log(LogLevel.Message, data); + public void LogInfo(object data) => Log(LogLevel.Info, data); + public void LogDebug(object data) => Log(LogLevel.Debug, data); + + public void Dispose() { } + } +} \ No newline at end of file diff --git a/BepInEx/Logging/PreloaderLogWriter.cs b/BepInEx/Logging/PreloaderLogWriter.cs deleted file mode 100644 index 7038a12..0000000 --- a/BepInEx/Logging/PreloaderLogWriter.cs +++ /dev/null @@ -1,128 +0,0 @@ -using System; -using System.Diagnostics; -using System.IO; -using System.Text; -using BepInEx.Bootstrap; -using BepInEx.ConsoleUtil; - -namespace BepInEx.Logging -{ - /// - /// A log writer specific to the . - /// - /// - public class PreloaderLogWriter : BaseLogger - { - /// - /// The which contains all logged entries, so it may be passed onto another log writer. - /// - public StringBuilder StringBuilder { get; protected set; } = new StringBuilder(); - - /// - /// Whether or not the log writer is redirecting console output, so it can be logged. - /// - public bool IsRedirectingConsole { get; protected set; } - - protected TextWriter stdout; - protected LoggerTraceListener traceListener; - - private bool _enabled = false; - - /// - /// Whether or not the log writer is writing and/or redirecting. - /// - public bool Enabled - { - get => _enabled; - set - { - if (value) - Enable(); - else - Disable(); - } - } - - /// Whether or not to redirect the console standard output. - public PreloaderLogWriter(bool redirectConsole) - { - IsRedirectingConsole = redirectConsole; - - stdout = Console.Out; - traceListener = new LoggerTraceListener(this); - } - - /// - /// Enables the log writer. - /// - public void Enable() - { - if (_enabled) - return; - - if (IsRedirectingConsole) - Console.SetOut(this); - else - Console.SetOut(Null); - - Trace.Listeners.Add(traceListener); - - _enabled = true; - } - - /// - /// Disables the log writer. - /// - public void Disable() - { - if (!_enabled) - return; - - Console.SetOut(stdout); - - Trace.Listeners.Remove(traceListener); - - _enabled = false; - } - - /// - /// Logs an entry to the Logger instance. - /// - /// The level of the entry. - /// The textual value of the entry. - public override void Log(LogLevel level, object entry) - { - Kon.ForegroundColor = level.GetConsoleColor(); - base.Log(level, entry); - Kon.ForegroundColor = ConsoleColor.Gray; - } - - public override void Write(char value) - { - StringBuilder.Append(value); - - stdout.Write(value); - } - - public override void Write(string value) - { - StringBuilder.Append(value); - - stdout.Write(value); - } - - protected override void Dispose(bool disposing) - { - Disable(); - StringBuilder.Length = 0; - - traceListener?.Dispose(); - traceListener = null; - } - - public override string ToString() - { - return StringBuilder.ToString().Trim(); - } - } -} \ No newline at end of file diff --git a/BepInEx/Logging/LoggerTraceListener.cs b/BepInEx/Logging/TraceLogSource.cs similarity index 79% rename from BepInEx/Logging/LoggerTraceListener.cs rename to BepInEx/Logging/TraceLogSource.cs index 7fb0a17..5d78884 100644 --- a/BepInEx/Logging/LoggerTraceListener.cs +++ b/BepInEx/Logging/TraceLogSource.cs @@ -3,6 +3,7 @@ using System.Diagnostics; using System.Linq; using System.Reflection; using Harmony; +using UnityEngineInternal; namespace BepInEx.Logging { @@ -10,14 +11,9 @@ namespace BepInEx.Logging /// A trace listener that writes to an underlying instance. /// /// - public class LoggerTraceListener : TraceListener + public class TraceLogSource : TraceListener { - /// - /// The logger instance that is being written to. - /// - public BaseLogger Logger { get; } - - static LoggerTraceListener() + static TraceLogSource() { try { @@ -26,10 +22,28 @@ namespace BepInEx.Logging catch { } //ignore everything, if it's thrown an exception, we're using an assembly that has already fixed this } - /// The logger instance to write to. - public LoggerTraceListener(BaseLogger logger) + public static bool IsListening { get; protected set; } = false; + + private static TraceLogSource traceListener; + + public static ILogSource CreateSource() { - Logger = logger; + if (traceListener == null) + { + traceListener = new TraceLogSource(); + Trace.Listeners.Add(traceListener); + IsListening = true; + } + + return traceListener.LogSource; + } + + protected ManualLogSource LogSource { get; } + + /// The logger instance to write to. + protected TraceLogSource() + { + LogSource = new ManualLogSource("Trace"); } /// @@ -38,7 +52,7 @@ namespace BepInEx.Logging /// The message to write. public override void Write(string message) { - Logger.Write(message); + LogSource.Log(LogLevel.None, message); } /// @@ -47,7 +61,7 @@ namespace BepInEx.Logging /// The message to write. public override void WriteLine(string message) { - Logger.WriteLine(message); + LogSource.Log(LogLevel.None, $"{message}\r\n"); } public override void TraceEvent(TraceEventCache eventCache, string source, TraceEventType eventType, int id, string format, params object[] args) @@ -77,7 +91,7 @@ namespace BepInEx.Logging break; } - Logger.Log(level, $"{source} : {message}"); + LogSource.Log(level, $"{source} : {message}"); } /// @@ -113,12 +127,11 @@ namespace BepInEx.Logging instance.Patch( typeof(Trace).GetMethod("DoTrace", BindingFlags.Static | BindingFlags.NonPublic), - new HarmonyMethod(typeof(TraceFixer).GetMethod(nameof(DoTraceReplacement), BindingFlags.Static | BindingFlags.Public)), - null); + prefix: new HarmonyMethod(typeof(TraceFixer).GetMethod(nameof(DoTraceReplacement), BindingFlags.Static | BindingFlags.NonPublic))); } - public static bool DoTraceReplacement(string kind, Assembly report, string message) + private static bool DoTraceReplacement(string kind, Assembly report, string message) { string arg = string.Empty; try diff --git a/BepInEx/Logging/UnityLogListener.cs b/BepInEx/Logging/UnityLogListener.cs new file mode 100644 index 0000000..a48899f --- /dev/null +++ b/BepInEx/Logging/UnityLogListener.cs @@ -0,0 +1,54 @@ +using System; +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace BepInEx.Logging +{ + /// + /// Logs entries using Unity specific outputs. + /// + public class UnityLogListener : ILogListener + { + internal static readonly Action WriteStringToUnityLog; + + [DllImport("mono.dll", EntryPoint = "mono_lookup_internal_call")] + private static extern IntPtr MonoLookupInternalCall(IntPtr gconstpointer); + + static UnityLogListener() + { + foreach (MethodInfo methodInfo in typeof(UnityEngine.UnityLogWriter).GetMethods(BindingFlags.Static | BindingFlags.Public)) + { + if (MonoLookupInternalCall(methodInfo.MethodHandle.Value) == IntPtr.Zero) + continue; + + WriteStringToUnityLog = (Action)Delegate.CreateDelegate(typeof(Action), methodInfo); + break; + } + } + + public void LogEvent(object sender, LogEventArgs eventArgs) + { + if (sender is UnityLogSource) + return; + + string log = $"[{eventArgs.Level}:{((ILogSource)sender).SourceName}] {eventArgs.Data}\r\n"; + + WriteStringToUnityLog?.Invoke(log); + } + + public void Dispose() { } + } +} + +namespace UnityEngine +{ + internal sealed class UnityLogWriter + { + [MethodImpl(MethodImplOptions.InternalCall)] + public static extern void WriteStringToUnityLogImpl(string s); + + [MethodImpl(MethodImplOptions.InternalCall)] + public static extern void WriteStringToUnityLog(string s); + } +} \ No newline at end of file diff --git a/BepInEx/Logging/UnityLogSource.cs b/BepInEx/Logging/UnityLogSource.cs new file mode 100644 index 0000000..407a07d --- /dev/null +++ b/BepInEx/Logging/UnityLogSource.cs @@ -0,0 +1,86 @@ +using System; +using System.Reflection; +using UnityEngine; + +namespace BepInEx.Logging +{ + /// + /// Logs entries using Unity specific outputs. + /// + public class UnityLogSource : ILogSource + { + public string SourceName { get; } = "Unity Log"; + public event EventHandler LogEvent; + + public UnityLogSource() + { + InternalUnityLogMessage += unityLogMessageHandler; + } + + private void unityLogMessageHandler(object sender, LogEventArgs eventArgs) + { + var newEventArgs = new LogEventArgs(eventArgs.Data, eventArgs.Level, this); + LogEvent?.Invoke(this, newEventArgs); + } + + private bool disposed = false; + public void Dispose() + { + if (!disposed) + { + InternalUnityLogMessage -= unityLogMessageHandler; + disposed = true; + } + } + + #region Static Unity handler + + private static event EventHandler InternalUnityLogMessage; + + static UnityLogSource() + { + var callback = new Application.LogCallback(OnUnityLogMessageReceived); + + EventInfo logEvent = typeof(Application).GetEvent("logMessageReceived", BindingFlags.Public | BindingFlags.Static); + if (logEvent != null) + { + logEvent.AddEventHandler(null, callback); + //UnsubscribeAction = () => logEvent.RemoveEventHandler(null, callback); + } + else + { + MethodInfo registerLogCallback = typeof(Application).GetMethod("RegisterLogCallback", BindingFlags.Public | BindingFlags.Static); + registerLogCallback.Invoke(null, new object[] { callback }); + //UnsubscribeAction = () => registerLogCallback.Invoke(null, new object[] { null }); + } + } + + private static void OnUnityLogMessageReceived(string message, string stackTrace, LogType type) + { + LogLevel logLevel; + + switch (type) + { + case LogType.Error: + case LogType.Assert: + case LogType.Exception: + logLevel = LogLevel.Error; + break; + case LogType.Warning: + logLevel = LogLevel.Warning; + break; + case LogType.Log: + default: + logLevel = LogLevel.Info; + break; + } + + if (type == LogType.Exception) + message += $"\nStack trace:\n{stackTrace}"; + + InternalUnityLogMessage?.Invoke(null, new LogEventArgs(message, logLevel, null)); + } + + #endregion + } +} \ No newline at end of file diff --git a/BepInEx/Logging/UnityLogWriter.cs b/BepInEx/Logging/UnityLogWriter.cs deleted file mode 100644 index 2ec9888..0000000 --- a/BepInEx/Logging/UnityLogWriter.cs +++ /dev/null @@ -1,125 +0,0 @@ -using System; -using System.Reflection; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; -using BepInEx.ConsoleUtil; -using UnityEngine; - -namespace BepInEx.Logging -{ - /// - /// Logs entries using Unity specific outputs. - /// - public class UnityLogWriter : BaseLogger - { - private static readonly Action WriteStringToUnityLog; - - [DllImport("mono.dll", EntryPoint = "mono_lookup_internal_call")] - private static extern IntPtr MonoLookupInternalCall(IntPtr gconstpointer); - - static UnityLogWriter() - { - foreach (MethodInfo methodInfo in typeof(UnityEngine.UnityLogWriter).GetMethods(BindingFlags.Static | BindingFlags.Public)) - { - if (MonoLookupInternalCall(methodInfo.MethodHandle.Value) == IntPtr.Zero) - continue; - - WriteStringToUnityLog = (Action)Delegate.CreateDelegate(typeof(Action), methodInfo); - break; - } - } - - /// - /// Writes a string specifically to the game output log. - /// - /// The value to write. - public void WriteToLog(string value) - { - WriteStringToUnityLog?.Invoke(value); - } - - protected void InternalWrite(string value) - { - Console.Write(value); - WriteToLog(value); - } - - /// - /// Logs an entry to the Logger instance. - /// - /// The level of the entry. - /// The textual value of the entry. - public override void Log(LogLevel level, object entry) - { - Kon.ForegroundColor = level.GetConsoleColor(); - base.Log(level, entry); - Kon.ForegroundColor = ConsoleColor.Gray; - - // If the display level got ignored, still write it to the log - if ((DisplayedLevels & level) == LogLevel.None) - WriteToLog($"[{level.GetHighestLevel()}] {entry}\r\n"); - } - - public override void WriteLine(string value) => InternalWrite($"{value}\r\n"); - public override void Write(char value) => InternalWrite(value.ToString()); - public override void Write(string value) => InternalWrite(value); - - /// - /// Start listening to Unity's log message events and sending the messages to BepInEx logger. - /// - public static void ListenUnityLogs() - { - Type application = typeof(Application); - - EventInfo logEvent = application.GetEvent("logMessageReceived", BindingFlags.Public | BindingFlags.Static); - if (logEvent != null) - { - logEvent.AddEventHandler(null, new Application.LogCallback(OnUnityLogMessageReceived)); - } - else - { - MethodInfo registerLogCallback = application.GetMethod("RegisterLogCallback", BindingFlags.Public | BindingFlags.Static); - registerLogCallback.Invoke(null, new object[] { new Application.LogCallback(OnUnityLogMessageReceived) }); - } - } - - private static void OnUnityLogMessageReceived(string message, string stackTrace, LogType type) - { - LogLevel logLevel = LogLevel.Message; - - switch (type) - { - case LogType.Error: - case LogType.Assert: - case LogType.Exception: - logLevel = LogLevel.Error; - break; - case LogType.Warning: - logLevel = LogLevel.Warning; - break; - case LogType.Log: - default: - logLevel = LogLevel.Info; - break; - } - - Logger.Log(logLevel, message); - if (type == LogType.Exception) - { - Logger.Log(logLevel, $"Stack trace:\n{stackTrace}"); - } - } - } -} - -namespace UnityEngine -{ - internal sealed class UnityLogWriter - { - [MethodImpl(MethodImplOptions.InternalCall)] - public static extern void WriteStringToUnityLogImpl(string s); - - [MethodImpl(MethodImplOptions.InternalCall)] - public static extern void WriteStringToUnityLog(string s); - } -} \ No newline at end of file