From 78d9847b6a8d26d6528adfc7f71b11be3e6ec42d Mon Sep 17 00:00:00 2001 From: Alex Barney Date: Thu, 25 Jul 2024 22:13:49 -0700 Subject: [PATCH] Skeleton or partially implement RomFS-related classes --- .../Fs/Common/DbmHierarchicalRomFileTable.cs | 298 ++++++++++++++++++ .../Common/DbmKeyValueRomStorageTemplate.cs | 164 ++++++++++ src/LibHac/Fs/Common/DbmRomPathTool.cs | 90 ++++++ src/LibHac/Fs/Common/DbmRomTypes.cs | 26 ++ src/LibHac/Fs/RomFsFileSystem.cs | 260 +++++++++++++++ src/LibHac/FsSrv/DefaultFsServerObjects.cs | 2 +- .../FsSrv/FsCreator/IRomFileSystemCreator.cs | 5 +- .../FsSrv/FsCreator/RomFileSystemCreator.cs | 67 +++- src/LibHac/FsSystem/RomFsFileSystem.cs | 229 ++++++++++++++ src/LibHac/Tools/FsSystem/NcaUtils/Nca.cs | 2 +- src/hactoolnet/ProcessRomfs.cs | 2 +- tests/LibHac.Tests/Fs/TypeLayoutTests.cs | 32 ++ 12 files changed, 1169 insertions(+), 8 deletions(-) create mode 100644 src/LibHac/Fs/Common/DbmHierarchicalRomFileTable.cs create mode 100644 src/LibHac/Fs/Common/DbmKeyValueRomStorageTemplate.cs create mode 100644 src/LibHac/Fs/Common/DbmRomPathTool.cs create mode 100644 src/LibHac/Fs/Common/DbmRomTypes.cs create mode 100644 src/LibHac/Fs/RomFsFileSystem.cs create mode 100644 src/LibHac/FsSystem/RomFsFileSystem.cs diff --git a/src/LibHac/Fs/Common/DbmHierarchicalRomFileTable.cs b/src/LibHac/Fs/Common/DbmHierarchicalRomFileTable.cs new file mode 100644 index 00000000..7a9218fc --- /dev/null +++ b/src/LibHac/Fs/Common/DbmHierarchicalRomFileTable.cs @@ -0,0 +1,298 @@ +// ReSharper disable UnusedMember.Local UnassignedField.Local +#pragma warning disable CS0649 // Field is never assigned to, and will always have its default value + +using System; +using System.Runtime.InteropServices; + +// ReSharper disable once CheckNamespace +namespace LibHac.Fs; + +using Position = uint; +using RomDirectoryId = uint; +using RomFileId = uint; + +public class HierarchicalRomFileTable : IDisposable +{ + private const Position InvalidPosition = ~default(Position); + + public struct FindPosition + { + public Position NextDirectory; + public Position NextFile; + } + + public struct CacheContext + { + public Position ParentFirstFilePosition; + public Position ParentLastFilePosition; + + public CacheContext() + { + ParentFirstFilePosition = InvalidPosition; + } + } + + private ref struct EntryKey + { + public RomEntryKey Key; + public RomPathTool.RomEntryName Name; + + public EntryKey() + { + Name = new RomPathTool.RomEntryName(); + } + + public readonly uint Hash() + { + uint hash = 123456789 ^ Key.Parent; + + foreach (byte c in Name.GetPath()) + { + hash = c ^ ((hash << 27) | (hash >> 5)); + } + + return hash; + } + } + + private struct RomEntryKey + { + public Position Parent; + + public readonly bool IsEqual(in RomEntryKey rhs, ReadOnlySpan lhsExtraKey, ReadOnlySpan rhsExtraKey) + { + if (Parent != rhs.Parent) + return false; + + if (lhsExtraKey.Length != rhsExtraKey.Length) + return false; + + return RomPathTool.IsEqualPath(lhsExtraKey, rhsExtraKey, lhsExtraKey.Length); + } + } + + [StructLayout(LayoutKind.Sequential)] + private struct DirectoryRomEntry + { + public Position Next; + public Position Dir; + public Position File; + } + + [StructLayout(LayoutKind.Sequential)] + private struct FileRomEntry + { + public Position Next; + public RomFileInfo Info; + } + + // Todo: Add generic types for the key types once generics can use ref structs + private class EntryMapTable : KeyValueRomStorageTemplate where TValue : unmanaged + { + public Result Add(out Position outPosition, in EntryKey key, in TValue value) + { + return AddInternal(out outPosition, in key.Key, key.Hash(), key.Name.GetPath(), in value).Ret(); + } + + public Result Get(out Position outPosition, out TValue outValue, in EntryKey key) + { + return GetInternal(out outPosition, out outValue, in key.Key, key.Hash(), key.Name.GetPath()).Ret(); + } + + public new Result GetByPosition(out RomEntryKey outKey, out TValue outValue, Position position) + { + return base.GetByPosition(out outKey, out outValue, position).Ret(); + } + + public new Result GetByPosition(out RomEntryKey outKey, out TValue outValue, Span outExtraKey, + out int outExtraKeySize, Position position) + { + return base.GetByPosition(out outKey, out outValue, outExtraKey, out outExtraKeySize, position).Ret(); + } + + public new Result SetByPosition(Position position, in TValue value) + { + return base.SetByPosition(position, in value).Ret(); + } + } + + public HierarchicalRomFileTable() + { + throw new NotImplementedException(); + } + + public void Dispose() + { + throw new NotImplementedException(); + } + + public static long QueryDirectoryEntryBucketStorageSize(uint bucketCount) + { + return EntryMapTable.QueryBucketCount(bucketCount); + } + + public static long QueryDirectoryEntrySize(uint extraKeySize) + { + return EntryMapTable.QueryEntrySize(extraKeySize); + } + + public static long QueryFileEntryBucketStorageSize(uint bucketCount) + { + return EntryMapTable.QueryBucketCount(bucketCount); + } + + public static long QueryFileEntrySize(uint extraKeySize) + { + return EntryMapTable.QueryEntrySize(extraKeySize); + } + + public static Result Format(ref readonly ValueSubStorage directoryBucketStorage, + ref readonly ValueSubStorage fileBucketStorage) + { + throw new NotImplementedException(); + } + + public Result Initialize(ref readonly ValueSubStorage directoryBucketStorage, + ref readonly ValueSubStorage directoryEntryStorage, ref readonly ValueSubStorage fileBucketStorage, + ref readonly ValueSubStorage fileEntryStorage) + { + throw new NotImplementedException(); + } + + public void FinalizeObject() + { + throw new NotImplementedException(); + } + + public Result CreateRootDirectory() + { + throw new NotImplementedException(); + } + + public Result CreateDirectory(out RomDirectoryId outId, ReadOnlySpan fullPath) + { + throw new NotImplementedException(); + } + + public Result CreateFile(out RomFileId outId, ReadOnlySpan fullPath, in RomFileInfo info) + { + throw new NotImplementedException(); + } + + public Result CreateFile(out RomFileId outId, ReadOnlySpan fullPath, in RomFileInfo info, + ref CacheContext cacheContext) + { + throw new NotImplementedException(); + } + + public Result ConvertPathToDirectoryId(out RomDirectoryId outId, ReadOnlySpan fullPath) + { + throw new NotImplementedException(); + } + + public Result ConvertPathToFileId(out RomFileId outId, ReadOnlySpan fullPath) + { + throw new NotImplementedException(); + } + + public Result OpenFile(out RomFileInfo outInfo, ReadOnlySpan fullPath) + { + throw new NotImplementedException(); + } + + public Result OpenFile(out RomFileInfo outInfo, RomFileId id) + { + throw new NotImplementedException(); + } + + public Result FindOpen(out FindPosition outPosition, ReadOnlySpan fullPath) + { + throw new NotImplementedException(); + } + + public Result FindOpen(out FindPosition outPosition, RomDirectoryId id) + { + throw new NotImplementedException(); + } + + public Result FindNextDirectory(Span outName, ref FindPosition findPosition, int length) + { + throw new NotImplementedException(); + } + + public Result FindNextFile(Span outName, ref FindPosition findPosition, int length) + { + throw new NotImplementedException(); + } + + public Result QueryRomFileSystemSize(out long outDirectoryEntrySize, out long outFileEntrySize) + { + throw new NotImplementedException(); + } + + private Result GetParent(out Position outParentPosition, ref EntryKey outDirectoryKey, + ref DirectoryRomEntry outDirectoryEntry, Position position, ref RomPathTool.RomEntryName name, + ReadOnlySpan fullPath) + { + throw new NotImplementedException(); + } + + private Result FindParentDirectoryRecursive(out Position outParentPosition, ref EntryKey outDirectoryKey, + ref DirectoryRomEntry outDirectoryEntry, ref RomPathTool.PathParser parser, ReadOnlySpan fullPath) + { + throw new NotImplementedException(); + } + + private Result FindPathRecursive(ref EntryKey outKey, ref DirectoryRomEntry outParentDirectoryEntry, + bool isDirectory, ReadOnlySpan fullPath) + { + throw new NotImplementedException(); + } + + private Result FindDirectoryRecursive(ref EntryKey outKey, ref DirectoryRomEntry outParentDirectoryEntry, + ReadOnlySpan fullPath) + { + throw new NotImplementedException(); + } + + private Result FindFileRecursive(ref EntryKey outKey, ref DirectoryRomEntry outParentDirectoryEntry, + ReadOnlySpan fullPath) + { + throw new NotImplementedException(); + } + + private Result CheckSameEntryExists(in EntryKey key, Result resultIfExists) + { + throw new NotImplementedException(); + } + + private Result GetDirectoryEntry(out Position outPosition, out DirectoryRomEntry outEntry, in EntryKey key) + { + throw new NotImplementedException(); + } + + private Result GetDirectoryEntry(out DirectoryRomEntry outEntry, RomDirectoryId id) + { + throw new NotImplementedException(); + } + + private Result GetFileEntry(out Position outPosition, out FileRomEntry outEntry, in EntryKey key) + { + throw new NotImplementedException(); + } + + private Result GetFileEntry(out FileRomEntry outEntry, RomFileId id) + { + throw new NotImplementedException(); + } + + private Result OpenFile(out RomFileInfo outFileInfo, in EntryKey key) + { + throw new NotImplementedException(); + } + + private Result FindOpen(out FindPosition outPosition, in EntryKey key) + { + throw new NotImplementedException(); + } +} \ No newline at end of file diff --git a/src/LibHac/Fs/Common/DbmKeyValueRomStorageTemplate.cs b/src/LibHac/Fs/Common/DbmKeyValueRomStorageTemplate.cs new file mode 100644 index 00000000..3f2fb8b5 --- /dev/null +++ b/src/LibHac/Fs/Common/DbmKeyValueRomStorageTemplate.cs @@ -0,0 +1,164 @@ +// ReSharper disable UnusedMember.Local NotAccessedField.Local +#pragma warning disable CS0169 // Field is never used +#pragma warning disable CS0649 // Field is never assigned to, and will always have its default value +using System; +using System.Runtime.CompilerServices; +using LibHac.Common; +using LibHac.Diag; +using LibHac.Util; + +// ReSharper disable once CheckNamespace +namespace LibHac.Fs; + +using Position = uint; +using StorageSizeType = uint; + +public class KeyValueRomStorageTemplate : IDisposable where TKey : unmanaged where TValue : unmanaged +{ + private long _bucketCount; + private ValueSubStorage _bucketStorage; + private ValueSubStorage _entryStorage; + private long _totalEntrySize; + private uint _entryCount; + + private const Position InvalidPosition = ~default(Position); + + private struct StorageElement + { + public TKey Key; + public TValue Value; + public Position Next; + public StorageSizeType Size; + } + + public KeyValueRomStorageTemplate() + { + throw new NotImplementedException(); + } + + public virtual void Dispose() + { + throw new NotImplementedException(); + } + + public static uint QueryBucketCount(long bucketStorageSize) + { + return (uint)(bucketStorageSize / Unsafe.SizeOf()); + } + + public static long QueryBucketCount(uint bucketCount) + { + return bucketCount * Unsafe.SizeOf(); + } + + public static long QueryEntrySize(uint extraSize) + { + // Todo: Use AlignOf for the alignment when it's added to the language + return Alignment.AlignUp(Unsafe.SizeOf() + extraSize, 4); + } + + public static Result Format(ref ValueSubStorage bucketStorage, uint bucketCount) + { + Position pos = InvalidPosition; + + for (int i = 0; i < bucketCount; i++) + { + Result res = bucketStorage.Write(i * Unsafe.SizeOf(), SpanHelpers.AsByteSpan(ref pos)); + if (res.IsFailure()) return res.Miss(); + } + + return Result.Success; + } + + public Result Initialize(ref readonly ValueSubStorage bucketStorage, long bucketCount, + ref readonly ValueSubStorage entryStorage) + { + throw new NotImplementedException(); + } + + public void FinalizeObject() + { + throw new NotImplementedException(); + } + + public long GetTotalEntrySize() => _totalEntrySize; + + private long HashToBucket(uint hashKey) => hashKey % _bucketCount; + + protected Result AddInternal(out Position outPosition, in TKey key, uint hashKey, ReadOnlySpan extraKey, + in TValue value) + { + throw new NotImplementedException(); + } + + protected Result GetInternal(out Position outPosition, out TValue outValue, in TKey key, uint hashKey, + ReadOnlySpan extraKey) + { + throw new NotImplementedException(); + } + + protected Result GetByPosition(out TKey outKey, out TValue outValue, Position position) + { + throw new NotImplementedException(); + } + + protected Result GetByPosition(out TKey outKey, out TValue outValue, Span outExtraKey, + out int outExtraKeySize, Position position) + { + throw new NotImplementedException(); + } + + protected Result SetByPosition(Position position, in TValue value) + { + Result res = ReadKeyValue(out StorageElement element, position); + if (res.IsFailure()) return res.Miss(); + + element.Value = value; + return WriteKeyValue(in element, position, default).Ret(); + } + + private Result FindInternal(out Position outPosition, out Position outPreviousPosition, + out StorageElement outStorageElement, in TKey key, uint hashKey, ReadOnlySpan extraKey) + { + throw new NotImplementedException(); + } + + private Result AllocateEntry(out Position outPosition, uint extraSize) + { + throw new NotImplementedException(); + } + + private Result LinkEntry(out Position outNextPosition, Position position, uint hashKey) + { + throw new NotImplementedException(); + } + + private Result ReadBucket(out Position outPosition, long index) + { + throw new NotImplementedException(); + } + + private Result WriteBucket(Position position, long index) + { + Assert.SdkRequiresLess(index, _bucketCount); + + long offset = index * Unsafe.SizeOf(); + return _bucketStorage.Write(offset, SpanHelpers.AsReadOnlyByteSpan(in position)).Ret(); + } + + private Result ReadKeyValue(out StorageElement outElement, Position position) + { + throw new NotImplementedException(); + } + + private Result ReadKeyValue(out StorageElement outElement, Span outExtraKey, out int outExtraKeySize, + Position position) + { + throw new NotImplementedException(); + } + + private Result WriteKeyValue(in StorageElement element, Position position, ReadOnlySpan extraKey) + { + throw new NotImplementedException(); + } +} \ No newline at end of file diff --git a/src/LibHac/Fs/Common/DbmRomPathTool.cs b/src/LibHac/Fs/Common/DbmRomPathTool.cs new file mode 100644 index 00000000..3d114c97 --- /dev/null +++ b/src/LibHac/Fs/Common/DbmRomPathTool.cs @@ -0,0 +1,90 @@ +using System; + +// ReSharper disable once CheckNamespace +namespace LibHac.Fs; + +public static class RomPathTool +{ + public static bool IsEqualPath(ReadOnlySpan path1, ReadOnlySpan path2, int length) + { + throw new NotImplementedException(); + } + + public ref struct PathParser + { + public PathParser() + { + throw new NotImplementedException(); + } + + public Result Initialize(ReadOnlySpan fullPath) + { + throw new NotImplementedException(); + } + + public void FinalizeObject() + { + throw new NotImplementedException(); + } + + public readonly bool IsParseFinished() + { + throw new NotImplementedException(); + } + + public readonly bool IsDirectoryPath() + { + throw new NotImplementedException(); + } + + public Result GetNextDirectoryName(out RomEntryName outName) + { + throw new NotImplementedException(); + } + + public readonly Result GetAsDirectoryName(out RomEntryName outName) + { + throw new NotImplementedException(); + } + + public readonly Result GetAsFileName(out RomEntryName outName) + { + throw new NotImplementedException(); + } + } + + public ref struct RomEntryName + { + private ReadOnlySpan _path; + + public RomEntryName() + { + _path = default; + } + + public void Initialize(ReadOnlySpan path) + { + _path = path; + } + + public readonly bool IsCurrentDirectory() + { + return _path.Length == 1 && _path[0] == (byte)'.'; + } + + public readonly bool IsParentDirectory() + { + return _path.Length == 2 && _path[0] == (byte)'.' && _path[1] == (byte)'.'; + } + + public readonly bool IsRootDirectory() + { + return _path.Length == 0; + } + + public readonly ReadOnlySpan GetPath() + { + return _path; + } + } +} \ No newline at end of file diff --git a/src/LibHac/Fs/Common/DbmRomTypes.cs b/src/LibHac/Fs/Common/DbmRomTypes.cs new file mode 100644 index 00000000..daf43c66 --- /dev/null +++ b/src/LibHac/Fs/Common/DbmRomTypes.cs @@ -0,0 +1,26 @@ +using System.Runtime.InteropServices; + +// ReSharper disable once CheckNamespace +namespace LibHac.Fs; + +[StructLayout(LayoutKind.Sequential)] +public struct RomFileSystemInformation +{ + public long HeaderSize; + public long DirectoryBucketOffset; + public long DirectoryBucketSize; + public long DirectoryEntryOffset; + public long DirectoryEntrySize; + public long FileBucketOffset; + public long FileBucketSize; + public long FileEntryOffset; + public long FileEntrySize; + public long DataOffset; +} + +[StructLayout(LayoutKind.Sequential)] +public struct RomFileInfo +{ + public Int64 Offset; + public Int64 Size; +} \ No newline at end of file diff --git a/src/LibHac/Fs/RomFsFileSystem.cs b/src/LibHac/Fs/RomFsFileSystem.cs new file mode 100644 index 00000000..58d45738 --- /dev/null +++ b/src/LibHac/Fs/RomFsFileSystem.cs @@ -0,0 +1,260 @@ +// ReSharper disable UnusedMember.Local NotAccessedField.Local UnusedType.Local +#pragma warning disable CS0169 // Field is never used +#pragma warning disable CS0649 // Field is never assigned to, and will always have its default value + +using System; +using LibHac.Common; +using LibHac.Fs.Fsa; + +namespace LibHac.Fs.Impl +{ + public class RomFsFile : IFile + { + private RomFsFileSystem _parent; + private long _startOffset; + private long _emdOffset; + + public RomFsFile(RomFsFileSystem parent, long startOffset, long emdOffset) + { + throw new NotImplementedException(); + } + + public long GetOffset() + { + throw new NotImplementedException(); + } + + public long GetSize() + { + throw new NotImplementedException(); + } + + public IStorage GetStorage() + { + throw new NotImplementedException(); + } + + protected override Result DoRead(out long bytesRead, long offset, Span destination, in ReadOption option) + { + throw new NotImplementedException(); + } + + protected override Result DoGetSize(out long size) + { + throw new NotImplementedException(); + } + + protected override Result DoOperateRange(Span outBuffer, OperationId operationId, long offset, long size, + ReadOnlySpan inBuffer) + { + throw new NotImplementedException(); + } + + protected override Result DoWrite(long offset, ReadOnlySpan source, in WriteOption option) + { + throw new NotImplementedException(); + } + + protected override Result DoSetSize(long size) + { + throw new NotImplementedException(); + } + + protected override Result DoFlush() + { + throw new NotImplementedException(); + } + } +} + +namespace LibHac.Fs +{ + file static class Anonymous + { + public static long CalculateRequiredWorkingMemorySize(in RomFileSystemInformation fsInfo) + { + return fsInfo.DirectoryBucketSize + fsInfo.DirectoryEntrySize + fsInfo.FileBucketSize + fsInfo.FileEntrySize; + } + + public static Result ReadFile(IStorage storage, long offset, Span buffer) + { + throw new NotImplementedException(); + } + + public static Result ReadFileHeader(IStorage storage, out RomFileSystemInformation outHeader) + { + throw new NotImplementedException(); + } + } + + file class RomFsDirectory : IDirectory + { + private RomFsFileSystem _parent; + private HierarchicalRomFileTable.FindPosition _currentPosition; + private HierarchicalRomFileTable.FindPosition _initialPosition; + private OpenDirectoryMode _mode; + + public RomFsDirectory(RomFsFileSystem parent, in HierarchicalRomFileTable.FindPosition initialFindPosition, + OpenDirectoryMode mode) + { + throw new NotImplementedException(); + } + + public override void Dispose() + { + throw new NotImplementedException(); + } + + protected override Result DoRead(out long entriesRead, Span entryBuffer) + { + throw new NotImplementedException(); + } + + protected override Result DoGetEntryCount(out long entryCount) + { + throw new NotImplementedException(); + } + + private Result ReadInternal(out long outReadCount, ref HierarchicalRomFileTable.FindPosition findPosition, + Span entryBuffer) + { + throw new NotImplementedException(); + } + } + + public class RomFsFileSystem : IFileSystem + { + private HierarchicalRomFileTable _romFileTable; + private IStorage _baseStorage; + private UniqueRef _uniqueBaseStorage; + private UniqueRef _directoryBucketStorage; + private UniqueRef _directoryEntryStorage; + private UniqueRef _fileBucketStorage; + private UniqueRef _fileEntryStorage; + private long _entrySize; + + public RomFsFileSystem() + { + throw new NotImplementedException(); + } + + public override void Dispose() + { + throw new NotImplementedException(); + } + + public IStorage GetBaseStorage() => _baseStorage; + public HierarchicalRomFileTable GetRomFileTable() => _romFileTable; + + public static Result GetRequiredWorkingMemorySize(out long outValue, IStorage storage) + { + throw new NotImplementedException(); + } + + public Result Initialize(ref UniqueRef baseStorage, Memory workingMemory, + bool isFileSystemCacheUsed) + { + throw new NotImplementedException(); + } + + public Result Initialize(IStorage baseStorage, Memory workingMemory, bool isFileSystemCacheUsed) + { + throw new NotImplementedException(); + } + + protected override Result DoOpenFile(ref UniqueRef outFile, ref readonly Path path, OpenMode mode) + { + throw new NotImplementedException(); + } + + protected override Result DoOpenDirectory(ref UniqueRef outDirectory, ref readonly Path path, OpenDirectoryMode mode) + { + throw new NotImplementedException(); + } + + protected override Result DoGetEntryType(out DirectoryEntryType entryType, ref readonly Path path) + { + throw new NotImplementedException(); + } + + protected override Result DoCreateFile(ref readonly Path path, long size, CreateFileOptions option) + { + throw new NotImplementedException(); + } + + protected override Result DoDeleteFile(ref readonly Path path) + { + throw new NotImplementedException(); + } + + protected override Result DoCreateDirectory(ref readonly Path path) + { + throw new NotImplementedException(); + } + + protected override Result DoDeleteDirectory(ref readonly Path path) + { + throw new NotImplementedException(); + } + + protected override Result DoDeleteDirectoryRecursively(ref readonly Path path) + { + throw new NotImplementedException(); + } + + protected override Result DoCleanDirectoryRecursively(ref readonly Path path) + { + throw new NotImplementedException(); + } + + protected override Result DoRenameFile(ref readonly Path currentPath, ref readonly Path newPath) + { + throw new NotImplementedException(); + } + + protected override Result DoRenameDirectory(ref readonly Path currentPath, ref readonly Path newPath) + { + throw new NotImplementedException(); + } + + protected override Result DoCommit() + { + throw new NotImplementedException(); + } + + protected override Result DoCommitProvisionally(long counter) + { + throw new NotImplementedException(); + } + + protected override Result DoRollback() + { + throw new NotImplementedException(); + } + + protected override Result DoGetFreeSpaceSize(out long freeSpace, ref readonly Path path) + { + throw new NotImplementedException(); + } + + protected override Result DoGetTotalSpaceSize(out long totalSpace, ref readonly Path path) + { + throw new NotImplementedException(); + } + + protected override Result DoGetFileSystemAttribute(out FileSystemAttribute outAttribute) + { + throw new NotImplementedException(); + } + + public Result GetFileBaseOffset(out long outOffset, ReadOnlySpan path) + { + throw new NotImplementedException(); + } + + private Result GetFileInfo(out RomFileInfo outInfo, ReadOnlySpan path) + { + throw new NotImplementedException(); + } + } +} \ No newline at end of file diff --git a/src/LibHac/FsSrv/DefaultFsServerObjects.cs b/src/LibHac/FsSrv/DefaultFsServerObjects.cs index 91e14596..37cb642d 100644 --- a/src/LibHac/FsSrv/DefaultFsServerObjects.cs +++ b/src/LibHac/FsSrv/DefaultFsServerObjects.cs @@ -36,7 +36,7 @@ public class DefaultFsServerObjects IBufferManager bufferManager = null; IHash256GeneratorFactorySelector ncaHashGeneratorFactorySelector = null; - creators.RomFileSystemCreator = new RomFileSystemCreator(); + creators.RomFileSystemCreator = new RomFileSystemCreator(memoryResource); creators.PartitionFileSystemCreator = new PartitionFileSystemCreator(); creators.StorageOnNcaCreator = new StorageOnNcaCreator(memoryResource, bufferManager, InitializeNcaReader, new NcaCompressionConfiguration(), ncaHashGeneratorFactorySelector); creators.TargetManagerFileSystemCreator = new TargetManagerFileSystemCreator(); diff --git a/src/LibHac/FsSrv/FsCreator/IRomFileSystemCreator.cs b/src/LibHac/FsSrv/FsCreator/IRomFileSystemCreator.cs index ec70c8bd..9463f00a 100644 --- a/src/LibHac/FsSrv/FsCreator/IRomFileSystemCreator.cs +++ b/src/LibHac/FsSrv/FsCreator/IRomFileSystemCreator.cs @@ -1,10 +1,11 @@ -using LibHac.Common; +using System; +using LibHac.Common; using LibHac.Fs; using LibHac.Fs.Fsa; namespace LibHac.FsSrv.FsCreator; -public interface IRomFileSystemCreator +public interface IRomFileSystemCreator : IDisposable { Result Create(ref SharedRef outFileSystem, ref readonly SharedRef romFsStorage); } \ No newline at end of file diff --git a/src/LibHac/FsSrv/FsCreator/RomFileSystemCreator.cs b/src/LibHac/FsSrv/FsCreator/RomFileSystemCreator.cs index 5bc2d710..807d261a 100644 --- a/src/LibHac/FsSrv/FsCreator/RomFileSystemCreator.cs +++ b/src/LibHac/FsSrv/FsCreator/RomFileSystemCreator.cs @@ -1,16 +1,77 @@ using LibHac.Common; using LibHac.Fs; using LibHac.Fs.Fsa; -using LibHac.Tools.FsSystem.RomFs; +using Buffer = LibHac.Mem.Buffer; +using RomFsFileSystem = LibHac.FsSystem.RomFsFileSystem; namespace LibHac.FsSrv.FsCreator; +/// +/// Extends by allocating a cache buffer that is then deallocated upon disposal. +/// +/// Based on nnSdk 18.3.0 (FS 18.0.0) +file class RomFileSystemWithBuffer : RomFsFileSystem +{ + private const int MaxBufferSize = 1024 * 128; + + private Buffer _metaCacheBuffer; + private MemoryResource _allocator; + + public RomFileSystemWithBuffer(MemoryResource allocator) + { + _allocator = allocator; + } + + public override void Dispose() + { + if (!_metaCacheBuffer.IsNull) + _allocator.Deallocate(ref _metaCacheBuffer); + + base.Dispose(); + } + + public Result Initialize(ref readonly SharedRef baseStorage) + { + Result res = GetRequiredWorkingMemorySize(out long bufferSize, baseStorage.Get); + if (res.IsFailure() || bufferSize == 0 || bufferSize >= MaxBufferSize) + { + return Initialize(in baseStorage, Buffer.Empty, useCache: false).Ret(); + } + + _metaCacheBuffer = _allocator.Allocate(bufferSize); + if (_metaCacheBuffer.IsNull) + { + return Initialize(in baseStorage, Buffer.Empty, useCache: false).Ret(); + } + + return Initialize(in baseStorage, _metaCacheBuffer, useCache: true).Ret(); + } +} + +/// +/// Takes a containing a RomFs and opens it as an +/// +/// Based on nnSdk 18.3.0 (FS 18.0.0) public class RomFileSystemCreator : IRomFileSystemCreator { - // todo: Implement properly + private MemoryResource _allocator; + + public RomFileSystemCreator(MemoryResource allocator) + { + _allocator = allocator; + } + + public void Dispose() { } + public Result Create(ref SharedRef outFileSystem, ref readonly SharedRef romFsStorage) { - outFileSystem.Reset(new RomFsFileSystem(in romFsStorage)); + using var fs = new SharedRef(new RomFileSystemWithBuffer(_allocator)); + if (!fs.HasValue) return ResultFs.AllocationMemoryFailedInRomFileSystemCreatorA.Log(); + + Result res = fs.Get.Initialize(in romFsStorage); + if (res.IsFailure()) return res.Miss(); + + outFileSystem.SetByMove(ref fs.Ref); return Result.Success; } } \ No newline at end of file diff --git a/src/LibHac/FsSystem/RomFsFileSystem.cs b/src/LibHac/FsSystem/RomFsFileSystem.cs new file mode 100644 index 00000000..10ad833c --- /dev/null +++ b/src/LibHac/FsSystem/RomFsFileSystem.cs @@ -0,0 +1,229 @@ +// ReSharper disable UnusedMember.Local NotAccessedField.Local UnusedType.Local +#pragma warning disable CS0169 // Field is never used +#pragma warning disable CS0649 // Field is never assigned to, and will always have its default value +using System; +using LibHac.Common; +using LibHac.Fs; +using LibHac.Fs.Fsa; +using Buffer = LibHac.Mem.Buffer; + +namespace LibHac.FsSystem; + +file static class Anonymous +{ + public static long CalculateRequiredWorkingMemorySize(in RomFileSystemInformation fsInfo) + { + return fsInfo.DirectoryBucketSize + fsInfo.DirectoryEntrySize + fsInfo.FileBucketSize + fsInfo.FileEntrySize; + } +} + +file class RomFsFile : IFile +{ + private RomFsFileSystem _parent; + private long _startOffset; + private long _emdOffset; + + public RomFsFile(RomFsFileSystem parent, long startOffset, long emdOffset) + { + throw new NotImplementedException(); + } + + private long GetSize() => _emdOffset - _startOffset; + + protected override Result DoRead(out long bytesRead, long offset, Span destination, in ReadOption option) + { + throw new NotImplementedException(); + } + + protected override Result DoGetSize(out long size) + { + throw new NotImplementedException(); + } + + protected override Result DoOperateRange(Span outBuffer, OperationId operationId, long offset, long size, + ReadOnlySpan inBuffer) + { + throw new NotImplementedException(); + } + + protected override Result DoWrite(long offset, ReadOnlySpan source, in WriteOption option) + { + throw new NotImplementedException(); + } + + protected override Result DoSetSize(long size) + { + throw new NotImplementedException(); + } + + protected override Result DoFlush() + { + throw new NotImplementedException(); + } +} + +file class RomFsDirectory : IDirectory +{ + private RomFsFileSystem _parent; + private HierarchicalRomFileTable.FindPosition _currentPosition; + private HierarchicalRomFileTable.FindPosition _initialPosition; + private OpenDirectoryMode _mode; + + public RomFsDirectory(RomFsFileSystem parent, in HierarchicalRomFileTable.FindPosition initialFindPosition, + OpenDirectoryMode mode) + { + throw new NotImplementedException(); + } + + public override void Dispose() + { + throw new NotImplementedException(); + } + + protected override Result DoRead(out long entriesRead, Span entryBuffer) + { + throw new NotImplementedException(); + } + + protected override Result DoGetEntryCount(out long entryCount) + { + throw new NotImplementedException(); + } + + private Result ReadInternal(out long outReadCount, ref HierarchicalRomFileTable.FindPosition findPosition, + Span entryBuffer) + { + throw new NotImplementedException(); + } +} + +public class RomFsFileSystem : IFileSystem +{ + private HierarchicalRomFileTable _romFileTable; + private IStorage _baseStorage; + private SharedRef _sharedBaseStorage; + private UniqueRef _directoryBucketStorage; + private UniqueRef _directoryEntryStorage; + private UniqueRef _fileBucketStorage; + private UniqueRef _fileEntryStorage; + private long _entrySize; + + public RomFsFileSystem() + { + throw new NotImplementedException(); + } + + public override void Dispose() + { + throw new NotImplementedException(); + } + + public IStorage GetBaseFile() => _baseStorage; + public HierarchicalRomFileTable GetRomFileTable() => _romFileTable; + + public static Result GetRequiredWorkingMemorySize(out long outValue, IStorage storage) + { + throw new NotImplementedException(); + } + + public Result Initialize(ref readonly SharedRef baseStorage, Buffer workingMemory, bool useCache) + { + throw new NotImplementedException(); + } + + public Result Initialize(IStorage baseStorage, Buffer workingMemory, bool useCache) + { + throw new NotImplementedException(); + } + + private Result CheckPathFormat(ref readonly Path path) + { + throw new NotImplementedException(); + } + + public Result GetFileBaseOffset(out long outOffset, ref readonly Path path) + { + throw new NotImplementedException(); + } + + protected override Result DoOpenFile(ref UniqueRef outFile, ref readonly Path path, OpenMode mode) + { + throw new NotImplementedException(); + } + + protected override Result DoOpenDirectory(ref UniqueRef outDirectory, ref readonly Path path, + OpenDirectoryMode mode) + { + throw new NotImplementedException(); + } + + protected override Result DoGetEntryType(out DirectoryEntryType entryType, ref readonly Path path) + { + throw new NotImplementedException(); + } + + protected override Result DoCreateFile(ref readonly Path path, long size, CreateFileOptions option) + { + throw new NotImplementedException(); + } + + protected override Result DoDeleteFile(ref readonly Path path) + { + throw new NotImplementedException(); + } + + protected override Result DoCreateDirectory(ref readonly Path path) + { + throw new NotImplementedException(); + } + + protected override Result DoDeleteDirectory(ref readonly Path path) + { + throw new NotImplementedException(); + } + + protected override Result DoDeleteDirectoryRecursively(ref readonly Path path) + { + throw new NotImplementedException(); + } + + protected override Result DoCleanDirectoryRecursively(ref readonly Path path) + { + throw new NotImplementedException(); + } + + protected override Result DoRenameFile(ref readonly Path currentPath, ref readonly Path newPath) + { + throw new NotImplementedException(); + } + + protected override Result DoRenameDirectory(ref readonly Path currentPath, ref readonly Path newPath) + { + throw new NotImplementedException(); + } + + protected override Result DoCommit() + { + throw new NotImplementedException(); + } + + protected override Result DoCommitProvisionally(long counter) + { + throw new NotImplementedException(); + } + + protected override Result DoGetFreeSpaceSize(out long freeSpace, ref readonly Path path) + { + throw new NotImplementedException(); + } + + protected override Result DoGetFileSystemAttribute(out FileSystemAttribute outAttribute) + { + throw new NotImplementedException(); + } + + private Result GetFileInfo(out RomFileInfo outInfo, ReadOnlySpan path) + { + throw new NotImplementedException(); + } +} \ No newline at end of file diff --git a/src/LibHac/Tools/FsSystem/NcaUtils/Nca.cs b/src/LibHac/Tools/FsSystem/NcaUtils/Nca.cs index ded4be41..e4f2023c 100644 --- a/src/LibHac/Tools/FsSystem/NcaUtils/Nca.cs +++ b/src/LibHac/Tools/FsSystem/NcaUtils/Nca.cs @@ -12,8 +12,8 @@ using LibHac.Fs.Fsa; using LibHac.FsSystem; using LibHac.Spl; using LibHac.Tools.Crypto; -using LibHac.Tools.FsSystem.RomFs; using KeyType = LibHac.Common.Keys.KeyType; +using RomFsFileSystem = LibHac.Tools.FsSystem.RomFs.RomFsFileSystem; namespace LibHac.Tools.FsSystem.NcaUtils; diff --git a/src/hactoolnet/ProcessRomfs.cs b/src/hactoolnet/ProcessRomfs.cs index be897c00..54e4c8a1 100644 --- a/src/hactoolnet/ProcessRomfs.cs +++ b/src/hactoolnet/ProcessRomfs.cs @@ -3,7 +3,7 @@ using LibHac.Fs; using LibHac.FsSystem; using LibHac.Tools.Fs; using LibHac.Tools.FsSystem; -using LibHac.Tools.FsSystem.RomFs; +using RomFsFileSystem = LibHac.Tools.FsSystem.RomFs.RomFsFileSystem; namespace hactoolnet; diff --git a/tests/LibHac.Tests/Fs/TypeLayoutTests.cs b/tests/LibHac.Tests/Fs/TypeLayoutTests.cs index 54b2008c..bc5d7e09 100644 --- a/tests/LibHac.Tests/Fs/TypeLayoutTests.cs +++ b/tests/LibHac.Tests/Fs/TypeLayoutTests.cs @@ -563,4 +563,36 @@ public class TypeLayoutTests Assert.Equal(8, Unsafe.SizeOf()); Assert.Equal(4, AlignOf()); } + + [Fact] + public static void RomFileSystemInformation_Layout() + { + var s = new RomFileSystemInformation(); + + Assert.Equal(0x50, Unsafe.SizeOf()); + + Assert.Equal(0x00, GetOffset(in s, in s.HeaderSize)); + Assert.Equal(0x08, GetOffset(in s, in s.DirectoryBucketOffset)); + Assert.Equal(0x10, GetOffset(in s, in s.DirectoryBucketSize)); + Assert.Equal(0x18, GetOffset(in s, in s.DirectoryEntryOffset)); + Assert.Equal(0x20, GetOffset(in s, in s.DirectoryEntrySize)); + Assert.Equal(0x28, GetOffset(in s, in s.FileBucketOffset)); + Assert.Equal(0x30, GetOffset(in s, in s.FileBucketSize)); + Assert.Equal(0x38, GetOffset(in s, in s.FileEntryOffset)); + Assert.Equal(0x40, GetOffset(in s, in s.FileEntrySize)); + Assert.Equal(0x48, GetOffset(in s, in s.DataOffset)); + } + + [Fact] + public static void RomFileInfo_Layout() + { + var s = new RomFileInfo(); + + Assert.Equal(0x10, Unsafe.SizeOf()); + + Assert.Equal(0, GetOffset(in s, in s.Offset)); + Assert.Equal(8, GetOffset(in s, in s.Size)); + + Assert.Equal(4, AlignOf()); + } } \ No newline at end of file