mirror of
https://github.com/Thealexbarney/LibHac.git
synced 2024-11-14 10:49:41 +01:00
Skeleton or partially implement RomFS-related classes
This commit is contained in:
parent
dc6f4fa489
commit
78d9847b6a
12 changed files with 1169 additions and 8 deletions
298
src/LibHac/Fs/Common/DbmHierarchicalRomFileTable.cs
Normal file
298
src/LibHac/Fs/Common/DbmHierarchicalRomFileTable.cs
Normal file
|
@ -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<byte> lhsExtraKey, ReadOnlySpan<byte> 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<TValue> : KeyValueRomStorageTemplate<RomEntryKey, TValue> 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<byte> 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<DirectoryRomEntry>.QueryBucketCount(bucketCount);
|
||||
}
|
||||
|
||||
public static long QueryDirectoryEntrySize(uint extraKeySize)
|
||||
{
|
||||
return EntryMapTable<DirectoryRomEntry>.QueryEntrySize(extraKeySize);
|
||||
}
|
||||
|
||||
public static long QueryFileEntryBucketStorageSize(uint bucketCount)
|
||||
{
|
||||
return EntryMapTable<FileRomEntry>.QueryBucketCount(bucketCount);
|
||||
}
|
||||
|
||||
public static long QueryFileEntrySize(uint extraKeySize)
|
||||
{
|
||||
return EntryMapTable<FileRomEntry>.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<byte> fullPath)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public Result CreateFile(out RomFileId outId, ReadOnlySpan<byte> fullPath, in RomFileInfo info)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public Result CreateFile(out RomFileId outId, ReadOnlySpan<byte> fullPath, in RomFileInfo info,
|
||||
ref CacheContext cacheContext)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public Result ConvertPathToDirectoryId(out RomDirectoryId outId, ReadOnlySpan<byte> fullPath)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public Result ConvertPathToFileId(out RomFileId outId, ReadOnlySpan<byte> fullPath)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public Result OpenFile(out RomFileInfo outInfo, ReadOnlySpan<byte> fullPath)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public Result OpenFile(out RomFileInfo outInfo, RomFileId id)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public Result FindOpen(out FindPosition outPosition, ReadOnlySpan<byte> fullPath)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public Result FindOpen(out FindPosition outPosition, RomDirectoryId id)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public Result FindNextDirectory(Span<byte> outName, ref FindPosition findPosition, int length)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public Result FindNextFile(Span<byte> 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<byte> fullPath)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
private Result FindParentDirectoryRecursive(out Position outParentPosition, ref EntryKey outDirectoryKey,
|
||||
ref DirectoryRomEntry outDirectoryEntry, ref RomPathTool.PathParser parser, ReadOnlySpan<byte> fullPath)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
private Result FindPathRecursive(ref EntryKey outKey, ref DirectoryRomEntry outParentDirectoryEntry,
|
||||
bool isDirectory, ReadOnlySpan<byte> fullPath)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
private Result FindDirectoryRecursive(ref EntryKey outKey, ref DirectoryRomEntry outParentDirectoryEntry,
|
||||
ReadOnlySpan<byte> fullPath)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
private Result FindFileRecursive(ref EntryKey outKey, ref DirectoryRomEntry outParentDirectoryEntry,
|
||||
ReadOnlySpan<byte> 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();
|
||||
}
|
||||
}
|
164
src/LibHac/Fs/Common/DbmKeyValueRomStorageTemplate.cs
Normal file
164
src/LibHac/Fs/Common/DbmKeyValueRomStorageTemplate.cs
Normal file
|
@ -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<TKey, TValue> : 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<Position>());
|
||||
}
|
||||
|
||||
public static long QueryBucketCount(uint bucketCount)
|
||||
{
|
||||
return bucketCount * Unsafe.SizeOf<Position>();
|
||||
}
|
||||
|
||||
public static long QueryEntrySize(uint extraSize)
|
||||
{
|
||||
// Todo: Use AlignOf<StorageElement> for the alignment when it's added to the language
|
||||
return Alignment.AlignUp(Unsafe.SizeOf<StorageElement>() + 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<Position>(), 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<byte> extraKey,
|
||||
in TValue value)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
protected Result GetInternal(out Position outPosition, out TValue outValue, in TKey key, uint hashKey,
|
||||
ReadOnlySpan<byte> 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<byte> 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<byte> 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<Position>();
|
||||
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<byte> outExtraKey, out int outExtraKeySize,
|
||||
Position position)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
private Result WriteKeyValue(in StorageElement element, Position position, ReadOnlySpan<byte> extraKey)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
90
src/LibHac/Fs/Common/DbmRomPathTool.cs
Normal file
90
src/LibHac/Fs/Common/DbmRomPathTool.cs
Normal file
|
@ -0,0 +1,90 @@
|
|||
using System;
|
||||
|
||||
// ReSharper disable once CheckNamespace
|
||||
namespace LibHac.Fs;
|
||||
|
||||
public static class RomPathTool
|
||||
{
|
||||
public static bool IsEqualPath(ReadOnlySpan<byte> path1, ReadOnlySpan<byte> path2, int length)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public ref struct PathParser
|
||||
{
|
||||
public PathParser()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public Result Initialize(ReadOnlySpan<byte> 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<byte> _path;
|
||||
|
||||
public RomEntryName()
|
||||
{
|
||||
_path = default;
|
||||
}
|
||||
|
||||
public void Initialize(ReadOnlySpan<byte> 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<byte> GetPath()
|
||||
{
|
||||
return _path;
|
||||
}
|
||||
}
|
||||
}
|
26
src/LibHac/Fs/Common/DbmRomTypes.cs
Normal file
26
src/LibHac/Fs/Common/DbmRomTypes.cs
Normal file
|
@ -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;
|
||||
}
|
260
src/LibHac/Fs/RomFsFileSystem.cs
Normal file
260
src/LibHac/Fs/RomFsFileSystem.cs
Normal file
|
@ -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<byte> destination, in ReadOption option)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
protected override Result DoGetSize(out long size)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
protected override Result DoOperateRange(Span<byte> outBuffer, OperationId operationId, long offset, long size,
|
||||
ReadOnlySpan<byte> inBuffer)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
protected override Result DoWrite(long offset, ReadOnlySpan<byte> 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<byte> 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<DirectoryEntry> 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<DirectoryEntry> entryBuffer)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
|
||||
public class RomFsFileSystem : IFileSystem
|
||||
{
|
||||
private HierarchicalRomFileTable _romFileTable;
|
||||
private IStorage _baseStorage;
|
||||
private UniqueRef<IStorage> _uniqueBaseStorage;
|
||||
private UniqueRef<IStorage> _directoryBucketStorage;
|
||||
private UniqueRef<IStorage> _directoryEntryStorage;
|
||||
private UniqueRef<IStorage> _fileBucketStorage;
|
||||
private UniqueRef<IStorage> _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<IStorage> baseStorage, Memory<byte> workingMemory,
|
||||
bool isFileSystemCacheUsed)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public Result Initialize(IStorage baseStorage, Memory<byte> workingMemory, bool isFileSystemCacheUsed)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
protected override Result DoOpenFile(ref UniqueRef<IFile> outFile, ref readonly Path path, OpenMode mode)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
protected override Result DoOpenDirectory(ref UniqueRef<IDirectory> 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<byte> path)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
private Result GetFileInfo(out RomFileInfo outInfo, ReadOnlySpan<byte> path)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
|
|
|
@ -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<IFileSystem> outFileSystem, ref readonly SharedRef<IStorage> romFsStorage);
|
||||
}
|
|
@ -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;
|
||||
|
||||
/// <summary>
|
||||
/// Extends <see cref="RomFsFileSystem"/> by allocating a cache buffer that is then deallocated upon disposal.
|
||||
/// </summary>
|
||||
/// <remarks>Based on nnSdk 18.3.0 (FS 18.0.0)</remarks>
|
||||
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<IStorage> 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();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Takes a <see cref="IStorage"/> containing a RomFs and opens it as an <see cref="IFileSystem"/>
|
||||
/// </summary>
|
||||
/// <remarks>Based on nnSdk 18.3.0 (FS 18.0.0)</remarks>
|
||||
public class RomFileSystemCreator : IRomFileSystemCreator
|
||||
{
|
||||
// todo: Implement properly
|
||||
private MemoryResource _allocator;
|
||||
|
||||
public RomFileSystemCreator(MemoryResource allocator)
|
||||
{
|
||||
_allocator = allocator;
|
||||
}
|
||||
|
||||
public void Dispose() { }
|
||||
|
||||
public Result Create(ref SharedRef<IFileSystem> outFileSystem, ref readonly SharedRef<IStorage> romFsStorage)
|
||||
{
|
||||
outFileSystem.Reset(new RomFsFileSystem(in romFsStorage));
|
||||
using var fs = new SharedRef<RomFileSystemWithBuffer>(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;
|
||||
}
|
||||
}
|
229
src/LibHac/FsSystem/RomFsFileSystem.cs
Normal file
229
src/LibHac/FsSystem/RomFsFileSystem.cs
Normal file
|
@ -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<byte> destination, in ReadOption option)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
protected override Result DoGetSize(out long size)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
protected override Result DoOperateRange(Span<byte> outBuffer, OperationId operationId, long offset, long size,
|
||||
ReadOnlySpan<byte> inBuffer)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
protected override Result DoWrite(long offset, ReadOnlySpan<byte> 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<DirectoryEntry> 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<DirectoryEntry> entryBuffer)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
|
||||
public class RomFsFileSystem : IFileSystem
|
||||
{
|
||||
private HierarchicalRomFileTable _romFileTable;
|
||||
private IStorage _baseStorage;
|
||||
private SharedRef<IStorage> _sharedBaseStorage;
|
||||
private UniqueRef<IStorage> _directoryBucketStorage;
|
||||
private UniqueRef<IStorage> _directoryEntryStorage;
|
||||
private UniqueRef<IStorage> _fileBucketStorage;
|
||||
private UniqueRef<IStorage> _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<IStorage> 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<IFile> outFile, ref readonly Path path, OpenMode mode)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
protected override Result DoOpenDirectory(ref UniqueRef<IDirectory> 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<byte> path)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -563,4 +563,36 @@ public class TypeLayoutTests
|
|||
Assert.Equal(8, Unsafe.SizeOf<Int64>());
|
||||
Assert.Equal(4, AlignOf<Int64>());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public static void RomFileSystemInformation_Layout()
|
||||
{
|
||||
var s = new RomFileSystemInformation();
|
||||
|
||||
Assert.Equal(0x50, Unsafe.SizeOf<RomFileSystemInformation>());
|
||||
|
||||
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<RomFileInfo>());
|
||||
|
||||
Assert.Equal(0, GetOffset(in s, in s.Offset));
|
||||
Assert.Equal(8, GetOffset(in s, in s.Size));
|
||||
|
||||
Assert.Equal(4, AlignOf<RomFileInfo>());
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue