Add SaveDataFileSystem

This commit is contained in:
Alex Barney 2019-01-04 21:00:56 -07:00
parent 9f181c7b45
commit f1f52e7151
18 changed files with 465 additions and 118 deletions

View file

@ -1,6 +1,7 @@
using System; using System;
using System.Buffers; using System.Buffers;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO;
namespace LibHac.IO namespace LibHac.IO
{ {
@ -96,5 +97,29 @@ namespace LibHac.IO
logger?.SetTotal(0); logger?.SetTotal(0);
} }
} }
public static Stream AsStream(this IFile storage) => new NxFileStream(storage, true);
public static Stream AsStream(this IFile storage, bool keepOpen) => new NxFileStream(storage, keepOpen);
public static int GetEntryCount(this IFileSystem fs, OpenDirectoryMode mode)
{
return fs.OpenDirectory("/", OpenDirectoryMode.All).GetEntryCountRecursive(mode);
}
public static int GetEntryCountRecursive(this IDirectory directory, OpenDirectoryMode mode)
{
int count = 0;
foreach (DirectoryEntry entry in directory.EnumerateEntries())
{
if (entry.Type == DirectoryEntryType.Directory && (mode & OpenDirectoryMode.Directories) != 0 ||
entry.Type == DirectoryEntryType.File && (mode & OpenDirectoryMode.Files) != 0)
{
count++;
}
}
return count;
}
} }
} }

View file

