diff --git a/src/LibHac/IO/FileBase.cs b/src/LibHac/IO/FileBase.cs index ab280fd5..b51260c3 100644 --- a/src/LibHac/IO/FileBase.cs +++ b/src/LibHac/IO/FileBase.cs @@ -79,6 +79,9 @@ namespace LibHac.IO } } + /// + /// Specifies which operations are available on an . + /// [Flags] public enum OpenMode { diff --git a/src/LibHac/IO/IDirectory.cs b/src/LibHac/IO/IDirectory.cs index b9a82673..a22c0ea1 100644 --- a/src/LibHac/IO/IDirectory.cs +++ b/src/LibHac/IO/IDirectory.cs @@ -2,13 +2,38 @@ namespace LibHac.IO { + /// + /// Provides an interface for enumerating the child entries of a directory. + /// public interface IDirectory { + /// + /// The that contains the current . + /// IFileSystem ParentFileSystem { get; } + + /// + /// The full path of the current in its . + /// string FullPath { get; } + + /// + /// Specifies which types of entries will be enumerated when is called. + /// OpenDirectoryMode Mode { get; } + /// + /// Returns an enumerable collection the file system entries of the types specified by + /// that this directory contains. Does not search subdirectories. + /// + /// An enumerable collection of file system entries in this directory. IEnumerable Read(); + + /// + /// Returns the number of file system entries of the types specified by + /// that this directory contains. Does not search subdirectories. + /// + /// The number of child entries the directory contains. int GetEntryCount(); } } \ No newline at end of file diff --git a/src/LibHac/IO/IFile.cs b/src/LibHac/IO/IFile.cs index c6e130dd..4367479f 100644 --- a/src/LibHac/IO/IFile.cs +++ b/src/LibHac/IO/IFile.cs @@ -2,13 +2,65 @@ namespace LibHac.IO { + /// + /// Provides an interface for reading and writing a sequence of bytes. + /// + /// is similar to , and has a few main differences: + /// + /// - allows an to be set that controls read, write + /// and append permissions for the file. + /// + /// - If the cannot read or write as many bytes as requested, it will read + /// or write as many bytes as it can and return that number of bytes to the caller. + /// + /// - If is called on an offset past the end of the , + /// the mode is set and the file supports expansion, + /// the file will be expanded so that it is large enough to contain the written data. public interface IFile : IDisposable { + /// + /// The permissions mode for the current file. + /// OpenMode Mode { get; } + + /// + /// Reads a sequence of bytes from the current . + /// + /// The buffer where the read bytes will be stored. + /// The number of bytes read will be no larger than the length of the buffer. + /// The offset in the at which to begin reading. + /// The total number of bytes read into the buffer. This can be less than the + /// size of the buffer if the IFile is too short to fulfill the request. + /// is invalid. + /// The file's does not allow reading. int Read(Span destination, long offset); + + /// + /// Writes a sequence of bytes to the current . + /// + /// The buffer containing the bytes to be written. + /// The offset in the at which to begin writing. + /// is negative. + /// The file's does not allow this request. void Write(ReadOnlySpan source, long offset); + + /// + /// Causes any buffered data to be written to the underlying device. + /// void Flush(); + + /// + /// Gets the number of bytes in the file. + /// + /// The length of the file in bytes. long GetSize(); + + /// + /// Sets the size of the file in bytes. + /// + /// The desired size of the file in bytes. + /// If increasing the file size, The file's + /// does not allow this appending. void SetSize(long size); } } \ No newline at end of file diff --git a/src/LibHac/IO/IFileSystem.cs b/src/LibHac/IO/IFileSystem.cs index bfedc57c..09879fd6 100644 --- a/src/LibHac/IO/IFileSystem.cs +++ b/src/LibHac/IO/IFileSystem.cs @@ -1,23 +1,116 @@ using System; +using System.IO; namespace LibHac.IO { + /// + /// Provides an interface for accessing a file system. / is used as the path delimiter. + /// public interface IFileSystem { + /// + /// Creates all directories and subdirectories in the specified path unless they already exist. + /// + /// The full path of the directory to create. + /// An I/O error occurred while creating the directory. void CreateDirectory(string path); + + /// + /// Creates or overwrites a file at the specified path. + /// + /// The full path of the file to create. + /// The initial size of the created file. + /// Flags to control how the file is created. + /// Should usually be + /// An I/O error occurred while creating the file. void CreateFile(string path, long size, CreateFileOptions options); + + /// + /// Deletes the specified directory. + /// + /// The full path of the directory to delete. + /// The specified directory does not exist. + /// An I/O error occurred while deleting the directory. void DeleteDirectory(string path); + + /// + /// Deletes the specified file. + /// + /// The full path of the file to delete. + /// The specified file does not exist. + /// An I/O error occurred while deleting the file. void DeleteFile(string path); + + /// + /// Creates an instance for enumerating the specified directory. + /// + /// The directory's full path. + /// Specifies which sub-entries should be enumerated. + /// An instance for the specified directory. + /// The specified directory does not exist. + /// An I/O error occurred while opening the directory. IDirectory OpenDirectory(string path, OpenDirectoryMode mode); + + /// + /// Opens an instance for the specified path. + /// + /// The full path of the file to open. + /// Specifies the access permissions of the created . + /// An instance for the specified path. + /// The specified file does not exist. + /// An I/O error occurred while deleting the file. IFile OpenFile(string path, OpenMode mode); + + /// + /// Renames or moves a directory to a new location. + /// + /// The full path of the directory to rename. + /// The new full path of the directory. + /// The specified directory does not exist. + /// An I/O error occurred while deleting the directory. void RenameDirectory(string srcPath, string dstPath); + + /// + /// Renames or moves a file to a new location. + /// + /// The full path of the file to rename. + /// The new full path of the file. + /// The specified file does not exist. + /// An I/O error occurred while deleting the file. void RenameFile(string srcPath, string dstPath); + + /// + /// Determines whether the specified directory exists. + /// + /// The full path of the directory to check. + /// if the directory exists, otherwise . bool DirectoryExists(string path); + + /// + /// Determines whether the specified file exists. + /// + /// The full path of the file to check. + /// if the file exists, otherwise . bool FileExists(string path); + + /// + /// Determines whether the specified path is a file or directory. + /// + /// The full path to check. + /// The of the file. + /// The specified path does not exist. DirectoryEntryType GetEntryType(string path); + + /// + /// Commits any changes to a transactional file system. + /// Does nothing if called on a non-transactional file system. + /// void Commit(); } + /// + /// Specifies which types of entries are returned when enumerating an . + /// [Flags] public enum OpenDirectoryMode { @@ -26,10 +119,16 @@ namespace LibHac.IO All = Directories | Files } + /// + /// Optional file creation flags. + /// [Flags] public enum CreateFileOptions { None = 0, + /// + /// On a , creates a concatenation file. + /// CreateConcatenationFile = 1 << 0 } } \ No newline at end of file diff --git a/src/LibHac/IO/IStorage.cs b/src/LibHac/IO/IStorage.cs index be5c6d2f..7116b6b7 100644 --- a/src/LibHac/IO/IStorage.cs +++ b/src/LibHac/IO/IStorage.cs @@ -2,6 +2,9 @@ namespace LibHac.IO { + /// + /// Provides an interface for reading and writing a sequence of bytes. + /// public interface IStorage : IDisposable { /// @@ -9,14 +12,17 @@ namespace LibHac.IO /// /// The buffer where the read bytes will be stored. /// The number of bytes read will be equal to the length of the buffer. - /// The offset in the to begin reading from. + /// The offset in the at which to begin reading. + /// Invalid offset or the IStorage contains fewer bytes than requested. void Read(Span destination, long offset); /// /// Writes a sequence of bytes to the current . /// /// The buffer containing the bytes to be written. - /// The offset in the to begin writing to. + /// The offset in the at which to begin writing. + /// Invalid offset or + /// is too large to be written to the IStorage. void Write(ReadOnlySpan source, long offset); /// diff --git a/src/LibHac/IO/LocalFileSystem.cs b/src/LibHac/IO/LocalFileSystem.cs index 3eae17d0..0eabad86 100644 --- a/src/LibHac/IO/LocalFileSystem.cs +++ b/src/LibHac/IO/LocalFileSystem.cs @@ -1,5 +1,4 @@ -using System; -using System.IO; +using System.IO; namespace LibHac.IO { @@ -145,9 +144,6 @@ namespace LibHac.IO throw new FileNotFoundException(path); } - public void Commit() - { - throw new NotImplementedException(); - } + public void Commit() { } } } diff --git a/src/LibHac/IO/PartitionFileSystem.cs b/src/LibHac/IO/PartitionFileSystem.cs index b8792c56..9c79b141 100644 --- a/src/LibHac/IO/PartitionFileSystem.cs +++ b/src/LibHac/IO/PartitionFileSystem.cs @@ -81,7 +81,7 @@ namespace LibHac.IO public void DeleteFile(string path) => throw new NotSupportedException(); public void RenameDirectory(string srcPath, string dstPath) => throw new NotSupportedException(); public void RenameFile(string srcPath, string dstPath) => throw new NotSupportedException(); - public void Commit() => throw new NotSupportedException(); + public void Commit() { } } public enum PartitionFileSystemType diff --git a/src/LibHac/IO/RomFs/HierarchicalRomFileTable.cs b/src/LibHac/IO/RomFs/HierarchicalRomFileTable.cs index 94571480..d27c8e19 100644 --- a/src/LibHac/IO/RomFs/HierarchicalRomFileTable.cs +++ b/src/LibHac/IO/RomFs/HierarchicalRomFileTable.cs @@ -5,11 +5,34 @@ using System.Text; namespace LibHac.IO.RomFs { + /// + /// Represents the file table used by the RomFS format. + /// + /// + /// This file table stores the structure of the file tree in a RomFS. + /// Each file or directory entry is stored in the table using its full path as a key. + /// Once added, a file or directory is assigned an ID that can also be used to retrieve it. + /// Each file entry contains the size of the file and its offset in the RomFS. + /// Each directory entry contains the IDs for its first child file and first child directory. + /// + /// The table is represented by four byte arrays. Two of the arrays contain the hash buckets and + /// entries for the files, and the other two for the directories. + /// + /// Once all files have been added to the table, should be called + /// to optimize the size of the table. + /// public class HierarchicalRomFileTable { private RomFsDictionary FileTable { get; } private RomFsDictionary DirectoryTable { get; } + /// + /// Initializes a from an existing table. + /// + /// + /// + /// + /// public HierarchicalRomFileTable(IStorage dirHashTable, IStorage dirEntryTable, IStorage fileHashTable, IStorage fileEntryTable) { @@ -17,8 +40,18 @@ namespace LibHac.IO.RomFs DirectoryTable = new RomFsDictionary(dirHashTable, dirEntryTable); } + /// + /// Initializes a new that has the default initial capacity. + /// public HierarchicalRomFileTable() : this(0, 0) { } + /// + /// Initializes a new that has the specified initial capacity. + /// + /// The initial number of directories that the + /// can contain. + /// The initial number of files that the + /// can contain. public HierarchicalRomFileTable(int directoryCapacity, int fileCapacity) { FileTable = new RomFsDictionary(fileCapacity); @@ -73,6 +106,13 @@ namespace LibHac.IO.RomFs return false; } + /// + /// Opens a directory for enumeration. + /// + /// The full path of the directory to open. + /// The initial position of the directory enumerator. + /// if the table contains a directory with the specified path; + /// otherwise, . public bool TryOpenDirectory(string path, out FindPosition position) { FindDirectoryRecursive(GetUtf8Bytes(path), out RomEntryKey key); @@ -87,6 +127,13 @@ namespace LibHac.IO.RomFs return false; } + /// + /// Opens a directory for enumeration. + /// + /// The ID of the directory to open. + /// When this method returns, contains the initial position of the directory enumerator. + /// if the table contains a directory with the specified path; + /// otherwise, . public bool TryOpenDirectory(int directoryId, out FindPosition position) { if (DirectoryTable.TryGetValue(directoryId, out RomKeyValuePair keyValuePair)) @@ -99,6 +146,15 @@ namespace LibHac.IO.RomFs return false; } + /// + /// Returns the next file in a directory and updates the enumerator's position. + /// + /// The current position of the directory enumerator. + /// This position will be updated when the method returns. + /// When this method returns, contains the file's metadata. + /// When this method returns, contains the file's name (Not the full path). + /// if the next file was successfully returned. + /// if there are no more files to enumerate. public bool FindNextFile(ref FindPosition position, out RomFileInfo info, out string name) { if (position.NextFile == -1) @@ -117,6 +173,14 @@ namespace LibHac.IO.RomFs return true; } + /// + /// Returns the next child directory in a directory and updates the enumerator's position. + /// + /// The current position of the directory enumerator. + /// This position will be updated when the method returns. + /// When this method returns, contains the directory's name (Not the full path). + /// if the next directory was successfully returned. + /// if there are no more directories to enumerate. public bool FindNextDirectory(ref FindPosition position, out string name) { if (position.NextDirectory == -1) @@ -132,7 +196,13 @@ namespace LibHac.IO.RomFs return true; } - public void CreateFile(string path, ref RomFileInfo fileInfo) + /// + /// Adds a file to the file table. If the file already exists + /// its will be updated. + /// + /// The full path of the file to be added. + /// The file information to be stored. + public void AddFile(string path, ref RomFileInfo fileInfo) { path = PathTools.Normalize(path); ReadOnlySpan pathBytes = GetUtf8Bytes(path); @@ -140,13 +210,25 @@ namespace LibHac.IO.RomFs CreateFileRecursiveInternal(pathBytes, ref fileInfo); } - public void CreateDirectory(string path) + /// + /// Adds a directory to the file table. If the directory already exists, + /// no action is performed. + /// + /// The full path of the directory to be added. + public void AddDirectory(string path) { path = PathTools.Normalize(path); CreateDirectoryRecursive(GetUtf8Bytes(path)); } + /// + /// Sets the capacity of this dictionary to what it would be if + /// it had been originally initialized with all its entries. + /// + /// This method can be used to minimize the memory overhead + /// once it is known that no new elements will be added. + /// public void TrimExcess() { DirectoryTable.TrimExcess(); @@ -263,9 +345,9 @@ namespace LibHac.IO.RomFs } { - ref FileRomEntry entry = ref FileTable.AddOrGet(ref key, out int offset, out _, out _); - entry.NextSibling = -1; + ref FileRomEntry entry = ref FileTable.AddOrGet(ref key, out int offset, out bool alreadyExists, out _); entry.Info = fileInfo; + if (alreadyExists) entry.NextSibling = -1; ref DirectoryRomEntry parent = ref DirectoryTable.GetValueReference(prevOffset); diff --git a/src/LibHac/IO/RomFs/RomFsBuilder.cs b/src/LibHac/IO/RomFs/RomFsBuilder.cs index 3e8bfbe5..accb502c 100644 --- a/src/LibHac/IO/RomFs/RomFsBuilder.cs +++ b/src/LibHac/IO/RomFs/RomFsBuilder.cs @@ -5,6 +5,12 @@ using System.Linq; namespace LibHac.IO.RomFs { + /// + /// Builds a RomFS from a collection of files. + /// + /// A produces a view of a RomFS archive. + /// When doing so, it will create an instance that will + /// provide the RomFS data when read. Random seek is supported. public class RomFsBuilder { private const int FileAlignment = 0x10; @@ -15,8 +21,15 @@ namespace LibHac.IO.RomFs private HierarchicalRomFileTable FileTable { get; } = new HierarchicalRomFileTable(); private long CurrentOffset { get; set; } + /// + /// Creates a new, empty + /// public RomFsBuilder() { } + /// + /// Creates a new and populates it with all + /// the files in the specified . + /// public RomFsBuilder(IFileSystem input) { foreach (DirectoryEntry file in input.EnumerateEntries().Where(x => x.Type == DirectoryEntryType.File) @@ -26,6 +39,11 @@ namespace LibHac.IO.RomFs } } + /// + /// Adds a file to the RomFS. + /// + /// The full path in the RomFS + /// An of the file data to add. public void AddFile(string path, IFile file) { var fileInfo = new RomFileInfo(); @@ -43,9 +61,15 @@ namespace LibHac.IO.RomFs var padding = new NullStorage(CurrentOffset - newOffset); Sources.Add(padding); - FileTable.CreateFile(path, ref fileInfo); + FileTable.AddFile(path, ref fileInfo); } + /// + /// Returns a view of a RomFS containing all the currently added files. + /// Additional files may be added and a new view produced without + /// invalidating previously built RomFS views. + /// + /// public IStorage Build() { FileTable.TrimExcess(); diff --git a/src/LibHac/IO/RomFs/RomFsEntries.cs b/src/LibHac/IO/RomFs/RomFsEntries.cs index f026fad8..9d97e495 100644 --- a/src/LibHac/IO/RomFs/RomFsEntries.cs +++ b/src/LibHac/IO/RomFs/RomFsEntries.cs @@ -46,10 +46,15 @@ namespace LibHac.IO.RomFs public long Length; } + /// + /// Represents the current position when enumerating a directory's contents. + /// [StructLayout(LayoutKind.Sequential)] public struct FindPosition { + /// The ID of the next directory to be enumerated. public int NextDirectory; + /// The ID of the next file to be enumerated. public int NextFile; } } diff --git a/src/LibHac/IO/RomFs/RomFsFileSystem.cs b/src/LibHac/IO/RomFs/RomFsFileSystem.cs index 54d5598c..57f76a9e 100644 --- a/src/LibHac/IO/RomFs/RomFsFileSystem.cs +++ b/src/LibHac/IO/RomFs/RomFsFileSystem.cs @@ -34,10 +34,7 @@ namespace LibHac.IO.RomFs throw new FileNotFoundException(path); } - public void Commit() - { - throw new NotSupportedException(); - } + public void Commit() { } public IDirectory OpenDirectory(string path, OpenDirectoryMode mode) {