using System; using System.Collections.Generic; using System.Reflection; using HarmonyLib; using UnityEngine; /// /// Drops known-noise log lines inside Unity's own logger, so they never reach the /// console or BepInEx's LogOutput.log. The game funnels both Debug.LogError and /// Debug.LogWarning through Engine.Logger.Log(LogType, object) - that is the /// single place where a message can be refused before anyone formats it. /// internal static class LogNoiseFilter { private const string SelfTestProbe = "The character used for Underline is not available in font asset [radicade-filter-self-test]."; internal static void Apply(Harmony harmony) { var prefix = AccessTools.Method(typeof(LogNoiseFilter), nameof(Prefix)); if (prefix == null) { Diag.Warn("[LogFilter] the filter method was not found; known-noise log spam stays as-is."); return; } var patched = 0; foreach (var target in Targets()) { try { harmony.Patch(target, prefix: new HarmonyMethod(prefix)); patched++; } catch (Exception ex) { Diag.Warn("[LogFilter] could not patch " + target.DeclaringType?.Name + "." + target.Name + " (" + ex.GetType().Name + ": " + ex.Message + ")."); } } if (patched == 0) { Diag.Warn("[LogFilter] no Unity log funnel could be patched; the RigidbodyEx and font-underline spam stays as-is."); return; } Diag.Info("[LogFilter] armed on " + patched + " Unity log funnel(s): " + LogNoiseRules.PatternCount + " known-noise pattern(s) are dropped before they are printed."); SelfTest(); } private static IEnumerable Targets() { var log = AccessTools.Method(typeof(Logger), "Log", new[] { typeof(LogType), typeof(Il2CppSystem.Object) }); if (log != null) yield return log; var logWithContext = AccessTools.Method(typeof(Logger), "Log", new[] { typeof(LogType), typeof(Il2CppSystem.Object), typeof(UnityEngine.Object) }); if (logWithContext != null) yield return logWithContext; } private static bool Prefix(object[] __args) { try { if (__args == null || __args.Length < 2) return true; var message = __args[1]; if (message == null) return true; return !LogNoiseRules.ShouldDrop(message.ToString()); } catch { return true; } } /// /// Fires one probe line through Debug.LogWarning and checks that the rules saw /// it. If they did not, the filter is not wired to this build's logger and the /// log says so instead of pretending the spam is gone. /// private static void SelfTest() { try { var before = LogNoiseRules.DroppedCount; UnityEngine.Debug.LogWarning(SelfTestProbe); if (LogNoiseRules.DroppedCount > before) Diag.Info("[LogFilter] self-test PASS: a probe log line was intercepted inside Unity's logger."); else Diag.Warn("[LogFilter] self-test FAIL: a probe log line reached the log, so this build's spam is not being filtered."); } catch (Exception ex) { Diag.Warn("[LogFilter] self-test could not run (" + ex.GetType().Name + ": " + ex.Message + ")."); } } }