Skip to content

Commit

Permalink
Added WinForms renderer with net45 support
Browse files Browse the repository at this point in the history
  • Loading branch information
Aragas committed Apr 5, 2024
1 parent 093ddbd commit df111f9
Show file tree
Hide file tree
Showing 13 changed files with 418 additions and 36 deletions.
2 changes: 1 addition & 1 deletion src/BUTR.CrashReport.Bannerlord.Tool/Program.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using BUTR.CrashReport.Models;
using BUTR.CrashReport.Renderer.Html;

using CommandLine;

Expand All @@ -10,7 +11,6 @@
using System.Text.Json;
using System.Text.Json.Serialization;
using System.Threading.Tasks;
using BUTR.CrashReport.Renderer.Html;

namespace BUTR.CrashReport.Bannerlord.Tool;

Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<TargetFrameworks>net45;netstandard2.0</TargetFrameworks>
<Nullable>enable</Nullable>
<LangVersion>preview</LangVersion>
</PropertyGroup>
Expand All @@ -24,7 +24,7 @@
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\BUTR.CrashReport\BUTR.CrashReport.csproj" Targets="DisableILRepack;Build" />
<ProjectReference Include="..\BUTR.CrashReport.Models\BUTR.CrashReport.Models.csproj" />
</ItemGroup>

</Project>
4 changes: 2 additions & 2 deletions src/BUTR.CrashReport.Renderer.Html/CrashReportHtml.Html.cs
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
using BUTR.CrashReport.Models;

using System.Collections.Generic;
using System.Text;

using BUTR.CrashReport.Models;

namespace BUTR.CrashReport.Renderer.Html;

partial class CrashReportHtml
Expand Down
53 changes: 27 additions & 26 deletions src/BUTR.CrashReport.Renderer.Html/CrashReportHtml.cs
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
using BUTR.CrashReport.Models;
using BUTR.CrashReport.Renderer.Html.Extensions;

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using BUTR.CrashReport.Extensions;
using BUTR.CrashReport.Models;

namespace BUTR.CrashReport.Renderer.Html;

