Add Result methods for debugging (#90)

- 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
This commit is contained in:
Alex Barney 2019-10-25 22:13:09 -05:00 committed by GitHub
parent 172817a7d5
commit cccb811293
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 145 additions and 4 deletions

View file

@ -82,10 +82,10 @@ namespace LibHac
{ {
if (!string.IsNullOrWhiteSpace(InnerMessage)) if (!string.IsNullOrWhiteSpace(InnerMessage))
{ {
return $"{ResultValue.ErrorCode}: {InnerMessage}"; return $"{ResultValue.ToStringWithName()}: {InnerMessage}";
} }
return ResultValue.ErrorCode; return ResultValue.ToStringWithName();
} }
} }
} }

View file

@ -4,7 +4,7 @@ using System.Diagnostics;
namespace LibHac namespace LibHac
{ {
[Serializable] [Serializable]
[DebuggerDisplay("{ToString()}")] [DebuggerDisplay("{ToStringWithName(),nq}")]
public struct Result : IEquatable<Result> public struct Result : IEquatable<Result>
{ {
public readonly int Value; public readonly int Value;
@ -42,6 +42,9 @@ namespace LibHac
/// <returns>The called <see cref="Result"/> value.</returns> /// <returns>The called <see cref="Result"/> value.</returns>
public Result Log() public Result Log()
{ {
#if DEBUG
LogCallback?.Invoke(this);
#endif
return this; return this;
} }
@ -52,9 +55,43 @@ namespace LibHac
/// <returns>The called <see cref="Result"/> value.</returns> /// <returns>The called <see cref="Result"/> value.</returns>
public Result LogConverted(Result originalResult) public Result LogConverted(Result originalResult)
{ {
#if DEBUG
ConvertedLogCallback?.Invoke(this, originalResult);
#endif
return this; 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() public override string ToString()
{ {
return IsSuccess() ? "Success" : ErrorCode; return IsSuccess() ? "Success" : ErrorCode;

View file

@ -19,6 +19,7 @@ namespace hactoolnet
new CliOption("titlekeys", 1, (o, a) => o.TitleKeyFile = a[0]), new CliOption("titlekeys", 1, (o, a) => o.TitleKeyFile = a[0]),
new CliOption("consolekeys", 1, (o, a) => o.ConsoleKeyFile = a[0]), new CliOption("consolekeys", 1, (o, a) => o.ConsoleKeyFile = a[0]),
new CliOption("accesslog", 1, (o, a) => o.AccessLog = 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("section0", 1, (o, a) => o.SectionOut[0] = a[0]),
new CliOption("section1", 1, (o, a) => o.SectionOut[1] = a[0]), new CliOption("section1", 1, (o, a) => o.SectionOut[1] = a[0]),
new CliOption("section2", 1, (o, a) => o.SectionOut[2] = a[0]), new CliOption("section2", 1, (o, a) => o.SectionOut[2] = a[0]),

View file

@ -16,6 +16,7 @@ namespace hactoolnet
public string TitleKeyFile; public string TitleKeyFile;
public string ConsoleKeyFile; public string ConsoleKeyFile;
public string AccessLog; public string AccessLog;
public string ResultLog;
public string[] SectionOut = new string[4]; public string[] SectionOut = new string[4];
public string[] SectionOutDir = new string[4]; public string[] SectionOutDir = new string[4];
public string HeaderOut; public string HeaderOut;

View file

@ -25,7 +25,7 @@ namespace hactoolnet
if (ex.ResultValue != ex.InternalResultValue) if (ex.ResultValue != ex.InternalResultValue)
{ {
Console.Error.WriteLine($"Internal Code: {ex.InternalResultValue.ErrorCode}"); Console.Error.WriteLine($"Internal Code: {ex.InternalResultValue.ToStringWithName()}");
} }
Console.Error.WriteLine(); Console.Error.WriteLine();
@ -52,9 +52,12 @@ namespace hactoolnet
if (ctx.Options == null) return false; if (ctx.Options == null) return false;
StreamWriter logWriter = null; StreamWriter logWriter = null;
StreamWriter resultWriter = null;
try try
{ {
Result.GetResultNameHandler = ResultLogFunctions.TryGetResultName;
using (var logger = new ProgressBar()) using (var logger = new ProgressBar())
{ {
ctx.Logger = logger; ctx.Logger = logger;
@ -71,6 +74,15 @@ namespace hactoolnet
ctx.Horizon.Fs.SetAccessLogObject(accessLog); 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); OpenKeyset(ctx);
if (ctx.Options.RunCustom) if (ctx.Options.RunCustom)
@ -85,6 +97,9 @@ namespace hactoolnet
finally finally
{ {
logWriter?.Dispose(); logWriter?.Dispose();
resultWriter?.Dispose();
ResultLogFunctions.LogWriter = null;
} }
return true; return true;

View file

@ -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);
}
}
}