mirror of
https://github.com/Thealexbarney/LibHac.git
synced 2024-11-14 10:49:41 +01:00
Add SaveDataFileSystem
This commit is contained in:
parent
9f181c7b45
commit
f1f52e7151
18 changed files with 465 additions and 118 deletions
|
@ -1,6 +1,7 @@
|
|||
using System;
|
||||
using System.Buffers;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
|
||||
namespace LibHac.IO
|
||||
{
|
||||
|
@ -96,5 +97,29 @@ namespace LibHac.IO
|
|||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,6 +6,7 @@ namespace LibHac.IO
|
|||
{
|
||||
IFileSystem ParentFileSystem { get; }
|
||||
string FullPath { get; }
|
||||
OpenDirectoryMode Mode { get; }
|
||||
|
||||
IEnumerable<DirectoryEntry> Read();
|
||||
int GetEntryCount();
|
||||
|
|
|
@ -10,7 +10,7 @@ namespace LibHac.IO
|
|||
public string FullPath { get; }
|
||||
|
||||
private string LocalPath { get; }
|
||||
private OpenDirectoryMode Mode { get; }
|
||||
public OpenDirectoryMode Mode { get; }
|
||||
private DirectoryInfo DirInfo { get; }
|
||||
|
||||
public LocalDirectory(LocalFileSystem fs, string path, OpenDirectoryMode mode)
|
||||
|
|
75
src/LibHac/IO/NxFileStream.cs
Normal file
75
src/LibHac/IO/NxFileStream.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -9,7 +9,7 @@ namespace LibHac.IO
|
|||
public PartitionFileSystem ParentFileSystem { get; }
|
||||
public string FullPath { get; }
|
||||
|
||||
private OpenDirectoryMode Mode { get; }
|
||||
public OpenDirectoryMode Mode { get; }
|
||||
|
||||
public PartitionDirectory(PartitionFileSystem fs, string path, OpenDirectoryMode mode)
|
||||
{
|
||||
|
|
|
@ -9,7 +9,7 @@ namespace LibHac.IO
|
|||
public string FullPath { get; }
|
||||
|
||||
private RomfsDir Directory { get; }
|
||||
private OpenDirectoryMode Mode { get; }
|
||||
public OpenDirectoryMode Mode { get; }
|
||||
|
||||
public RomFsDirectory(RomFsFileSystem fs, string path, OpenDirectoryMode mode)
|
||||
{
|
||||
|
|
|
@ -35,7 +35,7 @@ namespace LibHac.IO.Save
|
|||
|
||||
public byte[] Data { get; }
|
||||
|
||||
public Header(Keyset keyset, IStorage storage)
|
||||
public Header(IStorage storage, Keyset keyset)
|
||||
{
|
||||
MainStorage = storage;
|
||||
MainHeader = MainStorage.Slice(0x100, 0x200);
|
||||
|
|
75
src/LibHac/IO/Save/SaveDataDirectory.cs
Normal file
75
src/LibHac/IO/Save/SaveDataDirectory.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
51
src/LibHac/IO/Save/SaveDataFile.cs
Normal file
51
src/LibHac/IO/Save/SaveDataFile.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,10 +1,9 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
|
||||
namespace LibHac.IO.Save
|
||||
{
|
||||
public class SaveData : IDisposable
|
||||
public class SaveDataFileSystem : IFileSystem
|
||||
{
|
||||
public Header Header { get; }
|
||||
public IStorage BaseStorage { get; }
|
||||
|
@ -19,16 +18,15 @@ namespace LibHac.IO.Save
|
|||
public HierarchicalDuplexStorage DuplexStorage { get; }
|
||||
public JournalStorage JournalStorage { get; }
|
||||
|
||||
public DirectoryEntry RootDirectory => SaveDataFileSystemCore.RootDirectory;
|
||||
public FileEntry[] Files => SaveDataFileSystemCore.Files;
|
||||
public DirectoryEntry[] Directories => SaveDataFileSystemCore.Directories;
|
||||
private Keyset Keyset { get; }
|
||||
|
||||
public SaveData(Keyset keyset, IStorage storage, IntegrityCheckLevel integrityCheckLevel, bool leaveOpen)
|
||||
public SaveDataFileSystem(Keyset keyset, IStorage storage, IntegrityCheckLevel integrityCheckLevel, bool leaveOpen)
|
||||
{
|
||||
BaseStorage = storage;
|
||||
LeaveOpen = leaveOpen;
|
||||
Keyset = keyset;
|
||||
|
||||
Header = new Header(keyset, BaseStorage);
|
||||
Header = new Header(BaseStorage, keyset);
|
||||
FsLayout layout = Header.Layout;
|
||||
|
||||
IStorage dataRemapBase = BaseStorage.Slice(layout.FileMapDataOffset, layout.FileMapDataSize);
|
||||
|
@ -119,21 +117,56 @@ namespace LibHac.IO.Save
|
|||
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 CommitHeader(Keyset keyset)
|
||||
public void Commit()
|
||||
{
|
||||
Commit(Keyset);
|
||||
}
|
||||
|
||||
public bool Commit(Keyset keyset)
|
||||
{
|
||||
// todo
|
||||
Stream headerStream = BaseStorage.AsStream();
|
||||
|
||||
var hashData = new byte[0x3d00];
|
||||
|
@ -145,7 +178,7 @@ namespace LibHac.IO.Save
|
|||
headerStream.Position = 0x108;
|
||||
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 cmac = new byte[0x10];
|
||||
|
@ -169,39 +202,5 @@ namespace LibHac.IO.Save
|
|||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -3,7 +3,7 @@ using System.IO;
|
|||
|
||||
namespace LibHac.IO.Save
|
||||
{
|
||||
public class SaveDataFileSystemCore
|
||||
public class SaveDataFileSystemCore : IFileSystem
|
||||
{
|
||||
private IStorage BaseStorage { get; }
|
||||
private IStorage HeaderStorage { get; }
|
||||
|
@ -11,10 +11,11 @@ namespace LibHac.IO.Save
|
|||
public AllocationTable AllocationTable { get; }
|
||||
private SaveHeader Header { get; }
|
||||
|
||||
public DirectoryEntry RootDirectory { get; private set; }
|
||||
public FileEntry[] Files { get; private set; }
|
||||
public DirectoryEntry[] Directories { get; private set; }
|
||||
public Dictionary<string, FileEntry> FileDictionary { get; }
|
||||
public SaveDirectoryEntry RootDirectory { get; private set; }
|
||||
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)
|
||||
{
|
||||
|
@ -25,18 +26,23 @@ namespace LibHac.IO.Save
|
|||
Header = new SaveHeader(HeaderStorage);
|
||||
|
||||
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)
|
||||
{
|
||||
if (!FileDictionary.TryGetValue(filename, out FileEntry file))
|
||||
if (!FileDictionary.TryGetValue(filename, out SaveFileEntry file))
|
||||
{
|
||||
throw new FileNotFoundException();
|
||||
}
|
||||
|
@ -44,7 +50,7 @@ namespace LibHac.IO.Save
|
|||
return OpenFile(file);
|
||||
}
|
||||
|
||||
public IStorage OpenFile(FileEntry file)
|
||||
public IStorage OpenFile(SaveFileEntry file)
|
||||
{
|
||||
if (file.BlockIndex < 0)
|
||||
{
|
||||
|
@ -55,7 +61,76 @@ namespace LibHac.IO.Save
|
|||
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 void Commit()
|
||||
{
|
||||
throw new System.NotImplementedException();
|
||||
}
|
||||
|
||||
public IStorage GetBaseStorage() => BaseStorage.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 fileTableStream = OpenFatBlock(AllocationTable.Header.FileTableBlock, 1000000);
|
||||
|
||||
DirectoryEntry[] dirEntries = ReadDirEntries(dirTableStream);
|
||||
FileEntry[] fileEntries = ReadFileEntries(fileTableStream);
|
||||
SaveDirectoryEntry[] dirEntries = ReadDirEntries(dirTableStream);
|
||||
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.FirstChildIndex != 0) dir.FirstChild = dirEntries[dir.FirstChildIndex];
|
||||
|
@ -79,7 +154,7 @@ namespace LibHac.IO.Save
|
|||
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.NextInChainIndex != 0) file.NextInChain = fileEntries[file.NextInChainIndex];
|
||||
|
@ -89,16 +164,16 @@ namespace LibHac.IO.Save
|
|||
|
||||
RootDirectory = dirEntries[2];
|
||||
|
||||
FileEntry fileChain = fileEntries[1].NextInChain;
|
||||
var files = new List<FileEntry>();
|
||||
SaveFileEntry fileChain = fileEntries[1].NextInChain;
|
||||
var files = new List<SaveFileEntry>();
|
||||
while (fileChain != null)
|
||||
{
|
||||
files.Add(fileChain);
|
||||
fileChain = fileChain.NextInChain;
|
||||
}
|
||||
|
||||
DirectoryEntry dirChain = dirEntries[1].NextInChain;
|
||||
var dirs = new List<DirectoryEntry>();
|
||||
SaveDirectoryEntry dirChain = dirEntries[1].NextInChain;
|
||||
var dirs = new List<SaveDirectoryEntry>();
|
||||
while (dirChain != null)
|
||||
{
|
||||
dirs.Add(dirChain);
|
||||
|
@ -108,37 +183,37 @@ namespace LibHac.IO.Save
|
|||
Files = files.ToArray();
|
||||
Directories = dirs.ToArray();
|
||||
|
||||
FsEntry.ResolveFilenames(Files);
|
||||
FsEntry.ResolveFilenames(Directories);
|
||||
SaveFsEntry.ResolveFilenames(Files);
|
||||
SaveFsEntry.ResolveFilenames(Directories);
|
||||
}
|
||||
|
||||
private FileEntry[] ReadFileEntries(IStorage storage)
|
||||
private SaveFileEntry[] ReadFileEntries(IStorage storage)
|
||||
{
|
||||
var reader = new BinaryReader(storage.AsStream());
|
||||
int count = reader.ReadInt32();
|
||||
|
||||
reader.BaseStream.Position -= 4;
|
||||
|
||||
var entries = new FileEntry[count];
|
||||
var entries = new SaveFileEntry[count];
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
entries[i] = new FileEntry(reader);
|
||||
entries[i] = new SaveFileEntry(reader);
|
||||
}
|
||||
|
||||
return entries;
|
||||
}
|
||||
|
||||
private DirectoryEntry[] ReadDirEntries(IStorage storage)
|
||||
private SaveDirectoryEntry[] ReadDirEntries(IStorage storage)
|
||||
{
|
||||
var reader = new BinaryReader(storage.AsStream());
|
||||
int count = reader.ReadInt32();
|
||||
|
||||
reader.BaseStream.Position -= 4;
|
||||
|
||||
var entries = new DirectoryEntry[count];
|
||||
var entries = new SaveDirectoryEntry[count];
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
entries[i] = new DirectoryEntry(reader);
|
||||
entries[i] = new SaveDirectoryEntry(reader);
|
||||
}
|
||||
|
||||
return entries;
|
||||
|
|
|
@ -6,23 +6,23 @@ using System.Text;
|
|||
namespace LibHac.IO.Save
|
||||
{
|
||||
[DebuggerDisplay("{" + nameof(FullPath) + "}")]
|
||||
public abstract class FsEntry
|
||||
public abstract class SaveFsEntry
|
||||
{
|
||||
public int ParentDirIndex { get; protected set; }
|
||||
public string Name { get; protected 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 sb = new StringBuilder();
|
||||
string delimiter = "/";
|
||||
foreach (FsEntry file in entries)
|
||||
foreach (SaveFsEntry file in entries)
|
||||
{
|
||||
list.Add(file.Name);
|
||||
DirectoryEntry dir = file.ParentDir;
|
||||
SaveDirectoryEntry dir = file.ParentDir;
|
||||
while (dir != null)
|
||||
{
|
||||
list.Add(delimiter);
|
||||
|
@ -35,14 +35,14 @@ namespace LibHac.IO.Save
|
|||
sb.Append(list[i]);
|
||||
}
|
||||
|
||||
file.FullPath = sb.ToString();
|
||||
file.FullPath = sb.Length == 0 ? delimiter : sb.ToString();
|
||||
list.Clear();
|
||||
sb.Clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class FileEntry : FsEntry
|
||||
public class SaveFileEntry : SaveFsEntry
|
||||
{
|
||||
public int NextSiblingIndex { get; }
|
||||
public int BlockIndex { get; }
|
||||
|
@ -50,10 +50,10 @@ namespace LibHac.IO.Save
|
|||
public long Field54 { get; }
|
||||
public int NextInChainIndex { get; }
|
||||
|
||||
public FileEntry NextSibling { get; internal set; }
|
||||
public FileEntry NextInChain { get; internal set; }
|
||||
public SaveFileEntry NextSibling { get; internal set; }
|
||||
public SaveFileEntry NextInChain { get; internal set; }
|
||||
|
||||
public FileEntry(BinaryReader reader)
|
||||
public SaveFileEntry(BinaryReader reader)
|
||||
{
|
||||
long start = reader.BaseStream.Position;
|
||||
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 FirstChildIndex { get; }
|
||||
|
@ -76,12 +76,12 @@ namespace LibHac.IO.Save
|
|||
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 SaveDirectoryEntry NextSibling { get; internal set; }
|
||||
public SaveDirectoryEntry FirstChild { get; internal set; }
|
||||
public SaveFileEntry FirstFile { get; internal set; }
|
||||
public SaveDirectoryEntry NextInChain { get; internal set; }
|
||||
|
||||
public DirectoryEntry(BinaryReader reader)
|
||||
public SaveDirectoryEntry(BinaryReader reader)
|
||||
{
|
||||
long start = reader.BaseStream.Position;
|
||||
ParentDirIndex = reader.ReadInt32();
|
44
src/LibHac/IO/StorageFile.cs
Normal file
44
src/LibHac/IO/StorageFile.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -17,7 +17,7 @@ namespace LibHac
|
|||
public string SaveDir { get; }
|
||||
|
||||
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, Application> Applications { get; } = new Dictionary<ulong, Application>();
|
||||
|
||||
|
@ -118,7 +118,7 @@ namespace LibHac
|
|||
|
||||
foreach (string file in files)
|
||||
{
|
||||
SaveData save = null;
|
||||
SaveDataFileSystem save = null;
|
||||
string saveName = Path.GetFileNameWithoutExtension(file);
|
||||
|
||||
try
|
||||
|
@ -127,7 +127,7 @@ namespace LibHac
|
|||
|
||||
string sdPath = "/" + Util.GetRelativePath(file, SaveDir).Replace('\\', '/');
|
||||
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)
|
||||
{
|
||||
|
|
|
@ -103,9 +103,9 @@ namespace NandReader
|
|||
private static List<Ticket> ReadTickets(Keyset keyset, Stream savefile)
|
||||
{
|
||||
var tickets = new List<Ticket>();
|
||||
var save = new SaveData(keyset, savefile.AsStorage(), IntegrityCheckLevel.None, true);
|
||||
var ticketList = new BinaryReader(save.OpenFile("/ticket_list.bin").AsStream());
|
||||
var ticketFile = new BinaryReader(save.OpenFile("/ticket.bin").AsStream());
|
||||
var save = new SaveDataFileSystem(keyset, savefile.AsStorage(), IntegrityCheckLevel.None, true);
|
||||
var ticketList = new BinaryReader(save.OpenFile("/ticket_list.bin", OpenMode.Read).AsStream());
|
||||
var ticketFile = new BinaryReader(save.OpenFile("/ticket.bin", OpenMode.Read).AsStream());
|
||||
|
||||
ulong titleId = ticketList.ReadUInt64();
|
||||
while (titleId != ulong.MaxValue)
|
||||
|
|
|
@ -87,9 +87,9 @@ namespace NandReaderGui.ViewModel
|
|||
private static List<Ticket> ReadTickets(Keyset keyset, Stream savefile)
|
||||
{
|
||||
var tickets = new List<Ticket>();
|
||||
var save = new SaveData(keyset, savefile.AsStorage(), IntegrityCheckLevel.None, true);
|
||||
var ticketList = new BinaryReader(save.OpenFile("/ticket_list.bin").AsStream());
|
||||
var ticketFile = new BinaryReader(save.OpenFile("/ticket.bin").AsStream());
|
||||
var save = new SaveDataFileSystem(keyset, savefile.AsStorage(), IntegrityCheckLevel.None, true);
|
||||
var ticketList = new BinaryReader(save.OpenFile("/ticket_list.bin", OpenMode.Read).AsStream());
|
||||
var ticketFile = new BinaryReader(save.OpenFile("/ticket.bin", OpenMode.Read).AsStream());
|
||||
|
||||
ulong titleId = ticketList.ReadUInt64();
|
||||
while (titleId != ulong.MaxValue)
|
||||
|
|
|
@ -14,7 +14,7 @@ namespace hactoolnet
|
|||
{
|
||||
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)
|
||||
{
|
||||
|
@ -23,7 +23,7 @@ namespace hactoolnet
|
|||
|
||||
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)
|
||||
|
@ -99,13 +99,13 @@ namespace hactoolnet
|
|||
string destFilename = ctx.Options.ReplaceFileDest;
|
||||
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;
|
||||
}
|
||||
|
||||
|
@ -115,7 +115,7 @@ namespace hactoolnet
|
|||
}
|
||||
}
|
||||
|
||||
if (save.CommitHeader(ctx.Keyset))
|
||||
if (save.Commit(ctx.Keyset))
|
||||
{
|
||||
ctx.Logger.LogMessage("Successfully signed save file");
|
||||
}
|
||||
|
@ -129,7 +129,7 @@ namespace hactoolnet
|
|||
|
||||
if (ctx.Options.SignSave)
|
||||
{
|
||||
if (save.CommitHeader(ctx.Keyset))
|
||||
if (save.Commit(ctx.Keyset))
|
||||
{
|
||||
ctx.Logger.LogMessage("Successfully signed save file");
|
||||
}
|
||||
|
@ -143,9 +143,11 @@ namespace hactoolnet
|
|||
|
||||
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;
|
||||
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, "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, "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);
|
||||
|
||||
|
|
|
@ -286,7 +286,7 @@ namespace hactoolnet
|
|||
|
||||
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);
|
||||
save.Value.Extract(outDir, ctx.Logger);
|
||||
|
|
Loading…
Reference in a new issue