mirror of
https://github.com/Thealexbarney/LibHac.git
synced 2024-11-14 10:49:41 +01:00
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:
parent
172817a7d5
commit
cccb811293
6 changed files with 145 additions and 4 deletions
|
@ -82,10 +82,10 @@ namespace LibHac
|
|||
{
|
||||
if (!string.IsNullOrWhiteSpace(InnerMessage))
|
||||
{
|
||||
return $"{ResultValue.ErrorCode}: {InnerMessage}";
|
||||
return $"{ResultValue.ToStringWithName()}: {InnerMessage}";
|
||||
}
|
||||
|
||||
return ResultValue.ErrorCode;
|
||||
return ResultValue.ToStringWithName();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,7 +4,7 @@ using System.Diagnostics;
|
|||
namespace LibHac
|
||||
{
|
||||
[Serializable]
|
||||
[DebuggerDisplay("{ToString()}")]
|
||||
[DebuggerDisplay("{ToStringWithName(),nq}")]
|
||||
public struct Result : IEquatable<Result>
|
||||
{
|
||||
public readonly int Value;
|
||||
|
@ -42,6 +42,9 @@ namespace LibHac
|
|||
/// <returns>The called <see cref="Result"/> value.</returns>
|
||||
public Result Log()
|
||||
{
|
||||
#if DEBUG
|
||||
LogCallback?.Invoke(this);
|
||||
#endif
|
||||
return this;
|
||||
}
|
||||
|
||||
|
@ -52,9 +55,43 @@ namespace LibHac
|
|||
/// <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;
|
||||
|
|
|
@ -19,6 +19,7 @@ namespace hactoolnet
|
|||
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]),
|
||||
|
|
|
@ -16,6 +16,7 @@ namespace hactoolnet
|
|||
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;
|
||||
|
|
|
@ -25,7 +25,7 @@ namespace hactoolnet
|
|||
|
||||
if (ex.ResultValue != ex.InternalResultValue)
|
||||
{
|
||||
Console.Error.WriteLine($"Internal Code: {ex.InternalResultValue.ErrorCode}");
|
||||
Console.Error.WriteLine($"Internal Code: {ex.InternalResultValue.ToStringWithName()}");
|
||||
}
|
||||
|
||||
Console.Error.WriteLine();
|
||||
|
@ -52,9 +52,12 @@ namespace hactoolnet
|
|||
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;
|
||||
|
@ -71,6 +74,15 @@ namespace hactoolnet
|
|||
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)
|
||||
|
@ -85,6 +97,9 @@ namespace hactoolnet
|
|||
finally
|
||||
{
|
||||
logWriter?.Dispose();
|
||||
resultWriter?.Dispose();
|
||||
|
||||
ResultLogFunctions.LogWriter = null;
|
||||
}
|
||||
|
||||
return true;
|
||||
|
|
87
src/hactoolnet/ResultLog.cs
Normal file
87
src/hactoolnet/ResultLog.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue