using System; using System.Reflection; using System.Runtime.CompilerServices; using BepInEx.ConsoleUtil; using UnityEngine; namespace BepInEx.Logging { /// /// Logs entries using Unity specific outputs. /// public class UnityLogWriter : BaseLogger { private delegate void WriteStringToUnityLogDelegate(string s); private static readonly WriteStringToUnityLogDelegate WriteStringToUnityLog; static UnityLogWriter() { Type logWriter = typeof(UnityEngine.Logger).Assembly.GetType("UnityEngine.UnityLogWriter"); MethodInfo writeLog = logWriter.GetMethod("WriteStringToUnityLog", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); if (writeLog == null) { writeLog = logWriter.GetMethod("WriteStringToUnityLogImpl", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); if (writeLog == null) return; } WriteStringToUnityLog = (WriteStringToUnityLogDelegate)Delegate.CreateDelegate(typeof(WriteStringToUnityLogDelegate), writeLog); } /// /// 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}"); } } } }