public static partial class CrashReportHtml
{
private static readonly string MiniDumpTag = "<!-- MINI DUMP -->";
Expand Down Expand Up @@ -66,7 +67,7 @@ public static string AddData(string htmlReport, string gzipBase64CrashReportJson
<![endif]>
""");
}

htmlReport = htmlReport
.Replace(JsonModelTag, gzipBase64CrashReportJson)
.Replace(JsonModelButtonTag, """
Expand All @@ -80,7 +81,7 @@ public static string AddData(string htmlReport, string gzipBase64CrashReportJson

return htmlReport;
}

public static string Build(CrashReportModel crashReportModel, IEnumerable<LogSource> files) => GetBase(crashReportModel, files);

private static string GetRecursiveExceptionHtml(CrashReportModel crashReport, ExceptionModel? ex)
Expand All @@ -93,7 +94,7 @@ private static string GetRecursiveExceptionHtml(CrashReportModel crashReport, Ex

var moduleId = stacktrace?.ExecutingMethod.ModuleId ?? "UNKNOWN";
var sourceModuleId = ex.SourceModuleId ?? "UNKNOWN";

var pluginId = stacktrace?.ExecutingMethod.LoaderPluginId ?? "UNKNOWN";
var sourcePluginId = ex.SourceLoaderPluginId ?? "UNKNOWN";

Expand All @@ -102,10 +103,10 @@ private static string GetRecursiveExceptionHtml(CrashReportModel crashReport, Ex
var hasInner = ex.InnerException is not null;
return new StringBuilder()
.Append("Exception Information:").Append("<br/>")
.AppendIf(moduleId != "UNKNOWN", sb => sb.Append("Potential Module Id: ").Append("<b><a href='javascript:;' onclick='scrollToElement(\"").Append(moduleId).Append("\")'>").Append(moduleId).Append("</a></b>").Append("<br/>"))
.AppendIf(sourceModuleId != "UNKNOWN", sb => sb.Append("Potential Source Module Id: ").Append("<b><a href='javascript:;' onclick='scrollToElement(\"").Append(sourceModuleId).Append("\")'>").Append(sourceModuleId).Append("</a></b>").Append("<br/>"))
.AppendIf(pluginId != "UNKNOWN", sb => sb.Append("Potential Plugin Id: ").Append("<b><a href='javascript:;' onclick='scrollToElement(\"").Append(pluginId).Append("\")'>").Append(pluginId).Append("</a></b>").Append("<br/>"))
.AppendIf(sourcePluginId != "UNKNOWN", sb => sb.Append("Potential Source Plugin Id: ").Append("<b><a href='javascript:;' onclick='scrollToElement(\"").Append(sourcePluginId).Append("\")'>").Append(sourcePluginId).Append("</a></b>").Append("<br/>"))
.AppendIf(moduleId != "UNKNOWN", sb => sb.Append("Potential Module Id: ").Append("<b><a href='javascript:;' onclick='scrollToElement(\"").Append(moduleId).Append("\")'>").Append(moduleId).Append("</a></b>").Append("<br/>"))
.AppendIf(sourceModuleId != "UNKNOWN", sb => sb.Append("Potential Source Module Id: ").Append("<b><a href='javascript:;' onclick='scrollToElement(\"").Append(sourceModuleId).Append("\")'>").Append(sourceModuleId).Append("</a></b>").Append("<br/>"))
.AppendIf(pluginId != "UNKNOWN", sb => sb.Append("Potential Plugin Id: ").Append("<b><a href='javascript:;' onclick='scrollToElement(\"").Append(pluginId).Append("\")'>").Append(pluginId).Append("</a></b>").Append("<br/>"))
.AppendIf(sourcePluginId != "UNKNOWN", sb => sb.Append("Potential Source Plugin Id: ").Append("<b><a href='javascript:;' onclick='scrollToElement(\"").Append(sourcePluginId).Append("\")'>").Append(sourcePluginId).Append("</a></b>").Append("<br/>"))
.Append("Type: ").Append(ex.Type.EscapeGenerics()).Append("<br/>")
.AppendIf(hasMessage, sb => sb.Append("Message: ").Append(ex.Message.EscapeGenerics()).Append("<br/>"))
.AppendIf(hasCallStack, sb => sb.Append("Stacktrace:").Append("<br/>"))
Expand Down Expand Up @@ -139,8 +140,8 @@ private static string GetEnhancedStacktraceHtml(CrashReportModel crashReport)
.Append("Executing Method:")
.Append("<ul>")
.Append("<li>")
.AppendIf(moduleId2 != "UNKNOWN", sb => sb.Append("Module Id: ").Append("<b><a href='javascript:;' onclick='scrollToElement(\"").Append(moduleId2).Append("\")'>").Append(moduleId2).Append("</a></b>").Append("<br/>"))
.AppendIf(pluginId2 != "UNKNOWN", sb => sb.Append("Plugin Id: ").Append("<b><a href='javascript:;' onclick='scrollToElement(\"").Append(pluginId2).Append("\")'>").Append(pluginId2).Append("</a></b>").Append("<br/>"))
.AppendIf(moduleId2 != "UNKNOWN", sb => sb.Append("Module Id: ").Append("<b><a href='javascript:;' onclick='scrollToElement(\"").Append(moduleId2).Append("\")'>").Append(moduleId2).Append("</a></b>").Append("<br/>"))
.AppendIf(pluginId2 != "UNKNOWN", sb => sb.Append("Plugin Id: ").Append("<b><a href='javascript:;' onclick='scrollToElement(\"").Append(pluginId2).Append("\")'>").Append(pluginId2).Append("</a></b>").Append("<br/>"))
.Append("Method: ").Append(stacktrace.ExecutingMethod.MethodFullDescription.EscapeGenerics()).Append("<br/>")
.Append("Method From Stackframe Issue: ").Append(stacktrace.MethodFromStackframeIssue).Append("<br/>")
.Append("Approximate IL Offset: ").Append(stacktrace.ILOffset is not null ? $"{stacktrace.ILOffset:X4}" : "UNKNOWN").Append("<br/>")
Expand Down Expand Up @@ -189,15 +190,15 @@ private static string GetEnhancedStacktraceHtml(CrashReportModel crashReport)
{
var moduleId3 = stacktrace.OriginalMethod.ModuleId ?? "UNKNOWN";
var pluginId3 = stacktrace.OriginalMethod.LoaderPluginId ?? "UNKNOWN";

var id01 = random.Next();
var id02 = random.Next();
var id03 = random.Next();
sbMain.Append("Original Method:")
.Append("<ul>")
.Append("<li>")
.AppendIf(moduleId3 != "UNKNOWN", sb => sb.Append("Module Id: ").Append("<b><a href='javascript:;' onclick='scrollToElement(\"").Append(moduleId3).Append("\")'>").Append(moduleId3).Append("</a></b>").Append("<br/>"))
.AppendIf(pluginId3 != "UNKNOWN", sb => sb.Append("Plugin Id: ").Append("<b><a href='javascript:;' onclick='scrollToElement(\"").Append(pluginId3).Append("\")'>").Append(pluginId3).Append("</a></b>").Append("<br/>"))
.AppendIf(moduleId3 != "UNKNOWN", sb => sb.Append("Module Id: ").Append("<b><a href='javascript:;' onclick='scrollToElement(\"").Append(moduleId3).Append("\")'>").Append(moduleId3).Append("</a></b>").Append("<br/>"))
.AppendIf(pluginId3 != "UNKNOWN", sb => sb.Append("Plugin Id: ").Append("<b><a href='javascript:;' onclick='scrollToElement(\"").Append(pluginId3).Append("\")'>").Append(pluginId3).Append("</a></b>").Append("<br/>"))
.Append("Method: ").Append(stacktrace.OriginalMethod.MethodFullDescription.EscapeGenerics()).Append("<br/>")
.AppendIf(stacktrace.OriginalMethod.ILInstructions.Count > 0, sb => sb
.Append(ContainerCode($"{id01}", "IL:", string.Join(Environment.NewLine, stacktrace.OriginalMethod.ILInstructions.Select(x => x.EscapeGenerics())))))
Expand Down Expand Up @@ -239,13 +240,13 @@ private static void AddInvolvedModules(CrashReportModel crashReport, StringBuild
foreach (var method in stacktrace.PatchMethods)
{
var harmonyPatch = method as MethodHarmonyPatch;

// Ignore blank transpilers used to force the jitter to skip inlining
if (method.MethodName == "BlankTranspiler") continue;
var moduleId2 = method.ModuleId ?? "UNKNOWN";
sbMain.Append("<li>")
.AppendIf(moduleId2 == "UNKNOWN", sb => sb.Append("Module Id: ").Append(moduleId2).Append("<br/>"))
.AppendIf(moduleId2 != "UNKNOWN", sb => sb.Append("Module Id: ").Append("<b><a href='javascript:;' onclick='scrollToElement(\"").Append(moduleId2).Append("\")'>").Append(moduleId2).Append("</a></b>").Append("<br/>"))
.AppendIf(moduleId2 == "UNKNOWN", sb => sb.Append("Module Id: ").Append(moduleId2).Append("<br/>"))
.AppendIf(moduleId2 != "UNKNOWN", sb => sb.Append("Module Id: ").Append("<b><a href='javascript:;' onclick='scrollToElement(\"").Append(moduleId2).Append("\")'>").Append(moduleId2).Append("</a></b>").Append("<br/>"))
.Append("Method: ").Append(method.MethodFullDescription.EscapeGenerics()).Append("<br/>")
.AppendIf(harmonyPatch is not null, sb => sb.Append("Harmony Patch Type: ").Append(harmonyPatch!.PatchType).Append("<br/>"))
.Append("</li>");
Expand Down Expand Up @@ -283,13 +284,13 @@ private static void AddInvolvedPlugins(CrashReportModel crashReport, StringBuild
foreach (var method in stacktrace.PatchMethods)
{
var harmonyPatch = method as MethodHarmonyPatch;

// Ignore blank transpilers used to force the jitter to skip inlining
if (method.MethodName == "BlankTranspiler") continue;
var pluginId2 = method.LoaderPluginId ?? "UNKNOWN";
sbMain.Append("<li>")
.AppendIf(pluginId2 == "UNKNOWN", sb => sb.Append("Plugin Id: ").Append(pluginId2).Append("<br/>"))
.AppendIf(pluginId2 != "UNKNOWN", sb => sb.Append("Plugin Id: ").Append("<b><a href='javascript:;' onclick='scrollToElement(\"").Append(pluginId2).Append("\")'>").Append(pluginId2).Append("</a></b>").Append("<br/>"))
.AppendIf(pluginId2 == "UNKNOWN", sb => sb.Append("Plugin Id: ").Append(pluginId2).Append("<br/>"))
.AppendIf(pluginId2 != "UNKNOWN", sb => sb.Append("Plugin Id: ").Append("<b><a href='javascript:;' onclick='scrollToElement(\"").Append(pluginId2).Append("\")'>").Append(pluginId2).Append("</a></b>").Append("<br/>"))
.Append("Method: ").Append(method.MethodFullDescription.EscapeGenerics()).Append("<br/>")
.AppendIf(harmonyPatch is not null, sb => sb.Append("Harmony Patch Type: ").Append(harmonyPatch!.PatchType).Append("<br/>"))
.Append("</li>");
Expand Down Expand Up @@ -463,10 +464,10 @@ void AppendAdditionalAssemblies(ModuleModel module)
{
if (module.Capabilities.Count == 0)
sb.Append("<li>").Append("None").Append("</li>");

foreach (var capability in module.Capabilities)
sb.Append("<li>").Append(capability).Append("</li>");

return sb;
})
.Append("</ul>")
Expand All @@ -488,7 +489,7 @@ void AppendAdditionalAssemblies(ModuleModel module)

return moduleBuilder.ToString();
}

private static string GetLoadedBLSEPluginsHtml(CrashReportModel crashReport)
{
var moduleBuilder = new StringBuilder();
Expand Down Expand Up @@ -572,8 +573,8 @@ void AppendPatches(string name, IEnumerable<HarmonyPatchModel> patches)
var hasBefore = patch.Before.Count > 0;
var hasAfter = patch.After.Count > 0;
patchBuilder.Append("<li>")
.AppendIf(moduleId != "UNKNOWN", sb => sb.Append("Module Id: ").Append("<b><a href='javascript:;' onclick='scrollToElement(\"").Append(moduleId).Append("\")'>").Append(moduleId).Append("</a></b>").Append("; "))
.AppendIf(pluginId != "UNKNOWN", sb => sb.Append("Plugin Id: ").Append("<b><a href='javascript:;' onclick='scrollToElement(\"").Append(pluginId).Append("\")'>").Append(pluginId).Append("</a></b>").Append("; "))
.AppendIf(moduleId != "UNKNOWN", sb => sb.Append("Module Id: ").Append("<b><a href='javascript:;' onclick='scrollToElement(\"").Append(moduleId).Append("\")'>").Append(moduleId).Append("</a></b>").Append("; "))
.AppendIf(pluginId != "UNKNOWN", sb => sb.Append("Plugin Id: ").Append("<b><a href='javascript:;' onclick='scrollToElement(\"").Append(pluginId).Append("\")'>").Append(pluginId).Append("</a></b>").Append("; "))
.Append("Owner: ").Append(patch.Owner).Append("; ")
.Append("Namespace: ").Append(patch.Namespace).Append("; ")
.AppendIf(hasIndex, sb => sb.Append("Index: ").Append(patch.Index).Append("; "))
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
using BUTR.CrashReport.Models;
using BUTR.CrashReport.Renderer.Html.Utils;

namespace BUTR.CrashReport.Renderer.Html.Extensions;

/// <summary>
/// Extensions for <inheritdoc cref="BUTR.CrashReport.Models.AssemblyModel"/>
/// </summary>
internal static class AssemblyModelExtensions
{
/// <summary>
/// <inheritdoc cref="System.Reflection.AssemblyName.FullName"/>
/// </summary>
/// <returns><inheritdoc cref="System.Reflection.AssemblyName.FullName"/></returns>
public static string GetFullName(this AssemblyModel model) =>
AssemblyNameFormatter.ComputeDisplayName(model.Id.Name, model.Id.Version, model.CultureName, model.Id.PublicKeyToken);
}
4 changes: 2 additions & 2 deletions src/BUTR.CrashReport.Renderer.Html/StringBuilderExtensions.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
Expand All @@ -10,7 +10,7 @@ internal static class StringBuilderExtensions
public static string EscapeGenerics(this string str) => str.Replace("<", "&lt;").Replace(">", "&gt;");

public static StringBuilder AppendIf(this StringBuilder builder, Func<StringBuilder, StringBuilder> lambda) => lambda(builder);

public static StringBuilder AppendJoin(this StringBuilder builder, string separator, IEnumerable<string> lines) => AppendJoinIf(builder, true, separator, lines.ToArray());
public static StringBuilder AppendJoin(this StringBuilder builder, char separator, IEnumerable<string> lines) => AppendJoinIf(builder, true, separator, lines.ToArray());
public static StringBuilder AppendJoinIf(this StringBuilder builder, bool condition, string separator, IReadOnlyList<string> lines)
Expand Down
98 changes: 98 additions & 0 deletions src/BUTR.CrashReport.Renderer.Html/Utils/AssemblyNameFormatter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;

namespace BUTR.CrashReport.Renderer.Html.Utils;

internal static class AssemblyNameFormatter
{
public static string ComputeDisplayName(string? name, string? version, string? cultureName, string? publicKeyToken)
{
if (name == string.Empty)
throw new FileLoadException();

var sb = new StringBuilder();
if (name != null)
{
sb.AppendQuoted(name);
}

if (version != null)
{
sb.Append(", Version=");
sb.Append(version);
}

if (cultureName != null)
{
if (cultureName == string.Empty)
cultureName = "neutral";
sb.Append(", Culture=");
sb.AppendQuoted(cultureName);
}

if (publicKeyToken != null)
{
if (publicKeyToken == string.Empty)
publicKeyToken = "null";
sb.Append(", PublicKeyToken=").Append(publicKeyToken);
}

// NOTE: By design (desktop compat) AssemblyName.FullName and ToString() do not include ProcessorArchitecture.

return sb.ToString();
}

private static void AppendQuoted(this StringBuilder sb, string s)
{
var needsQuoting = false;
const char quoteChar = '\"';

//@todo: App-compat: You can use double or single quotes to quote a name, and Fusion (or rather the IdentityAuthority) picks one
// by some algorithm. Rather than guess at it, I'll just use double-quote consistently.
if (s != s.Trim() || s.Contains("\"") || s.Contains("\'"))
needsQuoting = true;

if (needsQuoting)
sb.Append(quoteChar);

for (var i = 0; i < s.Length; i++)
{
var addedEscape = false;
foreach (var kv in EscapeSequences)
{
var key = kv.Key;
var escapeReplacement = kv.Value;

if (s[i] != escapeReplacement[0])
continue;
if (s.Length - i < escapeReplacement.Length)
continue;
if (s.Substring(i, escapeReplacement.Length) == escapeReplacement)
{
sb.Append('\\');
sb.Append(key);
addedEscape = true;
}
}

if (!addedEscape)
sb.Append(s[i]);
}

if (needsQuoting)
sb.Append(quoteChar);
}

private static readonly KeyValuePair<char, string>[] EscapeSequences =
[
new('\\', "\\"),
new(',', ","),
new('=', "="),
new('\'', "'"),
new('\"', "\""),
new('n', Environment.NewLine),
new('t', "\t")
];
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFrameworks>net45;netcoreapp3.0</TargetFrameworks>
<UseWindowsForms>true</UseWindowsForms>
<LangVersion>preview</LangVersion>
<Nullable>enable</Nullable>
</PropertyGroup>

<ItemGroup>
<ProjectReference Include="..\BUTR.CrashReport.Models\BUTR.CrashReport.Models.csproj" />
</ItemGroup>

<ItemGroup>
<Reference Include="System.Windows.Forms" Condition="'$(TargetFramework)' == 'net45'" />
</ItemGroup>

</Project>
Loading

0 comments on commit df111f9

Please sign in to comment.