@ -6,6 +6,7 @@ namespace LibHac.IO
{ {
IFileSystem ParentFileSystem { get; } IFileSystem ParentFileSystem { get; }
string FullPath { get; } string FullPath { get; }
OpenDirectoryMode Mode { get; }
IEnumerable<DirectoryEntry> Read(); IEnumerable<DirectoryEntry> Read();
int GetEntryCount(); int GetEntryCount();

View file

@ -10,7 +10,7 @@ namespace LibHac.IO
public string FullPath { get; } public string FullPath { get; }
private string LocalPath { get; } private string LocalPath { get; }
private OpenDirectoryMode Mode { get; } public OpenDirectoryMode Mode { get; }
private DirectoryInfo DirInfo { get; } private DirectoryInfo DirInfo { get; }
public LocalDirectory(LocalFileSystem fs, string path, OpenDirectoryMode mode) public LocalDirectory(LocalFileSystem fs, string path, OpenDirectoryMode mode)

View file

@ -0,0 +1,75 @@
using System;
using System.IO;
namespace LibHac.IO
{
public class NxFileStream : Stream
{
private IFile BaseFile { get; }
private bool LeaveOpen { get; }
public NxFileStream(IFile baseFile, bool leaveOpen)
{
BaseFile = baseFile;
LeaveOpen = leaveOpen;
Length = baseFile.GetSize();
}
public override int Read(byte[] buffer, int offset, int count)
{
int toRead = (int)Math.Min(count, Length - Position);
BaseFile.Read(buffer.AsSpan(offset, count), Position);
Position += toRead;
return toRead;
}
public override void Write(byte[] buffer, int offset, int count)
{
BaseFile.Write(buffer.AsSpan(offset, count), Position);
Position += count;
}
public override void Flush()
{
BaseFile.Flush();
}
public override long Seek(long offset, SeekOrigin origin)
{
switch (origin)
{
case SeekOrigin.Begin:
Position = offset;
break;
case SeekOrigin.Current:
Position += offset;
break;
case SeekOrigin.End:
Position = Length - offset;
break;
}
return Position;
}
public override void SetLength(long value)
{
throw new NotImplementedException();
}
// todo access
public override bool CanRead => true;
public override bool CanSeek => true;
public override bool CanWrite => true;
public override long Length { get; }
public override long Position { get; set; }
protected override void Dispose(bool disposing)
{
if (!LeaveOpen) BaseFile?.Dispose();
base.Dispose(disposing);
}
}
}

View file

@ -9,7 +9,7 @@ namespace LibHac.IO
public PartitionFileSystem ParentFileSystem { get; } public PartitionFileSystem ParentFileSystem { get; }
public string FullPath { get; } public string FullPath { get; }
private OpenDirectoryMode Mode { get; } public OpenDirectoryMode Mode { get; }
public PartitionDirectory(PartitionFileSystem fs, string path, OpenDirectoryMode mode) public PartitionDirectory(PartitionFileSystem fs, string path, OpenDirectoryMode mode)
{ {

View file

@ -9,7 +9,7 @@ namespace LibHac.IO
public string FullPath { get; } public string FullPath { get; }
private RomfsDir Directory { get; } private RomfsDir Directory { get; }
private OpenDirectoryMode Mode { get; } public OpenDirectoryMode Mode { get; }
public RomFsDirectory(RomFsFileSystem fs, string path, OpenDirectoryMode mode) public RomFsDirectory(RomFsFileSystem fs, string path, OpenDirectoryMode mode)
{ {

View file

@ -35,7 +35,7 @@ namespace LibHac.IO.Save
public byte[] Data { get; } public byte[] Data { get; }
public Header(Keyset keyset, IStorage storage) public Header(IStorage storage, Keyset keyset)
{ {
MainStorage = storage; MainStorage = storage;
MainHeader = MainStorage.Slice(0x100, 0x200); MainHeader = MainStorage.Slice(0x100, 0x200);

View file

@ -0,0 +1,75 @@
using System.Collections.Generic;
namespace LibHac.IO.Save
{
class SaveDataDirectory : IDirectory
{
public IFileSystem ParentFileSystem { get; }
public string FullPath { get; }
public OpenDirectoryMode Mode { get; }
private SaveDirectoryEntry Directory { get; }
public SaveDataDirectory(SaveDataFileSystemCore fs, string path, SaveDirectoryEntry dir, OpenDirectoryMode mode)
{
ParentFileSystem = fs;
Directory = dir;
FullPath = path;
Mode = mode;
}
public IEnumerable<DirectoryEntry> Read()
{
if (Mode.HasFlag(OpenDirectoryMode.Directories))
{
SaveDirectoryEntry dirEntry = Directory.FirstChild;
while (dirEntry != null)
{
yield return new DirectoryEntry(dirEntry.Name, FullPath + '/' + dirEntry.Name, DirectoryEntryType.Directory, 0);
dirEntry = dirEntry.NextSibling;
}
}
if (Mode.HasFlag(OpenDirectoryMode.Files))
{
SaveFileEntry fileEntry = Directory.FirstFile;
while (fileEntry != null)
{
yield return new DirectoryEntry(fileEntry.Name, FullPath + '/' + fileEntry.Name, DirectoryEntryType.File, fileEntry.FileSize);
fileEntry = fileEntry.NextSibling;
}
}
}
public int GetEntryCount()
{
int count = 0;
if (Mode.HasFlag(OpenDirectoryMode.Directories))
{
SaveDirectoryEntry dirEntry = Directory.FirstChild;
while (dirEntry != null)
{
count++;
dirEntry = dirEntry.NextSibling;
}
}
if (Mode.HasFlag(OpenDirectoryMode.Files))
{
SaveFileEntry fileEntry = Directory.FirstFile;
while (fileEntry != null)
{
count++;
fileEntry = fileEntry.NextSibling;
}
}
return count;
}
}
}

View file

@ -0,0 +1,51 @@
using System;
namespace LibHac.IO.Save
{
public class SaveDataFile : FileBase
{
private AllocationTableStorage BaseStorage { get; }
private long Offset { get; }
private long Size { get; }
public SaveDataFile(AllocationTableStorage baseStorage, long offset, long size, OpenMode mode)
{
Mode = mode;
BaseStorage = baseStorage;
Offset = offset;
Size = size;
}
public override int Read(Span<byte> destination, long offset)
{
int toRead = ValidateReadParamsAndGetSize(destination, offset);
long storageOffset = Offset + offset;
BaseStorage.Read(destination.Slice(0, toRead), storageOffset);
return toRead;
}
public override void Write(ReadOnlySpan<byte> source, long offset)
{
ValidateWriteParams(source, offset);
BaseStorage.Write(source, offset);
}
public override void Flush()
{
BaseStorage.Flush();
}
public override long GetSize()
{
return Size;
}
public override void SetSize(long size)
{
throw new NotImplementedException();
}
}
}

View file

@ -1,10 +1,9 @@
using System; using System.Collections.Generic;
using System.Collections.Generic;
using System.IO; using System.IO;
namespace LibHac.IO.Save namespace LibHac.IO.Save
{ {
public class SaveData : IDisposable public class SaveDataFileSystem : IFileSystem
{ {
public Header Header { get; } public Header Header { get; }
public IStorage BaseStorage { get; } public IStorage BaseStorage { get; }
@ -19,16 +18,15 @@ namespace LibHac.IO.Save
public HierarchicalDuplexStorage DuplexStorage { get; } public HierarchicalDuplexStorage DuplexStorage { get; }
public JournalStorage JournalStorage { get; } public JournalStorage JournalStorage { get; }
public DirectoryEntry RootDirectory => SaveDataFileSystemCore.RootDirectory; private Keyset Keyset { get; }
public FileEntry[] Files => SaveDataFileSystemCore.Files;
public DirectoryEntry[] Directories => SaveDataFileSystemCore.Directories;
public SaveData(Keyset keyset, IStorage storage, IntegrityCheckLevel integrityCheckLevel, bool leaveOpen) public SaveDataFileSystem(Keyset keyset, IStorage storage, IntegrityCheckLevel integrityCheckLevel, bool leaveOpen)
{ {
BaseStorage = storage; BaseStorage = storage;
LeaveOpen = leaveOpen; LeaveOpen = leaveOpen;
Keyset = keyset;
Header = new Header(keyset, BaseStorage); Header = new Header(BaseStorage, keyset);
FsLayout layout = Header.Layout; FsLayout layout = Header.Layout;
IStorage dataRemapBase = BaseStorage.Slice(layout.FileMapDataOffset, layout.FileMapDataSize); IStorage dataRemapBase = BaseStorage.Slice(layout.FileMapDataOffset, layout.FileMapDataSize);
@ -119,21 +117,56 @@ namespace LibHac.IO.Save
IntegrityStorageType.Save, integrityCheckLevel, LeaveOpen); IntegrityStorageType.Save, integrityCheckLevel, LeaveOpen);
} }
public IStorage OpenFile(string filename) public void CreateDirectory(string path)
{ {
return SaveDataFileSystemCore.OpenFile(filename); throw new System.NotImplementedException();
} }
public IStorage OpenFile(FileEntry file) public void CreateFile(string path, long size)
{ {
return SaveDataFileSystemCore.OpenFile(file); throw new System.NotImplementedException();
} }
public void DeleteDirectory(string path)
{
throw new System.NotImplementedException();
}
public void DeleteFile(string path)
{
throw new System.NotImplementedException();
}
public IDirectory OpenDirectory(string path, OpenDirectoryMode mode)
{
return SaveDataFileSystemCore.OpenDirectory(path, mode);
}
public IFile OpenFile(string path, OpenMode mode)
{
return SaveDataFileSystemCore.OpenFile(path, mode);
}
public void RenameDirectory(string srcPath, string dstPath)
{
throw new System.NotImplementedException();
}
public void RenameFile(string srcPath, string dstPath)
{
throw new System.NotImplementedException();
}
public bool DirectoryExists(string path) => SaveDataFileSystemCore.DirectoryExists(path);
public bool FileExists(string filename) => SaveDataFileSystemCore.FileExists(filename); public bool FileExists(string filename) => SaveDataFileSystemCore.FileExists(filename);
public bool CommitHeader(Keyset keyset) public void Commit()
{
Commit(Keyset);
}
public bool Commit(Keyset keyset)
{ {
// todo
Stream headerStream = BaseStorage.AsStream(); Stream headerStream = BaseStorage.AsStream();
var hashData = new byte[0x3d00]; var hashData = new byte[0x3d00];
@ -145,7 +178,7 @@ namespace LibHac.IO.Save
headerStream.Position = 0x108; headerStream.Position = 0x108;
headerStream.Write(hash, 0, hash.Length); headerStream.Write(hash, 0, hash.Length);
if (keyset.SaveMacKey.IsEmpty()) return false; if (keyset == null || keyset.SaveMacKey.IsEmpty()) return false;
var cmacData = new byte[0x200]; var cmacData = new byte[0x200];
var cmac = new byte[0x10]; var cmac = new byte[0x10];
@ -169,39 +202,5 @@ namespace LibHac.IO.Save
return validity; return validity;
} }
protected virtual void Dispose(bool disposing)
{
if (disposing && !LeaveOpen)
{
BaseStorage?.Dispose();
}
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
}
public static class SavefileExtensions
{
public static void Extract(this SaveData save, string outDir, IProgressReport logger = null)
{
foreach (FileEntry file in save.Files)
{
IStorage storage = save.OpenFile(file);
string outName = outDir + file.FullPath;
string dir = Path.GetDirectoryName(outName);
if (!string.IsNullOrWhiteSpace(dir)) Directory.CreateDirectory(dir);
using (var outFile = new FileStream(outName, FileMode.Create, FileAccess.ReadWrite))
{
logger?.LogMessage(file.FullPath);
storage.CopyToStream(outFile, storage.Length, logger);
}
}
}
} }
} }

View file

@ -3,7 +3,7 @@ using System.IO;
namespace LibHac.IO.Save namespace LibHac.IO.Save
{ {
public class SaveDataFileSystemCore public class SaveDataFileSystemCore : IFileSystem
{ {
private IStorage BaseStorage { get; } private IStorage BaseStorage { get; }
private IStorage HeaderStorage { get; } private IStorage HeaderStorage { get; }
@ -11,10 +11,11 @@ namespace LibHac.IO.Save
public AllocationTable AllocationTable { get; } public AllocationTable AllocationTable { get; }
private SaveHeader Header { get; } private SaveHeader Header { get; }
public DirectoryEntry RootDirectory { get; private set; } public SaveDirectoryEntry RootDirectory { get; private set; }
public FileEntry[] Files { get; private set; } private SaveFileEntry[] Files { get; set; }
public DirectoryEntry[] Directories { get; private set; } private SaveDirectoryEntry[] Directories { get; set; }
public Dictionary<string, FileEntry> FileDictionary { get; } 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)
{ {
@ -25,18 +26,23 @@ namespace LibHac.IO.Save
Header = new SaveHeader(HeaderStorage); Header = new SaveHeader(HeaderStorage);
ReadFileInfo(); ReadFileInfo();
var dictionary = new Dictionary<string, FileEntry>();
foreach (FileEntry entry in Files) FileDictionary = new Dictionary<string, SaveFileEntry>();
foreach (SaveFileEntry entry in Files)
{ {
dictionary[entry.FullPath] = entry; FileDictionary[entry.FullPath] = entry;
} }
FileDictionary = dictionary; DirDictionary = new Dictionary<string, SaveDirectoryEntry>();
foreach (SaveDirectoryEntry entry in Directories)
{
DirDictionary[entry.FullPath] = entry;
}
} }
public IStorage OpenFile(string filename) public IStorage OpenFile(string filename)
{ {
if (!FileDictionary.TryGetValue(filename, out FileEntry file)) if (!FileDictionary.TryGetValue(filename, out SaveFileEntry file))
{ {
throw new FileNotFoundException(); throw new FileNotFoundException();
} }
@ -44,7 +50,7 @@ namespace LibHac.IO.Save
return OpenFile(file); return OpenFile(file);
} }
public IStorage OpenFile(FileEntry file) public IStorage OpenFile(SaveFileEntry file)
{ {
if (file.BlockIndex < 0) if (file.BlockIndex < 0)
{ {
@ -55,7 +61,76 @@ namespace LibHac.IO.Save
return OpenFatBlock(file.BlockIndex, file.FileSize); return OpenFatBlock(file.BlockIndex, file.FileSize);
} }
public void CreateDirectory(string path)
{
throw new System.NotImplementedException();
}
public void CreateFile(string path, long size)
{
throw new System.NotImplementedException();
}
public void DeleteDirectory(string path)
{
throw new System.NotImplementedException();
}
public void DeleteFile(string path)
{
throw new System.NotImplementedException();
}
public IDirectory OpenDirectory(string path, OpenDirectoryMode mode)
{
path = PathTools.Normalize(path);
if (!DirDictionary.TryGetValue(path, out SaveDirectoryEntry dir))
{
throw new DirectoryNotFoundException(path);
}
return new SaveDataDirectory(this, path, dir, mode);
}
public IFile OpenFile(string path, OpenMode mode)
{
if (!FileDictionary.TryGetValue(path, out SaveFileEntry file))
{
throw new FileNotFoundException();
}
if (file.BlockIndex < 0)
{
// todo
return new StorageFile(new MemoryStorage(new byte[0]), OpenMode.ReadWrite);
}
AllocationTableStorage storage = OpenFatBlock(file.BlockIndex, file.FileSize);
return new SaveDataFile(storage, 0, file.FileSize, mode);
}
public void RenameDirectory(string srcPath, string dstPath)
{
throw new System.NotImplementedException();
}
public void RenameFile(string srcPath, string dstPath)
{
throw new System.NotImplementedException();
}
public bool DirectoryExists(string path)
{
throw new System.NotImplementedException();
}
public bool FileExists(string filename) => FileDictionary.ContainsKey(filename); public bool FileExists(string filename) => FileDictionary.ContainsKey(filename);
public void Commit()
{
throw new System.NotImplementedException();
}
public IStorage GetBaseStorage() => BaseStorage.WithAccess(FileAccess.Read); public IStorage GetBaseStorage() => BaseStorage.WithAccess(FileAccess.Read);
public IStorage GetHeaderStorage() => HeaderStorage.WithAccess(FileAccess.Read); public IStorage GetHeaderStorage() => HeaderStorage.WithAccess(FileAccess.Read);
@ -66,10 +141,10 @@ namespace LibHac.IO.Save
AllocationTableStorage dirTableStream = OpenFatBlock(AllocationTable.Header.DirectoryTableBlock, 1000000); AllocationTableStorage dirTableStream = OpenFatBlock(AllocationTable.Header.DirectoryTableBlock, 1000000);
AllocationTableStorage fileTableStream = OpenFatBlock(AllocationTable.Header.FileTableBlock, 1000000); AllocationTableStorage fileTableStream = OpenFatBlock(AllocationTable.Header.FileTableBlock, 1000000);
DirectoryEntry[] dirEntries = ReadDirEntries(dirTableStream); SaveDirectoryEntry[] dirEntries = ReadDirEntries(dirTableStream);
FileEntry[] fileEntries = ReadFileEntries(fileTableStream); SaveFileEntry[] fileEntries = ReadFileEntries(fileTableStream);
foreach (DirectoryEntry dir in dirEntries) foreach (SaveDirectoryEntry dir in dirEntries)
{ {
if (dir.NextSiblingIndex != 0) dir.NextSibling = dirEntries[dir.NextSiblingIndex]; if (dir.NextSiblingIndex != 0) dir.NextSibling = dirEntries[dir.NextSiblingIndex];
if (dir.FirstChildIndex != 0) dir.FirstChild = dirEntries[dir.FirstChildIndex]; if (dir.FirstChildIndex != 0) dir.FirstChild = dirEntries[dir.FirstChildIndex];
@ -79,7 +154,7 @@ namespace LibHac.IO.Save
dir.ParentDir = dirEntries[dir.ParentDirIndex]; dir.ParentDir = dirEntries[dir.ParentDirIndex];
} }
foreach (FileEntry file in fileEntries) foreach (SaveFileEntry file in fileEntries)
{ {
if (file.NextSiblingIndex != 0) file.NextSibling = fileEntries[file.NextSiblingIndex]; if (file.NextSiblingIndex != 0) file.NextSibling = fileEntries[file.NextSiblingIndex];
if (file.NextInChainIndex != 0) file.NextInChain = fileEntries[file.NextInChainIndex]; if (file.NextInChainIndex != 0) file.NextInChain = fileEntries[file.NextInChainIndex];
@ -89,16 +164,16 @@ namespace LibHac.IO.Save
RootDirectory = dirEntries[2]; RootDirectory = dirEntries[2];
FileEntry fileChain = fileEntries[1].NextInChain; SaveFileEntry fileChain = fileEntries[1].NextInChain;
var files = new List<FileEntry>(); var files = new List<SaveFileEntry>();
while (fileChain != null) while (fileChain != null)
{ {
files.Add(fileChain); files.Add(fileChain);
fileChain = fileChain.NextInChain; fileChain = fileChain.NextInChain;
} }
DirectoryEntry dirChain = dirEntries[1].NextInChain; SaveDirectoryEntry dirChain = dirEntries[1].NextInChain;
var dirs = new List<DirectoryEntry>(); var dirs = new List<SaveDirectoryEntry>();
while (dirChain != null) while (dirChain != null)
{ {
dirs.Add(dirChain); dirs.Add(dirChain);
@ -108,37 +183,37 @@ namespace LibHac.IO.Save
Files = files.ToArray(); Files = files.ToArray();
Directories = dirs.ToArray(); Directories = dirs.ToArray();
FsEntry.ResolveFilenames(Files); SaveFsEntry.ResolveFilenames(Files);
FsEntry.ResolveFilenames(Directories); SaveFsEntry.ResolveFilenames(Directories);
} }
private FileEntry[] ReadFileEntries(IStorage storage) private SaveFileEntry[] ReadFileEntries(IStorage storage)
{ {
var reader = new BinaryReader(storage.AsStream()); var reader = new BinaryReader(storage.AsStream());
int count = reader.ReadInt32(); int count = reader.ReadInt32();
reader.BaseStream.Position -= 4; reader.BaseStream.Position -= 4;
var entries = new FileEntry[count]; var entries = new SaveFileEntry[count];
for (int i = 0; i < count; i++) for (int i = 0; i < count; i++)
{ {
entries[i] = new FileEntry(reader); entries[i] = new SaveFileEntry(reader);
} }
return entries; return entries;
} }
private DirectoryEntry[] ReadDirEntries(IStorage storage) private SaveDirectoryEntry[] ReadDirEntries(IStorage storage)
{ {
var reader = new BinaryReader(storage.AsStream()); var reader = new BinaryReader(storage.AsStream());
int count = reader.ReadInt32(); int count = reader.ReadInt32();
reader.BaseStream.Position -= 4; reader.BaseStream.Position -= 4;
var entries = new DirectoryEntry[count]; var entries = new SaveDirectoryEntry[count];
for (int i = 0; i < count; i++) for (int i = 0; i < count; i++)
{ {
entries[i] = new DirectoryEntry(reader); entries[i] = new SaveDirectoryEntry(reader);
} }
return entries; return entries;

View file

@ -6,23 +6,23 @@ using System.Text;
namespace LibHac.IO.Save namespace LibHac.IO.Save
{ {
[DebuggerDisplay("{" + nameof(FullPath) + "}")] [DebuggerDisplay("{" + nameof(FullPath) + "}")]
public abstract class FsEntry public abstract class SaveFsEntry
{ {
public int ParentDirIndex { get; protected set; } public int ParentDirIndex { get; protected set; }
public string Name { get; protected set; } public string Name { get; protected set; }
public string FullPath { get; private set; } public string FullPath { get; private set; }
public DirectoryEntry ParentDir { get; internal set; } public SaveDirectoryEntry ParentDir { get; internal set; }
internal static void ResolveFilenames(IEnumerable<FsEntry> entries) internal static void ResolveFilenames(IEnumerable<SaveFsEntry> entries)
{ {
var list = new List<string>(); var list = new List<string>();
var sb = new StringBuilder(); var sb = new StringBuilder();
string delimiter = "/"; string delimiter = "/";
foreach (FsEntry file in entries) foreach (SaveFsEntry file in entries)
{ {
list.Add(file.Name); list.Add(file.Name);
DirectoryEntry dir = file.ParentDir; SaveDirectoryEntry dir = file.ParentDir;
while (dir != null) while (dir != null)
{ {
list.Add(delimiter); list.Add(delimiter);
@ -35,14 +35,14 @@ namespace LibHac.IO.Save
sb.Append(list[i]); sb.Append(list[i]);
} }
file.FullPath = sb.ToString(); file.FullPath = sb.Length == 0 ? delimiter : sb.ToString();
list.Clear(); list.Clear();
sb.Clear(); sb.Clear();
} }
} }
} }
public class FileEntry : FsEntry public class SaveFileEntry : SaveFsEntry
{ {
public int NextSiblingIndex { get; } public int NextSiblingIndex { get; }
public int BlockIndex { get; } public int BlockIndex { get; }
@ -50,10 +50,10 @@ namespace LibHac.IO.Save
public long Field54 { get; } public long Field54 { get; }
public int NextInChainIndex { get; } public int NextInChainIndex { get; }
public FileEntry NextSibling { get; internal set; } public SaveFileEntry NextSibling { get; internal set; }
public FileEntry NextInChain { get; internal set; } public SaveFileEntry NextInChain { get; internal set; }
public FileEntry(BinaryReader reader) public SaveFileEntry(BinaryReader reader)
{ {
long start = reader.BaseStream.Position; long start = reader.BaseStream.Position;
ParentDirIndex = reader.ReadInt32(); ParentDirIndex = reader.ReadInt32();
@ -68,7 +68,7 @@ namespace LibHac.IO.Save
} }
} }
public class DirectoryEntry : FsEntry public class SaveDirectoryEntry : SaveFsEntry
{ {
public int NextSiblingIndex { get; } public int NextSiblingIndex { get; }
public int FirstChildIndex { get; } public int FirstChildIndex { get; }
@ -76,12 +76,12 @@ namespace LibHac.IO.Save
public long Field54 { get; } public long Field54 { get; }
public int NextInChainIndex { get; } public int NextInChainIndex { get; }
public DirectoryEntry NextSibling { get; internal set; } public SaveDirectoryEntry NextSibling { get; internal set; }
public DirectoryEntry FirstChild { get; internal set; } public SaveDirectoryEntry FirstChild { get; internal set; }
public FileEntry FirstFile { get; internal set; } public SaveFileEntry FirstFile { get; internal set; }
public DirectoryEntry NextInChain { get; internal set; } public SaveDirectoryEntry NextInChain { get; internal set; }
public DirectoryEntry(BinaryReader reader) public SaveDirectoryEntry(BinaryReader reader)
{ {
long start = reader.BaseStream.Position; long start = reader.BaseStream.Position;
ParentDirIndex = reader.ReadInt32(); ParentDirIndex = reader.ReadInt32();

View file

@ -0,0 +1,44 @@
using System;
namespace LibHac.IO
{
public class StorageFile : FileBase
{
private IStorage BaseStorage { get; }
public StorageFile(IStorage baseStorage, OpenMode mode)
{
BaseStorage = baseStorage;
Mode = mode;
}
public override int Read(Span<byte> destination, long offset)
{
int toRead = ValidateReadParamsAndGetSize(destination, offset);
BaseStorage.Read(destination.Slice(0, toRead), offset);
return toRead;
}
public override void Write(ReadOnlySpan<byte> source, long offset)
{
BaseStorage.Write(source, offset);
}
public override void Flush()
{
BaseStorage.Flush();
}
public override long GetSize()
{
return BaseStorage.Length;
}
public override void SetSize(long size)
{
throw new NotImplementedException();
}
}
}

View file

@ -17,7 +17,7 @@ namespace LibHac
public string SaveDir { get; } public string SaveDir { get; }
public Dictionary<string, Nca> Ncas { get; } = new Dictionary<string, Nca>(StringComparer.OrdinalIgnoreCase); public Dictionary<string, Nca> Ncas { get; } = new Dictionary<string, Nca>(StringComparer.OrdinalIgnoreCase);
public Dictionary<string, SaveData> Saves { get; } = new Dictionary<string, SaveData>(StringComparer.OrdinalIgnoreCase); public Dictionary<string, SaveDataFileSystem> Saves { get; } = new Dictionary<string, SaveDataFileSystem>(StringComparer.OrdinalIgnoreCase);
public Dictionary<ulong, Title> Titles { get; } = new Dictionary<ulong, Title>(); public Dictionary<ulong, Title> Titles { get; } = new Dictionary<ulong, Title>();
public Dictionary<ulong, Application> Applications { get; } = new Dictionary<ulong, Application>(); public Dictionary<ulong, Application> Applications { get; } = new Dictionary<ulong, Application>();
@ -118,7 +118,7 @@ namespace LibHac
foreach (string file in files) foreach (string file in files)
{ {
SaveData save = null; SaveDataFileSystem save = null;
string saveName = Path.GetFileNameWithoutExtension(file); string saveName = Path.GetFileNameWithoutExtension(file);
try try
@ -127,7 +127,7 @@ namespace LibHac
string sdPath = "/" + Util.GetRelativePath(file, SaveDir).Replace('\\', '/'); string sdPath = "/" + Util.GetRelativePath(file, SaveDir).Replace('\\', '/');
var nax0 = new Nax0(Keyset, storage, sdPath, false); var nax0 = new Nax0(Keyset, storage, sdPath, false);
save = new SaveData(Keyset, nax0.BaseStorage, IntegrityCheckLevel.None, true); save = new SaveDataFileSystem(Keyset, nax0.BaseStorage, IntegrityCheckLevel.None, true);
} }
catch (Exception ex) catch (Exception ex)
{ {

View file

@ -103,9 +103,9 @@ namespace NandReader
private static List<Ticket> ReadTickets(Keyset keyset, Stream savefile) private static List<Ticket> ReadTickets(Keyset keyset, Stream savefile)
{ {
var tickets = new List<Ticket>(); var tickets = new List<Ticket>();
var save = new SaveData(keyset, savefile.AsStorage(), IntegrityCheckLevel.None, true); var save = new SaveDataFileSystem(keyset, savefile.AsStorage(), IntegrityCheckLevel.None, true);
var ticketList = new BinaryReader(save.OpenFile("/ticket_list.bin").AsStream()); var ticketList = new BinaryReader(save.OpenFile("/ticket_list.bin", OpenMode.Read).AsStream());
var ticketFile = new BinaryReader(save.OpenFile("/ticket.bin").AsStream()); var ticketFile = new BinaryReader(save.OpenFile("/ticket.bin", OpenMode.Read).AsStream());
ulong titleId = ticketList.ReadUInt64(); ulong titleId = ticketList.ReadUInt64();
while (titleId != ulong.MaxValue) while (titleId != ulong.MaxValue)

View file

@ -87,9 +87,9 @@ namespace NandReaderGui.ViewModel
private static List<Ticket> ReadTickets(Keyset keyset, Stream savefile) private static List<Ticket> ReadTickets(Keyset keyset, Stream savefile)
{ {
var tickets = new List<Ticket>(); var tickets = new List<Ticket>();
var save = new SaveData(keyset, savefile.AsStorage(), IntegrityCheckLevel.None, true); var save = new SaveDataFileSystem(keyset, savefile.AsStorage(), IntegrityCheckLevel.None, true);
var ticketList = new BinaryReader(save.OpenFile("/ticket_list.bin").AsStream()); var ticketList = new BinaryReader(save.OpenFile("/ticket_list.bin", OpenMode.Read).AsStream());
var ticketFile = new BinaryReader(save.OpenFile("/ticket.bin").AsStream()); var ticketFile = new BinaryReader(save.OpenFile("/ticket.bin", OpenMode.Read).AsStream());
ulong titleId = ticketList.ReadUInt64(); ulong titleId = ticketList.ReadUInt64();
while (titleId != ulong.MaxValue) while (titleId != ulong.MaxValue)

View file

@ -14,7 +14,7 @@ namespace hactoolnet
{ {
using (var file = new FileStream(ctx.Options.InFile, FileMode.Open, FileAccess.ReadWrite)) using (var file = new FileStream(ctx.Options.InFile, FileMode.Open, FileAccess.ReadWrite))
{ {
var save = new SaveData(ctx.Keyset, file.AsStorage(), ctx.Options.IntegrityLevel, true); var save = new SaveDataFileSystem(ctx.Keyset, file.AsStorage(), ctx.Options.IntegrityLevel, true);
if (ctx.Options.Validate) if (ctx.Options.Validate)
{ {
@ -23,7 +23,7 @@ namespace hactoolnet
if (ctx.Options.OutDir != null) if (ctx.Options.OutDir != null)
{ {
save.Extract(ctx.Options.OutDir, ctx.Logger); save.SaveDataFileSystemCore.Extract(ctx.Options.OutDir, ctx.Logger);
} }
if (ctx.Options.DebugOutDir != null) if (ctx.Options.DebugOutDir != null)
@ -99,13 +99,13 @@ namespace hactoolnet
string destFilename = ctx.Options.ReplaceFileDest; string destFilename = ctx.Options.ReplaceFileDest;
if (!destFilename.StartsWith("/")) destFilename = '/' + destFilename; if (!destFilename.StartsWith("/")) destFilename = '/' + destFilename;
using (IStorage inFile = new FileStream(ctx.Options.ReplaceFileSource, FileMode.Open, FileAccess.Read).AsStorage(false)) using (IFile inFile = new StorageFile(new FileStream(ctx.Options.ReplaceFileSource, FileMode.Open, FileAccess.Read).AsStorage(false), OpenMode.ReadWrite))
{ {
using (IStorage outFile = save.OpenFile(destFilename)) using (IFile outFile = save.OpenFile(destFilename, OpenMode.ReadWrite))
{ {
if (inFile.Length != outFile.Length) if (inFile.GetSize() != outFile.GetSize())
{ {
ctx.Logger.LogMessage($"Replacement file must be the same size as the original file. ({outFile.Length} bytes)"); ctx.Logger.LogMessage($"Replacement file must be the same size as the original file. ({outFile.GetSize()} bytes)");
return; return;
} }
@ -115,7 +115,7 @@ namespace hactoolnet
} }
} }
if (save.CommitHeader(ctx.Keyset)) if (save.Commit(ctx.Keyset))
{ {
ctx.Logger.LogMessage("Successfully signed save file"); ctx.Logger.LogMessage("Successfully signed save file");
} }
@ -129,7 +129,7 @@ namespace hactoolnet
if (ctx.Options.SignSave) if (ctx.Options.SignSave)
{ {
if (save.CommitHeader(ctx.Keyset)) if (save.Commit(ctx.Keyset))
{ {
ctx.Logger.LogMessage("Successfully signed save file"); ctx.Logger.LogMessage("Successfully signed save file");
} }
@ -143,9 +143,11 @@ namespace hactoolnet
if (ctx.Options.ListFiles) if (ctx.Options.ListFiles)
{ {
foreach (FileEntry fileEntry in save.Files) IDirectory dir = save.SaveDataFileSystemCore.OpenDirectory("/", OpenDirectoryMode.All);
foreach (DirectoryEntry entry in dir.EnumerateEntries())
{ {
ctx.Logger.LogMessage(fileEntry.FullPath); ctx.Logger.LogMessage(entry.FullPath);
} }
} }
@ -153,7 +155,7 @@ namespace hactoolnet
} }
} }
private static string Print(this SaveData save) private static string Print(this SaveDataFileSystem save)
{ {
int colLen = 25; int colLen = 25;
var sb = new StringBuilder(); var sb = new StringBuilder();
@ -170,7 +172,7 @@ namespace hactoolnet
PrintItem(sb, colLen, "Save Data Size:", $"0x{save.Header.ExtraData.DataSize:x16} ({Util.GetBytesReadable(save.Header.ExtraData.DataSize)})"); PrintItem(sb, colLen, "Save Data Size:", $"0x{save.Header.ExtraData.DataSize:x16} ({Util.GetBytesReadable(save.Header.ExtraData.DataSize)})");
PrintItem(sb, colLen, "Journal Size:", $"0x{save.Header.ExtraData.JournalSize:x16} ({Util.GetBytesReadable(save.Header.ExtraData.JournalSize)})"); PrintItem(sb, colLen, "Journal Size:", $"0x{save.Header.ExtraData.JournalSize:x16} ({Util.GetBytesReadable(save.Header.ExtraData.JournalSize)})");
PrintItem(sb, colLen, $"Header Hash{save.Header.HeaderHashValidity.GetValidityString()}:", save.Header.Layout.Hash); PrintItem(sb, colLen, $"Header Hash{save.Header.HeaderHashValidity.GetValidityString()}:", save.Header.Layout.Hash);
PrintItem(sb, colLen, "Number of Files:", save.Files.Length); PrintItem(sb, colLen, "Number of Files:", save.GetEntryCount(OpenDirectoryMode.Files));
PrintIvfcHash(sb, colLen, 4, save.Header.Ivfc, IntegrityStorageType.Save); PrintIvfcHash(sb, colLen, 4, save.Header.Ivfc, IntegrityStorageType.Save);

View file

@ -286,7 +286,7 @@ namespace hactoolnet
private static void ExportSdSaves(Context ctx, SwitchFs switchFs) private static void ExportSdSaves(Context ctx, SwitchFs switchFs)
{ {
foreach (KeyValuePair<string, SaveData> save in switchFs.Saves) foreach (KeyValuePair<string, SaveDataFileSystem> save in switchFs.Saves)
{ {
string outDir = Path.Combine(ctx.Options.SaveOutDir, save.Key); string outDir = Path.Combine(ctx.Options.SaveOutDir, save.Key);
save.Value.Extract(outDir, ctx.Logger); save.Value.Extract(outDir, ctx.Logger);