mirror of
https://github.com/Thealexbarney/LibHac.git
synced 2024-11-14 10:49:41 +01:00
Use the dictionary in the romfs instead of creating our own
This commit is contained in:
parent
e5f808cd2d
commit
fb4619f4ab
11 changed files with 481 additions and 215 deletions
|
@ -33,7 +33,9 @@ namespace LibHac.IO
|
||||||
{
|
{
|
||||||
var levelData = new IntegrityVerificationStorage(levelInfo[i], Levels[i - 1], integrityCheckLevel, leaveOpen);
|
var levelData = new IntegrityVerificationStorage(levelInfo[i], Levels[i - 1], integrityCheckLevel, leaveOpen);
|
||||||
|
|
||||||
Levels[i] = new CachedStorage(levelData, 4, leaveOpen);
|
int cacheCount = Math.Min((int)Util.DivideByRoundUp(levelData.Length, levelInfo[i].BlockSize), 4);
|
||||||
|
|
||||||
|
Levels[i] = new CachedStorage(levelData, cacheCount, leaveOpen);
|
||||||
LevelValidities[i - 1] = levelData.BlockValidities;
|
LevelValidities[i - 1] = levelData.BlockValidities;
|
||||||
IntegrityStorages[i - 1] = levelData;
|
IntegrityStorages[i - 1] = levelData;
|
||||||
}
|
}
|
||||||
|
|
197
src/LibHac/IO/HierarchicalRomFileTable.cs
Normal file
197
src/LibHac/IO/HierarchicalRomFileTable.cs
Normal file
|
@ -0,0 +1,197 @@
|
||||||
|
using System;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
|
namespace LibHac.IO
|
||||||
|
{
|
||||||
|
public class HierarchicalRomFileTable
|
||||||
|
{
|
||||||
|
private IStorage DirHashTableStorage { get; }
|
||||||
|
private IStorage DirEntryTableStorage { get; }
|
||||||
|
private IStorage FileHashTableStorage { get; }
|
||||||
|
private IStorage FileEntryTableStorage { get; }
|
||||||
|
|
||||||
|
private RomFsDictionary<FileRomEntry> FileTable { get; }
|
||||||
|
private RomFsDictionary<DirectoryRomEntry> DirectoryTable { get; }
|
||||||
|
|
||||||
|
public HierarchicalRomFileTable(IStorage dirHashTable, IStorage dirEntryTable, IStorage fileHashTable,
|
||||||
|
IStorage fileEntryTable)
|
||||||
|
{
|
||||||
|
DirHashTableStorage = dirHashTable;
|
||||||
|
DirEntryTableStorage = dirEntryTable;
|
||||||
|
FileHashTableStorage = fileHashTable;
|
||||||
|
FileEntryTableStorage = fileEntryTable;
|
||||||
|
|
||||||
|
FileTable = new RomFsDictionary<FileRomEntry>(FileHashTableStorage, FileEntryTableStorage);
|
||||||
|
DirectoryTable = new RomFsDictionary<DirectoryRomEntry>(DirHashTableStorage, DirEntryTableStorage);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool OpenFile(string path, out RomFileInfo fileInfo)
|
||||||
|
{
|
||||||
|
FindFileRecursive(path.AsSpan(), out RomEntryKey key);
|
||||||
|
|
||||||
|
if (FileTable.TryGetValue(ref key, out FileRomEntry entry, out int _))
|
||||||
|
{
|
||||||
|
fileInfo = entry.Info;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
fileInfo = default;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool OpenFile(int offset, out RomFileInfo fileInfo)
|
||||||
|
{
|
||||||
|
if (FileTable.TryGetValue(offset, out FileRomEntry entry))
|
||||||
|
{
|
||||||
|
fileInfo = entry.Info;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
fileInfo = default;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool OpenDirectory(string path, out FindPosition position)
|
||||||
|
{
|
||||||
|
FindDirectoryRecursive(path.AsSpan(), out RomEntryKey key);
|
||||||
|
|
||||||
|
if (DirectoryTable.TryGetValue(ref key, out DirectoryRomEntry entry, out int _))
|
||||||
|
{
|
||||||
|
position = entry.Pos;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
position = default;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool OpenDirectory(int offset, out FindPosition position)
|
||||||
|
{
|
||||||
|
if (DirectoryTable.TryGetValue(offset, out DirectoryRomEntry entry))
|
||||||
|
{
|
||||||
|
position = entry.Pos;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
position = default;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool FindNextFile(ref FindPosition position, out RomFileInfo info, out string name)
|
||||||
|
{
|
||||||
|
if (FileTable.TryGetValue(position.NextFile, out FileRomEntry entry, out name))
|
||||||
|
{
|
||||||
|
position.NextFile = entry.NextSibling;
|
||||||
|
info = entry.Info;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
info = default;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool FindNextDirectory(ref FindPosition position, out string name)
|
||||||
|
{
|
||||||
|
if (DirectoryTable.TryGetValue(position.NextDirectory, out DirectoryRomEntry entry, out name))
|
||||||
|
{
|
||||||
|
position.NextDirectory = entry.NextSibling;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void FindFileRecursive(ReadOnlySpan<char> path, out RomEntryKey key)
|
||||||
|
{
|
||||||
|
var parser = new PathParser(path);
|
||||||
|
FindParentDirectoryRecursive(ref parser, out DirectoryRomEntry _, out int parentOffset);
|
||||||
|
|
||||||
|
key = new RomEntryKey(parser.GetCurrent(), parentOffset);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void FindDirectoryRecursive(ReadOnlySpan<char> path, out RomEntryKey key)
|
||||||
|
{
|
||||||
|
var parser = new PathParser(path);
|
||||||
|
FindParentDirectoryRecursive(ref parser, out DirectoryRomEntry _, out int parentOffset);
|
||||||
|
|
||||||
|
ReadOnlySpan<char> name = parser.GetCurrent();
|
||||||
|
if (name.Length == 0) parentOffset = 0;
|
||||||
|
|
||||||
|
key = new RomEntryKey(name, parentOffset);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void FindParentDirectoryRecursive(ref PathParser parser, out DirectoryRomEntry parentEntry, out int parentOffset)
|
||||||
|
{
|
||||||
|
parentEntry = default;
|
||||||
|
parentOffset = default;
|
||||||
|
RomEntryKey key = default;
|
||||||
|
|
||||||
|
while (parser.TryGetNext(out key.Name) && !parser.IsFinished())
|
||||||
|
{
|
||||||
|
DirectoryTable.TryGetValue(ref key, out parentEntry, out parentOffset);
|
||||||
|
key.Parent = parentOffset;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal ref struct RomEntryKey
|
||||||
|
{
|
||||||
|
public ReadOnlySpan<char> Name;
|
||||||
|
public int Parent;
|
||||||
|
|
||||||
|
public RomEntryKey(ReadOnlySpan<char> name, int parent)
|
||||||
|
{
|
||||||
|
Name = name;
|
||||||
|
Parent = parent;
|
||||||
|
}
|
||||||
|
|
||||||
|
public uint GetRomHashCode()
|
||||||
|
{
|
||||||
|
uint hash = 123456789 ^ (uint)Parent;
|
||||||
|
|
||||||
|
foreach (char c in Name)
|
||||||
|
{
|
||||||
|
hash = c ^ ((hash << 27) | (hash >> 5));
|
||||||
|
}
|
||||||
|
|
||||||
|
return hash;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[StructLayout(LayoutKind.Sequential)]
|
||||||
|
public struct RomFsEntry<T> where T : unmanaged
|
||||||
|
{
|
||||||
|
public int Parent;
|
||||||
|
public T Value;
|
||||||
|
public int Next;
|
||||||
|
public int KeyLength;
|
||||||
|
}
|
||||||
|
|
||||||
|
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
||||||
|
public struct FileRomEntry
|
||||||
|
{
|
||||||
|
public int NextSibling;
|
||||||
|
public RomFileInfo Info;
|
||||||
|
}
|
||||||
|
|
||||||
|
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
||||||
|
public struct RomFileInfo
|
||||||
|
{
|
||||||
|
public long Offset;
|
||||||
|
public long Length;
|
||||||
|
}
|
||||||
|
|
||||||
|
[StructLayout(LayoutKind.Sequential)]
|
||||||
|
public struct DirectoryRomEntry
|
||||||
|
{
|
||||||
|
public int NextSibling;
|
||||||
|
public FindPosition Pos;
|
||||||
|
}
|
||||||
|
|
||||||
|
[StructLayout(LayoutKind.Sequential)]
|
||||||
|
public struct FindPosition
|
||||||
|
{
|
||||||
|
public int NextDirectory;
|
||||||
|
public int NextFile;
|
||||||
|
}
|
||||||
|
}
|
60
src/LibHac/IO/PathParser.cs
Normal file
60
src/LibHac/IO/PathParser.cs
Normal file
|
@ -0,0 +1,60 @@
|
||||||
|
using System;
|
||||||
|
using System.Diagnostics;
|
||||||
|
|
||||||
|
namespace LibHac.IO
|
||||||
|
{
|
||||||
|
public ref struct PathParser
|
||||||
|
{
|
||||||
|
private ReadOnlySpan<char> _path;
|
||||||
|
private int _offset;
|
||||||
|
private int _length;
|
||||||
|
private bool _finished;
|
||||||
|
|
||||||
|
public PathParser(ReadOnlySpan<char> path)
|
||||||
|
{
|
||||||
|
Debug.Assert(PathTools.IsNormalized(path));
|
||||||
|
|
||||||
|
if (path.Length < 1 || path[0] != '/')
|
||||||
|
{
|
||||||
|
throw new ArgumentException("Path must begin with a '/'");
|
||||||
|
}
|
||||||
|
|
||||||
|
_path = path;
|
||||||
|
_offset = 0;
|
||||||
|
_length = 0;
|
||||||
|
_finished = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool TryGetNext(out ReadOnlySpan<char> name)
|
||||||
|
{
|
||||||
|
bool success = MoveNext();
|
||||||
|
name = GetCurrent();
|
||||||
|
return success;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool MoveNext()
|
||||||
|
{
|
||||||
|
if (_finished) return false;
|
||||||
|
|
||||||
|
_offset = _offset + _length + 1;
|
||||||
|
int end = _offset;
|
||||||
|
|
||||||
|
while (end < _path.Length && _path[end] != '/')
|
||||||
|
{
|
||||||
|
end++;
|
||||||
|
}
|
||||||
|
|
||||||
|
_finished = end + 1 >= _path.Length;
|
||||||
|
_length = end - _offset;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ReadOnlySpan<char> GetCurrent()
|
||||||
|
{
|
||||||
|
return _path.Slice(_offset, _length);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IsFinished() => _finished;
|
||||||
|
}
|
||||||
|
}
|
|
@ -91,6 +91,35 @@ namespace LibHac.IO
|
||||||
return path.Substring(0, i);
|
return path.Substring(0, i);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static bool IsNormalized(ReadOnlySpan<char> path)
|
||||||
|
{
|
||||||
|
var state = NormalizeState.Initial;
|
||||||
|
|
||||||
|
foreach (char c in path)
|
||||||
|
{
|
||||||
|
switch (state)
|
||||||
|
{
|
||||||
|
case NormalizeState.Initial when c == '/': state = NormalizeState.Delimiter; break;
|
||||||
|
case NormalizeState.Initial: return false;
|
||||||
|
|
||||||
|
case NormalizeState.Normal when c == '/': state = NormalizeState.Delimiter; break;
|
||||||
|
|
||||||
|
case NormalizeState.Delimiter when c == '/': return false;
|
||||||
|
case NormalizeState.Delimiter when c == '.': state = NormalizeState.Dot; break;
|
||||||
|
case NormalizeState.Delimiter: state = NormalizeState.Normal; break;
|
||||||
|
|
||||||
|
case NormalizeState.Dot when c == '/': return false;
|
||||||
|
case NormalizeState.Dot when c == '.': state = NormalizeState.DoubleDot; break;
|
||||||
|
case NormalizeState.Dot: state = NormalizeState.Normal; break;
|
||||||
|
|
||||||
|
case NormalizeState.DoubleDot when c == '/': return false;
|
||||||
|
case NormalizeState.DoubleDot: state = NormalizeState.Normal; break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return state == NormalizeState.Normal || state == NormalizeState.Delimiter;
|
||||||
|
}
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
internal static bool IsDirectorySeparator(char c)
|
internal static bool IsDirectorySeparator(char c)
|
||||||
{
|
{
|
||||||
|
@ -111,5 +140,14 @@ namespace LibHac.IO
|
||||||
(index + 3 == path.Length || IsDirectorySeparator(path[index + 3])) &&
|
(index + 3 == path.Length || IsDirectorySeparator(path[index + 3])) &&
|
||||||
path[index + 1] == '.' && path[index + 2] == '.';
|
path[index + 1] == '.' && path[index + 2] == '.';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private enum NormalizeState
|
||||||
|
{
|
||||||
|
Initial,
|
||||||
|
Normal,
|
||||||
|
Delimiter,
|
||||||
|
Dot,
|
||||||
|
DoubleDot
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
128
src/LibHac/IO/RomFsDictionary.cs
Normal file
128
src/LibHac/IO/RomFsDictionary.cs
Normal file
|
@ -0,0 +1,128 @@
|
||||||
|
using System;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.IO;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace LibHac.IO
|
||||||
|
{
|
||||||
|
internal class RomFsDictionary<T> where T : unmanaged
|
||||||
|
{
|
||||||
|
private int HashBucketCount { get; }
|
||||||
|
private IStorage BucketStorage { get; }
|
||||||
|
private IStorage EntryStorage { get; }
|
||||||
|
|
||||||
|
// Hack around not being able to get the size of generic structures
|
||||||
|
private readonly int _sizeOfEntry = 12 + Marshal.SizeOf<T>();
|
||||||
|
|
||||||
|
public RomFsDictionary(IStorage bucketStorage, IStorage entryStorage)
|
||||||
|
{
|
||||||
|
BucketStorage = bucketStorage;
|
||||||
|
EntryStorage = entryStorage;
|
||||||
|
HashBucketCount = (int)(bucketStorage.Length / 4);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool TryGetValue(ref RomEntryKey key, out T value, out int offset)
|
||||||
|
{
|
||||||
|
int i = FindEntry(ref key);
|
||||||
|
offset = i;
|
||||||
|
|
||||||
|
if (i >= 0)
|
||||||
|
{
|
||||||
|
GetEntryInternal(i, out RomFsEntry<T> entry);
|
||||||
|
value = entry.Value;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
value = default;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool TryGetValue(int offset, out T value, out string entryName)
|
||||||
|
{
|
||||||
|
if (offset < 0 || offset + _sizeOfEntry >= EntryStorage.Length)
|
||||||
|
{
|
||||||
|
value = default;
|
||||||
|
entryName = default;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
GetEntryInternal(offset, out RomFsEntry<T> entry, out entryName);
|
||||||
|
value = entry.Value;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool TryGetValue(int offset, out T value)
|
||||||
|
{
|
||||||
|
if (offset < 0 || offset + _sizeOfEntry >= EntryStorage.Length)
|
||||||
|
{
|
||||||
|
value = default;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
GetEntryInternal(offset, out RomFsEntry<T> entry);
|
||||||
|
value = entry.Value;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private int FindEntry(ref RomEntryKey key)
|
||||||
|
{
|
||||||
|
uint hashCode = key.GetRomHashCode();
|
||||||
|
int i = GetBucket((int)(hashCode % HashBucketCount));
|
||||||
|
|
||||||
|
while (i != -1)
|
||||||
|
{
|
||||||
|
GetEntryInternal(i, out RomFsEntry<T> entry);
|
||||||
|
|
||||||
|
if (IsEqual(ref key, ref entry, i))
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
i = entry.Next;
|
||||||
|
}
|
||||||
|
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool IsEqual(ref RomEntryKey key, ref RomFsEntry<T> entry, int entryOffset)
|
||||||
|
{
|
||||||
|
if (key.Parent != entry.Parent) return false;
|
||||||
|
if (key.Name.Length != entry.KeyLength) return false;
|
||||||
|
|
||||||
|
GetEntryInternal(entryOffset, out RomFsEntry<T> _, out string name);
|
||||||
|
|
||||||
|
return key.Name.Equals(name.AsSpan(), StringComparison.Ordinal);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void GetEntryInternal(int offset, out RomFsEntry<T> outEntry)
|
||||||
|
{
|
||||||
|
Span<byte> b = stackalloc byte[_sizeOfEntry];
|
||||||
|
EntryStorage.Read(b, offset);
|
||||||
|
outEntry = MemoryMarshal.Read<RomFsEntry<T>>(b);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void GetEntryInternal(int offset, out RomFsEntry<T> outEntry, out string entryName)
|
||||||
|
{
|
||||||
|
GetEntryInternal(offset, out outEntry);
|
||||||
|
|
||||||
|
if (outEntry.KeyLength > 0x300)
|
||||||
|
{
|
||||||
|
throw new InvalidDataException("Rom entry name is too long.");
|
||||||
|
}
|
||||||
|
|
||||||
|
var buf = new byte[outEntry.KeyLength];
|
||||||
|
EntryStorage.Read(buf, offset + _sizeOfEntry);
|
||||||
|
entryName = Encoding.ASCII.GetString(buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
private int GetBucket(int index)
|
||||||
|
{
|
||||||
|
Debug.Assert(index < HashBucketCount);
|
||||||
|
|
||||||
|
Span<byte> buf = stackalloc byte[4];
|
||||||
|
BucketStorage.Read(buf, index * 4);
|
||||||
|
return MemoryMarshal.Read<int>(buf);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,52 +1,43 @@
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
|
||||||
|
|
||||||
namespace LibHac.IO
|
namespace LibHac.IO
|
||||||
{
|
{
|
||||||
public class RomFsDirectory : IDirectory
|
public class RomFsDirectory : IDirectory
|
||||||
{
|
{
|
||||||
public IFileSystem ParentFileSystem { get; }
|
IFileSystem IDirectory.ParentFileSystem => ParentFileSystem;
|
||||||
|
public RomFsFileSystem ParentFileSystem { get; }
|
||||||
public string FullPath { get; }
|
public string FullPath { get; }
|
||||||
|
|
||||||
private RomfsDir Directory { get; }
|
|
||||||
public OpenDirectoryMode Mode { get; }
|
public OpenDirectoryMode Mode { get; }
|
||||||
|
|
||||||
public RomFsDirectory(RomFsFileSystem fs, string path, OpenDirectoryMode mode)
|
private FindPosition InitialPosition { get; }
|
||||||
{
|
|
||||||
path = PathTools.Normalize(path);
|
|
||||||
|
|
||||||
if (!fs.DirectoryDict.TryGetValue(path, out RomfsDir dir))
|
public RomFsDirectory(RomFsFileSystem fs, string path, FindPosition position, OpenDirectoryMode mode)
|
||||||
{
|
{
|
||||||
throw new DirectoryNotFoundException(path);
|
|
||||||
}
|
|
||||||
|
|
||||||
ParentFileSystem = fs;
|
ParentFileSystem = fs;
|
||||||
Directory = dir;
|
InitialPosition = position;
|
||||||
FullPath = path;
|
FullPath = path;
|
||||||
Mode = mode;
|
Mode = mode;
|
||||||
}
|
}
|
||||||
|
|
||||||
public IEnumerable<DirectoryEntry> Read()
|
public IEnumerable<DirectoryEntry> Read()
|
||||||
{
|
{
|
||||||
|
FindPosition position = InitialPosition;
|
||||||
|
HierarchicalRomFileTable tab = ParentFileSystem.FileTable;
|
||||||
|
|
||||||
if (Mode.HasFlag(OpenDirectoryMode.Directories))
|
if (Mode.HasFlag(OpenDirectoryMode.Directories))
|
||||||
{
|
{
|
||||||
RomfsDir dirEntry = Directory.FirstChild;
|
while (tab.FindNextDirectory(ref position, out string name))
|
||||||
|
|
||||||
while (dirEntry != null)
|
|
||||||
{
|
{
|
||||||
yield return new DirectoryEntry(dirEntry.Name, FullPath + '/' + dirEntry.Name, DirectoryEntryType.Directory, 0);
|
yield return new DirectoryEntry(name, FullPath + '/' + name, DirectoryEntryType.Directory, 0);
|
||||||
dirEntry = dirEntry.NextSibling;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Mode.HasFlag(OpenDirectoryMode.Files))
|
if (Mode.HasFlag(OpenDirectoryMode.Files))
|
||||||
{
|
{
|
||||||
RomfsFile fileEntry = Directory.FirstFile;
|
while (tab.FindNextFile(ref position, out RomFileInfo info, out string name))
|
||||||
|
|
||||||
while (fileEntry != null)
|
|
||||||
{
|
{
|
||||||
yield return new DirectoryEntry(fileEntry.Name, FullPath + '/' + fileEntry.Name, DirectoryEntryType.File, fileEntry.DataLength);
|
yield return new DirectoryEntry(name, FullPath + '/' + name, DirectoryEntryType.File, info.Length);
|
||||||
fileEntry = fileEntry.NextSibling;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -55,25 +46,22 @@ namespace LibHac.IO
|
||||||
{
|
{
|
||||||
int count = 0;
|
int count = 0;
|
||||||
|
|
||||||
|
FindPosition position = InitialPosition;
|
||||||
|
HierarchicalRomFileTable tab = ParentFileSystem.FileTable;
|
||||||
|
|
||||||
if (Mode.HasFlag(OpenDirectoryMode.Directories))
|
if (Mode.HasFlag(OpenDirectoryMode.Directories))
|
||||||
{
|
{
|
||||||
RomfsDir dirEntry = Directory.FirstChild;
|
while (tab.FindNextDirectory(ref position, out string _))
|
||||||
|
|
||||||
while (dirEntry != null)
|
|
||||||
{
|
{
|
||||||
count++;
|
count++;
|
||||||
dirEntry = dirEntry.NextSibling;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Mode.HasFlag(OpenDirectoryMode.Files))
|
if (Mode.HasFlag(OpenDirectoryMode.Files))
|
||||||
{
|
{
|
||||||
RomfsFile fileEntry = Directory.FirstFile;
|
while (tab.FindNextFile(ref position, out RomFileInfo _, out string _))
|
||||||
|
|
||||||
while (fileEntry != null)
|
|
||||||
{
|
{
|
||||||
count++;
|
count++;
|
||||||
fileEntry = fileEntry.NextSibling;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,97 +1,35 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
|
|
||||||
namespace LibHac.IO
|
namespace LibHac.IO
|
||||||
{
|
{
|
||||||
public class RomFsFileSystem : IFileSystem
|
public class RomFsFileSystem : IFileSystem
|
||||||
{
|
{
|
||||||
public RomfsHeader Header { get; }
|
public RomfsHeader Header { get; }
|
||||||
public List<RomfsDir> Directories { get; } = new List<RomfsDir>();
|
|
||||||
public List<RomfsFile> Files { get; } = new List<RomfsFile>();
|
|
||||||
public RomfsDir RootDir { get; }
|
|
||||||
|
|
||||||
public Dictionary<string, RomfsFile> FileDict { get; }
|
public HierarchicalRomFileTable FileTable { get; }
|
||||||
public Dictionary<string, RomfsDir> DirectoryDict { get; }
|
|
||||||
private IStorage BaseStorage { get; }
|
private IStorage BaseStorage { get; }
|
||||||
|
|
||||||
// todo Don't parse entire table when opening
|
// todo Don't parse entire table when opening
|
||||||
public RomFsFileSystem(IStorage storage)
|
public RomFsFileSystem(IStorage storage)
|
||||||
{
|
{
|
||||||
BaseStorage = storage;
|
BaseStorage = storage;
|
||||||
|
Header = new RomfsHeader(storage.AsFile(OpenMode.Read));
|
||||||
|
|
||||||
byte[] dirMetaTable;
|
IStorage dirHashTable = storage.Slice(Header.DirHashTableOffset, Header.DirHashTableSize);
|
||||||
byte[] fileMetaTable;
|
IStorage dirEntryTable = storage.Slice(Header.DirMetaTableOffset, Header.DirMetaTableSize);
|
||||||
|
IStorage fileHashTable = storage.Slice(Header.FileHashTableOffset, Header.FileHashTableSize);
|
||||||
|
IStorage fileEntryTable = storage.Slice(Header.FileMetaTableOffset, Header.FileMetaTableSize);
|
||||||
|
|
||||||
using (var reader = new BinaryReader(BaseStorage.AsStream(), Encoding.Default, true))
|
FileTable = new HierarchicalRomFileTable(dirHashTable, dirEntryTable, fileHashTable, fileEntryTable);
|
||||||
{
|
|
||||||
Header = new RomfsHeader(reader);
|
|
||||||
reader.BaseStream.Position = Header.DirMetaTableOffset;
|
|
||||||
dirMetaTable = reader.ReadBytes((int)Header.DirMetaTableSize);
|
|
||||||
reader.BaseStream.Position = Header.FileMetaTableOffset;
|
|
||||||
fileMetaTable = reader.ReadBytes((int)Header.FileMetaTableSize);
|
|
||||||
}
|
|
||||||
|
|
||||||
using (var reader = new BinaryReader(new MemoryStream(dirMetaTable)))
|
|
||||||
{
|
|
||||||
int position = 0;
|
|
||||||
while (position + 20 < Header.DirMetaTableSize)
|
|
||||||
{
|
|
||||||
var dir = new RomfsDir(reader) { Offset = position };
|
|
||||||
Directories.Add(dir);
|
|
||||||
if (dir.ParentDirOffset == position) RootDir = dir;
|
|
||||||
position = (int)reader.BaseStream.Position;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
using (var reader = new BinaryReader(new MemoryStream(fileMetaTable)))
|
|
||||||
{
|
|
||||||
int position = 0;
|
|
||||||
while (position + 20 < Header.FileMetaTableSize)
|
|
||||||
{
|
|
||||||
var file = new RomfsFile(reader) { Offset = position };
|
|
||||||
Files.Add(file);
|
|
||||||
position = (int)reader.BaseStream.Position;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
SetReferences();
|
|
||||||
RomfsEntry.ResolveFilenames(Files);
|
|
||||||
RomfsEntry.ResolveFilenames(Directories);
|
|
||||||
FileDict = Files.ToDictionary(x => x.FullPath, x => x);
|
|
||||||
DirectoryDict = Directories.ToDictionary(x => x.FullPath, x => x);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void SetReferences()
|
|
||||||
{
|
|
||||||
Dictionary<int, RomfsDir> dirDict = Directories.ToDictionary(x => x.Offset, x => x);
|
|
||||||
Dictionary<int, RomfsFile> fileDict = Files.ToDictionary(x => x.Offset, x => x);
|
|
||||||
|
|
||||||
foreach (RomfsDir dir in Directories)
|
|
||||||
{
|
|
||||||
if (dir.ParentDirOffset >= 0 && dir.ParentDirOffset != dir.Offset) dir.ParentDir = dirDict[dir.ParentDirOffset];
|
|
||||||
if (dir.NextSiblingOffset >= 0) dir.NextSibling = dirDict[dir.NextSiblingOffset];
|
|
||||||
if (dir.FirstChildOffset >= 0) dir.FirstChild = dirDict[dir.FirstChildOffset];
|
|
||||||
if (dir.FirstFileOffset >= 0) dir.FirstFile = fileDict[dir.FirstFileOffset];
|
|
||||||
if (dir.NextDirHashOffset >= 0) dir.NextDirHash = dirDict[dir.NextDirHashOffset];
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (RomfsFile file in Files)
|
|
||||||
{
|
|
||||||
if (file.ParentDirOffset >= 0) file.ParentDir = dirDict[file.ParentDirOffset];
|
|
||||||
if (file.NextSiblingOffset >= 0) file.NextSibling = fileDict[file.NextSiblingOffset];
|
|
||||||
if (file.NextFileHashOffset >= 0) file.NextFileHash = fileDict[file.NextFileHashOffset];
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public DirectoryEntryType GetEntryType(string path)
|
public DirectoryEntryType GetEntryType(string path)
|
||||||
{
|
{
|
||||||
path = PathTools.Normalize(path);
|
path = PathTools.Normalize(path);
|
||||||
|
|
||||||
if (FileDict.ContainsKey(path)) return DirectoryEntryType.File;
|
if (FileExists(path)) return DirectoryEntryType.File;
|
||||||
if (DirectoryDict.ContainsKey(path)) return DirectoryEntryType.Directory;
|
if (DirectoryExists(path)) return DirectoryEntryType.Directory;
|
||||||
|
|
||||||
throw new FileNotFoundException(path);
|
throw new FileNotFoundException(path);
|
||||||
}
|
}
|
||||||
|
@ -103,14 +41,21 @@ namespace LibHac.IO
|
||||||
|
|
||||||
public IDirectory OpenDirectory(string path, OpenDirectoryMode mode)
|
public IDirectory OpenDirectory(string path, OpenDirectoryMode mode)
|
||||||
{
|
{
|
||||||
return new RomFsDirectory(this, path, mode);
|
path = PathTools.Normalize(path);
|
||||||
|
|
||||||
|
if (!FileTable.OpenDirectory(path, out FindPosition position))
|
||||||
|
{
|
||||||
|
throw new DirectoryNotFoundException();
|
||||||
|
}
|
||||||
|
|
||||||
|
return new RomFsDirectory(this, path, position, mode);
|
||||||
}
|
}
|
||||||
|
|
||||||
public IFile OpenFile(string path, OpenMode mode)
|
public IFile OpenFile(string path, OpenMode mode)
|
||||||
{
|
{
|
||||||
path = PathTools.Normalize(path);
|
path = PathTools.Normalize(path);
|
||||||
|
|
||||||
if (!FileDict.TryGetValue(path, out RomfsFile file))
|
if (!FileTable.OpenFile(path, out RomFileInfo info))
|
||||||
{
|
{
|
||||||
throw new FileNotFoundException();
|
throw new FileNotFoundException();
|
||||||
}
|
}
|
||||||
|
@ -120,26 +65,21 @@ namespace LibHac.IO
|
||||||
throw new ArgumentOutOfRangeException(nameof(mode), "RomFs files must be opened read-only.");
|
throw new ArgumentOutOfRangeException(nameof(mode), "RomFs files must be opened read-only.");
|
||||||
}
|
}
|
||||||
|
|
||||||
return OpenFile(file);
|
return new RomFsFile(BaseStorage, Header.DataOffset + info.Offset, info.Length);
|
||||||
}
|
|
||||||
|
|
||||||
public IFile OpenFile(RomfsFile file)
|
|
||||||
{
|
|
||||||
return new RomFsFile(BaseStorage, Header.DataOffset + file.DataOffset, file.DataLength);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool DirectoryExists(string path)
|
public bool DirectoryExists(string path)
|
||||||
{
|
{
|
||||||
path = PathTools.Normalize(path);
|
path = PathTools.Normalize(path);
|
||||||
|
|
||||||
return DirectoryDict.ContainsKey(path);
|
return FileTable.OpenDirectory(path, out FindPosition _);
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool FileExists(string path)
|
public bool FileExists(string path)
|
||||||
{
|
{
|
||||||
path = PathTools.Normalize(path);
|
path = PathTools.Normalize(path);
|
||||||
|
|
||||||
return FileDict.ContainsKey(path);
|
return FileTable.OpenFile(path, out RomFileInfo _);
|
||||||
}
|
}
|
||||||
|
|
||||||
public IStorage GetBaseStorage()
|
public IStorage GetBaseStorage()
|
||||||
|
@ -168,8 +108,10 @@ namespace LibHac.IO
|
||||||
public long FileMetaTableSize { get; }
|
public long FileMetaTableSize { get; }
|
||||||
public long DataOffset { get; }
|
public long DataOffset { get; }
|
||||||
|
|
||||||
public RomfsHeader(BinaryReader reader)
|
public RomfsHeader(IFile file)
|
||||||
{
|
{
|
||||||
|
var reader = new FileReader(file);
|
||||||
|
|
||||||
HeaderSize = reader.ReadInt64();
|
HeaderSize = reader.ReadInt64();
|
||||||
DirHashTableOffset = reader.ReadInt64();
|
DirHashTableOffset = reader.ReadInt64();
|
||||||
DirHashTableSize = reader.ReadInt64();
|
DirHashTableSize = reader.ReadInt64();
|
||||||
|
|
|
@ -1,98 +0,0 @@
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Diagnostics;
|
|
||||||
using System.IO;
|
|
||||||
using System.Text;
|
|
||||||
|
|
||||||
namespace LibHac.IO
|
|
||||||
{
|
|
||||||
public abstract class RomfsEntry
|
|
||||||
{
|
|
||||||
public int Offset { get; set; }
|
|
||||||
public int ParentDirOffset { get; protected set; }
|
|
||||||
public int NameLength { get; protected set; }
|
|
||||||
public string Name { get; protected set; }
|
|
||||||
|
|
||||||
public RomfsDir ParentDir { get; internal set; }
|
|
||||||
public string FullPath { get; private set; }
|
|
||||||
|
|
||||||
internal static void ResolveFilenames(IEnumerable<RomfsEntry> entries)
|
|
||||||
{
|
|
||||||
var list = new List<string>();
|
|
||||||
var sb = new StringBuilder();
|
|
||||||
const string delimiter = "/";
|
|
||||||
foreach (RomfsEntry file in entries)
|
|
||||||
{
|
|
||||||
list.Add(file.Name);
|
|
||||||
RomfsDir dir = file.ParentDir;
|
|
||||||
while (dir != null)
|
|
||||||
{
|
|
||||||
list.Add(delimiter);
|
|
||||||
list.Add(dir.Name);
|
|
||||||
dir = dir.ParentDir;
|
|
||||||
}
|
|
||||||
|
|
||||||
//todo
|
|
||||||
if (list.Count == 1) list.Add("/");
|
|
||||||
|
|
||||||
for (int i = list.Count - 1; i >= 0; i--)
|
|
||||||
{
|
|
||||||
sb.Append(list[i]);
|
|
||||||
}
|
|
||||||
|
|
||||||
file.FullPath = sb.ToString();
|
|
||||||
list.Clear();
|
|
||||||
sb.Clear();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[DebuggerDisplay("{" + nameof(Name) + "}")]
|
|
||||||
public class RomfsDir : RomfsEntry
|
|
||||||
{
|
|
||||||
public int NextSiblingOffset { get; }
|
|
||||||
public int FirstChildOffset { get; }
|
|
||||||
public int FirstFileOffset { get; }
|
|
||||||
public int NextDirHashOffset { get; }
|
|
||||||
|
|
||||||
public RomfsDir NextSibling { get; internal set; }
|
|
||||||
public RomfsDir FirstChild { get; internal set; }
|
|
||||||
public RomfsFile FirstFile { get; internal set; }
|
|
||||||
public RomfsDir NextDirHash { get; internal set; }
|
|
||||||
|
|
||||||
public RomfsDir(BinaryReader reader)
|
|
||||||
{
|
|
||||||
ParentDirOffset = reader.ReadInt32();
|
|
||||||
NextSiblingOffset = reader.ReadInt32();
|
|
||||||
FirstChildOffset = reader.ReadInt32();
|
|
||||||
FirstFileOffset = reader.ReadInt32();
|
|
||||||
NextDirHashOffset = reader.ReadInt32();
|
|
||||||
NameLength = reader.ReadInt32();
|
|
||||||
Name = reader.ReadUtf8(NameLength);
|
|
||||||
reader.BaseStream.Position = Util.GetNextMultiple(reader.BaseStream.Position, 4);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[DebuggerDisplay("{" + nameof(Name) + "}")]
|
|
||||||
public class RomfsFile : RomfsEntry
|
|
||||||
{
|
|
||||||
public int NextSiblingOffset { get; }
|
|
||||||
public long DataOffset { get; }
|
|
||||||
public long DataLength { get; }
|
|
||||||
public int NextFileHashOffset { get; }
|
|
||||||
|
|
||||||
public RomfsFile NextSibling { get; internal set; }
|
|
||||||
public RomfsFile NextFileHash { get; internal set; }
|
|
||||||
|
|
||||||
public RomfsFile(BinaryReader reader)
|
|
||||||
{
|
|
||||||
ParentDirOffset = reader.ReadInt32();
|
|
||||||
NextSiblingOffset = reader.ReadInt32();
|
|
||||||
DataOffset = reader.ReadInt64();
|
|
||||||
DataLength = reader.ReadInt64();
|
|
||||||
NextFileHashOffset = reader.ReadInt32();
|
|
||||||
NameLength = reader.ReadInt32();
|
|
||||||
Name = reader.ReadUtf8(NameLength);
|
|
||||||
reader.BaseStream.Position = Util.GetNextMultiple(reader.BaseStream.Position, 4);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -102,6 +102,15 @@ namespace LibHac.IO
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static byte[] ToArray(this IStorage storage)
|
||||||
|
{
|
||||||
|
if (storage == null) return new byte[0];
|
||||||
|
|
||||||
|
var arr = new byte[storage.Length];
|
||||||
|
storage.CopyTo(new MemoryStorage(arr));
|
||||||
|
return arr;
|
||||||
|
}
|
||||||
|
|
||||||
public static void CopyToStream(this IStorage input, Stream output, long length, IProgressReport progress = null)
|
public static void CopyToStream(this IStorage input, Stream output, long length, IProgressReport progress = null)
|
||||||
{
|
{
|
||||||
const int bufferSize = 0x8000;
|
const int bufferSize = 0x8000;
|
||||||
|
|
|
@ -46,9 +46,9 @@ namespace hactoolnet
|
||||||
{
|
{
|
||||||
var romfs = new RomFsFileSystem(nca.OpenSection(1, false, ctx.Options.IntegrityLevel, true));
|
var romfs = new RomFsFileSystem(nca.OpenSection(1, false, ctx.Options.IntegrityLevel, true));
|
||||||
|
|
||||||
foreach (RomfsFile romfsFile in romfs.Files)
|
foreach (DirectoryEntry entry in romfs.EnumerateEntries())
|
||||||
{
|
{
|
||||||
ctx.Logger.LogMessage(romfsFile.FullPath);
|
ctx.Logger.LogMessage(entry.FullPath);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -18,9 +18,9 @@ namespace hactoolnet
|
||||||
{
|
{
|
||||||
if (ctx.Options.ListRomFs)
|
if (ctx.Options.ListRomFs)
|
||||||
{
|
{
|
||||||
foreach (RomfsFile romfsFile in romfs.Files)
|
foreach (DirectoryEntry entry in romfs.EnumerateEntries())
|
||||||
{
|
{
|
||||||
ctx.Logger.LogMessage(romfsFile.FullPath);
|
ctx.Logger.LogMessage(entry.FullPath);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue