diff --git a/src/LibHac/IO/FileSystemExtensions.cs b/src/LibHac/IO/FileSystemExtensions.cs index 99ee1452..0f35b999 100644 --- a/src/LibHac/IO/FileSystemExtensions.cs +++ b/src/LibHac/IO/FileSystemExtensions.cs @@ -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; + } } } diff --git a/src/LibHac/IO/IDirectory.cs b/src/LibHac/IO/IDirectory.cs index a75a6a84..b9a82673 100644 --- a/src/LibHac/IO/IDirectory.cs +++ b/src/LibHac/IO/IDirectory.cs @@ -6,6 +6,7 @@ namespace LibHac.IO { IFileSystem ParentFileSystem { get; } string FullPath { get; } + OpenDirectoryMode Mode { get; } IEnumerable Read(); int GetEntryCount(); diff --git a/src/LibHac/IO/LocalDirectory.cs b/src/LibHac/IO/LocalDirectory.cs index c2686b71..8b2455c1 100644 --- a/src/LibHac/IO/LocalDirectory.cs +++ b/src/LibHac/IO/LocalDirectory.cs @@ -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) diff --git a/src/LibHac/IO/NxFileStream.cs b/src/LibHac/IO/NxFileStream.cs new file mode 100644 index 00000000..b361fc18 --- /dev/null +++ b/src/LibHac/IO/NxFileStream.cs @@ -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); + } + } +} diff --git a/src/LibHac/IO/PartitionDirectory.cs b/src/LibHac/IO/PartitionDirectory.cs index 54214b7d..45240391 100644 --- a/src/LibHac/IO/PartitionDirectory.cs +++ b/src/LibHac/IO/PartitionDirectory.cs @@ -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) { diff --git a/src/LibHac/IO/RomFsDirectory.cs b/src/LibHac/IO/RomFsDirectory.cs index 3355197c..4fdc5d2a 100644 --- a/src/LibHac/IO/RomFsDirectory.cs +++ b/src/LibHac/IO/RomFsDirectory.cs @@ -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) { diff --git a/src/LibHac/IO/Save/Header.cs b/src/LibHac/IO/Save/Header.cs index acc7e3db..a90bdf3e 100644 --- a/src/LibHac/IO/Save/Header.cs +++ b/src/LibHac/IO/Save/Header.cs @@ -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); diff --git a/src/LibHac/IO/Save/SaveDataDirectory.cs b/src/LibHac/IO/Save/SaveDataDirectory.cs new file mode 100644 index 00000000..fccb58ff --- /dev/null +++ b/src/LibHac/IO/Save/SaveDataDirectory.cs @@ -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 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; + } + } +} diff --git a/src/LibHac/IO/Save/SaveDataFile.cs b/src/LibHac/IO/Save/SaveDataFile.cs new file mode 100644 index 00000000..bf852858 --- /dev/null +++ b/src/LibHac/IO/Save/SaveDataFile.cs @@ -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 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 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(); + } + } +} diff --git a/src/LibHac/IO/Save/SaveData.cs b/src/LibHac/IO/Save/SaveDataFileSystem.cs similarity index 76% rename from src/LibHac/IO/Save/SaveData.cs rename to src/LibHac/IO/Save/SaveDataFileSystem.cs index 08971e80..025c13d3 100644 --- a/src/LibHac/IO/Save/SaveData.cs +++ b/src/LibHac/IO/Save/SaveDataFileSystem.cs @@ -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); - } - } - } } } diff --git a/src/LibHac/IO/Save/SaveDataFileSystemCore.cs b/src/LibHac/IO/Save/SaveDataFileSystemCore.cs index f56b33b8..f5794113 100644 --- a/src/LibHac/IO/Save/SaveDataFileSystemCore.cs +++ b/src/LibHac/IO/Save/SaveDataFileSystemCore.cs @@ -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 FileDictionary { get; } + public SaveDirectoryEntry RootDirectory { get; private set; } + private SaveFileEntry[] Files { get; set; } + private SaveDirectoryEntry[] Directories { get; set; } + private Dictionary FileDictionary { get; } + private Dictionary DirDictionary { get; } public SaveDataFileSystemCore(IStorage storage, IStorage allocationTable, IStorage header) { @@ -25,18 +26,23 @@ namespace LibHac.IO.Save Header = new SaveHeader(HeaderStorage); ReadFileInfo(); - var dictionary = new Dictionary(); - foreach (FileEntry entry in Files) + + FileDictionary = new Dictionary(); + foreach (SaveFileEntry entry in Files) { - dictionary[entry.FullPath] = entry; + FileDictionary[entry.FullPath] = entry; } - FileDictionary = dictionary; + DirDictionary = new Dictionary(); + 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(); + SaveFileEntry fileChain = fileEntries[1].NextInChain; + var files = new List(); while (fileChain != null) { files.Add(fileChain); fileChain = fileChain.NextInChain; } - DirectoryEntry dirChain = dirEntries[1].NextInChain; - var dirs = new List(); + SaveDirectoryEntry dirChain = dirEntries[1].NextInChain; + var dirs = new List(); 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; diff --git a/src/LibHac/IO/Save/FsEntry.cs b/src/LibHac/IO/Save/SaveFsEntry.cs similarity index 70% rename from src/LibHac/IO/Save/FsEntry.cs rename to src/LibHac/IO/Save/SaveFsEntry.cs index 84b09e63..2e8401e3 100644 --- a/src/LibHac/IO/Save/FsEntry.cs +++ b/src/LibHac/IO/Save/SaveFsEntry.cs @@ -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 entries) + internal static void ResolveFilenames(IEnumerable entries) { var list = new List(); 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(); diff --git a/src/LibHac/IO/StorageFile.cs b/src/LibHac/IO/StorageFile.cs new file mode 100644 index 00000000..24fab335 --- /dev/null +++ b/src/LibHac/IO/StorageFile.cs @@ -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 destination, long offset) + { + int toRead = ValidateReadParamsAndGetSize(destination, offset); + + BaseStorage.Read(destination.Slice(0, toRead), offset); + + return toRead; + } + + public override void Write(ReadOnlySpan 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(); + } + } +} diff --git a/src/LibHac/SwitchFs.cs b/src/LibHac/SwitchFs.cs index e7fb1d69..fb75b7f9 100644 --- a/src/LibHac/SwitchFs.cs +++ b/src/LibHac/SwitchFs.cs @@ -17,7 +17,7 @@ namespace LibHac public string SaveDir { get; } public Dictionary Ncas { get; } = new Dictionary(StringComparer.OrdinalIgnoreCase); - public Dictionary Saves { get; } = new Dictionary(StringComparer.OrdinalIgnoreCase); + public Dictionary Saves { get; } = new Dictionary(StringComparer.OrdinalIgnoreCase); public Dictionary Titles { get; } = new Dictionary(); public Dictionary Applications { get; } = new Dictionary(); @@ -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) { diff --git a/src/NandReader/Program.cs b/src/NandReader/Program.cs index e41eb999..3bdcb7e3 100644 --- a/src/NandReader/Program.cs +++ b/src/NandReader/Program.cs @@ -103,9 +103,9 @@ namespace NandReader private static List ReadTickets(Keyset keyset, Stream savefile) { var tickets = new List(); - 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) diff --git a/src/NandReaderGui/ViewModel/NandViewModel.cs b/src/NandReaderGui/ViewModel/NandViewModel.cs index 9068a8cd..1bf7dfb2 100644 --- a/src/NandReaderGui/ViewModel/NandViewModel.cs +++ b/src/NandReaderGui/ViewModel/NandViewModel.cs @@ -87,9 +87,9 @@ namespace NandReaderGui.ViewModel private static List ReadTickets(Keyset keyset, Stream savefile) { var tickets = new List(); - 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) diff --git a/src/hactoolnet/ProcessSave.cs b/src/hactoolnet/ProcessSave.cs index 1fab9fe9..7910726a 100644 --- a/src/hactoolnet/ProcessSave.cs +++ b/src/hactoolnet/ProcessSave.cs @@ -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); diff --git a/src/hactoolnet/ProcessSwitchFs.cs b/src/hactoolnet/ProcessSwitchFs.cs index e955067f..ab2eaf10 100644 --- a/src/hactoolnet/ProcessSwitchFs.cs +++ b/src/hactoolnet/ProcessSwitchFs.cs @@ -286,7 +286,7 @@ namespace hactoolnet private static void ExportSdSaves(Context ctx, SwitchFs switchFs) { - foreach (KeyValuePair save in switchFs.Saves) + foreach (KeyValuePair save in switchFs.Saves) { string outDir = Path.Combine(ctx.Options.SaveOutDir, save.Key); save.Value.Extract(outDir, ctx.Logger);