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);
|
||||
|
||||
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;
|
||||
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);
|
||||
}
|
||||
|
||||
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)]
|
||||
internal static bool IsDirectorySeparator(char c)
|
||||
{
|
||||
|
@ -111,5 +140,14 @@ namespace LibHac.IO
|
|||
(index + 3 == path.Length || IsDirectorySeparator(path[index + 3])) &&
|
||||
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.IO;
|
||||
|
||||
namespace LibHac.IO
|
||||
{
|
||||
public class RomFsDirectory : IDirectory
|
||||
{
|
||||
public IFileSystem ParentFileSystem { get; }
|
||||
IFileSystem IDirectory.ParentFileSystem => ParentFileSystem;
|
||||
public RomFsFileSystem ParentFileSystem { get; }
|
||||
public string FullPath { get; }
|
||||
|
||||
private RomfsDir Directory { get; }
|
||||
public OpenDirectoryMode Mode { get; }
|
||||
|
||||
public RomFsDirectory(RomFsFileSystem fs, string path, OpenDirectoryMode mode)
|
||||
{
|
||||
path = PathTools.Normalize(path);
|
||||
private FindPosition InitialPosition { get; }
|
||||
|
||||
if (!fs.DirectoryDict.TryGetValue(path, out RomfsDir dir))
|
||||
public RomFsDirectory(RomFsFileSystem fs, string path, FindPosition position, OpenDirectoryMode mode)
|
||||
{
|
||||
throw new DirectoryNotFoundException(path);
|
||||
}
|
||||
|
||||
ParentFileSystem = fs;
|
||||
Directory = dir;
|
||||
InitialPosition = position;
|
||||
FullPath = path;
|
||||
Mode = mode;
|
||||
}
|
||||
|
||||
public IEnumerable<DirectoryEntry> Read()
|
||||
{
|
||||
FindPosition position = InitialPosition;
|
||||
HierarchicalRomFileTable tab = ParentFileSystem.FileTable;
|
||||
|
||||
if (Mode.HasFlag(OpenDirectoryMode.Directories))
|
||||
{
|
||||
RomfsDir dirEntry = Directory.FirstChild;
|
||||
|
||||
while (dirEntry != null)
|
||||
while (tab.FindNextDirectory(ref position, out string name))
|
||||
{
|
||||
yield return new DirectoryEntry(dirEntry.Name, FullPath + '/' + dirEntry.Name, DirectoryEntryType.Directory, 0);
|
||||
dirEntry = dirEntry.NextSibling;
|
||||
yield return new DirectoryEntry(name, FullPath + '/' + name, DirectoryEntryType.Directory, 0);
|
||||
}
|
||||
}
|
||||
|
||||
if (Mode.HasFlag(OpenDirectoryMode.Files))
|
||||
{
|
||||
RomfsFile fileEntry = Directory.FirstFile;
|
||||
|
||||
while (fileEntry != null)
|
||||
while (tab.FindNextFile(ref position, out RomFileInfo info, out string name))
|
||||
{
|
||||
yield return new DirectoryEntry(fileEntry.Name, FullPath + '/' + fileEntry.Name, DirectoryEntryType.File, fileEntry.DataLength);
|
||||
fileEntry = fileEntry.NextSibling;
|
||||
yield return new DirectoryEntry(name, FullPath + '/' + name, DirectoryEntryType.File, info.Length);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -55,25 +46,22 @@ namespace LibHac.IO
|
|||
{
|
||||
int count = 0;
|
||||
|
||||
FindPosition position = InitialPosition;
|
||||
HierarchicalRomFileTable tab = ParentFileSystem.FileTable;
|
||||
|
||||
if (Mode.HasFlag(OpenDirectoryMode.Directories))
|
||||
{
|
||||
RomfsDir dirEntry = Directory.FirstChild;
|
||||
|
||||
while (dirEntry != null)
|
||||
while (tab.FindNextDirectory(ref position, out string _))
|
||||
{
|
||||
count++;
|
||||
dirEntry = dirEntry.NextSibling;
|
||||
}
|
||||
}
|
||||
|
||||
if (Mode.HasFlag(OpenDirectoryMode.Files))
|
||||
{
|
||||
RomfsFile fileEntry = Directory.FirstFile;
|
||||
|
||||
while (fileEntry != null)
|
||||
while (tab.FindNextFile(ref position, out RomFileInfo _, out string _))
|
||||
{
|
||||
count++;
|
||||
fileEntry = fileEntry.NextSibling;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,97 +1,35 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace LibHac.IO
|
||||
{
|
||||
public class RomFsFileSystem : IFileSystem
|
||||
{
|
||||
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 Dictionary<string, RomfsDir> DirectoryDict { get; }
|
||||
public HierarchicalRomFileTable FileTable { get; }
|
||||
private IStorage BaseStorage { get; }
|
||||
|
||||
// todo Don't parse entire table when opening
|
||||
public RomFsFileSystem(IStorage storage)
|
||||
{
|
||||
BaseStorage = storage;
|
||||
Header = new RomfsHeader(storage.AsFile(OpenMode.Read));
|
||||
|
||||
byte[] dirMetaTable;
|
||||
byte[] fileMetaTable;
|
||||
IStorage dirHashTable = storage.Slice(Header.DirHashTableOffset, Header.DirHashTableSize);
|
||||
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))
|
||||
{
|
||||
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];
|
||||
}
|
||||
FileTable = new HierarchicalRomFileTable(dirHashTable, dirEntryTable, fileHashTable, fileEntryTable);
|
||||
}
|
||||
|
||||
public DirectoryEntryType GetEntryType(string path)
|
||||
{
|
||||
path = PathTools.Normalize(path);
|
||||
|
||||
if (FileDict.ContainsKey(path)) return DirectoryEntryType.File;
|
||||
if (DirectoryDict.ContainsKey(path)) return DirectoryEntryType.Directory;
|
||||
if (FileExists(path)) return DirectoryEntryType.File;
|
||||
if (DirectoryExists(path)) return DirectoryEntryType.Directory;
|
||||
|
||||
throw new FileNotFoundException(path);
|
||||
}
|
||||
|
@ -103,14 +41,21 @@ namespace LibHac.IO
|
|||
|
||||
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)
|
||||
{
|
||||
path = PathTools.Normalize(path);
|
||||
|
||||
if (!FileDict.TryGetValue(path, out RomfsFile file))
|
||||
if (!FileTable.OpenFile(path, out RomFileInfo info))
|
||||
{
|
||||
throw new FileNotFoundException();
|
||||
}
|
||||
|
@ -120,26 +65,21 @@ namespace LibHac.IO
|
|||
throw new ArgumentOutOfRangeException(nameof(mode), "RomFs files must be opened read-only.");
|
||||
}
|
||||
|
||||
return OpenFile(file);
|
||||
}
|
||||
|
||||
public IFile OpenFile(RomfsFile file)
|
||||
{
|
||||
return new RomFsFile(BaseStorage, Header.DataOffset + file.DataOffset, file.DataLength);
|
||||
return new RomFsFile(BaseStorage, Header.DataOffset + info.Offset, info.Length);
|
||||
}
|
||||
|
||||
public bool DirectoryExists(string path)
|
||||
{
|
||||
path = PathTools.Normalize(path);
|
||||
|
||||
return DirectoryDict.ContainsKey(path);
|
||||
return FileTable.OpenDirectory(path, out FindPosition _);
|
||||
}
|
||||
|
||||
public bool FileExists(string path)
|
||||
{
|
||||
path = PathTools.Normalize(path);
|
||||
|
||||
return FileDict.ContainsKey(path);
|
||||
return FileTable.OpenFile(path, out RomFileInfo _);
|
||||
}
|
||||
|
||||
public IStorage GetBaseStorage()
|
||||
|
@ -168,8 +108,10 @@ namespace LibHac.IO
|
|||
public long FileMetaTableSize { get; }
|
||||
public long DataOffset { get; }
|
||||
|
||||
public RomfsHeader(BinaryReader reader)
|
||||
public RomfsHeader(IFile file)
|
||||
{
|
||||
var reader = new FileReader(file);
|
||||
|
||||
HeaderSize = reader.ReadInt64();
|
||||
DirHashTableOffset = 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)
|
||||
{
|
||||
const int bufferSize = 0x8000;
|
||||
|
|
|
@ -46,9 +46,9 @@ namespace hactoolnet
|
|||
{
|
||||
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)
|
||||
{
|
||||
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