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); } }