mirror of
https://github.com/Thealexbarney/LibHac.git
synced 2024-11-14 10:49:41 +01:00
Merge pull request #43 from Thealexbarney/savedata
Directly read from the save FS file table
This commit is contained in:
commit
c6c2eb04c6
13 changed files with 580 additions and 217 deletions
|
@ -31,7 +31,6 @@ namespace LibHac.IO
|
||||||
BlockValidities = new Validity[SectorCount];
|
BlockValidities = new Validity[SectorCount];
|
||||||
}
|
}
|
||||||
|
|
||||||
// todo Take short path when integrity checks are disabled
|
|
||||||
private void ReadImpl(Span<byte> destination, long offset, IntegrityCheckLevel integrityCheckLevel)
|
private void ReadImpl(Span<byte> destination, long offset, IntegrityCheckLevel integrityCheckLevel)
|
||||||
{
|
{
|
||||||
int count = destination.Length;
|
int count = destination.Length;
|
||||||
|
@ -39,8 +38,6 @@ namespace LibHac.IO
|
||||||
if (count < 0 || count > SectorSize)
|
if (count < 0 || count > SectorSize)
|
||||||
throw new ArgumentOutOfRangeException(nameof(destination), "Length is invalid.");
|
throw new ArgumentOutOfRangeException(nameof(destination), "Length is invalid.");
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
long blockIndex = offset / SectorSize;
|
long blockIndex = offset / SectorSize;
|
||||||
|
|
||||||
if (BlockValidities[blockIndex] == Validity.Invalid && integrityCheckLevel == IntegrityCheckLevel.ErrorOnInvalid)
|
if (BlockValidities[blockIndex] == Validity.Invalid && integrityCheckLevel == IntegrityCheckLevel.ErrorOnInvalid)
|
||||||
|
@ -48,13 +45,10 @@ namespace LibHac.IO
|
||||||
throw new InvalidDataException("Hash error!");
|
throw new InvalidDataException("Hash error!");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Type != IntegrityStorageType.Save && integrityCheckLevel == IntegrityCheckLevel.None)
|
bool needsHashCheck = integrityCheckLevel != IntegrityCheckLevel.None &&
|
||||||
{
|
BlockValidities[blockIndex] == Validity.Unchecked;
|
||||||
BaseStorage.Read(destination, offset);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (BlockValidities[blockIndex] != Validity.Unchecked)
|
if (Type != IntegrityStorageType.Save && !needsHashCheck)
|
||||||
{
|
{
|
||||||
BaseStorage.Read(destination, offset);
|
BaseStorage.Read(destination, offset);
|
||||||
return;
|
return;
|
||||||
|
@ -64,11 +58,20 @@ namespace LibHac.IO
|
||||||
long hashPos = blockIndex * DigestSize;
|
long hashPos = blockIndex * DigestSize;
|
||||||
HashStorage.Read(hashBuffer, hashPos);
|
HashStorage.Read(hashBuffer, hashPos);
|
||||||
|
|
||||||
if (Type == IntegrityStorageType.Save && Util.IsEmpty(hashBuffer))
|
if (Type == IntegrityStorageType.Save)
|
||||||
{
|
{
|
||||||
destination.Clear();
|
if (Util.IsEmpty(hashBuffer))
|
||||||
BlockValidities[blockIndex] = Validity.Valid;
|
{
|
||||||
return;
|
destination.Clear();
|
||||||
|
BlockValidities[blockIndex] = Validity.Valid;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!needsHashCheck)
|
||||||
|
{
|
||||||
|
BaseStorage.Read(destination, offset);
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
byte[] dataBuffer = ArrayPool<byte>.Shared.Rent(SectorSize);
|
byte[] dataBuffer = ArrayPool<byte>.Shared.Rent(SectorSize);
|
||||||
|
|
|
@ -4,8 +4,12 @@ namespace LibHac.IO
|
||||||
{
|
{
|
||||||
public class NullFile : FileBase
|
public class NullFile : FileBase
|
||||||
{
|
{
|
||||||
public NullFile() { }
|
public NullFile()
|
||||||
public NullFile(long length) => Length = length;
|
{
|
||||||
|
Mode = OpenMode.ReadWrite;
|
||||||
|
}
|
||||||
|
|
||||||
|
public NullFile(long length) : this() => Length = length;
|
||||||
|
|
||||||
private long Length { get; }
|
private long Length { get; }
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,5 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Runtime.CompilerServices;
|
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
using System.Text;
|
|
||||||
|
|
||||||
namespace LibHac.IO.RomFs
|
namespace LibHac.IO.RomFs
|
||||||
{
|
{
|
||||||
|
@ -82,7 +80,7 @@ namespace LibHac.IO.RomFs
|
||||||
|
|
||||||
public bool TryOpenFile(string path, out RomFileInfo fileInfo)
|
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<FileRomEntry> keyValuePair))
|
if (FileTable.TryGetValue(ref key, out RomKeyValuePair<FileRomEntry> keyValuePair))
|
||||||
{
|
{
|
||||||
|
@ -115,7 +113,7 @@ namespace LibHac.IO.RomFs
|
||||||
/// otherwise, <see langword="false"/>.</returns>
|
/// otherwise, <see langword="false"/>.</returns>
|
||||||
public bool TryOpenDirectory(string path, out FindPosition position)
|
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<DirectoryRomEntry> keyValuePair))
|
if (DirectoryTable.TryGetValue(ref key, out RomKeyValuePair<DirectoryRomEntry> keyValuePair))
|
||||||
{
|
{
|
||||||
|
@ -168,7 +166,7 @@ namespace LibHac.IO.RomFs
|
||||||
position.NextFile = entry.NextSibling;
|
position.NextFile = entry.NextSibling;
|
||||||
info = entry.Info;
|
info = entry.Info;
|
||||||
|
|
||||||
name = GetUtf8String(nameBytes);
|
name = Util.GetUtf8String(nameBytes);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -191,7 +189,8 @@ namespace LibHac.IO.RomFs
|
||||||
|
|
||||||
ref DirectoryRomEntry entry = ref DirectoryTable.GetValueReference(position.NextDirectory, out Span<byte> nameBytes);
|
ref DirectoryRomEntry entry = ref DirectoryTable.GetValueReference(position.NextDirectory, out Span<byte> nameBytes);
|
||||||
position.NextDirectory = entry.NextSibling;
|
position.NextDirectory = entry.NextSibling;
|
||||||
name = GetUtf8String(nameBytes);
|
|
||||||
|
name = Util.GetUtf8String(nameBytes);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -205,7 +204,7 @@ namespace LibHac.IO.RomFs
|
||||||
public void AddFile(string path, ref RomFileInfo fileInfo)
|
public void AddFile(string path, ref RomFileInfo fileInfo)
|
||||||
{
|
{
|
||||||
path = PathTools.Normalize(path);
|
path = PathTools.Normalize(path);
|
||||||
ReadOnlySpan<byte> pathBytes = GetUtf8Bytes(path);
|
ReadOnlySpan<byte> pathBytes = Util.GetUtf8Bytes(path);
|
||||||
|
|
||||||
if(path == "/") throw new ArgumentException("Path cannot be empty");
|
if(path == "/") throw new ArgumentException("Path cannot be empty");
|
||||||
|
|
||||||
|
@ -221,7 +220,7 @@ namespace LibHac.IO.RomFs
|
||||||
{
|
{
|
||||||
path = PathTools.Normalize(path);
|
path = PathTools.Normalize(path);
|
||||||
|
|
||||||
CreateDirectoryRecursive(GetUtf8Bytes(path));
|
CreateDirectoryRecursive(Util.GetUtf8Bytes(path));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -237,21 +236,6 @@ namespace LibHac.IO.RomFs
|
||||||
FileTable.TrimExcess();
|
FileTable.TrimExcess();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static ReadOnlySpan<byte> GetUtf8Bytes(string value)
|
|
||||||
{
|
|
||||||
return Encoding.UTF8.GetBytes(value).AsSpan();
|
|
||||||
}
|
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
||||||
private static string GetUtf8String(ReadOnlySpan<byte> value)
|
|
||||||
{
|
|
||||||
#if NETFRAMEWORK
|
|
||||||
return Encoding.UTF8.GetString(value.ToArray());
|
|
||||||
#else
|
|
||||||
return Encoding.UTF8.GetString(value);
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
private void CreateRootDirectory()
|
private void CreateRootDirectory()
|
||||||
{
|
{
|
||||||
var key = new RomEntryKey(ReadOnlySpan<byte>.Empty, 0);
|
var key = new RomEntryKey(ReadOnlySpan<byte>.Empty, 0);
|
||||||
|
@ -371,26 +355,15 @@ namespace LibHac.IO.RomFs
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void FindFileRecursive(ReadOnlySpan<byte> path, out RomEntryKey key)
|
private void FindPathRecursive(ReadOnlySpan<byte> path, out RomEntryKey key)
|
||||||
{
|
{
|
||||||
var parser = new PathParser(path);
|
var parser = new PathParser(path);
|
||||||
key = default;
|
key = default;
|
||||||
|
|
||||||
while (parser.TryGetNext(out key.Name) && !parser.IsFinished())
|
do
|
||||||
{
|
{
|
||||||
key.Parent = DirectoryTable.GetOffsetFromKey(ref key);
|
key.Parent = DirectoryTable.GetOffsetFromKey(ref key);
|
||||||
}
|
} while (parser.TryGetNext(out key.Name) && !parser.IsFinished());
|
||||||
}
|
|
||||||
|
|
||||||
private void FindDirectoryRecursive(ReadOnlySpan<byte> 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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[StructLayout(LayoutKind.Sequential, Pack = 4)]
|
[StructLayout(LayoutKind.Sequential, Pack = 4)]
|
||||||
|
|
|
@ -16,7 +16,7 @@ namespace LibHac.IO.Save
|
||||||
|
|
||||||
if (!BeginIteration(initialBlock))
|
if (!BeginIteration(initialBlock))
|
||||||
{
|
{
|
||||||
throw new ArgumentException($"Attempted to start FAT iteration from an invalid block. ({initialBlock}");
|
throw new ArgumentException($"Attempted to start FAT iteration from an invalid block. ({initialBlock})");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -24,9 +24,9 @@ namespace LibHac.IO.Save
|
||||||
{
|
{
|
||||||
AllocationTableEntry tableEntry = Fat.Entries[initialBlock + 1];
|
AllocationTableEntry tableEntry = Fat.Entries[initialBlock + 1];
|
||||||
|
|
||||||
if (!tableEntry.IsListStart())
|
if (!tableEntry.IsListStart() && initialBlock != -1)
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (tableEntry.IsSingleBlockSegment())
|
if (tableEntry.IsSingleBlockSegment())
|
||||||
|
|
127
src/LibHac/IO/Save/HierarchicalSaveFileTable.cs
Normal file
127
src/LibHac/IO/Save/HierarchicalSaveFileTable.cs
Normal file
|
@ -0,0 +1,127 @@
|
||||||
|
using System;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
|
namespace LibHac.IO.Save
|
||||||
|
{
|
||||||
|
public class HierarchicalSaveFileTable
|
||||||
|
{
|
||||||
|
private SaveFsList<FileSaveEntry> FileTable { get; }
|
||||||
|
private SaveFsList<DirectorySaveEntry> DirectoryTable { get; }
|
||||||
|
|
||||||
|
public HierarchicalSaveFileTable(IStorage dirTable, IStorage fileTable)
|
||||||
|
{
|
||||||
|
FileTable = new SaveFsList<FileSaveEntry>(fileTable);
|
||||||
|
DirectoryTable = new SaveFsList<DirectorySaveEntry>(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<byte> 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<byte> 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<byte> 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -2,43 +2,42 @@
|
||||||
|
|
||||||
namespace LibHac.IO.Save
|
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 string FullPath { get; }
|
||||||
|
|
||||||
public OpenDirectoryMode Mode { 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;
|
ParentFileSystem = fs;
|
||||||
Directory = dir;
|
InitialPosition = position;
|
||||||
FullPath = path;
|
FullPath = path;
|
||||||
Mode = mode;
|
Mode = mode;
|
||||||
}
|
}
|
||||||
|
|
||||||
public IEnumerable<DirectoryEntry> Read()
|
public IEnumerable<DirectoryEntry> Read()
|
||||||
{
|
{
|
||||||
|
SaveFindPosition position = InitialPosition;
|
||||||
|
HierarchicalSaveFileTable tab = ParentFileSystem.FileTable;
|
||||||
|
|
||||||
if (Mode.HasFlag(OpenDirectoryMode.Directories))
|
if (Mode.HasFlag(OpenDirectoryMode.Directories))
|
||||||
{
|
{
|
||||||
SaveDirectoryEntry 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))
|
||||||
{
|
{
|
||||||
SaveFileEntry fileEntry = Directory.FirstFile;
|
while (tab.FindNextFile(ref position, out SaveFileInfo info, out string name))
|
||||||
|
|
||||||
while (fileEntry != null)
|
|
||||||
{
|
{
|
||||||
yield return new DirectoryEntry(fileEntry.Name, FullPath + '/' + fileEntry.Name, DirectoryEntryType.File, fileEntry.FileSize);
|
yield return new DirectoryEntry(name, FullPath + '/' + name, DirectoryEntryType.File, info.Length);
|
||||||
fileEntry = fileEntry.NextSibling;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -47,25 +46,22 @@ namespace LibHac.IO.Save
|
||||||
{
|
{
|
||||||
int count = 0;
|
int count = 0;
|
||||||
|
|
||||||
|
SaveFindPosition position = InitialPosition;
|
||||||
|
HierarchicalSaveFileTable tab = ParentFileSystem.FileTable;
|
||||||
|
|
||||||
if (Mode.HasFlag(OpenDirectoryMode.Directories))
|
if (Mode.HasFlag(OpenDirectoryMode.Directories))
|
||||||
{
|
{
|
||||||
SaveDirectoryEntry 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))
|
||||||
{
|
{
|
||||||
SaveFileEntry fileEntry = Directory.FirstFile;
|
while (tab.FindNextFile(ref position, out SaveFileInfo _, out string _))
|
||||||
|
|
||||||
while (fileEntry != null)
|
|
||||||
{
|
{
|
||||||
count++;
|
count++;
|
||||||
fileEntry = fileEntry.NextSibling;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
using System.Collections.Generic;
|
using System.IO;
|
||||||
using System.IO;
|
|
||||||
|
|
||||||
namespace LibHac.IO.Save
|
namespace LibHac.IO.Save
|
||||||
{
|
{
|
||||||
|
@ -11,11 +10,7 @@ namespace LibHac.IO.Save
|
||||||
public AllocationTable AllocationTable { get; }
|
public AllocationTable AllocationTable { get; }
|
||||||
private SaveHeader Header { get; }
|
private SaveHeader Header { get; }
|
||||||
|
|
||||||
public SaveDirectoryEntry RootDirectory { get; private set; }
|
public HierarchicalSaveFileTable FileTable { get; }
|
||||||
private SaveFileEntry[] Files { get; set; }
|
|
||||||
private SaveDirectoryEntry[] Directories { get; set; }
|
|
||||||
private Dictionary<string, SaveFileEntry> FileDictionary { get; }
|
|
||||||
private Dictionary<string, SaveDirectoryEntry> DirDictionary { get; }
|
|
||||||
|
|
||||||
public SaveDataFileSystemCore(IStorage storage, IStorage allocationTable, IStorage header)
|
public SaveDataFileSystemCore(IStorage storage, IStorage allocationTable, IStorage header)
|
||||||
{
|
{
|
||||||
|
@ -24,40 +19,12 @@ namespace LibHac.IO.Save
|
||||||
AllocationTable = new AllocationTable(allocationTable, header.Slice(0x18, 0x30));
|
AllocationTable = new AllocationTable(allocationTable, header.Slice(0x18, 0x30));
|
||||||
|
|
||||||
Header = new SaveHeader(HeaderStorage);
|
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();
|
FileTable = new HierarchicalSaveFileTable(dirTableStorage, fileTableStorage);
|
||||||
|
|
||||||
FileDictionary = new Dictionary<string, SaveFileEntry>();
|
|
||||||
foreach (SaveFileEntry entry in Files)
|
|
||||||
{
|
|
||||||
FileDictionary[entry.FullPath] = entry;
|
|
||||||
}
|
|
||||||
|
|
||||||
DirDictionary = new Dictionary<string, SaveDirectoryEntry>();
|
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void CreateDirectory(string path)
|
public void CreateDirectory(string path)
|
||||||
|
@ -84,31 +51,31 @@ namespace LibHac.IO.Save
|
||||||
{
|
{
|
||||||
path = PathTools.Normalize(path);
|
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)
|
public IFile OpenFile(string path, OpenMode mode)
|
||||||
{
|
{
|
||||||
path = PathTools.Normalize(path);
|
path = PathTools.Normalize(path);
|
||||||
|
|
||||||
if (!FileDictionary.TryGetValue(path, out SaveFileEntry file))
|
if (!FileTable.TryOpenFile(path, out SaveFileInfo file))
|
||||||
{
|
{
|
||||||
throw new FileNotFoundException();
|
throw new FileNotFoundException();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (file.BlockIndex < 0)
|
if (file.StartBlock < 0)
|
||||||
{
|
{
|
||||||
return new NullFile();
|
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)
|
public void RenameDirectory(string srcPath, string dstPath)
|
||||||
|
@ -125,22 +92,22 @@ namespace LibHac.IO.Save
|
||||||
{
|
{
|
||||||
path = PathTools.Normalize(path);
|
path = PathTools.Normalize(path);
|
||||||
|
|
||||||
return DirDictionary.ContainsKey(path);
|
return FileTable.TryOpenDirectory(path, out SaveFindPosition _);
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool FileExists(string path)
|
public bool FileExists(string path)
|
||||||
{
|
{
|
||||||
path = PathTools.Normalize(path);
|
path = PathTools.Normalize(path);
|
||||||
|
|
||||||
return FileDictionary.ContainsKey(path);
|
return FileTable.TryOpenFile(path, out SaveFileInfo _);
|
||||||
}
|
}
|
||||||
|
|
||||||
public DirectoryEntryType GetEntryType(string path)
|
public DirectoryEntryType GetEntryType(string path)
|
||||||
{
|
{
|
||||||
path = PathTools.Normalize(path);
|
path = PathTools.Normalize(path);
|
||||||
|
|
||||||
if (DirDictionary.ContainsKey(path)) return DirectoryEntryType.Directory;
|
if (FileExists(path)) return DirectoryEntryType.File;
|
||||||
if (FileDictionary.ContainsKey(path)) return DirectoryEntryType.File;
|
if (DirectoryExists(path)) return DirectoryEntryType.Directory;
|
||||||
|
|
||||||
throw new FileNotFoundException(path);
|
throw new FileNotFoundException(path);
|
||||||
}
|
}
|
||||||
|
@ -153,90 +120,6 @@ namespace LibHac.IO.Save
|
||||||
public IStorage GetBaseStorage() => BaseStorage.AsReadOnly();
|
public IStorage GetBaseStorage() => BaseStorage.AsReadOnly();
|
||||||
public IStorage GetHeaderStorage() => HeaderStorage.AsReadOnly();
|
public IStorage GetHeaderStorage() => HeaderStorage.AsReadOnly();
|
||||||
|
|
||||||
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<SaveFileEntry>();
|
|
||||||
while (fileChain != null)
|
|
||||||
{
|
|
||||||
files.Add(fileChain);
|
|
||||||
fileChain = fileChain.NextInChain;
|
|
||||||
}
|
|
||||||
|
|
||||||
SaveDirectoryEntry dirChain = dirEntries[1].NextInChain;
|
|
||||||
var dirs = new List<SaveDirectoryEntry>();
|
|
||||||
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)
|
private AllocationTableStorage OpenFatBlock(int blockIndex, long size)
|
||||||
{
|
{
|
||||||
return new AllocationTableStorage(BaseStorage, AllocationTable, (int)Header.BlockSize, blockIndex, size);
|
return new AllocationTableStorage(BaseStorage, AllocationTable, (int)Header.BlockSize, blockIndex, size);
|
||||||
|
|
17
src/LibHac/IO/Save/SaveExtensions.cs
Normal file
17
src/LibHac/IO/Save/SaveExtensions.cs
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace LibHac.IO.Save
|
||||||
|
{
|
||||||
|
public static class SaveExtensions
|
||||||
|
{
|
||||||
|
public static IEnumerable<(int block, int length)> DumpChain(this AllocationTable table, int startBlock)
|
||||||
|
{
|
||||||
|
var iterator = new AllocationTableIterator(table, startBlock);
|
||||||
|
|
||||||
|
do
|
||||||
|
{
|
||||||
|
yield return (iterator.PhysicalBlock, iterator.CurrentSegmentSize);
|
||||||
|
} while (iterator.MoveNext());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
36
src/LibHac/IO/Save/SaveFsEntries.cs
Normal file
36
src/LibHac/IO/Save/SaveFsEntries.cs
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
using System;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
|
namespace LibHac.IO.Save
|
||||||
|
{
|
||||||
|
internal ref struct SaveEntryKey
|
||||||
|
{
|
||||||
|
public ReadOnlySpan<byte> Name;
|
||||||
|
public int Parent;
|
||||||
|
|
||||||
|
public SaveEntryKey(ReadOnlySpan<byte> name, int parent)
|
||||||
|
{
|
||||||
|
Name = name;
|
||||||
|
Parent = parent;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
||||||
|
public struct SaveFileInfo
|
||||||
|
{
|
||||||
|
public int StartBlock;
|
||||||
|
public long Length;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Represents the current position when enumerating a directory's contents.
|
||||||
|
/// </summary>
|
||||||
|
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
||||||
|
public struct SaveFindPosition
|
||||||
|
{
|
||||||
|
/// <summary>The ID of the next directory to be enumerated.</summary>
|
||||||
|
public int NextDirectory;
|
||||||
|
/// <summary>The ID of the next file to be enumerated.</summary>
|
||||||
|
public long NextFile;
|
||||||
|
}
|
||||||
|
}
|
176
src/LibHac/IO/Save/SaveFsList.cs
Normal file
176
src/LibHac/IO/Save/SaveFsList.cs
Normal file
|
@ -0,0 +1,176 @@
|
||||||
|
using System;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
|
namespace LibHac.IO.Save
|
||||||
|
{
|
||||||
|
internal class SaveFsList<T> 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<SaveFsEntry>();
|
||||||
|
|
||||||
|
public SaveFsList(IStorage tableStorage)
|
||||||
|
{
|
||||||
|
Storage = tableStorage;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int GetOffsetFromKey(ref SaveEntryKey key)
|
||||||
|
{
|
||||||
|
Span<byte> entryBytes = stackalloc byte[_sizeOfEntry];
|
||||||
|
Span<byte> 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the value and name associated with the specific index.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="index">The index of the value to get.</param>
|
||||||
|
/// <param name="value">Contains the corresponding value if the method returns <see langword="true"/>.</param>
|
||||||
|
/// <param name="name">The name of the given index will be written to this span if the method returns <see langword="true"/>.
|
||||||
|
/// This span must be at least <see cref="MaxNameLength"/> bytes long.</param>
|
||||||
|
/// <returns><see langword="true"/> if the <see cref="SaveFsList{T}"/> contains an element with
|
||||||
|
/// the specified key; otherwise, <see langword="false"/>.</returns>
|
||||||
|
public bool TryGetValue(int index, out T value, ref Span<byte> name)
|
||||||
|
{
|
||||||
|
Debug.Assert(name.Length >= MaxNameLength);
|
||||||
|
|
||||||
|
if (index < 0 || index >= GetListCapacity())
|
||||||
|
{
|
||||||
|
value = default;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
GetValue(index, out value, ref name);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the value and name associated with the specific index.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="index">The index of the value to get.</param>
|
||||||
|
/// <param name="value">Contains the corresponding value when the method returns.</param>
|
||||||
|
/// <param name="name">The name of the given index will be written to this span when the method returns.
|
||||||
|
/// This span must be at least <see cref="MaxNameLength"/> bytes long.</param>
|
||||||
|
public void GetValue(int index, out T value, ref Span<byte> name)
|
||||||
|
{
|
||||||
|
Debug.Assert(name.Length >= MaxNameLength);
|
||||||
|
|
||||||
|
Span<byte> entryBytes = stackalloc byte[_sizeOfEntry];
|
||||||
|
Span<byte> 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<byte> buf = stackalloc byte[sizeof(int)];
|
||||||
|
Storage.Read(buf, 4);
|
||||||
|
|
||||||
|
return MemoryMarshal.Read<int>(buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
private int GetListLength()
|
||||||
|
{
|
||||||
|
Span<byte> buf = stackalloc byte[sizeof(int)];
|
||||||
|
Storage.Read(buf, 0);
|
||||||
|
|
||||||
|
return MemoryMarshal.Read<int>(buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ReadEntry(int index, out SaveFsEntry entry)
|
||||||
|
{
|
||||||
|
Span<byte> bytes = stackalloc byte[_sizeOfEntry];
|
||||||
|
ReadEntry(index, bytes);
|
||||||
|
|
||||||
|
entry = GetEntryFromBytes(bytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ReadEntry(int index, Span<byte> entry)
|
||||||
|
{
|
||||||
|
Debug.Assert(entry.Length == _sizeOfEntry);
|
||||||
|
|
||||||
|
int offset = index * _sizeOfEntry;
|
||||||
|
Storage.Read(entry, offset);
|
||||||
|
}
|
||||||
|
|
||||||
|
private ref SaveFsEntry GetEntryFromBytes(Span<byte> entry)
|
||||||
|
{
|
||||||
|
return ref MemoryMarshal.Cast<byte, SaveFsEntry>(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 { }
|
||||||
|
}
|
||||||
|
}
|
|
@ -34,9 +34,10 @@
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup Condition=" '$(TargetFramework)' == 'net46' ">
|
<ItemGroup Condition=" '$(TargetFramework)' == 'net46' ">
|
||||||
<PackageReference Include="System.Numerics.Vectors" Version="4.5.0" />
|
|
||||||
<PackageReference Include="System.Memory" Version="4.5.2" />
|
|
||||||
<PackageReference Include="System.Buffers" Version="4.5.0" />
|
<PackageReference Include="System.Buffers" Version="4.5.0" />
|
||||||
|
<PackageReference Include="System.Memory" Version="4.5.2" />
|
||||||
|
<PackageReference Include="System.Numerics.Vectors" Version="4.5.0" />
|
||||||
|
<PackageReference Include="System.ValueTuple" Version="4.5.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup Condition=" '$(TargetFramework)' == 'netcoreapp2.1' ">
|
<ItemGroup Condition=" '$(TargetFramework)' == 'netcoreapp2.1' ">
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Numerics;
|
using System.Numerics;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
|
||||||
|
@ -64,6 +65,71 @@ namespace LibHac
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 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.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="s1">The first string to be compared.</param>
|
||||||
|
/// <param name="s2">The first string to be compared.</param>
|
||||||
|
/// <returns><see langword="true"/> if the strings are equal;
|
||||||
|
/// otherwise <see langword="false"/>.</returns>
|
||||||
|
public static bool StringSpansEqual(ReadOnlySpan<byte> s1, ReadOnlySpan<byte> s2)
|
||||||
|
{
|
||||||
|
// Make s1 the long string for simplicity
|
||||||
|
if (s1.Length < s2.Length)
|
||||||
|
{
|
||||||
|
ReadOnlySpan<byte> 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<byte> GetUtf8Bytes(string value)
|
||||||
|
{
|
||||||
|
return Encoding.UTF8.GetBytes(value).AsSpan();
|
||||||
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public static string GetUtf8String(ReadOnlySpan<byte> value)
|
||||||
|
{
|
||||||
|
#if NETFRAMEWORK
|
||||||
|
return Encoding.UTF8.GetString(value.ToArray());
|
||||||
|
#else
|
||||||
|
return Encoding.UTF8.GetString(value);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string GetUtf8StringNullTerminated(ReadOnlySpan<byte> 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<byte>)array).IsEmpty();
|
public static bool IsEmpty(this byte[] array) => ((ReadOnlySpan<byte>)array).IsEmpty();
|
||||||
|
|
||||||
public static bool IsEmpty(this ReadOnlySpan<byte> span)
|
public static bool IsEmpty(this ReadOnlySpan<byte> 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;
|
long start = reader.BaseStream.Position;
|
||||||
int size = 0;
|
int size = 0;
|
||||||
|
@ -145,7 +211,7 @@ namespace LibHac
|
||||||
return text;
|
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;
|
long start = reader.BaseStream.Position;
|
||||||
int size = 0;
|
int size = 0;
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
@ -151,6 +152,86 @@ namespace hactoolnet
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx.Logger.LogMessage(save.Print());
|
ctx.Logger.LogMessage(save.Print());
|
||||||
|
//ctx.Logger.LogMessage(PrintFatLayout(save));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReSharper disable once UnusedMember.Local
|
||||||
|
private static string PrintFatLayout(this SaveDataFileSystem save)
|
||||||
|
{
|
||||||
|
var sb = new StringBuilder();
|
||||||
|
|
||||||
|
foreach (DirectoryEntry entry in save.EnumerateEntries().Where(x => x.Type == DirectoryEntryType.File))
|
||||||
|
{
|
||||||
|
save.SaveDataFileSystemCore.FileTable.TryOpenFile(entry.FullPath, out SaveFileInfo fileInfo);
|
||||||
|
if (fileInfo.StartBlock < 0) continue;
|
||||||
|
|
||||||
|
IEnumerable<(int block, int length)> chain = save.SaveDataFileSystemCore.AllocationTable.DumpChain(fileInfo.StartBlock);
|
||||||
|
|
||||||
|
sb.AppendLine(entry.FullPath);
|
||||||
|
sb.AppendLine(PrintBlockChain(chain));
|
||||||
|
}
|
||||||
|
|
||||||
|
sb.AppendLine("Directory Table");
|
||||||
|
sb.AppendLine(PrintBlockChain(save.SaveDataFileSystemCore.AllocationTable.DumpChain(0)));
|
||||||
|
|
||||||
|
sb.AppendLine("File Table");
|
||||||
|
sb.AppendLine(PrintBlockChain(save.SaveDataFileSystemCore.AllocationTable.DumpChain(1)));
|
||||||
|
|
||||||
|
sb.AppendLine("Free blocks");
|
||||||
|
sb.AppendLine(PrintBlockChain(save.SaveDataFileSystemCore.AllocationTable.DumpChain(-1)));
|
||||||
|
|
||||||
|
return sb.ToString();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string PrintBlockChain(IEnumerable<(int block, int length)> chain)
|
||||||
|
{
|
||||||
|
var sb = new StringBuilder();
|
||||||
|
int segmentCount = 0;
|
||||||
|
int segmentStart = -1;
|
||||||
|
int segmentEnd = -1;
|
||||||
|
|
||||||
|
foreach ((int block, int length) in chain)
|
||||||
|
{
|
||||||
|
if (segmentStart == -1)
|
||||||
|
{
|
||||||
|
segmentStart = block;
|
||||||
|
segmentEnd = block + length - 1;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (block == segmentEnd + 1)
|
||||||
|
{
|
||||||
|
segmentEnd += length;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
PrintSegment();
|
||||||
|
|
||||||
|
segmentStart = block;
|
||||||
|
segmentEnd = block + length - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
PrintSegment();
|
||||||
|
|
||||||
|
return sb.ToString();
|
||||||
|
|
||||||
|
void PrintSegment()
|
||||||
|
{
|
||||||
|
if (segmentCount > 0) sb.Append(", ");
|
||||||
|
|
||||||
|
if (segmentStart == segmentEnd)
|
||||||
|
{
|
||||||
|
sb.Append(segmentStart);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
sb.Append($"{segmentStart}-{segmentEnd}");
|
||||||
|
}
|
||||||
|
|
||||||
|
segmentCount++;
|
||||||
|
segmentStart = -1;
|
||||||
|
segmentEnd = -1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue