Add some RomFS and IFileStorage documentation.

This commit is contained in:
Alex Barney 2019-02-04 19:24:06 -06:00
parent 5a0cd17dbf
commit a89eb34243
11 changed files with 307 additions and 18 deletions

View file

@ -79,6 +79,9 @@ namespace LibHac.IO
}
}
/// <summary>
/// Specifies which operations are available on an <see cref="IFile"/>.
/// </summary>
[Flags]
public enum OpenMode
{

View file

@ -2,13 +2,38 @@
namespace LibHac.IO
{
/// <summary>
/// Provides an interface for enumerating the child entries of a directory.
/// </summary>
public interface IDirectory
{
/// <summary>
/// The <see cref="IFileSystem"/> that contains the current <see cref="IDirectory"/>.
/// </summary>
IFileSystem ParentFileSystem { get; }
/// <summary>
/// The full path of the current <see cref="IDirectory"/> in its <see cref="ParentFileSystem"/>.
/// </summary>
string FullPath { get; }
/// <summary>
/// Specifies which types of entries will be enumerated when <see cref="Read"/> is called.
/// </summary>
OpenDirectoryMode Mode { get; }
/// <summary>
/// Returns an enumerable collection the file system entries of the types specified by
/// <see cref="Mode"/> that this directory contains. Does not search subdirectories.
/// </summary>
/// <returns>An enumerable collection of file system entries in this directory.</returns>
IEnumerable<DirectoryEntry> Read();
/// <summary>
/// Returns the number of file system entries of the types specified by
/// <see cref="Mode"/> that this directory contains. Does not search subdirectories.
/// </summary>
/// <returns>The number of child entries the directory contains.</returns>
int GetEntryCount();
}
}

View file

@ -2,13 +2,65 @@
namespace LibHac.IO
{
/// <summary>
/// Provides an interface for reading and writing a sequence of bytes.
/// </summary>
/// <remarks><see cref="IFile"/> is similar to <see cref="IStorage"/>, and has a few main differences:
///
/// - <see cref="IFile"/> allows an <see cref="OpenMode"/> to be set that controls read, write
/// and append permissions for the file.
///
/// - If the <see cref="IFile"/> 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 <see cref="Write"/> is called on an offset past the end of the <see cref="IFile"/>,
/// the <see cref="OpenMode.Append"/> mode is set and the file supports expansion,
/// the file will be expanded so that it is large enough to contain the written data.</remarks>
public interface IFile : IDisposable
{
/// <summary>
/// The permissions mode for the current file.
/// </summary>
OpenMode Mode { get; }
/// <summary>
/// Reads a sequence of bytes from the current <see cref="IFile"/>.
/// </summary>
/// <param name="destination">The buffer where the read bytes will be stored.
/// The number of bytes read will be no larger than the length of the buffer.</param>
/// <param name="offset">The offset in the <see cref="IFile"/> at which to begin reading.</param>
/// <returns>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.</returns>
/// <exception cref="ArgumentOutOfRangeException"><paramref name="offset"/> is invalid.</exception>
/// <exception cref="NotSupportedException">The file's <see cref="OpenMode"/> does not allow reading.</exception>
int Read(Span<byte> destination, long offset);
/// <summary>
/// Writes a sequence of bytes to the current <see cref="IFile"/>.
/// </summary>
/// <param name="source">The buffer containing the bytes to be written.</param>
/// <param name="offset">The offset in the <see cref="IStorage"/> at which to begin writing.</param>
/// <exception cref="ArgumentOutOfRangeException"><paramref name="offset"/> is negative.</exception>
/// <exception cref="NotSupportedException">The file's <see cref="OpenMode"/> does not allow this request.</exception>
void Write(ReadOnlySpan<byte> source, long offset);
/// <summary>
/// Causes any buffered data to be written to the underlying device.
/// </summary>
void Flush();
/// <summary>
/// Gets the number of bytes in the file.
/// </summary>
/// <returns>The length of the file in bytes.</returns>
long GetSize();
/// <summary>
/// Sets the size of the file in bytes.
/// </summary>
/// <param name="size">The desired size of the file in bytes.</param>
/// <exception cref="NotSupportedException">If increasing the file size, The file's
/// <see cref="OpenMode"/> does not allow this appending.</exception>
void SetSize(long size);
}
}

View file

@ -1,23 +1,116 @@
using System;
using System.IO;
namespace LibHac.IO
{
/// <summary>
/// Provides an interface for accessing a file system. <c>/</c> is used as the path delimiter.
/// </summary>
public interface IFileSystem
{
/// <summary>
/// Creates all directories and subdirectories in the specified path unless they already exist.
/// </summary>
/// <param name="path">The full path of the directory to create.</param>
/// <exception cref="IOException">An I/O error occurred while creating the directory.</exception>
void CreateDirectory(string path);
/// <summary>
/// Creates or overwrites a file at the specified path.
/// </summary>
/// <param name="path">The full path of the file to create.</param>
/// <param name="size">The initial size of the created file.</param>
/// <param name="options">Flags to control how the file is created.
/// Should usually be <see cref="CreateFileOptions.None"/></param>
/// <exception cref="IOException">An I/O error occurred while creating the file.</exception>
void CreateFile(string path, long size, CreateFileOptions options);
/// <summary>
/// Deletes the specified directory.
/// </summary>
/// <param name="path">The full path of the directory to delete.</param>
/// <exception cref="DirectoryNotFoundException">The specified directory does not exist.</exception>
/// <exception cref="IOException">An I/O error occurred while deleting the directory.</exception>
void DeleteDirectory(string path);
/// <summary>
/// Deletes the specified file.
/// </summary>
/// <param name="path">The full path of the file to delete.</param>
/// <exception cref="FileNotFoundException">The specified file does not exist.</exception>
/// <exception cref="IOException">An I/O error occurred while deleting the file.</exception>
void DeleteFile(string path);
/// <summary>
/// Creates an <see cref="IDirectory"/> instance for enumerating the specified directory.
/// </summary>
/// <param name="path">The directory's full path.</param>
/// <param name="mode">Specifies which sub-entries should be enumerated.</param>
/// <returns>An <see cref="IDirectory"/> instance for the specified directory.</returns>
/// <exception cref="DirectoryNotFoundException">The specified directory does not exist.</exception>
/// <exception cref="IOException">An I/O error occurred while opening the directory.</exception>
IDirectory OpenDirectory(string path, OpenDirectoryMode mode);
/// <summary>
/// Opens an <see cref="IFile"/> instance for the specified path.
/// </summary>
/// <param name="path">The full path of the file to open.</param>
/// <param name="mode">Specifies the access permissions of the created <see cref="IFile"/>.</param>
/// <returns>An <see cref="IFile"/> instance for the specified path.</returns>
/// <exception cref="FileNotFoundException">The specified file does not exist.</exception>
/// <exception cref="IOException">An I/O error occurred while deleting the file.</exception>
IFile OpenFile(string path, OpenMode mode);
/// <summary>
/// Renames or moves a directory to a new location.
/// </summary>
/// <param name="srcPath">The full path of the directory to rename.</param>
/// <param name="dstPath">The new full path of the directory.</param>
/// <exception cref="DirectoryNotFoundException">The specified directory does not exist.</exception>
/// <exception cref="IOException">An I/O error occurred while deleting the directory.</exception>
void RenameDirectory(string srcPath, string dstPath);
/// <summary>
/// Renames or moves a file to a new location.
/// </summary>
/// <param name="srcPath">The full path of the file to rename.</param>
/// <param name="dstPath">The new full path of the file.</param>
/// <exception cref="FileNotFoundException">The specified file does not exist.</exception>
/// <exception cref="IOException">An I/O error occurred while deleting the file.</exception>
void RenameFile(string srcPath, string dstPath);
/// <summary>
/// Determines whether the specified directory exists.
/// </summary>
/// <param name="path">The full path of the directory to check.</param>
/// <returns><see langword="true"/> if the directory exists, otherwise <see langword="false"/>.</returns>
bool DirectoryExists(string path);
/// <summary>
/// Determines whether the specified file exists.
/// </summary>
/// <param name="path">The full path of the file to check.</param>
/// <returns><see langword="true"/> if the file exists, otherwise <see langword="false"/>.</returns>
bool FileExists(string path);
/// <summary>
/// Determines whether the specified path is a file or directory.
/// </summary>
/// <param name="path">The full path to check.</param>
/// <returns>The <see cref="DirectoryEntryType"/> of the file.</returns>
/// <exception cref="FileNotFoundException">The specified path does not exist.</exception>
DirectoryEntryType GetEntryType(string path);
/// <summary>
/// Commits any changes to a transactional file system.
/// Does nothing if called on a non-transactional file system.
/// </summary>
void Commit();
}
/// <summary>
/// Specifies which types of entries are returned when enumerating an <see cref="IDirectory"/>.
/// </summary>
[Flags]
public enum OpenDirectoryMode
{
@ -26,10 +119,16 @@ namespace LibHac.IO
All = Directories | Files
}
/// <summary>
/// Optional file creation flags.
/// </summary>
[Flags]
public enum CreateFileOptions
{
None = 0,
/// <summary>
/// On a <see cref="ConcatenationFileSystem"/>, creates a concatenation file.
/// </summary>
CreateConcatenationFile = 1 << 0
}
}

View file

@ -2,6 +2,9 @@
namespace LibHac.IO
{
/// <summary>
/// Provides an interface for reading and writing a sequence of bytes.
/// </summary>
public interface IStorage : IDisposable
{
/// <summary>
@ -9,14 +12,17 @@ namespace LibHac.IO
/// </summary>
/// <param name="destination">The buffer where the read bytes will be stored.
/// The number of bytes read will be equal to the length of the buffer.</param>
/// <param name="offset">The offset in the <see cref="IStorage"/> to begin reading from.</param>
/// <param name="offset">The offset in the <see cref="IStorage"/> at which to begin reading.</param>
/// <exception cref="ArgumentException">Invalid offset or the IStorage contains fewer bytes than requested. </exception>
void Read(Span<byte> destination, long offset);
/// <summary>
/// Writes a sequence of bytes to the current <see cref="IStorage"/>.
/// </summary>
/// <param name="source">The buffer containing the bytes to be written.</param>
/// <param name="offset">The offset in the <see cref="IStorage"/> to begin writing to.</param>
/// <param name="offset">The offset in the <see cref="IStorage"/> at which to begin writing.</param>
/// <exception cref="ArgumentException">Invalid offset or <paramref name="source"/>
/// is too large to be written to the IStorage. </exception>
void Write(ReadOnlySpan<byte> source, long offset);
/// <summary>

View file

@ -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() { }
}
}

View file

@ -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

View file

@ -5,11 +5,34 @@ using System.Text;
namespace LibHac.IO.RomFs
{
/// <summary>
/// Represents the file table used by the RomFS format.
/// </summary>
/// <remarks>
/// 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, <see cref="TrimExcess"/> should be called
/// to optimize the size of the table.
/// </remarks>
public class HierarchicalRomFileTable
{
private RomFsDictionary<FileRomEntry> FileTable { get; }
private RomFsDictionary<DirectoryRomEntry> DirectoryTable { get; }
/// <summary>
/// Initializes a <see cref="HierarchicalRomFileTable"/> from an existing table.
/// </summary>
/// <param name="dirHashTable"></param>
/// <param name="dirEntryTable"></param>
/// <param name="fileHashTable"></param>
/// <param name="fileEntryTable"></param>
public HierarchicalRomFileTable(IStorage dirHashTable, IStorage dirEntryTable, IStorage fileHashTable,
IStorage fileEntryTable)
{
@ -17,8 +40,18 @@ namespace LibHac.IO.RomFs
DirectoryTable = new RomFsDictionary<DirectoryRomEntry>(dirHashTable, dirEntryTable);
}
/// <summary>
/// Initializes a new <see cref="HierarchicalRomFileTable"/> that has the default initial capacity.
/// </summary>
public HierarchicalRomFileTable() : this(0, 0) { }
/// <summary>
/// Initializes a new <see cref="HierarchicalRomFileTable"/> that has the specified initial capacity.
/// </summary>
/// <param name="directoryCapacity">The initial number of directories that the
/// <see cref="HierarchicalRomFileTable"/> can contain.</param>
/// <param name="fileCapacity">The initial number of files that the
/// <see cref="HierarchicalRomFileTable"/> can contain.</param>
public HierarchicalRomFileTable(int directoryCapacity, int fileCapacity)
{
FileTable = new RomFsDictionary<FileRomEntry>(fileCapacity);
@ -73,6 +106,13 @@ namespace LibHac.IO.RomFs
return false;
}
/// <summary>
/// Opens a directory for enumeration.
/// </summary>
/// <param name="path">The full path of the directory to open.</param>
/// <param name="position">The initial position of the directory enumerator.</param>
/// <returns><see langword="true"/> if the table contains a directory with the specified path;
/// otherwise, <see langword="false"/>.</returns>
public bool TryOpenDirectory(string path, out FindPosition position)
{
FindDirectoryRecursive(GetUtf8Bytes(path), out RomEntryKey key);
@ -87,6 +127,13 @@ namespace LibHac.IO.RomFs
return false;
}
/// <summary>
/// Opens a directory for enumeration.
/// </summary>
/// <param name="directoryId">The ID of the directory to open.</param>
/// <param name="position">When this method returns, contains the initial position of the directory enumerator.</param>
/// <returns><see langword="true"/> if the table contains a directory with the specified path;
/// otherwise, <see langword="false"/>.</returns>
public bool TryOpenDirectory(int directoryId, out FindPosition position)
{
if (DirectoryTable.TryGetValue(directoryId, out RomKeyValuePair<DirectoryRomEntry> keyValuePair))
@ -99,6 +146,15 @@ namespace LibHac.IO.RomFs
return false;
}
/// <summary>
/// Returns the next file in a directory and updates the enumerator's position.
/// </summary>
/// <param name="position">The current position of the directory enumerator.
/// This position will be updated when the method returns.</param>
/// <param name="info">When this method returns, contains the file's metadata.</param>
/// <param name="name">When this method returns, contains the file's name (Not the full path).</param>
/// <returns><see langword="true"/> if the next file was successfully returned.
/// <see langword="false"/> if there are no more files to enumerate.</returns>
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;
}
/// <summary>
/// Returns the next child directory in a directory and updates the enumerator's position.
/// </summary>
/// <param name="position">The current position of the directory enumerator.
/// This position will be updated when the method returns.</param>
/// <param name="name">When this method returns, contains the directory's name (Not the full path).</param>
/// <returns><see langword="true"/> if the next directory was successfully returned.
/// <see langword="false"/> if there are no more directories to enumerate.</returns>
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)
/// <summary>
/// Adds a file to the file table. If the file already exists
/// its <see cref="RomFileInfo"/> will be updated.
/// </summary>
/// <param name="path">The full path of the file to be added.</param>
/// <param name="fileInfo">The file information to be stored.</param>
public void AddFile(string path, ref RomFileInfo fileInfo)
{
path = PathTools.Normalize(path);
ReadOnlySpan<byte> pathBytes = GetUtf8Bytes(path);
@ -140,13 +210,25 @@ namespace LibHac.IO.RomFs
CreateFileRecursiveInternal(pathBytes, ref fileInfo);
}
public void CreateDirectory(string path)
/// <summary>
/// Adds a directory to the file table. If the directory already exists,
/// no action is performed.
/// </summary>
/// <param name="path">The full path of the directory to be added.</param>
public void AddDirectory(string path)
{
path = PathTools.Normalize(path);
CreateDirectoryRecursive(GetUtf8Bytes(path));
}
/// <summary>
/// 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.
/// </summary>
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);

