diff --git a/src/LibHac/Common/Buffer.cs b/src/LibHac/Common/Buffer.cs index 9e8880d6..ffe69932 100644 --- a/src/LibHac/Common/Buffer.cs +++ b/src/LibHac/Common/Buffer.cs @@ -65,4 +65,63 @@ namespace LibHac.Common return Bytes.ToHexString(); } } + + [DebuggerDisplay("{ToString()}")] + [StructLayout(LayoutKind.Sequential, Size = 32)] + public struct Buffer32 + { + [DebuggerBrowsable(DebuggerBrowsableState.Never)] private ulong _dummy0; + [DebuggerBrowsable(DebuggerBrowsableState.Never)] private ulong _dummy1; + [DebuggerBrowsable(DebuggerBrowsableState.Never)] private ulong _dummy2; + [DebuggerBrowsable(DebuggerBrowsableState.Never)] private ulong _dummy3; + + public byte this[int i] + { + get => Bytes[i]; + set => Bytes[i] = value; + } + + public Span Bytes => SpanHelpers.AsByteSpan(ref this); + + // Prevent a defensive copy by changing the read-only in reference to a reference with Unsafe.AsRef() + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator Span(in Buffer32 value) + { + return SpanHelpers.AsByteSpan(ref Unsafe.AsRef(in value)); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator ReadOnlySpan(in Buffer32 value) + { + return SpanHelpers.AsReadOnlyByteSpan(ref Unsafe.AsRef(in value)); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ref T As() where T : unmanaged + { + if (Unsafe.SizeOf() > (uint)Unsafe.SizeOf()) + { + throw new ArgumentException(); + } + + return ref MemoryMarshal.GetReference(AsSpan()); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Span AsSpan() where T : unmanaged + { + return SpanHelpers.AsSpan(ref this); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly ReadOnlySpan AsReadOnlySpan() where T : unmanaged + { + return SpanHelpers.AsReadOnlySpan(ref Unsafe.AsRef(in this)); + } + + public override string ToString() + { + return Bytes.ToHexString(); + } + } } \ No newline at end of file diff --git a/src/LibHac/Crypto/CryptoUtil.cs b/src/LibHac/Crypto/CryptoUtil.cs new file mode 100644 index 00000000..fc91d7ea --- /dev/null +++ b/src/LibHac/Crypto/CryptoUtil.cs @@ -0,0 +1,30 @@ +using System; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace LibHac.Crypto +{ + internal static class CryptoUtil + { + public static bool IsSameBytes(ReadOnlySpan buffer1, ReadOnlySpan buffer2, int length) + { + if (buffer1.Length < (uint)length || buffer2.Length < (uint)length) + throw new ArgumentOutOfRangeException(nameof(length)); + + return IsSameBytes(ref MemoryMarshal.GetReference(buffer1), ref MemoryMarshal.GetReference(buffer2), length); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static bool IsSameBytes(ref byte p1, ref byte p2, int length) + { + int result = 0; + + for (int i = 0; i < length; i++) + { + result |= Unsafe.Add(ref p1, i) ^ Unsafe.Add(ref p2, i); + } + + return result == 0; + } + } +} \ No newline at end of file diff --git a/src/LibHac/Fs/ResultFs.cs b/src/LibHac/Fs/ResultFs.cs index 6244ac3a..63eeb5d5 100644 --- a/src/LibHac/Fs/ResultFs.cs +++ b/src/LibHac/Fs/ResultFs.cs @@ -58,8 +58,11 @@ public static Result InvalidHashInIvfc => new Result(ModuleFs, 4604); public static Result IvfcHashIsEmpty => new Result(ModuleFs, 4612); public static Result InvalidHashInIvfcTopLayer => new Result(ModuleFs, 4613); + public static Result InvalidPartitionFileSystemHashOffset => new Result(ModuleFs, 4642); + public static Result InvalidPartitionFileSystemHash => new Result(ModuleFs, 4643); public static Result InvalidPartitionFileSystemMagic => new Result(ModuleFs, 4644); public static Result InvalidHashedPartitionFileSystemMagic => new Result(ModuleFs, 4645); + public static Result InvalidPartitionFileSystemEntryNameOffset => new Result(ModuleFs, 4646); public static Result Result4662 => new Result(ModuleFs, 4662); public static Result SaveDataAllocationTableCorruptedInternal => new Result(ModuleFs, 4722); @@ -127,6 +130,7 @@ public static Result UnsupportedOperationModifyReadOnlyFile => new Result(ModuleFs, 6372); public static Result UnsupportedOperationModifyPartitionFileSystem => new Result(ModuleFs, 6374); public static Result UnsupportedOperationInPartitionFileSetSize => new Result(ModuleFs, 6376); + public static Result UnsupportedOperationIdInPartitionFileSystem => new Result(ModuleFs, 6377); public static Result PermissionDenied => new Result(ModuleFs, 6400); public static Result ExternalKeyAlreadyRegistered => new Result(ModuleFs, 6452); diff --git a/src/LibHac/FsService/Creators/PartitionFileSystemCreator.cs b/src/LibHac/FsService/Creators/PartitionFileSystemCreator.cs index c05b77b0..3d78508f 100644 --- a/src/LibHac/FsService/Creators/PartitionFileSystemCreator.cs +++ b/src/LibHac/FsService/Creators/PartitionFileSystemCreator.cs @@ -1,5 +1,6 @@ -using System; -using LibHac.Fs; +using LibHac.Fs; +using LibHac.FsSystem; +using LibHac.FsSystem.Detail; namespace LibHac.FsService.Creators { @@ -7,7 +8,17 @@ namespace LibHac.FsService.Creators { public Result Create(out IFileSystem fileSystem, IStorage pFsStorage) { - throw new NotImplementedException(); + var partitionFs = new PartitionFileSystemCore(); + + Result rc = partitionFs.Initialize(pFsStorage); + if (rc.IsFailure()) + { + fileSystem = default; + return rc; + } + + fileSystem = partitionFs; + return Result.Success; } } } diff --git a/src/LibHac/FsSystem/Detail/PartitionFileSystemFormats.cs b/src/LibHac/FsSystem/Detail/PartitionFileSystemFormats.cs new file mode 100644 index 00000000..866edb15 --- /dev/null +++ b/src/LibHac/FsSystem/Detail/PartitionFileSystemFormats.cs @@ -0,0 +1,39 @@ +using System.Runtime.InteropServices; +using LibHac.Common; + +namespace LibHac.FsSystem.Detail +{ + public interface IPartitionFileSystemEntry + { + long Offset { get; } + long Size { get; } + int NameOffset { get; } + } + + [StructLayout(LayoutKind.Sequential, Size = 0x18)] + public struct StandardEntry : IPartitionFileSystemEntry + { + public long Offset; + public long Size; + public int NameOffset; + + long IPartitionFileSystemEntry.Offset => Offset; + long IPartitionFileSystemEntry.Size => Size; + int IPartitionFileSystemEntry.NameOffset => NameOffset; + } + + [StructLayout(LayoutKind.Sequential, Size = 0x40)] + public struct HashedEntry : IPartitionFileSystemEntry + { + public long Offset; + public long Size; + public int NameOffset; + public int HashSize; + public long HashOffset; + public Buffer32 Hash; + + long IPartitionFileSystemEntry.Offset => Offset; + long IPartitionFileSystemEntry.Size => Size; + int IPartitionFileSystemEntry.NameOffset => NameOffset; + } +} diff --git a/src/LibHac/FsSystem/PartitionFileSystemCore.cs b/src/LibHac/FsSystem/PartitionFileSystemCore.cs new file mode 100644 index 00000000..37dd80ea --- /dev/null +++ b/src/LibHac/FsSystem/PartitionFileSystemCore.cs @@ -0,0 +1,373 @@ +using System; +using System.Runtime.CompilerServices; +using LibHac.Common; +using LibHac.Crypto; +using LibHac.Fs; +using LibHac.FsSystem.Detail; + +namespace LibHac.FsSystem +{ + public class PartitionFileSystemCore : FileSystemBase where T : unmanaged, IPartitionFileSystemEntry + { + private IStorage BaseStorage { get; set; } + private PartitionFileSystemMetaCore MetaData { get; set; } + private bool IsInitialized { get; set; } + private int DataOffset { get; set; } + + public Result Initialize(IStorage baseStorage) + { + if (IsInitialized) + return ResultFs.PreconditionViolation.Log(); + + MetaData = new PartitionFileSystemMetaCore(); + + Result rc = MetaData.Initialize(baseStorage); + if (rc.IsFailure()) return rc; + + BaseStorage = baseStorage; + DataOffset = MetaData.Size; + IsInitialized = true; + + return Result.Success; + } + + protected override Result OpenDirectoryImpl(out IDirectory directory, string path, OpenDirectoryMode mode) + { + directory = default; + + if (!IsInitialized) + return ResultFs.PreconditionViolation.Log(); + + ReadOnlySpan rootPath = new[] { (byte)'/' }; + + if (StringUtils.Compare(rootPath, path.ToU8Span(), 2) != 0) + return ResultFs.PathNotFound.Log(); + + directory = new PartitionDirectory(this, mode); + + return Result.Success; + } + + protected override Result OpenFileImpl(out IFile file, string path, OpenMode mode) + { + file = default; + + if (!IsInitialized) + return ResultFs.PreconditionViolation.Log(); + + if (!mode.HasFlag(OpenMode.Read) && !mode.HasFlag(OpenMode.Write)) + return ResultFs.InvalidArgument.Log(); + + int entryIndex = MetaData.FindEntry(path.ToU8Span().Slice(1)); + if (entryIndex < 0) return ResultFs.PathNotFound.Log(); + + ref T entry = ref MetaData.GetEntry(entryIndex); + + file = new PartitionFile(this, ref entry, mode); + + return Result.Success; + } + + protected override Result GetEntryTypeImpl(out DirectoryEntryType entryType, string path) + { + entryType = default; + + if (!IsInitialized) + return ResultFs.PreconditionViolation.Log(); + + if (string.IsNullOrEmpty(path) || path[0] != '/') + return ResultFs.InvalidPathFormat.Log(); + + ReadOnlySpan rootPath = new[] { (byte)'/' }; + + if (StringUtils.Compare(rootPath, path.ToU8Span(), 2) == 0) + { + entryType = DirectoryEntryType.Directory; + return Result.Success; + } + + if (MetaData.FindEntry(path.ToU8Span().Slice(1)) >= 0) + { + entryType = DirectoryEntryType.File; + return Result.Success; + } + + return ResultFs.PathNotFound.Log(); + } + + protected override Result CommitImpl() + { + return Result.Success; + } + + protected override Result CreateDirectoryImpl(string path) => ResultFs.UnsupportedOperationModifyPartitionFileSystem.Log(); + protected override Result CreateFileImpl(string path, long size, CreateFileOptions options) => ResultFs.UnsupportedOperationModifyPartitionFileSystem.Log(); + protected override Result DeleteDirectoryImpl(string path) => ResultFs.UnsupportedOperationModifyPartitionFileSystem.Log(); + protected override Result DeleteDirectoryRecursivelyImpl(string path) => ResultFs.UnsupportedOperationModifyPartitionFileSystem.Log(); + protected override Result CleanDirectoryRecursivelyImpl(string path) => ResultFs.UnsupportedOperationModifyPartitionFileSystem.Log(); + protected override Result DeleteFileImpl(string path) => ResultFs.UnsupportedOperationModifyPartitionFileSystem.Log(); + protected override Result RenameDirectoryImpl(string oldPath, string newPath) => ResultFs.UnsupportedOperationModifyPartitionFileSystem.Log(); + protected override Result RenameFileImpl(string oldPath, string newPath) => ResultFs.UnsupportedOperationModifyPartitionFileSystem.Log(); + + private class PartitionFile : FileBase + { + private PartitionFileSystemCore ParentFs { get; } + private OpenMode Mode { get; } + private T _entry; + + public PartitionFile(PartitionFileSystemCore parentFs, ref T entry, OpenMode mode) + { + ParentFs = parentFs; + _entry = entry; + Mode = mode; + } + + protected override Result ReadImpl(out long bytesRead, long offset, Span destination, ReadOption options) + { + bytesRead = default; + + Result rc = ValidateReadParams(out long bytesToRead, offset, destination.Length, Mode); + if (rc.IsFailure()) return rc; + + bool hashNeeded = false; + long fileStorageOffset = ParentFs.DataOffset + _entry.Offset; + + if (typeof(T) == typeof(HashedEntry)) + { + ref HashedEntry entry = ref Unsafe.As(ref _entry); + + long readEnd = offset + destination.Length; + long hashEnd = entry.HashOffset + entry.HashSize; + + // The hash must be checked if any part of the hashed region is read + hashNeeded = entry.HashOffset < readEnd && hashEnd >= offset; + } + + if (!hashNeeded) + { + rc = ParentFs.BaseStorage.Read(fileStorageOffset + offset, destination.Slice(0, (int)bytesToRead)); + } + else + { + ref HashedEntry entry = ref Unsafe.As(ref _entry); + + long readEnd = offset + destination.Length; + long hashEnd = entry.HashOffset + entry.HashSize; + + // Make sure the hashed region doesn't extend past the end of the file + // N's code requires that the hashed region starts at the beginning of the file + if (entry.HashOffset != 0 || hashEnd > entry.Size) + return ResultFs.InvalidPartitionFileSystemHashOffset.Log(); + + long storageOffset = fileStorageOffset + offset; + + // Nintendo checks for overflow here but not in other places for some reason + if (storageOffset < 0) + return ResultFs.ValueOutOfRange.Log(); + + IHash sha256 = Sha256.CreateSha256Generator(); + sha256.Initialize(); + + var actualHash = new Buffer32(); + + // If the area to read contains the entire hashed area + if (entry.HashOffset >= offset && hashEnd <= readEnd) + { + rc = ParentFs.BaseStorage.Read(storageOffset, destination.Slice(0, (int)bytesToRead)); + if (rc.IsFailure()) return rc; + + Span hashedArea = destination.Slice((int)(entry.HashOffset - offset), entry.HashSize); + sha256.Update(hashedArea); + } + else + { + // Can't start a read in the middle of the hashed region + if (readEnd > hashEnd || entry.HashOffset > offset) + { + return ResultFs.InvalidPartitionFileSystemHashOffset.Log(); + } + + int hashRemaining = entry.HashSize; + int readRemaining = (int)bytesToRead; + long readPos = fileStorageOffset + entry.HashOffset; + int outBufPos = 0; + + const int hashBufferSize = 0x200; + Span hashBuffer = stackalloc byte[hashBufferSize]; + + while (hashRemaining > 0) + { + int toRead = Math.Min(hashRemaining, hashBufferSize); + Span hashBufferSliced = hashBuffer.Slice(0, toRead); + + rc = ParentFs.BaseStorage.Read(readPos, hashBufferSliced); + if (rc.IsFailure()) return rc; + + sha256.Update(hashBufferSliced); + + if (readRemaining > 0 && storageOffset <= readPos + toRead) + { + int hashBufferOffset = (int)Math.Max(storageOffset - readPos, 0); + int toCopy = Math.Min(readRemaining, toRead - hashBufferOffset); + + hashBuffer.Slice(hashBufferOffset, toCopy).CopyTo(destination.Slice(outBufPos)); + + outBufPos += toCopy; + readRemaining -= toCopy; + } + + hashRemaining -= toRead; + readPos += toRead; + } + } + + sha256.GetHash(actualHash); + + if (!CryptoUtil.IsSameBytes(entry.Hash, actualHash, Sha256.DigestSize)) + { + destination.Slice(0, (int)bytesToRead).Clear(); + + return ResultFs.InvalidPartitionFileSystemHash.Log(); + } + + rc = Result.Success; + } + + if (rc.IsSuccess()) + bytesRead = bytesToRead; + + return rc; + } + + protected override Result WriteImpl(long offset, ReadOnlySpan source, WriteOption options) + { + Result rc = ValidateWriteParams(offset, source.Length, Mode, out bool isResizeNeeded); + if (rc.IsFailure()) return rc; + + if (isResizeNeeded) + return ResultFs.UnsupportedOperationInPartitionFileSetSize.Log(); + + if (_entry.Size < offset) + return ResultFs.ValueOutOfRange.Log(); + + if (_entry.Size < source.Length + offset) + return ResultFs.InvalidSize.Log(); + + return ParentFs.BaseStorage.Write(ParentFs.DataOffset + _entry.Offset + offset, source); + } + + protected override Result FlushImpl() + { + if (Mode.HasFlag(OpenMode.Write)) + { + return ParentFs.BaseStorage.Flush(); + } + + return Result.Success; + } + + protected override Result SetSizeImpl(long size) + { + if (Mode.HasFlag(OpenMode.Write)) + { + return ResultFs.UnsupportedOperationInPartitionFileSetSize.Log(); + } + + return ResultFs.InvalidOpenModeForWrite.Log(); + } + + protected override Result GetSizeImpl(out long size) + { + size = _entry.Size; + + return Result.Success; + } + + protected override Result OperateRangeImpl(Span outBuffer, OperationId operationId, long offset, long size, ReadOnlySpan inBuffer) + { + switch (operationId) + { + case OperationId.InvalidateCache: + if (!Mode.HasFlag(OpenMode.Read)) + return ResultFs.InvalidOpenModeForRead.Log(); + + if (Mode.HasFlag(OpenMode.Write)) + return ResultFs.UnsupportedOperationIdInPartitionFileSystem.Log(); + + break; + case OperationId.QueryRange: + break; + default: + return ResultFs.UnsupportedOperationIdInPartitionFileSystem.Log(); + } + + if (offset < 0 || offset > _entry.Size) + return ResultFs.ValueOutOfRange.Log(); + + if (size < 0 || offset + size > _entry.Size) + return ResultFs.InvalidSize.Log(); + + long offsetInStorage = ParentFs.DataOffset + _entry.Offset + offset; + + return ParentFs.BaseStorage.OperateRange(outBuffer, operationId, offsetInStorage, size, inBuffer); + } + } + + private class PartitionDirectory : IDirectory + { + private PartitionFileSystemCore ParentFs { get; } + private int CurrentIndex { get; set; } + private OpenDirectoryMode Mode { get; } + + public PartitionDirectory(PartitionFileSystemCore parentFs, OpenDirectoryMode mode) + { + ParentFs = parentFs; + CurrentIndex = 0; + Mode = mode; + } + + public Result Read(out long entriesRead, Span entryBuffer) + { + if (Mode.HasFlag(OpenDirectoryMode.File)) + { + int totalEntryCount = ParentFs.MetaData.GetEntryCount(); + int toReadCount = Math.Min(totalEntryCount - CurrentIndex, entryBuffer.Length); + + for (int i = 0; i < toReadCount; i++) + { + entryBuffer[i].Type = DirectoryEntryType.File; + entryBuffer[i].Size = ParentFs.MetaData.GetEntry(CurrentIndex).Size; + + U8Span name = ParentFs.MetaData.GetName(CurrentIndex); + StringUtils.Copy(entryBuffer[i].Name, name); + entryBuffer[i].Name[FsPath.MaxLength] = 0; + + CurrentIndex++; + } + + entriesRead = toReadCount; + } + else + { + entriesRead = 0; + } + + return Result.Success; + } + + public Result GetEntryCount(out long entryCount) + { + if (Mode.HasFlag(OpenDirectoryMode.File)) + { + entryCount = ParentFs.MetaData.GetEntryCount(); + } + else + { + entryCount = 0; + } + + return Result.Success; + } + } + } +} diff --git a/src/LibHac/FsSystem/PartitionFileSystemMetaCore.cs b/src/LibHac/FsSystem/PartitionFileSystemMetaCore.cs new file mode 100644 index 00000000..52e4edf6 --- /dev/null +++ b/src/LibHac/FsSystem/PartitionFileSystemMetaCore.cs @@ -0,0 +1,194 @@ +using System; +using System.Diagnostics; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using LibHac.Common; +using LibHac.Fs; +using LibHac.FsSystem.Detail; + +namespace LibHac.FsSystem +{ + public class PartitionFileSystemMetaCore where T : unmanaged, IPartitionFileSystemEntry + { + private static int HeaderSize => Unsafe.SizeOf
(); + private static int EntrySize => Unsafe.SizeOf(); + + private bool IsInitialized { get; set; } + private int EntryCount { get; set; } + private int StringTableSize { get; set; } + private int StringTableOffset { get; set; } + private byte[] Buffer { get; set; } + + public int Size { get; private set; } + + public Result Initialize(IStorage baseStorage) + { + var header = new Header(); + + Result rc = baseStorage.Read(0, SpanHelpers.AsByteSpan(ref header)); + if (rc.IsFailure()) return rc; + + int pfsMetaSize = HeaderSize + header.EntryCount * EntrySize + header.StringTableSize; + Buffer = new byte[pfsMetaSize]; + Size = pfsMetaSize; + + return Initialize(baseStorage, Buffer); + } + + private Result Initialize(IStorage baseStorage, Span buffer) + { + if (buffer.Length < HeaderSize) + return ResultFs.InvalidSize.Log(); + + Result rc = baseStorage.Read(0, buffer.Slice(0, HeaderSize)); + if (rc.IsFailure()) return rc; + + ref Header header = ref Unsafe.As(ref MemoryMarshal.GetReference(buffer)); + + if (header.Magic != GetMagicValue()) + return GetInvalidMagicResult(); + + EntryCount = header.EntryCount; + + int entryTableOffset = HeaderSize; + int entryTableSize = EntryCount * EntrySize; + + StringTableOffset = entryTableOffset + entryTableSize; + StringTableSize = header.StringTableSize; + + int pfsMetaSize = StringTableOffset + StringTableSize; + + if (buffer.Length < pfsMetaSize) + return ResultFs.InvalidSize.Log(); + + rc = baseStorage.Read(entryTableOffset, + buffer.Slice(entryTableOffset, entryTableSize + StringTableSize)); + + if (rc.IsSuccess()) + { + IsInitialized = true; + } + + return rc; + } + + public int GetEntryCount() + { + // FS aborts instead of returning the result value + if (!IsInitialized) + throw new HorizonResultException(ResultFs.PreconditionViolation.Log()); + + return EntryCount; + } + + public int FindEntry(U8Span name) + { + // FS aborts instead of returning the result value + if (!IsInitialized) + throw new HorizonResultException(ResultFs.PreconditionViolation.Log()); + + int stringTableSize = StringTableSize; + + ReadOnlySpan entries = GetEntries(); + ReadOnlySpan names = GetStringTable(); + + for (int i = 0; i < entries.Length; i++) + { + if (stringTableSize <= entries[i].NameOffset) + { + throw new HorizonResultException(ResultFs.InvalidPartitionFileSystemEntryNameOffset.Log()); + } + + ReadOnlySpan entryName = names.Slice(entries[i].NameOffset); + + if (StringUtils.Compare(name, entryName) == 0) + { + return i; + } + } + + return -1; + } + + public ref T GetEntry(int index) + { + if (!IsInitialized || index < 0 || index > EntryCount) + throw new HorizonResultException(ResultFs.PreconditionViolation.Log()); + + return ref GetEntries()[index]; + } + + public U8Span GetName(int index) + { + int nameOffset = GetEntry(index).NameOffset; + ReadOnlySpan table = GetStringTable(); + + // Nintendo doesn't check the offset here like they do in FindEntry, but we will for safety + if (table.Length <= nameOffset) + { + throw new HorizonResultException(ResultFs.InvalidPartitionFileSystemEntryNameOffset.Log()); + } + + return new U8Span(table.Slice(nameOffset)); + } + + private Span GetEntries() + { + Debug.Assert(IsInitialized); + Debug.Assert(Buffer.Length >= HeaderSize + EntryCount * EntrySize); + + Span entryBuffer = Buffer.AsSpan(HeaderSize, EntryCount * EntrySize); + return MemoryMarshal.Cast(entryBuffer); + } + + private ReadOnlySpan GetStringTable() + { + Debug.Assert(IsInitialized); + Debug.Assert(Buffer.Length >= StringTableOffset + StringTableSize); + + return Buffer.AsSpan(StringTableOffset, StringTableSize); + } + + // You can't attach constant values to interfaces in C#, so workaround that + // by getting the values based on which generic type is used + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Result GetInvalidMagicResult() + { + if (typeof(T) == typeof(StandardEntry)) + { + return ResultFs.InvalidPartitionFileSystemMagic; + } + + if (typeof(T) == typeof(HashedEntry)) + { + return ResultFs.InvalidHashedPartitionFileSystemMagic; + } + + throw new NotSupportedException(); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static uint GetMagicValue() + { + if (typeof(T) == typeof(StandardEntry)) + { + return 0x30534650; // PFS0 + } + + if (typeof(T) == typeof(HashedEntry)) + { + return 0x30534648; // HFS0 + } + + throw new NotSupportedException(); + } + + [StructLayout(LayoutKind.Sequential, Size = 0x10)] + private struct Header + { + public uint Magic; + public int EntryCount; + public int StringTableSize; + } + } +} diff --git a/src/LibHac/LibHac.csproj b/src/LibHac/LibHac.csproj index 6a15bcf3..ef1e3163 100644 --- a/src/LibHac/LibHac.csproj +++ b/src/LibHac/LibHac.csproj @@ -35,7 +35,7 @@ - +