Last active
September 26, 2020 16:46
-
-
Save pardeike/fc0f044437d137d101b2992637e4455b to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using HarmonyLib; | |
using RimWorld; | |
using System; | |
using System.Collections.Generic; | |
using System.Diagnostics; | |
using System.Linq; | |
using System.Reflection; | |
using Verse; | |
using Verse.Sound; | |
namespace Brrainz | |
{ | |
[HarmonyPatch] | |
static class ExceptionHandler | |
{ | |
static readonly string RimworldAssemblyName = typeof(Pawn).Assembly.GetName().Name; | |
static readonly MethodInfo FinalizerMethod = SymbolExtensions.GetMethodInfo(() => Finalizer(default)); | |
static readonly Dictionary<Assembly, ModMetaData> MetaDataCache = new Dictionary<Assembly, ModMetaData>(); | |
static bool Prepare(MethodBase original) | |
{ | |
if (original == null) return true; | |
var info = Harmony.GetPatchInfo(original); | |
var ok = info == null || info.Finalizers.All(finalizer => finalizer.PatchMethod != FinalizerMethod); | |
if (ok) Log.Warning($"Brrainz: Patching {original.DeclaringType.FullName}.{original.Name} to report exceptions in mods"); | |
return ok; | |
} | |
static IEnumerable<MethodBase> TargetMethods() | |
{ | |
yield return AccessTools.Method(typeof(Root_Play), nameof(Root_Play.Start)); | |
yield return AccessTools.Method(typeof(Root_Play), nameof(Root_Play.Update)); | |
yield return AccessTools.Method(typeof(SoundRoot), nameof(SoundRoot.Update)); | |
yield return AccessTools.Method(typeof(UIRoot_Entry), nameof(UIRoot_Entry.Init)); | |
yield return AccessTools.Method(typeof(UIRoot_Entry), nameof(UIRoot_Entry.UIRootOnGUI)); | |
yield return AccessTools.Method(typeof(UIRoot_Entry), nameof(UIRoot_Entry.UIRootUpdate)); | |
yield return AccessTools.Method(typeof(UIRoot_Play), nameof(UIRoot_Play.Init)); | |
yield return AccessTools.Method(typeof(UIRoot_Play), nameof(UIRoot_Play.UIRootOnGUI)); | |
yield return AccessTools.Method(typeof(UIRoot_Play), nameof(UIRoot_Play.UIRootUpdate)); | |
} | |
static bool IsModMethod(MethodBase method) | |
{ | |
if (method == FinalizerMethod) return false; | |
var references = method.DeclaringType.Assembly.GetReferencedAssemblies(); | |
return references.Any(assemblyName => assemblyName.Name == RimworldAssemblyName); | |
} | |
static ModMetaData GetModMetaData(Assembly assembly) | |
{ | |
if (MetaDataCache.TryGetValue(assembly, out var metaData) == false) | |
{ | |
var contentPack = LoadedModManager.RunningMods | |
.FirstOrDefault(m => m.assemblies.loadedAssemblies.Contains(assembly)); | |
if (contentPack != null) | |
metaData = new ModMetaData(contentPack.RootDir); | |
MetaDataCache.Add(assembly, metaData); | |
} | |
return metaData; | |
} | |
[HarmonyPriority(int.MinValue)] | |
static void Finalizer(Exception __exception) | |
{ | |
var n = 0; | |
var exception = __exception; | |
MethodBase topMethod = null; | |
var seenAssemblies = new HashSet<Assembly>(); | |
var lines = new List<string>(); | |
while (exception != null) | |
{ | |
var st = new StackTrace(exception); | |
st.GetFrames().Select(frame => frame.GetMethod()) | |
.DoIf(method => method != null, method => | |
{ | |
topMethod = topMethod ?? method; | |
var assembly = method.DeclaringType.Assembly; | |
if (IsModMethod(method) && seenAssemblies.Add(assembly)) | |
{ | |
var metaData = GetModMetaData(assembly); | |
if (metaData != null && metaData.IsCoreMod == false) | |
{ | |
if (n == 0) | |
{ | |
lines.Add($"### Exception: {__exception.Message.Trim()}"); | |
lines.Add($"### Where: {topMethod.FullDescription()}"); | |
lines.Add("### Involved mods in order of most likely cause:"); | |
lines.Add("### [MOD_NAME_AND_AUTHOR] [STEAM_ID] [URL] [LAST_METHOD_EXECUTED]"); | |
} | |
lines.Add($"### {++n}) [{metaData.Name} by {metaData.Author}] [{metaData.SteamAppId}] [{metaData.Url}] [{method.DeclaringType.FullName}::{method.Name}]"); | |
} | |
} | |
}); | |
exception = exception.InnerException; | |
} | |
lines.Do(line => Log.Warning(line)); | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment