From cb8b088487f5e8b9a16e02ab23b0218f100f1dce Mon Sep 17 00:00:00 2001 From: Alex Barney Date: Tue, 12 May 2020 17:06:55 -0700 Subject: [PATCH] Misc additions and warning fixes (#134) * Run codegen and fix DeliveryCacheFileMetaEntry layout * Use KipReader instead of Kip * Ensure feature parity between U8String structs * Don't use the obsolete Nacp class * Add some null checks * Address ReSharper warnings * The Result structs should be readonly --- src/LibHac/Bcat/Detail/Ipc/IServiceCreator.cs | 4 +- .../Core/DeliveryCacheFileMetaEntry.cs | 3 +- src/LibHac/Bcat/ResultBcat.cs | 10 +++ src/LibHac/Common/U8SpanMutable.cs | 41 ++++++++++++- src/LibHac/Common/U8String.cs | 2 +- src/LibHac/Common/U8StringMutable.cs | 14 ++++- src/LibHac/Fs/InMemoryFileSystem.cs | 8 ++- src/LibHac/FsSystem/CachedStorage.cs | 11 +++- src/LibHac/FsSystem/StorageExtensions.cs | 6 ++ src/LibHac/HorizonClient.cs | 1 + src/LibHac/HorizonResultException.cs | 4 +- src/LibHac/Kip.cs | 11 ++-- src/LibHac/Loader/KipReader.cs | 61 ++++++++++++++++--- src/LibHac/MissingKeyException.cs | 2 +- src/LibHac/Result.cs | 4 +- src/LibHac/Sm/ServiceManager.cs | 1 + src/LibHac/Sm/ServiceName.cs | 2 + src/LibHac/SwitchFs.cs | 22 ++++--- src/hactoolnet/ProcessKip.cs | 11 ++-- src/hactoolnet/ProcessSwitchFs.cs | 20 +++--- src/hactoolnet/ResultLogger.cs | 26 ++++++-- src/hactoolnet/ResultNameResolver.cs | 9 ++- .../Fs/DirectorySaveDataFileSystemTests.cs | 6 +- .../Fs/FileSystemClientTests/ShimTests/Bis.cs | 2 +- tests/LibHac.Tests/ResultNameResolver.cs | 9 ++- 25 files changed, 228 insertions(+), 62 deletions(-) diff --git a/src/LibHac/Bcat/Detail/Ipc/IServiceCreator.cs b/src/LibHac/Bcat/Detail/Ipc/IServiceCreator.cs index 015d39ba..0b7c136a 100644 --- a/src/LibHac/Bcat/Detail/Ipc/IServiceCreator.cs +++ b/src/LibHac/Bcat/Detail/Ipc/IServiceCreator.cs @@ -1,6 +1,4 @@ -using LibHac.Ncm; - -namespace LibHac.Bcat.Detail.Ipc +namespace LibHac.Bcat.Detail.Ipc { public interface IServiceCreator { diff --git a/src/LibHac/Bcat/Detail/Service/Core/DeliveryCacheFileMetaEntry.cs b/src/LibHac/Bcat/Detail/Service/Core/DeliveryCacheFileMetaEntry.cs index 53d2392f..2ffa897a 100644 --- a/src/LibHac/Bcat/Detail/Service/Core/DeliveryCacheFileMetaEntry.cs +++ b/src/LibHac/Bcat/Detail/Service/Core/DeliveryCacheFileMetaEntry.cs @@ -6,7 +6,8 @@ namespace LibHac.Bcat.Detail.Service.Core internal struct DeliveryCacheFileMetaEntry { [FieldOffset(0x00)] public FileName Name; - [FieldOffset(0x20)] public long Size; + [FieldOffset(0x20)] public long Id; + [FieldOffset(0x28)] public long Size; [FieldOffset(0x30)] public Digest Digest; } } diff --git a/src/LibHac/Bcat/ResultBcat.cs b/src/LibHac/Bcat/ResultBcat.cs index 9a5fd6b1..c91295c1 100644 --- a/src/LibHac/Bcat/ResultBcat.cs +++ b/src/LibHac/Bcat/ResultBcat.cs @@ -21,10 +21,16 @@ namespace LibHac.Bcat public static Result.Base NotFound => new Result.Base(ModuleBcat, 2); /// Error code: 2122-0003; Inner value: 0x67a public static Result.Base TargetLocked => new Result.Base(ModuleBcat, 3); + /// Error code: 2122-0004; Inner value: 0x87a + public static Result.Base TargetAlreadyMounted => new Result.Base(ModuleBcat, 4); + /// Error code: 2122-0005; Inner value: 0xa7a + public static Result.Base TargetNotMounted => new Result.Base(ModuleBcat, 5); /// Error code: 2122-0006; Inner value: 0xc7a public static Result.Base AlreadyOpen => new Result.Base(ModuleBcat, 6); /// Error code: 2122-0007; Inner value: 0xe7a public static Result.Base NotOpen => new Result.Base(ModuleBcat, 7); + /// Error code: 2122-0008; Inner value: 0x107a + public static Result.Base InternetRequestDenied => new Result.Base(ModuleBcat, 8); /// Error code: 2122-0009; Inner value: 0x127a public static Result.Base ServiceOpenLimitReached => new Result.Base(ModuleBcat, 9); /// Error code: 2122-0010; Inner value: 0x147a @@ -33,10 +39,14 @@ namespace LibHac.Bcat public static Result.Base NetworkServiceAccountNotAvailable => new Result.Base(ModuleBcat, 31); /// Error code: 2122-0080; Inner value: 0xa07a public static Result.Base PassphrasePathNotFound => new Result.Base(ModuleBcat, 80); + /// Error code: 2122-0081; Inner value: 0xa27a + public static Result.Base DataVerificationFailed => new Result.Base(ModuleBcat, 81); /// Error code: 2122-0090; Inner value: 0xb47a public static Result.Base PermissionDenied => new Result.Base(ModuleBcat, 90); /// Error code: 2122-0091; Inner value: 0xb67a public static Result.Base AllocationFailed => new Result.Base(ModuleBcat, 91); + /// Error code: 2122-0098; Inner value: 0xc47a + public static Result.Base InvalidOperation => new Result.Base(ModuleBcat, 98); /// Error code: 2122-0204; Inner value: 0x1987a public static Result.Base InvalidDeliveryCacheStorageFile => new Result.Base(ModuleBcat, 204); /// Error code: 2122-0205; Inner value: 0x19a7a diff --git a/src/LibHac/Common/U8SpanMutable.cs b/src/LibHac/Common/U8SpanMutable.cs index c89d5c34..418fdccc 100644 --- a/src/LibHac/Common/U8SpanMutable.cs +++ b/src/LibHac/Common/U8SpanMutable.cs @@ -1,11 +1,13 @@ using System; using System.Diagnostics; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; using System.Text; namespace LibHac.Common { [DebuggerDisplay("{ToString()}")] - public ref struct U8SpanMutable + public readonly ref struct U8SpanMutable { private readonly Span _buffer; @@ -28,6 +30,29 @@ namespace LibHac.Common _buffer = Encoding.UTF8.GetBytes(value); } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public byte GetOrNull(int i) + { + byte value = 0; + + if ((uint)i < (uint)_buffer.Length) + { + value = GetUnsafe(i); + } + + return value; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public byte GetUnsafe(int i) + { +#if DEBUG + return _buffer[i]; +#else + return Unsafe.Add(ref MemoryMarshal.GetReference(_buffer), i); +#endif + } + public U8SpanMutable Slice(int start) { return new U8SpanMutable(_buffer.Slice(start)); @@ -56,6 +81,18 @@ namespace LibHac.Common return new U8StringMutable(_buffer.ToArray()); } - public bool IsNull() => _buffer == default; + /// + /// Checks if the has no buffer. + /// + /// if the span has no buffer. + /// Otherwise, . + public bool IsNull() => _buffer.IsEmpty; + + /// + /// Checks if the has no buffer or begins with a null terminator. + /// + /// if the span has no buffer or begins with a null terminator. + /// Otherwise, . + public bool IsEmpty() => _buffer.IsEmpty || MemoryMarshal.GetReference(_buffer) == 0; } } diff --git a/src/LibHac/Common/U8String.cs b/src/LibHac/Common/U8String.cs index c41083bb..56d60eaf 100644 --- a/src/LibHac/Common/U8String.cs +++ b/src/LibHac/Common/U8String.cs @@ -5,7 +5,7 @@ using System.Text; namespace LibHac.Common { [DebuggerDisplay("{ToString()}")] - public struct U8String + public readonly struct U8String { private readonly byte[] _buffer; diff --git a/src/LibHac/Common/U8StringMutable.cs b/src/LibHac/Common/U8StringMutable.cs index 2a0d659b..c808014f 100644 --- a/src/LibHac/Common/U8StringMutable.cs +++ b/src/LibHac/Common/U8StringMutable.cs @@ -5,7 +5,7 @@ using System.Text; namespace LibHac.Common { [DebuggerDisplay("{ToString()}")] - public struct U8StringMutable + public readonly struct U8StringMutable { private readonly byte[] _buffer; @@ -53,6 +53,18 @@ namespace LibHac.Common return StringUtils.Utf8ZToString(_buffer); } + /// + /// Checks if the has no buffer. + /// + /// if the string has no buffer. + /// Otherwise, . public bool IsNull() => _buffer == null; + + /// + /// Checks if the has no buffer or begins with a null terminator. + /// + /// if the string has no buffer or begins with a null terminator. + /// Otherwise, . + public bool IsEmpty() => _buffer == null || _buffer.Length < 1 || _buffer[0] == 0; } } diff --git a/src/LibHac/Fs/InMemoryFileSystem.cs b/src/LibHac/Fs/InMemoryFileSystem.cs index 0b793424..048e5dbf 100644 --- a/src/LibHac/Fs/InMemoryFileSystem.cs +++ b/src/LibHac/Fs/InMemoryFileSystem.cs @@ -265,7 +265,13 @@ namespace LibHac.Fs entry.Type = DirectoryEntryType.File; entry.Attributes = CurrentFile.Attributes; - CurrentFile.File.GetSize(out entry.Size); + + Result rc = CurrentFile.File.GetSize(out entry.Size); + if (rc.IsFailure()) + { + entriesRead = 0; + return rc; + } i++; CurrentFile = CurrentFile.Next; diff --git a/src/LibHac/FsSystem/CachedStorage.cs b/src/LibHac/FsSystem/CachedStorage.cs index 956ef1cf..bba36958 100644 --- a/src/LibHac/FsSystem/CachedStorage.cs +++ b/src/LibHac/FsSystem/CachedStorage.cs @@ -145,10 +145,19 @@ namespace LibHac.FsSystem Blocks.AddFirst(node); } + // If a supposedly active block is null, something's really wrong + if (node is null) + { + throw new NullReferenceException("CachedStorage cache block is unexpectedly null."); + } + return node.Value; } - node = Blocks.Last; + // An inactive node shouldn't be null, but we'll fix it if it is anyway + node = Blocks.Last ?? + new LinkedListNode(new CacheBlock {Buffer = new byte[BlockSize], Index = -1}); + FlushBlock(node.Value); CacheBlock block = node.Value; diff --git a/src/LibHac/FsSystem/StorageExtensions.cs b/src/LibHac/FsSystem/StorageExtensions.cs index 3deebdf5..107b5a80 100644 --- a/src/LibHac/FsSystem/StorageExtensions.cs +++ b/src/LibHac/FsSystem/StorageExtensions.cs @@ -8,19 +8,25 @@ namespace LibHac.FsSystem { public static class StorageExtensions { + [Obsolete("The standard Span-based IStorage methods should be used instead.")] public static Result Read(this IStorage storage, long offset, byte[] buffer, int count, int bufferOffset) { ValidateStorageParameters(buffer, offset, count, bufferOffset); return storage.Read(offset, buffer.AsSpan(bufferOffset, count)); } + [Obsolete("The standard Span-based IStorage methods should be used instead.")] public static Result Write(this IStorage storage, long offset, byte[] buffer, int count, int bufferOffset) { ValidateStorageParameters(buffer, offset, count, bufferOffset); return storage.Write(offset, buffer.AsSpan(bufferOffset, count)); } + // todo: remove this method when the above Read and Write methods are removed + // (Hopefully they're still above, or else someone didn't listen) + // ReSharper disable ParameterOnlyUsedForPreconditionCheck.Local private static void ValidateStorageParameters(byte[] buffer, long offset, int count, int bufferOffset) + // ReSharper restore ParameterOnlyUsedForPreconditionCheck.Local { if (buffer == null) throw new ArgumentNullException(nameof(buffer)); if (offset < 0) throw new ArgumentOutOfRangeException(nameof(offset), "Argument must be non-negative."); diff --git a/src/LibHac/HorizonClient.cs b/src/LibHac/HorizonClient.cs index 16207b76..0801e6d6 100644 --- a/src/LibHac/HorizonClient.cs +++ b/src/LibHac/HorizonClient.cs @@ -7,6 +7,7 @@ namespace LibHac { public class HorizonClient { + // ReSharper disable once UnusedAutoPropertyAccessor.Local private Horizon Horizon { get; } private Lazy ArpLazy { get; } diff --git a/src/LibHac/HorizonResultException.cs b/src/LibHac/HorizonResultException.cs index 209176bb..8beeabb6 100644 --- a/src/LibHac/HorizonResultException.cs +++ b/src/LibHac/HorizonResultException.cs @@ -63,8 +63,8 @@ namespace LibHac protected HorizonResultException(SerializationInfo info, StreamingContext context) : base(info, context) { - InternalResultValue = (Result)info.GetValue(nameof(InternalResultValue), InternalResultValue.GetType()); - ResultValue = (Result)info.GetValue(nameof(ResultValue), ResultValue.GetType()); + InternalResultValue = (Result)(info.GetValue(nameof(InternalResultValue), InternalResultValue.GetType()) ?? default(Result)); + ResultValue = (Result)(info.GetValue(nameof(ResultValue), ResultValue.GetType()) ?? default(Result)); InnerMessage = (string)info.GetValue(nameof(InnerMessage), InnerMessage.GetType()); } diff --git a/src/LibHac/Kip.cs b/src/LibHac/Kip.cs index ceaf5f55..8e7c89be 100644 --- a/src/LibHac/Kip.cs +++ b/src/LibHac/Kip.cs @@ -2,6 +2,7 @@ using System.IO; using LibHac.Fs; using LibHac.FsSystem; +using LibHac.Loader; namespace LibHac { @@ -171,7 +172,7 @@ namespace LibHac public class Ini1 { - public Kip[] Kips { get; } + public KipReader[] Kips { get; } public string Magic { get; } public int Size { get; } @@ -194,13 +195,15 @@ namespace LibHac Size = reader.ReadInt32(); KipCount = reader.ReadInt32(); - Kips = new Kip[KipCount]; + Kips = new KipReader[KipCount]; int offset = 0x10; for (int i = 0; i < KipCount; i++) { - Kips[i] = new Kip(Storage.Slice(offset)); - offset += Kips[i].Size; + Kips[i] = new KipReader(); + Kips[i].Initialize(Storage.Slice(offset)).ThrowIfFailure(); + + offset += Kips[i].GetUncompressedSize(); } } } diff --git a/src/LibHac/Loader/KipReader.cs b/src/LibHac/Loader/KipReader.cs index 649d7f00..708a2c25 100644 --- a/src/LibHac/Loader/KipReader.cs +++ b/src/LibHac/Loader/KipReader.cs @@ -10,7 +10,7 @@ namespace LibHac.Loader { public class KipReader { - private IFile KipFile { get; set; } + private IStorage KipStorage { get; set; } private KipHeader _header; @@ -34,21 +34,48 @@ namespace LibHac.Loader public int AffinityMask => _header.AffinityMask; public int StackSize => _header.StackSize; - public Result Initialize(IFile kipFile) + public Result Initialize(IStorage kipData) { - if (kipFile is null) + if (kipData is null) return ResultLibHac.NullArgument.Log(); - Result rc = kipFile.Read(out long bytesRead, 0, SpanHelpers.AsByteSpan(ref _header), ReadOption.None); + // Verify there's enough data to read the header + Result rc = kipData.GetSize(out long kipSize); if (rc.IsFailure()) return rc; - if (bytesRead != Unsafe.SizeOf()) + if (kipSize < Unsafe.SizeOf()) return ResultLibHac.InvalidKipFileSize.Log(); + rc = kipData.Read(0, SpanHelpers.AsByteSpan(ref _header)); + if (rc.IsFailure()) return rc; + if (!_header.IsValid) return ResultLibHac.InvalidKipMagic.Log(); - KipFile = kipFile; + KipStorage = kipData; + return Result.Success; + } + + /// + /// Gets the raw input KIP file. + /// + /// If the operation returns successfully, an + /// containing the KIP data. + /// The of the operation. + public Result GetRawData(out IStorage kipData) + { + kipData = default; + + int kipFileSize = GetFileSize(); + + Result rc = KipStorage.GetSize(out long inputFileSize); + if (rc.IsFailure()) return rc; + + // Verify the input KIP file isn't truncated + if (inputFileSize < kipFileSize) + return ResultLibHac.InvalidKipFileSize.Log(); + + kipData = new SubStorage2(KipStorage, 0, kipFileSize); return Result.Success; } @@ -70,6 +97,18 @@ namespace LibHac.Loader } } + private int GetFileSize() + { + int size = Unsafe.SizeOf(); + + for (int i = 0; i < Segments.Length; i++) + { + size += Segments[i].FileSize; + } + + return size; + } + public int GetUncompressedSize() { int size = Unsafe.SizeOf(); @@ -108,13 +147,17 @@ namespace LibHac.Loader int offset = CalculateSegmentOffset((int)segment); - // Read the segment data. - rc = KipFile.Read(out long bytesRead, offset, buffer.Slice(0, segmentHeader.FileSize), ReadOption.None); + // Verify the segment offset is in-range + rc = KipStorage.GetSize(out long kipSize); if (rc.IsFailure()) return rc; - if (bytesRead != segmentHeader.FileSize) + if (kipSize < offset + segmentHeader.FileSize) return ResultLibHac.InvalidKipFileSize.Log(); + // Read the segment data. + rc = KipStorage.Read(offset, buffer.Slice(0, segmentHeader.FileSize)); + if (rc.IsFailure()) return rc; + // Decompress if necessary. bool isCompressed = segment switch { diff --git a/src/LibHac/MissingKeyException.cs b/src/LibHac/MissingKeyException.cs index 806785c9..f2588d81 100644 --- a/src/LibHac/MissingKeyException.cs +++ b/src/LibHac/MissingKeyException.cs @@ -69,7 +69,7 @@ namespace LibHac : base(info, context) { Name = info.GetString(nameof(Name)); - Type = (KeyType)info.GetValue(nameof(Type), Type.GetType()); + Type = (KeyType)(info.GetValue(nameof(Type), Type.GetType()) ?? default(KeyType)); } void ISerializable.GetObjectData(SerializationInfo info, StreamingContext context) diff --git a/src/LibHac/Result.cs b/src/LibHac/Result.cs index 660e4be1..830a7ffc 100644 --- a/src/LibHac/Result.cs +++ b/src/LibHac/Result.cs @@ -11,7 +11,7 @@ namespace LibHac /// [Serializable] [DebuggerDisplay("{" + nameof(ToStringWithName) + "(),nq}")] - public struct Result : IEquatable + public readonly struct Result : IEquatable { private const BaseType SuccessValue = default; /// @@ -218,7 +218,7 @@ namespace LibHac /// public static Result.Base SdCardAccessFailed { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new Result.Base(ModuleFs, 2000, 2499); } /// [DebuggerDisplay("{" + nameof(ToStringWithName) + "(),nq}")] - public struct Base + public readonly struct Base { private const int DescriptionEndBitsOffset = ReservedBitsOffset; private readonly ulong _value; diff --git a/src/LibHac/Sm/ServiceManager.cs b/src/LibHac/Sm/ServiceManager.cs index 9f37c8d2..7f31d592 100644 --- a/src/LibHac/Sm/ServiceManager.cs +++ b/src/LibHac/Sm/ServiceManager.cs @@ -11,6 +11,7 @@ namespace LibHac.Sm // isn't blocked waiting for something better. internal class ServiceManager { + // ReSharper disable once UnusedAutoPropertyAccessor.Local private Horizon Horizon { get; } private Dictionary Services { get; } = new Dictionary(); diff --git a/src/LibHac/Sm/ServiceName.cs b/src/LibHac/Sm/ServiceName.cs index 7c3deb3a..be92af43 100644 --- a/src/LibHac/Sm/ServiceName.cs +++ b/src/LibHac/Sm/ServiceName.cs @@ -9,6 +9,8 @@ namespace LibHac.Sm { private const int MaxLength = 8; + // The Name should always be assigned in the below Encode method + // ReSharper disable once UnassignedReadonlyField public readonly ulong Name; public static ServiceName Encode(ReadOnlySpan name) diff --git a/src/LibHac/SwitchFs.cs b/src/LibHac/SwitchFs.cs index 1d1af95c..565403be 100644 --- a/src/LibHac/SwitchFs.cs +++ b/src/LibHac/SwitchFs.cs @@ -9,6 +9,7 @@ using LibHac.FsSystem; using LibHac.FsSystem.NcaUtils; using LibHac.FsSystem.Save; using LibHac.Ncm; +using LibHac.Ns; namespace LibHac { @@ -195,13 +196,16 @@ namespace LibHac IFileSystem romfs = title.ControlNca.OpenFileSystem(NcaSectionType.Data, IntegrityCheckLevel.ErrorOnInvalid); romfs.OpenFile(out IFile control, "/control.nacp".ToU8Span(), OpenMode.Read).ThrowIfFailure(); - title.Control = new Nacp(control.AsStream()); - - foreach (NacpDescription desc in title.Control.Descriptions) + using (control) { - if (!string.IsNullOrWhiteSpace(desc.Title)) + control.Read(out _, 0, title.Control.ByteSpan).ThrowIfFailure(); + } + + foreach (ref ApplicationControlTitle desc in title.Control.Value.Titles) + { + if (!desc.Name.IsEmpty()) { - title.Name = desc.Title; + title.Name = desc.Name.ToString(); break; } } @@ -320,7 +324,7 @@ namespace LibHac public Cnmt Metadata { get; internal set; } public string Name { get; internal set; } - public Nacp Control { get; internal set; } + public BlitStruct Control { get; } = new BlitStruct(1); public SwitchFsNca MetaNca { get; internal set; } public SwitchFsNca MainNca { get; internal set; } public SwitchFsNca ControlNca { get; internal set; } @@ -339,7 +343,7 @@ namespace LibHac public ulong TitleId { get; private set; } public TitleVersion Version { get; private set; } - public Nacp Nacp { get; private set; } + public BlitStruct Nacp { get; private set; } = new BlitStruct(1); public string Name { get; private set; } public string DisplayVersion { get; private set; } @@ -374,14 +378,14 @@ namespace LibHac { Name = Patch.Name; Version = Patch.Version; - DisplayVersion = Patch.Control?.DisplayVersion ?? ""; + DisplayVersion = Patch.Control.Value.DisplayVersion.ToString(); Nacp = Patch.Control; } else if (Main != null) { Name = Main.Name; Version = Main.Version; - DisplayVersion = Main.Control?.DisplayVersion ?? ""; + DisplayVersion = Main.Control.Value.DisplayVersion.ToString(); Nacp = Main.Control; } else diff --git a/src/hactoolnet/ProcessKip.cs b/src/hactoolnet/ProcessKip.cs index 1b36225c..17b99902 100644 --- a/src/hactoolnet/ProcessKip.cs +++ b/src/hactoolnet/ProcessKip.cs @@ -1,6 +1,5 @@ using System.IO; using LibHac; -using LibHac.Fs; using LibHac.FsSystem; using LibHac.Loader; @@ -10,7 +9,7 @@ namespace hactoolnet { public static void ProcessKip1(Context ctx) { - using (var file = new LocalFile(ctx.Options.InFile, OpenMode.Read)) + using (var file = new LocalStorage(ctx.Options.InFile, FileAccess.Read)) { var kip = new KipReader(); kip.Initialize(file).ThrowIfFailure(); @@ -38,9 +37,13 @@ namespace hactoolnet { Directory.CreateDirectory(outDir); - foreach (Kip kip in ini1.Kips) + foreach (KipReader kip in ini1.Kips) { - kip.OpenRawFile().WriteAllBytes(Path.Combine(outDir, $"{kip.Header.Name}.kip1")); + var uncompressed = new byte[kip.GetUncompressedSize()]; + + kip.ReadUncompressedKip(uncompressed).ThrowIfFailure(); + + File.WriteAllBytes(Path.Combine(outDir, $"{kip.Name.ToString()}.kip1"), uncompressed); } } } diff --git a/src/hactoolnet/ProcessSwitchFs.cs b/src/hactoolnet/ProcessSwitchFs.cs index b60841a3..637848cd 100644 --- a/src/hactoolnet/ProcessSwitchFs.cs +++ b/src/hactoolnet/ProcessSwitchFs.cs @@ -9,6 +9,7 @@ using LibHac.Fs; using LibHac.FsSystem; using LibHac.FsSystem.NcaUtils; using LibHac.FsSystem.Save; +using LibHac.Ns; namespace hactoolnet { @@ -241,7 +242,7 @@ namespace hactoolnet title.Version?.ToString(), title.Metadata?.Type.ToString(), Util.GetBytesReadable(title.GetSize()), - title.Control?.DisplayVersion, + title.Control.Value.DisplayVersion.ToString(), title.Name); } @@ -283,12 +284,17 @@ namespace hactoolnet sb.AppendLine($"DLC: {Util.GetBytesReadable(app.AddOnContent.Sum(x => x.GetSize()))}"); } - if (app.Nacp?.UserTotalSaveDataSize > 0) - sb.AppendLine($"User save: {Util.GetBytesReadable(app.Nacp.UserTotalSaveDataSize)}"); - if (app.Nacp?.DeviceTotalSaveDataSize > 0) - sb.AppendLine($"System save: {Util.GetBytesReadable(app.Nacp.DeviceTotalSaveDataSize)}"); - if (app.Nacp?.BcatDeliveryCacheStorageSize > 0) - sb.AppendLine($"BCAT save: {Util.GetBytesReadable(app.Nacp.BcatDeliveryCacheStorageSize)}"); + ref ApplicationControlProperty nacp = ref app.Nacp.Value; + + long userTotalSaveDataSize = nacp.UserAccountSaveDataSize + nacp.UserAccountSaveDataJournalSize; + long deviceTotalSaveDataSize = nacp.DeviceSaveDataSize + nacp.DeviceSaveDataJournalSize; + + if (userTotalSaveDataSize > 0) + sb.AppendLine($"User save: {Util.GetBytesReadable(userTotalSaveDataSize)}"); + if (deviceTotalSaveDataSize > 0) + sb.AppendLine($"System save: {Util.GetBytesReadable(deviceTotalSaveDataSize)}"); + if (nacp.BcatDeliveryCacheStorageSize > 0) + sb.AppendLine($"BCAT save: {Util.GetBytesReadable(nacp.BcatDeliveryCacheStorageSize)}"); sb.AppendLine(); } diff --git a/src/hactoolnet/ResultLogger.cs b/src/hactoolnet/ResultLogger.cs index 2ffe89de..ea9cec34 100644 --- a/src/hactoolnet/ResultLogger.cs +++ b/src/hactoolnet/ResultLogger.cs @@ -37,7 +37,10 @@ namespace hactoolnet public void LogResult(Result result) { StackTrace st = GetStackTrace(); - MethodBase method = st.GetFrame(0).GetMethod(); + MethodBase method = st.GetFrame(0)?.GetMethod(); + + if (method is null) + return; // This result from these functions is usually noise because they // are frequently used to detect if a file exists @@ -90,7 +93,11 @@ namespace hactoolnet private void PrintLogEntry(LogEntry entry) { - MethodBase method = entry.StackTrace.GetFrame(0).GetMethod(); + MethodBase method = entry.StackTrace.GetFrame(0)?.GetMethod(); + + if (method is null) + return; + string methodName = $"{method.DeclaringType?.FullName}.{method.Name}"; bool printStackTrace = PrintStackTrace && !entry.IsConvertedResult; @@ -189,11 +196,22 @@ namespace hactoolnet IsConvertedResult = isConverted; OriginalResult = originalResult; - MethodBase method = stackTrace.GetFrame(0).GetMethod(); + MethodBase method = stackTrace.GetFrame(0)?.GetMethod(); + + if (method is null) + { + CallingMethod = string.Empty; + StackTraceText = string.Empty; + LineNumber = 0; + TimesCalled = 1; + + return; + } + CallingMethod = $"{method.DeclaringType?.FullName}.{method.Name}"; StackTraceText = stackTrace.ToString(); - LineNumber = stackTrace.GetFrame(0).GetFileLineNumber(); + LineNumber = stackTrace.GetFrame(0)?.GetFileLineNumber() ?? 0; TimesCalled = 1; } diff --git a/src/hactoolnet/ResultNameResolver.cs b/src/hactoolnet/ResultNameResolver.cs index ae2e2d4f..47f5bca1 100644 --- a/src/hactoolnet/ResultNameResolver.cs +++ b/src/hactoolnet/ResultNameResolver.cs @@ -23,12 +23,15 @@ namespace hactoolnet 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)) + .Where(x => x.PropertyType == typeof(Result.Base) && x.GetMethod?.IsStatic == true && x.SetMethod == null)) { - Result value = ((Result.Base)property.GetValue(null, null)).Value; + object value = property.GetValue(null, null); + if (value is null) continue; + + Result resultValue = ((Result.Base)value).Value; string name = $"{type.Name}{property.Name}"; - dict[value] = name; + dict[resultValue] = name; } return dict; diff --git a/tests/LibHac.Tests/Fs/DirectorySaveDataFileSystemTests.cs b/tests/LibHac.Tests/Fs/DirectorySaveDataFileSystemTests.cs index 10014f0e..87e8eb74 100644 --- a/tests/LibHac.Tests/Fs/DirectorySaveDataFileSystemTests.cs +++ b/tests/LibHac.Tests/Fs/DirectorySaveDataFileSystemTests.cs @@ -20,16 +20,16 @@ namespace LibHac.Tests.Fs private class DirectorySaveDataFileSystemCreator : IReopenableFileSystemCreator { - private IFileSystem _baseFileSystem { get; } + private IFileSystem BaseFileSystem { get; } public DirectorySaveDataFileSystemCreator() { - _baseFileSystem = new InMemoryFileSystem(); + BaseFileSystem = new InMemoryFileSystem(); } public IFileSystem Create() { - DirectorySaveDataFileSystem.CreateNew(out DirectorySaveDataFileSystem saveFs, _baseFileSystem, true, true) + DirectorySaveDataFileSystem.CreateNew(out DirectorySaveDataFileSystem saveFs, BaseFileSystem, true, true) .ThrowIfFailure(); return saveFs; diff --git a/tests/LibHac.Tests/Fs/FileSystemClientTests/ShimTests/Bis.cs b/tests/LibHac.Tests/Fs/FileSystemClientTests/ShimTests/Bis.cs index 9c41f8e2..c15f1be9 100644 --- a/tests/LibHac.Tests/Fs/FileSystemClientTests/ShimTests/Bis.cs +++ b/tests/LibHac.Tests/Fs/FileSystemClientTests/ShimTests/Bis.cs @@ -87,7 +87,7 @@ namespace LibHac.Tests.Fs.FileSystemClientTests.ShimTests [Fact] public void MountBis_InvalidPartition_ReturnsInvalidArgument() { - FileSystemClient fs = FileSystemServerFactory.CreateClient(out IFileSystem rootFs); + FileSystemClient fs = FileSystemServerFactory.CreateClient(out IFileSystem _); Assert.Result(ResultFs.InvalidArgument, fs.MountBis("boot1".ToU8Span(), BisPartitionId.BootPartition1Root)); } diff --git a/tests/LibHac.Tests/ResultNameResolver.cs b/tests/LibHac.Tests/ResultNameResolver.cs index 3991d1a5..f133ddd6 100644 --- a/tests/LibHac.Tests/ResultNameResolver.cs +++ b/tests/LibHac.Tests/ResultNameResolver.cs @@ -22,12 +22,15 @@ namespace LibHac.Tests 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)) + .Where(x => x.PropertyType == typeof(Result.Base) && x.GetMethod?.IsStatic == true && x.SetMethod == null)) { - Result value = ((Result.Base)property.GetValue(null, null)).Value; + object value = property.GetValue(null, null); + if (value is null) continue; + + Result resultValue = ((Result.Base)value).Value; string name = $"{type.Name}{property.Name}"; - dict[value] = name; + dict[resultValue] = name; } return dict;