From 0040db1153cf0f4cf0ffa2ba9e5598ad3602f5a5 Mon Sep 17 00:00:00 2001 From: Alex Barney Date: Fri, 24 Jan 2020 15:11:31 -0700 Subject: [PATCH] Add IResultLogger and IResultNameResolver --- src/LibHac/Fs/ResultFs.cs | 60 ++++---- src/LibHac/Kvdb/ResultKvdb.cs | 10 +- src/LibHac/Result.cs | 108 +++++++------ src/hactoolnet/Program.cs | 20 +-- src/hactoolnet/ResultLog.cs | 87 ----------- src/hactoolnet/ResultLogger.cs | 218 +++++++++++++++++++++++++++ src/hactoolnet/ResultNameResolver.cs | 37 +++++ 7 files changed, 365 insertions(+), 175 deletions(-) delete mode 100644 src/hactoolnet/ResultLog.cs create mode 100644 src/hactoolnet/ResultLogger.cs create mode 100644 src/hactoolnet/ResultNameResolver.cs diff --git a/src/LibHac/Fs/ResultFs.cs b/src/LibHac/Fs/ResultFs.cs index ba3a661e..016841a8 100644 --- a/src/LibHac/Fs/ResultFs.cs +++ b/src/LibHac/Fs/ResultFs.cs @@ -10,8 +10,8 @@ namespace LibHac.Fs public static Result.Base PathAlreadyExists => new Result.Base(ModuleFs, 2); public static Result.Base TargetLocked => new Result.Base(ModuleFs, 7); public static Result.Base DirectoryNotEmpty => new Result.Base(ModuleFs, 8); - public static Result.Base InsufficientFreeSpace => new Result.Base(ResultFs.ModuleFs, 30, 45); - public static Result.Base InsufficientFreeSpaceBis => new Result.Base(ResultFs.ModuleFs, 34, 38); + public static Result.Base InsufficientFreeSpace => new Result.Base(ModuleFs, 30, 45); + public static Result.Base InsufficientFreeSpaceBis => new Result.Base(ModuleFs, 34, 38); public static Result.Base InsufficientFreeSpaceBisCalibration => new Result.Base(ModuleFs, 35); public static Result.Base InsufficientFreeSpaceBisSafe => new Result.Base(ModuleFs, 36); public static Result.Base InsufficientFreeSpaceBisUser => new Result.Base(ModuleFs, 37); @@ -23,10 +23,10 @@ namespace LibHac.Fs public static Result.Base TargetNotFound => new Result.Base(ModuleFs, 1002); public static Result.Base ExternalKeyNotFound => new Result.Base(ModuleFs, 1004); - public static Result.Base SdCardAccessFailed { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new Result.Base(ResultFs.ModuleFs, 2000, 2499); } + public static Result.Base SdCardAccessFailed { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new Result.Base(ModuleFs, 2000, 2499); } public static Result.Base SdCardNotPresent => new Result.Base(ModuleFs, 2001); - public static Result.Base GameCardAccessFailed { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new Result.Base(ResultFs.ModuleFs, 2500, 2999); } + public static Result.Base GameCardAccessFailed { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new Result.Base(ModuleFs, 2500, 2999); } public static Result.Base InvalidBufferForGameCard => new Result.Base(ModuleFs, 2503); public static Result.Base GameCardNotInserted => new Result.Base(ModuleFs, 2520); @@ -41,20 +41,20 @@ namespace LibHac.Fs public static Result.Base SaveDataPathAlreadyExists => new Result.Base(ModuleFs, 3003); public static Result.Base OutOfRange => new Result.Base(ModuleFs, 3005); - public static Result.Base AllocationMemoryFailed { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new Result.Base(ResultFs.ModuleFs, 3200, 3499); } + public static Result.Base AllocationMemoryFailed { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new Result.Base(ModuleFs, 3200, 3499); } public static Result.Base AesXtsFileFileStorageAllocationError => new Result.Base(ModuleFs, 3312); public static Result.Base AesXtsFileXtsStorageAllocationError => new Result.Base(ModuleFs, 3313); public static Result.Base AesXtsFileAlignmentStorageAllocationError => new Result.Base(ModuleFs, 3314); public static Result.Base AesXtsFileStorageFileAllocationError => new Result.Base(ModuleFs, 3315); public static Result.Base AesXtsFileSubStorageAllocationError => new Result.Base(ModuleFs, 3383); - public static Result.Base MmcAccessFailed { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new Result.Base(ResultFs.ModuleFs, 3500, 3999); } + public static Result.Base MmcAccessFailed { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new Result.Base(ModuleFs, 3500, 3999); } - public static Result.Base DataCorrupted { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new Result.Base(ResultFs.ModuleFs, 4000, 4999); } - public static Result.Base RomCorrupted { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new Result.Base(ResultFs.ModuleFs, 4001, 4299); } + public static Result.Base DataCorrupted { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new Result.Base(ModuleFs, 4000, 4999); } + public static Result.Base RomCorrupted { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new Result.Base(ModuleFs, 4001, 4299); } public static Result.Base InvalidIndirectStorageSource => new Result.Base(ModuleFs, 4023); - public static Result.Base SaveDataCorrupted { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new Result.Base(ResultFs.ModuleFs, 4301, 4499); } + public static Result.Base SaveDataCorrupted { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new Result.Base(ModuleFs, 4301, 4499); } public static Result.Base Result4302 => new Result.Base(ModuleFs, 4302); public static Result.Base InvalidSaveDataEntryType => new Result.Base(ModuleFs, 4303); public static Result.Base InvalidSaveDataHeader => new Result.Base(ModuleFs, 4315); @@ -70,34 +70,34 @@ namespace LibHac.Fs public static Result.Base SaveDataFileTableCorrupted => new Result.Base(ModuleFs, 4463); public static Result.Base AllocationTableIteratedRangeEntry => new Result.Base(ModuleFs, 4464); - public static Result.Base NcaCorrupted { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new Result.Base(ResultFs.ModuleFs, 4501, 4599); } + public static Result.Base NcaCorrupted { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new Result.Base(ModuleFs, 4501, 4599); } - public static Result.Base IntegrityVerificationStorageCorrupted { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new Result.Base(ResultFs.ModuleFs, 4601, 4639); } + public static Result.Base IntegrityVerificationStorageCorrupted { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new Result.Base(ModuleFs, 4601, 4639); } public static Result.Base Result4602 => new Result.Base(ModuleFs, 4602); public static Result.Base Result4603 => new Result.Base(ModuleFs, 4603); public static Result.Base InvalidHashInIvfc => new Result.Base(ModuleFs, 4604); public static Result.Base IvfcHashIsEmpty => new Result.Base(ModuleFs, 4612); public static Result.Base InvalidHashInIvfcTopLayer => new Result.Base(ModuleFs, 4613); - public static Result.Base PartitionFileSystemCorrupted { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new Result.Base(ResultFs.ModuleFs, 4641, 4659); } + public static Result.Base PartitionFileSystemCorrupted { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new Result.Base(ModuleFs, 4641, 4659); } public static Result.Base InvalidPartitionFileSystemHashOffset => new Result.Base(ModuleFs, 4642); public static Result.Base InvalidPartitionFileSystemHash => new Result.Base(ModuleFs, 4643); public static Result.Base InvalidPartitionFileSystemMagic => new Result.Base(ModuleFs, 4644); public static Result.Base InvalidHashedPartitionFileSystemMagic => new Result.Base(ModuleFs, 4645); public static Result.Base InvalidPartitionFileSystemEntryNameOffset => new Result.Base(ModuleFs, 4646); - public static Result.Base BuiltInStorageCorrupted { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new Result.Base(ResultFs.ModuleFs, 4661, 4679); } + public static Result.Base BuiltInStorageCorrupted { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new Result.Base(ModuleFs, 4661, 4679); } public static Result.Base Result4662 => new Result.Base(ModuleFs, 4662); - public static Result.Base FatFileSystemCorrupted { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new Result.Base(ResultFs.ModuleFs, 4681, 4699); } - public static Result.Base HostFileSystemCorrupted { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new Result.Base(ResultFs.ModuleFs, 4701, 4719); } + public static Result.Base FatFileSystemCorrupted { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new Result.Base(ModuleFs, 4681, 4699); } + public static Result.Base HostFileSystemCorrupted { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new Result.Base(ModuleFs, 4701, 4719); } - public static Result.Base DatabaseCorrupted { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new Result.Base(ResultFs.ModuleFs, 4721, 4739); } + public static Result.Base DatabaseCorrupted { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new Result.Base(ModuleFs, 4721, 4739); } public static Result.Base SaveDataAllocationTableCorruptedInternal => new Result.Base(ModuleFs, 4722); public static Result.Base SaveDataFileTableCorruptedInternal => new Result.Base(ModuleFs, 4723); public static Result.Base AllocationTableIteratedRangeEntryInternal => new Result.Base(ModuleFs, 4724); - public static Result.Base AesXtsFileSystemCorrupted { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new Result.Base(ResultFs.ModuleFs, 4741, 4759); } + public static Result.Base AesXtsFileSystemCorrupted { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new Result.Base(ModuleFs, 4741, 4759); } public static Result.Base AesXtsFileHeaderTooShort => new Result.Base(ModuleFs, 4742); public static Result.Base AesXtsFileHeaderInvalidKeys => new Result.Base(ModuleFs, 4743); public static Result.Base AesXtsFileHeaderInvalidMagic => new Result.Base(ModuleFs, 4744); @@ -106,15 +106,15 @@ namespace LibHac.Fs public static Result.Base AesXtsFileHeaderInvalidKeysInRenameFile => new Result.Base(ModuleFs, 4747); public static Result.Base AesXtsFileHeaderInvalidKeysInSetSize => new Result.Base(ModuleFs, 4748); - public static Result.Base SaveDataTransferDataCorrupted { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new Result.Base(ResultFs.ModuleFs, 4761, 4769); } - public static Result.Base SignedSystemPartitionDataCorrupted { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new Result.Base(ResultFs.ModuleFs, 4771, 4779); } + public static Result.Base SaveDataTransferDataCorrupted { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new Result.Base(ModuleFs, 4761, 4769); } + public static Result.Base SignedSystemPartitionDataCorrupted { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new Result.Base(ModuleFs, 4771, 4779); } public static Result.Base GameCardLogoDataCorrupted => new Result.Base(ModuleFs, 4781); - public static Result.Base Range4811To4819 { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new Result.Base(ResultFs.ModuleFs, 4811, 4819); } + public static Result.Base Range4811To4819 { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new Result.Base(ModuleFs, 4811, 4819); } public static Result.Base Result4812 => new Result.Base(ModuleFs, 4812); - public static Result.Base Unexpected { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new Result.Base(ResultFs.ModuleFs, 5000, 5999); } + public static Result.Base Unexpected { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new Result.Base(ModuleFs, 5000, 5999); } public static Result.Base UnexpectedErrorInHostFileFlush => new Result.Base(ModuleFs, 5307); public static Result.Base UnexpectedErrorInHostFileGetSize => new Result.Base(ModuleFs, 5308); @@ -131,7 +131,7 @@ namespace LibHac.Fs public static Result.Base DirectoryUnobtainable => new Result.Base(ModuleFs, 6006); public static Result.Base NotNormalized => new Result.Base(ModuleFs, 6007); - public static Result.Base InvalidPathForOperation { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new Result.Base(ResultFs.ModuleFs, 6030, 6059); } + public static Result.Base InvalidPathForOperation { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new Result.Base(ModuleFs, 6030, 6059); } public static Result.Base DirectoryNotDeletable => new Result.Base(ModuleFs, 6031); public static Result.Base DestinationIsSubPathOfSource => new Result.Base(ModuleFs, 6032); public static Result.Base PathNotFoundInSaveDataFileTable => new Result.Base(ModuleFs, 6033); @@ -145,16 +145,16 @@ namespace LibHac.Fs public static Result.Base ExtensionSizeInvalid => new Result.Base(ModuleFs, 6067); public static Result.Base ReadOldSaveDataInfoReader => new Result.Base(ModuleFs, 6068); - public static Result.Base InvalidEnumValue { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new Result.Base(ResultFs.ModuleFs, 6080, 6099); } + public static Result.Base InvalidEnumValue { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new Result.Base(ModuleFs, 6080, 6099); } public static Result.Base InvalidSaveDataState => new Result.Base(ModuleFs, 6081); public static Result.Base InvalidSaveDataSpaceId => new Result.Base(ModuleFs, 6082); - public static Result.Base InvalidOperationForOpenMode { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new Result.Base(ResultFs.ModuleFs, 6200, 6299); } + public static Result.Base InvalidOperationForOpenMode { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new Result.Base(ModuleFs, 6200, 6299); } public static Result.Base FileExtensionWithoutOpenModeAllowAppend => new Result.Base(ModuleFs, 6201); public static Result.Base InvalidOpenModeForRead => new Result.Base(ModuleFs, 6202); public static Result.Base InvalidOpenModeForWrite => new Result.Base(ModuleFs, 6203); - public static Result.Base UnsupportedOperation { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new Result.Base(ResultFs.ModuleFs, 6300, 6399); } + public static Result.Base UnsupportedOperation { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new Result.Base(ModuleFs, 6300, 6399); } public static Result.Base SubStorageNotResizable => new Result.Base(ModuleFs, 6302); public static Result.Base SubStorageNotResizableMiddleOfFile => new Result.Base(ModuleFs, 6303); public static Result.Base UnsupportedOperationInMemoryStorageSetSize => new Result.Base(ModuleFs, 6304); @@ -176,7 +176,7 @@ namespace LibHac.Fs public static Result.Base UnsupportedOperationInPartitionFileSetSize => new Result.Base(ModuleFs, 6376); public static Result.Base UnsupportedOperationIdInPartitionFileSystem => new Result.Base(ModuleFs, 6377); - public static Result.Base PermissionDenied { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new Result.Base(ResultFs.ModuleFs, 6400, 6449); } + public static Result.Base PermissionDenied { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new Result.Base(ModuleFs, 6400, 6449); } public static Result.Base ExternalKeyAlreadyRegistered => new Result.Base(ModuleFs, 6452); public static Result.Base WriteStateUnflushed => new Result.Base(ModuleFs, 6454); @@ -184,17 +184,17 @@ namespace LibHac.Fs public static Result.Base AllocatorAlignmentViolation => new Result.Base(ModuleFs, 6461); public static Result.Base UserNotExist => new Result.Base(ModuleFs, 6465); - public static Result.Base EntryNotFound { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new Result.Base(ResultFs.ModuleFs, 6600, 6699); } + public static Result.Base EntryNotFound { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new Result.Base(ModuleFs, 6600, 6699); } - public static Result.Base OutOfResource { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new Result.Base(ResultFs.ModuleFs, 6700, 6799); } + public static Result.Base OutOfResource { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new Result.Base(ModuleFs, 6700, 6799); } public static Result.Base MappingTableFull => new Result.Base(ModuleFs, 6706); public static Result.Base AllocationTableInsufficientFreeBlocks => new Result.Base(ModuleFs, 6707); public static Result.Base OpenCountLimit => new Result.Base(ModuleFs, 6709); - public static Result.Base MappingFailed { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new Result.Base(ResultFs.ModuleFs, 6800, 6899); } + public static Result.Base MappingFailed { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new Result.Base(ModuleFs, 6800, 6899); } public static Result.Base RemapStorageMapFull => new Result.Base(ModuleFs, 6811); - public static Result.Base BadState { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new Result.Base(ResultFs.ModuleFs, 6900, 6999); } + public static Result.Base BadState { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new Result.Base(ModuleFs, 6900, 6999); } public static Result.Base SubStorageNotInitialized => new Result.Base(ModuleFs, 6902); public static Result.Base NotMounted => new Result.Base(ModuleFs, 6905); public static Result.Base SaveDataIsExtending => new Result.Base(ModuleFs, 6906); diff --git a/src/LibHac/Kvdb/ResultKvdb.cs b/src/LibHac/Kvdb/ResultKvdb.cs index 8fec9a36..5c677e34 100644 --- a/src/LibHac/Kvdb/ResultKvdb.cs +++ b/src/LibHac/Kvdb/ResultKvdb.cs @@ -4,10 +4,10 @@ { public const int ModuleKvdb = 20; - public static Result TooLargeKeyOrDbFull => new Result(ModuleKvdb, 1); - public static Result KeyNotFound => new Result(ModuleKvdb, 2); - public static Result AllocationFailed => new Result(ModuleKvdb, 4); - public static Result InvalidKeyValue => new Result(ModuleKvdb, 5); - public static Result BufferInsufficient => new Result(ModuleKvdb, 6); + public static Result.Base TooLargeKeyOrDbFull => new Result.Base(ModuleKvdb, 1); + public static Result.Base KeyNotFound => new Result.Base(ModuleKvdb, 2); + public static Result.Base AllocationFailed => new Result.Base(ModuleKvdb, 4); + public static Result.Base InvalidKeyValue => new Result.Base(ModuleKvdb, 5); + public static Result.Base BufferInsufficient => new Result.Base(ModuleKvdb, 6); } } diff --git a/src/LibHac/Result.cs b/src/LibHac/Result.cs index 4297e6b8..9101d813 100644 --- a/src/LibHac/Result.cs +++ b/src/LibHac/Result.cs @@ -11,6 +11,10 @@ namespace LibHac public struct Result : IEquatable { private const BaseType SuccessValue = default; + public static Result Success => new Result(SuccessValue); + + private static IResultLogger Logger { get; set; } + private static IResultNameResolver NameResolver { get; set; } private const int ModuleBitsOffset = 0; private const int ModuleBitsCount = 9; @@ -27,8 +31,6 @@ namespace LibHac private readonly BaseType _value; - public static Result Success => new Result(SuccessValue); - public Result(BaseType value) { _value = GetBitsValue(value, ModuleBitsOffset, ModuleBitsCount + DescriptionBitsCount); @@ -53,18 +55,6 @@ namespace LibHac public bool IsSuccess() => _value == SuccessValue; public bool IsFailure() => !IsSuccess(); - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static BaseType GetBitsValue(BaseType value, int bitsOffset, int bitsCount) - { - return (value >> bitsOffset) & ~(~default(BaseType) << bitsCount); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static BaseType SetBitsValue(int value, int bitsOffset, int bitsCount) - { - return ((uint)value & ~(~default(BaseType) << bitsCount)) << bitsOffset; - } - public void ThrowIfFailure() { if (IsFailure()) @@ -74,8 +64,10 @@ namespace LibHac } /// - /// A function that can contain code for logging or debugging returned results. - /// Intended to be used when returning a non-zero Result: + /// Performs no action in release mode. + /// In debug mode, logs returned results using the set by . + ///
Intended to always be used when returning a non-zero . + ///

Example: /// return result.Log(); ///
/// The called value. @@ -87,7 +79,7 @@ namespace LibHac } /// - /// Same as , but for when one result is converted to another. + /// In debug mode, logs converted results using the set by . /// /// The original value. /// The called value. @@ -98,37 +90,17 @@ namespace LibHac return this; } - [Conditional("DEBUG")] - private void LogImpl() - { - LogCallback?.Invoke(this); - } - - [Conditional("DEBUG")] - private void LogConvertedImpl(Result originalResult) - { - ConvertedLogCallback?.Invoke(this, originalResult); - } - - 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; + IResultNameResolver resolver = NameResolver; - if (func == null) + if (resolver == null) { name = default; return false; } - return func(this, out name); + return resolver.TryResolveName(this, out name); } public string ToStringWithName() @@ -150,6 +122,40 @@ namespace LibHac public static bool operator ==(Result left, Result right) => left.Equals(right); public static bool operator !=(Result left, Result right) => !left.Equals(right); + public static void SetLogger(IResultLogger logger) + { + Logger = logger; + } + + public static void SetNameResolver(IResultNameResolver nameResolver) + { + NameResolver = nameResolver; + } + + [Conditional("DEBUG")] + private void LogImpl() + { + Logger?.LogResult(this); + } + + [Conditional("DEBUG")] + private void LogConvertedImpl(Result originalResult) + { + Logger?.LogConvertedResult(this, originalResult); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static BaseType GetBitsValue(BaseType value, int bitsOffset, int bitsCount) + { + return (value >> bitsOffset) & ~(~default(BaseType) << bitsCount); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static BaseType SetBitsValue(int value, int bitsOffset, int bitsCount) + { + return ((uint)value & ~(~default(BaseType) << bitsCount)) << bitsOffset; + } + public struct Base { private const int DescriptionEndBitsOffset = ReservedBitsOffset; @@ -175,6 +181,7 @@ namespace LibHac /// /// The representing the start of this result range. + /// If returning a from a function, use instead. /// public Result Value => new Result((BaseType)_value); @@ -198,9 +205,11 @@ namespace LibHac } /// - /// A function that can contain code for logging or debugging returned results. - /// Intended to be used when returning a non-zero Result: - /// return result.Log(); + /// Performs no action in release mode. + /// In debug mode, logs returned results using the set by . + ///
Intended to always be used when returning a non-zero . + ///

Example: + /// return ResultFs.PathNotFound.Log(); ///
/// The representing the start of this result range. public Result Log() @@ -209,7 +218,7 @@ namespace LibHac } /// - /// Same as , but for when one result is converted to another. + /// In debug mode, logs converted results using the set by . /// /// The original value. /// The representing the start of this result range. @@ -230,5 +239,16 @@ namespace LibHac return ((uint)value & ~(~default(ulong) << bitsCount)) << bitsOffset; } } + + public interface IResultLogger + { + public void LogResult(Result result); + public void LogConvertedResult(Result result, Result originalResult); + } + + public interface IResultNameResolver + { + public bool TryResolveName(Result result, out string name); + } } } diff --git a/src/hactoolnet/Program.cs b/src/hactoolnet/Program.cs index 3d0514d7..cb7f7672 100644 --- a/src/hactoolnet/Program.cs +++ b/src/hactoolnet/Program.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.IO; using System.Text; using LibHac; @@ -52,11 +52,11 @@ namespace hactoolnet if (ctx.Options == null) return false; StreamWriter logWriter = null; - StreamWriter resultWriter = null; + ResultLogger resultLogger = null; try { - Result.GetResultNameHandler = ResultLogFunctions.TryGetResultName; + Result.SetNameResolver(new ResultNameResolver()); using (var logger = new ProgressBar()) { @@ -76,11 +76,10 @@ namespace hactoolnet if (ctx.Options.ResultLog != null) { - resultWriter = new StreamWriter(ctx.Options.ResultLog); - ResultLogFunctions.LogWriter = resultWriter; + resultLogger = new ResultLogger(new StreamWriter(ctx.Options.ResultLog), + printStackTrace: true, printSourceInfo: true, combineRepeats: true); - Result.LogCallback = ResultLogFunctions.LogResult; - Result.ConvertedLogCallback = ResultLogFunctions.LogConvertedResult; + Result.SetLogger(resultLogger); } OpenKeyset(ctx); @@ -97,9 +96,12 @@ namespace hactoolnet finally { logWriter?.Dispose(); - resultWriter?.Dispose(); - ResultLogFunctions.LogWriter = null; + if (resultLogger != null) + { + Result.SetLogger(null); + resultLogger.Dispose(); + } } return true; diff --git a/src/hactoolnet/ResultLog.cs b/src/hactoolnet/ResultLog.cs deleted file mode 100644 index 348b4223..00000000 --- a/src/hactoolnet/ResultLog.cs +++ /dev/null @@ -1,87 +0,0 @@ -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 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 (ResultFs.PathNotFound.Includes(result) && - 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 GetResultNames() - { - var dict = new Dictionary(); - - 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); - } - } -} diff --git a/src/hactoolnet/ResultLogger.cs b/src/hactoolnet/ResultLogger.cs new file mode 100644 index 00000000..2ffe89de --- /dev/null +++ b/src/hactoolnet/ResultLogger.cs @@ -0,0 +1,218 @@ +using System; +using System.Diagnostics; +using System.IO; +using System.Reflection; +using LibHac; +using LibHac.Fs; + +namespace hactoolnet +{ + internal class ResultLogger : Result.IResultLogger, IDisposable + { + private TextWriter Writer { get; } + private bool PrintStackTrace { get; } + private bool PrintSourceInfo { get; } + private bool CombineRepeats { get; } + + private LogEntry _pendingEntry; + private bool LastEntryPrintedNewLine { get; set; } = true; + + public ResultLogger(TextWriter writer, bool printStackTrace, bool printSourceInfo, bool combineRepeats) + { + Writer = writer ?? throw new ArgumentNullException(nameof(writer)); + PrintStackTrace = printStackTrace; + PrintSourceInfo = printSourceInfo; + CombineRepeats = combineRepeats; + + bool isDebugMode = false; + CheckIfDebugMode(ref isDebugMode); + + // ReSharper disable once ConditionIsAlwaysTrueOrFalse + if (!isDebugMode) + { + Writer.WriteLine("The result log is only enabled when running in debug mode."); + } + } + + public void LogResult(Result result) + { + StackTrace st = GetStackTrace(); + 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 (ResultFs.PathNotFound.Includes(result) && + typeof(IFileSystem).IsAssignableFrom(method.DeclaringType) && + method.Name.StartsWith(nameof(IFileSystem.GetEntryType)) || + method.Name.StartsWith(nameof(IAttributeFileSystem.GetFileAttributes))) + { + return; + } + + AddLogEntry(new LogEntry(result, st)); + } + + public void LogConvertedResult(Result result, Result originalResult) + { + StackTrace st = GetStackTrace(); + + AddLogEntry(new LogEntry(result, st, originalResult)); + } + + private void AddLogEntry(LogEntry entry) + { + if (CombineRepeats && _pendingEntry.IsRepeat(entry, PrintStackTrace && !entry.IsConvertedResult)) + { + _pendingEntry.TimesCalled++; + return; + } + + PrintPendingEntry(); + + if (CombineRepeats) + { + _pendingEntry = entry; + } + else + { + PrintLogEntry(entry); + } + } + + private void PrintPendingEntry() + { + if (_pendingEntry.StackTrace != null) + { + PrintLogEntry(_pendingEntry); + _pendingEntry = default; + } + } + + private void PrintLogEntry(LogEntry entry) + { + MethodBase method = entry.StackTrace.GetFrame(0).GetMethod(); + string methodName = $"{method.DeclaringType?.FullName}.{method.Name}"; + + bool printStackTrace = PrintStackTrace && !entry.IsConvertedResult; + + // Make sure there's a new line if printing a stack trace + // A stack trace includes a new line at the end of it, so add the new line only if needed + string entryText = printStackTrace && !LastEntryPrintedNewLine ? Environment.NewLine : string.Empty; + + string lineNumber = entry.LineNumber > 0 ? $":line{entry.LineNumber}" : string.Empty; + + if (entry.IsConvertedResult) + { + entryText += $"{entry.OriginalResult.ToStringWithName()} was converted to {entry.Result.ToStringWithName()} by {methodName}{lineNumber}"; + } + else + { + entryText += $"{entry.Result.ToStringWithName()} was returned by {methodName}{lineNumber}"; + } + + if (entry.TimesCalled > 1) + { + entryText += $" {entry.TimesCalled} times"; + } + + Writer.WriteLine(entryText); + + if (printStackTrace) + { + Writer.WriteLine(entry.StackTraceText); + } + + LastEntryPrintedNewLine = printStackTrace; + } + + // Returns the stack trace starting at the method that called Log() + private StackTrace GetStackTrace() + { + var st = new StackTrace(); + int framesToSkip = 0; + + for (; framesToSkip < st.FrameCount; framesToSkip++) + { + Type declaringType = st.GetFrame(framesToSkip)?.GetMethod()?.DeclaringType; + + if (declaringType == null) + { + framesToSkip = 0; + break; + } + + if (declaringType != typeof(ResultLogger) && + declaringType != typeof(Result) && + declaringType != typeof(Result.Base)) + { + break; + } + } + + return new StackTrace(framesToSkip, PrintSourceInfo); + } + + // You can't negate a conditional attribute, so this is a hacky workaround + [Conditional("DEBUG")] + // ReSharper disable once RedundantAssignment + private void CheckIfDebugMode(ref bool isDebugMode) + { + isDebugMode = true; + } + + public void Dispose() + { + PrintPendingEntry(); + Writer.Dispose(); + } + + private struct LogEntry + { + public Result Result { get; } + public Result OriginalResult { get; } + public string CallingMethod { get; } + public StackTrace StackTrace { get; } + public string StackTraceText { get; } + + // Line number will be 0 if there's no source info + public int LineNumber { get; } + public int TimesCalled { get; set; } + public bool IsConvertedResult { get; } + + public LogEntry(Result result, StackTrace stackTrace) : this(result, stackTrace, false, default) { } + public LogEntry(Result result, StackTrace stackTrace, Result originalResult) : this(result, stackTrace, true, originalResult) { } + + private LogEntry(Result result, StackTrace stackTrace, bool isConverted, Result originalResult) + { + Result = result; + StackTrace = stackTrace; + IsConvertedResult = isConverted; + OriginalResult = originalResult; + + MethodBase method = stackTrace.GetFrame(0).GetMethod(); + CallingMethod = $"{method.DeclaringType?.FullName}.{method.Name}"; + + StackTraceText = stackTrace.ToString(); + LineNumber = stackTrace.GetFrame(0).GetFileLineNumber(); + TimesCalled = 1; + } + + public bool IsRepeat(LogEntry other, bool compareByStackTrace) + { + if (Result != other.Result || + IsConvertedResult != other.IsConvertedResult || + (IsConvertedResult && OriginalResult != other.OriginalResult)) + { + return false; + } + + if (compareByStackTrace) + { + return StackTraceText == other.StackTraceText; + } + + return LineNumber == other.LineNumber && CallingMethod == other.CallingMethod; + } + } + } +} diff --git a/src/hactoolnet/ResultNameResolver.cs b/src/hactoolnet/ResultNameResolver.cs new file mode 100644 index 00000000..ae2e2d4f --- /dev/null +++ b/src/hactoolnet/ResultNameResolver.cs @@ -0,0 +1,37 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using LibHac; + +namespace hactoolnet +{ + internal class ResultNameResolver : Result.IResultNameResolver + { + private Lazy> ResultNames { get; } = new Lazy>(GetResultNames); + + public bool TryResolveName(Result result, out string name) + { + return ResultNames.Value.TryGetValue(result, out name); + } + + private static Dictionary GetResultNames() + { + var dict = new Dictionary(); + + Assembly assembly = typeof(Result).Assembly; + + foreach (TypeInfo type in assembly.DefinedTypes.Where(x => x.Name.Contains("Result"))) + foreach (PropertyInfo property in type.DeclaredProperties + .Where(x => x.PropertyType == typeof(Result.Base) && x.GetMethod.IsStatic && x.SetMethod == null)) + { + Result value = ((Result.Base)property.GetValue(null, null)).Value; + string name = $"{type.Name}{property.Name}"; + + dict[value] = name; + } + + return dict; + } + } +}