From f0c09d7712530bdce9717d363a7af569c7f61523 Mon Sep 17 00:00:00 2001 From: Alex Barney Date: Mon, 11 Mar 2019 23:45:05 -0500 Subject: [PATCH] Add direct read support for the savedata file table --- .../IO/RomFs/HierarchicalRomFileTable.cs | 47 +---- .../IO/Save/HierarchicalSaveFileTable.cs | 127 +++++++++++++ src/LibHac/IO/Save/SaveDataDirectory.cs | 42 ++--- src/LibHac/IO/Save/SaveDataFileSystemCore.cs | 154 ++------------- src/LibHac/IO/Save/SaveFsEntries.cs | 36 ++++ src/LibHac/IO/Save/SaveFsList.cs | 176 ++++++++++++++++++ src/LibHac/Util.cs | 70 ++++++- src/hactoolnet/ProcessSave.cs | 7 +- 8 files changed, 457 insertions(+), 202 deletions(-) create mode 100644 src/LibHac/IO/Save/HierarchicalSaveFileTable.cs create mode 100644 src/LibHac/IO/Save/SaveFsEntries.cs create mode 100644 src/LibHac/IO/Save/SaveFsList.cs diff --git a/src/LibHac/IO/RomFs/HierarchicalRomFileTable.cs b/src/LibHac/IO/RomFs/HierarchicalRomFileTable.cs index f163d230..64f2541b 100644 --- a/src/LibHac/IO/RomFs/HierarchicalRomFileTable.cs +++ b/src/LibHac/IO/RomFs/HierarchicalRomFileTable.cs @@ -1,7 +1,5 @@ using System; -using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -using System.Text; namespace LibHac.IO.RomFs { @@ -82,7 +80,7 @@ namespace LibHac.IO.RomFs public bool TryOpenFile(string path, out RomFileInfo fileInfo) { - FindFileRecursive(GetUtf8Bytes(path), out RomEntryKey key); + FindPathRecursive(Util.GetUtf8Bytes(path), out RomEntryKey key); if (FileTable.TryGetValue(ref key, out RomKeyValuePair keyValuePair)) { @@ -115,7 +113,7 @@ namespace LibHac.IO.RomFs /// otherwise, . public bool TryOpenDirectory(string path, out FindPosition position) { - FindDirectoryRecursive(GetUtf8Bytes(path), out RomEntryKey key); + FindPathRecursive(Util.GetUtf8Bytes(path), out RomEntryKey key); if (DirectoryTable.TryGetValue(ref key, out RomKeyValuePair keyValuePair)) { @@ -168,7 +166,7 @@ namespace LibHac.IO.RomFs position.NextFile = entry.NextSibling; info = entry.Info; - name = GetUtf8String(nameBytes); + name = Util.GetUtf8String(nameBytes); return true; } @@ -191,7 +189,8 @@ namespace LibHac.IO.RomFs ref DirectoryRomEntry entry = ref DirectoryTable.GetValueReference(position.NextDirectory, out Span nameBytes); position.NextDirectory = entry.NextSibling; - name = GetUtf8String(nameBytes); + + name = Util.GetUtf8String(nameBytes); return true; } @@ -205,7 +204,7 @@ namespace LibHac.IO.RomFs public void AddFile(string path, ref RomFileInfo fileInfo) { path = PathTools.Normalize(path); - ReadOnlySpan pathBytes = GetUtf8Bytes(path); + ReadOnlySpan pathBytes = Util.GetUtf8Bytes(path); if(path == "/") throw new ArgumentException("Path cannot be empty"); @@ -221,7 +220,7 @@ namespace LibHac.IO.RomFs { path = PathTools.Normalize(path); - CreateDirectoryRecursive(GetUtf8Bytes(path)); + CreateDirectoryRecursive(Util.GetUtf8Bytes(path)); } /// @@ -237,21 +236,6 @@ namespace LibHac.IO.RomFs FileTable.TrimExcess(); } - private static ReadOnlySpan GetUtf8Bytes(string value) - { - return Encoding.UTF8.GetBytes(value).AsSpan(); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static string GetUtf8String(ReadOnlySpan value) - { -#if NETFRAMEWORK - return Encoding.UTF8.GetString(value.ToArray()); -#else - return Encoding.UTF8.GetString(value); -#endif - } - private void CreateRootDirectory() { var key = new RomEntryKey(ReadOnlySpan.Empty, 0); @@ -371,26 +355,15 @@ namespace LibHac.IO.RomFs } } - private void FindFileRecursive(ReadOnlySpan path, out RomEntryKey key) + private void FindPathRecursive(ReadOnlySpan path, out RomEntryKey key) { var parser = new PathParser(path); key = default; - while (parser.TryGetNext(out key.Name) && !parser.IsFinished()) + do { key.Parent = DirectoryTable.GetOffsetFromKey(ref key); - } - } - - private void FindDirectoryRecursive(ReadOnlySpan path, out RomEntryKey key) - { - var parser = new PathParser(path); - key = default; - - while (parser.TryGetNext(out key.Name) && !parser.IsFinished()) - { - key.Parent = DirectoryTable.GetOffsetFromKey(ref key); - } + } while (parser.TryGetNext(out key.Name) && !parser.IsFinished()); } [StructLayout(LayoutKind.Sequential, Pack = 4)] diff --git a/src/LibHac/IO/Save/HierarchicalSaveFileTable.cs b/src/LibHac/IO/Save/HierarchicalSaveFileTable.cs new file mode 100644 index 00000000..fceaa472 --- /dev/null +++ b/src/LibHac/IO/Save/HierarchicalSaveFileTable.cs @@ -0,0 +1,127 @@ +using System; +using System.Runtime.InteropServices; + +namespace LibHac.IO.Save +{ + public class HierarchicalSaveFileTable + { + private SaveFsList FileTable { get; } + private SaveFsList DirectoryTable { get; } + + public HierarchicalSaveFileTable(IStorage dirTable, IStorage fileTable) + { + FileTable = new SaveFsList(fileTable); + DirectoryTable = new SaveFsList(dirTable); + } + + public bool TryOpenFile(string path, out SaveFileInfo fileInfo) + { + FindPathRecursive(Util.GetUtf8Bytes(path), out SaveEntryKey key); + + if (FileTable.TryGetValue(ref key, out FileSaveEntry value)) + { + fileInfo = value.Info; + return true; + } + + fileInfo = default; + return false; + } + + public bool FindNextFile(ref SaveFindPosition position, out SaveFileInfo info, out string name) + { + if (position.NextFile == 0) + { + info = default; + name = default; + return false; + } + + Span nameBytes = stackalloc byte[FileTable.MaxNameLength]; + + bool success = FileTable.TryGetValue((int)position.NextFile, out FileSaveEntry entry, ref nameBytes); + + // todo error message + if (!success) + { + info = default; + name = default; + return false; + } + + position.NextFile = entry.NextSibling; + info = entry.Info; + + name = Util.GetUtf8StringNullTerminated(nameBytes); + + return true; + } + + public bool FindNextDirectory(ref SaveFindPosition position, out string name) + { + if (position.NextDirectory == 0) + { + name = default; + return false; + } + + Span nameBytes = stackalloc byte[FileTable.MaxNameLength]; + + bool success = DirectoryTable.TryGetValue(position.NextDirectory, out DirectorySaveEntry entry, ref nameBytes); + + // todo error message + if (!success) + { + name = default; + return false; + } + + position.NextDirectory = entry.NextSibling; + + name = Util.GetUtf8StringNullTerminated(nameBytes); + + return true; + } + + public bool TryOpenDirectory(string path, out SaveFindPosition position) + { + FindPathRecursive(Util.GetUtf8Bytes(path), out SaveEntryKey key); + + if (DirectoryTable.TryGetValue(ref key, out DirectorySaveEntry value)) + { + position = value.Pos; + return true; + } + + position = default; + return false; + } + + private void FindPathRecursive(ReadOnlySpan path, out SaveEntryKey key) + { + var parser = new PathParser(path); + key = default; + + do + { + key.Parent = DirectoryTable.GetOffsetFromKey(ref key); + } while (parser.TryGetNext(out key.Name) && !parser.IsFinished()); + } + + [StructLayout(LayoutKind.Sequential, Pack = 1)] + private struct DirectorySaveEntry + { + public int NextSibling; + public SaveFindPosition Pos; + public long Field10; + } + + [StructLayout(LayoutKind.Sequential, Pack = 1)] + private struct FileSaveEntry + { + public int NextSibling; + public SaveFileInfo Info; + public long Field10; + } + } +} diff --git a/src/LibHac/IO/Save/SaveDataDirectory.cs b/src/LibHac/IO/Save/SaveDataDirectory.cs index fccb58ff..074fb59b 100644 --- a/src/LibHac/IO/Save/SaveDataDirectory.cs +++ b/src/LibHac/IO/Save/SaveDataDirectory.cs @@ -2,43 +2,42 @@ namespace LibHac.IO.Save { - class SaveDataDirectory : IDirectory + public class SaveDataDirectory : IDirectory { - public IFileSystem ParentFileSystem { get; } + IFileSystem IDirectory.ParentFileSystem => ParentFileSystem; + public SaveDataFileSystemCore ParentFileSystem { get; } public string FullPath { get; } public OpenDirectoryMode Mode { get; } - private SaveDirectoryEntry Directory { get; } - public SaveDataDirectory(SaveDataFileSystemCore fs, string path, SaveDirectoryEntry dir, OpenDirectoryMode mode) + private SaveFindPosition InitialPosition { get; } + + public SaveDataDirectory(SaveDataFileSystemCore fs, string path, SaveFindPosition position, OpenDirectoryMode mode) { ParentFileSystem = fs; - Directory = dir; + InitialPosition = position; FullPath = path; Mode = mode; } public IEnumerable Read() { + SaveFindPosition position = InitialPosition; + HierarchicalSaveFileTable tab = ParentFileSystem.FileTable; + if (Mode.HasFlag(OpenDirectoryMode.Directories)) { - SaveDirectoryEntry 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)) { - SaveFileEntry fileEntry = Directory.FirstFile; - - while (fileEntry != null) + while (tab.FindNextFile(ref position, out SaveFileInfo info, out string name)) { - yield return new DirectoryEntry(fileEntry.Name, FullPath + '/' + fileEntry.Name, DirectoryEntryType.File, fileEntry.FileSize); - fileEntry = fileEntry.NextSibling; + yield return new DirectoryEntry(name, FullPath + '/' + name, DirectoryEntryType.File, info.Length); } } } @@ -47,25 +46,22 @@ namespace LibHac.IO.Save { int count = 0; + SaveFindPosition position = InitialPosition; + HierarchicalSaveFileTable tab = ParentFileSystem.FileTable; + if (Mode.HasFlag(OpenDirectoryMode.Directories)) { - SaveDirectoryEntry dirEntry = Directory.FirstChild; - - while (dirEntry != null) + while (tab.FindNextDirectory(ref position, out string _)) { count++; - dirEntry = dirEntry.NextSibling; } } if (Mode.HasFlag(OpenDirectoryMode.Files)) { - SaveFileEntry fileEntry = Directory.FirstFile; - - while (fileEntry != null) + while (tab.FindNextFile(ref position, out SaveFileInfo _, out string _)) { count++; - fileEntry = fileEntry.NextSibling; } } diff --git a/src/LibHac/IO/Save/SaveDataFileSystemCore.cs b/src/LibHac/IO/Save/SaveDataFileSystemCore.cs index 35a8cdd1..4a714b8f 100644 --- a/src/LibHac/IO/Save/SaveDataFileSystemCore.cs +++ b/src/LibHac/IO/Save/SaveDataFileSystemCore.cs @@ -1,5 +1,4 @@ -using System.Collections.Generic; -using System.IO; +using System.IO; namespace LibHac.IO.Save { @@ -11,11 +10,7 @@ namespace LibHac.IO.Save public AllocationTable AllocationTable { get; } private SaveHeader Header { get; } - public SaveDirectoryEntry RootDirectory { get; private set; } - private SaveFileEntry[] Files { get; set; } - private SaveDirectoryEntry[] Directories { get; set; } - private Dictionary FileDictionary { get; } - private Dictionary DirDictionary { get; } + public HierarchicalSaveFileTable FileTable { get; } public SaveDataFileSystemCore(IStorage storage, IStorage allocationTable, IStorage header) { @@ -24,40 +19,12 @@ namespace LibHac.IO.Save AllocationTable = new AllocationTable(allocationTable, header.Slice(0x18, 0x30)); Header = new SaveHeader(HeaderStorage); + + // todo: Query the FAT for the file size when none is given + AllocationTableStorage dirTableStorage = OpenFatBlock(AllocationTable.Header.DirectoryTableBlock, 1000000); + AllocationTableStorage fileTableStorage = OpenFatBlock(AllocationTable.Header.FileTableBlock, 1000000); - ReadFileInfo(); - - FileDictionary = new Dictionary(); - foreach (SaveFileEntry entry in Files) - { - FileDictionary[entry.FullPath] = entry; - } - - DirDictionary = new Dictionary(); - foreach (SaveDirectoryEntry entry in Directories) - { - DirDictionary[entry.FullPath] = entry; - } - } - - public IStorage OpenFile(string filename) - { - if (!FileDictionary.TryGetValue(filename, out SaveFileEntry file)) - { - throw new FileNotFoundException(); - } - - return OpenFile(file); - } - - public IStorage OpenFile(SaveFileEntry file) - { - if (file.BlockIndex < 0) - { - return new NullStorage(0); - } - - return OpenFatBlock(file.BlockIndex, file.FileSize); + FileTable = new HierarchicalSaveFileTable(dirTableStorage, fileTableStorage); } public void CreateDirectory(string path) @@ -84,31 +51,31 @@ namespace LibHac.IO.Save { path = PathTools.Normalize(path); - if (!DirDictionary.TryGetValue(path, out SaveDirectoryEntry dir)) + if (!FileTable.TryOpenDirectory(path, out SaveFindPosition position)) { - throw new DirectoryNotFoundException(path); + throw new DirectoryNotFoundException(); } - return new SaveDataDirectory(this, path, dir, mode); + return new SaveDataDirectory(this, path, position, mode); } public IFile OpenFile(string path, OpenMode mode) { path = PathTools.Normalize(path); - if (!FileDictionary.TryGetValue(path, out SaveFileEntry file)) + if (!FileTable.TryOpenFile(path, out SaveFileInfo file)) { throw new FileNotFoundException(); } - if (file.BlockIndex < 0) + if (file.StartBlock < 0) { return new NullFile(); } - AllocationTableStorage storage = OpenFatBlock(file.BlockIndex, file.FileSize); + AllocationTableStorage storage = OpenFatBlock(file.StartBlock, file.Length); - return new SaveDataFile(storage, 0, file.FileSize, mode); + return new SaveDataFile(storage, 0, file.Length, mode); } public void RenameDirectory(string srcPath, string dstPath) @@ -125,22 +92,22 @@ namespace LibHac.IO.Save { path = PathTools.Normalize(path); - return DirDictionary.ContainsKey(path); + return FileTable.TryOpenDirectory(path, out SaveFindPosition _); } public bool FileExists(string path) { path = PathTools.Normalize(path); - return FileDictionary.ContainsKey(path); + return FileTable.TryOpenFile(path, out SaveFileInfo _); } public DirectoryEntryType GetEntryType(string path) { path = PathTools.Normalize(path); - if (DirDictionary.ContainsKey(path)) return DirectoryEntryType.Directory; - if (FileDictionary.ContainsKey(path)) return DirectoryEntryType.File; + if (FileExists(path)) return DirectoryEntryType.File; + if (DirectoryExists(path)) return DirectoryEntryType.Directory; throw new FileNotFoundException(path); } @@ -152,91 +119,6 @@ namespace LibHac.IO.Save public IStorage GetBaseStorage() => BaseStorage.AsReadOnly(); public IStorage GetHeaderStorage() => HeaderStorage.AsReadOnly(); - public SaveFileEntry GetFileEntry(string path) => FileDictionary[path]; - - private void ReadFileInfo() - { - // todo: Query the FAT for the file size when none is given - AllocationTableStorage dirTableStream = OpenFatBlock(AllocationTable.Header.DirectoryTableBlock, 1000000); - AllocationTableStorage fileTableStream = OpenFatBlock(AllocationTable.Header.FileTableBlock, 1000000); - - SaveDirectoryEntry[] dirEntries = ReadDirEntries(dirTableStream); - SaveFileEntry[] fileEntries = ReadFileEntries(fileTableStream); - - foreach (SaveDirectoryEntry dir in dirEntries) - { - if (dir.NextSiblingIndex != 0) dir.NextSibling = dirEntries[dir.NextSiblingIndex]; - if (dir.FirstChildIndex != 0) dir.FirstChild = dirEntries[dir.FirstChildIndex]; - if (dir.FirstFileIndex != 0) dir.FirstFile = fileEntries[dir.FirstFileIndex]; - if (dir.NextInChainIndex != 0) dir.NextInChain = dirEntries[dir.NextInChainIndex]; - if (dir.ParentDirIndex != 0 && dir.ParentDirIndex < dirEntries.Length) - dir.ParentDir = dirEntries[dir.ParentDirIndex]; - } - - foreach (SaveFileEntry file in fileEntries) - { - if (file.NextSiblingIndex != 0) file.NextSibling = fileEntries[file.NextSiblingIndex]; - if (file.NextInChainIndex != 0) file.NextInChain = fileEntries[file.NextInChainIndex]; - if (file.ParentDirIndex != 0 && file.ParentDirIndex < dirEntries.Length) - file.ParentDir = dirEntries[file.ParentDirIndex]; - } - - RootDirectory = dirEntries[2]; - - SaveFileEntry fileChain = fileEntries[1].NextInChain; - var files = new List(); - while (fileChain != null) - { - files.Add(fileChain); - fileChain = fileChain.NextInChain; - } - - SaveDirectoryEntry dirChain = dirEntries[1].NextInChain; - var dirs = new List(); - while (dirChain != null) - { - dirs.Add(dirChain); - dirChain = dirChain.NextInChain; - } - - Files = files.ToArray(); - Directories = dirs.ToArray(); - - SaveFsEntry.ResolveFilenames(Files); - SaveFsEntry.ResolveFilenames(Directories); - } - - private SaveFileEntry[] ReadFileEntries(IStorage storage) - { - var reader = new BinaryReader(storage.AsStream()); - int count = reader.ReadInt32(); - - reader.BaseStream.Position -= 4; - - var entries = new SaveFileEntry[count]; - for (int i = 0; i < count; i++) - { - entries[i] = new SaveFileEntry(reader); - } - - return entries; - } - - private SaveDirectoryEntry[] ReadDirEntries(IStorage storage) - { - var reader = new BinaryReader(storage.AsStream()); - int count = reader.ReadInt32(); - - reader.BaseStream.Position -= 4; - - var entries = new SaveDirectoryEntry[count]; - for (int i = 0; i < count; i++) - { - entries[i] = new SaveDirectoryEntry(reader); - } - - return entries; - } private AllocationTableStorage OpenFatBlock(int blockIndex, long size) { diff --git a/src/LibHac/IO/Save/SaveFsEntries.cs b/src/LibHac/IO/Save/SaveFsEntries.cs new file mode 100644 index 00000000..3eb5b9d1 --- /dev/null +++ b/src/LibHac/IO/Save/SaveFsEntries.cs @@ -0,0 +1,36 @@ +using System; +using System.Runtime.InteropServices; + +namespace LibHac.IO.Save +{ + internal ref struct SaveEntryKey + { + public ReadOnlySpan Name; + public int Parent; + + public SaveEntryKey(ReadOnlySpan name, int parent) + { + Name = name; + Parent = parent; + } + } + + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct SaveFileInfo + { + public int StartBlock; + public long Length; + } + + /// + /// Represents the current position when enumerating a directory's contents. + /// + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct SaveFindPosition + { + /// The ID of the next directory to be enumerated. + public int NextDirectory; + /// The ID of the next file to be enumerated. + public long NextFile; + } +} diff --git a/src/LibHac/IO/Save/SaveFsList.cs b/src/LibHac/IO/Save/SaveFsList.cs new file mode 100644 index 00000000..0e091e7a --- /dev/null +++ b/src/LibHac/IO/Save/SaveFsList.cs @@ -0,0 +1,176 @@ +using System; +using System.Diagnostics; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace LibHac.IO.Save +{ + internal class SaveFsList where T : unmanaged + { + private const int FreeListHeadIndex = 0; + private const int UsedListHeadIndex = 1; + public int MaxNameLength { get; } = 0x40; + + private IStorage Storage { get; } + + private readonly int _sizeOfEntry = Unsafe.SizeOf(); + + public SaveFsList(IStorage tableStorage) + { + Storage = tableStorage; + } + + public int GetOffsetFromKey(ref SaveEntryKey key) + { + Span entryBytes = stackalloc byte[_sizeOfEntry]; + Span name = entryBytes.Slice(4, MaxNameLength); + ref SaveFsEntry entry = ref GetEntryFromBytes(entryBytes); + + int capacity = GetListCapacity(); + int entryId = -1; + + ReadEntry(UsedListHeadIndex, entryBytes); + + while (entry.Next > 0) + { + if (entry.Next > capacity) throw new IndexOutOfRangeException("Save entry index out of range"); + + entryId = entry.Next; + ReadEntry(entry.Next, out entry); + + if (entry.Parent == key.Parent && Util.StringSpansEqual(name, key.Name)) + { + break; + } + } + + return entryId; + } + + public bool TryGetValue(ref SaveEntryKey key, out T value) + { + int index = GetOffsetFromKey(ref key); + + if (index < 0) + { + value = default; + return false; + } + + return TryGetValue(index, out value); + } + + public bool TryGetValue(int index, out T value) + { + if (index < 0 || index >= GetListCapacity()) + { + value = default; + return false; + } + + GetValue(index, out value); + + return true; + } + + public void GetValue(int index, out T value) + { + ReadEntry(index, out SaveFsEntry entry); + value = entry.Value; + } + + /// + /// Gets the value and name associated with the specific index. + /// + /// The index of the value to get. + /// Contains the corresponding value if the method returns . + /// The name of the given index will be written to this span if the method returns . + /// This span must be at least bytes long. + /// if the contains an element with + /// the specified key; otherwise, . + public bool TryGetValue(int index, out T value, ref Span name) + { + Debug.Assert(name.Length >= MaxNameLength); + + if (index < 0 || index >= GetListCapacity()) + { + value = default; + return false; + } + + GetValue(index, out value, ref name); + + return true; + } + + /// + /// Gets the value and name associated with the specific index. + /// + /// The index of the value to get. + /// Contains the corresponding value when the method returns. + /// The name of the given index will be written to this span when the method returns. + /// This span must be at least bytes long. + public void GetValue(int index, out T value, ref Span name) + { + Debug.Assert(name.Length >= MaxNameLength); + + Span entryBytes = stackalloc byte[_sizeOfEntry]; + Span nameSpan = entryBytes.Slice(4, MaxNameLength); + ref SaveFsEntry entry = ref GetEntryFromBytes(entryBytes); + + ReadEntry(index, out entry); + + nameSpan.CopyTo(name); + value = entry.Value; + } + + private int GetListCapacity() + { + Span buf = stackalloc byte[sizeof(int)]; + Storage.Read(buf, 4); + + return MemoryMarshal.Read(buf); + } + + private int GetListLength() + { + Span buf = stackalloc byte[sizeof(int)]; + Storage.Read(buf, 0); + + return MemoryMarshal.Read(buf); + } + + private void ReadEntry(int index, out SaveFsEntry entry) + { + Span bytes = stackalloc byte[_sizeOfEntry]; + ReadEntry(index, bytes); + + entry = GetEntryFromBytes(bytes); + } + + private void ReadEntry(int index, Span entry) + { + Debug.Assert(entry.Length == _sizeOfEntry); + + int offset = index * _sizeOfEntry; + Storage.Read(entry, offset); + } + + private ref SaveFsEntry GetEntryFromBytes(Span entry) + { + return ref MemoryMarshal.Cast(entry)[0]; + } + + [StructLayout(LayoutKind.Sequential, Pack = 1)] + private struct SaveFsEntry + { + public int Parent; + private NameDummy Name; + public T Value; + public int Next; + } + + [StructLayout(LayoutKind.Sequential, Size = 0x40)] + private struct NameDummy { } + } +} diff --git a/src/LibHac/Util.cs b/src/LibHac/Util.cs index cb2a3329..f66486da 100644 --- a/src/LibHac/Util.cs +++ b/src/LibHac/Util.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.IO; using System.Numerics; +using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Text; @@ -64,6 +65,71 @@ namespace LibHac return true; } + /// + /// Compares two strings stored int byte spans. For the strings to be equal, + /// they must terminate in the same place. + /// A string can be terminated by either a null character or the end of the span. + /// + /// The first string to be compared. + /// The first string to be compared. + /// if the strings are equal; + /// otherwise . + public static bool StringSpansEqual(ReadOnlySpan s1, ReadOnlySpan s2) + { + // Make s1 the long string for simplicity + if (s1.Length < s2.Length) + { + ReadOnlySpan tmp = s1; + s1 = s2; + s2 = tmp; + } + + int shortLength = s2.Length; + int i; + + for (i = 0; i < shortLength; i++) + { + if (s1[i] != s2[i]) return false; + + // Both strings are null-terminated + if (s1[i] == 0) return true; + } + + // The bytes in the short string equal those in the long. + // Check if the strings are the same length or if the next + // character in the long string is a null character + return s1.Length == s2.Length || s1[i] == 0; + } + + public static ReadOnlySpan GetUtf8Bytes(string value) + { + return Encoding.UTF8.GetBytes(value).AsSpan(); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static string GetUtf8String(ReadOnlySpan value) + { +#if NETFRAMEWORK + return Encoding.UTF8.GetString(value.ToArray()); +#else + return Encoding.UTF8.GetString(value); +#endif + } + + public static string GetUtf8StringNullTerminated(ReadOnlySpan value) + { + int i; + for (i = 0; i < value.Length && value[i] != 0; i++) { } + + value = value.Slice(0, i); + +#if NETFRAMEWORK + return Encoding.UTF8.GetString(value.ToArray()); +#else + return Encoding.UTF8.GetString(value); +#endif + } + public static bool IsEmpty(this byte[] array) => ((ReadOnlySpan)array).IsEmpty(); public static bool IsEmpty(this ReadOnlySpan span) @@ -128,7 +194,7 @@ namespace LibHac } } - public static string ReadAsciiZ(this BinaryReader reader, int maxLength = int.MaxValue) + public static string ReadAsciiZ(this BinaryReader reader, int maxLength = Int32.MaxValue) { long start = reader.BaseStream.Position; int size = 0; @@ -145,7 +211,7 @@ namespace LibHac return text; } - public static string ReadUtf8Z(this BinaryReader reader, int maxLength = int.MaxValue) + public static string ReadUtf8Z(this BinaryReader reader, int maxLength = Int32.MaxValue) { long start = reader.BaseStream.Position; int size = 0; diff --git a/src/hactoolnet/ProcessSave.cs b/src/hactoolnet/ProcessSave.cs index a7d70704..d8b397a6 100644 --- a/src/hactoolnet/ProcessSave.cs +++ b/src/hactoolnet/ProcessSave.cs @@ -163,11 +163,10 @@ namespace hactoolnet foreach (DirectoryEntry entry in save.EnumerateEntries().Where(x => x.Type == DirectoryEntryType.File)) { - SaveFileEntry saveEntry = save.SaveDataFileSystemCore.GetFileEntry(entry.FullPath); + save.SaveDataFileSystemCore.FileTable.TryOpenFile(entry.FullPath, out SaveFileInfo fileInfo); + if (fileInfo.StartBlock < 0) continue; - if (saveEntry.BlockIndex < 0) continue; - - IEnumerable<(int block, int length)> chain = save.SaveDataFileSystemCore.AllocationTable.DumpChain(saveEntry.BlockIndex); + IEnumerable<(int block, int length)> chain = save.SaveDataFileSystemCore.AllocationTable.DumpChain(fileInfo.StartBlock); sb.AppendLine(entry.FullPath); sb.AppendLine(PrintBlockChain(chain));