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