From fcddc8a301a49da879a3d6ecfc0d1e1fca34a15d Mon Sep 17 00:00:00 2001 From: Vitalii Mikhailov Date: Sun, 13 Oct 2024 19:56:26 +0300 Subject: [PATCH] Progress --- .../BUTR.CrashReport.Bannerlord.Parser.csproj | 61 -- .../CrashReportParser.cs | 544 ------------------ .../Extensions/IEnumerableExtensions.cs | 10 - .../Extensions/StringExtensions.cs | 10 - .../BUTR.CrashReport.Bannerlord.Source.csproj | 1 + .../CrashReportCreatorHelper.cs | 24 +- .../CrashReportShared.cs | 93 +-- .../HarmonyProvider.cs | 197 +++++-- .../MonoModProvider.cs | 91 +++ .../MonoModUtils.cs | 252 ++++++++ .../BepInExIntegration.cs | 8 +- .../BepInExIntegration.cs | 8 +- .../AssemblyArchitectureType.cs | 37 ++ src/BUTR.CrashReport.Models/AssemblyModel.cs | 2 +- .../{AssemblyModelType.cs => AssemblyType.cs} | 2 +- ...uleOrPluginModel.cs => CapabilityModel.cs} | 8 +- .../CrashReportModel.cs | 21 +- .../DependencyMetadataModel.cs | 2 +- ...ModelType.cs => DependencyMetadataType.cs} | 2 +- .../EnhancedStacktraceFrameModel.cs | 13 +- .../HarmonyPatchModel.cs | 10 +- .../HarmonyPatchType.cs | 6 +- .../HarmonyPatchesModel.cs | 6 +- .../LoaderPluginModel.cs | 4 +- .../{LogEntry.cs => LogEntryModel.cs} | 4 +- .../{LogSource.cs => LogSourceModel.cs} | 6 +- src/BUTR.CrashReport.Models/MetadataModel.cs | 13 +- ...odExecuting.cs => MethodExecutingModel.cs} | 4 +- .../{MethodSimple.cs => MethodSimpleModel.cs} | 4 +- src/BUTR.CrashReport.Models/ModuleModel.cs | 4 +- ...ModDetourModel.cs => MonoModPatchModel.cs} | 42 +- ...rModelType.cs => MonoModPatchModelType.cs} | 2 +- ...DetoursModel.cs => MonoModPatchesModel.cs} | 4 +- .../NativeAssemblyArchitectureType.cs | 35 -- .../RuntimePatchModel.cs | 35 ++ .../RuntimePatchesModel.cs | 27 + ...oModuleOrLoaderPlugin.cs => UpdateInfo.cs} | 4 +- .../CrashReportHtml.Html.cs | 2 +- .../CrashReportHtml.cs | 43 +- .../CrashReportImGui.cs | 11 +- .../Extensions/AssemblyModelTypeExtensions.cs | 2 +- .../ICrashReportRendererUtilities.cs | 28 +- .../ImGui/CmGui.Methods.cs | 10 + .../Renderer/ImGuiRenderer.01.Summary.cs | 79 ++- .../ImGuiRenderer.03.EnhancedStacktrace.cs | 26 +- .../ImGuiRenderer.05.InstalledModules.cs | 8 +- .../ImGuiRenderer.06.LoadedLoaderPlugins.cs | 4 +- .../Renderer/ImGuiRenderer.07.Assemblies.cs | 59 +- .../ImGuiRenderer.09.MonoModPatches.cs | 115 ++++ .../ImGuiRenderer.09.RuntimePatches.cs | 109 ++++ ....cs => ImGuiRenderer.10.HarmonyPatches.cs} | 8 +- .../Renderer/ImGuiRenderer.10.LogFiles.cs | 9 +- .../Renderer/ImGuiRenderer.ImGui.cs | 8 +- .../Renderer/ImGuiRenderer.cs | 77 ++- .../CrashReportWinForms.cs | 4 +- .../ICrashReportRendererUtilities.cs | 23 +- src/BUTR.CrashReport.Tool/Program.cs | 2 +- src/BUTR.CrashReport/CrashReportInfo.cs | 50 +- .../Interfaces/IAssemblyUtilities.cs | 2 +- .../Interfaces/ICommonProvider.cs | 13 + .../Interfaces/IHarmonyProvider.cs | 27 +- .../Interfaces/IMonoModProvider.cs | 37 ++ .../Interfaces/IPatchProvider.cs | 16 + src/BUTR.CrashReport/Models/HarmonyPatch.cs | 6 +- src/BUTR.CrashReport/Models/HarmonyPatches.cs | 54 +- src/BUTR.CrashReport/Models/MethodEntry.cs | 12 +- .../Models/MethodEntryHarmony.cs | 8 +- .../Models/MethodEntryMonoMod.cs | 14 + .../Models/MethodEntryRuntimePatch.cs | 12 + .../Models/StackFrameRuntimePatch.cs | 42 ++ .../Models/StacktraceEntry.cs | 22 +- .../Utils/CrashReportModelUtils.cs | 141 ++++- .../Utils/CrashReportUtils.cs | 153 +++-- 73 files changed, 1717 insertions(+), 1115 deletions(-) delete mode 100644 src/BUTR.CrashReport.Bannerlord.Parser/BUTR.CrashReport.Bannerlord.Parser.csproj delete mode 100644 src/BUTR.CrashReport.Bannerlord.Parser/CrashReportParser.cs delete mode 100644 src/BUTR.CrashReport.Bannerlord.Parser/Extensions/IEnumerableExtensions.cs delete mode 100644 src/BUTR.CrashReport.Bannerlord.Parser/Extensions/StringExtensions.cs create mode 100644 src/BUTR.CrashReport.Bannerlord.Source/MonoModProvider.cs create mode 100644 src/BUTR.CrashReport.Bannerlord.Source/MonoModUtils.cs create mode 100644 src/BUTR.CrashReport.Models/AssemblyArchitectureType.cs rename src/BUTR.CrashReport.Models/{AssemblyModelType.cs => AssemblyType.cs} (97%) rename src/BUTR.CrashReport.Models/{CapabilityModuleOrPluginModel.cs => CapabilityModel.cs} (76%) rename src/BUTR.CrashReport.Models/{DependencyMetadataModelType.cs => DependencyMetadataType.cs} (92%) rename src/BUTR.CrashReport.Models/{LogEntry.cs => LogEntryModel.cs} (94%) rename src/BUTR.CrashReport.Models/{LogSource.cs => LogSourceModel.cs} (88%) rename src/BUTR.CrashReport.Models/{MethodExecuting.cs => MethodExecutingModel.cs} (88%) rename src/BUTR.CrashReport.Models/{MethodSimple.cs => MethodSimpleModel.cs} (98%) rename src/BUTR.CrashReport.Models/{MonoModDetourModel.cs => MonoModPatchModel.cs} (51%) rename src/BUTR.CrashReport.Models/{MonoModDetourModelType.cs => MonoModPatchModelType.cs} (88%) rename src/BUTR.CrashReport.Models/{MonoModDetoursModel.cs => MonoModPatchesModel.cs} (85%) create mode 100644 src/BUTR.CrashReport.Models/RuntimePatchModel.cs create mode 100644 src/BUTR.CrashReport.Models/RuntimePatchesModel.cs rename src/BUTR.CrashReport.Models/{UpdateInfoModuleOrLoaderPlugin.cs => UpdateInfo.cs} (89%) create mode 100644 src/BUTR.CrashReport.Renderer.ImGui/Renderer/ImGuiRenderer.09.MonoModPatches.cs create mode 100644 src/BUTR.CrashReport.Renderer.ImGui/Renderer/ImGuiRenderer.09.RuntimePatches.cs rename src/BUTR.CrashReport.Renderer.ImGui/Renderer/{ImGuiRenderer.09.HarmonyPatches.cs => ImGuiRenderer.10.HarmonyPatches.cs} (98%) create mode 100644 src/BUTR.CrashReport/Interfaces/ICommonProvider.cs create mode 100644 src/BUTR.CrashReport/Interfaces/IMonoModProvider.cs create mode 100644 src/BUTR.CrashReport/Interfaces/IPatchProvider.cs create mode 100644 src/BUTR.CrashReport/Models/MethodEntryMonoMod.cs create mode 100644 src/BUTR.CrashReport/Models/MethodEntryRuntimePatch.cs create mode 100644 src/BUTR.CrashReport/Models/StackFrameRuntimePatch.cs diff --git a/src/BUTR.CrashReport.Bannerlord.Parser/BUTR.CrashReport.Bannerlord.Parser.csproj b/src/BUTR.CrashReport.Bannerlord.Parser/BUTR.CrashReport.Bannerlord.Parser.csproj deleted file mode 100644 index f77b8e8..0000000 --- a/src/BUTR.CrashReport.Bannerlord.Parser/BUTR.CrashReport.Bannerlord.Parser.csproj +++ /dev/null @@ -1,61 +0,0 @@ - - - - netstandard2.0 - preview - enable - true - true - - - - Debug;Release - false - true - true - false - - - - - - - - BUTR.CrashReport.Bannerlord.Parser - BUTR.CrashReport.Bannerlord.Parser - Contains the legacy crash report parser for version less than 13 - MIT - https://raw.githubusercontent.com/BUTR/BUTR.CrashReport/master/assets/Icon128x128.png - butr crash report bannerlord - - - - - - - - - - - - - - - - Utils\Anonymizer.cs - - - - - - $(ILRepackExcludeAssemblies);$(ProjectDir)$(OutputPath)BUTR.CrashReport.Models.dll; - - - - - - false - - - - diff --git a/src/BUTR.CrashReport.Bannerlord.Parser/CrashReportParser.cs b/src/BUTR.CrashReport.Bannerlord.Parser/CrashReportParser.cs deleted file mode 100644 index a6d39a5..0000000 --- a/src/BUTR.CrashReport.Bannerlord.Parser/CrashReportParser.cs +++ /dev/null @@ -1,544 +0,0 @@ -using BUTR.CrashReport.Bannerlord.Parser.Extensions; -using BUTR.CrashReport.Models; -using BUTR.CrashReport.Utils; - -using HtmlAgilityPack; - -using System; -using System.Collections.Generic; -using System.Globalization; -using System.IO; -using System.IO.Compression; -using System.Linq; -using System.Text; - -namespace BUTR.CrashReport.Bannerlord.Parser; - -/// -/// Parses a rendered Crash Report -/// -public static class CrashReportParser -{ - private delegate bool MatchSpan(ReadOnlySpan span); - private static IReadOnlyList GetAllOpenTags(ReadOnlySpan content, MatchSpan matcher) - { - var list = new List(); - var span = content; - while (span.IndexOf('<') is var idxOpen and not -1 && span.Slice(idxOpen).IndexOf('>') is var idxClose and not -1) - { - var tag = span.Slice(idxOpen, idxClose + 1); - span = span.Slice(idxOpen + idxClose + 1); - if (tag.Length < 2 || tag[1] == '/' || tag[^2] == '/') continue; - if (matcher(tag)) list.Add(tag.ToString()); - } - return list; - } - - private static IList GetEnhancedStacktrace(ReadOnlySpan rawContent, int version, HtmlNode node) - { - const string enhancedStacktraceStartDelimiter1 = "
"; - const string enhancedStacktraceStartDelimiter2 = "
"; - const string enhancedStacktraceEndDelimiter = "
"; - - var idx = 0; - if (rawContent.IndexOf(enhancedStacktraceStartDelimiter1.AsSpan(), StringComparison.Ordinal) is var enhancedStacktraceStartIdx1 and not -1) idx = enhancedStacktraceStartIdx1; - if (rawContent.IndexOf(enhancedStacktraceStartDelimiter2.AsSpan(), StringComparison.Ordinal) is var enhancedStacktraceStartIdx2 and not -1) idx = enhancedStacktraceStartIdx2; - - if (version < 1000 && idx != -1) - { - var enhancedStacktraceEndIdx = rawContent.Slice(idx).IndexOf(enhancedStacktraceEndDelimiter.AsSpan(), StringComparison.Ordinal) - enhancedStacktraceEndDelimiter.Length; - var enhancedStacktraceRaw = rawContent.Slice(idx, enhancedStacktraceEndIdx).ToString(); - while (GetAllOpenTags(enhancedStacktraceRaw.AsSpan(), span => !span.SequenceEqual(enhancedStacktraceStartDelimiter1.AsSpan()) && !span.SequenceEqual(enhancedStacktraceStartDelimiter2.AsSpan()) && span is not "
    " and not "
  • " and not "
    " and not "
    ") is { Count: > 0 } toEscape)
    -            {
    -                enhancedStacktraceRaw = toEscape.Aggregate(enhancedStacktraceRaw, (current, s) => current.Replace(s, s.Replace("<", "<").Replace(">", ">")));
    -            }
    -            //var openTags = GetAllOpenTags(enhancedStacktraceRaw, span => !span.SequenceEqual("
      ") && !span.SequenceEqual("
    • ") && !span.SequenceEqual("
      ")).ToArray(); - var enhancedStacktraceDoc = new HtmlDocument(); - enhancedStacktraceDoc.LoadHtml(enhancedStacktraceRaw); - node = enhancedStacktraceDoc.DocumentNode; - } - - return node.SelectSingleNode("descendant::div[@id=\"enhanced-stacktrace\"]/ul")?.ChildNodes.Where(cn => cn.Name == "li").Select(ParseEnhancedStacktrace).ToArray() ?? []; - } - - private static HtmlDocument Create(ref string content) - { - content = content.Replace("", "NULL"); - var document = new HtmlDocument(); - document.LoadHtml(content); - return document; - } - - /// - /// Parses a HTML string that contains the json data - /// - /// - /// - public static string? ParseHtmlJson(string content) - { - var document = Create(ref content); - var gzipBase64Json = document.DocumentNode.SelectSingleNode("descendant::div[@id=\"json-model-data\"]")?.InnerText; - if (string.IsNullOrEmpty(gzipBase64Json)) - return null; - - return DecompressJson(gzipBase64Json!); - } - - private static string DecompressJson(string gzipBase64Json) - { - var compressedStream = new MemoryStream(Convert.FromBase64String(gzipBase64Json)); - using var decompressorStream = new DeflateStream(compressedStream, CompressionMode.Decompress); - using var decompressedStream = new MemoryStream(); - decompressorStream.CopyTo(decompressedStream); - return Encoding.UTF8.GetString(decompressedStream.ToArray()); - } - - /// - /// Attempts to parse HTML content and will use the Json model if present or use the HTML code - /// - public static bool TryParse(string content, out byte version, out CrashReportModel? crashReportModel, out string? crashReportJson) - { - try - { - var document = Create(ref content); - - var versionStr = document.DocumentNode.SelectSingleNode("descendant::report")?.Attributes?["version"]?.Value; - version = byte.TryParse(versionStr, out var v) ? v : (byte) 1; - switch (version) - { - case >= 13: - { - crashReportModel = null; - var gzipBase64Json = document.DocumentNode.SelectSingleNode("descendant::div[@id=\"json-model-data\"]")?.InnerText; - crashReportJson = DecompressJson(gzipBase64Json!); - return true; - } - default: - { - crashReportModel = ParseLegacyHtmlInternal(version, document, content); - crashReportJson = null; - return true; - } - } - } - catch (Exception) - { - version = 0; - crashReportModel = null; - crashReportJson = null; - return false; - } - } - - /// - /// Parses the log files from the old HTML report - /// - /// - /// - public static IEnumerable ParseLegacyHtmlLogs(string content) - { - var document = Create(ref content); - - var versionStr = document.DocumentNode.SelectSingleNode("descendant::report")?.Attributes?["version"]?.Value; - var version = byte.TryParse(versionStr, out var v) ? v : (byte) 1; - switch (version) - { - case >= 13: - { - return []; - } - default: - { - var exceptionNode = document.DocumentNode.SelectSingleNode("descendant::div[@id=\"log-files\"]"); - return ParseLogsInternal(exceptionNode); - } - } - } - - private static IEnumerable ParseLogsInternal(HtmlNode node) - { - static LogEntry? ParseLogEntry(HtmlNode node) - { - var line = node.InnerText; - var idxDateStart = line.IndexOf('[') + 1; - var idxDateEnd = line.IndexOf(']'); - if (idxDateStart == -1 || idxDateEnd == -1) return null; - - if (!DateTimeOffset.TryParse(line.Substring(idxDateStart, idxDateEnd - idxDateStart), DateTimeFormatInfo.InvariantInfo, DateTimeStyles.RoundtripKind, out var date)) - return null; - - var idxTypeStart = line.IndexOf('[', idxDateEnd + 1) + 1; - var idxTypeEnd = line.IndexOf(']', idxDateEnd + 1); - if (idxTypeStart == -1 || idxTypeEnd == -1) return null; - - var idxLevelStart = line.IndexOf('[', idxTypeEnd + 1) + 1; - var idxLevelEnd = line.IndexOf(']', idxTypeEnd + 1); - if (idxLevelStart == -1 || idxLevelEnd == -1) return null; - - return new() - { - Date = date, - Type = line.Substring(idxTypeStart, idxTypeEnd - idxTypeStart), - Level = line.Substring(idxLevelStart, idxLevelEnd - idxLevelStart) switch - { - "VRB" => LogLevel.Verbose, - "DBG" => LogLevel.Debug, - "INF" => LogLevel.Information, - "WRN" => LogLevel.Warning, - "ERR" => LogLevel.Error, - "FTL" => LogLevel.Fatal, - _ => LogLevel.Information, - }, - Message = line.Substring(idxLevelEnd + 3), - }; - } - - foreach (var source in node.SelectNodes("ul/li")) - { - var name = source.ChildNodes.First().InnerText; - var entries = source.SelectNodes("ul/ul/li")?.Select(ParseLogEntry) ?? Array.Empty(); - yield return new() - { - Name = name, - Logs = entries.OfType().ToList(), - AdditionalMetadata = Array.Empty(), - }; - } - } - - /// - /// Parses the HTML file with a specific version provided - /// - public static CrashReportModel ParseLegacyHtml(byte version, string content) - { - var document = Create(ref content); - return ParseLegacyHtmlInternal(version, document, content); - } - - private static CrashReportModel ParseLegacyHtmlInternal(byte version, HtmlDocument document, string content) - { - var node = document.DocumentNode; - var id = node.SelectSingleNode("descendant::report")?.Attributes?["id"]?.Value ?? string.Empty; - var gameVersion = node.SelectSingleNode("descendant::game")?.Attributes?["version"]?.Value ?? string.Empty; - var installedModules = node.SelectSingleNode("descendant::div[@id=\"installed-modules\"]/ul")?.ChildNodes.Where(cn => cn.Name == "li").Select(x => ParseModule(version, x)).DistinctBy(x => x.Id).ToArray() ?? []; - var exception = ParseExceptions(node.SelectSingleNode("descendant::div[@id=\"exception\"]"), installedModules); - var involvedModules = node.SelectSingleNode("descendant::div[@id=\"involved-modules\"]/ul")?.ChildNodes.Where(cn => cn.Name == "li").SelectMany(ParseInvolvedModule).ToArray() ?? []; - var enhancedStacktrace = GetEnhancedStacktrace(content.AsSpan(), version, node); - - var assemblies = node.SelectSingleNode("descendant::div[@id=\"assemblies\"]/ul")?.ChildNodes.Where(cn => cn.Name == "li").Select(x => ParseAssembly(x, installedModules)).ToArray() ?? []; - var harmonyPatches = node.SelectSingleNode("descendant::div[@id=\"harmony-patches\"]/ul").ChildNodes.Where(cn => cn.Name == "li").Select(ParseHarmonyPatch).ToArray(); - var launcherType = node.SelectSingleNode("descendant::launcher")?.Attributes?["type"]?.Value ?? string.Empty; - var launcherVersion = node.SelectSingleNode("descendant::launcher")?.Attributes?["version"]?.Value ?? string.Empty; - var runtime = node.SelectSingleNode("descendant::runtime")?.Attributes?["value"]?.Value ?? string.Empty; - var butrloaderVersion = node.SelectSingleNode("descendant::butrloader")?.Attributes?["version"]?.Value ?? string.Empty; - var blseVersion = node.SelectSingleNode("descendant::blse")?.Attributes?["version"]?.Value ?? string.Empty; - var launcherexVersion = node.SelectSingleNode("descendant::launcherex")?.Attributes?["version"]?.Value ?? string.Empty; - - return new CrashReportModel - { - Id = Guid.TryParse(id, out var val) ? val : Guid.Empty, - Version = version, - Exception = exception, - Metadata = new() - { - GameName = "Bannerlord", - GameVersion = gameVersion, - LoaderPluginProviderName = !string.IsNullOrEmpty(butrloaderVersion) ? "BUTRLoader" : string.IsNullOrEmpty(blseVersion) ? "BLSE" : null, - LoaderPluginProviderVersion = !string.IsNullOrEmpty(butrloaderVersion) ? butrloaderVersion : string.IsNullOrEmpty(blseVersion) ? blseVersion : null, - LauncherType = launcherType, - LauncherVersion = launcherVersion, - Runtime = runtime, - OperatingSystemType = OperatingSystemType.Unknown, - OperatingSystemVersion = null, - AdditionalMetadata = new List - { - new() { Key = "LauncherExVersion", Value = launcherexVersion }, - }, - }, - Modules = installedModules, - InvolvedModules = involvedModules, - EnhancedStacktrace = enhancedStacktrace, - Assemblies = assemblies, - NativeModules = Array.Empty(), - HarmonyPatches = harmonyPatches, - //MonoModDetours = Array.Empty(), - LoaderPlugins = Array.Empty(), - InvolvedLoaderPlugins = Array.Empty(), - AdditionalMetadata = Array.Empty(), - }; - } - - private static ExceptionModel ParseExceptions(HtmlNode node, ModuleModel[] modules) - { - var exceptions = new List(); - - foreach (var exception in node.InnerHtml.Split("Inner Exception information")) - { - var exceptionLines = exception.Split(["
      ", "
      "], StringSplitOptions.RemoveEmptyEntries).Select(x => x.Trim().Trim('\n')).Where(x => x.Length != 0).ToList(); - var type = exceptionLines.First(x => x.StartsWith("Type: ")).Substring(6); - var message = exceptionLines.First(x => x.StartsWith("Message: ")).Substring(9); - var source = exceptionLines.First(x => x.StartsWith("Source: ")).Substring(8); - var callstackIdx = exceptionLines.FindIndex(x => x.StartsWith("CallStack:")); - var callstack = string.Join(Environment.NewLine, exceptionLines.Skip(callstackIdx + 1)).Replace("
        \n", "").Replace("
      1. ", "").Replace("
      2. \n", Environment.NewLine).Replace("
      ", ""); - exceptions.Add(new ExceptionModel - { - SourceAssemblyId = null, - SourceModuleId = modules.Any(x => x.Id == source) ? source : null, - SourceLoaderPluginId = null, - Type = type, - Message = message, - CallStack = callstack, - InnerException = null, - AdditionalMetadata = Array.Empty(), - }); - } - - var currentException = default(ExceptionModel); - foreach (var exception in exceptions.AsEnumerable().Reverse()) - { - exception.InnerException = currentException; - currentException = exception; - } - - return currentException!; - } - - private static ModuleModel ParseModule(byte version, HtmlNode node) - { - static string GetField(IEnumerable lines, string field) => lines - .FirstOrDefault(l => l.StartsWith($"{field}:"))?.Split([$"{field}:"], StringSplitOptions.None).Skip(1).FirstOrDefault()?.Trim() ?? string.Empty; - - static IReadOnlyList GetRange(IEnumerable lines, string bField, IEnumerable eFields) => lines - .SkipWhile(l => !l.StartsWith($"{bField}:")).Skip(1) - .TakeWhile(l => eFields.All(f => !l.StartsWith($"{f}:"))) - .ToArray(); - - static IList GetModuleDependencyMetadatas(IReadOnlyList lines) => lines.Select(sml => new DependencyMetadataModel - { - Type = sml.StartsWith("Load Before") ? DependencyMetadataModelType.LoadBefore - : sml.StartsWith("Load After") ? DependencyMetadataModelType.LoadAfter - : sml.StartsWith("Incompatible") ? DependencyMetadataModelType.Incompatible - : 0, - ModuleOrPluginId = sml.Replace("Load Before", "").Replace("Load After", "").Replace("Incompatible", "").Replace("(optional)", "").Trim(), - IsOptional = sml.Contains("(optional)"), - Version = null, // Was not available pre 13 - VersionRange = null, // Was not available pre 13 - AdditionalMetadata = Array.Empty(), - }).ToArray(); - - static IList GetModuleSubModules(IReadOnlyList lines) => lines - .Select((item, index) => new { Item = item, Index = index }) - .Where(o => !o.Item.Contains(':') && !o.Item.Contains(".dll")) - .Select(o => lines.Skip(o.Index + 1).TakeWhile(l => l.Contains(':') || l.Contains(".dll")).ToArray()) - .Select(sml => new ModuleSubModuleModel - { - Name = sml.FirstOrDefault(l => l.StartsWith("Name:"))?.Split("Name:").Skip(1).FirstOrDefault()?.Trim() ?? string.Empty, - AssemblyId = new() - { - Name = sml.FirstOrDefault(l => l.StartsWith("DLLName:"))?.Split("DLLName:").Skip(1).FirstOrDefault()?.Trim() ?? string.Empty, - Version = null, - PublicKeyToken = null - }, - Entrypoint = sml.FirstOrDefault(l => l.StartsWith("SubModuleClassType:"))?.Split("SubModuleClassType:").Skip(1).FirstOrDefault()?.Trim() ?? string.Empty, - AdditionalMetadata = sml.SkipWhile(l => !l.StartsWith("Tags:")).Skip(1).TakeWhile(l => !l.StartsWith("Assemblies:")).Select(l => - { - var split = l.Split(':', StringSplitOptions.RemoveEmptyEntries).Select(x => x.Trim()).ToArray(); - return new MetadataModel { Key = split[0], Value = split[1], }; - }).Concat(sml.SkipWhile(l => !l.StartsWith("Assemblies:")).Skip(1).Select(l => - { - return new MetadataModel { Key = "METADATA:Assembly", Value = l, }; - })).ToArray(), - }) - .ToArray(); - - var lines = node.InnerText.Split(["\r\n", "\r", "\n"], StringSplitOptions.RemoveEmptyEntries).Select(x => x.Trim()).ToArray(); - var isVortex = GetField(lines, "Vortex").Equals("true", StringComparison.OrdinalIgnoreCase); - var moduleModel = new ModuleModel - { - Id = GetField(lines, "Id"), - Name = GetField(lines, "Name"), - Version = GetField(lines, "Version"), - IsExternal = GetField(lines, "External").Equals("true", StringComparison.OrdinalIgnoreCase), - IsOfficial = GetField(lines, "Official").Equals("true", StringComparison.OrdinalIgnoreCase), - IsSingleplayer = GetField(lines, "Singleplayer").Equals("true", StringComparison.OrdinalIgnoreCase), - IsMultiplayer = GetField(lines, "Multiplayer").Equals("true", StringComparison.OrdinalIgnoreCase), - Url = GetField(lines, "Url"), - UpdateInfo = null, - DependencyMetadatas = GetModuleDependencyMetadatas(GetRange(lines, version == 1 ? "Dependency Metadatas" : "Dependencies", ["SubModules", "Additional Assemblies", "Url" - ])), - SubModules = GetModuleSubModules(GetRange(lines, "SubModules", ["Additional Assemblies"])), - Capabilities = Array.Empty(), - AdditionalMetadata = new List { new() { Key = "METADATA:MANAGED_BY_VORTEX", Value = isVortex.ToString() } }.Concat(lines.SkipWhile(l => !l.StartsWith("Additional Assemblies:")).Skip(1).Select(l => - { - return new MetadataModel { Key = "METADATA:AdditionalAssembly", Value = l, }; - })).ToList(), - }; - return moduleModel; - } - - private static IEnumerable ParseInvolvedModule(HtmlNode node) - { - var id = node.ChildNodes.FirstOrDefault(x => x.Name == "a")?.InnerText.Trim() ?? string.Empty; - return node.ChildNodes.FirstOrDefault(x => x.Name == "ul")?.ChildNodes.Select(x => - { - var lines = x.InnerHtml.Split("
      "); - var frame = lines.FirstOrDefault(y => y.StartsWith("Frame: "))?.Replace("::", ".").Substring(7) ?? string.Empty; - return new InvolvedModuleOrPluginModel - { - ModuleOrLoaderPluginId = id, - EnhancedStacktraceFrameName = frame, - AdditionalMetadata = Array.Empty(), - }; - }) ?? Array.Empty(); - } - - private static EnhancedStacktraceFrameModel ParseEnhancedStacktrace(HtmlNode node) - { - var frameLine = node.ChildNodes.FirstOrDefault()?.InnerText.Trim().Replace("\r\n", string.Empty) ?? string.Empty; - var name = frameLine.Replace("Frame: ", ""); - var ilOffsetIdx = name.IndexOf(" (IL Offset: ", StringComparison.Ordinal); - name = ilOffsetIdx != -1 ? name.Substring(0, ilOffsetIdx) : name; - var ilOffset = int.TryParse(frameLine.Split("(IL Offset: ").Skip(1).FirstOrDefault()?.Replace(")", string.Empty).Trim(), out var ilOffsetVal) ? ilOffsetVal : -1; - - var methods = new List(); - foreach (var childNode in node.ChildNodes.FirstOrDefault(x => x.Name == "ul")?.ChildNodes ?? Enumerable.Empty()) - { - var lines = childNode.InnerHtml.Replace("<", "<").Replace(">", ">").Trim().Split("
      ", StringSplitOptions.RemoveEmptyEntries); - var module = lines.Length > 0 ? lines[0].Substring(8) : null; - var methodFullDescription = lines.Length > 1 ? lines[1].Substring(8).Replace("::", ".") : string.Empty; - var idx1 = methodFullDescription.IndexOf("(", StringComparison.Ordinal); - var idx2 = idx1 != -1 ? methodFullDescription.Substring(0, idx1).LastIndexOf(" ", StringComparison.Ordinal) : -1; - var method = idx2 != -1 ? methodFullDescription.Substring(idx2 + 1) : string.Empty; - var methodSplit = method.Split("("); - var parameters = methodSplit.Length > 1 - ? methodSplit[1].Trim(')').Split(" ", StringSplitOptions.RemoveEmptyEntries) - .Where((_, i) => i % 2 == 0) - .Select(x => x.Trim(',')) - .ToList() - : []; - var methodFullName = methodSplit[0].Replace("::", "."); - var split = methodFullName.Split('.'); - methods.Add(new() - { - AssemblyId = null, - ModuleId = module, - LoaderPluginId = null, - MethodDeclaredTypeName = split.Length == 1 ? null : string.Join(".", split.Take(split.Length - 1)), - MethodName = split.Last(), - MethodFullDescription = methodFullDescription, - MethodParameters = parameters, - ILInstructions = Array.Empty(), - CSharpILMixedInstructions = Array.Empty(), - CSharpInstructions = Array.Empty(), - AdditionalMetadata = Array.Empty(), - }); - } - - var executingMethod = methods.Last(); - return new() - { - ILOffset = ilOffset, - NativeOffset = null, - FrameDescription = name, - ExecutingMethod = new() - { - AssemblyId = null, - ModuleId = executingMethod.ModuleId, - LoaderPluginId = null, - MethodDeclaredTypeName = executingMethod.MethodDeclaredTypeName, - MethodName = executingMethod.MethodName, - MethodFullDescription = executingMethod.MethodFullDescription, - MethodParameters = executingMethod.MethodParameters, - NativeInstructions = Array.Empty(), - ILInstructions = executingMethod.ILInstructions, - CSharpILMixedInstructions = Array.Empty(), - CSharpInstructions = Array.Empty(), - AdditionalMetadata = executingMethod.AdditionalMetadata, - }, - OriginalMethod = null, - PatchMethods = methods.Count == 1 ? [] : methods.Take(methods.Count - 1).ToArray(), - MethodFromStackframeIssue = false, - AdditionalMetadata = Array.Empty(), - }; - } - - private static AssemblyModel ParseAssembly(HtmlNode node, ModuleModel[] modules) - { - var splt = node.InnerText.Split(',', StringSplitOptions.RemoveEmptyEntries).Select(x => x.Trim()).ToArray(); - var outerHtml = node.OuterHtml; - - var isDynamic = splt[3].Equals("DYNAMIC"); - var isEmpty = splt[3].Equals("EMPTY"); - - var module = modules.FirstOrDefault(x => x.AdditionalMetadata.Where(y => y.Key == "METADATA:AdditionalAssembly").Any(kv => - { - var splt2 = kv.Value.Split(" ("); - var fullName = splt2[1].TrimEnd(')'); - return fullName.StartsWith(splt[0]); - })); - var assemblyModel = new AssemblyModel - { - Id = new() - { - Name = splt[0], - Version = splt[1], - PublicKeyToken = null, - }, - ModuleId = module?.Id, - LoaderPluginId = null, - CultureName = null, - Architecture = Enum.TryParse(splt[2], true, out var arch) ? arch : AssemblyArchitectureType.Unknown, - Hash = isDynamic || isEmpty ? string.Empty : splt[3], - AnonymizedPath = isDynamic ? "DYNAMIC" : isEmpty ? "EMPTY" : Anonymizer.AnonymizePath(splt[4]), - - Type = (outerHtml.Contains("dynamic_assembly") ? AssemblyModelType.Dynamic - : outerHtml.Contains("gac_assembly") ? AssemblyModelType.GAC - : outerHtml.Contains("tw_assembly") ? AssemblyModelType.GameCore - : outerHtml.Contains("tw_module_assembly") ? AssemblyModelType.GameModule - : outerHtml.Contains("module_assembly") ? AssemblyModelType.Module - : AssemblyModelType.Unclassified) | (isDynamic ? AssemblyModelType.Dynamic : AssemblyModelType.Unclassified), - - ImportedTypeReferences = Array.Empty(), - ImportedAssemblyReferences = Array.Empty(), - - AdditionalMetadata = Array.Empty(), - }; - return assemblyModel; - } - - private static HarmonyPatchesModel ParseHarmonyPatch(HtmlNode node) - { - static HarmonyPatchModel ParsePatch(HtmlNode node, HarmonyPatchType type) - { - var split = node.InnerText.Split(';', StringSplitOptions.RemoveEmptyEntries).Select(x => x.Trim()).ToArray(); - return new HarmonyPatchModel - { - Type = type, - AssemblyId = null, - ModuleId = null, - LoaderPluginId = null, - Owner = split.FirstOrDefault(x => x.StartsWith("Owner: "))?.Split(':')[1] ?? string.Empty, - Namespace = split.FirstOrDefault(x => x.StartsWith("Namespace: "))?.Split(':')[1] ?? string.Empty, - Index = split.FirstOrDefault(x => x.StartsWith("Index: "))?.Split(':')[1] is { } strIndex && int.TryParse(strIndex, out var index) ? index : 0, - Priority = split.FirstOrDefault(x => x.StartsWith("Priority: "))?.Split(':')[1] is { } strPriority && int.TryParse(strPriority, out var piority) ? piority : 400, - Before = split.FirstOrDefault(x => x.StartsWith("Before: "))?.Split(':')[1].Split(',') ?? [], - After = split.FirstOrDefault(x => x.StartsWith("After: "))?.Split(':')[1].Split(',') ?? [], - AdditionalMetadata = Array.Empty(), - }; - } - - var originalMethodFullName = node.ChildNodes.Skip(0).First().InnerText.Trim('\n'); - var prefixes = node.ChildNodes.FirstOrDefault(x => x.InnerText?.Contains("Prefixes") == true)?.SelectSingleNode("descendant::ul/li")?.ChildNodes.Select(x => ParsePatch(x, HarmonyPatchType.Prefix)).ToArray() ?? []; - var postfixes = node.ChildNodes.FirstOrDefault(x => x.InnerText?.Contains("Postfixes") == true)?.SelectSingleNode("descendant::ul/li")?.ChildNodes.Select(x => ParsePatch(x, HarmonyPatchType.Postfix)).ToArray() ?? []; - var transpilers = node.ChildNodes.FirstOrDefault(x => x.InnerText?.Contains("Transpilers") == true)?.SelectSingleNode("descendant::ul/li")?.ChildNodes.Select(x => ParsePatch(x, HarmonyPatchType.Transpiler)).ToArray() ?? []; - var finalizers = node.ChildNodes.FirstOrDefault(x => x.InnerText?.Contains("Finalizers") == true)?.SelectSingleNode("descendant::ul/li")?.ChildNodes.Select(x => ParsePatch(x, HarmonyPatchType.Finalizer)).ToArray() ?? []; - var harmonyPatchModel = new HarmonyPatchesModel - { - OriginalMethodName = originalMethodFullName.Split('.').Last(), - OriginalMethodDeclaredTypeName = originalMethodFullName, - Patches = prefixes.Concat(postfixes).Concat(transpilers).Concat(finalizers).ToArray(), - AdditionalMetadata = Array.Empty(), - }; - return harmonyPatchModel; - } -} \ No newline at end of file diff --git a/src/BUTR.CrashReport.Bannerlord.Parser/Extensions/IEnumerableExtensions.cs b/src/BUTR.CrashReport.Bannerlord.Parser/Extensions/IEnumerableExtensions.cs deleted file mode 100644 index 41b6bd3..0000000 --- a/src/BUTR.CrashReport.Bannerlord.Parser/Extensions/IEnumerableExtensions.cs +++ /dev/null @@ -1,10 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; - -namespace BUTR.CrashReport.Bannerlord.Parser.Extensions; - -internal static class IEnumerableExtensions -{ - public static IEnumerable DistinctBy(this IEnumerable items, Func property) => items.GroupBy(property).Select(x => x.First()); -} \ No newline at end of file diff --git a/src/BUTR.CrashReport.Bannerlord.Parser/Extensions/StringExtensions.cs b/src/BUTR.CrashReport.Bannerlord.Parser/Extensions/StringExtensions.cs deleted file mode 100644 index 5b16938..0000000 --- a/src/BUTR.CrashReport.Bannerlord.Parser/Extensions/StringExtensions.cs +++ /dev/null @@ -1,10 +0,0 @@ -using System; - -namespace BUTR.CrashReport.Bannerlord.Parser.Extensions; - -internal static class StringExtensions -{ - public static string[] Split(this string str, string separator) => str.Split([separator], StringSplitOptions.None); - - public static string[] Split(this string str, string separator, StringSplitOptions stringSplitOptions) => str.Split([separator], stringSplitOptions); -} \ No newline at end of file diff --git a/src/BUTR.CrashReport.Bannerlord.Source/BUTR.CrashReport.Bannerlord.Source.csproj b/src/BUTR.CrashReport.Bannerlord.Source/BUTR.CrashReport.Bannerlord.Source.csproj index 4768c3a..6a76a14 100644 --- a/src/BUTR.CrashReport.Bannerlord.Source/BUTR.CrashReport.Bannerlord.Source.csproj +++ b/src/BUTR.CrashReport.Bannerlord.Source/BUTR.CrashReport.Bannerlord.Source.csproj @@ -42,6 +42,7 @@ + diff --git a/src/BUTR.CrashReport.Bannerlord.Source/CrashReportCreatorHelper.cs b/src/BUTR.CrashReport.Bannerlord.Source/CrashReportCreatorHelper.cs index 20d3417..262e3b9 100644 --- a/src/BUTR.CrashReport.Bannerlord.Source/CrashReportCreatorHelper.cs +++ b/src/BUTR.CrashReport.Bannerlord.Source/CrashReportCreatorHelper.cs @@ -136,7 +136,7 @@ public virtual CrashReportMetadataModel GetCrashReportMetadataModel(CrashReportI }; } - private OperatingSystemType GetOperatingSystemType() + private static OperatingSystemType GetOperatingSystemType() { if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { @@ -151,7 +151,7 @@ private OperatingSystemType GetOperatingSystemType() return OperatingSystemType.Unknown; } - private string? GetOSVersion() + private static string? GetOSVersion() { if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) return OSUtilities.GetOSVersionWindows(); @@ -182,7 +182,7 @@ private OperatingSystemType GetOperatingSystemType() public virtual ILoaderPluginInfo? GetAssemblyPlugin(CrashReportInfo crashReport, Assembly assembly) => null; - public virtual AssemblyModelType GetAssemblyType(AssemblyModelType type, CrashReportInfo crashReport, Assembly assembly) + public virtual AssemblyType GetAssemblyType(AssemblyType type, CrashReportInfo crashReport, Assembly assembly) { static bool IsTWCore(Assembly assembly) { @@ -200,11 +200,11 @@ static bool IsTWCore(Assembly assembly) return true; } - if (IsTWCore(assembly)) type |= AssemblyModelType.GameCore; + if (IsTWCore(assembly)) type |= AssemblyType.GameCore; var module = !assembly.IsDynamic ? ModuleInfoHelper.GetModuleByType(AccessTools2.GetTypesFromAssembly(assembly).FirstOrDefault()) : null; - if (module is not null && !module.IsOfficial) type |= AssemblyModelType.Module; - if (module is not null && module.IsOfficial) type |= AssemblyModelType.GameModule; + if (module is not null && !module.IsOfficial) type |= AssemblyType.Module; + if (module is not null && module.IsOfficial) type |= AssemblyType.GameModule; return type; } @@ -253,13 +253,13 @@ public virtual List ToModuleModels(ICollection loadedM Id = x.Id, Name = x.Id, Version = x.Version, - UpdateInfo = x.UpdateInfo is not null && x.UpdateInfo.Split(':') is { Length: 2 } split ? new UpdateInfoModuleOrLoaderPlugin + UpdateInfo = x.UpdateInfo is not null && x.UpdateInfo.Split(':') is { Length: 2 } split ? new UpdateInfo { Provider = split[0], Value = split[1], } : null, Dependencies = Array.Empty(), - Capabilities = Array.Empty(), + Capabilities = Array.Empty(), AdditionalMetadata = Array.Empty(), }).ToList(); @@ -285,12 +285,12 @@ public virtual bool TryHandlePath(string path, out string anonymizedPath) protected static ModuleModel Convert(ModuleInfoExtendedHelper module, bool isManagedByVortex, ICollection assemblies) { - var updateInfos = module.UpdateInfo.Split(';').Select(x => x.Split(':') is { Length: 2 } split ? new UpdateInfoModuleOrLoaderPlugin() + var updateInfos = module.UpdateInfo.Split(';').Select(x => x.Split(':') is { Length: 2 } split ? new UpdateInfo() { Provider = split[0], Value = split[1], - } : null).OfType().ToArray(); - var capabilities = new List(); + } : null).OfType().ToArray(); + var capabilities = new List(); var moduleModel = new ModuleModel { Id = module.Id, @@ -305,7 +305,7 @@ protected static ModuleModel Convert(ModuleInfoExtendedHelper module, bool isMan DependencyMetadatas = module.DependenciesAllDistinct().Select(x => new DependencyMetadataModel { ModuleOrPluginId = x.Id, - Type = x.IsIncompatible ? DependencyMetadataModelType.Incompatible : (DependencyMetadataModelType) x.LoadType, + Type = x.IsIncompatible ? DependencyMetadataType.Incompatible : (DependencyMetadataType) x.LoadType, IsOptional = x.IsOptional, Version = !x.Version.Equals(ApplicationVersion.Empty) ? x.Version.ToString() : null, VersionRange = !x.VersionRange.Equals(ApplicationVersionRange.Empty) ? x.VersionRange.ToString() : null, diff --git a/src/BUTR.CrashReport.Bannerlord.Source/CrashReportShared.cs b/src/BUTR.CrashReport.Bannerlord.Source/CrashReportShared.cs index 81df0e2..0e9d623 100644 --- a/src/BUTR.CrashReport.Bannerlord.Source/CrashReportShared.cs +++ b/src/BUTR.CrashReport.Bannerlord.Source/CrashReportShared.cs @@ -136,112 +136,55 @@ internal static class CrashReportShared "TaleWorlds.*Culture*", }; - public static IEnumerable GetModuleCapabilities(ICollection assemblies, ModuleModel module) + public static IEnumerable GetModuleCapabilities(ICollection assemblies, ModuleModel module) { if (module.ContainsTypeReferences(assemblies, CrashReportShared.OSFileSystemTypeReferences)) - yield return new CapabilityModuleOrPluginModel("OS File System"); + yield return new CapabilityModel("OS File System"); if (module.ContainsTypeReferences(assemblies, CrashReportShared.GameFileSystemTypeReferences)) - yield return new CapabilityModuleOrPluginModel("Game File System"); + yield return new CapabilityModel("Game File System"); if (module.ContainsTypeReferences(assemblies, CrashReportShared.ShellTypeReferences)) - yield return new CapabilityModuleOrPluginModel("Shell"); + yield return new CapabilityModel("Shell"); if (module.ContainsTypeReferences(assemblies, CrashReportShared.SaveSystemTypeReferences)) - yield return new CapabilityModuleOrPluginModel("Save System"); + yield return new CapabilityModel("Save System"); if (module.ContainsAssemblyReferences(assemblies, CrashReportShared.SaveSystemAssemblyReferences)) - yield return new CapabilityModuleOrPluginModel("Save System"); + yield return new CapabilityModel("Save System"); if (module.ContainsTypeReferences(assemblies, CrashReportShared.GameEntitiesTypeReferences)) - yield return new CapabilityModuleOrPluginModel("Game Entities"); + yield return new CapabilityModel("Game Entities"); if (module.ContainsAssemblyReferences(assemblies, CrashReportShared.GameEntitiesAssemblyReferences)) - yield return new CapabilityModuleOrPluginModel("Game Entities"); + yield return new CapabilityModel("Game Entities"); if (module.ContainsAssemblyReferences(assemblies, CrashReportShared.InputSystemAssemblyReferences)) - yield return new CapabilityModuleOrPluginModel("Input System"); + yield return new CapabilityModel("Input System"); if (module.ContainsAssemblyReferences(assemblies, CrashReportShared.LocalizationSystemAssemblyReferences)) - yield return new CapabilityModuleOrPluginModel("Localization"); + yield return new CapabilityModel("Localization"); if (module.ContainsTypeReferences(assemblies, CrashReportShared.UITypeReferences)) - yield return new CapabilityModuleOrPluginModel("User Interface"); + yield return new CapabilityModel("User Interface"); if (module.ContainsAssemblyReferences(assemblies, CrashReportShared.UIAssemblyReferences)) - yield return new CapabilityModuleOrPluginModel("User Interface"); + yield return new CapabilityModel("User Interface"); if (module.ContainsTypeReferences(assemblies, CrashReportShared.HttpTypeReferences)) - yield return new CapabilityModuleOrPluginModel("Http"); + yield return new CapabilityModel("Http"); if (module.ContainsTypeReferences(assemblies, CrashReportShared.AchievementSystemTypeReferences)) - yield return new CapabilityModuleOrPluginModel("Achievements"); + yield return new CapabilityModel("Achievements"); if (module.ContainsTypeReferences(assemblies, CrashReportShared.CampaignSystemTypeReferences)) - yield return new CapabilityModuleOrPluginModel("Campaign"); + yield return new CapabilityModel("Campaign"); if (module.ContainsTypeReferences(assemblies, CrashReportShared.SkillSystemTypeReferences)) - yield return new CapabilityModuleOrPluginModel("Skills"); + yield return new CapabilityModel("Skills"); if (module.ContainsTypeReferences(assemblies, CrashReportShared.ItemSystemTypeReferences)) - yield return new CapabilityModuleOrPluginModel("Items"); + yield return new CapabilityModel("Items"); if (module.ContainsTypeReferences(assemblies, CrashReportShared.CultureSystemTypeReferences)) - yield return new CapabilityModuleOrPluginModel("Cultures"); - } - - public static string GetBUTRLoaderVersion(CrashReportModel crashReport) - { - if (crashReport.Assemblies.FirstOrDefault(x => x.Id.Name == "Bannerlord.BUTRLoader") is { } bAssembly) - return bAssembly.Id.Version ?? string.Empty; - return string.Empty; - } - - public static string GetBLSEVersion(CrashReportModel crashReport) - { - if (crashReport.Assemblies.FirstOrDefault(x => x.Id.Name == "Bannerlord.BLSE") is { } bAssembly) - return bAssembly.Id.Version ?? string.Empty; - return string.Empty; - } - - public static string GetLauncherType(CrashReportModel crashReport) - { - if (crashReport.AdditionalMetadata.FirstOrDefault(x => x.Key == "METADATA:Parent_Process_Name")?.Value is { } parentProcessName) - { - return parentProcessName switch - { - "Vortex" => "vortex", - "BannerLordLauncher" => "bannerlordlauncher", - "steam" => "steam", - "GalaxyClient" => "gog", - "EpicGamesLauncher" => "epicgames", - "devenv" => "debuggervisualstudio", - "JetBrains.Debugger.Worker64c" => "debuggerjetbrains", - "explorer" => "explorer", - "NovusLauncher" => "novus", - "ModOrganizer" => "modorganizer", - _ => $"unknown launcher - {parentProcessName}" - }; - } - - if (!string.IsNullOrEmpty(GetBUTRLoaderVersion(crashReport))) - return "butrloader"; - - if (!string.IsNullOrEmpty(GetBLSEVersion(crashReport))) - return "blse"; - - return "vanilla"; - } - - public static string GetLauncherVersion(CrashReportModel crashReport) - { - if (crashReport.AdditionalMetadata.FirstOrDefault(x => x.Key == "METADATA:Parent_Process_File_Version")?.Value is { } parentProcessFileVersion) - return parentProcessFileVersion; - - if (GetBUTRLoaderVersion(crashReport) is { } bVersion && !string.IsNullOrEmpty(bVersion)) - return bVersion; - - if (GetBLSEVersion(crashReport) is { } blseVersion && !string.IsNullOrEmpty(blseVersion)) - return blseVersion; - - return "0"; + yield return new CapabilityModel("Cultures"); } } } diff --git a/src/BUTR.CrashReport.Bannerlord.Source/HarmonyProvider.cs b/src/BUTR.CrashReport.Bannerlord.Source/HarmonyProvider.cs index 708b926..6fc5cbb 100644 --- a/src/BUTR.CrashReport.Bannerlord.Source/HarmonyProvider.cs +++ b/src/BUTR.CrashReport.Bannerlord.Source/HarmonyProvider.cs @@ -7,7 +7,7 @@ // Consider migrating to PackageReferences instead: // https://docs.microsoft.com/en-us/nuget/consume-packages/migrate-packages-config-to-package-reference // Migrating brings the following benefits: -// * The "BUTR.CrashReport.Bannerlord.Source" folder and the "CrashReportCreatorHelper.cs" file don't appear in your project. +// * The "BUTR.CrashReport.Bannerlord.Source" folder and the "HarmonyProvider.cs" file don't appear in your project. // * The added file is immutable and can therefore not be modified by coincidence. // * Updating/Uninstalling the package will work flawlessly. // @@ -36,6 +36,8 @@ // SOFTWARE. #endregion +using TaleWorlds.MountAndBlade.GauntletUI.Widgets.Multiplayer.Lobby; + #if !BUTRCRASHREPORT_DISABLE #nullable enable #if !BUTRCRASHREPORT_ENABLEWARNINGS @@ -45,10 +47,13 @@ namespace BUTR.CrashReport.Bannerlord { using global::BUTR.CrashReport.Interfaces; + using global::BUTR.CrashReport.Models; using global::HarmonyLib; + using global::HarmonyLib.BUTR.Extensions; using global::System; + using global::System.Collections; using global::System.Collections.Generic; using global::System.Diagnostics; using global::System.Linq; @@ -56,72 +61,112 @@ namespace BUTR.CrashReport.Bannerlord using static global::HarmonyLib.BUTR.Extensions.AccessTools2; - public class HarmonyProvider : IHarmonyProvider + public class PatchProvider : IPatchProvider { - private static class MonoModUtils + private static IEnumerable GetPatches(string type, MethodBase originalMethod, IEnumerable patches) => patches.Select(x => new RuntimePatch { - private delegate object GetCurrentRuntimeDelegate(); - private static readonly GetCurrentRuntimeDelegate? CurrentRuntimeMethod = GetPropertyGetterDelegate( - "MonoMod.RuntimeDetour.DetourHelper:Runtime", logErrorInTrace: false); - - private delegate MethodBase GetIdentifiableOldDelegate(object instance, MethodBase method); - private static readonly GetIdentifiableOldDelegate? GetIdentifiableOldMethod = GetDelegate( - "MonoMod.RuntimeDetour.IDetourRuntimePlatform:GetIdentifiable", logErrorInTrace: false); - - private delegate IntPtr GetNativeStartDelegate(object instance, MethodBase method); - private static readonly GetNativeStartDelegate? GetNativeStartMethod = GetDelegate( - "MonoMod.RuntimeDetour.IDetourRuntimePlatform:GetNativeStart", logErrorInTrace: false); - - - private delegate object GetCurrentPlatformTripleDelegate(); - private static readonly GetCurrentPlatformTripleDelegate? CurrentPlatformTripleMethod = GetPropertyGetterDelegate( - "MonoMod.Core.Platforms.PlatformTriple:Current", logErrorInTrace: false); + PatchProvider = "Harmony", + PatchType = type, + Original = originalMethod, + Patch = x.PatchMethod, + AdditionalMetadata = new List + { + new("Owner", x.owner), + new("Index", x.index.ToString()), + new("Priority", x.priority.ToString()), + new("Before", string.Join(", ", x.before)), + new("After", string.Join(", ", x.after)), + }, + }); - private delegate MethodBase GetIdentifiableDelegate(object instance, MethodBase method); - private static readonly GetIdentifiableDelegate? GetIdentifiableMethod = GetDelegate( - "MonoMod.Core.Platforms.PlatformTriple:GetIdentifiable", logErrorInTrace: false); + public IList GetAllPatches() + { + var runtimePatches = new List(); + + foreach (var originalMethod in Harmony.GetAllPatchedMethods()) + { + var patches = Harmony.GetPatchInfo(originalMethod); + if (patches is null) continue; + + runtimePatches.AddRange(GetPatches(originalMethod)); + } + + return runtimePatches; + } - private delegate IntPtr GetNativeMethodBodyDelegate(object instance, MethodBase method); - private static readonly GetNativeMethodBodyDelegate? GetNativeMethodBodyMethod = GetDelegate( - "MonoMod.Core.Platforms.PlatformTriple:GetNativeMethodBody", logErrorInTrace: false); + public IList GetPatches(MethodBase originalMethod) + { + var patches = Harmony.GetPatchInfo(originalMethod); + if (patches is null) return new List(); + + var runtimePatches = new List(); + runtimePatches.AddRange(GetPatches("Prefix", originalMethod, patches.Prefixes)); + runtimePatches.AddRange(GetPatches("Postfixes", originalMethod, patches.Postfixes)); + runtimePatches.AddRange(GetPatches("Finalizers", originalMethod, patches.Finalizers)); + runtimePatches.AddRange(GetPatches("Transpilers", originalMethod, patches.Transpilers)); + + return runtimePatches; + } - public static MethodBase? GetIdentifiable(MethodBase method) + public StackFrameRuntimePatch? GetPatches(StackFrame frame) + { + MethodBase? executingMethod; + var methodFromStackframeIssue = false; + try + { + executingMethod = Harmony.GetMethodFromStackframe(frame); + } + // NullReferenceException means the method was not found. Harmony doesn't handle this case gracefully + catch (NullReferenceException e) + { + Trace.TraceError(e.ToString()); + executingMethod = frame.GetMethod()!; + } + // The given generic instantiation was invalid. + // From what I understand, this will occur with generic methods + // Also when static constructors throw errors, Harmony resolution will fail + catch (Exception e) { - try - { - if (CurrentRuntimeMethod?.Invoke() is { } runtime) - return GetIdentifiableOldMethod?.Invoke(runtime, method); + Trace.TraceError(e.ToString()); + methodFromStackframeIssue = true; + executingMethod = frame.GetMethod()!; + } - if (CurrentPlatformTripleMethod?.Invoke() is { } platformTriple) - return GetIdentifiableMethod?.Invoke(platformTriple, method); - } - catch (Exception e) - { - Trace.TraceError(e.ToString()); - } + return null; + //return GetPatches(executingMethod); + } - return null; + public MethodBase GetOriginalMethod(StackFrame frame) + { + try + { + if (Harmony.GetMethodFromStackframe(frame) is MethodInfo method) + return Harmony.GetOriginalMethod(method); } - - public static IntPtr GetNativeMethodBody(MethodBase method) + catch (Exception e) { - try - { - if (CurrentRuntimeMethod?.Invoke() is { } runtine) - return GetNativeStartMethod?.Invoke(runtine, method) ?? IntPtr.Zero; - - if (CurrentPlatformTripleMethod?.Invoke() is { } platformTriple) - return GetNativeMethodBodyMethod?.Invoke(platformTriple, method) ?? IntPtr.Zero; - } - catch (Exception e) - { - Trace.TraceError(e.ToString()); - } + Trace.TraceError(e.ToString()); + } + return frame.GetMethod(); + } + public IntPtr GetNativeMethodBody(MethodBase method) + { + try + { + return MonoModUtils.GetNativeMethodBody(method); + } + catch (Exception e) + { + Trace.TraceError(e.ToString()); return IntPtr.Zero; } } - + } + + /* + public class HarmonyProvider : IHarmonyProvider + { public virtual IEnumerable GetAllPatchedMethods() => Harmony.GetAllPatchedMethods(); public virtual global::BUTR.CrashReport.Models.HarmonyPatches? GetPatchInfo(MethodBase originalMethod) @@ -148,36 +193,66 @@ public static IntPtr GetNativeMethodBody(MethodBase method) }; } - public virtual MethodBase? GetOriginalMethod(MethodInfo replacement) + public virtual HarmonyPatches? GetPatchInfo(StackFrame frame, IModuleProvider moduleProvider, ILoaderPluginProvider loaderPluginProvider) { + MethodBase? executingMethod; + var methodFromStackframeIssue = false; try { - return Harmony.GetOriginalMethod(replacement); + executingMethod = Harmony.GetMethodFromStackframe(frame); + } + // NullReferenceException means the method was not found. Harmony doesn't handle this case gracefully + catch (NullReferenceException e) + { + Trace.TraceError(e.ToString()); + executingMethod = frame.GetMethod()!; } + // The given generic instantiation was invalid. + // From what I understand, this will occur with generic methods + // Also when static constructors throw errors, Harmony resolution will fail catch (Exception e) { Trace.TraceError(e.ToString()); - return null; + methodFromStackframeIssue = true; + executingMethod = frame.GetMethod()!; } + + var executingIdentifiableMethod = executingMethod is MethodInfo mi ? MonoModUtils.GetIdentifiable(mi) as MethodInfo ?? mi : null; + var originalIdentifiableMethod = executingIdentifiableMethod is not null ? Harmony.GetOriginalMethod(executingIdentifiableMethod) : null; + return originalIdentifiableMethod is not null ? GetPatchInfo(originalIdentifiableMethod) : null; } - public virtual MethodBase? GetMethodFromStackframe(StackFrame frame) + public virtual MethodInfo? GetExecutingMethod(StackFrame frame) { try { - return Harmony.GetMethodFromStackframe(frame); + if (Harmony.GetMethodFromStackframe(frame) is MethodInfo method) + return method; } catch (Exception e) { Trace.TraceError(e.ToString()); - return null; } + return null; + } + + public virtual MethodBase? GetOriginalMethod(StackFrame frame) + { + try + { + if (Harmony.GetMethodFromStackframe(frame) is MethodInfo method) + return Harmony.GetOriginalMethod(method); + } + catch (Exception e) + { + Trace.TraceError(e.ToString()); + } + return null; } - - public MethodBase? GetIdentifiable(MethodBase method) => MonoModUtils.GetIdentifiable(method); public IntPtr GetNativeMethodBody(MethodBase method) => MonoModUtils.GetNativeMethodBody(method); } + */ } #pragma warning restore diff --git a/src/BUTR.CrashReport.Bannerlord.Source/MonoModProvider.cs b/src/BUTR.CrashReport.Bannerlord.Source/MonoModProvider.cs new file mode 100644 index 0000000..75ee1a0 --- /dev/null +++ b/src/BUTR.CrashReport.Bannerlord.Source/MonoModProvider.cs @@ -0,0 +1,91 @@ +// +// This code file has automatically been added by the "BUTR.CrashReport.Bannerlord.Source" NuGet package (https://www.nuget.org/packages/BUTR.CrashReport.Bannerlord.Source). +// Please see https://github.com/BUTR/BUTR.CrashReport for more information. +// +// IMPORTANT: +// DO NOT DELETE THIS FILE if you are using a "packages.config" file to manage your NuGet references. +// Consider migrating to PackageReferences instead: +// https://docs.microsoft.com/en-us/nuget/consume-packages/migrate-packages-config-to-package-reference +// Migrating brings the following benefits: +// * The "BUTR.CrashReport.Bannerlord.Source" folder and the "MonoModProvider.cs" file don't appear in your project. +// * The added file is immutable and can therefore not be modified by coincidence. +// * Updating/Uninstalling the package will work flawlessly. +// + +#region License +// MIT License +// +// Copyright (c) Bannerlord's Unofficial Tools & Resources +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +#endregion + +#if !BUTRCRASHREPORT_DISABLE +#nullable enable +#if !BUTRCRASHREPORT_ENABLEWARNINGS +#pragma warning disable +#endif + +namespace BUTR.CrashReport.Bannerlord +{ + /* + using global::BUTR.CrashReport.Interfaces; + using global::BUTR.CrashReport.Models; + + using global::HarmonyLib; + using global::HarmonyLib.BUTR.Extensions; + + using global::System; + using global::System.Collections; + using global::System.Collections.Generic; + using global::System.Diagnostics; + using global::System.Linq; + using global::System.Reflection; + + using static global::HarmonyLib.BUTR.Extensions.AccessTools2; + + public class MonoModProvider : IMonoModProvider + { + public virtual IEnumerable GetAllPatchedMethods() => Harmony.GetAllPatchedMethods(); + + public virtual MonoModPatches? GetPatchInfo(StackFrame frame, IModuleProvider moduleProvider, ILoaderPluginProvider loaderPluginProvider) + { + return null; + } + + public virtual MethodInfo? GetExecutingMethod(StackFrame frame) + { + return null; + } + + public virtual MethodBase? GetOriginalMethod(StackFrame frame) + { + return null; + } + + public virtual MonoModPatches? GetPatchInfo(MethodBase originalMethod) => MonoModUtils.GetPatches(originalMethod); + + public IntPtr GetNativeMethodBody(MethodBase method) => MonoModUtils.GetNativeMethodBody(method); + } + */ +} + +#pragma warning restore +#nullable restore +#endif // BUTRCRASHREPORT_DISABLE \ No newline at end of file diff --git a/src/BUTR.CrashReport.Bannerlord.Source/MonoModUtils.cs b/src/BUTR.CrashReport.Bannerlord.Source/MonoModUtils.cs new file mode 100644 index 0000000..5e9838b --- /dev/null +++ b/src/BUTR.CrashReport.Bannerlord.Source/MonoModUtils.cs @@ -0,0 +1,252 @@ +// +// This code file has automatically been added by the "BUTR.CrashReport.Bannerlord.Source" NuGet package (https://www.nuget.org/packages/BUTR.CrashReport.Bannerlord.Source). +// Please see https://github.com/BUTR/BUTR.CrashReport for more information. +// +// IMPORTANT: +// DO NOT DELETE THIS FILE if you are using a "packages.config" file to manage your NuGet references. +// Consider migrating to PackageReferences instead: +// https://docs.microsoft.com/en-us/nuget/consume-packages/migrate-packages-config-to-package-reference +// Migrating brings the following benefits: +// * The "BUTR.CrashReport.Bannerlord.Source" folder and the "MonoModProvider.cs" file don't appear in your project. +// * The added file is immutable and can therefore not be modified by coincidence. +// * Updating/Uninstalling the package will work flawlessly. +// + +#region License +// MIT License +// +// Copyright (c) Bannerlord's Unofficial Tools & Resources +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +#endregion + +#if !BUTRCRASHREPORT_DISABLE +#nullable enable +#if !BUTRCRASHREPORT_ENABLEWARNINGS +#pragma warning disable +#endif + +namespace BUTR.CrashReport.Bannerlord +{ + using global::BUTR.CrashReport.Interfaces; + using global::BUTR.CrashReport.Models; + + using global::HarmonyLib; + using global::HarmonyLib.BUTR.Extensions; + + using global::System; + using global::System.Collections; + using global::System.Collections.Generic; + using global::System.Diagnostics; + using global::System.Linq; + using global::System.Reflection; + + using static global::HarmonyLib.BUTR.Extensions.AccessTools2; + + internal static class MonoModUtils + { + private static readonly AccessTools.FieldRef? DetourStatesField = FieldRefAccess( + "MonoMod.RuntimeDetour.DetourManager:detourStates", logErrorInTrace: false); + private static readonly AccessTools.FieldRef? InfoField = FieldRefAccess( + "MonoMod.RuntimeDetour.DetourManager+ManagedDetourState:info", logErrorInTrace: false); + private static readonly AccessTools.FieldRef? DetoursField = FieldRefAccess( + "MonoMod.RuntimeDetour.MethodDetourInfo:lazyDetours", logErrorInTrace: false); + private static readonly AccessTools.FieldRef? ILHooksField = FieldRefAccess( + "MonoMod.RuntimeDetour.MethodDetourInfo:lazyILHooks", logErrorInTrace: false); + + private delegate MethodBase GetDetourInfoEntryDelegate(object instance); + private static readonly GetDetourInfoEntryDelegate? GetDetourInfoEntry = GetPropertyGetterDelegate( + "MonoMod.RuntimeDetour.DetourInfo:Entry", logErrorInTrace: false); + + private delegate MethodBase GetILHookInfoManipulatorMethodDelegate(object instance); + private static readonly GetILHookInfoManipulatorMethodDelegate? GetILHookInfoManipulatorMethod = GetPropertyGetterDelegate( + "MonoMod.RuntimeDetour.ILHookInfo:ManipulatorMethod", logErrorInTrace: false); + + private delegate bool GetDetourBaseIsAppliedDelegate(object instance); + private static readonly GetDetourBaseIsAppliedDelegate? GetDetourBaseIsApplied = GetPropertyGetterDelegate( + "MonoMod.RuntimeDetour.DetourBase:IsApplied", logErrorInTrace: false); + + private delegate object GetDetourBaseConfigDelegate(object instance); + private static readonly GetDetourBaseConfigDelegate? GetDetourBaseConfig = GetPropertyGetterDelegate( + "MonoMod.RuntimeDetour.DetourBase:Config", logErrorInTrace: false); + + private delegate string GetDetourConfigIdDelegate(object instance); + private static readonly GetDetourConfigIdDelegate? GetDetourConfigId = GetPropertyGetterDelegate( + "MonoMod.RuntimeDetour.DetourConfig:Id", logErrorInTrace: false); + + private delegate int? GetDetourConfigPriorityDelegate(object instance); + private static readonly GetDetourConfigPriorityDelegate? GetDetourConfigPriority = GetPropertyGetterDelegate( + "MonoMod.RuntimeDetour.DetourConfig:Priority", logErrorInTrace: false); + + private delegate int GetDetourConfigSubPriorityDelegate(object instance); + private static readonly GetDetourConfigSubPriorityDelegate? GetDetourConfigSubPriority = GetPropertyGetterDelegate( + "MonoMod.RuntimeDetour.DetourConfig:SubPriority", logErrorInTrace: false); + + private delegate IEnumerable GetDetourConfigBeforeDelegate(object instance); + private static readonly GetDetourConfigBeforeDelegate? GetDetourConfigBefore = GetPropertyGetterDelegate( + "MonoMod.RuntimeDetour.DetourConfig:Before", logErrorInTrace: false); + + private delegate IEnumerable GetDetourConfigAfterDelegate(object instance); + private static readonly GetDetourConfigAfterDelegate? GetDetourConfigAfter = GetPropertyGetterDelegate( + "MonoMod.RuntimeDetour.DetourConfig:After", logErrorInTrace: false); + + //private delegate object GetCurrentRuntimeDelegate(); + //private static readonly AccessTools.FieldRef? DetoursField = FieldRefAccess( + // "MonoMod.RuntimeDetour.DetourInfo:Entry", logErrorInTrace: false); + + private delegate object GetCurrentRuntimeDelegate(); + private static readonly GetCurrentRuntimeDelegate? CurrentRuntimeMethod = GetPropertyGetterDelegate( + "MonoMod.RuntimeDetour.DetourHelper:Runtime", logErrorInTrace: false); + + private delegate MethodBase GetIdentifiableOldDelegate(object instance, MethodBase method); + private static readonly GetIdentifiableOldDelegate? GetIdentifiableOldMethod = GetDelegate( + "MonoMod.RuntimeDetour.IDetourRuntimePlatform:GetIdentifiable", logErrorInTrace: false); + + private delegate IntPtr GetNativeStartDelegate(object instance, MethodBase method); + private static readonly GetNativeStartDelegate? GetNativeStartMethod = GetDelegate( + "MonoMod.RuntimeDetour.IDetourRuntimePlatform:GetNativeStart", logErrorInTrace: false); + + + private delegate object GetCurrentPlatformTripleDelegate(); + private static readonly GetCurrentPlatformTripleDelegate? CurrentPlatformTripleMethod = GetPropertyGetterDelegate( + "MonoMod.Core.Platforms.PlatformTriple:Current", logErrorInTrace: false); + + private delegate MethodBase GetIdentifiableDelegate(object instance, MethodBase method); + private static readonly GetIdentifiableDelegate? GetIdentifiableMethod = GetDelegate( + "MonoMod.Core.Platforms.PlatformTriple:GetIdentifiable", logErrorInTrace: false); + + private delegate IntPtr GetNativeMethodBodyDelegate(object instance, MethodBase method); + private static readonly GetNativeMethodBodyDelegate? GetNativeMethodBodyMethod = GetDelegate( + "MonoMod.Core.Platforms.PlatformTriple:GetNativeMethodBody", logErrorInTrace: false); + + public static MethodBase? GetIdentifiable(MethodBase method) + { + try + { + if (CurrentRuntimeMethod?.Invoke() is { } runtime) + return GetIdentifiableOldMethod?.Invoke(runtime, method); + + if (CurrentPlatformTripleMethod?.Invoke() is { } platformTriple) + return GetIdentifiableMethod?.Invoke(platformTriple, method); + } + catch (Exception e) + { + Trace.TraceError(e.ToString()); + } + + return null; + } + + public static IntPtr GetNativeMethodBody(MethodBase method) + { + try + { + if (CurrentRuntimeMethod?.Invoke() is { } runtine) + return GetNativeStartMethod?.Invoke(runtine, method) ?? IntPtr.Zero; + + if (CurrentPlatformTripleMethod?.Invoke() is { } platformTriple) + return GetNativeMethodBodyMethod?.Invoke(platformTriple, method) ?? IntPtr.Zero; + } + catch (Exception e) + { + Trace.TraceError(e.ToString()); + } + + return IntPtr.Zero; + } + + /* + public static MonoModPatches? GetPatches(MethodBase originalMethod) + { + try + { + if (DetourStatesField?.Invoke() is not { } detourStates) + return null; + + var detourState = detourStates.Contains(originalMethod) ? detourStates[originalMethod] : null; + if (detourState is null) return null; + + if (InfoField?.Invoke(detourState) is not { } info) + return null; + + if (DetoursField?.Invoke(info) is not { } detours) + return null; + + if (ILHooksField?.Invoke(info) is not { } ilHooks) + return null; + + var detourList = new List(); + foreach (var detour in detours) + { + var config = GetDetourBaseConfig?.Invoke(detour); + detourList.Add(new MonoModPatch + { + Method = GetDetourInfoEntry?.Invoke(detour), + IsActive = GetDetourBaseIsApplied?.Invoke(detour) ?? false, + Id = GetDetourConfigId?.Invoke(config) ?? "Unknown", + Index = null, + MaxIndex = null, + GlobalIndex = null, + Priority = GetDetourConfigPriority?.Invoke(config), + SubPriority = GetDetourConfigSubPriority?.Invoke(config) ?? 0, + Before = GetDetourConfigBefore?.Invoke(config)?.ToArray() ?? new string[0], + After = GetDetourConfigAfter.Invoke(config)?.ToArray() ?? new string[0], + }); + } + + var ilHooksList = new List(); + foreach (var ilHook in ilHooks) + { + var config = GetDetourBaseConfig?.Invoke(ilHook); + ilHooksList.Add(new MonoModPatch + { + Method = GetILHookInfoManipulatorMethod?.Invoke(ilHook), + IsActive = GetDetourBaseIsApplied?.Invoke(ilHook) ?? false, + Id = GetDetourConfigId?.Invoke(config) ?? "Unknown", + Index = null, + MaxIndex = null, + GlobalIndex = null, + Priority = GetDetourConfigPriority?.Invoke(config), + SubPriority = GetDetourConfigSubPriority?.Invoke(config) ?? 0, + Before = GetDetourConfigBefore?.Invoke(config)?.ToArray() ?? new string[0], + After = GetDetourConfigAfter.Invoke(config)?.ToArray() ?? new string[0], + }); + } + + return new MonoModPatches + { + Detours = detourList, + ILHooks = ilHooksList, + }; + + } + catch (Exception e) + { + Trace.TraceError(e.ToString()); + } + + return null; + } + */ + } +} + +#pragma warning restore +#nullable restore +#endif // BUTRCRASHREPORT_DISABLE \ No newline at end of file diff --git a/src/BUTR.CrashReport.BepInEx5.Source/BepInExIntegration.cs b/src/BUTR.CrashReport.BepInEx5.Source/BepInExIntegration.cs index 3bf7dfe..2692d3d 100644 --- a/src/BUTR.CrashReport.BepInEx5.Source/BepInExIntegration.cs +++ b/src/BUTR.CrashReport.BepInEx5.Source/BepInExIntegration.cs @@ -53,11 +53,11 @@ namespace BUTR.CrashReport.BepInEx5 public abstract class BepInExIntegration { - public abstract IEnumerable GetModuleCapabilities(ICollection assemblies, LoaderPluginModel loaderPlugin); + public abstract IEnumerable GetModuleCapabilities(ICollection assemblies, LoaderPluginModel loaderPlugin); public List GetPlugins(ICollection assemblies) => Chainloader.PluginInfos.Select(kv => { - var capabilities = new List(); + var capabilities = new List(); var loaderPlugin = new LoaderPluginModel { Id = kv.Value.Metadata.GUID, @@ -69,7 +69,7 @@ public List GetPlugins(ICollection assemblies) ModuleOrPluginId = x.DependencyGUID, Version = x.MinimumVersion.ToString(4), VersionRange = null, - Type = DependencyMetadataModelType.LoadBefore, + Type = DependencyMetadataType.LoadBefore, IsOptional = x.Flags.HasFlag(BepInDependency.DependencyFlags.SoftDependency), AdditionalMetadata = new List { @@ -92,7 +92,7 @@ public List GetPlugins(ICollection assemblies) ModuleOrPluginId = x.IncompatibilityGUID, Version = null, VersionRange = null, - Type = DependencyMetadataModelType.Incompatible, + Type = DependencyMetadataType.Incompatible, IsOptional = false, AdditionalMetadata = Array.Empty(), })) diff --git a/src/BUTR.CrashReport.BepInEx6.Source/BepInExIntegration.cs b/src/BUTR.CrashReport.BepInEx6.Source/BepInExIntegration.cs index 417ec12..5691151 100644 --- a/src/BUTR.CrashReport.BepInEx6.Source/BepInExIntegration.cs +++ b/src/BUTR.CrashReport.BepInEx6.Source/BepInExIntegration.cs @@ -53,11 +53,11 @@ namespace BUTR.CrashReport.BepInEx6 public abstract class BepInExIntegration { - public abstract IEnumerable GetModuleCapabilities(ICollection assemblies, LoaderPluginModel loaderPlugin); + public abstract IEnumerable GetModuleCapabilities(ICollection assemblies, LoaderPluginModel loaderPlugin); private List GetPlugins(BaseChainloader chainloader, ICollection assemblies) => chainloader.Plugins.Select(kv => { - var capabilities = new List(); + var capabilities = new List(); var loaderPlugin = new LoaderPluginModel { Id = kv.Value.Metadata.GUID, @@ -69,7 +69,7 @@ private List GetPlugins(BaseChainloader cha ModuleOrPluginId = x.DependencyGUID, Version = null, VersionRange = x.VersionRange.ToString(), - Type = DependencyMetadataModelType.LoadBefore, + Type = DependencyMetadataType.LoadBefore, IsOptional = x.Flags.HasFlag(BepInDependency.DependencyFlags.SoftDependency), AdditionalMetadata = new List { @@ -81,7 +81,7 @@ private List GetPlugins(BaseChainloader cha ModuleOrPluginId = x.IncompatibilityGUID, Version = null, VersionRange = null, - Type = DependencyMetadataModelType.Incompatible, + Type = DependencyMetadataType.Incompatible, IsOptional = false, AdditionalMetadata = Array.Empty(), })).ToList(), diff --git a/src/BUTR.CrashReport.Models/AssemblyArchitectureType.cs b/src/BUTR.CrashReport.Models/AssemblyArchitectureType.cs new file mode 100644 index 0000000..577ec8f --- /dev/null +++ b/src/BUTR.CrashReport.Models/AssemblyArchitectureType.cs @@ -0,0 +1,37 @@ +namespace BUTR.CrashReport.Models; + +/// +/// Represents the architecture of a native assembly. +/// +public enum AssemblyArchitectureType +{ + /// + /// Unknown architecture. + /// + Unknown = 0, + + /// + /// + /// + MSIL = 1, + + /// + /// + /// + X86 = 2, + + /// + /// + /// + IA64 = 3, + + /// + /// + /// + Amd64 = 4, + + /// + /// + /// + Arm = 5, +} \ No newline at end of file diff --git a/src/BUTR.CrashReport.Models/AssemblyModel.cs b/src/BUTR.CrashReport.Models/AssemblyModel.cs index f77c86e..b776243 100644 --- a/src/BUTR.CrashReport.Models/AssemblyModel.cs +++ b/src/BUTR.CrashReport.Models/AssemblyModel.cs @@ -50,7 +50,7 @@ public sealed record AssemblyModel /// /// The detected types for the assembly. /// - public required AssemblyModelType Type { get; set; } + public required AssemblyType Type { get; set; } /// /// The list of imported type references from the assembly. diff --git a/src/BUTR.CrashReport.Models/AssemblyModelType.cs b/src/BUTR.CrashReport.Models/AssemblyType.cs similarity index 97% rename from src/BUTR.CrashReport.Models/AssemblyModelType.cs rename to src/BUTR.CrashReport.Models/AssemblyType.cs index 4db26dc..7e8244f 100644 --- a/src/BUTR.CrashReport.Models/AssemblyModelType.cs +++ b/src/BUTR.CrashReport.Models/AssemblyType.cs @@ -6,7 +6,7 @@ namespace BUTR.CrashReport.Models; /// Represents attributes for a .NET assembly. /// [Flags] -public enum AssemblyModelType +public enum AssemblyType { /// /// Unknown assembly origin diff --git a/src/BUTR.CrashReport.Models/CapabilityModuleOrPluginModel.cs b/src/BUTR.CrashReport.Models/CapabilityModel.cs similarity index 76% rename from src/BUTR.CrashReport.Models/CapabilityModuleOrPluginModel.cs rename to src/BUTR.CrashReport.Models/CapabilityModel.cs index df9d2db..a18f2bc 100644 --- a/src/BUTR.CrashReport.Models/CapabilityModuleOrPluginModel.cs +++ b/src/BUTR.CrashReport.Models/CapabilityModel.cs @@ -3,7 +3,7 @@ /// /// Represents the functionality that is used by the module or plugin. /// -public sealed record CapabilityModuleOrPluginModel +public sealed record CapabilityModel { /// /// The name of the capability. @@ -16,12 +16,12 @@ public sealed record CapabilityModuleOrPluginModel public string Description { get; set; } = string.Empty; /// - /// Creates a new instance of . + /// Creates a new instance of . /// - public CapabilityModuleOrPluginModel(string name) => Name = name; + public CapabilityModel(string name) => Name = name; /// - public bool Equals(CapabilityModuleOrPluginModel? other) + public bool Equals(CapabilityModel? other) { if (ReferenceEquals(null, other)) return false; if (ReferenceEquals(this, other)) return true; diff --git a/src/BUTR.CrashReport.Models/CrashReportModel.cs b/src/BUTR.CrashReport.Models/CrashReportModel.cs index d73f129..cb643de 100644 --- a/src/BUTR.CrashReport.Models/CrashReportModel.cs +++ b/src/BUTR.CrashReport.Models/CrashReportModel.cs @@ -53,20 +53,23 @@ public sealed record CrashReportModel /// The list of native modules that are present. /// public required IList NativeModules { get; set; } = new List(); - + + /// + /// The list of runtime patches that are present. + /// + public required IList RuntimePatches { get; set; } = new List(); /* /// - /// The list of MonoMod detours that are present. - /// MonoMod does not keep a list of detours. If you have a library that does, here it could be exposed. + /// The list of MonoMod patches that are present. /// - public required IList MonoModDetours { get; set; } = new List(); - */ + public required IList MonoModPatches { get; set; } = new List(); /// /// The list of Harmony patches that are present. /// public required IList HarmonyPatches { get; set; } = new List(); + */ /// /// The list of loader plugins that are present. @@ -97,7 +100,9 @@ public bool Equals(CrashReportModel? other) InvolvedModules.SequenceEqual(other.InvolvedModules) && EnhancedStacktrace.SequenceEqual(other.EnhancedStacktrace) && Assemblies.SequenceEqual(other.Assemblies) && - HarmonyPatches.SequenceEqual(other.HarmonyPatches) && + RuntimePatches.SequenceEqual(other.RuntimePatches) && + //HarmonyPatches.SequenceEqual(other.HarmonyPatches) && + //HarmonyPatches.SequenceEqual(other.HarmonyPatches) && LoaderPlugins.SequenceEqual(other.LoaderPlugins) && InvolvedLoaderPlugins.SequenceEqual(other.InvolvedLoaderPlugins) && AdditionalMetadata.SequenceEqual(other.AdditionalMetadata); @@ -116,7 +121,9 @@ public override int GetHashCode() hashCode = (hashCode * 397) ^ InvolvedModules.GetHashCode(); hashCode = (hashCode * 397) ^ EnhancedStacktrace.GetHashCode(); hashCode = (hashCode * 397) ^ Assemblies.GetHashCode(); - hashCode = (hashCode * 397) ^ HarmonyPatches.GetHashCode(); + hashCode = (hashCode * 397) ^ RuntimePatches.GetHashCode(); + //hashCode = (hashCode * 397) ^ HarmonyPatches.GetHashCode(); + //hashCode = (hashCode * 397) ^ HarmonyPatches.GetHashCode(); hashCode = (hashCode * 397) ^ LoaderPlugins.GetHashCode(); hashCode = (hashCode * 397) ^ InvolvedLoaderPlugins.GetHashCode(); hashCode = (hashCode * 397) ^ AdditionalMetadata.GetHashCode(); diff --git a/src/BUTR.CrashReport.Models/DependencyMetadataModel.cs b/src/BUTR.CrashReport.Models/DependencyMetadataModel.cs index 037dd31..955dbc3 100644 --- a/src/BUTR.CrashReport.Models/DependencyMetadataModel.cs +++ b/src/BUTR.CrashReport.Models/DependencyMetadataModel.cs @@ -16,7 +16,7 @@ public sealed record DependencyMetadataModel /// /// The dependency type. /// - public required DependencyMetadataModelType Type { get; set; } + public required DependencyMetadataType Type { get; set; } /// /// Whether the dependency is required. diff --git a/src/BUTR.CrashReport.Models/DependencyMetadataModelType.cs b/src/BUTR.CrashReport.Models/DependencyMetadataType.cs similarity index 92% rename from src/BUTR.CrashReport.Models/DependencyMetadataModelType.cs rename to src/BUTR.CrashReport.Models/DependencyMetadataType.cs index 42b4b83..0c89ad4 100644 --- a/src/BUTR.CrashReport.Models/DependencyMetadataModelType.cs +++ b/src/BUTR.CrashReport.Models/DependencyMetadataType.cs @@ -3,7 +3,7 @@ /// /// Represents the type of teh dependency metadata. /// -public enum DependencyMetadataModelType +public enum DependencyMetadataType { /// /// The depencency will load before this module. diff --git a/src/BUTR.CrashReport.Models/EnhancedStacktraceFrameModel.cs b/src/BUTR.CrashReport.Models/EnhancedStacktraceFrameModel.cs index 0c6d1af..6381498 100644 --- a/src/BUTR.CrashReport.Models/EnhancedStacktraceFrameModel.cs +++ b/src/BUTR.CrashReport.Models/EnhancedStacktraceFrameModel.cs @@ -14,11 +14,6 @@ public sealed record EnhancedStacktraceFrameModel /// public required string FrameDescription { get; set; } - /// - /// Whether there was an issue with getting the data from the stackframe. - /// - public required bool MethodFromStackframeIssue { get; set; } - /// /// /// @@ -34,17 +29,17 @@ public sealed record EnhancedStacktraceFrameModel /// /// The method from the stacktrace frame that is being executed. /// - public required MethodExecuting ExecutingMethod { get; set; } + public required MethodExecutingModel ExecutingMethod { get; set; } /// /// The original method that is being patched. Is null when no patches exists. Use instead. /// - public required MethodSimple? OriginalMethod { get; set; } + public required MethodSimpleModel? OriginalMethod { get; set; } /// /// The list of patch methods that are applied to the method. /// - public required IList PatchMethods { get; set; } = new List(); + public required IList PatchMethods { get; set; } = new List(); /// /// @@ -58,7 +53,6 @@ public bool Equals(EnhancedStacktraceFrameModel? other) if (ReferenceEquals(null, other)) return false; if (ReferenceEquals(this, other)) return true; return FrameDescription == other.FrameDescription && - MethodFromStackframeIssue == other.MethodFromStackframeIssue && ILOffset == other.ILOffset && NativeOffset == other.NativeOffset && ExecutingMethod.Equals(other.ExecutingMethod) && @@ -73,7 +67,6 @@ public override int GetHashCode() unchecked { var hashCode = FrameDescription.GetHashCode(); - hashCode = (hashCode * 397) ^ MethodFromStackframeIssue.GetHashCode(); hashCode = (hashCode * 397) ^ ILOffset.GetHashCode(); hashCode = (hashCode * 397) ^ NativeOffset.GetHashCode(); hashCode = (hashCode * 397) ^ ExecutingMethod.GetHashCode(); diff --git a/src/BUTR.CrashReport.Models/HarmonyPatchModel.cs b/src/BUTR.CrashReport.Models/HarmonyPatchModel.cs index 9019c63..dfc573b 100644 --- a/src/BUTR.CrashReport.Models/HarmonyPatchModel.cs +++ b/src/BUTR.CrashReport.Models/HarmonyPatchModel.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +/* +using System.Collections.Generic; using System.Linq; namespace BUTR.CrashReport.Models; @@ -36,10 +37,6 @@ public sealed record HarmonyPatchModel /// public required string Owner { get; set; } - /// - /// - /// - /// public required string Namespace { get; set; } /// @@ -109,4 +106,5 @@ public override int GetHashCode() return hashCode; } } -} \ No newline at end of file +} +*/ \ No newline at end of file diff --git a/src/BUTR.CrashReport.Models/HarmonyPatchType.cs b/src/BUTR.CrashReport.Models/HarmonyPatchType.cs index 0602546..8f58eb2 100644 --- a/src/BUTR.CrashReport.Models/HarmonyPatchType.cs +++ b/src/BUTR.CrashReport.Models/HarmonyPatchType.cs @@ -1,4 +1,5 @@ -namespace BUTR.CrashReport.Models; +/* +namespace BUTR.CrashReport.Models; /// /// Represents the type of a Harmony patch. @@ -24,4 +25,5 @@ public enum HarmonyPatchType /// /// Transpiler = 4, -} \ No newline at end of file +} +*/ \ No newline at end of file diff --git a/src/BUTR.CrashReport.Models/HarmonyPatchesModel.cs b/src/BUTR.CrashReport.Models/HarmonyPatchesModel.cs index ad34f09..160dd22 100644 --- a/src/BUTR.CrashReport.Models/HarmonyPatchesModel.cs +++ b/src/BUTR.CrashReport.Models/HarmonyPatchesModel.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +/* +using System.Collections.Generic; using System.Linq; namespace BUTR.CrashReport.Models; @@ -52,4 +53,5 @@ public override int GetHashCode() return hashCode; } } -} \ No newline at end of file +} +*/ \ No newline at end of file diff --git a/src/BUTR.CrashReport.Models/LoaderPluginModel.cs b/src/BUTR.CrashReport.Models/LoaderPluginModel.cs index ac56578..6abb2c3 100644 --- a/src/BUTR.CrashReport.Models/LoaderPluginModel.cs +++ b/src/BUTR.CrashReport.Models/LoaderPluginModel.cs @@ -26,7 +26,7 @@ public sealed record LoaderPluginModel /// /// The information for updating the module. /// - public required UpdateInfoModuleOrLoaderPlugin? UpdateInfo { get; set; } + public required UpdateInfo? UpdateInfo { get; set; } /// /// The plugin dependencies of the plugin @@ -36,7 +36,7 @@ public sealed record LoaderPluginModel /// /// The capabilities, if there are any. /// - public required IList Capabilities { get; set; } = new List(); + public required IList Capabilities { get; set; } = new List(); /// /// diff --git a/src/BUTR.CrashReport.Models/LogEntry.cs b/src/BUTR.CrashReport.Models/LogEntryModel.cs similarity index 94% rename from src/BUTR.CrashReport.Models/LogEntry.cs rename to src/BUTR.CrashReport.Models/LogEntryModel.cs index 489c11f..a50f18c 100644 --- a/src/BUTR.CrashReport.Models/LogEntry.cs +++ b/src/BUTR.CrashReport.Models/LogEntryModel.cs @@ -5,7 +5,7 @@ namespace BUTR.CrashReport.Models; /// /// Represents a single log entry. /// -public sealed record LogEntry +public sealed record LogEntryModel { /// /// The date and time this log entry was created. @@ -28,7 +28,7 @@ public sealed record LogEntry public required string Message { get; set; } /// - public bool Equals(LogEntry? other) + public bool Equals(LogEntryModel? other) { if (ReferenceEquals(null, other)) return false; if (ReferenceEquals(this, other)) return true; diff --git a/src/BUTR.CrashReport.Models/LogSource.cs b/src/BUTR.CrashReport.Models/LogSourceModel.cs similarity index 88% rename from src/BUTR.CrashReport.Models/LogSource.cs rename to src/BUTR.CrashReport.Models/LogSourceModel.cs index 4d5bb77..668cb25 100644 --- a/src/BUTR.CrashReport.Models/LogSource.cs +++ b/src/BUTR.CrashReport.Models/LogSourceModel.cs @@ -6,7 +6,7 @@ namespace BUTR.CrashReport.Models; /// /// Represents a log source. Could be a file or anything else. /// -public sealed record LogSource +public sealed record LogSourceModel { /// /// The name of the log source. @@ -16,7 +16,7 @@ public sealed record LogSource /// /// The log entries associated with the log source. /// - public required IList Logs { get; set; } = new List(); + public required IList Logs { get; set; } = new List(); /// /// @@ -25,7 +25,7 @@ public sealed record LogSource public required IList AdditionalMetadata { get; set; } = new List(); /// - public bool Equals(LogSource? other) + public bool Equals(LogSourceModel? other) { if (ReferenceEquals(null, other)) return false; if (ReferenceEquals(this, other)) return true; diff --git a/src/BUTR.CrashReport.Models/MetadataModel.cs b/src/BUTR.CrashReport.Models/MetadataModel.cs index 5d5cd13..5b2cef6 100644 --- a/src/BUTR.CrashReport.Models/MetadataModel.cs +++ b/src/BUTR.CrashReport.Models/MetadataModel.cs @@ -1,4 +1,6 @@ -namespace BUTR.CrashReport.Models; +using System.Diagnostics.CodeAnalysis; + +namespace BUTR.CrashReport.Models; /// /// Represents a generic metadata extension for any model. @@ -15,6 +17,15 @@ public sealed record MetadataModel /// public required string Value { get; set; } + public MetadataModel() { } + + [SetsRequiredMembers] + public MetadataModel(string key, string value) + { + Key = key; + Value = value; + } + /// public bool Equals(MetadataModel? other) { diff --git a/src/BUTR.CrashReport.Models/MethodExecuting.cs b/src/BUTR.CrashReport.Models/MethodExecutingModel.cs similarity index 88% rename from src/BUTR.CrashReport.Models/MethodExecuting.cs rename to src/BUTR.CrashReport.Models/MethodExecutingModel.cs index 266c627..84c4da8 100644 --- a/src/BUTR.CrashReport.Models/MethodExecuting.cs +++ b/src/BUTR.CrashReport.Models/MethodExecutingModel.cs @@ -6,7 +6,7 @@ namespace BUTR.CrashReport.Models; /// /// Represents the actual executing method of a stack trace frame. Can the the original method or a patched method. /// -public sealed record MethodExecuting : MethodSimple +public sealed record MethodExecutingModel : MethodSimpleModel { /// /// The native code of the method that was compiled by the JIT. @@ -14,7 +14,7 @@ public sealed record MethodExecuting : MethodSimple public required IList NativeInstructions { get; set; } = new List(); /// - public bool Equals(MethodExecuting? other) + public bool Equals(MethodExecutingModel? other) { if (ReferenceEquals(null, other)) return false; if (ReferenceEquals(this, other)) return true; diff --git a/src/BUTR.CrashReport.Models/MethodSimple.cs b/src/BUTR.CrashReport.Models/MethodSimpleModel.cs similarity index 98% rename from src/BUTR.CrashReport.Models/MethodSimple.cs rename to src/BUTR.CrashReport.Models/MethodSimpleModel.cs index 47259d4..8cb2ae0 100644 --- a/src/BUTR.CrashReport.Models/MethodSimple.cs +++ b/src/BUTR.CrashReport.Models/MethodSimpleModel.cs @@ -6,7 +6,7 @@ namespace BUTR.CrashReport.Models; /// /// Represents a method. /// -public record MethodSimple +public record MethodSimpleModel { /// /// The assembly identity of the assembly that contains the method. @@ -70,7 +70,7 @@ public record MethodSimple public required IList AdditionalMetadata { get; set; } = new List(); /// - public virtual bool Equals(MethodSimple? other) + public virtual bool Equals(MethodSimpleModel? other) { if (ReferenceEquals(null, other)) return false; if (ReferenceEquals(this, other)) return true; diff --git a/src/BUTR.CrashReport.Models/ModuleModel.cs b/src/BUTR.CrashReport.Models/ModuleModel.cs index 02786a9..d8dc279 100644 --- a/src/BUTR.CrashReport.Models/ModuleModel.cs +++ b/src/BUTR.CrashReport.Models/ModuleModel.cs @@ -51,7 +51,7 @@ public sealed record ModuleModel /// /// The information for updating the module. /// - public required UpdateInfoModuleOrLoaderPlugin? UpdateInfo { get; set; } + public required UpdateInfo? UpdateInfo { get; set; } /// /// The dependencies of the module, if there are any. @@ -66,7 +66,7 @@ public sealed record ModuleModel /// /// The capabilities, if there are any. /// - public required IList Capabilities { get; set; } = new List(); + public required IList Capabilities { get; set; } = new List(); /// /// diff --git a/src/BUTR.CrashReport.Models/MonoModDetourModel.cs b/src/BUTR.CrashReport.Models/MonoModPatchModel.cs similarity index 51% rename from src/BUTR.CrashReport.Models/MonoModDetourModel.cs rename to src/BUTR.CrashReport.Models/MonoModPatchModel.cs index 5523305..3470caf 100644 --- a/src/BUTR.CrashReport.Models/MonoModDetourModel.cs +++ b/src/BUTR.CrashReport.Models/MonoModPatchModel.cs @@ -6,17 +6,37 @@ namespace BUTR.CrashReport.Models; /// /// Represents a MonoMod detour. /// -public sealed record MonoModDetourModel +public sealed class MonoModPatchModel { /// /// The type of the detour. /// - public required MonoModDetourModelType Type { get; set; } + public required MonoModPatchModelType Type { get; set; } /// - /// The method that is doing the detour. + /// Is null if not from a module. /// - public required MethodSimple? Method { get; set; } + /// + public required string? ModuleId { get; set; } + + /// + /// Is null if not from a module. + /// + /// + public required string? LoaderPluginId { get; set; } + + /// + /// The of the assembly that contains the patch. + /// + public required AssemblyIdModel? AssemblyId { get; set; } + + public required string Namespace { get; set; } + + /// + /// + /// + /// + public required bool IsActive { get; set; } /// /// @@ -24,11 +44,23 @@ public sealed record MonoModDetourModel /// public required string Id { get; set; } + public required int? Index { get; set; } + + public required int? MaxIndex { get; set; } + + public required int? GlobalIndex { get; set; } + /// /// /// /// - public required int Priority { get; set; } + public required int? Priority { get; set; } + + /// + /// + /// + /// + public required int? SubPriority { get; set; } /// /// diff --git a/src/BUTR.CrashReport.Models/MonoModDetourModelType.cs b/src/BUTR.CrashReport.Models/MonoModPatchModelType.cs similarity index 88% rename from src/BUTR.CrashReport.Models/MonoModDetourModelType.cs rename to src/BUTR.CrashReport.Models/MonoModPatchModelType.cs index c8a3142..7d7a0b3 100644 --- a/src/BUTR.CrashReport.Models/MonoModDetourModelType.cs +++ b/src/BUTR.CrashReport.Models/MonoModPatchModelType.cs @@ -4,7 +4,7 @@ namespace BUTR.CrashReport.Models; /// /// Type of the MonoMod detour /// -public enum MonoModDetourModelType +public enum MonoModPatchModelType { /// /// Native or Simple detour diff --git a/src/BUTR.CrashReport.Models/MonoModDetoursModel.cs b/src/BUTR.CrashReport.Models/MonoModPatchesModel.cs similarity index 85% rename from src/BUTR.CrashReport.Models/MonoModDetoursModel.cs rename to src/BUTR.CrashReport.Models/MonoModPatchesModel.cs index 75d151b..51006a7 100644 --- a/src/BUTR.CrashReport.Models/MonoModDetoursModel.cs +++ b/src/BUTR.CrashReport.Models/MonoModPatchesModel.cs @@ -6,7 +6,7 @@ namespace BUTR.CrashReport.Models; /// /// Represents the list of MonoMod detours for a given original method. /// -public sealed record MonoModDetoursModel +public sealed class MonoModPatchesModel { /// /// The original method type name. @@ -21,7 +21,7 @@ public sealed record MonoModDetoursModel /// /// The list of MonoMod detours. /// - public required IList Detours { get; set; } = new List(); + public required IList Detours { get; set; } = new List(); /// /// diff --git a/src/BUTR.CrashReport.Models/NativeAssemblyArchitectureType.cs b/src/BUTR.CrashReport.Models/NativeAssemblyArchitectureType.cs index 395c5e5..b92d9d8 100644 --- a/src/BUTR.CrashReport.Models/NativeAssemblyArchitectureType.cs +++ b/src/BUTR.CrashReport.Models/NativeAssemblyArchitectureType.cs @@ -1,40 +1,5 @@ namespace BUTR.CrashReport.Models; -/// -/// Represents the architecture of a native assembly. -/// -public enum AssemblyArchitectureType -{ - /// - /// Unknown architecture. - /// - Unknown = 0, - - /// - /// - /// - MSIL = 1, - - /// - /// - /// - X86 = 2, - - /// - /// - /// - IA64 = 3, - - /// - /// - /// - Amd64 = 4, - - /// - /// - /// - Arm = 5, -} /// /// Represents the architecture of a native assembly. /// diff --git a/src/BUTR.CrashReport.Models/RuntimePatchModel.cs b/src/BUTR.CrashReport.Models/RuntimePatchModel.cs new file mode 100644 index 0000000..6b8bd33 --- /dev/null +++ b/src/BUTR.CrashReport.Models/RuntimePatchModel.cs @@ -0,0 +1,35 @@ +using System.Collections.Generic; + +namespace BUTR.CrashReport.Models; + +public sealed class RuntimePatchModel +{ + /// + /// Is null if not from a module. + /// + /// + public required string? ModuleId { get; set; } + + /// + /// Is null if not from a module. + /// + /// + public required string? LoaderPluginId { get; set; } + + /// + /// The of the assembly that contains the patch. + /// + public required AssemblyIdModel? AssemblyId { get; set; } + + public required string Provider { get; set; } + + public required string Type { get; set; } + + public required string FullName { get; set; } + + /// + /// + /// + /// + public required IList AdditionalMetadata { get; set; } = new List(); +} \ No newline at end of file diff --git a/src/BUTR.CrashReport.Models/RuntimePatchesModel.cs b/src/BUTR.CrashReport.Models/RuntimePatchesModel.cs new file mode 100644 index 0000000..1553216 --- /dev/null +++ b/src/BUTR.CrashReport.Models/RuntimePatchesModel.cs @@ -0,0 +1,27 @@ +using System.Collections.Generic; + +namespace BUTR.CrashReport.Models; + +public sealed class RuntimePatchesModel +{ + /// + /// The original method type name. + /// + public required string? OriginalMethodDeclaredTypeName { get; set; } + + /// + /// The original method name. + /// + public required string? OriginalMethodName { get; set; } + + /// + /// The list of MonoMod detours. + /// + public required IList Patches { get; set; } = new List(); + + /// + /// + /// + /// + public required IList AdditionalMetadata { get; set; } = new List(); +} \ No newline at end of file diff --git a/src/BUTR.CrashReport.Models/UpdateInfoModuleOrLoaderPlugin.cs b/src/BUTR.CrashReport.Models/UpdateInfo.cs similarity index 89% rename from src/BUTR.CrashReport.Models/UpdateInfoModuleOrLoaderPlugin.cs rename to src/BUTR.CrashReport.Models/UpdateInfo.cs index 9c84466..16555dd 100644 --- a/src/BUTR.CrashReport.Models/UpdateInfoModuleOrLoaderPlugin.cs +++ b/src/BUTR.CrashReport.Models/UpdateInfo.cs @@ -3,7 +3,7 @@ /// /// Represents the module or loader plugin update information. /// -public sealed record UpdateInfoModuleOrLoaderPlugin +public sealed record UpdateInfo { /// /// The provider of the update. Can be 'NexusMods' or 'GitHub', for example. @@ -19,7 +19,7 @@ public sealed record UpdateInfoModuleOrLoaderPlugin public override string ToString() => $"{Provider}:{Value}"; /// - public bool Equals(UpdateInfoModuleOrLoaderPlugin? other) + public bool Equals(UpdateInfo? other) { if (ReferenceEquals(null, other)) return false; if (ReferenceEquals(this, other)) return true; diff --git a/src/BUTR.CrashReport.Renderer.Html/CrashReportHtml.Html.cs b/src/BUTR.CrashReport.Renderer.Html/CrashReportHtml.Html.cs index 00780de..468452f 100644 --- a/src/BUTR.CrashReport.Renderer.Html/CrashReportHtml.Html.cs +++ b/src/BUTR.CrashReport.Renderer.Html/CrashReportHtml.Html.cs @@ -8,7 +8,7 @@ namespace BUTR.CrashReport.Renderer.Html; partial class CrashReportHtml { #pragma warning disable format // @formatter:off - private static string GetBase(CrashReportModel crashReport, IEnumerable files) + private static string GetBase(CrashReportModel crashReport, IEnumerable files) { var sbMetadata = new StringBuilder(); foreach (var metadata in crashReport.Metadata.AdditionalMetadata) diff --git a/src/BUTR.CrashReport.Renderer.Html/CrashReportHtml.cs b/src/BUTR.CrashReport.Renderer.Html/CrashReportHtml.cs index 41fd53d..6e8c81e 100644 --- a/src/BUTR.CrashReport.Renderer.Html/CrashReportHtml.cs +++ b/src/BUTR.CrashReport.Renderer.Html/CrashReportHtml.cs @@ -82,7 +82,7 @@ public static string AddData(string htmlReport, string gzipBase64CrashReportJson return htmlReport; } - public static string Build(CrashReportModel crashReportModel, IEnumerable files) => GetBase(crashReportModel, files); + public static string Build(CrashReportModel crashReportModel, IEnumerable files) => GetBase(crashReportModel, files); private static string GetRecursiveExceptionHtml(CrashReportModel crashReport, ExceptionModel? ex) { @@ -143,7 +143,6 @@ private static string GetEnhancedStacktraceHtml(CrashReportModel crashReport) .AppendIf(moduleId2 != "UNKNOWN", sb => sb.Append("Module Id: ").Append("").Append(moduleId2).Append("").Append("
      ")) .AppendIf(pluginId2 != "UNKNOWN", sb => sb.Append("Plugin Id: ").Append("").Append(pluginId2).Append("").Append("
      ")) .Append("Method: ").Append(stacktrace.ExecutingMethod.MethodFullDescription.EscapeGenerics()).Append("
      ") - .Append("Method From Stackframe Issue: ").Append(stacktrace.MethodFromStackframeIssue).Append("
      ") .Append("Approximate IL Offset: ").Append(stacktrace.ILOffset is not null ? $"{stacktrace.ILOffset:X4}" : "UNKNOWN").Append("
      ") .Append("Native Offset: ").Append(stacktrace.NativeOffset is not null ? $"{stacktrace.NativeOffset:X4}" : "UNKNOWN").Append("
      ") .AppendIf(stacktrace.ExecutingMethod.ILInstructions.Count > 0, sp => sp @@ -288,7 +287,7 @@ void AppendDependencies(ModuleModel module) { var hasVersion = !string.IsNullOrEmpty(dependentModule.Version); var hasVersionRange = !string.IsNullOrEmpty(dependentModule.VersionRange); - if (dependentModule.Type == DependencyMetadataModelType.Incompatible) + if (dependentModule.Type == DependencyMetadataType.Incompatible) { deps[dependentModule.ModuleOrPluginId] = tmp.Clear() .Append("Incompatible ") @@ -300,7 +299,7 @@ void AppendDependencies(ModuleModel module) .AppendIf(hasVersionRange, dependentModule.VersionRange) .ToString(); } - else if (dependentModule.Type == DependencyMetadataModelType.LoadAfter) + else if (dependentModule.Type == DependencyMetadataType.LoadAfter) { deps[dependentModule.ModuleOrPluginId] = tmp.Clear() .Append("Load ").Append("After ") @@ -312,7 +311,7 @@ void AppendDependencies(ModuleModel module) .AppendIf(hasVersionRange, dependentModule.VersionRange) .ToString(); } - else if (dependentModule.Type == DependencyMetadataModelType.LoadBefore) + else if (dependentModule.Type == DependencyMetadataType.LoadBefore) { deps[dependentModule.ModuleOrPluginId] = tmp.Clear() .Append("Load ").Append("Before ") @@ -392,12 +391,12 @@ void AppendAdditionalAssemblies(ModuleModel module) _ => "modules-container", }; var additionalUpdateInfo = module.AdditionalMetadata.FirstOrDefault(x => x.Key == "AdditionalUpdateInfos")?.Value.Split(';').Select(x => x.Split(':') is { Length: 2 } split - ? new UpdateInfoModuleOrLoaderPlugin + ? new UpdateInfo { Provider = split[0], Value = split[1], } - : null).OfType().ToArray() ?? []; + : null).OfType().ToArray() ?? []; var hasDependencies = dependenciesBuilder.Length != 0; var hasUrl = !string.IsNullOrWhiteSpace(module.Url); var hasUpdateInfo = module.UpdateInfo is not null; @@ -467,12 +466,12 @@ private static string GetLoadedPluginsHtml(CrashReportModel crashReport) { var container = "modules-container"; var additionalUpdateInfo = plugin.AdditionalMetadata.FirstOrDefault(x => x.Key == "AdditionalUpdateInfos")?.Value.Split(';').Select(x => x.Split(':') is { Length: 2 } split - ? new UpdateInfoModuleOrLoaderPlugin + ? new UpdateInfo { Provider = split[0], Value = split[1], } - : null).OfType().ToArray() ?? []; + : null).OfType().ToArray() ?? []; var hasUpdateInfo = plugin.UpdateInfo is not null; var hasAdditionalUpdateInfo = additionalUpdateInfo.Length > 0; moduleBuilder.Append("
    • ") @@ -506,18 +505,18 @@ void AppendAssembly(AssemblyModel assembly) { var @class = string.Join(" ", assembly.Type.GetFlags().Select(x => x switch { - AssemblyModelType.Dynamic => "dynamic_assembly", - AssemblyModelType.GAC => "gac_assembly", - AssemblyModelType.System => "sys_assembly", - AssemblyModelType.GameCore => "game_assembly", - AssemblyModelType.GameModule => "game_module_assembly", - AssemblyModelType.Module => "module_assembly", - AssemblyModelType.Loader => "loader_assembly", - AssemblyModelType.LoaderPlugin => "loader_plugin_assembly", - _ when assembly.Type == AssemblyModelType.Unclassified => "unclas_assembly", + AssemblyType.Dynamic => "dynamic_assembly", + AssemblyType.GAC => "gac_assembly", + AssemblyType.System => "sys_assembly", + AssemblyType.GameCore => "game_assembly", + AssemblyType.GameModule => "game_module_assembly", + AssemblyType.Module => "module_assembly", + AssemblyType.Loader => "loader_assembly", + AssemblyType.LoaderPlugin => "loader_plugin_assembly", + _ when assembly.Type == AssemblyType.Unclassified => "unclas_assembly", _ => string.Empty })); - var isDynamic = assembly.Type.HasFlag(AssemblyModelType.Dynamic); + var isDynamic = assembly.Type.HasFlag(AssemblyType.Dynamic); var hasPath = assembly.AnonymizedPath != "EMPTY" && !string.IsNullOrWhiteSpace(assembly.AnonymizedPath); sb0.Append("
    • ") .Append(assembly.Id.Name).Append(", ") @@ -566,6 +565,7 @@ private static string GetHarmonyPatchesHtml(CrashReportModel crashReport) var patchesBuilder = new StringBuilder(); var patchBuilder = new StringBuilder(); + /* void AppendPatches(string name, IEnumerable patches) { patchBuilder.Clear(); @@ -594,7 +594,9 @@ void AppendPatches(string name, IEnumerable patches) patchesBuilder.Append("
    • ").Append(name).Append("
        ").Append(patchBuilder).Append("
      ").Append("
    • "); } } + */ + /* TODO: harmonyPatchesListBuilder.Append("
        "); foreach (var harmonyPatch in crashReport.HarmonyPatches) { @@ -617,11 +619,12 @@ void AppendPatches(string name, IEnumerable patches) } } harmonyPatchesListBuilder.Append("
      "); + */ return harmonyPatchesListBuilder.ToString(); } - private static string GetLogFilesHtml(IEnumerable files) + private static string GetLogFilesHtml(IEnumerable files) { var sb = new StringBuilder(); diff --git a/src/BUTR.CrashReport.Renderer.ImGui/CrashReportImGui.cs b/src/BUTR.CrashReport.Renderer.ImGui/CrashReportImGui.cs index 9c86239..79d9297 100644 --- a/src/BUTR.CrashReport.Renderer.ImGui/CrashReportImGui.cs +++ b/src/BUTR.CrashReport.Renderer.ImGui/CrashReportImGui.cs @@ -26,25 +26,30 @@ static CrashReportImGui() GlfwWindowing.RegisterPlatform(); } - public static void ShowAndWait(Exception exception, IList logSources, Dictionary additionalMetadata, + public static void ShowAndWait(Exception exception, IList logSources, Dictionary additionalMetadata, ICrashReportMetadataProvider crashReportMetadataProvider, IStacktraceFilter stacktraceFilter, IAssemblyUtilities assemblyUtilities, IModuleProvider moduleProvider, ILoaderPluginProvider loaderPluginProvider, + IPatchProvider patchProvider, + /* + ICommonProvider commonProvider, + IMonoModProvider monoModProvider, IHarmonyProvider harmonyProvider, + */ IModelConverter modelConverter, IPathAnonymizer pathAnonymizer, ICrashReportRendererUtilities crashReportRendererUtilities) { - var crashReport = CrashReportInfo.Create(exception, additionalMetadata, stacktraceFilter, assemblyUtilities, moduleProvider, loaderPluginProvider, harmonyProvider); + var crashReport = CrashReportInfo.Create(exception, additionalMetadata, stacktraceFilter, assemblyUtilities, moduleProvider, loaderPluginProvider, patchProvider); var crashReportModel = CrashReportInfo.ToModel(crashReport, crashReportMetadataProvider, modelConverter, moduleProvider, loaderPluginProvider, assemblyUtilities, pathAnonymizer); ShowAndWait(crashReportModel, logSources, crashReportRendererUtilities); } - public static void ShowAndWait(CrashReportModel crashReportModel, IList logSources, ICrashReportRendererUtilities crashReportRendererUtilities) + public static void ShowAndWait(CrashReportModel crashReportModel, IList logSources, ICrashReportRendererUtilities crashReportRendererUtilities) { if (PathResolver.Default is DefaultPathResolver pr) pr.Resolvers = [path => crashReportRendererUtilities.GetNativeLibrariesFolderPath().Select(x => Path.Combine(x, path))]; diff --git a/src/BUTR.CrashReport.Renderer.ImGui/Extensions/AssemblyModelTypeExtensions.cs b/src/BUTR.CrashReport.Renderer.ImGui/Extensions/AssemblyModelTypeExtensions.cs index 57dd960..6f05895 100644 --- a/src/BUTR.CrashReport.Renderer.ImGui/Extensions/AssemblyModelTypeExtensions.cs +++ b/src/BUTR.CrashReport.Renderer.ImGui/Extensions/AssemblyModelTypeExtensions.cs @@ -9,5 +9,5 @@ internal static class AssemblyModelTypeExtensions private const MethodImplOptions AggressiveOptimization = (MethodImplOptions) 512; [MethodImpl(MethodImplOptions.AggressiveInlining | AggressiveOptimization)] - public static bool IsSet(this AssemblyModelType self, AssemblyModelType flag) => (self & flag) == flag; + public static bool IsSet(this AssemblyType self, AssemblyType flag) => (self & flag) == flag; } \ No newline at end of file diff --git a/src/BUTR.CrashReport.Renderer.ImGui/ICrashReportRendererUtilities.cs b/src/BUTR.CrashReport.Renderer.ImGui/ICrashReportRendererUtilities.cs index e06b18c..5a56d53 100644 --- a/src/BUTR.CrashReport.Renderer.ImGui/ICrashReportRendererUtilities.cs +++ b/src/BUTR.CrashReport.Renderer.ImGui/ICrashReportRendererUtilities.cs @@ -1,19 +1,37 @@ -using BUTR.CrashReport.Models; +using System; +using BUTR.CrashReport.Models; using System.Collections.Generic; namespace BUTR.CrashReport.Renderer.ImGui; +[Flags] +public enum CrashReportRendererCapabilities +{ + None = 0, + SaveAsHtml = 1 << 0, + SaveAsZip = 1 << 1, + CopyAsHtml = 1 << 2, + Upload = 1 << 3, + HasSaveFiles = 1 << 4, + HasScreenshots = 1 << 5, + HasMiniDump = 1 << 6, + PluginLoader = 1 << 7, + Logs = 1 << 8, +} + public interface ICrashReportRendererUtilities { + CrashReportRendererCapabilities Capabilities { get; } + IEnumerable GetNativeLibrariesFolderPath(); - void Upload(CrashReportModel crashReport, ICollection logSources); + void Upload(CrashReportModel crashReport, ICollection logSources); - void CopyAsHtml(CrashReportModel crashReport, ICollection logSources); + void CopyAsHtml(CrashReportModel crashReport, ICollection logSources); // TODO: Right now we rely on the implementation to provide a dialog and do the save // We should instead do the dialog within ImGui and then provide here the path/stream of where to save the file - void SaveCrashReportAsHtml(CrashReportModel crashReport, ICollection logSources, bool addMiniDump, bool addLatestSave, bool addScreenshots); - void SaveCrashReportAsZip(CrashReportModel crashReport, ICollection logSources); + void SaveAsHtml(CrashReportModel crashReport, ICollection logSources, bool addMiniDump, bool addLatestSave, bool addScreenshots); + void SaveAsZip(CrashReportModel crashReport, ICollection logSources); } \ No newline at end of file diff --git a/src/BUTR.CrashReport.Renderer.ImGui/ImGui/CmGui.Methods.cs b/src/BUTR.CrashReport.Renderer.ImGui/ImGui/CmGui.Methods.cs index 0f687c8..ec04efe 100644 --- a/src/BUTR.CrashReport.Renderer.ImGui/ImGui/CmGui.Methods.cs +++ b/src/BUTR.CrashReport.Renderer.ImGui/ImGui/CmGui.Methods.cs @@ -53,6 +53,16 @@ public void Text(ReadOnlySpan fmt) igText(fmtPtr); } } + + + [MethodImpl(MethodImplOptions.AggressiveInlining | AggressiveOptimization)] + public void TextSameLine(bool fmt) + { + var @true = "true\0"u8; + var @false = "false\0"u8; + Text(fmt ? @true : @false); + SameLine(0, 0); + } [MethodImpl(MethodImplOptions.AggressiveInlining | AggressiveOptimization)] public void TextSameLine(int value) diff --git a/src/BUTR.CrashReport.Renderer.ImGui/Renderer/ImGuiRenderer.01.Summary.cs b/src/BUTR.CrashReport.Renderer.ImGui/Renderer/ImGuiRenderer.01.Summary.cs index 0893ab1..c3166b5 100644 --- a/src/BUTR.CrashReport.Renderer.ImGui/Renderer/ImGuiRenderer.01.Summary.cs +++ b/src/BUTR.CrashReport.Renderer.ImGui/Renderer/ImGuiRenderer.01.Summary.cs @@ -17,6 +17,8 @@ partial class ImGuiRenderer private void RenderSummary() { + var capabilities = _crashReportRendererUtilities.Capabilities; + if (_imgui.BeginTable("Buttons\0"u8, 2)) { _imgui.TableNextColumn(); @@ -25,44 +27,71 @@ private void RenderSummary() _imgui.SetWindowFontScale(1); _imgui.TableNextColumn(); - if (_imgui.Button("Save Report as HTML\0"u8)) _crashReportRendererUtilities.SaveCrashReportAsHtml(_crashReport, _logSources, _addMiniDump, _addLatestSave, _addScreenshots); - _imgui.SameLine(); - if (_imgui.Button("Save Report as ZIP\0"u8)) _crashReportRendererUtilities.SaveCrashReportAsZip(_crashReport, _logSources); - _imgui.SameLine(); + if (capabilities.HasFlag(CrashReportRendererCapabilities.SaveAsHtml)) + { + if (_imgui.Button("Save Report as HTML\0"u8)) _crashReportRendererUtilities.SaveAsHtml(_crashReport, _logSources, _addMiniDump, _addLatestSave, _addScreenshots); + _imgui.SameLine(); + } + if (capabilities.HasFlag(CrashReportRendererCapabilities.SaveAsZip)) + { + if (_imgui.Button("Save Report as ZIP\0"u8)) _crashReportRendererUtilities.SaveAsZip(_crashReport, _logSources); + _imgui.SameLine(); + } if (_imgui.Button("Close Report and Continue\0"u8, in Secondary, in Secondary2, in Secondary3)) _onClose(); _imgui.TableNextColumn(); _imgui.TableNextColumn(); - if (_imgui.Button("Copy as HTML\0"u8)) _crashReportRendererUtilities.CopyAsHtml(_crashReport, _logSources); - _imgui.SameLine(); - if (_imgui.Button("Upload Report as Permalink\0"u8)) _crashReportRendererUtilities.Upload(_crashReport, _logSources); - _imgui.TableNextColumn(); - _imgui.TableNextColumn(); - - _imgui.Text("Save Report as HTML Options:\0"u8); - _imgui.TableNextColumn(); - _imgui.TableNextColumn(); - - _imgui.Checkbox("Include Screenshot\0"u8, ref _addScreenshots); - _imgui.TableNextColumn(); - _imgui.TableNextColumn(); - - _imgui.Checkbox("Include Latest Save File\0"u8, ref _addLatestSave); - _imgui.TableNextColumn(); - _imgui.TableNextColumn(); - - _imgui.Checkbox("Include Mini Dump\0"u8, ref _addMiniDump); + if (capabilities.HasFlag(CrashReportRendererCapabilities.CopyAsHtml)) + { + if (_imgui.Button("Copy as HTML\0"u8)) _crashReportRendererUtilities.CopyAsHtml(_crashReport, _logSources); + _imgui.SameLine(); + } + if (capabilities.HasFlag(CrashReportRendererCapabilities.Upload)) + { + if (_imgui.Button("Upload Report as Permalink\0"u8)) _crashReportRendererUtilities.Upload(_crashReport, _logSources); + } + if (capabilities.HasFlag(CrashReportRendererCapabilities.CopyAsHtml | CrashReportRendererCapabilities.Upload)) + { + _imgui.TableNextColumn(); + _imgui.TableNextColumn(); + } + + if (capabilities.HasFlag(CrashReportRendererCapabilities.HasScreenshots | CrashReportRendererCapabilities.HasSaveFiles | CrashReportRendererCapabilities.HasMiniDump)) + { + _imgui.Text("Save Report as HTML Options:\0"u8); + _imgui.TableNextColumn(); + _imgui.TableNextColumn(); + } + + if (capabilities.HasFlag(CrashReportRendererCapabilities.HasScreenshots)) + { + _imgui.Checkbox("Include Screenshot\0"u8, ref _addScreenshots); + _imgui.TableNextColumn(); + _imgui.TableNextColumn(); + } + + if (capabilities.HasFlag(CrashReportRendererCapabilities.HasSaveFiles)) + { + _imgui.Checkbox("Include Latest Save File\0"u8, ref _addLatestSave); + _imgui.TableNextColumn(); + _imgui.TableNextColumn(); + } + + if (capabilities.HasFlag(CrashReportRendererCapabilities.HasMiniDump)) + { + _imgui.Checkbox("Include Mini Dump\0"u8, ref _addMiniDump); + } _imgui.EndTable(); } - _imgui.Text("Clicking 'Close Report and Continue' will continue with the Game's error report mechanism.\0"u8); + _imgui.Text("Clicking 'Close Report and Continue' will continue with the Game's error reporting mechanism.\0"u8); _imgui.Separator(); _imgui.NewLine(); _imgui.SetWindowFontScale(2); _imgui.TextSameLine(_crashReport.Metadata.GameName ?? string.Empty); - _imgui.Text(" has encountered a problem and will close itself!\0"u8); + _imgui.Text(" has encountered a problem!\0"u8); _imgui.SetWindowFontScale(1); _imgui.NewLine(); diff --git a/src/BUTR.CrashReport.Renderer.ImGui/Renderer/ImGuiRenderer.03.EnhancedStacktrace.cs b/src/BUTR.CrashReport.Renderer.ImGui/Renderer/ImGuiRenderer.03.EnhancedStacktrace.cs index 5307a3b..e91135b 100644 --- a/src/BUTR.CrashReport.Renderer.ImGui/Renderer/ImGuiRenderer.03.EnhancedStacktrace.cs +++ b/src/BUTR.CrashReport.Renderer.ImGui/Renderer/ImGuiRenderer.03.EnhancedStacktrace.cs @@ -19,20 +19,20 @@ private class IntEqualityComparer : IEqualityComparer public bool Equals(int x, int y) => x == y; public int GetHashCode(int obj) => obj; } - private class MethodSimpleEqualityComparer : IEqualityComparer + private class MethodSimpleEqualityComparer : IEqualityComparer { public static MethodSimpleEqualityComparer Instance { get; } = new(); - public bool Equals(MethodSimple? x, MethodSimple? y) => ReferenceEquals(x, y); // We can just reference compare here - public int GetHashCode(MethodSimple obj) => obj.GetHashCode(); + public bool Equals(MethodSimpleModel? x, MethodSimpleModel? y) => ReferenceEquals(x, y); // We can just reference compare here + public int GetHashCode(MethodSimpleModel obj) => obj.GetHashCode(); } private enum CodeType { IL = 0, CSharpILMixed = 1, CSharp = 2, Native = 3 } - private readonly Dictionary _methodIdUtf8 = new(MethodSimpleEqualityComparer.Instance); - private readonly Dictionary _methodCodeLinesUtf8 = new(MethodSimpleEqualityComparer.Instance); - private readonly Dictionary _methodCodeLineCount = new(MethodSimpleEqualityComparer.Instance); + private readonly Dictionary _methodIdUtf8 = new(MethodSimpleEqualityComparer.Instance); + private readonly Dictionary _methodCodeLinesUtf8 = new(MethodSimpleEqualityComparer.Instance); + private readonly Dictionary _methodCodeLineCount = new(MethodSimpleEqualityComparer.Instance); private readonly Dictionary _offsetsUtf8 = new(IntEqualityComparer.Instance); - private static void SetCodeDictionary(IDictionary methodDict, MethodSimple key, CodeType codeType, TValue value) + private static void SetCodeDictionary(IDictionary methodDict, MethodSimpleModel key, CodeType codeType, TValue value) { if (!methodDict.TryGetValue(key, out var codeArray)) methodDict[key] = (codeArray = new TValue[(int) CodeType.Native + 1]); @@ -49,7 +49,7 @@ string GetLabelId(string name) return $"{name}##{colapsingHeaderId}"; } - void SetupCodeExecuting(MethodExecuting? method) + void SetupCodeExecuting(MethodExecutingModel? method) { if (method is null) return; @@ -59,7 +59,7 @@ void SetupCodeExecuting(MethodExecuting? method) SetCodeDictionary(_methodCodeLinesUtf8, method, CodeType.Native, UnsafeHelper.ToUtf8Array(string.Join("\n", method.NativeInstructions).Trim('\n'))); SetCodeDictionary(_methodCodeLineCount, method, CodeType.Native, method.NativeInstructions.Count); } - void SetupCode(MethodSimple? method) + void SetupCode(MethodSimpleModel? method) { if (method is null) return; @@ -94,7 +94,7 @@ void SetupCode(MethodSimple? method) } } - private void RenderMethodLines(MethodSimple method, CodeType codeType) + private void RenderMethodLines(MethodSimpleModel method, CodeType codeType) { var id = _methodIdUtf8[method][Unsafe.As(ref codeType)]; var lines = _methodCodeLinesUtf8[method][Unsafe.As(ref codeType)]; @@ -118,14 +118,14 @@ private void RenderMethodLines(MethodSimple method, CodeType codeType) _imgui.TreePop(); } } - private void RenderCodeExecuting(MethodExecuting? method) + private void RenderCodeExecuting(MethodExecutingModel? method) { if (method is null) return; RenderCode(method); RenderMethodLines(method, CodeType.Native); } - private void RenderCode(MethodSimple? method) + private void RenderCode(MethodSimpleModel? method) { if (method is null) return; @@ -154,8 +154,6 @@ private void RenderEnhancedStacktrace() if (moduleId1 != "UNKNOWN") _imgui.RenderId("Module Id:\0"u8, moduleId1); if (pluginId1 != "UNKNOWN") _imgui.RenderId("Plugin Id:\0"u8, pluginId1); - _imgui.TextSameLine("Method From Stackframe Issue: \0"u8); - _imgui.Text(stacktrace.MethodFromStackframeIssue); _imgui.TextSameLine("Approximate IL Offset: \0"u8); _imgui.Text(stacktrace.ILOffset is not null ? _offsetsUtf8[stacktrace.ILOffset.Value] : "UNKNOWN\0"u8); _imgui.TextSameLine("Native Offset: \0"u8); diff --git a/src/BUTR.CrashReport.Renderer.ImGui/Renderer/ImGuiRenderer.05.InstalledModules.cs b/src/BUTR.CrashReport.Renderer.ImGui/Renderer/ImGuiRenderer.05.InstalledModules.cs index d956399..c4e8d85 100644 --- a/src/BUTR.CrashReport.Renderer.ImGui/Renderer/ImGuiRenderer.05.InstalledModules.cs +++ b/src/BUTR.CrashReport.Renderer.ImGui/Renderer/ImGuiRenderer.05.InstalledModules.cs @@ -39,12 +39,12 @@ private void InitializeInstalledModules() } var additionalUpdateInfo = module.AdditionalMetadata.FirstOrDefault(x => x.Key == "AdditionalUpdateInfos")?.Value.Split(';').Select(x => x.Split(':') is { Length: 2 } split - ? new UpdateInfoModuleOrLoaderPlugin + ? new UpdateInfo { Provider = split[0], Value = split[1], } - : null).OfType().ToArray() ?? []; + : null).OfType().ToArray() ?? []; _moduleAdditionalUpdateInfos[module.Id] = additionalUpdateInfo.Select(x => UnsafeHelper.ToUtf8Array(x.ToString())).ToArray(); for (var j = 0; j < module.DependencyMetadatas.Count; j++) @@ -68,7 +68,7 @@ private void RenderDependencies(ModuleModel module) for (var i = 0; i < module.DependencyMetadatas.Count; i++) { var dependentModule = module.DependencyMetadatas[i]; - var type = Clamp(dependentModule.Type, DependencyMetadataModelType.LoadBefore, DependencyMetadataModelType.Incompatible); + var type = Clamp(dependentModule.Type, DependencyMetadataType.LoadBefore, DependencyMetadataType.Incompatible); _imgui.Bullet(); _imgui.TextSameLine(_dependencyTypeNames[type]); _imgui.SmallButtonSameLine(dependentModule.ModuleOrPluginId); @@ -76,7 +76,7 @@ private void RenderDependencies(ModuleModel module) } } - private void RenderCapabilities(IList moduleCapabilities) + private void RenderCapabilities(IList moduleCapabilities) { if (moduleCapabilities.Count == 0) return; diff --git a/src/BUTR.CrashReport.Renderer.ImGui/Renderer/ImGuiRenderer.06.LoadedLoaderPlugins.cs b/src/BUTR.CrashReport.Renderer.ImGui/Renderer/ImGuiRenderer.06.LoadedLoaderPlugins.cs index 4e720be..1feceab 100644 --- a/src/BUTR.CrashReport.Renderer.ImGui/Renderer/ImGuiRenderer.06.LoadedLoaderPlugins.cs +++ b/src/BUTR.CrashReport.Renderer.ImGui/Renderer/ImGuiRenderer.06.LoadedLoaderPlugins.cs @@ -25,12 +25,12 @@ private void InitializeInstalledLoaderPlugins() } var additionalUpdateInfo = loaderPlugin.AdditionalMetadata.FirstOrDefault(x => x.Key == "AdditionalUpdateInfos")?.Value.Split(';').Select(x => x.Split(':') is { Length: 2 } split - ? new UpdateInfoModuleOrLoaderPlugin + ? new UpdateInfo { Provider = split[0], Value = split[1], } - : null).OfType().ToArray() ?? []; + : null).OfType().ToArray() ?? []; _loaderPluginIdAdditionalUpdateInfos[loaderPlugin.Id] = additionalUpdateInfo.Select(x => UnsafeHelper.ToUtf8Array(x.ToString())).ToArray(); } } diff --git a/src/BUTR.CrashReport.Renderer.ImGui/Renderer/ImGuiRenderer.07.Assemblies.cs b/src/BUTR.CrashReport.Renderer.ImGui/Renderer/ImGuiRenderer.07.Assemblies.cs index caac194..3855159 100644 --- a/src/BUTR.CrashReport.Renderer.ImGui/Renderer/ImGuiRenderer.07.Assemblies.cs +++ b/src/BUTR.CrashReport.Renderer.ImGui/Renderer/ImGuiRenderer.07.Assemblies.cs @@ -6,6 +6,7 @@ using ImGuiNET; using System.Collections.Generic; +using System.Linq; namespace BUTR.CrashReport.Renderer.ImGui.Renderer; @@ -28,6 +29,16 @@ private class AssemblyModelEqualityComparer : IEqualityComparer private static bool _hideDynamicAssemblies; private static bool _hideUnclassifiedAssemblies; + private static bool _hasSystemAssemblies; + private static bool _hasGACAssemblies; + private static bool _hasGameAssemblies; + private static bool _hasGameModulesAssemblies; + private static bool _hasModulesAssemblies; + private static bool _hasLoaderAssemblies; + private static bool _hasLoaderPluginsAssemblies; + private static bool _hasDynamicAssemblies; + private static bool _hasUnclassifiedAssemblies; + private readonly Dictionary _assemblyFullNameUtf8 = new(AssemblyModelEqualityComparer.Instance); private static readonly byte[][] _architectureTypeNames = @@ -47,37 +58,47 @@ private void InitializeAssemblies() var assembly = _crashReport.Assemblies[i]; _assemblyFullNameUtf8[assembly] = UnsafeHelper.ToUtf8Array(assembly.GetFullName()); } + + _hasSystemAssemblies = _crashReport.Assemblies.Any(x => x.Type.IsSet(AssemblyType.System)); + _hasGACAssemblies = _crashReport.Assemblies.Any(x => x.Type.IsSet(AssemblyType.GAC)); + _hasGameAssemblies = _crashReport.Assemblies.Any(x => x.Type.IsSet(AssemblyType.GameCore)); + _hasGameModulesAssemblies = _crashReport.Assemblies.Any(x => x.Type.IsSet(AssemblyType.GameModule)); + _hasModulesAssemblies = _crashReport.Assemblies.Any(x => x.Type.IsSet(AssemblyType.Module)); + _hasLoaderAssemblies = _crashReport.Assemblies.Any(x => x.Type.IsSet(AssemblyType.Loader)); + _hasLoaderPluginsAssemblies = _crashReport.Assemblies.Any(x => x.Type.IsSet(AssemblyType.LoaderPlugin)); + _hasDynamicAssemblies = _crashReport.Assemblies.Any(x => x.Type.IsSet(AssemblyType.Dynamic)); + _hasUnclassifiedAssemblies = _crashReport.Assemblies.Any(x => x.Type == AssemblyType.Unclassified); } private void RenderAssemblies() { _imgui.PushStyleVar(ImGuiStyleVar.FrameBorderSize, 1); _imgui.TextSameLine("Hide: \0"u8); - _imgui.CheckboxSameLine(" System | \0"u8, ref _hideSystemAssemblies); - _imgui.CheckboxSameLine(" GAC | \0"u8, ref _hideGACAssemblies); - _imgui.CheckboxSameLine(" Game | \0"u8, ref _hideGameAssemblies); - _imgui.CheckboxSameLine(" Game Modules | \0"u8, ref _hideGameModulesAssemblies); - _imgui.CheckboxSameLine(" Modules | \0"u8, ref _hideModulesAssemblies); - _imgui.CheckboxSameLine(" Loader | \0"u8, ref _hideLoaderAssemblies); - _imgui.CheckboxSameLine(" Loader Plugins | \0"u8, ref _hideLoaderPluginsAssemblies); - _imgui.CheckboxSameLine(" Dynamic | \0"u8, ref _hideDynamicAssemblies); - _imgui.Checkbox(" Unclassified \0"u8, ref _hideUnclassifiedAssemblies); + if (_hasSystemAssemblies) _imgui.CheckboxSameLine(" System | \0"u8, ref _hideSystemAssemblies); + if (_hasGACAssemblies) _imgui.CheckboxSameLine(" GAC | \0"u8, ref _hideGACAssemblies); + if (_hasGameAssemblies) _imgui.CheckboxSameLine(" Game | \0"u8, ref _hideGameAssemblies); + if (_hasGameModulesAssemblies) _imgui.CheckboxSameLine(" Game Modules | \0"u8, ref _hideGameModulesAssemblies); + if (_hasModulesAssemblies) _imgui.CheckboxSameLine(" Modules | \0"u8, ref _hideModulesAssemblies); + if (_hasLoaderAssemblies) _imgui.CheckboxSameLine(" Loader | \0"u8, ref _hideLoaderAssemblies); + if (_hasLoaderPluginsAssemblies) _imgui.CheckboxSameLine(" Loader Plugins | \0"u8, ref _hideLoaderPluginsAssemblies); + if (_hasDynamicAssemblies) _imgui.CheckboxSameLine(" Dynamic | \0"u8, ref _hideDynamicAssemblies); + if (_hasUnclassifiedAssemblies) _imgui.Checkbox(" Unclassified \0"u8, ref _hideUnclassifiedAssemblies); _imgui.PopStyleVar(); for (var i = 0; i < _crashReport.Assemblies.Count; i++) { var assembly = _crashReport.Assemblies[i]; - if (_hideSystemAssemblies && assembly.Type.IsSet(AssemblyModelType.System)) continue; - if (_hideGACAssemblies && assembly.Type.IsSet(AssemblyModelType.GAC)) continue; - if (_hideGameAssemblies && assembly.Type.IsSet(AssemblyModelType.GameCore)) continue; - if (_hideGameModulesAssemblies && assembly.Type.IsSet(AssemblyModelType.GameModule)) continue; - if (_hideModulesAssemblies && assembly.Type.IsSet(AssemblyModelType.Module)) continue; - if (_hideLoaderAssemblies && assembly.Type.IsSet(AssemblyModelType.Loader)) continue; - if (_hideLoaderPluginsAssemblies && assembly.Type.IsSet(AssemblyModelType.LoaderPlugin)) continue; - if (_hideDynamicAssemblies && assembly.Type.IsSet(AssemblyModelType.Dynamic)) continue; - if (_hideUnclassifiedAssemblies && assembly.Type == AssemblyModelType.Unclassified) continue; + if (_hideSystemAssemblies && assembly.Type.IsSet(AssemblyType.System)) continue; + if (_hideGACAssemblies && assembly.Type.IsSet(AssemblyType.GAC)) continue; + if (_hideGameAssemblies && assembly.Type.IsSet(AssemblyType.GameCore)) continue; + if (_hideGameModulesAssemblies && assembly.Type.IsSet(AssemblyType.GameModule)) continue; + if (_hideModulesAssemblies && assembly.Type.IsSet(AssemblyType.Module)) continue; + if (_hideLoaderAssemblies && assembly.Type.IsSet(AssemblyType.Loader)) continue; + if (_hideLoaderPluginsAssemblies && assembly.Type.IsSet(AssemblyType.LoaderPlugin)) continue; + if (_hideDynamicAssemblies && assembly.Type.IsSet(AssemblyType.Dynamic)) continue; + if (_hideUnclassifiedAssemblies && assembly.Type == AssemblyType.Unclassified) continue; - var isDynamic = assembly.Type.IsSet(AssemblyModelType.Dynamic); + var isDynamic = assembly.Type.IsSet(AssemblyType.Dynamic); var hasPath = assembly.AnonymizedPath != "EMPTY" && assembly.AnonymizedPath != "DYNAMIC" && !string.IsNullOrWhiteSpace(assembly.AnonymizedPath); _imgui.Bullet(); diff --git a/src/BUTR.CrashReport.Renderer.ImGui/Renderer/ImGuiRenderer.09.MonoModPatches.cs b/src/BUTR.CrashReport.Renderer.ImGui/Renderer/ImGuiRenderer.09.MonoModPatches.cs new file mode 100644 index 0000000..e5cff01 --- /dev/null +++ b/src/BUTR.CrashReport.Renderer.ImGui/Renderer/ImGuiRenderer.09.MonoModPatches.cs @@ -0,0 +1,115 @@ +/* +using BUTR.CrashReport.Models; +using BUTR.CrashReport.Renderer.ImGui.Extensions; +using BUTR.CrashReport.Renderer.ImGui.UnsafeUtils; +using BUTR.CrashReport.Renderer.ImGui.Utils; + +using HonkPerf.NET.Core; +using HonkPerf.NET.RefLinq; +using HonkPerf.NET.RefLinq.Enumerators; + +using ImGuiNET; + +using System; +using System.Collections.Generic; + +namespace BUTR.CrashReport.Renderer.ImGui.Renderer; + +partial class ImGuiRenderer +{ + private class MonoModPatchesModelEqualityComparer : IEqualityComparer + { + public static MonoModPatchesModelEqualityComparer Instance { get; } = new(); + public bool Equals(MonoModPatchesModel? x, MonoModPatchesModel? y) => ReferenceEquals(x, y); // We can just reference compare here + public int GetHashCode(MonoModPatchesModel obj) => obj.GetHashCode(); + } + + private class MonoModPatchModelEqualityComparer : IEqualityComparer + { + public static MonoModPatchModelEqualityComparer Instance { get; } = new(); + public bool Equals(MonoModPatchModel? x, MonoModPatchModel? y) => ReferenceEquals(x, y); // We can just reference compare here + public int GetHashCode(MonoModPatchModel obj) => obj.GetHashCode(); + } + + private readonly Dictionary _monoModMethodNameFullUtf8 = new(MonoModPatchesModelEqualityComparer.Instance); + private readonly Dictionary _monoModBeforeUtf8 = new(MonoModPatchModelEqualityComparer.Instance); + private readonly Dictionary _monoModAfterUtf8 = new(MonoModPatchModelEqualityComparer.Instance); + + private void InitializeMonoModPatches() + { + for (var i = 0; i < _crashReport.MonoModPatches.Count; i++) + { + var monoModPatch = _crashReport.MonoModPatches[i]; + var methodNameFull = !string.IsNullOrEmpty(monoModPatch.OriginalMethodDeclaredTypeName) + ? $"{monoModPatch.OriginalMethodDeclaredTypeName}.{monoModPatch.OriginalMethodName}" + : monoModPatch.OriginalMethodName ?? string.Empty; + _monoModMethodNameFullUtf8[monoModPatch] = UnsafeHelper.ToUtf8Array(methodNameFull); + + for (var j = 0; j < monoModPatch.Detours.Count; j++) + { + var detour = monoModPatch.Detours[j]; + for (var k = 0; k < detour.Before.Count; k++) + { + var before = detour.Before[k]; + _monoModBeforeUtf8[detour] = UnsafeHelper.ToUtf8Array(before); + } + for (var m = 0; m < detour.After.Count; m++) + { + var after = detour.After[m]; + _monoModAfterUtf8[detour] = UnsafeHelper.ToUtf8Array(after); + } + } + } + } + + private void RenderMonoModPatches(ReadOnlySpan name, RefLinqEnumerable, IListEnumerator>> patches) + { + foreach (var patch in patches) + { + var moduleId = patch.ModuleId ?? "UNKNOWN"; + var pluginId = patch.LoaderPluginId ?? "UNKNOWN"; + + _imgui.Bullet(); + _imgui.Text(name); + _imgui.Indent(); + + if (moduleId != "UNKNOWN") { _imgui.RenderId("Module Id:\0"u8, moduleId); _imgui.SameLine(0, 0); } + if (pluginId != "UNKNOWN") { _imgui.RenderId("Plugin Id:\0"u8, pluginId); _imgui.SameLine(0, 0); } + _imgui.TextSameLine(" Id: \0"u8); + _imgui.TextSameLine(patch.Id); + _imgui.TextSameLine(" Namespace: \0"u8); + _imgui.TextSameLine(patch.Namespace); + _imgui.TextSameLine(" IsActive: \0"u8); + _imgui.TextSameLine(patch.IsActive); + if (patch.Index is not null) { _imgui.TextSameLine(" Index: \0"u8); _imgui.TextSameLine(patch.Index.Value); } + if (patch.MaxIndex is not null) { _imgui.TextSameLine(" MaxIndex: \0"u8); _imgui.TextSameLine(patch.MaxIndex.Value); } + if (patch.GlobalIndex is not null) { _imgui.TextSameLine(" GlobalIndex: \0"u8); _imgui.TextSameLine(patch.GlobalIndex.Value); } + if (patch.Priority is not null) { _imgui.TextSameLine(" Priority: \0"u8); _imgui.TextSameLine(patch.Priority.Value); } + if (patch.SubPriority is not null) { _imgui.TextSameLine(" SubPriority: \0"u8); _imgui.TextSameLine(patch.SubPriority.Value); } + if (patch.Before.Count > 0) { _imgui.TextSameLine(" Before: \0"u8); _imgui.TextSameLine(_monoModBeforeUtf8[patch]); } + if (patch.After.Count > 0) { _imgui.TextSameLine(" After: \0"u8); _imgui.TextSameLine(_monoModAfterUtf8[patch]); } + _imgui.NewLine(); + + _imgui.Unindent(); + } + } + + private void RenderMonoModPatches() + { + for (var i = 0; i < _crashReport.MonoModPatches.Count; i++) + { + var monoModPatch = _crashReport.MonoModPatches[i]; + var methodNameFull = _monoModMethodNameFullUtf8[monoModPatch]; + + if (_imgui.TreeNode(methodNameFull, ImGuiTreeNodeFlags.DefaultOpen)) + { + RenderMonoModPatches("Detours\0"u8, monoModPatch.Detours.ToRefLinq().Where(x => x.Type == MonoModPatchModelType.Detour)); + RenderMonoModPatches("ILHooks\0"u8, monoModPatch.Detours.ToRefLinq().Where(x => x.Type == MonoModPatchModelType.ILHook)); + _imgui.NewLine(); + + _imgui.TreePop(); + } + } + } +} +*/ \ No newline at end of file diff --git a/src/BUTR.CrashReport.Renderer.ImGui/Renderer/ImGuiRenderer.09.RuntimePatches.cs b/src/BUTR.CrashReport.Renderer.ImGui/Renderer/ImGuiRenderer.09.RuntimePatches.cs new file mode 100644 index 0000000..eab2076 --- /dev/null +++ b/src/BUTR.CrashReport.Renderer.ImGui/Renderer/ImGuiRenderer.09.RuntimePatches.cs @@ -0,0 +1,109 @@ +using BUTR.CrashReport.Models; +using BUTR.CrashReport.Renderer.ImGui.Extensions; +using BUTR.CrashReport.Renderer.ImGui.UnsafeUtils; +using BUTR.CrashReport.Renderer.ImGui.Utils; + +using HonkPerf.NET.Core; +using HonkPerf.NET.RefLinq; +using HonkPerf.NET.RefLinq.Enumerators; + +using ImGuiNET; + +using System; +using System.Collections.Generic; +using System.Linq; + +namespace BUTR.CrashReport.Renderer.ImGui.Renderer; + +partial class ImGuiRenderer +{ + private class RuntimePatchesModelEqualityComparer : IEqualityComparer + { + public static RuntimePatchesModelEqualityComparer Instance { get; } = new(); + public bool Equals(RuntimePatchesModel? x, RuntimePatchesModel? y) => ReferenceEquals(x, y); // We can just reference compare here + public int GetHashCode(RuntimePatchesModel obj) => obj.GetHashCode(); + } + + private class RuntimePatchModelEqualityComparer : IEqualityComparer + { + public static RuntimePatchModelEqualityComparer Instance { get; } = new(); + public bool Equals(RuntimePatchModel? x, RuntimePatchModel? y) => ReferenceEquals(x, y); // We can just reference compare here + public int GetHashCode(RuntimePatchModel obj) => obj.GetHashCode(); + } + + private readonly List _runtimePatchProviders = new(); + private readonly List _runtimePatchTypes = new(); + private readonly Dictionary _runtimeMethodNameFullUtf8 = new(RuntimePatchesModelEqualityComparer.Instance); + + private void InitializeRuntimePatches() + { + for (var i = 0; i < _crashReport.RuntimePatches.Count; i++) + { + var runtimePatch = _crashReport.RuntimePatches[i]; + var methodNameFull = !string.IsNullOrEmpty(runtimePatch.OriginalMethodDeclaredTypeName) + ? $"{runtimePatch.OriginalMethodDeclaredTypeName}.{runtimePatch.OriginalMethodName}" + : runtimePatch.OriginalMethodName ?? string.Empty; + _runtimeMethodNameFullUtf8[runtimePatch] = UnsafeHelper.ToUtf8Array(methodNameFull); + } + + _runtimePatchProviders.AddRange(_crashReport.RuntimePatches.SelectMany(x => x.Patches).Select(x => x.Provider).Distinct()); + + _runtimePatchTypes.AddRange(_crashReport.RuntimePatches.SelectMany(x => x.Patches).Select(x => x.Type).Distinct()); + } + + private void RenderRuntimePatches(RefLinqEnumerable, IListEnumerator>> patches) + { + foreach (var patch in patches) + { + var moduleId = patch.ModuleId ?? "UNKNOWN"; + var pluginId = patch.LoaderPluginId ?? "UNKNOWN"; + + _imgui.Bullet(); + _imgui.TextSameLine(patch.Provider); + _imgui.TextSameLine(" \0"u8); + _imgui.TextSameLine(patch.Type); + _imgui.NewLine(); + _imgui.Indent(); + + if (moduleId != "UNKNOWN") { _imgui.RenderId("Module Id:\0"u8, moduleId); _imgui.SameLine(0, 0); } + if (pluginId != "UNKNOWN") { _imgui.RenderId("Plugin Id:\0"u8, pluginId); _imgui.SameLine(0, 0); } + + _imgui.TextSameLine(" Full Name: \0"u8); + _imgui.TextSameLine(patch.FullName); + + for (var i = 0; i < patch.AdditionalMetadata.Count; i++) + { + var metadata = patch.AdditionalMetadata[i]; + if (string.IsNullOrEmpty(metadata.Key) || string.IsNullOrEmpty(metadata.Value)) continue; + _imgui.TextSameLine(" \0"u8); + _imgui.TextSameLine(metadata.Key); + _imgui.TextSameLine(": \0"u8); + _imgui.TextSameLine(metadata.Value); + } + _imgui.NewLine(); + + _imgui.Unindent(); + } + } + + private void RenderRuntimePatches() + { + for (var i = 0; i < _crashReport.RuntimePatches.Count; i++) + { + var runtimePatch = _crashReport.RuntimePatches[i]; + var methodNameFull = _runtimeMethodNameFullUtf8[runtimePatch]; + + if (_imgui.TreeNode(methodNameFull, ImGuiTreeNodeFlags.DefaultOpen)) + { + for (var j = 0; j < _runtimePatchTypes.Count; j++) + { + var type = _runtimePatchTypes[j]; + RenderRuntimePatches(runtimePatch.Patches.ToRefLinq().Where(x => x.Type == type)); + } + _imgui.NewLine(); + + _imgui.TreePop(); + } + } + } +} \ No newline at end of file diff --git a/src/BUTR.CrashReport.Renderer.ImGui/Renderer/ImGuiRenderer.09.HarmonyPatches.cs b/src/BUTR.CrashReport.Renderer.ImGui/Renderer/ImGuiRenderer.10.HarmonyPatches.cs similarity index 98% rename from src/BUTR.CrashReport.Renderer.ImGui/Renderer/ImGuiRenderer.09.HarmonyPatches.cs rename to src/BUTR.CrashReport.Renderer.ImGui/Renderer/ImGuiRenderer.10.HarmonyPatches.cs index 553e21d..570d515 100644 --- a/src/BUTR.CrashReport.Renderer.ImGui/Renderer/ImGuiRenderer.09.HarmonyPatches.cs +++ b/src/BUTR.CrashReport.Renderer.ImGui/Renderer/ImGuiRenderer.10.HarmonyPatches.cs @@ -1,4 +1,5 @@ -using BUTR.CrashReport.Models; +/* +using BUTR.CrashReport.Models; using BUTR.CrashReport.Renderer.ImGui.Extensions; using BUTR.CrashReport.Renderer.ImGui.UnsafeUtils; using BUTR.CrashReport.Renderer.ImGui.Utils; @@ -79,7 +80,7 @@ private void RenderHarmonyPatches(ReadOnlySpan name, RefLinqEnumerable 0) { _imgui.TextSameLine(" Before: \0"u8); _imgui.TextSameLine(_harmonyBeforeUtf8[patch]); } if (patch.After.Count > 0) { _imgui.TextSameLine(" After: \0"u8); _imgui.TextSameLine(_harmonyAfterUtf8[patch]); } _imgui.NewLine(); @@ -107,4 +108,5 @@ private void RenderHarmonyPatches() } } } -} \ No newline at end of file +} +*/ \ No newline at end of file diff --git a/src/BUTR.CrashReport.Renderer.ImGui/Renderer/ImGuiRenderer.10.LogFiles.cs b/src/BUTR.CrashReport.Renderer.ImGui/Renderer/ImGuiRenderer.10.LogFiles.cs index bfdc540..8723578 100644 --- a/src/BUTR.CrashReport.Renderer.ImGui/Renderer/ImGuiRenderer.10.LogFiles.cs +++ b/src/BUTR.CrashReport.Renderer.ImGui/Renderer/ImGuiRenderer.10.LogFiles.cs @@ -50,9 +50,12 @@ private void RenderLogFiles() var date = logEntry.Date; var color = logEntry.Level switch { - LogLevel.Fatal => Red, - LogLevel.Error => Red, - LogLevel.Warning => Orange, + LogLevel.Fatal => Fatal, + LogLevel.Error => Error, + LogLevel.Warning => Warn, + LogLevel.Information => Info, + LogLevel.Debug => Debug, + LogLevel.Verbose => Debug, _ => Black }; var level = Clamp(logEntry.Level, LogLevel.None, LogLevel.Fatal); diff --git a/src/BUTR.CrashReport.Renderer.ImGui/Renderer/ImGuiRenderer.ImGui.cs b/src/BUTR.CrashReport.Renderer.ImGui/Renderer/ImGuiRenderer.ImGui.cs index c355f31..4e9574d 100644 --- a/src/BUTR.CrashReport.Renderer.ImGui/Renderer/ImGuiRenderer.ImGui.cs +++ b/src/BUTR.CrashReport.Renderer.ImGui/Renderer/ImGuiRenderer.ImGui.cs @@ -10,8 +10,12 @@ partial class ImGuiRenderer private static readonly Vector4 Zero4 = Vector4.Zero; private static readonly Vector4 Black = FromColor(0, 0, 0, 255); private static readonly Vector4 White = FromColor(255, 255, 255, 255); - private static readonly Vector4 Red = FromColor(255, 0, 0, 255); - private static readonly Vector4 Orange = FromColor(255, 165, 0, 255); + + private static readonly Vector4 Debug = FromColor(54, 96, 146, 255); + private static readonly Vector4 Info = FromColor(0, 125, 60, 255); + private static readonly Vector4 Warn = FromColor(225, 125, 50, 255); + private static readonly Vector4 Error = FromColor(240, 0, 0, 255); + private static readonly Vector4 Fatal = FromColor(190, 0, 0, 255); private static readonly Vector4 Background = FromColor(236, 236, 236, 255); private static readonly Vector4 Plugin = FromColor(255, 255, 224, 255); diff --git a/src/BUTR.CrashReport.Renderer.ImGui/Renderer/ImGuiRenderer.cs b/src/BUTR.CrashReport.Renderer.ImGui/Renderer/ImGuiRenderer.cs index 2a91ce2..8bbe5fa 100644 --- a/src/BUTR.CrashReport.Renderer.ImGui/Renderer/ImGuiRenderer.cs +++ b/src/BUTR.CrashReport.Renderer.ImGui/Renderer/ImGuiRenderer.cs @@ -51,13 +51,14 @@ private static int Clamp(TEnum n, TEnum min, TEnum max) where TEnum : Enu private readonly CmGui _imgui; private readonly CrashReportModel _crashReport; - private readonly IList _logSources; + private readonly IList _logSources; private readonly ICrashReportRendererUtilities _crashReportRendererUtilities; private readonly Action _onClose; + private byte[] _involvedModulesAndPluginsTitle = []; private byte[] _loadedPluginsTitle = []; - public ImGuiRenderer(CmGui imgui, CrashReportModel crashReport, IList logSources, ICrashReportRendererUtilities crashReportRendererUtilities, Action onClose) + public ImGuiRenderer(CmGui imgui, CrashReportModel crashReport, IList logSources, ICrashReportRendererUtilities crashReportRendererUtilities, Action onClose) { _imgui = imgui; _crashReport = crashReport; @@ -73,15 +74,16 @@ public ImGuiRenderer(CmGui imgui, CrashReportModel crashReport, IList InitializeInstalledLoaderPlugins(); InitializeAssemblies(); InitializeNatives(); - InitializeHarmonyPatches(); + InitializeRuntimePatches(); InitializeLogFiles(); } private void InitializeMain() { - _loadedPluginsTitle = !string.IsNullOrEmpty(_crashReport.Metadata.LoaderPluginProviderName) - ? UnsafeHelper.ToUtf8Array($"Loaded {_crashReport.Metadata.LoaderPluginProviderName} Plugins") - : []; + _loadedPluginsTitle = UnsafeHelper.ToUtf8Array($"Loaded {_crashReport.Metadata.LoaderPluginProviderName} Plugins\0"); + _involvedModulesAndPluginsTitle = _crashReportRendererUtilities.Capabilities.HasFlag(CrashReportRendererCapabilities.PluginLoader) + ? "Involved Modules and Plugins\0"u8.ToArray() + : "Involved Modules\0"u8.ToArray(); } public void Render() @@ -134,10 +136,10 @@ public void Render() } _imgui.EndChild(); - if (_imgui.BeginChild("Involved Modules and Plugins\0"u8, in Zero2, in White, ImGuiChildFlags.Border | ImGuiChildFlags.AutoResizeY, ImGuiWindowFlags.None)) + if (_imgui.BeginChild(_involvedModulesAndPluginsTitle, in Zero2, in White, ImGuiChildFlags.Border | ImGuiChildFlags.AutoResizeY, ImGuiWindowFlags.None)) { _imgui.SetWindowFontScale(2); - if (_imgui.TreeNode("Involved Modules and Plugins\0"u8)) + if (_imgui.TreeNode(_involvedModulesAndPluginsTitle)) { _imgui.SetWindowFontScale(1); RenderInvolvedModulesAndPlugins(); @@ -160,7 +162,7 @@ public void Render() } _imgui.EndChild(); - if (_loadedPluginsTitle.Length > 0) + if (_crashReportRendererUtilities.Capabilities.HasFlag(CrashReportRendererCapabilities.PluginLoader)) { if (_imgui.BeginChild(_loadedPluginsTitle, in Zero2, in White, ImGuiChildFlags.Border | ImGuiChildFlags.AutoResizeY, ImGuiWindowFlags.None)) { @@ -202,32 +204,69 @@ public void Render() } _imgui.EndChild(); - if (_imgui.BeginChild("Harmony Patches\0"u8, in Zero2, in White, ImGuiChildFlags.Border | ImGuiChildFlags.AutoResizeY, ImGuiWindowFlags.None)) + /* + if (_crashReportRendererUtilities.Capabilities.HasFlag(CrashReportRendererCapabilities.MonoModPatches)) { - _imgui.SetWindowFontScale(2); - if (_imgui.TreeNode("Harmony Patches\0"u8)) + if (_imgui.BeginChild("MonoMod Patches\0"u8, in Zero2, in White, ImGuiChildFlags.Border | ImGuiChildFlags.AutoResizeY, ImGuiWindowFlags.None)) { + _imgui.SetWindowFontScale(2); + if (_imgui.TreeNode("MonoMod Patches\0"u8)) + { + _imgui.SetWindowFontScale(1); + RenderMonoModPatches(); + } + _imgui.TreePop(); _imgui.SetWindowFontScale(1); - RenderHarmonyPatches(); } - _imgui.TreePop(); - _imgui.SetWindowFontScale(1); + _imgui.EndChild(); } - _imgui.EndChild(); - if (_imgui.BeginChild("Log Files\0"u8, in Zero2, in White, ImGuiChildFlags.Border | ImGuiChildFlags.AutoResizeY, ImGuiWindowFlags.None)) + if (_crashReportRendererUtilities.Capabilities.HasFlag(CrashReportRendererCapabilities.HarmonyPatches)) + { + if (_imgui.BeginChild("Harmony Patches\0"u8, in Zero2, in White, ImGuiChildFlags.Border | ImGuiChildFlags.AutoResizeY, ImGuiWindowFlags.None)) + { + _imgui.SetWindowFontScale(2); + if (_imgui.TreeNode("Harmony Patches\0"u8)) + { + _imgui.SetWindowFontScale(1); + RenderHarmonyPatches(); + } + _imgui.TreePop(); + _imgui.SetWindowFontScale(1); + } + _imgui.EndChild(); + } + */ + + if (_imgui.BeginChild("Runtime Patches\0"u8, in Zero2, in White, ImGuiChildFlags.Border | ImGuiChildFlags.AutoResizeY, ImGuiWindowFlags.None)) { _imgui.SetWindowFontScale(2); - if (_imgui.TreeNode("Log Files\0"u8)) + if (_imgui.TreeNode("Runtime Patches\0"u8)) { _imgui.SetWindowFontScale(1); - RenderLogFiles(); + RenderRuntimePatches(); } _imgui.TreePop(); _imgui.SetWindowFontScale(1); } _imgui.EndChild(); + if (_crashReportRendererUtilities.Capabilities.HasFlag(CrashReportRendererCapabilities.Logs)) + { + if (_imgui.BeginChild("Log Files\0"u8, in Zero2, in White, ImGuiChildFlags.Border | ImGuiChildFlags.AutoResizeY, ImGuiWindowFlags.None)) + { + _imgui.SetWindowFontScale(2); + if (_imgui.TreeNode("Log Files\0"u8)) + { + _imgui.SetWindowFontScale(1); + RenderLogFiles(); + } + _imgui.TreePop(); + _imgui.SetWindowFontScale(1); + } + _imgui.EndChild(); + } + _imgui.End(); } } diff --git a/src/BUTR.CrashReport.Renderer.WinForms/CrashReportWinForms.cs b/src/BUTR.CrashReport.Renderer.WinForms/CrashReportWinForms.cs index 01bd196..c8c6648 100644 --- a/src/BUTR.CrashReport.Renderer.WinForms/CrashReportWinForms.cs +++ b/src/BUTR.CrashReport.Renderer.WinForms/CrashReportWinForms.cs @@ -84,14 +84,14 @@ function handleIncludeScreenshot(cb) { private ICrashReportRendererUtilities CrashReportRendererUtilities { get; } private CrashReportModel CrashReport { get; } - private ICollection LogSources { get; } + private ICollection LogSources { get; } private string ReportInHtml { get; } public bool IncludeMiniDump { get; set; } public bool IncludeSaveFile { get; set; } public bool IncludeScreenshot { get; set; } - public CrashReportWinForms(CrashReportModel crashReport, ICollection logSources, ICrashReportRendererUtilities crashReportRendererUtilities) + public CrashReportWinForms(CrashReportModel crashReport, ICollection logSources, ICrashReportRendererUtilities crashReportRendererUtilities) { CrashReportRendererUtilities = crashReportRendererUtilities; CrashReport = crashReport; diff --git a/src/BUTR.CrashReport.Renderer.WinForms/ICrashReportRendererUtilities.cs b/src/BUTR.CrashReport.Renderer.WinForms/ICrashReportRendererUtilities.cs index a87c0b3..467ceb8 100644 --- a/src/BUTR.CrashReport.Renderer.WinForms/ICrashReportRendererUtilities.cs +++ b/src/BUTR.CrashReport.Renderer.WinForms/ICrashReportRendererUtilities.cs @@ -1,14 +1,29 @@ -using BUTR.CrashReport.Models; +using System; +using BUTR.CrashReport.Models; using System.Collections.Generic; namespace BUTR.CrashReport.Renderer.WinForms; +[Flags] +public enum CrashReportRendererCapabilities +{ + None = 0, + SaveAsHtml = 1 << 0, + CopyAsHtml = 1 << 1, + Upload = 1 << 2, + HasSaveFiles = 1 << 3, + HasScreenshots = 1 << 4, + HasMiniDump = 1 << 5, +} + public interface ICrashReportRendererUtilities { - void Upload(CrashReportModel crashReport, ICollection logSources); + CrashReportRendererCapabilities Capabilities { get; } + + void Upload(CrashReportModel crashReport, ICollection logSources); - string CopyAsHtml(CrashReportModel crashReport, ICollection logSources); + string CopyAsHtml(CrashReportModel crashReport, ICollection logSources); - void SaveCrashReportAsHtml(CrashReportModel crashReport, ICollection logSources, bool addMiniDump, bool addLatestSave, bool addScreenshots); + void SaveCrashReportAsHtml(CrashReportModel crashReport, ICollection logSources, bool addMiniDump, bool addLatestSave, bool addScreenshots); } \ No newline at end of file diff --git a/src/BUTR.CrashReport.Tool/Program.cs b/src/BUTR.CrashReport.Tool/Program.cs index 93f5715..1d5c869 100644 --- a/src/BUTR.CrashReport.Tool/Program.cs +++ b/src/BUTR.CrashReport.Tool/Program.cs @@ -64,7 +64,7 @@ public static async Task Main(string[] args) { Converters = { new JsonStringEnumConverter() } })!; - var logs = logsStream is not null ? JsonSerializer.Deserialize(logsStream)! : []; + var logs = logsStream is not null ? JsonSerializer.Deserialize(logsStream)! : []; var html = CrashReportHtml.AddData(CrashReportHtml.Build(crashReport, logs), crashReportJson, minidump, saveFile, screenshot); diff --git a/src/BUTR.CrashReport/CrashReportInfo.cs b/src/BUTR.CrashReport/CrashReportInfo.cs index dcc964d..5c00f1d 100644 --- a/src/BUTR.CrashReport/CrashReportInfo.cs +++ b/src/BUTR.CrashReport/CrashReportInfo.cs @@ -58,8 +58,9 @@ public static CrashReportModel ToModel(CrashReportInfo crashReport, Modules = modules, Assemblies = assemblies, NativeModules = nativeAssemblies, - HarmonyPatches = CrashReportModelUtils.GetHarmonyPatches(crashReport, assemblies, moduleProvider, loaderPluginProvider), - //MonoModDetours = Array.Empty(), + RuntimePatches = CrashReportModelUtils.GetRuntimePatches(crashReport, assemblies, moduleProvider, loaderPluginProvider), + //HarmonyPatches = CrashReportModelUtils.GetHarmonyPatches(crashReport, assemblies, moduleProvider, loaderPluginProvider), + //MonoModPatches = CrashReportModelUtils.GetMonoModDetours(crashReport, assemblies, moduleProvider, loaderPluginProvider), LoaderPlugins = plugins, InvolvedLoaderPlugins = CrashReportModelUtils.GetInvolvedPlugins(crashReport), Metadata = metadata, @@ -75,14 +76,16 @@ public static CrashReportInfo Create(Exception exception, Dictionary - new(exception, additionalMetadata, stacktraceFilter, assemblyUtilities, moduleProvider, loaderPluginProvider, harmonyProvider); + IPatchProvider patchProvider /*, + IMonoModProvider monoModProvider, + IHarmonyProvider harmonyProvider*/) => + new(exception, additionalMetadata, stacktraceFilter, assemblyUtilities, moduleProvider, loaderPluginProvider, patchProvider/*, monoModProvider, harmonyProvider*/); /// /// /// /// - public readonly byte Version = 14; + public readonly byte Version = 15; /// /// @@ -128,11 +131,25 @@ public static CrashReportInfo Create(Exception exception, Dictionary public Dictionary ImportedTypeReferences { get; } + /// + /// + /// + /// + public Dictionary> LoadedRuntimePatches { get; } = new(); + + /* + /// + /// + /// + /// + public Dictionary LoadedMonoModPatches { get; } = new(); + /// /// /// /// public Dictionary LoadedHarmonyPatches { get; } = new(); + */ /// /// Additional metadata about the crash. @@ -147,7 +164,9 @@ private CrashReportInfo(Exception exception, Dictionary addition IAssemblyUtilities assemblyUtilities, IModuleProvider moduleProvider, ILoaderPluginProvider loaderPluginProvider, - IHarmonyProvider harmonyProvider) + IPatchProvider patchProvider /*, + IMonoModProvider monoModProvider, + IHarmonyProvider harmonyProvider */) { var assemblies = assemblyUtilities.Assemblies().ToArray(); @@ -164,14 +183,31 @@ private CrashReportInfo(Exception exception, Dictionary addition FullName = y.FullName }).ToArray()); - Stacktrace = CrashReportUtils.GetAllInvolvedModules(Exception, assemblies, assemblyUtilities, moduleProvider, loaderPluginProvider, harmonyProvider).ToArray(); + Stacktrace = CrashReportUtils.GetAllInvolvedModules(Exception, assemblies, assemblyUtilities, moduleProvider, loaderPluginProvider, patchProvider /*monoModProvider, harmonyProvider*/).ToArray(); FilteredStacktrace = stacktraceFilter.Filter(Stacktrace).ToArray(); + /* + foreach (var originalMethod in monoModProvider.GetAllPatchedMethods()) + { + var patches = monoModProvider.GetPatchInfo(originalMethod); + if (originalMethod is null || patches is null) continue; + LoadedMonoModPatches.Add(originalMethod, patches); + } + foreach (var originalMethod in harmonyProvider.GetAllPatchedMethods()) { var patches = harmonyProvider.GetPatchInfo(originalMethod); if (originalMethod is null || patches is null) continue; LoadedHarmonyPatches.Add(originalMethod, patches); } + */ + + var runtimePatches = patchProvider.GetAllPatches(); + foreach (var runtimePatch in runtimePatches) + { + if (!LoadedRuntimePatches.ContainsKey(runtimePatch.Original)) + LoadedRuntimePatches[runtimePatch.Original] = new List(); + LoadedRuntimePatches[runtimePatch.Original].Add(runtimePatch); + } } } \ No newline at end of file diff --git a/src/BUTR.CrashReport/Interfaces/IAssemblyUtilities.cs b/src/BUTR.CrashReport/Interfaces/IAssemblyUtilities.cs index 028d566..fc09dc7 100644 --- a/src/BUTR.CrashReport/Interfaces/IAssemblyUtilities.cs +++ b/src/BUTR.CrashReport/Interfaces/IAssemblyUtilities.cs @@ -34,5 +34,5 @@ public interface IAssemblyUtilities /// /// Gets the type of the assembly /// - AssemblyModelType GetAssemblyType(AssemblyModelType type, CrashReportInfo crashReport, Assembly assembly); + AssemblyType GetAssemblyType(AssemblyType type, CrashReportInfo crashReport, Assembly assembly); } \ No newline at end of file diff --git a/src/BUTR.CrashReport/Interfaces/ICommonProvider.cs b/src/BUTR.CrashReport/Interfaces/ICommonProvider.cs new file mode 100644 index 0000000..d0fe5f4 --- /dev/null +++ b/src/BUTR.CrashReport/Interfaces/ICommonProvider.cs @@ -0,0 +1,13 @@ +using System; +using System.Diagnostics; +using System.Reflection; + +namespace BUTR.CrashReport.Interfaces; + +public interface ICommonProvider +{ + /// + /// Returns the JIT compiled (native) start address of a method. + /// + IntPtr GetNativeMethodBody(MethodBase method); +} \ No newline at end of file diff --git a/src/BUTR.CrashReport/Interfaces/IHarmonyProvider.cs b/src/BUTR.CrashReport/Interfaces/IHarmonyProvider.cs index e2db729..bf2c569 100644 --- a/src/BUTR.CrashReport/Interfaces/IHarmonyProvider.cs +++ b/src/BUTR.CrashReport/Interfaces/IHarmonyProvider.cs @@ -1,5 +1,4 @@ -using BUTR.CrashReport.Models; - +/* using System; using System.Collections.Generic; using System.Diagnostics; @@ -16,31 +15,29 @@ public interface IHarmonyProvider /// Returns all patched methods. /// IEnumerable GetAllPatchedMethods(); - + /// /// Returns the patch information for a given method. /// /// HarmonyPatches? GetPatchInfo(MethodBase originalMethod); - /// - /// Returns the original method for a given patch method. - /// - /// The patch method - MethodBase? GetOriginalMethod(MethodInfo replacement); + HarmonyPatches? GetPatchInfo(StackFrame frame, IModuleProvider moduleProvider, ILoaderPluginProvider loaderPluginProvider); /// - /// Returns the method from a stackframe. + /// Returns the actual executing method from a stackframe. /// - MethodBase? GetMethodFromStackframe(StackFrame frame); - + MethodInfo? GetExecutingMethod(StackFrame frame); + /// - /// The runtime can return several different MethodInfo's that point to the same method. Will return the correct one. + /// Returns the original method for a given patch method. /// - MethodBase? GetIdentifiable(MethodBase method); - + /// The frame + MethodBase? GetOriginalMethod(StackFrame frame); + /// /// Returns the JIT compiled (native) start address of a method. /// IntPtr GetNativeMethodBody(MethodBase method); -} \ No newline at end of file +} +*/ \ No newline at end of file diff --git a/src/BUTR.CrashReport/Interfaces/IMonoModProvider.cs b/src/BUTR.CrashReport/Interfaces/IMonoModProvider.cs new file mode 100644 index 0000000..247e625 --- /dev/null +++ b/src/BUTR.CrashReport/Interfaces/IMonoModProvider.cs @@ -0,0 +1,37 @@ +/* +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Reflection; +using BUTR.CrashReport.Models; + +namespace BUTR.CrashReport.Interfaces; + +public interface IMonoModProvider +{ + /// + /// Returns all patched methods. + /// + IEnumerable GetAllPatchedMethods(); + + MonoModPatches? GetPatchInfo(MethodBase originalMethod); + + MonoModPatches? GetPatchInfo(StackFrame frame, IModuleProvider moduleProvider, ILoaderPluginProvider loaderPluginProvider); + + /// + /// Returns the actual executing method from a stackframe. + /// + MethodInfo? GetExecutingMethod(StackFrame frame); + + /// + /// Returns the original method for a given patch method. + /// + /// The frame + MethodBase? GetOriginalMethod(StackFrame frame); + + /// + /// Returns the JIT compiled (native) start address of a method. + /// + IntPtr GetNativeMethodBody(MethodBase method); +} +*/ \ No newline at end of file diff --git a/src/BUTR.CrashReport/Interfaces/IPatchProvider.cs b/src/BUTR.CrashReport/Interfaces/IPatchProvider.cs new file mode 100644 index 0000000..c6c35cc --- /dev/null +++ b/src/BUTR.CrashReport/Interfaces/IPatchProvider.cs @@ -0,0 +1,16 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Reflection; +using BUTR.CrashReport.Models; + +namespace BUTR.CrashReport.Interfaces; + +public interface IPatchProvider +{ + IList GetAllPatches(); + IList GetPatches(MethodBase originalMethod); + StackFrameRuntimePatch? GetPatches(StackFrame frame); + MethodBase GetOriginalMethod(StackFrame frame); + IntPtr GetNativeMethodBody(MethodBase method); +} \ No newline at end of file diff --git a/src/BUTR.CrashReport/Models/HarmonyPatch.cs b/src/BUTR.CrashReport/Models/HarmonyPatch.cs index ec59ad4..2699724 100644 --- a/src/BUTR.CrashReport/Models/HarmonyPatch.cs +++ b/src/BUTR.CrashReport/Models/HarmonyPatch.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +/* +using System.Collections.Generic; using System.Reflection; namespace BUTR.CrashReport.Models; @@ -42,4 +43,5 @@ public record HarmonyPatch /// /// public required HarmonyPatchType Type { get; set; } -} \ No newline at end of file +} +*/ \ No newline at end of file diff --git a/src/BUTR.CrashReport/Models/HarmonyPatches.cs b/src/BUTR.CrashReport/Models/HarmonyPatches.cs index 08142ab..568f49a 100644 --- a/src/BUTR.CrashReport/Models/HarmonyPatches.cs +++ b/src/BUTR.CrashReport/Models/HarmonyPatches.cs @@ -1,7 +1,30 @@ using System.Collections.Generic; +using System.Reflection; namespace BUTR.CrashReport.Models; +public record RuntimePatches +{ + public required IList Patches { get; set; } +} + +public record RuntimePatch +{ + public required string PatchProvider { get; set; } + + public required string PatchType { get; set; } + + public required MethodBase Original { get; set; } + public required MethodBase Patch { get; set; } + + /// + /// + /// + /// + public required IList AdditionalMetadata { get; set; } = new List(); +} + +/* /// /// /// @@ -26,4 +49,33 @@ public record HarmonyPatches /// ///
      public required IList Transpilers { get; set; } -} \ No newline at end of file +} + +public record MonoModPatches +{ + public required IList Detours { get; set; } + public required IList ILHooks { get; set; } +} + +public record MonoModPatch +{ + public required MethodBase Method { get; set; } + + public required bool IsActive { get; set; } + + public required string Id { get; set; } + + public required int? Index { get; set; } + + public required int? MaxIndex { get; set; } + + public required int? GlobalIndex { get; set; } + public required int? Priority { get; set; } + + public required int? SubPriority { get; set; } + + public required IList Before { get; set; } = new List(); + + public required IList After { get; set; } = new List(); +} +*/ \ No newline at end of file diff --git a/src/BUTR.CrashReport/Models/MethodEntry.cs b/src/BUTR.CrashReport/Models/MethodEntry.cs index 675ee87..848228f 100644 --- a/src/BUTR.CrashReport/Models/MethodEntry.cs +++ b/src/BUTR.CrashReport/Models/MethodEntry.cs @@ -25,20 +25,20 @@ public abstract record MethodEntry public required ILoaderPluginInfo? LoaderPluginInfo { get; set; } /// - /// + /// /// - /// + /// public required string[] ILInstructions { get; set; } /// - /// + /// /// - /// + /// public required string[] CSharpILMixedInstructions { get; set; } /// - /// + /// /// - /// + /// public required string[] CSharpInstructions { get; set; } } \ No newline at end of file diff --git a/src/BUTR.CrashReport/Models/MethodEntryHarmony.cs b/src/BUTR.CrashReport/Models/MethodEntryHarmony.cs index 02b4f6e..bdcae3a 100644 --- a/src/BUTR.CrashReport/Models/MethodEntryHarmony.cs +++ b/src/BUTR.CrashReport/Models/MethodEntryHarmony.cs @@ -1,7 +1,8 @@ -namespace BUTR.CrashReport.Models; +/* +namespace BUTR.CrashReport.Models; /// -/// Represents a harmony patch. +/// Represents a Harmony patch. /// public record MethodEntryHarmony : MethodEntry { @@ -9,4 +10,5 @@ public record MethodEntryHarmony : MethodEntry /// The harmony patch. ///
      public required HarmonyPatch Patch { get; set; } -} \ No newline at end of file +} +*/ \ No newline at end of file diff --git a/src/BUTR.CrashReport/Models/MethodEntryMonoMod.cs b/src/BUTR.CrashReport/Models/MethodEntryMonoMod.cs new file mode 100644 index 0000000..bbcaa46 --- /dev/null +++ b/src/BUTR.CrashReport/Models/MethodEntryMonoMod.cs @@ -0,0 +1,14 @@ +/* +namespace BUTR.CrashReport.Models; + +/// +/// Represents a MonoMod patch. +/// +public record MethodEntryMonoMod : MethodEntry +{ + /// + /// The harmony patch. + /// + public required MonoModPatch Patch { get; set; } +} +*/ \ No newline at end of file diff --git a/src/BUTR.CrashReport/Models/MethodEntryRuntimePatch.cs b/src/BUTR.CrashReport/Models/MethodEntryRuntimePatch.cs new file mode 100644 index 0000000..231b174 --- /dev/null +++ b/src/BUTR.CrashReport/Models/MethodEntryRuntimePatch.cs @@ -0,0 +1,12 @@ +namespace BUTR.CrashReport.Models; + +/// +/// Represents a MonoMod patch. +/// +public record MethodEntryRuntimePatch : MethodEntry +{ + /// + /// The harmony patch. + /// + public required RuntimePatch Patch { get; set; } +} \ No newline at end of file diff --git a/src/BUTR.CrashReport/Models/StackFrameRuntimePatch.cs b/src/BUTR.CrashReport/Models/StackFrameRuntimePatch.cs new file mode 100644 index 0000000..23a7aa3 --- /dev/null +++ b/src/BUTR.CrashReport/Models/StackFrameRuntimePatch.cs @@ -0,0 +1,42 @@ +using System; +using System.Collections.Generic; +using System.Reflection; + +namespace BUTR.CrashReport.Models; + +public record StackFrameRuntimePatch +{ + /// + /// + /// + public required MethodBase? OriginalMethod { get; set; } + + /// + /// + /// + public required MethodBase? ExecutingMethod { get; set; } + + /// + /// + /// + public required IList Patches { get; set; } + + public required int ILOffset { get; set; } + + public required int NativeILOffset { get; set; } + + public required IntPtr NativeCodePtr { get; set; } + + /// + /// Deconstructs the object. + /// + public void Deconstruct(out MethodBase? original, out MethodBase? executing, out IList patches, out int ilOffset, out int nativeILOffset, out IntPtr nativeCodePtr) + { + original = OriginalMethod; + executing = ExecutingMethod; + patches = Patches; + ilOffset = ILOffset; + nativeILOffset = NativeILOffset; + nativeCodePtr = NativeCodePtr; + } +} \ No newline at end of file diff --git a/src/BUTR.CrashReport/Models/StacktraceEntry.cs b/src/BUTR.CrashReport/Models/StacktraceEntry.cs index fcbf190..7a9c535 100644 --- a/src/BUTR.CrashReport/Models/StacktraceEntry.cs +++ b/src/BUTR.CrashReport/Models/StacktraceEntry.cs @@ -19,12 +19,6 @@ public record StacktraceEntry /// public required MethodEntrySimple? OriginalMethod { get; set; } - /// - /// - /// - /// - public required bool MethodFromStackframeIssue { get; set; } - /// /// The module that holds the method. Can be null. /// @@ -54,27 +48,27 @@ public record StacktraceEntry public required string StackFrameDescription { get; set; } /// - /// + /// /// - /// + /// public required string[] NativeInstructions { get; set; } /// - /// + /// /// - /// + /// public required string[] ILInstructions { get; set; } /// - /// + /// /// - /// + /// public required string[] CSharpILMixedInstructions { get; set; } /// - /// + /// /// - /// + /// public required string[] CSharpInstructions { get; set; } /// diff --git a/src/BUTR.CrashReport/Utils/CrashReportModelUtils.cs b/src/BUTR.CrashReport/Utils/CrashReportModelUtils.cs index dde1701..495fec7 100644 --- a/src/BUTR.CrashReport/Utils/CrashReportModelUtils.cs +++ b/src/BUTR.CrashReport/Utils/CrashReportModelUtils.cs @@ -51,12 +51,12 @@ public static List GetEnhancedStacktrace(CrashRepo { foreach (var entry in stacktrace) { - var methods = new List(entry.PatchMethods.Length); + var methods = new List(entry.PatchMethods.Length); foreach (var patchMethod in entry.PatchMethods) { var patchAssemblyName = entry.Method.DeclaringType?.Assembly.GetName(); var patchAssembly = patchAssemblyName is not null ? assemblies.FirstOrDefault(x => x.Id.Equals(patchAssemblyName)) : null; - var methodSimple = new MethodSimple + var methodSimple = new MethodSimpleModel { AssemblyId = patchAssembly?.Id, ModuleId = patchMethod.ModuleInfo?.Id, @@ -72,10 +72,12 @@ public static List GetEnhancedStacktrace(CrashRepo }; methods.Add(patchMethod switch { + /* MethodEntryHarmony meh => methodSimple with { AdditionalMetadata = methodSimple.AdditionalMetadata.Append(new MetadataModel { Key = "HarmonyPatchType", Value = meh.Patch.Type.ToString() }).ToArray() }, + */ _ => methodSimple }); } @@ -88,9 +90,9 @@ public static List GetEnhancedStacktrace(CrashRepo // Do not reverse engineer copyrighted or flagged original assemblies static bool IsProtected(AssemblyModel? assembly) => assembly is not null && - ((assembly.Type & AssemblyModelType.GameCore) != 0 || - (assembly.Type & AssemblyModelType.GameModule) != 0 || - (assembly.Type & AssemblyModelType.ProtectedFromDisassembly) != 0); + ((assembly.Type & AssemblyType.GameCore) != 0 || + (assembly.Type & AssemblyType.GameModule) != 0 || + (assembly.Type & AssemblyType.ProtectedFromDisassembly) != 0); var skipDisassemblyForOriginal = IsProtected(originalAssembly); var skipDisassemblyForExecuting = IsProtected(originalAssembly) || IsProtected(executingAssembly); @@ -130,7 +132,6 @@ static bool IsProtected(AssemblyModel? assembly) => assembly is not null && PatchMethods = methods, ILOffset = entry.ILOffset, NativeOffset = entry.NativeOffset, - MethodFromStackframeIssue = entry.MethodFromStackframeIssue, AdditionalMetadata = Array.Empty(), }); } @@ -243,7 +244,7 @@ static bool IsProtectedFromDisassembly(Assembly assembly) var assemblyName = kv.Key; var assembly = kv.Value; - var type = AssemblyModelType.Unclassified; + var type = AssemblyType.Unclassified; // TODO: On unity the system folder is the unity root folder. // With unity thre is not system folder, so everything will classified as Game @@ -253,15 +254,15 @@ static bool IsProtectedFromDisassembly(Assembly assembly) (assembly.GetCustomAttribute()?.Product == "Microsoft® .NET Framework") || (assembly.GetCustomAttribute()?.Product == "Microsoft® .NET"); - if (isSystem) type |= AssemblyModelType.System; - if (assembly.IsDynamic) type |= AssemblyModelType.Dynamic; - if (IsGAC(assembly)) type |= AssemblyModelType.GAC; - if (IsProtectedFromDisassembly(assembly)) type |= AssemblyModelType.ProtectedFromDisassembly; + if (isSystem) type |= AssemblyType.System; + if (assembly.IsDynamic) type |= AssemblyType.Dynamic; + if (IsGAC(assembly)) type |= AssemblyType.GAC; + if (IsProtectedFromDisassembly(assembly)) type |= AssemblyType.ProtectedFromDisassembly; var module = assemblyUtilities.GetAssemblyModule(crashReport, assembly); - if (module is not null) type |= AssemblyModelType.Module; + if (module is not null) type |= AssemblyType.Module; var loaderPlugin = assemblyUtilities.GetAssemblyPlugin(crashReport, assembly); - if (loaderPlugin is not null) type |= AssemblyModelType.LoaderPlugin; + if (loaderPlugin is not null) type |= AssemblyType.LoaderPlugin; type = assemblyUtilities.GetAssemblyType(type, crashReport, assembly); @@ -277,10 +278,10 @@ static bool IsProtectedFromDisassembly(Assembly assembly) LoaderPluginId = loaderPlugin?.Id, CultureName = assemblyName.CultureName, Architecture = (AssemblyArchitectureType) GetProcessorArchitecture(assemblyName), - Hash = assembly.IsDynamic || string.IsNullOrWhiteSpace(assembly.Location) || !File.Exists(assembly.Location) ? string.Empty : CrashReportUtils.CalculateMD5(assembly.Location), + Hash = assembly.IsDynamic || string.IsNullOrWhiteSpace(assembly.Location) || !File.Exists(assembly.Location) ? "NONE" : CrashReportUtils.CalculateMD5(assembly.Location), AnonymizedPath = assembly.IsDynamic ? "DYNAMIC" : string.IsNullOrWhiteSpace(assembly.Location) ? "EMPTY" : !File.Exists(assembly.Location) ? "MISSING" : anonymizedPath, Type = type, - ImportedTypeReferences = (type & AssemblyModelType.System) == 0 + ImportedTypeReferences = (type & AssemblyType.System) == 0 ? crashReport.ImportedTypeReferences.TryGetValue(assemblyName, out var values) ? values.Select(x => new AssemblyImportedTypeReferenceModel { Namespace = x.Namespace, @@ -288,7 +289,7 @@ static bool IsProtectedFromDisassembly(Assembly assembly) FullName = x.FullName, }).ToArray() : [] : [], - ImportedAssemblyReferences = (type & AssemblyModelType.System) == 0 + ImportedAssemblyReferences = (type & AssemblyType.System) == 0 ? assembly.GetReferencedAssemblies().Select(AssemblyImportedReferenceModelExtensions.Create).ToArray() : [], AdditionalMetadata = Array.Empty(), @@ -297,7 +298,8 @@ static bool IsProtectedFromDisassembly(Assembly assembly) return assemblyModels; } - + + /* /// /// Returns the /// @@ -356,4 +358,109 @@ static void AppendPatches(ICollection builder, HarmonyPatchTy return builder; } + + /// + /// Returns the + /// + public static List GetMonoModDetours(CrashReportInfo crashReport, IReadOnlyCollection assemblies, IModuleProvider moduleProvider, ILoaderPluginProvider loaderPluginProvider) + { + var builder = new List(crashReport.LoadedMonoModPatches.Count); + + static void AppendPatches(ICollection builder, MonoModPatchModelType type, IEnumerable patches, IReadOnlyCollection assemblies, IModuleProvider moduleProvider, ILoaderPluginProvider loaderPluginProvider) + { + foreach (var patch in patches) + { + var assemblyId = patch.Method.DeclaringType?.Assembly.GetName() is { } asmName ? AssemblyIdModel.FromAssembly(asmName) : null; + var module = moduleProvider.GetModuleByType(patch.Method.DeclaringType); + var loaderPlugin = loaderPluginProvider.GetLoaderPluginByType(patch.Method.DeclaringType); + + builder.Add(new() + { + Id = patch.Id, + Namespace = $"{patch.Method.DeclaringType!.FullName}.{patch.Method.Name}", + Type = type, + IsActive = false, + AssemblyId = assemblyId, + ModuleId = module?.Id, + LoaderPluginId = loaderPlugin?.Id, + Index = patch.Index, + MaxIndex = patch.MaxIndex, + GlobalIndex = patch.GlobalIndex, + Priority = patch.Priority, + SubPriority = patch.SubPriority, + Before = patch.Before, + After = patch.After, + AdditionalMetadata = Array.Empty(), + }); + } + } + + foreach (var kv in crashReport.LoadedMonoModPatches) + { + var originalMethod = kv.Key; + var patches = kv.Value; + + var detoursBuilder = new List(patches.Detours.Count); + + AppendPatches(detoursBuilder, MonoModPatchModelType.Detour, patches.Detours, assemblies, moduleProvider, loaderPluginProvider); + AppendPatches(detoursBuilder, MonoModPatchModelType.ILHook, patches.ILHooks, assemblies, moduleProvider, loaderPluginProvider); + + if (detoursBuilder.Count > 0) + { + builder.Add(new() + { + OriginalMethodDeclaredTypeName = originalMethod.DeclaringType?.FullName, + OriginalMethodName = originalMethod.Name, + Detours = detoursBuilder, + AdditionalMetadata = Array.Empty(), + }); + } + } + + return builder; + } + */ + + public static List GetRuntimePatches(CrashReportInfo crashReport, IReadOnlyCollection assemblies, IModuleProvider moduleProvider, ILoaderPluginProvider loaderPluginProvider) + { + var builder = new List(crashReport.LoadedRuntimePatches.Count); + + foreach (var kv in crashReport.LoadedRuntimePatches) + { + var originalMethod = kv.Key; + var patches = kv.Value; + + var patchesBuilder = new List(crashReport.LoadedRuntimePatches.Count); + foreach (var patch in patches) + { + var assemblyId = patch.Patch.DeclaringType?.Assembly.GetName() is { } asmName ? AssemblyIdModel.FromAssembly(asmName) : null; + var module = moduleProvider.GetModuleByType(patch.Patch.DeclaringType); + var loaderPlugin = loaderPluginProvider.GetLoaderPluginByType(patch.Patch.DeclaringType); + + patchesBuilder.Add(new() + { + AssemblyId = assemblyId, + ModuleId = module?.Id, + LoaderPluginId = loaderPlugin?.Id, + Provider = patch.PatchProvider, + Type = patch.PatchType, + FullName = patch.Patch.DeclaringType is not null ? $"{patch.Patch.DeclaringType.FullName}.{patch.Patch.Name}" : patch.Patch.Name, + AdditionalMetadata = patch.AdditionalMetadata, + }); + } + + if (patchesBuilder.Count > 0) + { + builder.Add(new() + { + OriginalMethodDeclaredTypeName = originalMethod.DeclaringType?.FullName, + OriginalMethodName = originalMethod.Name, + Patches = patchesBuilder, + AdditionalMetadata = Array.Empty(), + }); + } + } + + return builder; + } } \ No newline at end of file diff --git a/src/BUTR.CrashReport/Utils/CrashReportUtils.cs b/src/BUTR.CrashReport/Utils/CrashReportUtils.cs index 348b185..73c4511 100644 --- a/src/BUTR.CrashReport/Utils/CrashReportUtils.cs +++ b/src/BUTR.CrashReport/Utils/CrashReportUtils.cs @@ -11,8 +11,6 @@ using static BUTR.CrashReport.Decompilers.Utils.MethodDecompiler; -using HarmonyPatch = BUTR.CrashReport.Models.HarmonyPatch; - namespace BUTR.CrashReport.Utils; /// @@ -40,20 +38,49 @@ public record StackframePatchData /// public required List Patches { get; set; } - /// - /// - /// - public required bool Issues { get; set; } + public required int ILOffset { get; set; } + + public required int NativeILOffset { get; set; } + public required IntPtr NativeCodePtr { get; set; } + /// /// Deconstructs the object. /// - public void Deconstruct(out MethodBase? original, out MethodInfo? replacement, out List patches, out bool issues) + public void Deconstruct(out MethodBase? original, out MethodInfo? replacement, out List patches, out int ilOffset, out int nativeILOffset, out IntPtr nativeCodePtr) { original = OriginalMethod; replacement = ExecutingMethod; patches = Patches; - issues = Issues; + ilOffset = ILOffset; + nativeILOffset = NativeILOffset; + nativeCodePtr = NativeCodePtr; + } + } + + /* + /// + /// Gets all involved modules in the exception stacktrace. + /// + public static IEnumerable<(MonoModPatch, IModuleInfo?, ILoaderPluginInfo?)> GetMonoModPatchMethods(MonoModPatches? patches, IModuleProvider moduleProvider, ILoaderPluginProvider loaderPluginProvider) + { + if (patches is null) + yield break; + + var patchMethods = patches.Detours.OrderBy(t => t.Priority) + .Concat(patches.ILHooks.OrderBy(t => t.Priority)); + + foreach (var patch in patchMethods) + { + var method = patch.Method; + if (method.DeclaringType is not { } declaringType) + continue; + + var moduleInfo = moduleProvider.GetModuleByType(declaringType); + var loaderPluginInfo = loaderPluginProvider.GetLoaderPluginByType(declaringType); + + if (moduleInfo is not null || loaderPluginInfo is not null) + yield return (patch, moduleInfo, loaderPluginInfo); } } @@ -83,6 +110,7 @@ public void Deconstruct(out MethodBase? original, out MethodInfo? replacement, o yield return (patch, moduleInfo, loaderPluginInfo); } } + */ /// /// Gets the module info if the method is from a mod. @@ -169,38 +197,40 @@ public void Deconstruct(out MethodBase? original, out MethodInfo? replacement, o return null; } + /* /// /// Gets the Harmony data from the stackframe. /// - public static StackframePatchData GetHarmonyData(StackFrame frame, IHarmonyProvider harmonyProvider, IModuleProvider moduleProvider, ILoaderPluginProvider loaderPluginProvider) + public static StackframePatchData GetPatchData(StackFrame frame, IPatchProvider patchProvider, IMonoModProvider monoModProvider, IHarmonyProvider harmonyProvider, IModuleProvider moduleProvider, ILoaderPluginProvider loaderPluginProvider) { - MethodBase? executingMethod; - var methodFromStackframeIssue = false; - try - { - executingMethod = harmonyProvider.GetMethodFromStackframe(frame); - } - // NullReferenceException means the method was not found. Harmony doesn't handle this case gracefully - catch (NullReferenceException e) - { - Trace.TraceError(e.ToString()); - executingMethod = frame.GetMethod()!; - } - // The given generic instantiation was invalid. - // From what I understand, this will occur with generic methods - // Also when static constructors throw errors, Harmony resolution will fail - catch (Exception e) + var patches = new List(); + + var executingMethodMonoMod = monoModProvider.GetExecutingMethod(frame); + var originalMethodMonoMod = monoModProvider.GetOriginalMethod(frame); + var ilOffsetMonoMod = frame.GetILOffset(); + var nativeILOffsetMonoMod = frame.GetNativeOffset(); + var nativeCodePtrMonoMod = executingMethodMonoMod is not null ? monoModProvider.GetNativeMethodBody(executingMethodMonoMod) : IntPtr.Zero; + var monoModPatches = monoModProvider.GetPatchInfo(frame); + foreach (var (patch, moduleInfo, loaderPluginInfo) in GetMonoModPatchMethods(monoModPatches, moduleProvider, loaderPluginProvider)) { - Trace.TraceError(e.ToString()); - methodFromStackframeIssue = true; - executingMethod = frame.GetMethod()!; + patches.Add(new MethodEntryMonoMod + { + Patch = patch, + Method = patch.Method, + ModuleInfo = moduleInfo, + LoaderPluginInfo = loaderPluginInfo, + ILInstructions = DecompileILCode(patch.Method), + CSharpILMixedInstructions = DecompileILWithCSharpCode(patch.Method), + CSharpInstructions = DecompileCSharpCode(patch.Method), + }); } - - var patches = new List(); - var executingIdentifiableMethod = executingMethod is MethodInfo mi ? harmonyProvider.GetIdentifiable(mi) as MethodInfo ?? mi : null; - var originalIdentifiableMethod = executingIdentifiableMethod is not null ? harmonyProvider.GetOriginalMethod(executingIdentifiableMethod) : null; - var harmonyPatches = originalIdentifiableMethod is not null ? harmonyProvider.GetPatchInfo(originalIdentifiableMethod) : null; - + + var executingMethodHarmony = harmonyProvider.GetExecutingMethod(frame); + var originalMethodHarmony = harmonyProvider.GetOriginalMethod(frame); + var ilOffsetHarmony = frame.GetILOffset(); + var nativeILOffsetHarmony = frame.GetNativeOffset(); + var nativeCodePtrHarmony = executingMethodHarmony is not null ? harmonyProvider.GetNativeMethodBody(executingMethodHarmony) : IntPtr.Zero; + var harmonyPatches = harmonyProvider.GetPatchInfo(frame); foreach (var (patch, moduleInfo, loaderPluginInfo) in GetHarmonyPatchMethods(harmonyPatches, moduleProvider, loaderPluginProvider)) { patches.Add(new MethodEntryHarmony @@ -217,22 +247,41 @@ public static StackframePatchData GetHarmonyData(StackFrame frame, IHarmonyProvi return new() { - OriginalMethod = originalIdentifiableMethod, - ExecutingMethod = executingIdentifiableMethod, + OriginalMethod = originalMethodMonoMod ?? originalMethodHarmony, + ExecutingMethod = executingMethodMonoMod ?? executingMethodHarmony, Patches = patches, - Issues = methodFromStackframeIssue, }; } + */ + + public static IEnumerable<(RuntimePatch, IModuleInfo?, ILoaderPluginInfo?)> GetPatchMethods(IList patches, IModuleProvider moduleProvider, ILoaderPluginProvider loaderPluginProvider) + { + if (patches is null) + yield break; + + foreach (var patch in patches) + { + var method = patch.Patch; + if (method.DeclaringType is not { } declaringType) + continue; + + var moduleInfo = moduleProvider.GetModuleByType(declaringType); + var loaderPluginInfo = loaderPluginProvider.GetLoaderPluginByType(declaringType); + + if (moduleInfo is not null || loaderPluginInfo is not null) + yield return (patch, moduleInfo, loaderPluginInfo); + } + } /// /// Gets all involved modules in the exception stacktrace. /// - public static IEnumerable GetAllInvolvedModules(Exception ex, ICollection assemblies, IAssemblyUtilities assemblyUtilities, IModuleProvider moduleProvider, ILoaderPluginProvider loaderPluginProvider, IHarmonyProvider harmonyProvider) + public static IEnumerable GetAllInvolvedModules(Exception ex, ICollection assemblies, IAssemblyUtilities assemblyUtilities, IModuleProvider moduleProvider, ILoaderPluginProvider loaderPluginProvider, IPatchProvider patchProvider/*, IMonoModProvider monoModProvider, IHarmonyProvider harmonyProvider*/) { var inner = ex.InnerException; if (inner is not null) { - foreach (var modInfo in GetAllInvolvedModules(inner, assemblies, assemblyUtilities, moduleProvider, loaderPluginProvider, harmonyProvider)) + foreach (var modInfo in GetAllInvolvedModules(inner, assemblies, assemblyUtilities, moduleProvider, loaderPluginProvider, patchProvider/*, monoModProvider, harmonyProvider*/)) yield return modInfo; } @@ -241,11 +290,11 @@ public static IEnumerable GetAllInvolvedModules(Exception ex, I { if (!frame.HasMethod()) continue; - var (originalMethod, executingMethod, patches, methodFromStackframeIssue) = GetHarmonyData(frame, harmonyProvider, moduleProvider, loaderPluginProvider); - - var ilOffset = frame.GetILOffset(); - var nativeILOffset = frame.GetNativeOffset(); - var nativeCodePtr = executingMethod is not null ? harmonyProvider.GetNativeMethodBody(executingMethod) : IntPtr.Zero; + //var (originalMethod, executingMethod, patches, ilOffset, nativeILOffset, nativeCodePtr) = GetPatchData(frame, patchProvider/*, monoModProvider, harmonyProvider*/, moduleProvider, loaderPluginProvider); + if (patchProvider.GetPatches(frame) is not { } data) continue; + + var (originalMethod, executingMethod, patches, ilOffset, nativeILOffset, nativeCodePtr) = data; + yield return new() { Method = executingMethod!, @@ -258,7 +307,6 @@ public static IEnumerable GetAllInvolvedModules(Exception ex, I CSharpILMixedInstructions = DecompileILWithCSharpCode(originalMethod), CSharpInstructions = DecompileCSharpCode(originalMethod), } : null, - MethodFromStackframeIssue = methodFromStackframeIssue, ModuleInfo = GetModuleInfoIfMod(executingMethod, assemblies, assemblyUtilities, moduleProvider), LoaderPluginInfo = GetLoaderPluginIfMod(executingMethod, assemblies, assemblyUtilities, loaderPluginProvider), ILOffset = ilOffset != StackFrame.OFFSET_UNKNOWN ? ilOffset : null, @@ -268,7 +316,20 @@ public static IEnumerable GetAllInvolvedModules(Exception ex, I ILInstructions = DecompileILCode(executingMethod), CSharpILMixedInstructions = DecompileILWithCSharpCode(executingMethod), CSharpInstructions = DecompileCSharpCode(executingMethod), - PatchMethods = patches.ToArray(), + PatchMethods = GetPatchMethods(patches, moduleProvider, loaderPluginProvider).Select(x => + { + var (patch, moduleInfo, loaderPluginInfo) = x; + return new MethodEntryRuntimePatch + { + Patch = patch, + Method = patch.Original, + ModuleInfo = moduleInfo, + LoaderPluginInfo = loaderPluginInfo, + ILInstructions = DecompileILCode(patch.Patch), + CSharpILMixedInstructions = DecompileILWithCSharpCode(patch.Patch), + CSharpInstructions = DecompileCSharpCode(patch.Patch), + }; + }).ToArray(), }; } }