View file

@ -5,6 +5,12 @@ using System.Linq;
namespace LibHac.IO.RomFs
{
/// <summary>
/// Builds a RomFS from a collection of files.
/// </summary>
/// <remarks>A <see cref="RomFsBuilder"/> produces a view of a RomFS archive.
/// When doing so, it will create an <see cref="IStorage"/> instance that will
/// provide the RomFS data when read. Random seek is supported.</remarks>
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; }
/// <summary>
/// Creates a new, empty <see cref="RomFsBuilder"/>
/// </summary>
public RomFsBuilder() { }
/// <summary>
/// Creates a new <see cref="RomFsBuilder"/> and populates it with all
/// the files in the specified <see cref="IFileSystem"/>.
/// </summary>
public RomFsBuilder(IFileSystem input)
{
foreach (DirectoryEntry file in input.EnumerateEntries().Where(x => x.Type == DirectoryEntryType.File)
@ -26,6 +39,11 @@ namespace LibHac.IO.RomFs
}
}
/// <summary>
/// Adds a file to the RomFS.
/// </summary>
/// <param name="path">The full path in the RomFS</param>
/// <param name="file">An <see cref="IFile"/> of the file data to add.</param>
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);
}
/// <summary>
/// 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.
/// </summary>
/// <returns></returns>
public IStorage Build()
{
FileTable.TrimExcess();

View file

@ -46,10 +46,15 @@ namespace LibHac.IO.RomFs
public long Length;
}
/// <summary>
/// Represents the current position when enumerating a directory's contents.
/// </summary>
[StructLayout(LayoutKind.Sequential)]
public struct FindPosition
{
/// <summary>The ID of the next directory to be enumerated.</summary>
public int NextDirectory;
/// <summary>The ID of the next file to be enumerated.</summary>
public int NextFile;
}
}

View file

@ -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)
{