Skip to content

Commit

Permalink
Add Result methods for debugging (Thealexbarney#90)
Browse files Browse the repository at this point in the history
- Allow setting a callback function for when Result.Log is called.
- Allow setting a function that returns a name for a Result value.
- Print Result name in error messages
  • Loading branch information
Thealexbarney authored Oct 26, 2019
1 parent 172817a commit cccb811
Show file tree
Hide file tree
Showing 6 changed files with 145 additions and 4 deletions.
4 changes: 2 additions & 2 deletions src/LibHac/HorizonResultException.cs
Original file line number Diff line number Diff line change
Expand Up @@ -82,10 +82,10 @@ public override string Message
{
if (!string.IsNullOrWhiteSpace(InnerMessage))
{
return $"{ResultValue.ErrorCode}: {InnerMessage}";
return $"{ResultValue.ToStringWithName()}: {InnerMessage}";
}

return ResultValue.ErrorCode;
return ResultValue.ToStringWithName();
}
}
}
Expand Down
39 changes: 38 additions & 1 deletion src/LibHac/Result.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
namespace LibHac
{
[Serializable]
[DebuggerDisplay("{ToString()}")]
[DebuggerDisplay("{ToStringWithName(),nq}")]
public struct Result : IEquatable<Result>
{
public readonly int Value;
Expand Down Expand Up @@ -42,6 +42,9 @@ public void ThrowIfFailure()
/// <returns>The called <see cref="Result"/> value.</returns>
public Result Log()
{
#if DEBUG
LogCallback?.Invoke(this);
#endif
return this;
}

Expand All @@ -52,9 +55,43 @@ public Result Log()
/// <returns>The called <see cref="Result"/> value.</returns>
public Result LogConverted(Result originalResult)
{
#if DEBUG
ConvertedLogCallback?.Invoke(this, originalResult);
#endif
return this;
}

public delegate void ResultLogger(Result result);
public delegate void ConvertedResultLogger(Result result, Result originalResult);
public delegate bool ResultNameGetter(Result result, out string name);

public static ResultLogger LogCallback { get; set; }
public static ConvertedResultLogger ConvertedLogCallback { get; set; }
public static ResultNameGetter GetResultNameHandler { get; set; }

public bool TryGetResultName(out string name)
{
ResultNameGetter func = GetResultNameHandler;

if (func == null)
{
name = default;
return false;
}

return func(this, out name);
}

public string ToStringWithName()
{
if (TryGetResultName(out string name))
{
return $"{name} ({ErrorCode})";
}

return ErrorCode;
}

public override string ToString()
{
return IsSuccess() ? "Success" : ErrorCode;
Expand Down
1 change: 1 addition & 0 deletions src/hactoolnet/CliParser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ internal static class CliParser
new CliOption("titlekeys", 1, (o, a) => o.TitleKeyFile = a[0]),
new CliOption("consolekeys", 1, (o, a) => o.ConsoleKeyFile = a[0]),
new CliOption("accesslog", 1, (o, a) => o.AccessLog = a[0]),
new CliOption("resultlog", 1, (o, a) => o.ResultLog = a[0]),
new CliOption("section0", 1, (o, a) => o.SectionOut[0] = a[0]),
new CliOption("section1", 1, (o, a) => o.SectionOut[1] = a[0]),
new CliOption("section2", 1, (o, a) => o.SectionOut[2] = a[0]),
Expand Down
1 change: 1 addition & 0 deletions src/hactoolnet/Options.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ internal class Options
public string TitleKeyFile;
public string ConsoleKeyFile;
public string AccessLog;
public string ResultLog;
public string[] SectionOut = new string[4];
public string[] SectionOutDir = new string[4];
public string HeaderOut;
Expand Down
17 changes: 16 additions & 1 deletion src/hactoolnet/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ public static int Main(string[] args)

if (ex.ResultValue != ex.InternalResultValue)
{
Console.Error.WriteLine($"Internal Code: {ex.InternalResultValue.ErrorCode}");
Console.Error.WriteLine($"Internal Code: {ex.InternalResultValue.ToStringWithName()}");
}

Console.Error.WriteLine();
Expand All @@ -52,9 +52,12 @@ private static bool Run(string[] args)
if (ctx.Options == null) return false;

StreamWriter logWriter = null;
StreamWriter resultWriter = null;

try
{
Result.GetResultNameHandler = ResultLogFunctions.TryGetResultName;

using (var logger = new ProgressBar())
{
ctx.Logger = logger;
Expand All @@ -71,6 +74,15 @@ private static bool Run(string[] args)
ctx.Horizon.Fs.SetAccessLogObject(accessLog);
}

if (ctx.Options.ResultLog != null)
{
resultWriter = new StreamWriter(ctx.Options.ResultLog);
ResultLogFunctions.LogWriter = resultWriter;

Result.LogCallback = ResultLogFunctions.LogResult;
Result.ConvertedLogCallback = ResultLogFunctions.LogConvertedResult;
}

OpenKeyset(ctx);

if (ctx.Options.RunCustom)
Expand All @@ -85,6 +97,9 @@ private static bool Run(string[] args)
finally
{
logWriter?.Dispose();
resultWriter?.Dispose();

ResultLogFunctions.LogWriter = null;
}

return true;
Expand Down
87 changes: 87 additions & 0 deletions src/hactoolnet/ResultLog.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using LibHac;
using LibHac.Fs;

namespace hactoolnet
{
public static class ResultLogFunctions
{
private static Dictionary<Result, string> ResultNames { get; } = GetResultNames();

public static TextWriter LogWriter { get; set; }

public static void LogResult(Result result)
{
if (LogWriter == null) return;

var st = new StackTrace(2, true);

if (st.FrameCount > 1)
{
MethodBase method = st.GetFrame(0).GetMethod();

// This result from these functions is usually noise because they
// are frequently used to detect if a file exists
if (result == ResultFs.PathNotFound &&
typeof(IFileSystem).IsAssignableFrom(method.DeclaringType) &&
method.Name.StartsWith(nameof(IFileSystem.GetEntryType)) ||
method.Name.StartsWith(nameof(IAttributeFileSystem.GetFileAttributes)))
{
// return;
}

string methodName = $"{method.DeclaringType?.FullName}.{method.Name}";

LogWriter.WriteLine($"{result.ToStringWithName()} returned by {methodName}");
LogWriter.WriteLine(st);
}
}

public static void LogConvertedResult(Result result, Result originalResult)
{
if (LogWriter == null) return;

var st = new StackTrace(2, false);

if (st.FrameCount > 1)
{
MethodBase method = st.GetFrame(0).GetMethod();

string methodName = $"{method.DeclaringType?.FullName}.{method.Name}";

LogWriter.WriteLine($"{originalResult.ToStringWithName()} was converted to {result.ToStringWithName()} by {methodName}");
}
}

public static Dictionary<Result, string> GetResultNames()
{
var dict = new Dictionary<Result, string>();

Assembly assembly = typeof(Result).Assembly;

foreach (Type type in assembly.GetTypes().Where(x => x.Name.Contains("Result")))
{
foreach (PropertyInfo property in type.GetProperties()
.Where(x => x.PropertyType == typeof(Result) && x.GetMethod.IsStatic && x.SetMethod == null))
{
var value = (Result)property.GetValue(null, null);
string name = $"{type.Name}{property.Name}";

dict.Add(value, name);
}
}

return dict;
}

public static bool TryGetResultName(Result result, out string name)
{
return ResultNames.TryGetValue(result, out name);
}
}
}

0 comments on commit cccb811

Please sign in to comment.