mirror of
https://github.com/Thealexbarney/LibHac.git
synced 2024-11-14 10:49:41 +01:00
Add PartitionFileSystemCore
This commit is contained in:
parent
0c4aad32a0
commit
d08e6b060c
8 changed files with 714 additions and 4 deletions
|
@ -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<byte> 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<byte>(in Buffer32 value)
|
||||
{
|
||||
return SpanHelpers.AsByteSpan(ref Unsafe.AsRef(in value));
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static implicit operator ReadOnlySpan<byte>(in Buffer32 value)
|
||||
{
|
||||
return SpanHelpers.AsReadOnlyByteSpan(ref Unsafe.AsRef(in value));
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public ref T As<T>() where T : unmanaged
|
||||
{
|
||||
if (Unsafe.SizeOf<T>() > (uint)Unsafe.SizeOf<Buffer32>())
|
||||
{
|
||||
throw new ArgumentException();
|
||||
}
|
||||
|
||||
return ref MemoryMarshal.GetReference(AsSpan<T>());
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public Span<T> AsSpan<T>() where T : unmanaged
|
||||
{
|
||||
return SpanHelpers.AsSpan<Buffer32, T>(ref this);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public readonly ReadOnlySpan<T> AsReadOnlySpan<T>() where T : unmanaged
|
||||
{
|
||||
return SpanHelpers.AsReadOnlySpan<Buffer32, T>(ref Unsafe.AsRef(in this));
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return Bytes.ToHexString();
|
||||
}
|
||||
}
|
||||
}
|
30
src/LibHac/Crypto/CryptoUtil.cs
Normal file
30
src/LibHac/Crypto/CryptoUtil.cs
Normal file
|
@ -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<byte> buffer1, ReadOnlySpan<byte> 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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
|
|
|
@ -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<StandardEntry>();
|
||||
|
||||
Result rc = partitionFs.Initialize(pFsStorage);
|
||||
if (rc.IsFailure())
|
||||
{
|
||||
fileSystem = default;
|
||||
return rc;
|
||||
}
|
||||
|
||||
fileSystem = partitionFs;
|
||||
return Result.Success;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
39
src/LibHac/FsSystem/Detail/PartitionFileSystemFormats.cs
Normal file
39
src/LibHac/FsSystem/Detail/PartitionFileSystemFormats.cs
Normal file
|
@ -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;
|
||||
}
|
||||
}
|
373
src/LibHac/FsSystem/PartitionFileSystemCore.cs
Normal file
373
src/LibHac/FsSystem/PartitionFileSystemCore.cs
Normal file
|
@ -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<T> : FileSystemBase where T : unmanaged, IPartitionFileSystemEntry
|
||||
{
|
||||
private IStorage BaseStorage { get; set; }
|
||||
private PartitionFileSystemMetaCore<T> 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<T>();
|
||||
|
||||
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<byte> 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<byte> 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<T> ParentFs { get; }
|
||||
private OpenMode Mode { get; }
|
||||
private T _entry;
|
||||
|
||||
public PartitionFile(PartitionFileSystemCore<T> parentFs, ref T entry, OpenMode mode)
|
||||
{
|
||||
ParentFs = parentFs;
|
||||
_entry = entry;
|
||||
Mode = mode;
|
||||
}
|
||||
|
||||
protected override Result ReadImpl(out long bytesRead, long offset, Span<byte> 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<T, HashedEntry>(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<T, HashedEntry>(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<byte> 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<byte> hashBuffer = stackalloc byte[hashBufferSize];
|
||||
|
||||
while (hashRemaining > 0)
|
||||
{
|
||||
int toRead = Math.Min(hashRemaining, hashBufferSize);
|
||||
Span<byte> 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<byte> 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<byte> outBuffer, OperationId operationId, long offset, long size, ReadOnlySpan<byte> 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<T> ParentFs { get; }
|
||||
private int CurrentIndex { get; set; }
|
||||
private OpenDirectoryMode Mode { get; }
|
||||
|
||||
public PartitionDirectory(PartitionFileSystemCore<T> parentFs, OpenDirectoryMode mode)
|
||||
{
|
||||
ParentFs = parentFs;
|
||||
CurrentIndex = 0;
|
||||
Mode = mode;
|
||||
}
|
||||
|
||||
public Result Read(out long entriesRead, Span<DirectoryEntry> 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
194
src/LibHac/FsSystem/PartitionFileSystemMetaCore.cs
Normal file
194
src/LibHac/FsSystem/PartitionFileSystemMetaCore.cs
Normal file
|
@ -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<T> where T : unmanaged, IPartitionFileSystemEntry
|
||||
{
|
||||
private static int HeaderSize => Unsafe.SizeOf<Header>();
|
||||
private static int EntrySize => Unsafe.SizeOf<T>();
|
||||
|
||||
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<byte> 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<byte, Header>(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<T> entries = GetEntries();
|
||||
ReadOnlySpan<byte> names = GetStringTable();
|
||||
|
||||
for (int i = 0; i < entries.Length; i++)
|
||||
{
|
||||
if (stringTableSize <= entries[i].NameOffset)
|
||||
{
|
||||
throw new HorizonResultException(ResultFs.InvalidPartitionFileSystemEntryNameOffset.Log());
|
||||
}
|
||||
|
||||
ReadOnlySpan<byte> 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<byte> 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<T> GetEntries()
|
||||
{
|
||||
Debug.Assert(IsInitialized);
|
||||
Debug.Assert(Buffer.Length >= HeaderSize + EntryCount * EntrySize);
|
||||
|
||||
Span<byte> entryBuffer = Buffer.AsSpan(HeaderSize, EntryCount * EntrySize);
|
||||
return MemoryMarshal.Cast<byte, T>(entryBuffer);
|
||||
}
|
||||
|
||||
private ReadOnlySpan<byte> 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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -35,7 +35,7 @@
|
|||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.0.0-beta2-19554-01" PrivateAssets="All"/>
|
||||
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.0.0-beta2-19554-01" PrivateAssets="All" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
|
Loading…
Reference in a new issue