diff --git a/src/LibHac/IO/DirectoryEntry.cs b/src/LibHac/IO/DirectoryEntry.cs new file mode 100644 index 00000000..591e9118 --- /dev/null +++ b/src/LibHac/IO/DirectoryEntry.cs @@ -0,0 +1,22 @@ +namespace LibHac.IO +{ + public class DirectoryEntry + { + public string Name { get; } + public DirectoryEntryType Type { get; } + public long Size { get; } + + public DirectoryEntry(string name, DirectoryEntryType type, long size) + { + Name = name; + Type = type; + Size = size; + } + } + + public enum DirectoryEntryType + { + Directory, + File + } +} diff --git a/src/LibHac/IO/HierarchicalRomFileTable.cs b/src/LibHac/IO/HierarchicalRomFileTable.cs new file mode 100644 index 00000000..ca6b77f2 --- /dev/null +++ b/src/LibHac/IO/HierarchicalRomFileTable.cs @@ -0,0 +1,7 @@ +namespace LibHac.IO +{ + public class HierarchicalRomFileTable + { + + } +} diff --git a/src/LibHac/IO/IDirectory.cs b/src/LibHac/IO/IDirectory.cs new file mode 100644 index 00000000..a26793d6 --- /dev/null +++ b/src/LibHac/IO/IDirectory.cs @@ -0,0 +1,10 @@ +namespace LibHac.IO +{ + public interface IDirectory + { + IFileSystem ParentFileSystem { get; } + + DirectoryEntry[] Read(); + int GetEntryCount(); + } +} \ No newline at end of file diff --git a/src/LibHac/IO/IFile.cs b/src/LibHac/IO/IFile.cs new file mode 100644 index 00000000..70b5212c --- /dev/null +++ b/src/LibHac/IO/IFile.cs @@ -0,0 +1,13 @@ +using System; + +namespace LibHac.IO +{ + public interface IFile + { + void Read(Span destination, long offset); + void Write(ReadOnlySpan source, long offset); + void Flush(); + long GetSize(); + long SetSize(); + } +} \ No newline at end of file diff --git a/src/LibHac/IO/IFileSystem.cs b/src/LibHac/IO/IFileSystem.cs new file mode 100644 index 00000000..9386f443 --- /dev/null +++ b/src/LibHac/IO/IFileSystem.cs @@ -0,0 +1,17 @@ +namespace LibHac.IO +{ + public interface IFileSystem + { + void Commit(); + void CreateDirectory(string path); + void CreateFile(string path, long size); + void DeleteDirectory(string path); + void DeleteFile(string path); + IDirectory OpenDirectory(string path); + IFile OpenFile(string path); + void RenameDirectory(string srcPath, string dstPath); + void RenameFile(string srcPath, string dstPath); + bool DirectoryExists(string path); + bool FileExists(string path); + } +} \ No newline at end of file diff --git a/src/LibHac/IO/IFileSystemExtensions.cs b/src/LibHac/IO/IFileSystemExtensions.cs new file mode 100644 index 00000000..7b7b213c --- /dev/null +++ b/src/LibHac/IO/IFileSystemExtensions.cs @@ -0,0 +1,67 @@ +using System; +using System.Collections.Generic; +using System.IO; + +namespace LibHac.IO +{ + public static class IFileSystemExtensions + { + public static void Extract(this IFileSystem fs, string outDir) + { + var root = fs.OpenDirectory("/"); + + foreach (var filename in root.EnumerateFiles()) + { + //Console.WriteLine(filename); + IFile file = fs.OpenFile(filename); + string outPath = Path.Combine(outDir, filename.TrimStart('/')); + + Directory.CreateDirectory(Path.GetDirectoryName(outPath)); + + using (var outFile = new FileStream(outPath, FileMode.Create, FileAccess.ReadWrite)) + { + file.CopyTo(outFile); + } + } + } + + public static IEnumerable EnumerateFiles(this IDirectory directory) + { + var entries = directory.Read(); + + foreach (var entry in entries) + { + if (entry.Type == DirectoryEntryType.Directory) + { + foreach(string a in EnumerateFiles(directory.ParentFileSystem.OpenDirectory(entry.Name))) + { + yield return a; + } + } + + if (entry.Type == DirectoryEntryType.File) + { + yield return entry.Name; + } + } + } + + public static void CopyTo(this IFile file, Stream output) + { + const int bufferSize = 0x8000; + long remaining = file.GetSize(); + long inOffset = 0; + var buffer = new byte[bufferSize]; + + while (remaining > 0) + { + int toWrite = (int)Math.Min(buffer.Length, remaining); + file.Read(buffer.AsSpan(0, toWrite), inOffset); + + output.Write(buffer, 0, toWrite); + remaining -= toWrite; + inOffset += toWrite; + } + } + } +} diff --git a/src/LibHac/IO/RomFsDirectory.cs b/src/LibHac/IO/RomFsDirectory.cs new file mode 100644 index 00000000..af3241eb --- /dev/null +++ b/src/LibHac/IO/RomFsDirectory.cs @@ -0,0 +1,72 @@ +using System.IO; + +namespace LibHac.IO +{ + public class RomFsDirectory : IDirectory + { + public IFileSystem ParentFileSystem { get; } + + private RomfsDir Directory { get; } + + public RomFsDirectory(RomFsFileSystem fs, string path) + { + if (!fs.DirectoryDict.TryGetValue(path, out RomfsDir dir)) + { + throw new DirectoryNotFoundException(path); + } + + ParentFileSystem = fs; + Directory = dir; + } + + public DirectoryEntry[] Read() + { + int count = GetEntryCount(); + + var entries = new DirectoryEntry[count]; + int index = 0; + + var dirEntry = Directory.FirstChild; + + while (dirEntry != null) + { + entries[index] = new DirectoryEntry(dirEntry.FullPath, DirectoryEntryType.Directory, 0); + dirEntry = dirEntry.NextSibling; + index++; + } + + RomfsFile fileEntry = Directory.FirstFile; + + while (fileEntry != null) + { + entries[index] = new DirectoryEntry(fileEntry.FullPath, DirectoryEntryType.File, fileEntry.DataLength); + fileEntry = fileEntry.NextSibling; + index++; + } + + return entries; + } + + public int GetEntryCount() + { + int count = 0; + RomfsDir dirEntry = Directory.FirstChild; + + while (dirEntry != null) + { + count++; + dirEntry = dirEntry.NextSibling; + } + + RomfsFile fileEntry = Directory.FirstFile; + + while (fileEntry != null) + { + count++; + fileEntry = fileEntry.NextSibling; + } + + return count; + } + } +} diff --git a/src/LibHac/IO/RomFsFile.cs b/src/LibHac/IO/RomFsFile.cs new file mode 100644 index 00000000..defff2c3 --- /dev/null +++ b/src/LibHac/IO/RomFsFile.cs @@ -0,0 +1,45 @@ +using System; + +namespace LibHac.IO +{ + public class RomFsFile : IFile + { + private IStorage BaseStorage { get; } + private long Offset { get; } + private long Size { get; } + + public RomFsFile(IStorage baseStorage, long offset, long size) + { + BaseStorage = baseStorage; + Offset = offset; + Size = size; + } + + public void Read(Span destination, long offset) + { + long storageOffset = Offset + offset; + + BaseStorage.Read(destination, storageOffset); + } + + public void Write(ReadOnlySpan source, long offset) + { + throw new NotImplementedException(); + } + + public void Flush() + { + throw new NotImplementedException(); + } + + public long GetSize() + { + return Size; + } + + public long SetSize() + { + throw new NotImplementedException(); + } + } +} diff --git a/src/LibHac/IO/RomFsFileSystem.cs b/src/LibHac/IO/RomFsFileSystem.cs new file mode 100644 index 00000000..f9506fda --- /dev/null +++ b/src/LibHac/IO/RomFsFileSystem.cs @@ -0,0 +1,152 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; + +namespace LibHac.IO +{ + public class RomFsFileSystem : IFileSystem + { + public RomfsHeader Header { get; } + public List Directories { get; } = new List(); + public List Files { get; } = new List(); + public RomfsDir RootDir { get; } + + public Dictionary FileDict { get; } + public Dictionary DirectoryDict { get; } + private IStorage BaseStorage { get; } + + public RomFsFileSystem(IStorage storage) + { + BaseStorage = storage; + + byte[] dirMetaTable; + byte[] fileMetaTable; + using (var reader = new BinaryReader(BaseStorage.AsStream(), Encoding.Default, true)) + { + Header = new RomfsHeader(reader); + reader.BaseStream.Position = Header.DirMetaTableOffset; + dirMetaTable = reader.ReadBytes((int)Header.DirMetaTableSize); + reader.BaseStream.Position = Header.FileMetaTableOffset; + fileMetaTable = reader.ReadBytes((int)Header.FileMetaTableSize); + } + + using (var reader = new BinaryReader(new MemoryStream(dirMetaTable))) + { + int position = 0; + while (position + 20 < Header.DirMetaTableSize) + { + var dir = new RomfsDir(reader) { Offset = position }; + Directories.Add(dir); + if (dir.ParentDirOffset == position) RootDir = dir; + position = (int)reader.BaseStream.Position; + } + } + + using (var reader = new BinaryReader(new MemoryStream(fileMetaTable))) + { + int position = 0; + while (position + 20 < Header.FileMetaTableSize) + { + var file = new RomfsFile(reader) { Offset = position }; + Files.Add(file); + position = (int)reader.BaseStream.Position; + } + } + + SetReferences(); + RomfsEntry.ResolveFilenames(Files); + RomfsEntry.ResolveFilenames(Directories); + FileDict = Files.ToDictionary(x => x.FullPath, x => x); + DirectoryDict = Directories.ToDictionary(x => x.FullPath, x => x); + } + + private void SetReferences() + { + Dictionary dirDict = Directories.ToDictionary(x => x.Offset, x => x); + Dictionary fileDict = Files.ToDictionary(x => x.Offset, x => x); + + foreach (RomfsDir dir in Directories) + { + if (dir.ParentDirOffset >= 0 && dir.ParentDirOffset != dir.Offset) dir.ParentDir = dirDict[dir.ParentDirOffset]; + if (dir.NextSiblingOffset >= 0) dir.NextSibling = dirDict[dir.NextSiblingOffset]; + if (dir.FirstChildOffset >= 0) dir.FirstChild = dirDict[dir.FirstChildOffset]; + if (dir.FirstFileOffset >= 0) dir.FirstFile = fileDict[dir.FirstFileOffset]; + if (dir.NextDirHashOffset >= 0) dir.NextDirHash = dirDict[dir.NextDirHashOffset]; + } + + foreach (RomfsFile file in Files) + { + if (file.ParentDirOffset >= 0) file.ParentDir = dirDict[file.ParentDirOffset]; + if (file.NextSiblingOffset >= 0) file.NextSibling = fileDict[file.NextSiblingOffset]; + if (file.NextFileHashOffset >= 0) file.NextFileHash = fileDict[file.NextFileHashOffset]; + } + } + + public void Commit() + { + throw new NotImplementedException(); + } + + public void CreateDirectory(string path) + { + throw new NotImplementedException(); + } + + public void CreateFile(string path, long size) + { + throw new NotImplementedException(); + } + + public void DeleteDirectory(string path) + { + throw new NotImplementedException(); + } + + public void DeleteFile(string path) + { + throw new NotImplementedException(); + } + + public IDirectory OpenDirectory(string path) + { + return new RomFsDirectory(this, path); + } + + public IFile OpenFile(string path) + { + if (!FileDict.TryGetValue(path, out RomfsFile file)) + { + throw new FileNotFoundException(); + } + + return OpenFile(file); + } + + public IFile OpenFile(RomfsFile file) + { + return new RomFsFile(BaseStorage, Header.DataOffset + file.DataOffset, file.DataLength); + } + + public void RenameDirectory(string srcPath, string dstPath) + { + throw new NotImplementedException(); + } + + public void RenameFile(string srcPath, string dstPath) + { + throw new NotImplementedException(); + } + + public bool DirectoryExists(string path) + { + throw new NotImplementedException(); + } + + public bool FileExists(string path) + { + return FileDict.ContainsKey(path); + } + } +} diff --git a/src/LibHac/Nca.cs b/src/LibHac/Nca.cs index 1121ee51..556c73bf 100644 --- a/src/LibHac/Nca.cs +++ b/src/LibHac/Nca.cs @@ -255,6 +255,15 @@ namespace LibHac return new HierarchicalIntegrityVerificationStorage(initInfo, integrityCheckLevel, leaveOpen); } + public IFileSystem OpenSectionFileSystem(int index) + { + IStorage storage = OpenSection(index, false, IntegrityCheckLevel.ErrorOnInvalid, true); + + var fs = new RomFsFileSystem(storage); + + return fs; + } + /// /// Sets a base to use when reading patches. /// diff --git a/src/LibHac/RomfsEntry.cs b/src/LibHac/RomfsEntry.cs index 1fa90653..1fef1bb9 100644 --- a/src/LibHac/RomfsEntry.cs +++ b/src/LibHac/RomfsEntry.cs @@ -31,6 +31,9 @@ namespace LibHac dir = dir.ParentDir; } + //todo + if (list.Count == 1) list.Add("/"); + for (int i = list.Count - 1; i >= 0; i--) { sb.Append(list[i]);