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
This commit is contained in:
Alex Barney 2020-05-12 17:06:55 -07:00 committed by GitHub
parent 44e4c7a311
commit cb8b088487
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
25 changed files with 228 additions and 62 deletions

View file

@ -1,6 +1,4 @@
using LibHac.Ncm;
namespace LibHac.Bcat.Detail.Ipc
namespace LibHac.Bcat.Detail.Ipc
{
public interface IServiceCreator
{

View file

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

View file

@ -21,10 +21,16 @@ namespace LibHac.Bcat
public static Result.Base NotFound => new Result.Base(ModuleBcat, 2);
/// <summary>Error code: 2122-0003; Inner value: 0x67a</summary>
public static Result.Base TargetLocked => new Result.Base(ModuleBcat, 3);
/// <summary>Error code: 2122-0004; Inner value: 0x87a</summary>
public static Result.Base TargetAlreadyMounted => new Result.Base(ModuleBcat, 4);
/// <summary>Error code: 2122-0005; Inner value: 0xa7a</summary>
public static Result.Base TargetNotMounted => new Result.Base(ModuleBcat, 5);
/// <summary>Error code: 2122-0006; Inner value: 0xc7a</summary>
public static Result.Base AlreadyOpen => new Result.Base(ModuleBcat, 6);
/// <summary>Error code: 2122-0007; Inner value: 0xe7a</summary>
public static Result.Base NotOpen => new Result.Base(ModuleBcat, 7);
/// <summary>Error code: 2122-0008; Inner value: 0x107a</summary>
public static Result.Base InternetRequestDenied => new Result.Base(ModuleBcat, 8);
/// <summary>Error code: 2122-0009; Inner value: 0x127a</summary>
public static Result.Base ServiceOpenLimitReached => new Result.Base(ModuleBcat, 9);
/// <summary>Error code: 2122-0010; Inner value: 0x147a</summary>
@ -33,10 +39,14 @@ namespace LibHac.Bcat
public static Result.Base NetworkServiceAccountNotAvailable => new Result.Base(ModuleBcat, 31);
/// <summary>Error code: 2122-0080; Inner value: 0xa07a</summary>
public static Result.Base PassphrasePathNotFound => new Result.Base(ModuleBcat, 80);
/// <summary>Error code: 2122-0081; Inner value: 0xa27a</summary>
public static Result.Base DataVerificationFailed => new Result.Base(ModuleBcat, 81);
/// <summary>Error code: 2122-0090; Inner value: 0xb47a</summary>
public static Result.Base PermissionDenied => new Result.Base(ModuleBcat, 90);
/// <summary>Error code: 2122-0091; Inner value: 0xb67a</summary>
public static Result.Base AllocationFailed => new Result.Base(ModuleBcat, 91);
/// <summary>Error code: 2122-0098; Inner value: 0xc47a</summary>
public static Result.Base InvalidOperation => new Result.Base(ModuleBcat, 98);
/// <summary>Error code: 2122-0204; Inner value: 0x1987a</summary>
public static Result.Base InvalidDeliveryCacheStorageFile => new Result.Base(ModuleBcat, 204);
/// <summary>Error code: 2122-0205; Inner value: 0x19a7a</summary>

View file

@ -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<byte> _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;
/// <summary>
/// Checks if the <see cref="U8StringMutable"/> has no buffer.
/// </summary>
/// <returns><see langword="true"/> if the span has no buffer.
/// Otherwise, <see langword="false"/>.</returns>
public bool IsNull() => _buffer.IsEmpty;
/// <summary>
/// Checks if the <see cref="U8StringMutable"/> has no buffer or begins with a null terminator.
/// </summary>
/// <returns><see langword="true"/> if the span has no buffer or begins with a null terminator.
/// Otherwise, <see langword="false"/>.</returns>
public bool IsEmpty() => _buffer.IsEmpty || MemoryMarshal.GetReference(_buffer) == 0;
}
}

View file

@ -5,7 +5,7 @@ using System.Text;
namespace LibHac.Common
{
[DebuggerDisplay("{ToString()}")]
public struct U8String
public readonly struct U8String
{
private readonly byte[] _buffer;

View file

@ -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);
}
/// <summary>
/// Checks if the <see cref="U8String"/> has no buffer.
/// </summary>
/// <returns><see langword="true"/> if the string has no buffer.
/// Otherwise, <see langword="false"/>.</returns>
public bool IsNull() => _buffer == null;
/// <summary>
/// Checks if the <see cref="U8String"/> has no buffer or begins with a null terminator.
/// </summary>
/// <returns><see langword="true"/> if the string has no buffer or begins with a null terminator.
/// Otherwise, <see langword="false"/>.</returns>
public bool IsEmpty() => _buffer == null || _buffer.Length < 1 || _buffer[0] == 0;
}
}

View file

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

View file

@ -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<CacheBlock>(new CacheBlock {Buffer = new byte[BlockSize], Index = -1});
FlushBlock(node.Value);
CacheBlock block = node.Value;

View file

@ -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.");

View file

@ -7,6 +7,7 @@ namespace LibHac
{
public class HorizonClient
{
// ReSharper disable once UnusedAutoPropertyAccessor.Local
private Horizon Horizon { get; }
private Lazy<ArpClient> ArpLazy { get; }

View file

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

View file

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

View file

@ -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<KipHeader>())
if (kipSize < Unsafe.SizeOf<KipHeader>())
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;
}
/// <summary>
/// Gets the raw input KIP file.
/// </summary>
/// <param name="kipData">If the operation returns successfully, an <see cref="IStorage"/>
/// containing the KIP data.</param>
/// <returns>The <see cref="Result"/> of the operation.</returns>
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<KipHeader>();
for (int i = 0; i < Segments.Length; i++)
{
size += Segments[i].FileSize;
}
return size;
}
public int GetUncompressedSize()
{
int size = Unsafe.SizeOf<KipHeader>();
@ -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
{

View file

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

View file

@ -11,7 +11,7 @@ namespace LibHac
/// </summary>
[Serializable]
[DebuggerDisplay("{" + nameof(ToStringWithName) + "(),nq}")]
public struct Result : IEquatable<Result>
public readonly struct Result : IEquatable<Result>
{
private const BaseType SuccessValue = default;
/// <summary>
@ -218,7 +218,7 @@ namespace LibHac
/// <c>public static Result.Base SdCardAccessFailed { [MethodImpl(MethodImplOptions.AggressiveInlining)] get =&gt; new Result.Base(ModuleFs, 2000, 2499); }</c>
/// </remarks>
[DebuggerDisplay("{" + nameof(ToStringWithName) + "(),nq}")]
public struct Base
public readonly struct Base
{
private const int DescriptionEndBitsOffset = ReservedBitsOffset;
private readonly ulong _value;

View file

@ -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<ServiceName, object> Services { get; } = new Dictionary<ServiceName, object>();

View file

@ -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<char> name)

View file

@ -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<ApplicationControlProperty> Control { get; } = new BlitStruct<ApplicationControlProperty>(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<ApplicationControlProperty> Nacp { get; private set; } = new BlitStruct<ApplicationControlProperty>(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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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