Savefile: Build file tree structure

This commit is contained in:
Alex Barney 2018-09-03 15:53:19 -05:00
parent 7bc718c975
commit 0dc36560fb
5 changed files with 157 additions and 82 deletions

View File

@ -1,10 +1,11 @@
namespace LibHac.Savefile
using System;
namespace LibHac.Savefile
{
public class AllocationTableIterator
{
private AllocationTable Fat { get; }
public bool IsValid { get; }
public int VirtualBlock { get; private set; }
public int PhysicalBlock { get; private set; }
public int CurrentSegmentSize { get; private set; }
@ -12,7 +13,10 @@
public AllocationTableIterator(AllocationTable table, int initialBlock)
{
Fat = table;
IsValid = BeginIteration(initialBlock);
if (!BeginIteration(initialBlock))
{
throw new ArgumentException($"Attempted to start FAT iteration from an invalid block. ({initialBlock}");
}
}
public bool BeginIteration(int initialBlock)
@ -47,7 +51,7 @@
var newEntry = Fat.Entries[newBlock];
VirtualBlock += CurrentSegmentSize;
if (newEntry.IsSingleBlockSegment())
{
CurrentSegmentSize = 1;

View File

@ -17,7 +17,6 @@ namespace LibHac.Savefile
BlockSize = blockSize;
Length = length;
Iterator = new AllocationTableIterator(table, initialBlock);
if (!Iterator.IsValid) Length = 0;
Data.Position = Iterator.PhysicalBlock * BlockSize;
}

View File

@ -1,63 +0,0 @@
using System.Collections.Generic;
using System.IO;
using System.Text;
namespace LibHac.Savefile
{
public class FileEntry
{
public int ParentDirIndex { get; }
public string Name { get; }
public int Field44 { get; }
public int BlockIndex { get; }
public long Size { get; }
public long Field54 { get; }
public int NextIndex { get; }
public long Offset { get; internal set; }
public string FullPath { get; private set; }
public FileEntry ParentDir { get; internal set; }
public FileEntry Next { get; internal set; }
public FileEntry(BinaryReader reader)
{
var start = reader.BaseStream.Position;
ParentDirIndex = reader.ReadInt32();
Name = reader.ReadUtf8Z(0x40);
reader.BaseStream.Position = start + 0x44;
Field44 = reader.ReadInt32();
BlockIndex = reader.ReadInt32();
Size = reader.ReadInt64();
Field54 = reader.ReadInt64();
NextIndex = reader.ReadInt32();
}
public static void ResolveFilenames(FileEntry[] entries)
{
var list = new List<string>();
var sb = new StringBuilder();
var delimiter = "/";
foreach (var file in entries)
{
list.Add(file.Name);
var dir = file.ParentDir;
while (dir != null)
{
list.Add(delimiter);
list.Add(dir.Name);
dir = dir.ParentDir;
}
for (int i = list.Count - 1; i >= 0; i--)
{
sb.Append(list[i]);
}
file.FullPath = sb.ToString();
list.Clear();
sb.Clear();
}
}
}
}

View File

@ -0,0 +1,98 @@
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Text;
namespace LibHac.Savefile
{
[DebuggerDisplay("{" + nameof(FullPath) + "}")]
public abstract class FsEntry
{
public int ParentDirIndex { get; protected set; }
public string Name { get; protected set; }
public string FullPath { get; private set; }
public DirectoryEntry ParentDir { get; internal set; }
internal static void ResolveFilenames(IEnumerable<FsEntry> entries)
{
var list = new List<string>();
var sb = new StringBuilder();
var delimiter = "/";
foreach (var file in entries)
{
list.Add(file.Name);
var dir = file.ParentDir;
while (dir != null)
{
list.Add(delimiter);
list.Add(dir.Name);
dir = dir.ParentDir;
}
for (int i = list.Count - 1; i >= 0; i--)
{
sb.Append(list[i]);
}
file.FullPath = sb.ToString();
list.Clear();
sb.Clear();
}
}
}
public class FileEntry : FsEntry
{
public int NextSiblingIndex { get; }
public int BlockIndex { get; }
public long FileSize { get; }
public long Field54 { get; }
public int NextInChainIndex { get; }
public FileEntry NextSibling { get; internal set; }
public FileEntry NextInChain { get; internal set; }
public FileEntry(BinaryReader reader)
{
var start = reader.BaseStream.Position;
ParentDirIndex = reader.ReadInt32();
Name = reader.ReadUtf8Z(0x40);
reader.BaseStream.Position = start + 0x44;
NextSiblingIndex = reader.ReadInt32();
BlockIndex = reader.ReadInt32();
FileSize = reader.ReadInt64();
Field54 = reader.ReadInt64();
NextInChainIndex = reader.ReadInt32();
}
}
public class DirectoryEntry : FsEntry
{
public int NextSiblingIndex { get; }
public int FirstChildIndex { get; }
public long FirstFileIndex { get; }
public long Field54 { get; }
public int NextInChainIndex { get; }
public DirectoryEntry NextSibling { get; internal set; }
public DirectoryEntry FirstChild { get; internal set; }
public FileEntry FirstFile { get; internal set; }
public DirectoryEntry NextInChain { get; internal set; }
public DirectoryEntry(BinaryReader reader)
{
var start = reader.BaseStream.Position;
ParentDirIndex = reader.ReadInt32();
Name = reader.ReadUtf8Z(0x40);
reader.BaseStream.Position = start + 0x44;
NextSiblingIndex = reader.ReadInt32();
FirstChildIndex = reader.ReadInt32();
FirstFileIndex = reader.ReadInt64();
Field54 = reader.ReadInt64();
NextInChainIndex = reader.ReadInt32();
}
}
}

View File

@ -1,5 +1,4 @@
using System;
using System.Collections.Generic;
using System.Collections.Generic;
using System.IO;
using System.Text;
using LibHac.Streams;
@ -33,7 +32,9 @@ namespace LibHac.Savefile
public Stream JournalLayer3Hash { get; }
public Stream JournalFat { get; }
public DirectoryEntry RootDirectory { get; private set; }
public FileEntry[] Files { get; private set; }
public DirectoryEntry[] Directories { get; private set; }
private Dictionary<string, FileEntry> FileDict { get; }
public Savefile(Stream file, IProgressReport logger = null)
@ -126,7 +127,7 @@ namespace LibHac.Savefile
return JournalStreamSource.CreateStream(0, 0);
}
return OpenFatBlock(file.BlockIndex, file.Size);
return OpenFatBlock(file.BlockIndex, file.FileSize);
}
private AllocationTableStream OpenFatBlock(int blockIndex, long size)
@ -138,34 +139,54 @@ namespace LibHac.Savefile
private void ReadFileInfo()
{
var blockSize = Header.Save.BlockSize;
// todo: Query the FAT for the file size when none is given
var dirTableStream = OpenFatBlock(Header.Save.DirectoryTableBlock, 1000000);
var fileTableStream = OpenFatBlock(Header.Save.FileTableBlock, 1000000);
FileEntry[] dirEntries = ReadFileEntries(dirTableStream);
DirectoryEntry[] dirEntries = ReadDirEntries(dirTableStream);
FileEntry[] fileEntries = ReadFileEntries(fileTableStream);
foreach (var dir in dirEntries)
foreach (DirectoryEntry dir in dirEntries)
{
if (dir.NextIndex != 0) dir.Next = dirEntries[dir.NextIndex];
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 (var file in fileEntries)
foreach (FileEntry file in fileEntries)
{
if (file.NextIndex != 0) file.Next = fileEntries[file.NextIndex];
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];
file.Offset = file.BlockIndex < 0 ? 0 : file.BlockIndex * blockSize;
}
Files = new FileEntry[fileEntries.Length - 2];
Array.Copy(fileEntries, 2, Files, 0, Files.Length);
RootDirectory = dirEntries[2];
FileEntry.ResolveFilenames(Files);
var fileChain = fileEntries[1].NextInChain;
var files = new List<FileEntry>();
while (fileChain != null)
{
files.Add(fileChain);
fileChain = fileChain.NextInChain;
}
var dirChain = dirEntries[1].NextInChain;
var dirs = new List<DirectoryEntry>();
while (dirChain != null)
{
dirs.Add(dirChain);
dirChain = dirChain.NextInChain;
}
Files = files.ToArray();
Directories = dirs.ToArray();
FsEntry.ResolveFilenames(Files);
FsEntry.ResolveFilenames(Directories);
}
private FileEntry[] ReadFileEntries(Stream stream)
@ -183,6 +204,22 @@ namespace LibHac.Savefile
return entries;
}
private DirectoryEntry[] ReadDirEntries(Stream stream)
{
var reader = new BinaryReader(stream);
var count = reader.ReadInt32();
reader.BaseStream.Position -= 4;
var entries = new DirectoryEntry[count];
for (int i = 0; i < count; i++)
{
entries[i] = new DirectoryEntry(reader);
}
return entries;
}
}
public static class SavefileExtensions