Return Result from IFileSystem methods

This commit is contained in:
Alex Barney 2019-09-08 14:28:42 -05:00
parent 69e7735666
commit 9e9fd19f63
35 changed files with 1246 additions and 1006 deletions

View File

@ -56,7 +56,9 @@ namespace LibHac.Fs
{
try
{
using (IFile file = BaseFileSystem.OpenFile(path, OpenMode.Read))
BaseFileSystem.OpenFile(out IFile file, path, OpenMode.Read).ThrowIfFailure();
using (file)
{
file.GetSize(out long fileSize).ThrowIfFailure();

View File

@ -27,14 +27,14 @@ namespace LibHac.Fs
BlockSize = blockSize;
}
public void CreateDirectory(string path)
public Result CreateDirectory(string path)
{
BaseFileSystem.CreateDirectory(path);
return BaseFileSystem.CreateDirectory(path);
}
public void CreateFile(string path, long size, CreateFileOptions options)
public Result CreateFile(string path, long size, CreateFileOptions options)
{
CreateFile(path, size, options, new byte[0x20]);
return CreateFile(path, size, options, new byte[0x20]);
}
/// <summary>
@ -45,83 +45,109 @@ namespace LibHac.Fs
/// <param name="options">Flags to control how the file is created.
/// Should usually be <see cref="CreateFileOptions.None"/></param>
/// <param name="key">The 256-bit key containing a 128-bit data key followed by a 128-bit tweak key.</param>
public void CreateFile(string path, long size, CreateFileOptions options, byte[] key)
public Result CreateFile(string path, long size, CreateFileOptions options, byte[] key)
{
long containerSize = AesXtsFile.HeaderLength + Util.AlignUp(size, 0x10);
BaseFileSystem.CreateFile(path, containerSize, options);
var header = new AesXtsFileHeader(key, size, path, KekSource, ValidationKey);
using (IFile baseFile = BaseFileSystem.OpenFile(path, OpenMode.Write))
Result rc = BaseFileSystem.OpenFile(out IFile baseFile, path, OpenMode.Write);
if (rc.IsFailure()) return rc;
using (baseFile)
{
baseFile.Write(0, header.ToBytes(false)).ThrowIfFailure();
rc = baseFile.Write(0, header.ToBytes(false));
if (rc.IsFailure()) return rc;
}
return Result.Success;
}
public void DeleteDirectory(string path)
public Result DeleteDirectory(string path)
{
BaseFileSystem.DeleteDirectory(path);
return BaseFileSystem.DeleteDirectory(path);
}
public void DeleteDirectoryRecursively(string path)
public Result DeleteDirectoryRecursively(string path)
{
BaseFileSystem.DeleteDirectoryRecursively(path);
return BaseFileSystem.DeleteDirectoryRecursively(path);
}
public void CleanDirectoryRecursively(string path)
public Result CleanDirectoryRecursively(string path)
{
BaseFileSystem.CleanDirectoryRecursively(path);
return BaseFileSystem.CleanDirectoryRecursively(path);
}
public void DeleteFile(string path)
public Result DeleteFile(string path)
{
BaseFileSystem.DeleteFile(path);
return BaseFileSystem.DeleteFile(path);
}
public IDirectory OpenDirectory(string path, OpenDirectoryMode mode)
public Result OpenDirectory(out IDirectory directory, string path, OpenDirectoryMode mode)
{
directory = default;
path = PathTools.Normalize(path);
IDirectory baseDir = BaseFileSystem.OpenDirectory(path, mode);
Result rc = BaseFileSystem.OpenDirectory(out IDirectory baseDir, path, mode);
if (rc.IsFailure()) return rc;
var dir = new AesXtsDirectory(this, baseDir, mode);
return dir;
directory = new AesXtsDirectory(this, baseDir, mode);
return Result.Success;
}
public IFile OpenFile(string path, OpenMode mode)
public Result OpenFile(out IFile file, string path, OpenMode mode)
{
file = default;
path = PathTools.Normalize(path);
IFile baseFile = BaseFileSystem.OpenFile(path, mode | OpenMode.Read);
var file = new AesXtsFile(mode, baseFile, path, KekSource, ValidationKey, BlockSize);
Result rc = BaseFileSystem.OpenFile(out IFile baseFile, path, mode | OpenMode.Read);
if (rc.IsFailure()) return rc;
file.ToDispose.Add(baseFile);
return file;
var xtsFile = new AesXtsFile(mode, baseFile, path, KekSource, ValidationKey, BlockSize);
xtsFile.ToDispose.Add(baseFile);
file = xtsFile;
return Result.Success;
}
public void RenameDirectory(string srcPath, string dstPath)
public Result RenameDirectory(string oldPath, string newPath)
{
srcPath = PathTools.Normalize(srcPath);
dstPath = PathTools.Normalize(dstPath);
oldPath = PathTools.Normalize(oldPath);
newPath = PathTools.Normalize(newPath);
BaseFileSystem.RenameDirectory(srcPath, dstPath);
// todo: Return proper result codes
// Official code procedure:
// Make sure all file headers can be decrypted
// Rename directory to the new path
// Reencrypt file headers with new path
// If no errors, return
// Reencrypt any modified file headers with the old path
// Rename directory to the old path
Result rc = BaseFileSystem.RenameDirectory(oldPath, newPath);
if (rc.IsFailure()) return rc;
try
{
RenameDirectoryImpl(srcPath, dstPath, false);
RenameDirectoryImpl(oldPath, newPath, false);
}
catch (Exception)
{
RenameDirectoryImpl(srcPath, dstPath, true);
BaseFileSystem.RenameDirectory(dstPath, srcPath);
RenameDirectoryImpl(oldPath, newPath, true);
BaseFileSystem.RenameDirectory(oldPath, newPath);
throw;
}
return Result.Success;
}
private void RenameDirectoryImpl(string srcDir, string dstDir, bool doRollback)
{
IDirectory dir = OpenDirectory(dstDir, OpenDirectoryMode.All);
OpenDirectory(out IDirectory dir, dstDir, OpenDirectoryMode.All);
foreach (DirectoryEntry entry in dir.Read())
{
@ -151,56 +177,60 @@ namespace LibHac.Fs
}
}
public void RenameFile(string srcPath, string dstPath)
public Result RenameFile(string oldPath, string newPath)
{
srcPath = PathTools.Normalize(srcPath);
dstPath = PathTools.Normalize(dstPath);
oldPath = PathTools.Normalize(oldPath);
newPath = PathTools.Normalize(newPath);
AesXtsFileHeader header = ReadXtsHeader(srcPath, srcPath);
// todo: Return proper result codes
BaseFileSystem.RenameFile(srcPath, dstPath);
AesXtsFileHeader header = ReadXtsHeader(oldPath, oldPath);
BaseFileSystem.RenameFile(oldPath, newPath);
try
{
WriteXtsHeader(header, dstPath, dstPath);
WriteXtsHeader(header, newPath, newPath);
}
catch (Exception)
{
BaseFileSystem.RenameFile(dstPath, srcPath);
WriteXtsHeader(header, srcPath, srcPath);
BaseFileSystem.RenameFile(newPath, oldPath);
WriteXtsHeader(header, oldPath, oldPath);
throw;
}
return Result.Success;
}
public DirectoryEntryType GetEntryType(string path)
public Result GetEntryType(out DirectoryEntryType entryType, string path)
{
return BaseFileSystem.GetEntryType(path);
return BaseFileSystem.GetEntryType(out entryType, path);
}
public FileTimeStampRaw GetFileTimeStampRaw(string path)
public Result GetFileTimeStampRaw(out FileTimeStampRaw timeStamp, string path)
{
return BaseFileSystem.GetFileTimeStampRaw(path);
return BaseFileSystem.GetFileTimeStampRaw(out timeStamp, path);
}
public long GetFreeSpaceSize(string path)
public Result GetFreeSpaceSize(out long freeSpace, string path)
{
return BaseFileSystem.GetFreeSpaceSize(path);
return BaseFileSystem.GetFreeSpaceSize(out freeSpace, path);
}
public long GetTotalSpaceSize(string path)
public Result GetTotalSpaceSize(out long totalSpace, string path)
{
return BaseFileSystem.GetTotalSpaceSize(path);
return BaseFileSystem.GetTotalSpaceSize(out totalSpace, path);
}
public void Commit()
public Result Commit()
{
BaseFileSystem.Commit();
return BaseFileSystem.Commit();
}
public void QueryEntry(Span<byte> outBuffer, ReadOnlySpan<byte> inBuffer, string path, QueryId queryId)
public Result QueryEntry(Span<byte> outBuffer, ReadOnlySpan<byte> inBuffer, QueryId queryId, string path)
{
BaseFileSystem.QueryEntry(outBuffer, inBuffer, path, queryId);
return BaseFileSystem.QueryEntry(outBuffer, inBuffer, queryId, path);
}
private AesXtsFileHeader ReadXtsHeader(string filePath, string keyPath)
@ -218,7 +248,12 @@ namespace LibHac.Fs
Debug.Assert(PathTools.IsNormalized(filePath.AsSpan()));
Debug.Assert(PathTools.IsNormalized(keyPath.AsSpan()));
using (IFile file = BaseFileSystem.OpenFile(filePath, OpenMode.Read))
header = null;
Result rc = BaseFileSystem.OpenFile(out IFile file, filePath, OpenMode.Read);
if (rc.IsFailure()) return false;
using (file)
{
header = new AesXtsFileHeader(file);
@ -233,7 +268,9 @@ namespace LibHac.Fs
header.EncryptHeader(keyPath, KekSource, ValidationKey);
using (IFile file = BaseFileSystem.OpenFile(filePath, OpenMode.ReadWrite))
BaseFileSystem.OpenFile(out IFile file, filePath, OpenMode.ReadWrite);
using (file)
{
file.Write(0, header.ToBytes(false), WriteOption.Flush).ThrowIfFailure();
}

View File

@ -130,7 +130,8 @@ namespace LibHac.Fs
public override Result SetSize(long size)
{
GetSize(out long currentSize).ThrowIfFailure();
Result rc = GetSize(out long currentSize);
if (rc.IsFailure()) return rc;
if (currentSize == size) return Result.Success;
@ -142,7 +143,7 @@ namespace LibHac.Fs
IFile currentLastSubFile = Sources[currentSubFileCount - 1];
long newSubFileSize = QuerySubFileSize(currentSubFileCount - 1, size, SubFileSize);
Result rc = currentLastSubFile.SetSize(newSubFileSize);
rc = currentLastSubFile.SetSize(newSubFileSize);
if (rc.IsFailure()) return rc;
for (int i = currentSubFileCount; i < newSubFileCount; i++)
@ -150,8 +151,13 @@ namespace LibHac.Fs
string newSubFilePath = ConcatenationFileSystem.GetSubFilePath(FilePath, i);
newSubFileSize = QuerySubFileSize(i, size, SubFileSize);
BaseFileSystem.CreateFile(newSubFilePath, newSubFileSize, CreateFileOptions.None);
Sources.Add(BaseFileSystem.OpenFile(newSubFilePath, Mode));
rc = BaseFileSystem.CreateFile(newSubFilePath, newSubFileSize, CreateFileOptions.None);
if (rc.IsFailure()) return rc;
rc = BaseFileSystem.OpenFile(out IFile newSubFile, newSubFilePath, Mode);
if (rc.IsFailure()) return rc;
Sources.Add(newSubFile);
}
}
else
@ -162,12 +168,14 @@ namespace LibHac.Fs
Sources.RemoveAt(i);
string subFilePath = ConcatenationFileSystem.GetSubFilePath(FilePath, i);
BaseFileSystem.DeleteFile(subFilePath);
rc = BaseFileSystem.DeleteFile(subFilePath);
if (rc.IsFailure()) return rc;
}
long newLastFileSize = QuerySubFileSize(newSubFileCount - 1, size, SubFileSize);
Result rc = Sources[newSubFileCount - 1].SetSize(newLastFileSize);
rc = Sources[newSubFileCount - 1].SetSize(newLastFileSize);
if (rc.IsFailure()) return rc;
}

View File

@ -68,11 +68,19 @@ namespace LibHac.Fs
#if CROSS_PLATFORM
private bool IsConcatenationFileHeuristic(string path)
{
if (BaseFileSystem.GetEntryType(path) != DirectoryEntryType.Directory) return false;
// Check if the path is a directory
Result getTypeResult = BaseFileSystem.GetEntryType(out DirectoryEntryType pathType, path);
if (getTypeResult.IsFailure() || pathType != DirectoryEntryType.Directory) return false;
if (BaseFileSystem.GetEntryType(PathTools.Combine(path, "00")) != DirectoryEntryType.File) return false;
// Check if the directory contains at least one subfile
getTypeResult = BaseFileSystem.GetEntryType(out DirectoryEntryType subFileType, PathTools.Combine(path, "00"));
if (getTypeResult.IsFailure() || subFileType != DirectoryEntryType.File) return false;
if (BaseFileSystem.OpenDirectory(path, OpenDirectoryMode.Directory).GetEntryCount() > 0) return false;
// Make sure the directory contains no subdirectories
Result rc = BaseFileSystem.OpenDirectory(out IDirectory dir, path, OpenDirectoryMode.Directory);
if (rc.IsFailure()) return false;
if (dir.GetEntryCount() > 0) return false;
// Should be enough checks to avoid most false positives. Maybe
return true;
@ -91,21 +99,21 @@ namespace LibHac.Fs
BaseFileSystem.SetFileAttributes(path, attributes);
}
public void CreateDirectory(string path)
public Result CreateDirectory(string path)
{
path = PathTools.Normalize(path);
string parent = PathTools.GetParentDirectory(path);
if (IsConcatenationFile(parent))
{
ThrowHelper.ThrowResult(ResultFs.PathNotFound,
"Cannot create a directory inside of a concatenation file");
// Cannot create a directory inside of a concatenation file
return ResultFs.PathNotFound.Log();
}
BaseFileSystem.CreateDirectory(path);
return BaseFileSystem.CreateDirectory(path);
}
public void CreateFile(string path, long size, CreateFileOptions options)
public Result CreateFile(string path, long size, CreateFileOptions options)
{
path = PathTools.Normalize(path);
@ -113,8 +121,7 @@ namespace LibHac.Fs
if (!options.HasFlag(CreateFileOptions.CreateConcatenationFile))
{
BaseFileSystem.CreateFile(path, size, newOptions);
return;
return BaseFileSystem.CreateFile(path, size, newOptions);
}
// A concatenation file directory can't contain normal files
@ -122,11 +129,13 @@ namespace LibHac.Fs
if (IsConcatenationFile(parentDir))
{
ThrowHelper.ThrowResult(ResultFs.PathNotFound,
"Cannot create a concatenation file inside of a concatenation file");
// Cannot create a file inside of a concatenation file
return ResultFs.PathNotFound.Log();
}
BaseFileSystem.CreateDirectory(path);
Result rc = BaseFileSystem.CreateDirectory(path);
if (rc.IsFailure()) return rc;
SetConcatenationFileAttribute(path);
long remaining = size;
@ -136,82 +145,95 @@ namespace LibHac.Fs
long fileSize = Math.Min(SubFileSize, remaining);
string fileName = GetSubFilePath(path, i);
BaseFileSystem.CreateFile(fileName, fileSize, CreateFileOptions.None);
Result createSubFileResult = BaseFileSystem.CreateFile(fileName, fileSize, CreateFileOptions.None);
if (createSubFileResult.IsFailure())
{
BaseFileSystem.DeleteDirectoryRecursively(path);
return createSubFileResult;
}
remaining -= fileSize;
}
return Result.Success;
}
public void DeleteDirectory(string path)
public Result DeleteDirectory(string path)
{
path = PathTools.Normalize(path);
if (IsConcatenationFile(path))
{
ThrowHelper.ThrowResult(ResultFs.PathNotFound);
return ResultFs.PathNotFound.Log();
}
BaseFileSystem.DeleteDirectory(path);
return BaseFileSystem.DeleteDirectory(path);
}
public void DeleteDirectoryRecursively(string path)
public Result DeleteDirectoryRecursively(string path)
{
path = PathTools.Normalize(path);
if (IsConcatenationFile(path)) ThrowHelper.ThrowResult(ResultFs.PathNotFound);
if (IsConcatenationFile(path)) return ResultFs.PathNotFound.Log();
BaseFileSystem.DeleteDirectoryRecursively(path);
return BaseFileSystem.DeleteDirectoryRecursively(path);
}
public void CleanDirectoryRecursively(string path)
public Result CleanDirectoryRecursively(string path)
{
path = PathTools.Normalize(path);
if (IsConcatenationFile(path)) ThrowHelper.ThrowResult(ResultFs.PathNotFound);
if (IsConcatenationFile(path)) return ResultFs.PathNotFound.Log();
BaseFileSystem.CleanDirectoryRecursively(path);
return BaseFileSystem.CleanDirectoryRecursively(path);
}
public void DeleteFile(string path)
public Result DeleteFile(string path)
{
path = PathTools.Normalize(path);
if (!IsConcatenationFile(path))
{
BaseFileSystem.DeleteFile(path);
return BaseFileSystem.DeleteFile(path);
}
int count = GetSubFileCount(path);
for (int i = 0; i < count; i++)
{
BaseFileSystem.DeleteFile(GetSubFilePath(path, i));
Result rc = BaseFileSystem.DeleteFile(GetSubFilePath(path, i));
if (rc.IsFailure()) return rc;
}
BaseFileSystem.DeleteDirectory(path);
return BaseFileSystem.DeleteDirectory(path);
}
public IDirectory OpenDirectory(string path, OpenDirectoryMode mode)
public Result OpenDirectory(out IDirectory directory, string path, OpenDirectoryMode mode)
{
directory = default;
path = PathTools.Normalize(path);
if (IsConcatenationFile(path))
{
ThrowHelper.ThrowResult(ResultFs.PathNotFound);
return ResultFs.PathNotFound.Log();
}
IDirectory parentDir = BaseFileSystem.OpenDirectory(path, OpenDirectoryMode.All);
var dir = new ConcatenationDirectory(this, parentDir, mode);
return dir;
Result rc = BaseFileSystem.OpenDirectory(out IDirectory parentDir, path, OpenDirectoryMode.All);
if (rc.IsFailure()) return rc;
directory = new ConcatenationDirectory(this, parentDir, mode);
return Result.Success;
}
public IFile OpenFile(string path, OpenMode mode)
public Result OpenFile(out IFile file, string path, OpenMode mode)
{
file = default;
path = PathTools.Normalize(path);
if (!IsConcatenationFile(path))
{
return BaseFileSystem.OpenFile(path, mode);
return BaseFileSystem.OpenFile(out file, path, mode);
}
int fileCount = GetSubFileCount(path);
@ -221,75 +243,85 @@ namespace LibHac.Fs
for (int i = 0; i < fileCount; i++)
{
string filePath = GetSubFilePath(path, i);
IFile file = BaseFileSystem.OpenFile(filePath, mode);
files.Add(file);
Result rc = BaseFileSystem.OpenFile(out IFile subFile, filePath, mode);
if (rc.IsFailure()) return rc;
files.Add(subFile);
}
return new ConcatenationFile(BaseFileSystem, path, files, SubFileSize, mode);
file = new ConcatenationFile(BaseFileSystem, path, files, SubFileSize, mode);
return Result.Success;
}
public void RenameDirectory(string srcPath, string dstPath)
public Result RenameDirectory(string oldPath, string newPath)
{
srcPath = PathTools.Normalize(srcPath);
dstPath = PathTools.Normalize(dstPath);
oldPath = PathTools.Normalize(oldPath);
newPath = PathTools.Normalize(newPath);
if (IsConcatenationFile(srcPath))
if (IsConcatenationFile(oldPath))
{
ThrowHelper.ThrowResult(ResultFs.PathNotFound);
return ResultFs.PathNotFound.Log();
}
BaseFileSystem.RenameDirectory(srcPath, dstPath);
return BaseFileSystem.RenameDirectory(oldPath, newPath);
}
public void RenameFile(string srcPath, string dstPath)
public Result RenameFile(string oldPath, string newPath)
{
srcPath = PathTools.Normalize(srcPath);
dstPath = PathTools.Normalize(dstPath);
oldPath = PathTools.Normalize(oldPath);
newPath = PathTools.Normalize(newPath);
if (IsConcatenationFile(srcPath))
if (IsConcatenationFile(oldPath))
{
BaseFileSystem.RenameDirectory(srcPath, dstPath);
return BaseFileSystem.RenameDirectory(oldPath, newPath);
}
else
{
BaseFileSystem.RenameFile(srcPath, dstPath);
return BaseFileSystem.RenameFile(oldPath, newPath);
}
}
public DirectoryEntryType GetEntryType(string path)
public Result GetEntryType(out DirectoryEntryType entryType, string path)
{
path = PathTools.Normalize(path);
if (IsConcatenationFile(path)) return DirectoryEntryType.File;
if (IsConcatenationFile(path))
{
entryType = DirectoryEntryType.File;
return Result.Success;
}
return BaseFileSystem.GetEntryType(path);
return BaseFileSystem.GetEntryType(out entryType, path);
}
public long GetFreeSpaceSize(string path)
public Result GetFreeSpaceSize(out long freeSpace, string path)
{
return BaseFileSystem.GetFreeSpaceSize(path);
return BaseFileSystem.GetFreeSpaceSize(out freeSpace, path);
}
public long GetTotalSpaceSize(string path)
public Result GetTotalSpaceSize(out long totalSpace, string path)
{
return BaseFileSystem.GetTotalSpaceSize(path);
return BaseFileSystem.GetTotalSpaceSize(out totalSpace, path);
}
public FileTimeStampRaw GetFileTimeStampRaw(string path)
public Result GetFileTimeStampRaw(out FileTimeStampRaw timeStamp, string path)
{
return BaseFileSystem.GetFileTimeStampRaw(path);
return BaseFileSystem.GetFileTimeStampRaw(out timeStamp, path);
}
public void Commit()
public Result Commit()
{
BaseFileSystem.Commit();
return BaseFileSystem.Commit();
}
public void QueryEntry(Span<byte> outBuffer, ReadOnlySpan<byte> inBuffer, string path, QueryId queryId)
public Result QueryEntry(Span<byte> outBuffer, ReadOnlySpan<byte> inBuffer, QueryId queryId, string path)
{
if (queryId != QueryId.MakeConcatFile) ThrowHelper.ThrowResult(ResultFs.UnsupportedOperationInConcatFsQueryEntry);
if (queryId != QueryId.MakeConcatFile) return ResultFs.UnsupportedOperationInConcatFsQueryEntry.Log();
SetConcatenationFileAttribute(path);
return Result.Success;
}
private int GetSubFileCount(string dirPath)

View File

@ -33,162 +33,170 @@ namespace LibHac.Fs
}
}
public void CreateDirectory(string path)
public Result CreateDirectory(string path)
{
string fullPath = GetFullPath(PathTools.Normalize(path));
lock (Locker)
{
BaseFs.CreateDirectory(fullPath);
return BaseFs.CreateDirectory(fullPath);
}
}
public void CreateFile(string path, long size, CreateFileOptions options)
public Result CreateFile(string path, long size, CreateFileOptions options)
{
string fullPath = GetFullPath(PathTools.Normalize(path));
lock (Locker)
{
BaseFs.CreateFile(fullPath, size, options);
return BaseFs.CreateFile(fullPath, size, options);
}
}
public void DeleteDirectory(string path)
public Result DeleteDirectory(string path)
{
string fullPath = GetFullPath(PathTools.Normalize(path));
lock (Locker)
{
BaseFs.DeleteDirectory(fullPath);
return BaseFs.DeleteDirectory(fullPath);
}
}
public void DeleteDirectoryRecursively(string path)
public Result DeleteDirectoryRecursively(string path)
{
string fullPath = GetFullPath(PathTools.Normalize(path));
lock (Locker)
{
BaseFs.DeleteDirectoryRecursively(fullPath);
return BaseFs.DeleteDirectoryRecursively(fullPath);
}
}
public void CleanDirectoryRecursively(string path)
public Result CleanDirectoryRecursively(string path)
{
string fullPath = GetFullPath(PathTools.Normalize(path));
lock (Locker)
{
BaseFs.CleanDirectoryRecursively(fullPath);
return BaseFs.CleanDirectoryRecursively(fullPath);
}
}
public void DeleteFile(string path)
public Result DeleteFile(string path)
{
string fullPath = GetFullPath(PathTools.Normalize(path));
lock (Locker)
{
BaseFs.DeleteFile(fullPath);
return BaseFs.DeleteFile(fullPath);
}
}
public IDirectory OpenDirectory(string path, OpenDirectoryMode mode)
public Result OpenDirectory(out IDirectory directory, string path, OpenDirectoryMode mode)
{
string fullPath = GetFullPath(PathTools.Normalize(path));
lock (Locker)
{
return BaseFs.OpenDirectory(fullPath, mode);
return BaseFs.OpenDirectory(out directory, fullPath, mode);
}
}
public IFile OpenFile(string path, OpenMode mode)
public Result OpenFile(out IFile file, string path, OpenMode mode)
{
file = default;
string fullPath = GetFullPath(PathTools.Normalize(path));
lock (Locker)
{
IFile baseFile = BaseFs.OpenFile(fullPath, mode);
var file = new DirectorySaveDataFile(this, baseFile);
Result rc = BaseFs.OpenFile(out IFile baseFile, fullPath, mode);
if (rc.IsFailure()) return rc;
file = new DirectorySaveDataFile(this, baseFile);
if (mode.HasFlag(OpenMode.Write))
{
OpenWritableFileCount++;
}
return file;
return Result.Success;
}
}
public void RenameDirectory(string srcPath, string dstPath)
public Result RenameDirectory(string oldPath, string newPath)
{
string fullSrcPath = GetFullPath(PathTools.Normalize(srcPath));
string fullDstPath = GetFullPath(PathTools.Normalize(dstPath));
string fullOldPath = GetFullPath(PathTools.Normalize(oldPath));
string fullNewPath = GetFullPath(PathTools.Normalize(newPath));
lock (Locker)
{
BaseFs.RenameDirectory(fullSrcPath, fullDstPath);
return BaseFs.RenameDirectory(fullOldPath, fullNewPath);
}
}
public void RenameFile(string srcPath, string dstPath)
public Result RenameFile(string oldPath, string newPath)
{
string fullSrcPath = GetFullPath(PathTools.Normalize(srcPath));
string fullDstPath = GetFullPath(PathTools.Normalize(dstPath));
string fullOldPath = GetFullPath(PathTools.Normalize(oldPath));
string fullNewPath = GetFullPath(PathTools.Normalize(newPath));
lock (Locker)
{
BaseFs.RenameFile(fullSrcPath, fullDstPath);
return BaseFs.RenameFile(fullOldPath, fullNewPath);
}
}
public DirectoryEntryType GetEntryType(string path)
public Result GetEntryType(out DirectoryEntryType entryType, string path)
{
string fullPath = GetFullPath(PathTools.Normalize(path));
lock (Locker)
{
return BaseFs.GetEntryType(fullPath);
return BaseFs.GetEntryType(out entryType, fullPath);
}
}
public long GetFreeSpaceSize(string path)
public Result GetFreeSpaceSize(out long freeSpace, string path)
{
ThrowHelper.ThrowResult(ResultFs.NotImplemented);
return default;
freeSpace = default;
return ResultFs.NotImplemented.Log();
}
public long GetTotalSpaceSize(string path)
public Result GetTotalSpaceSize(out long totalSpace, string path)
{
ThrowHelper.ThrowResult(ResultFs.NotImplemented);
return default;
totalSpace = default;
return ResultFs.NotImplemented.Log();
}
public FileTimeStampRaw GetFileTimeStampRaw(string path)
public Result GetFileTimeStampRaw(out FileTimeStampRaw timeStamp, string path)
{
ThrowHelper.ThrowResult(ResultFs.NotImplemented);
return default;
timeStamp = default;
return ResultFs.NotImplemented.Log();
}
public void Commit()
public Result Commit()
{
if (OpenWritableFileCount > 0)
lock (Locker)
{
ThrowHelper.ThrowResult(ResultFs.WritableFileOpen,
"All files must be closed before commiting save data.");
if (OpenWritableFileCount > 0)
{
// All files must be closed before commiting save data.
return ResultFs.WritableFileOpen.Log();
}
Result rc = SynchronizeDirectory(SyncDir, WorkingDir);
if (rc.IsFailure()) return rc;
rc = BaseFs.DeleteDirectoryRecursively(CommittedDir);
if (rc.IsFailure()) return rc;
return BaseFs.RenameDirectory(SyncDir, CommittedDir);
}
SynchronizeDirectory(SyncDir, WorkingDir);
BaseFs.DeleteDirectoryRecursively(CommittedDir);
BaseFs.RenameDirectory(SyncDir, CommittedDir);
}
public void QueryEntry(Span<byte> outBuffer, ReadOnlySpan<byte> inBuffer, string path, QueryId queryId)
public Result QueryEntry(Span<byte> outBuffer, ReadOnlySpan<byte> inBuffer, QueryId queryId, string path)
{
ThrowHelper.ThrowResult(ResultFs.NotImplemented);
return ResultFs.NotImplemented.Log();
}
private string GetFullPath(string path)
@ -196,19 +204,21 @@ namespace LibHac.Fs
return PathTools.Normalize(PathTools.Combine(WorkingDir, path));
}
private void SynchronizeDirectory(string dest, string src)
private Result SynchronizeDirectory(string dest, string src)
{
if (BaseFs.DirectoryExists(dest))
{
BaseFs.DeleteDirectoryRecursively(dest);
}
Result rc = BaseFs.DeleteDirectoryRecursively(dest);
if (rc.IsFailure() && rc != ResultFs.PathNotFound) return rc;
BaseFs.CreateDirectory(dest);
rc = BaseFs.CreateDirectory(dest);
if (rc.IsFailure()) return rc;
IDirectory sourceDir = BaseFs.OpenDirectory(src, OpenDirectoryMode.All);
IDirectory destDir = BaseFs.OpenDirectory(dest, OpenDirectoryMode.All);
rc = BaseFs.OpenDirectory(out IDirectory sourceDir, src, OpenDirectoryMode.All);
if (rc.IsFailure()) return rc;
sourceDir.CopyDirectory(destDir);
rc = BaseFs.OpenDirectory(out IDirectory destDir, dest, OpenDirectoryMode.All);
if (rc.IsFailure()) return rc;
return sourceDir.CopyDirectory(destDir);
}
internal void NotifyCloseWritableFile()

View File

@ -7,10 +7,11 @@ namespace LibHac.Fs
{
public static class FileSystemExtensions
{
public static void CopyDirectory(this IDirectory source, IDirectory dest, IProgressReport logger = null, CreateFileOptions options = CreateFileOptions.None)
public static Result CopyDirectory(this IDirectory source, IDirectory dest, IProgressReport logger = null, CreateFileOptions options = CreateFileOptions.None)
{
IFileSystem sourceFs = source.ParentFileSystem;
IFileSystem destFs = dest.ParentFileSystem;
Result rc;
foreach (DirectoryEntry entry in source.Read())
{
@ -20,32 +21,47 @@ namespace LibHac.Fs
if (entry.Type == DirectoryEntryType.Directory)
{
destFs.EnsureDirectoryExists(subDstPath);
IDirectory subSrcDir = sourceFs.OpenDirectory(subSrcPath, OpenDirectoryMode.All);
IDirectory subDstDir = destFs.OpenDirectory(subDstPath, OpenDirectoryMode.All);
subSrcDir.CopyDirectory(subDstDir, logger, options);
rc = sourceFs.OpenDirectory(out IDirectory subSrcDir, subSrcPath, OpenDirectoryMode.All);
if (rc.IsFailure()) return rc;
rc = destFs.OpenDirectory(out IDirectory subDstDir, subDstPath, OpenDirectoryMode.All);
if (rc.IsFailure()) return rc;
rc = subSrcDir.CopyDirectory(subDstDir, logger, options);
if (rc.IsFailure()) return rc;
}
if (entry.Type == DirectoryEntryType.File)
{
destFs.CreateOrOverwriteFile(subDstPath, entry.Size, options);
using (IFile srcFile = sourceFs.OpenFile(subSrcPath, OpenMode.Read))
using (IFile dstFile = destFs.OpenFile(subDstPath, OpenMode.Write | OpenMode.AllowAppend))
rc = sourceFs.OpenFile(out IFile srcFile, subSrcPath, OpenMode.Read);
if (rc.IsFailure()) return rc;
using (srcFile)
{
logger?.LogMessage(subSrcPath);
srcFile.CopyTo(dstFile, logger);
rc = destFs.OpenFile(out IFile dstFile, subDstPath, OpenMode.Write | OpenMode.AllowAppend);
if (rc.IsFailure()) return rc;
using (dstFile)
{
logger?.LogMessage(subSrcPath);
srcFile.CopyTo(dstFile, logger);
}
}
}
}
return Result.Success;
}
public static void CopyFileSystem(this IFileSystem source, IFileSystem dest, IProgressReport logger = null, CreateFileOptions options = CreateFileOptions.None)
{
IDirectory sourceRoot = source.OpenDirectory("/", OpenDirectoryMode.All);
IDirectory destRoot = dest.OpenDirectory("/", OpenDirectoryMode.All);
source.OpenDirectory(out IDirectory sourceRoot, "/", OpenDirectoryMode.All).ThrowIfFailure();
dest.OpenDirectory(out IDirectory destRoot, "/", OpenDirectoryMode.All).ThrowIfFailure();
sourceRoot.CopyDirectory(destRoot, logger, options);
sourceRoot.CopyDirectory(destRoot, logger, options).ThrowIfFailure();
}
public static void Extract(this IFileSystem source, string destinationPath, IProgressReport logger = null)
@ -67,7 +83,9 @@ namespace LibHac.Fs
public static IEnumerable<DirectoryEntry> EnumerateEntries(this IFileSystem fileSystem, string searchPattern, SearchOptions searchOptions)
{
return fileSystem.OpenDirectory("/", OpenDirectoryMode.All).EnumerateEntries(searchPattern, searchOptions);
fileSystem.OpenDirectory(out IDirectory rootDir, "/", OpenDirectoryMode.All).ThrowIfFailure();
return rootDir.EnumerateEntries(searchPattern, searchOptions);
}
public static IEnumerable<DirectoryEntry> EnumerateEntries(this IDirectory directory)
@ -91,7 +109,7 @@ namespace LibHac.Fs
if (entry.Type != DirectoryEntryType.Directory || !recurse) continue;
IDirectory subDir = fs.OpenDirectory(PathTools.Combine(directory.FullPath, entry.Name), OpenDirectoryMode.All);
fs.OpenDirectory(out IDirectory subDir, PathTools.Combine(directory.FullPath, entry.Name), OpenDirectoryMode.All).ThrowIfFailure();
foreach (DirectoryEntry subEntry in subDir.EnumerateEntries(searchPattern, searchOptions))
{
@ -139,7 +157,9 @@ namespace LibHac.Fs
public static int GetEntryCount(this IFileSystem fs, OpenDirectoryMode mode)
{
return fs.OpenDirectory("/", OpenDirectoryMode.All).GetEntryCountRecursive(mode);
fs.OpenDirectory(out IDirectory rootDir, "/", OpenDirectoryMode.All).ThrowIfFailure();
return rootDir.GetEntryCountRecursive(mode);
}
public static int GetEntryCountRecursive(this IDirectory directory, OpenDirectoryMode mode)
@ -171,7 +191,7 @@ namespace LibHac.Fs
public static void SetConcatenationFileAttribute(this IFileSystem fs, string path)
{
fs.QueryEntry(Span<byte>.Empty, Span<byte>.Empty, path, QueryId.MakeConcatFile);
fs.QueryEntry(Span<byte>.Empty, Span<byte>.Empty, QueryId.MakeConcatFile, path);
}
public static void CleanDirectoryRecursivelyGeneric(IDirectory directory)
@ -184,7 +204,7 @@ namespace LibHac.Fs
if (entry.Type == DirectoryEntryType.Directory)
{
IDirectory subDir = fs.OpenDirectory(subPath, OpenDirectoryMode.All);
fs.OpenDirectory(out IDirectory subDir, subPath, OpenDirectoryMode.All).ThrowIfFailure();
CleanDirectoryRecursivelyGeneric(subDir);
fs.DeleteDirectory(subPath);
@ -208,12 +228,16 @@ namespace LibHac.Fs
public static bool DirectoryExists(this IFileSystem fs, string path)
{
return fs.GetEntryType(path) == DirectoryEntryType.Directory;
Result rc = fs.GetEntryType(out DirectoryEntryType type, path);
return (rc.IsSuccess() && type == DirectoryEntryType.Directory);
}
public static bool FileExists(this IFileSystem fs, string path)
{
return fs.GetEntryType(path) == DirectoryEntryType.File;
Result rc = fs.GetEntryType(out DirectoryEntryType type, path);
return (rc.IsSuccess() && type == DirectoryEntryType.File);
}
public static void EnsureDirectoryExists(this IFileSystem fs, string path)

View File

@ -11,14 +11,15 @@ namespace LibHac.Fs
/// 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>
/// <returns>The <see cref="Result"/> of the requested operation.</returns>
/// <remarks>
/// A <see cref="HorizonResultException"/> will be thrown with the given <see cref="Result"/> under the following conditions:
/// The following <see cref="Result"/> codes may be returned under certain conditions:
///
/// The parent directory of the specified path does not exist: <see cref="ResultFs.PathNotFound"/>
/// Specified path already exists as either a file or directory: <see cref="ResultFs.PathAlreadyExists"/>
/// Insufficient free space to create the directory: <see cref="ResultFs.InsufficientFreeSpace"/>
/// </remarks>
void CreateDirectory(string path);
Result CreateDirectory(string path);
/// <summary>
/// Creates or overwrites a file at the specified path.
@ -27,162 +28,177 @@ namespace LibHac.Fs
/// <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>
/// <returns>The <see cref="Result"/> of the requested operation.</returns>
/// <remarks>
/// A <see cref="HorizonResultException"/> will be thrown with the given <see cref="Result"/> under the following conditions:
/// The following <see cref="Result"/> codes may be returned under certain conditions:
///
/// The parent directory of the specified path does not exist: <see cref="ResultFs.PathNotFound"/>
/// Specified path already exists as either a file or directory: <see cref="ResultFs.PathAlreadyExists"/>
/// Insufficient free space to create the file: <see cref="ResultFs.InsufficientFreeSpace"/>
/// </remarks>
void CreateFile(string path, long size, CreateFileOptions options);
Result 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>
/// <returns>The <see cref="Result"/> of the requested operation.</returns>
/// <remarks>
/// A <see cref="HorizonResultException"/> will be thrown with the given <see cref="Result"/> under the following conditions:
/// The following <see cref="Result"/> codes may be returned under certain conditions:
///
/// The specified path does not exist or is a file: <see cref="ResultFs.PathNotFound"/>
/// The specified directory is not empty: <see cref="ResultFs.DirectoryNotEmpty"/>
/// </remarks>
void DeleteDirectory(string path);
Result DeleteDirectory(string path);
/// <summary>
/// Deletes the specified directory and any subdirectories and files in the directory.
/// </summary>
/// <param name="path">The full path of the directory to delete.</param>
/// <returns>The <see cref="Result"/> of the requested operation.</returns>
/// <remarks>
/// A <see cref="HorizonResultException"/> will be thrown with the given <see cref="Result"/> under the following conditions:
/// The following <see cref="Result"/> codes may be returned under certain conditions:
///
/// The specified path does not exist or is a file: <see cref="ResultFs.PathNotFound"/>
/// </remarks>
void DeleteDirectoryRecursively(string path);
Result DeleteDirectoryRecursively(string path);
/// <summary>
/// Deletes any subdirectories and files in the specified directory.
/// </summary>
/// <param name="path">The full path of the directory to clean.</param>
/// <returns>The <see cref="Result"/> of the requested operation.</returns>
/// <remarks>
/// A <see cref="HorizonResultException"/> will be thrown with the given <see cref="Result"/> under the following conditions:
/// The following <see cref="Result"/> codes may be returned under certain conditions:
///
/// The specified path does not exist or is a file: <see cref="ResultFs.PathNotFound"/>
/// </remarks>
void CleanDirectoryRecursively(string path);
Result CleanDirectoryRecursively(string path);
/// <summary>
/// Deletes the specified file.
/// </summary>
/// <param name="path">The full path of the file to delete.</param>
/// <returns>The <see cref="Result"/> of the requested operation.</returns>
/// <remarks>
/// A <see cref="HorizonResultException"/> will be thrown with the given <see cref="Result"/> under the following conditions:
/// The following <see cref="Result"/> codes may be returned under certain conditions:
///
/// The specified path does not exist or is a directory: <see cref="ResultFs.PathNotFound"/>
/// </remarks>
void DeleteFile(string path);
Result DeleteFile(string path);
/// <summary>
/// Creates an <see cref="IDirectory"/> instance for enumerating the specified directory.
/// </summary>
/// <param name="directory">If the operation returns successfully,
/// An <see cref="IDirectory"/> instance for the specified directory.</param>
/// <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>
/// <returns>The <see cref="Result"/> of the requested operation.</returns>
/// <remarks>
/// A <see cref="HorizonResultException"/> will be thrown with the given <see cref="Result"/> under the following conditions:
/// The following <see cref="Result"/> codes may be returned under certain conditions:
///
/// The specified path does not exist or is a file: <see cref="ResultFs.PathNotFound"/>
/// </remarks>
IDirectory OpenDirectory(string path, OpenDirectoryMode mode);
Result OpenDirectory(out IDirectory directory, string path, OpenDirectoryMode mode);
/// <summary>
/// Opens an <see cref="IFile"/> instance for the specified path.
/// </summary>
/// <param name="file">If the operation returns successfully,
/// An <see cref="IFile"/> instance for the specified path.</param>
/// <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>
/// <returns>The <see cref="Result"/> of the requested operation.</returns>
/// <remarks>
/// A <see cref="HorizonResultException"/> will be thrown with the given <see cref="Result"/> under the following conditions:
/// The following <see cref="Result"/> codes may be returned under certain conditions:
///
/// The specified path does not exist or is a directory: <see cref="ResultFs.PathNotFound"/>
/// </remarks>
IFile OpenFile(string path, OpenMode mode);
Result OpenFile(out IFile file, 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>
/// <returns>An <see cref="IFile"/> instance for the specified path.</returns>
/// <param name="oldPath">The full path of the directory to rename.</param>
/// <param name="newPath">The new full path of the directory.</param>
/// <returns>The <see cref="Result"/> of the requested operation.</returns>
/// <remarks>
/// If <paramref name="srcPath"/> and <paramref name="dstPath"/> are the same, this function does nothing and returns successfully.
/// A <see cref="HorizonResultException"/> will be thrown with the given <see cref="Result"/> under the following conditions:
/// If <paramref name="oldPath"/> and <paramref name="newPath"/> are the same, this function does nothing and returns <see cref="Result.Success"/>.
/// The following <see cref="Result"/> codes may be returned under certain conditions:
///
/// <paramref name="srcPath"/> does not exist or is a file: <see cref="ResultFs.PathNotFound"/>
/// <paramref name="dstPath"/>'s parent directory does not exist: <see cref="ResultFs.PathNotFound"/>
/// <paramref name="dstPath"/> already exists as either a file or directory: <see cref="ResultFs.PathAlreadyExists"/>
/// Either <paramref name="srcPath"/> or <paramref name="dstPath"/> is a subpath of the other: <see cref="ResultFs.DestinationIsSubPathOfSource"/>
/// <paramref name="oldPath"/> does not exist or is a file: <see cref="ResultFs.PathNotFound"/>
/// <paramref name="newPath"/>'s parent directory does not exist: <see cref="ResultFs.PathNotFound"/>
/// <paramref name="newPath"/> already exists as either a file or directory: <see cref="ResultFs.PathAlreadyExists"/>
/// Either <paramref name="oldPath"/> or <paramref name="newPath"/> is a subpath of the other: <see cref="ResultFs.DestinationIsSubPathOfSource"/>
/// </remarks>
void RenameDirectory(string srcPath, string dstPath);
Result RenameDirectory(string oldPath, string newPath);
/// <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>
/// <param name="oldPath">The full path of the file to rename.</param>
/// <param name="newPath">The new full path of the file.</param>
/// <returns>The <see cref="Result"/> of the requested operation.</returns>
/// <remarks>
/// If <paramref name="srcPath"/> and <paramref name="dstPath"/> are the same, this function does nothing and returns successfully.
/// A <see cref="HorizonResultException"/> will be thrown with the given <see cref="Result"/> under the following conditions:
/// If <paramref name="oldPath"/> and <paramref name="newPath"/> are the same, this function does nothing and returns successfully.
/// The following <see cref="Result"/> codes may be returned under certain conditions:
///
/// <paramref name="srcPath"/> does not exist or is a directory: <see cref="ResultFs.PathNotFound"/>
/// <paramref name="dstPath"/>'s parent directory does not exist: <see cref="ResultFs.PathNotFound"/>
/// <paramref name="dstPath"/> already exists as either a file or directory: <see cref="ResultFs.PathAlreadyExists"/>
/// <paramref name="oldPath"/> does not exist or is a directory: <see cref="ResultFs.PathNotFound"/>
/// <paramref name="newPath"/>'s parent directory does not exist: <see cref="ResultFs.PathNotFound"/>
/// <paramref name="newPath"/> already exists as either a file or directory: <see cref="ResultFs.PathAlreadyExists"/>
/// </remarks>
void RenameFile(string srcPath, string dstPath);
Result RenameFile(string oldPath, string newPath);
/// <summary>
/// Determines whether the specified path is a file or directory, or does not exist.
/// </summary>
/// <param name="entryType">If the operation returns successfully, the <see cref="DirectoryEntryType"/> of the file.</param>
/// <param name="path">The full path to check.</param>
/// <returns>The <see cref="DirectoryEntryType"/> of the file.</returns>
/// <returns>The <see cref="Result"/> of the requested operation.</returns>
/// <remarks>
/// This function operates slightly differently than it does in Horizon OS.
/// Instead of returning <see cref="ResultFs.PathNotFound"/> when an entry is missing,
/// the function will return <see cref="DirectoryEntryType.NotFound"/>.
/// </remarks>
DirectoryEntryType GetEntryType(string path);
Result GetEntryType(out DirectoryEntryType entryType, string path);
/// <summary>
/// Gets the amount of available free space on a drive, in bytes.
/// </summary>
/// <param name="freeSpace">If the operation returns successfully, the amount of free space available on the drive, in bytes.</param>
/// <param name="path">The path of the drive to query. Unused in almost all cases.</param>
/// <returns>The amount of free space available on the drive, in bytes.</returns>
long GetFreeSpaceSize(string path);
/// <returns>The <see cref="Result"/> of the requested operation.</returns>
Result GetFreeSpaceSize(out long freeSpace, string path);
/// <summary>
/// Gets the total size of storage space on a drive, in bytes.
/// </summary>
/// <param name="totalSpace">If the operation returns successfully, the total size of the drive, in bytes.</param>
/// <param name="path">The path of the drive to query. Unused in almost all cases.</param>
/// <returns>The total size of the drive, in bytes.</returns>
long GetTotalSpaceSize(string path);
/// <returns>The <see cref="Result"/> of the requested operation.</returns>
Result GetTotalSpaceSize(out long totalSpace, string path);
/// <summary>
/// Gets the creation, last accessed, and last modified timestamps of a file or directory.
/// </summary>
/// <param name="timeStamp">If the operation returns successfully, the timestamps for the specified file or directory.
/// These value are expressed as Unix timestamps.</param>
/// <param name="path">The path of the file or directory.</param>
/// <returns>The timestamps for the specified file or directory.
/// This value is expressed as a Unix timestamp</returns>
/// <returns>The <see cref="Result"/> of the requested operation.</returns>
/// <remarks>
/// A <see cref="HorizonResultException"/> will be thrown with the given <see cref="Result"/> under the following conditions:
/// The following <see cref="Result"/> codes may be returned under certain conditions:
///
/// The specified path does not exist: <see cref="ResultFs.PathNotFound"/>
/// </remarks>
FileTimeStampRaw GetFileTimeStampRaw(string path);
Result GetFileTimeStampRaw(out FileTimeStampRaw timeStamp, string path);
/// <summary>
/// Commits any changes to a transactional file system.
/// Does nothing if called on a non-transactional file system.
/// </summary>
void Commit();
/// <returns>The <see cref="Result"/> of the requested operation.</returns>
Result Commit();
/// <summary>
/// Performs a query on the specified file.
@ -193,9 +209,10 @@ namespace LibHac.Fs
/// May be unused depending on the query type.</param>
/// <param name="inBuffer">The buffer for sending data to the query operation.
/// May be unused depending on the query type.</param>
/// <param name="path">The full path of the file to query.</param>
/// <param name="queryId">The type of query to perform.</param>
void QueryEntry(Span<byte> outBuffer, ReadOnlySpan<byte> inBuffer, string path, QueryId queryId);
/// <param name="path">The full path of the file to query.</param>
/// <returns>The <see cref="Result"/> of the requested operation.</returns>
Result QueryEntry(Span<byte> outBuffer, ReadOnlySpan<byte> inBuffer, QueryId queryId, string path);
}
/// <summary>

View File

@ -12,122 +12,138 @@ namespace LibHac.Fs
Sources.AddRange(sourceFileSystems);
}
public IDirectory OpenDirectory(string path, OpenDirectoryMode mode)
public Result OpenDirectory(out IDirectory directory, string path, OpenDirectoryMode mode)
{
directory = default;
path = PathTools.Normalize(path);
var dirs = new List<IDirectory>();
foreach (IFileSystem fs in Sources)
{
DirectoryEntryType type = fs.GetEntryType(path);
Result rc = fs.GetEntryType(out DirectoryEntryType entryType, path);
if (rc.IsFailure()) return rc;
if (type == DirectoryEntryType.File && dirs.Count == 0)
if (entryType == DirectoryEntryType.File && dirs.Count == 0)
{
ThrowHelper.ThrowResult(ResultFs.PathNotFound);
}
if (fs.GetEntryType(path) == DirectoryEntryType.Directory)
if (entryType == DirectoryEntryType.Directory)
{
dirs.Add(fs.OpenDirectory(path, mode));
rc = fs.OpenDirectory(out IDirectory subDirectory, path, mode);
if (rc.IsFailure()) return rc;
dirs.Add(subDirectory);
}
}
var dir = new LayeredFileSystemDirectory(this, dirs, path, mode);
return dir;
directory = new LayeredFileSystemDirectory(this, dirs, path, mode);
return Result.Success;
}
public IFile OpenFile(string path, OpenMode mode)
public Result OpenFile(out IFile file, string path, OpenMode mode)
{
file = default;
path = PathTools.Normalize(path);
foreach (IFileSystem fs in Sources)
{
DirectoryEntryType type = fs.GetEntryType(path);
Result rc = fs.GetEntryType(out DirectoryEntryType type, path);
if (rc.IsFailure()) return rc;
if (type == DirectoryEntryType.File)
{
return fs.OpenFile(path, mode);
return fs.OpenFile(out file, path, mode);
}
if (type == DirectoryEntryType.Directory)
{
ThrowHelper.ThrowResult(ResultFs.PathNotFound);
return ResultFs.PathNotFound.Log();
}
}
ThrowHelper.ThrowResult(ResultFs.PathNotFound);
return default;
return ResultFs.PathNotFound.Log();
}
public DirectoryEntryType GetEntryType(string path)
public Result GetEntryType(out DirectoryEntryType entryType, string path)
{
path = PathTools.Normalize(path);
foreach (IFileSystem fs in Sources)
{
DirectoryEntryType type = fs.GetEntryType(path);
Result getEntryResult = fs.GetEntryType(out DirectoryEntryType type, path);
if (type != DirectoryEntryType.NotFound) return type;
}
return DirectoryEntryType.NotFound;
}
public FileTimeStampRaw GetFileTimeStampRaw(string path)
{
path = PathTools.Normalize(path);
foreach (IFileSystem fs in Sources)
{
if (fs.GetEntryType(path) != DirectoryEntryType.NotFound)
if (getEntryResult.IsSuccess() && type != DirectoryEntryType.NotFound)
{
return fs.GetFileTimeStampRaw(path);
entryType = type;
return Result.Success;
}
}
ThrowHelper.ThrowResult(ResultFs.PathNotFound);
return default;
entryType = DirectoryEntryType.NotFound;
return ResultFs.PathNotFound.Log();
}
public void QueryEntry(Span<byte> outBuffer, ReadOnlySpan<byte> inBuffer, string path, QueryId queryId)
public Result GetFileTimeStampRaw(out FileTimeStampRaw timeStamp, string path)
{
path = PathTools.Normalize(path);
foreach (IFileSystem fs in Sources)
{
if (fs.GetEntryType(path) != DirectoryEntryType.NotFound)
Result getEntryResult = fs.GetEntryType(out DirectoryEntryType type, path);
if (getEntryResult.IsSuccess() && type != DirectoryEntryType.NotFound)
{
fs.QueryEntry(outBuffer, inBuffer, path, queryId);
return;
return fs.GetFileTimeStampRaw(out timeStamp, path);
}
}
ThrowHelper.ThrowResult(ResultFs.PathNotFound);
timeStamp = default;
return ResultFs.PathNotFound.Log();
}
public void Commit() { }
public void CreateDirectory(string path) => ThrowHelper.ThrowResult(ResultFs.UnsupportedOperation);
public void CreateFile(string path, long size, CreateFileOptions options) => ThrowHelper.ThrowResult(ResultFs.UnsupportedOperation);
public void DeleteDirectory(string path) => ThrowHelper.ThrowResult(ResultFs.UnsupportedOperation);
public void DeleteDirectoryRecursively(string path) => ThrowHelper.ThrowResult(ResultFs.UnsupportedOperation);
public void CleanDirectoryRecursively(string path) => ThrowHelper.ThrowResult(ResultFs.UnsupportedOperation);
public void DeleteFile(string path) => ThrowHelper.ThrowResult(ResultFs.UnsupportedOperation);
public void RenameDirectory(string srcPath, string dstPath) => ThrowHelper.ThrowResult(ResultFs.UnsupportedOperation);
public void RenameFile(string srcPath, string dstPath) => ThrowHelper.ThrowResult(ResultFs.UnsupportedOperation);
public long GetFreeSpaceSize(string path)
public Result QueryEntry(Span<byte> outBuffer, ReadOnlySpan<byte> inBuffer, QueryId queryId, string path)
{
ThrowHelper.ThrowResult(ResultFs.UnsupportedOperation);
return default;
path = PathTools.Normalize(path);
foreach (IFileSystem fs in Sources)
{
Result getEntryResult = fs.GetEntryType(out DirectoryEntryType type, path);
if (getEntryResult.IsSuccess() && type != DirectoryEntryType.NotFound)
{
return fs.QueryEntry(outBuffer, inBuffer, queryId, path);
}
}
return ResultFs.PathNotFound.Log();
}
public long GetTotalSpaceSize(string path)
public Result Commit()
{
ThrowHelper.ThrowResult(ResultFs.UnsupportedOperation);
return default;
return Result.Success;
}
public Result CreateDirectory(string path) => ResultFs.UnsupportedOperation.Log();
public Result CreateFile(string path, long size, CreateFileOptions options) => ResultFs.UnsupportedOperation.Log();
public Result DeleteDirectory(string path) => ResultFs.UnsupportedOperation.Log();
public Result DeleteDirectoryRecursively(string path) => ResultFs.UnsupportedOperation.Log();
public Result CleanDirectoryRecursively(string path) => ResultFs.UnsupportedOperation.Log();
public Result DeleteFile(string path) => ResultFs.UnsupportedOperation.Log();
public Result RenameDirectory(string oldPath, string newPath) => ResultFs.UnsupportedOperation.Log();
public Result RenameFile(string oldPath, string newPath) => ResultFs.UnsupportedOperation.Log();
public Result GetFreeSpaceSize(out long freeSpace, string path)
{
freeSpace = default;
return ResultFs.UnsupportedOperation.Log();
}
public Result GetTotalSpaceSize(out long totalSpace, string path)
{
totalSpace = default;
return ResultFs.UnsupportedOperation.Log();
}
}
}

View File

@ -58,7 +58,7 @@ namespace LibHac.Fs
return GetSizeInternal(info);
}
public void CreateDirectory(string path)
public Result CreateDirectory(string path)
{
string localPath = ResolveLocalPath(PathTools.Normalize(path));
@ -66,18 +66,18 @@ namespace LibHac.Fs
if (dir.Exists)
{
ThrowHelper.ThrowResult(ResultFs.PathAlreadyExists);
return ResultFs.PathAlreadyExists.Log();
}
if (dir.Parent?.Exists != true)
{
ThrowHelper.ThrowResult(ResultFs.PathNotFound);
return ResultFs.PathNotFound.Log();
}
CreateDirInternal(dir);
return CreateDirInternal(dir);
}
public void CreateFile(string path, long size, CreateFileOptions options)
public Result CreateFile(string path, long size, CreateFileOptions options)
{
string localPath = ResolveLocalPath(PathTools.Normalize(path));
@ -85,121 +85,140 @@ namespace LibHac.Fs
if (file.Exists)
{
ThrowHelper.ThrowResult(ResultFs.PathAlreadyExists);
return ResultFs.PathAlreadyExists.Log();
}
if (file.Directory?.Exists != true)
{
ThrowHelper.ThrowResult(ResultFs.PathNotFound);
return ResultFs.PathNotFound.Log();
}
using (FileStream stream = CreateFileInternal(file))
Result rc = CreateFileInternal(out FileStream stream, file);
using (stream)
{
SetStreamLengthInternal(stream, size);
if (rc.IsFailure()) return rc;
return SetStreamLengthInternal(stream, size);
}
}
public void DeleteDirectory(string path)
public Result DeleteDirectory(string path)
{
string localPath = ResolveLocalPath(PathTools.Normalize(path));
DirectoryInfo dir = GetDirInfo(localPath);
DeleteDirectoryInternal(dir, false);
return DeleteDirectoryInternal(dir, false);
}
public void DeleteDirectoryRecursively(string path)
public Result DeleteDirectoryRecursively(string path)
{
string localPath = ResolveLocalPath(PathTools.Normalize(path));
DirectoryInfo dir = GetDirInfo(localPath);
DeleteDirectoryInternal(dir, true);
return DeleteDirectoryInternal(dir, true);
}
public void CleanDirectoryRecursively(string path)
public Result CleanDirectoryRecursively(string path)
{
string localPath = ResolveLocalPath(PathTools.Normalize(path));
foreach (string file in Directory.EnumerateFiles(localPath))
{
DeleteFileInternal(GetFileInfo(file));
Result rc = DeleteFileInternal(GetFileInfo(file));
if (rc.IsFailure()) return rc;
}
foreach (string dir in Directory.EnumerateDirectories(localPath))
{
DeleteDirectoryInternal(GetDirInfo(dir), true);
Result rc = DeleteDirectoryInternal(GetDirInfo(dir), true);
if (rc.IsFailure()) return rc;
}
return Result.Success;
}
public void DeleteFile(string path)
public Result DeleteFile(string path)
{
string localPath = ResolveLocalPath(PathTools.Normalize(path));
FileInfo file = GetFileInfo(localPath);
DeleteFileInternal(file);
return DeleteFileInternal(file);
}
public IDirectory OpenDirectory(string path, OpenDirectoryMode mode)
public Result OpenDirectory(out IDirectory directory, string path, OpenDirectoryMode mode)
{
// Getting the local path is done in the LocalDirectory constructor
path = PathTools.Normalize(path);
directory = default;
if (GetEntryType(path) == DirectoryEntryType.File)
Result rc = GetEntryType(out DirectoryEntryType entryType, path);
if (rc.IsFailure()) return rc;
if (entryType == DirectoryEntryType.File)
{
ThrowHelper.ThrowResult(ResultFs.PathNotFound);
return ResultFs.PathNotFound.Log();
}
return new LocalDirectory(this, path, mode);
directory = new LocalDirectory(this, path, mode);
return Result.Success;
}
public IFile OpenFile(string path, OpenMode mode)
public Result OpenFile(out IFile file, string path, OpenMode mode)
{
file = default;
string localPath = ResolveLocalPath(PathTools.Normalize(path));
if (GetEntryType(path) == DirectoryEntryType.Directory)
Result rc = GetEntryType(out DirectoryEntryType entryType, path);
if (rc.IsFailure()) return rc;
if (entryType == DirectoryEntryType.Directory)
{
ThrowHelper.ThrowResult(ResultFs.PathNotFound);
return ResultFs.PathNotFound.Log();
}
return new LocalFile(localPath, mode);
file = new LocalFile(localPath, mode);
return Result.Success;
}
public void RenameDirectory(string srcPath, string dstPath)
public Result RenameDirectory(string oldPath, string newPath)
{
srcPath = PathTools.Normalize(srcPath);
dstPath = PathTools.Normalize(dstPath);
oldPath = PathTools.Normalize(oldPath);
newPath = PathTools.Normalize(newPath);
// Official FS behavior is to do nothing in this case
if (srcPath == dstPath) return;
if (oldPath == newPath) return Result.Success;
// FS does the subpath check before verifying the path exists
if (PathTools.IsSubPath(srcPath.AsSpan(), dstPath.AsSpan()))
if (PathTools.IsSubPath(oldPath.AsSpan(), newPath.AsSpan()))
{
ThrowHelper.ThrowResult(ResultFs.DestinationIsSubPathOfSource);
}
DirectoryInfo srcDir = GetDirInfo(ResolveLocalPath(srcPath));
DirectoryInfo dstDir = GetDirInfo(ResolveLocalPath(dstPath));
DirectoryInfo srcDir = GetDirInfo(ResolveLocalPath(oldPath));
DirectoryInfo dstDir = GetDirInfo(ResolveLocalPath(newPath));
RenameDirInternal(srcDir, dstDir);
return RenameDirInternal(srcDir, dstDir);
}
public void RenameFile(string srcPath, string dstPath)
public Result RenameFile(string oldPath, string newPath)
{
string srcLocalPath = ResolveLocalPath(PathTools.Normalize(srcPath));
string dstLocalPath = ResolveLocalPath(PathTools.Normalize(dstPath));
string srcLocalPath = ResolveLocalPath(PathTools.Normalize(oldPath));
string dstLocalPath = ResolveLocalPath(PathTools.Normalize(newPath));
// Official FS behavior is to do nothing in this case
if (srcLocalPath == dstLocalPath) return;
if (srcLocalPath == dstLocalPath) return Result.Success;
FileInfo srcFile = GetFileInfo(srcLocalPath);
FileInfo dstFile = GetFileInfo(dstLocalPath);
RenameFileInternal(srcFile, dstFile);
return RenameFileInternal(srcFile, dstFile);
}
public DirectoryEntryType GetEntryType(string path)
public Result GetEntryType(out DirectoryEntryType entryType, string path)
{
string localPath = ResolveLocalPath(PathTools.Normalize(path));
@ -207,48 +226,57 @@ namespace LibHac.Fs
if (dir.Exists)
{
return DirectoryEntryType.Directory;
entryType = DirectoryEntryType.Directory;
return Result.Success;
}
FileInfo file = GetFileInfo(localPath);
if (file.Exists)
{
return DirectoryEntryType.File;
entryType = DirectoryEntryType.File;
return Result.Success;
}
return DirectoryEntryType.NotFound;
entryType = DirectoryEntryType.NotFound;
return ResultFs.PathNotFound.Log();
}
public FileTimeStampRaw GetFileTimeStampRaw(string path)
public Result GetFileTimeStampRaw(out FileTimeStampRaw timeStamp, string path)
{
timeStamp = default;
string localPath = ResolveLocalPath(PathTools.Normalize(path));
if (!GetFileInfo(localPath).Exists) ThrowHelper.ThrowResult(ResultFs.PathNotFound);
FileTimeStampRaw timeStamp = default;
if (!GetFileInfo(localPath).Exists) return ResultFs.PathNotFound.Log();
timeStamp.Created = new DateTimeOffset(File.GetCreationTime(localPath)).ToUnixTimeSeconds();
timeStamp.Accessed = new DateTimeOffset(File.GetLastAccessTime(localPath)).ToUnixTimeSeconds();
timeStamp.Modified = new DateTimeOffset(File.GetLastWriteTime(localPath)).ToUnixTimeSeconds();
return timeStamp;
return Result.Success;
}
public long GetFreeSpaceSize(string path)
public Result GetFreeSpaceSize(out long freeSpace, string path)
{
return new DriveInfo(BasePath).AvailableFreeSpace;
freeSpace = new DriveInfo(BasePath).AvailableFreeSpace;
return Result.Success;
}
public long GetTotalSpaceSize(string path)
public Result GetTotalSpaceSize(out long totalSpace, string path)
{
return new DriveInfo(BasePath).TotalSize;
totalSpace = new DriveInfo(BasePath).TotalSize;
return Result.Success;
}
public void Commit() { }
public Result Commit()
{
return Result.Success;
}
public void QueryEntry(Span<byte> outBuffer, ReadOnlySpan<byte> inBuffer, string path, QueryId queryId) =>
ThrowHelper.ThrowResult(ResultFs.UnsupportedOperation);
public Result QueryEntry(Span<byte> outBuffer, ReadOnlySpan<byte> inBuffer, QueryId queryId, string path)
{
return ResultFs.UnsupportedOperation.Log();
}
private static long GetSizeInternal(FileInfo file)
{
@ -268,35 +296,36 @@ namespace LibHac.Fs
}
}
private static FileStream CreateFileInternal(FileInfo file)
private static Result CreateFileInternal(out FileStream file, FileInfo fileInfo)
{
file = default;
try
{
return new FileStream(file.FullName, FileMode.CreateNew, FileAccess.ReadWrite);
file = new FileStream(fileInfo.FullName, FileMode.CreateNew, FileAccess.ReadWrite);
}
catch (DirectoryNotFoundException ex)
catch (DirectoryNotFoundException)
{
ThrowHelper.ThrowResult(ResultFs.PathNotFound, ex);
throw;
return ResultFs.PathNotFound.Log();
}
catch (IOException ex) when (ex.HResult == ErrorDiskFull || ex.HResult == ErrorHandleDiskFull)
{
ThrowHelper.ThrowResult(ResultFs.InsufficientFreeSpace, ex);
throw;
return ResultFs.InsufficientFreeSpace.Log();
}
catch (IOException ex) when (ex.HResult == ErrorFileExists)
{
ThrowHelper.ThrowResult(ResultFs.PathAlreadyExists, ex);
throw;
return ResultFs.PathAlreadyExists.Log();
}
catch (Exception ex) when (ex is SecurityException || ex is UnauthorizedAccessException)
{
// todo: Should a HorizonResultException be thrown?
// todo: What Result value should be returned?
throw;
}
return Result.Success;
}
private static void SetStreamLengthInternal(Stream stream, long size)
private static Result SetStreamLengthInternal(Stream stream, long size)
{
try
{
@ -304,128 +333,131 @@ namespace LibHac.Fs
}
catch (IOException ex) when (ex.HResult == ErrorDiskFull || ex.HResult == ErrorHandleDiskFull)
{
ThrowHelper.ThrowResult(ResultFs.InsufficientFreeSpace, ex);
throw;
return ResultFs.InsufficientFreeSpace.Log();
}
return Result.Success;
}
private static void DeleteDirectoryInternal(DirectoryInfo dir, bool recursive)
private static Result DeleteDirectoryInternal(DirectoryInfo dir, bool recursive)
{
if (!dir.Exists) ThrowHelper.ThrowResult(ResultFs.PathNotFound);
if (!dir.Exists) return ResultFs.PathNotFound.Log();
try
{
dir.Delete(recursive);
}
catch (DirectoryNotFoundException ex)
catch (DirectoryNotFoundException)
{
ThrowHelper.ThrowResult(ResultFs.PathNotFound, ex);
throw;
return ResultFs.PathNotFound.Log();
}
catch (IOException ex) when (ex.HResult == ErrorDirNotEmpty)
{
ThrowHelper.ThrowResult(ResultFs.DirectoryNotEmpty, ex);
throw;
return ResultFs.DirectoryNotEmpty.Log();
}
catch (Exception ex) when (ex is IOException || ex is UnauthorizedAccessException)
{
// todo: Should a HorizonResultException be thrown?
// todo: What Result value should be returned?
throw;
}
EnsureDeleted(dir);
return Result.Success;
}
private static void DeleteFileInternal(FileInfo file)
private static Result DeleteFileInternal(FileInfo file)
{
if (!file.Exists) ThrowHelper.ThrowResult(ResultFs.PathNotFound);
if (!file.Exists) return ResultFs.PathNotFound.Log();
try
{
file.Delete();
}
catch (DirectoryNotFoundException ex)
catch (DirectoryNotFoundException)
{
ThrowHelper.ThrowResult(ResultFs.PathNotFound, ex);
throw;
return ResultFs.PathNotFound.Log();
}
catch (IOException ex) when (ex.HResult == ErrorDirNotEmpty)
{
ThrowHelper.ThrowResult(ResultFs.DirectoryNotEmpty, ex);
throw;
return ResultFs.DirectoryNotEmpty.Log();
}
catch (Exception ex) when (ex is IOException || ex is UnauthorizedAccessException)
{
// todo: Should a HorizonResultException be thrown?
// todo: What Result value should be returned?
throw;
}
EnsureDeleted(file);
return Result.Success;
}
private static void CreateDirInternal(DirectoryInfo dir)
private static Result CreateDirInternal(DirectoryInfo dir)
{
try
{
dir.Create();
}
catch (DirectoryNotFoundException ex)
catch (DirectoryNotFoundException)
{
ThrowHelper.ThrowResult(ResultFs.PathNotFound, ex);
throw;
return ResultFs.PathNotFound.Log();
}
catch (IOException ex) when (ex.HResult == ErrorDiskFull || ex.HResult == ErrorHandleDiskFull)
{
ThrowHelper.ThrowResult(ResultFs.InsufficientFreeSpace, ex);
throw;
return ResultFs.InsufficientFreeSpace.Log();
}
catch (Exception ex) when (ex is SecurityException || ex is UnauthorizedAccessException)
{
// todo: Should a HorizonResultException be thrown?
// todo: What Result value should be returned?
throw;
}
return Result.Success;
}
private static void RenameDirInternal(DirectoryInfo source, DirectoryInfo dest)
private static Result RenameDirInternal(DirectoryInfo source, DirectoryInfo dest)
{
if (!source.Exists) ThrowHelper.ThrowResult(ResultFs.PathNotFound);
if (dest.Exists) ThrowHelper.ThrowResult(ResultFs.PathAlreadyExists);
if (!source.Exists) return ResultFs.PathNotFound.Log();
if (dest.Exists) return ResultFs.PathAlreadyExists.Log();
try
{
source.MoveTo(dest.FullName);
}
catch (DirectoryNotFoundException ex)
catch (DirectoryNotFoundException)
{
ThrowHelper.ThrowResult(ResultFs.PathNotFound, ex);
throw;
return ResultFs.PathNotFound.Log();
}
catch (Exception ex) when (ex is IOException || ex is UnauthorizedAccessException)
{
// todo: Should a HorizonResultException be thrown?
// todo: What Result value should be returned?
throw;
}
return Result.Success;
}
private static void RenameFileInternal(FileInfo source, FileInfo dest)
private static Result RenameFileInternal(FileInfo source, FileInfo dest)
{
if (!source.Exists) ThrowHelper.ThrowResult(ResultFs.PathNotFound);
if (dest.Exists) ThrowHelper.ThrowResult(ResultFs.PathAlreadyExists);
if (!source.Exists) return ResultFs.PathNotFound.Log();
if (dest.Exists) return ResultFs.PathAlreadyExists.Log();
try
{
source.MoveTo(dest.FullName);
}
catch (DirectoryNotFoundException ex)
catch (DirectoryNotFoundException)
{
ThrowHelper.ThrowResult(ResultFs.PathNotFound, ex);
throw;
return ResultFs.PathNotFound.Log();
}
catch (Exception ex) when (ex is IOException || ex is UnauthorizedAccessException)
{
// todo: Should a HorizonResultException be thrown?
// todo: What Result value should be returned?
throw;
}
return Result.Success;
}

View File

@ -29,12 +29,13 @@ namespace LibHac.Fs
BaseStorage = storage;
}
public IDirectory OpenDirectory(string path, OpenDirectoryMode mode)
public Result OpenDirectory(out IDirectory directory, string path, OpenDirectoryMode mode)
{
return new PartitionDirectory(this, path, mode);
directory = new PartitionDirectory(this, path, mode);
return Result.Success;
}
public IFile OpenFile(string path, OpenMode mode)
public Result OpenFile(out IFile file, string path, OpenMode mode)
{
path = PathTools.Normalize(path).TrimStart('/');
@ -43,7 +44,8 @@ namespace LibHac.Fs
ThrowHelper.ThrowResult(ResultFs.PathNotFound);
}
return OpenFile(entry, mode);
file = OpenFile(entry, mode);
return Result.Success;
}
public IFile OpenFile(PartitionFileEntry entry, OpenMode mode)
@ -51,46 +53,59 @@ namespace LibHac.Fs
return new PartitionFile(BaseStorage, HeaderSize + entry.Offset, entry.Size, mode);
}
public DirectoryEntryType GetEntryType(string path)
public Result GetEntryType(out DirectoryEntryType entryType, string path)
{
entryType = DirectoryEntryType.NotFound;
path = PathTools.Normalize(path);
if (path == "/") return DirectoryEntryType.Directory;
if (path == "/")
{
entryType = DirectoryEntryType.Directory;
return Result.Success;
}
if (FileDict.ContainsKey(path.TrimStart('/'))) return DirectoryEntryType.File;
if (FileDict.ContainsKey(path.TrimStart('/')))
{
entryType = DirectoryEntryType.File;
return Result.Success;
}
return DirectoryEntryType.NotFound;
return ResultFs.PathNotFound.Log();
}
public void CreateDirectory(string path) => ThrowHelper.ThrowResult(ResultFs.UnsupportedOperationModifyPartitionFileSystem);
public void CreateFile(string path, long size, CreateFileOptions options) => ThrowHelper.ThrowResult(ResultFs.UnsupportedOperationModifyPartitionFileSystem);
public void DeleteDirectory(string path) => ThrowHelper.ThrowResult(ResultFs.UnsupportedOperationModifyPartitionFileSystem);
public void DeleteDirectoryRecursively(string path) => ThrowHelper.ThrowResult(ResultFs.UnsupportedOperationModifyPartitionFileSystem);
public void CleanDirectoryRecursively(string path) => ThrowHelper.ThrowResult(ResultFs.UnsupportedOperationModifyPartitionFileSystem);
public void DeleteFile(string path) => ThrowHelper.ThrowResult(ResultFs.UnsupportedOperationModifyPartitionFileSystem);
public void RenameDirectory(string srcPath, string dstPath) => ThrowHelper.ThrowResult(ResultFs.UnsupportedOperationModifyPartitionFileSystem);
public void RenameFile(string srcPath, string dstPath) => ThrowHelper.ThrowResult(ResultFs.UnsupportedOperationModifyPartitionFileSystem);
public Result CreateDirectory(string path) => ResultFs.UnsupportedOperationModifyPartitionFileSystem.Log();
public Result CreateFile(string path, long size, CreateFileOptions options) => ResultFs.UnsupportedOperationModifyPartitionFileSystem.Log();
public Result DeleteDirectory(string path) => ResultFs.UnsupportedOperationModifyPartitionFileSystem.Log();
public Result DeleteDirectoryRecursively(string path) => ResultFs.UnsupportedOperationModifyPartitionFileSystem.Log();
public Result CleanDirectoryRecursively(string path) => ResultFs.UnsupportedOperationModifyPartitionFileSystem.Log();
public Result DeleteFile(string path) => ResultFs.UnsupportedOperationModifyPartitionFileSystem.Log();
public Result RenameDirectory(string oldPath, string newPath) => ResultFs.UnsupportedOperationModifyPartitionFileSystem.Log();
public Result RenameFile(string oldPath, string newPath) => ResultFs.UnsupportedOperationModifyPartitionFileSystem.Log();
public long GetFreeSpaceSize(string path)
public Result GetFreeSpaceSize(out long freeSpace, string path)
{
ThrowHelper.ThrowResult(ResultFs.NotImplemented);
return default;
freeSpace = default;
return ResultFs.NotImplemented.Log();
}
public long GetTotalSpaceSize(string path)
public Result GetTotalSpaceSize(out long totalSpace, string path)
{
ThrowHelper.ThrowResult(ResultFs.NotImplemented);
return default;
totalSpace = default;
return ResultFs.NotImplemented.Log();
}
public FileTimeStampRaw GetFileTimeStampRaw(string path)
public Result GetFileTimeStampRaw(out FileTimeStampRaw timeStamp, string path)
{
ThrowHelper.ThrowResult(ResultFs.NotImplemented);
return default;
timeStamp = default;
return ResultFs.NotImplemented.Log();
}
public void Commit() { }
public void QueryEntry(Span<byte> outBuffer, ReadOnlySpan<byte> inBuffer, string path, QueryId queryId) => ThrowHelper.ThrowResult(ResultFs.NotImplemented);
public Result Commit()
{
return Result.Success;
}
public Result QueryEntry(Span<byte> outBuffer, ReadOnlySpan<byte> inBuffer, QueryId queryId, string path) => ResultFs.NotImplemented.Log();
}
public enum PartitionFileSystemType

View File

@ -22,11 +22,13 @@ namespace LibHac.Fs
/// </summary>
public PartitionFileSystemBuilder(IFileSystem input)
{
IDirectory rootDir = input.OpenDirectory("/", OpenDirectoryMode.File);
input.OpenDirectory(out IDirectory rootDir, "/", OpenDirectoryMode.File).ThrowIfFailure();
foreach (DirectoryEntry file in rootDir.Read().OrderBy(x => x.FullPath, StringComparer.Ordinal))
foreach (DirectoryEntry entry in rootDir.Read().OrderBy(x => x.FullPath, StringComparer.Ordinal))
{
AddFile(file.FullPath.TrimStart('/'), input.OpenFile(file.FullPath, OpenMode.Read));
input.OpenFile(out IFile file, entry.FullPath, OpenMode.Read).ThrowIfFailure();
AddFile(entry.FullPath.TrimStart('/'), file);
}
}

View File

@ -11,71 +11,82 @@ namespace LibHac.Fs
BaseFs = baseFileSystem;
}
public IDirectory OpenDirectory(string path, OpenDirectoryMode mode)
public Result OpenDirectory(out IDirectory directory, string path, OpenDirectoryMode mode)
{
IDirectory baseDir = BaseFs.OpenDirectory(path, mode);
return new ReadOnlyDirectory(this, baseDir);
directory = default;
Result rc = BaseFs.OpenDirectory(out IDirectory baseDir, path, mode);
if (rc.IsFailure()) return rc;
directory = new ReadOnlyDirectory(this, baseDir);
return Result.Success;
}
public IFile OpenFile(string path, OpenMode mode)
public Result OpenFile(out IFile file, string path, OpenMode mode)
{
IFile baseFile = BaseFs.OpenFile(path, mode);
return new ReadOnlyFile(baseFile);
file = default;
Result rc = BaseFs.OpenFile(out IFile baseFile, path, mode);
if (rc.IsFailure()) return rc;
file = new ReadOnlyFile(baseFile);
return Result.Success;
}
public DirectoryEntryType GetEntryType(string path)
public Result GetEntryType(out DirectoryEntryType entryType, string path)
{
return BaseFs.GetEntryType(path);
return BaseFs.GetEntryType(out entryType, path);
}
public long GetFreeSpaceSize(string path)
public Result GetFreeSpaceSize(out long freeSpace, string path)
{
return 0;
freeSpace = 0;
return Result.Success;
// FS does:
// return ResultFs.UnsupportedOperationReadOnlyFileSystemGetSpace.Log();
}
public long GetTotalSpaceSize(string path)
public Result GetTotalSpaceSize(out long totalSpace, string path)
{
return BaseFs.GetTotalSpaceSize(path);
return BaseFs.GetTotalSpaceSize(out totalSpace, path);
// FS does:
// return ResultFs.UnsupportedOperationReadOnlyFileSystemGetSpace.Log();
}
public FileTimeStampRaw GetFileTimeStampRaw(string path)
public Result GetFileTimeStampRaw(out FileTimeStampRaw timeStamp, string path)
{
return BaseFs.GetFileTimeStampRaw(path);
return BaseFs.GetFileTimeStampRaw(out timeStamp, path);
// FS does:
// return ResultFs.NotImplemented.Log();
}
public void Commit()
public Result Commit()
{
return Result.Success;
}
public void QueryEntry(Span<byte> outBuffer, ReadOnlySpan<byte> inBuffer, string path, QueryId queryId)
public Result QueryEntry(Span<byte> outBuffer, ReadOnlySpan<byte> inBuffer, QueryId queryId, string path)
{
BaseFs.QueryEntry(outBuffer, inBuffer, path, queryId);
return ResultFs.NotImplemented.Log();
}
public void CreateDirectory(string path) =>
ThrowHelper.ThrowResult(ResultFs.UnsupportedOperationModifyReadOnlyFileSystem);
public Result CreateDirectory(string path) => ResultFs.UnsupportedOperationModifyReadOnlyFileSystem.Log();
public void CreateFile(string path, long size, CreateFileOptions options) =>
ThrowHelper.ThrowResult(ResultFs.UnsupportedOperationModifyReadOnlyFileSystem);
public Result CreateFile(string path, long size, CreateFileOptions options) => ResultFs.UnsupportedOperationModifyReadOnlyFileSystem.Log();
public void DeleteDirectory(string path) =>
ThrowHelper.ThrowResult(ResultFs.UnsupportedOperationModifyReadOnlyFileSystem);
public Result DeleteDirectory(string path) => ResultFs.UnsupportedOperationModifyReadOnlyFileSystem.Log();
public void DeleteDirectoryRecursively(string path) =>
ThrowHelper.ThrowResult(ResultFs.UnsupportedOperationModifyReadOnlyFileSystem);
public Result DeleteDirectoryRecursively(string path) => ResultFs.UnsupportedOperationModifyReadOnlyFileSystem.Log();
public void CleanDirectoryRecursively(string path) =>
ThrowHelper.ThrowResult(ResultFs.UnsupportedOperationModifyReadOnlyFileSystem);
public Result CleanDirectoryRecursively(string path) => ResultFs.UnsupportedOperationModifyReadOnlyFileSystem.Log();
public void DeleteFile(string path) =>
ThrowHelper.ThrowResult(ResultFs.UnsupportedOperationModifyReadOnlyFileSystem);
public Result DeleteFile(string path) => ResultFs.UnsupportedOperationModifyReadOnlyFileSystem.Log();
public void RenameDirectory(string srcPath, string dstPath) =>
ThrowHelper.ThrowResult(ResultFs.UnsupportedOperationModifyReadOnlyFileSystem);
public void RenameFile(string srcPath, string dstPath) =>
ThrowHelper.ThrowResult(ResultFs.UnsupportedOperationModifyReadOnlyFileSystem);
public Result RenameDirectory(string oldPath, string newPath) => ResultFs.UnsupportedOperationModifyReadOnlyFileSystem.Log();
public Result RenameFile(string oldPath, string newPath) => ResultFs.UnsupportedOperationModifyReadOnlyFileSystem.Log();
}
}

View File

@ -11,7 +11,6 @@
public static Result InsufficientFreeSpace => new Result(ModuleFs, 30);
public static Result MountNameAlreadyExists => new Result(ModuleFs, 60);
public static Result ResultPartitionNotFound => new Result(ModuleFs, 1002);
public static Result TargetNotFound => new Result(ModuleFs, 1002);
public static Result NotImplemented => new Result(ModuleFs, 3001);

View File

@ -32,10 +32,12 @@ namespace LibHac.Fs.RomFs
/// </summary>
public RomFsBuilder(IFileSystem input)
{
foreach (DirectoryEntry file in input.EnumerateEntries().Where(x => x.Type == DirectoryEntryType.File)
foreach (DirectoryEntry entry in input.EnumerateEntries().Where(x => x.Type == DirectoryEntryType.File)
.OrderBy(x => x.FullPath, StringComparer.Ordinal))
{
AddFile(file.FullPath, input.OpenFile(file.FullPath, OpenMode.Read));
input.OpenFile(out IFile file, entry.FullPath, OpenMode.Read).ThrowIfFailure();
AddFile(entry.FullPath, file);
}
}

View File

@ -22,52 +22,63 @@ namespace LibHac.Fs.RomFs
FileTable = new HierarchicalRomFileTable<RomFileInfo>(dirHashTable, dirEntryTable, fileHashTable, fileEntryTable);
}
public DirectoryEntryType GetEntryType(string path)
public Result GetEntryType(out DirectoryEntryType entryType, string path)
{
entryType = DirectoryEntryType.NotFound;
path = PathTools.Normalize(path);
if (FileTable.TryOpenFile(path, out RomFileInfo _))
{
return DirectoryEntryType.File;
entryType = DirectoryEntryType.File;
return Result.Success;
}
if (FileTable.TryOpenDirectory(path, out FindPosition _))
{
return DirectoryEntryType.Directory;
entryType = DirectoryEntryType.Directory;
return Result.Success;
}
return DirectoryEntryType.NotFound;
return ResultFs.PathNotFound.Log();
}
public void Commit() { }
public IDirectory OpenDirectory(string path, OpenDirectoryMode mode)
public Result Commit()
{
return Result.Success;
}
public Result OpenDirectory(out IDirectory directory, string path, OpenDirectoryMode mode)
{
directory = default;
path = PathTools.Normalize(path);
if (!FileTable.TryOpenDirectory(path, out FindPosition position))
{
ThrowHelper.ThrowResult(ResultFs.PathNotFound);
return ResultFs.PathNotFound.Log();
}
return new RomFsDirectory(this, path, position, mode);
directory = new RomFsDirectory(this, path, position, mode);
return Result.Success;
}
public IFile OpenFile(string path, OpenMode mode)
public Result OpenFile(out IFile file, string path, OpenMode mode)
{
file = default;
path = PathTools.Normalize(path);
if (!FileTable.TryOpenFile(path, out RomFileInfo info))
{
ThrowHelper.ThrowResult(ResultFs.PathNotFound);
return ResultFs.PathNotFound.Log();
}
if (mode != OpenMode.Read)
{
ThrowHelper.ThrowResult(ResultFs.InvalidArgument, "RomFs files must be opened read-only.");
// RomFs files must be opened read-only.
return ResultFs.InvalidArgument.Log();
}
return new RomFsFile(BaseStorage, Header.DataOffset + info.Offset, info.Length);
file = new RomFsFile(BaseStorage, Header.DataOffset + info.Offset, info.Length);
return Result.Success;
}
public IStorage GetBaseStorage()
@ -75,50 +86,37 @@ namespace LibHac.Fs.RomFs
return BaseStorage;
}
public void CreateDirectory(string path) =>
ThrowHelper.ThrowResult(ResultFs.UnsupportedOperationModifyRomFsFileSystem);
public Result CreateDirectory(string path) => ResultFs.UnsupportedOperationModifyRomFsFileSystem.Log();
public Result CreateFile(string path, long size, CreateFileOptions options) => ResultFs.UnsupportedOperationModifyRomFsFileSystem.Log();
public Result DeleteDirectory(string path) => ResultFs.UnsupportedOperationModifyRomFsFileSystem.Log();
public Result DeleteDirectoryRecursively(string path) => ResultFs.UnsupportedOperationModifyRomFsFileSystem.Log();
public Result CleanDirectoryRecursively(string path) => ResultFs.UnsupportedOperationModifyRomFsFileSystem.Log();
public Result DeleteFile(string path) => ResultFs.UnsupportedOperationModifyRomFsFileSystem.Log();
public Result RenameDirectory(string oldPath, string newPath) => ResultFs.UnsupportedOperationModifyRomFsFileSystem.Log();
public Result RenameFile(string oldPath, string newPath) => ResultFs.UnsupportedOperationModifyRomFsFileSystem.Log();
public void CreateFile(string path, long size, CreateFileOptions options) =>
ThrowHelper.ThrowResult(ResultFs.UnsupportedOperationModifyRomFsFileSystem);
public void DeleteDirectory(string path) =>
ThrowHelper.ThrowResult(ResultFs.UnsupportedOperationModifyRomFsFileSystem);
public void DeleteDirectoryRecursively(string path) =>
ThrowHelper.ThrowResult(ResultFs.UnsupportedOperationModifyRomFsFileSystem);
public void CleanDirectoryRecursively(string path) =>
ThrowHelper.ThrowResult(ResultFs.UnsupportedOperationModifyRomFsFileSystem);
public void DeleteFile(string path) =>
ThrowHelper.ThrowResult(ResultFs.UnsupportedOperationModifyRomFsFileSystem);
public void RenameDirectory(string srcPath, string dstPath) =>
ThrowHelper.ThrowResult(ResultFs.UnsupportedOperationModifyRomFsFileSystem);
public void RenameFile(string srcPath, string dstPath) =>
ThrowHelper.ThrowResult(ResultFs.UnsupportedOperationModifyRomFsFileSystem);
public long GetFreeSpaceSize(string path)
public Result GetFreeSpaceSize(out long freeSpace, string path)
{
ThrowHelper.ThrowResult(ResultFs.UnsupportedOperationRomFsFileSystemGetSpace);
return default;
freeSpace = default;
return ResultFs.UnsupportedOperationRomFsFileSystemGetSpace.Log();
}
public long GetTotalSpaceSize(string path)
public Result GetTotalSpaceSize(out long totalSpace, string path)
{
ThrowHelper.ThrowResult(ResultFs.UnsupportedOperationRomFsFileSystemGetSpace);
return default;
totalSpace = default;
return ResultFs.UnsupportedOperationRomFsFileSystemGetSpace.Log();
}
public FileTimeStampRaw GetFileTimeStampRaw(string path)
public Result GetFileTimeStampRaw(out FileTimeStampRaw timeStamp, string path)
{
ThrowHelper.ThrowResult(ResultFs.NotImplemented);
return default;
timeStamp = default;
return ResultFs.NotImplemented.Log();
}
public void QueryEntry(Span<byte> outBuffer, ReadOnlySpan<byte> inBuffer, string path, QueryId queryId) =>
ThrowHelper.ThrowResult(ResultFs.NotImplemented);
public Result QueryEntry(Span<byte> outBuffer, ReadOnlySpan<byte> inBuffer, QueryId queryId, string path)
{
return ResultFs.NotImplemented.Log();
}
}
public class RomfsHeader

View File

@ -338,11 +338,11 @@ namespace LibHac.Fs.Save
FileTable.ChangeKey(ref oldKey, ref newKey);
}
public void RenameDirectory(string srcPath, string dstPath)
public Result RenameDirectory(string srcPath, string dstPath)
{
if (srcPath == dstPath || TryOpenFile(dstPath, out _) || TryOpenDirectory(dstPath, out _))
{
throw new IOException(Messages.DestPathAlreadyExists);
return ResultFs.PathAlreadyExists.Log();
}
ReadOnlySpan<byte> oldPathBytes = Util.GetUtf8Bytes(srcPath);
@ -350,19 +350,19 @@ namespace LibHac.Fs.Save
if (!FindPathRecursive(oldPathBytes, out SaveEntryKey oldKey))
{
throw new DirectoryNotFoundException();
return ResultFs.PathNotFound.Log();
}
int dirIndex = DirectoryTable.GetIndexFromKey(ref oldKey).Index;
if (!FindPathRecursive(newPathBytes, out SaveEntryKey newKey))
{
throw new IOException(Messages.PartialPathNotFound);
return ResultFs.PathNotFound.Log();
}
if (PathTools.IsSubPath(oldPathBytes, newPathBytes))
{
ThrowHelper.ThrowResult(ResultFs.DestinationIsSubPathOfSource);
return ResultFs.DestinationIsSubPathOfSource.Log();
}
if (oldKey.Parent != newKey.Parent)
@ -372,6 +372,8 @@ namespace LibHac.Fs.Save
}
DirectoryTable.ChangeKey(ref oldKey, ref newKey);
return Result.Success;
}
public bool TryOpenDirectory(string path, out SaveFindPosition position)

View File

@ -142,198 +142,114 @@ namespace LibHac.Fs.Save
IntegrityStorageType.Save, integrityCheckLevel, LeaveOpen);
}
public void CreateDirectory(string path)
public Result CreateDirectory(string path)
{
try
{
SaveDataFileSystemCore.CreateDirectory(path);
}
catch (HorizonResultException ex)
{
ConvertResultException(ex);
throw;
}
Result result = SaveDataFileSystemCore.CreateDirectory(path);
return SaveResults.ConvertToExternalResult(result);
}
public void CreateFile(string path, long size, CreateFileOptions options)
public Result CreateFile(string path, long size, CreateFileOptions options)
{
try
{
SaveDataFileSystemCore.CreateFile(path, size, options);
}
catch (HorizonResultException ex)
{
ConvertResultException(ex);
throw;
}
Result result = SaveDataFileSystemCore.CreateFile(path, size, options);
return SaveResults.ConvertToExternalResult(result);
}
public void DeleteDirectory(string path)
public Result DeleteDirectory(string path)
{
try
{
SaveDataFileSystemCore.DeleteDirectory(path);
}
catch (HorizonResultException ex)
{
ConvertResultException(ex);
throw;
}
Result result = SaveDataFileSystemCore.DeleteDirectory(path);
return SaveResults.ConvertToExternalResult(result);
}
public void DeleteDirectoryRecursively(string path)
public Result DeleteDirectoryRecursively(string path)
{
try
{
SaveDataFileSystemCore.DeleteDirectoryRecursively(path);
}
catch (HorizonResultException ex)
{
ConvertResultException(ex);
throw;
}
Result result = SaveDataFileSystemCore.DeleteDirectoryRecursively(path);
return SaveResults.ConvertToExternalResult(result);
}
public void CleanDirectoryRecursively(string path)
public Result CleanDirectoryRecursively(string path)
{
try
{
SaveDataFileSystemCore.CleanDirectoryRecursively(path);
}
catch (HorizonResultException ex)
{
ConvertResultException(ex);
throw;
}
Result result = SaveDataFileSystemCore.CleanDirectoryRecursively(path);
return SaveResults.ConvertToExternalResult(result);
}
public void DeleteFile(string path)
public Result DeleteFile(string path)
{
try
{
SaveDataFileSystemCore.DeleteFile(path);
}
catch (HorizonResultException ex)
{
ConvertResultException(ex);
throw;
}
Result result = SaveDataFileSystemCore.DeleteFile(path);
return SaveResults.ConvertToExternalResult(result);
}
public IDirectory OpenDirectory(string path, OpenDirectoryMode mode)
public Result OpenDirectory(out IDirectory directory, string path, OpenDirectoryMode mode)
{
try
{
return SaveDataFileSystemCore.OpenDirectory(path, mode);
}
catch (HorizonResultException ex)
{
ConvertResultException(ex);
throw;
}
Result result = SaveDataFileSystemCore.OpenDirectory(out directory, path, mode);
return SaveResults.ConvertToExternalResult(result);
}
public IFile OpenFile(string path, OpenMode mode)
public Result OpenFile(out IFile file, string path, OpenMode mode)
{
try
{
return SaveDataFileSystemCore.OpenFile(path, mode);
}
catch (HorizonResultException ex)
{
ConvertResultException(ex);
throw;
}
Result result = SaveDataFileSystemCore.OpenFile(out file, path, mode);
return SaveResults.ConvertToExternalResult(result);
}
public void RenameDirectory(string srcPath, string dstPath)
public Result RenameDirectory(string oldPath, string newPath)
{
try
{
SaveDataFileSystemCore.RenameDirectory(srcPath, dstPath);
}
catch (HorizonResultException ex)
{
ConvertResultException(ex);
throw;
}
Result result = SaveDataFileSystemCore.RenameDirectory(oldPath, newPath);
return SaveResults.ConvertToExternalResult(result);
}
public void RenameFile(string srcPath, string dstPath)
public Result RenameFile(string oldPath, string newPath)
{
try
{
SaveDataFileSystemCore.RenameFile(srcPath, dstPath);
}
catch (HorizonResultException ex)
{
ConvertResultException(ex);
throw;
}
Result result = SaveDataFileSystemCore.RenameFile(oldPath, newPath);
return SaveResults.ConvertToExternalResult(result);
}
public DirectoryEntryType GetEntryType(string path)
public Result GetEntryType(out DirectoryEntryType entryType, string path)
{
try
{
return SaveDataFileSystemCore.GetEntryType(path);
}
catch (HorizonResultException ex)
{
ConvertResultException(ex);
throw;
}
Result result = SaveDataFileSystemCore.GetEntryType(out entryType, path);
return SaveResults.ConvertToExternalResult(result);
}
public long GetFreeSpaceSize(string path)
public Result GetFreeSpaceSize(out long freeSpace, string path)
{
try
{
return SaveDataFileSystemCore.GetFreeSpaceSize(path);
}
catch (HorizonResultException ex)
{
ConvertResultException(ex);
throw;
}
Result result = SaveDataFileSystemCore.GetFreeSpaceSize(out freeSpace, path);
return SaveResults.ConvertToExternalResult(result);
}
public long GetTotalSpaceSize(string path)
public Result GetTotalSpaceSize(out long totalSpace, string path)
{
try
{
return SaveDataFileSystemCore.GetTotalSpaceSize(path);
}
catch (HorizonResultException ex)
{
ConvertResultException(ex);
throw;
}
Result result = SaveDataFileSystemCore.GetTotalSpaceSize(out totalSpace, path);
return SaveResults.ConvertToExternalResult(result);
}
public void Commit()
public Result Commit()
{
try
{
Commit(Keyset);
}
catch (HorizonResultException ex)
{
ConvertResultException(ex);
throw;
}
Result result = Commit(Keyset);
return SaveResults.ConvertToExternalResult(result);
}
public FileTimeStampRaw GetFileTimeStampRaw(string path)
public Result GetFileTimeStampRaw(out FileTimeStampRaw timeStamp, string path)
{
ThrowHelper.ThrowResult(ResultFs.NotImplemented);
return default;
timeStamp = default;
return ResultFs.NotImplemented.Log();
}
public void QueryEntry(Span<byte> outBuffer, ReadOnlySpan<byte> inBuffer, string path, QueryId queryId) =>
ThrowHelper.ThrowResult(ResultFs.NotImplemented);
public Result QueryEntry(Span<byte> outBuffer, ReadOnlySpan<byte> inBuffer, QueryId queryId, string path) =>
ResultFs.NotImplemented.Log();
public bool Commit(Keyset keyset)
public Result Commit(Keyset keyset)
{
CoreDataIvfcStorage.Flush();
FatIvfcStorage?.Flush();
@ -349,7 +265,7 @@ namespace LibHac.Fs.Save
headerStream.Position = 0x108;
headerStream.Write(hash, 0, hash.Length);
if (keyset == null || keyset.SaveMacKey.IsEmpty()) return false;
if (keyset == null || keyset.SaveMacKey.IsEmpty()) return ResultFs.PreconditionViolation;
var cmacData = new byte[0x200];
var cmac = new byte[0x10];
@ -363,7 +279,7 @@ namespace LibHac.Fs.Save
headerStream.Write(cmac, 0, 0x10);
headerStream.Flush();
return true;
return Result.Success;
}
public void FsTrim()
@ -395,10 +311,5 @@ namespace LibHac.Fs.Save
return journalValidity;
}
private void ConvertResultException(HorizonResultException ex)
{
ex.ResultValue = SaveResults.ConvertToExternalResult(ex.ResultValue);
}
}
}

View File

@ -27,14 +27,16 @@ namespace LibHac.Fs.Save
FileTable = new HierarchicalSaveFileTable(dirTableStorage, fileTableStorage);
}
public void CreateDirectory(string path)
public Result CreateDirectory(string path)
{
path = PathTools.Normalize(path);
FileTable.AddDirectory(path);
return Result.Success;
}
public void CreateFile(string path, long size, CreateFileOptions options)
public Result CreateFile(string path, long size, CreateFileOptions options)
{
path = PathTools.Normalize(path);
@ -43,7 +45,7 @@ namespace LibHac.Fs.Save
var emptyFileEntry = new SaveFileInfo { StartBlock = int.MinValue, Length = size };
FileTable.AddFile(path, ref emptyFileEntry);
return;
return Result.Success;
}
int blockCount = (int)Util.DivideByRoundUp(size, AllocationTable.Header.BlockSize);
@ -51,45 +53,56 @@ namespace LibHac.Fs.Save
if (startBlock == -1)
{
ThrowHelper.ThrowResult(ResultFs.AllocationTableInsufficientFreeBlocks,
"Not enough available space to create file.");
return ResultFs.AllocationTableInsufficientFreeBlocks.Log();
}
var fileEntry = new SaveFileInfo { StartBlock = startBlock, Length = size };
FileTable.AddFile(path, ref fileEntry);
return Result.Success;
}
public void DeleteDirectory(string path)
public Result DeleteDirectory(string path)
{
path = PathTools.Normalize(path);
FileTable.DeleteDirectory(path);
return Result.Success;
}
public void DeleteDirectoryRecursively(string path)
public Result DeleteDirectoryRecursively(string path)
{
path = PathTools.Normalize(path);
CleanDirectoryRecursively(path);
Result rc = CleanDirectoryRecursively(path);
if (rc.IsFailure()) return rc;
DeleteDirectory(path);
return Result.Success;
}
public void CleanDirectoryRecursively(string path)
public Result CleanDirectoryRecursively(string path)
{
path = PathTools.Normalize(path);
IDirectory dir = OpenDirectory(path, OpenDirectoryMode.All);
Result rc = OpenDirectory(out IDirectory dir, path, OpenDirectoryMode.All);
if (rc.IsFailure()) return rc;
FileSystemExtensions.CleanDirectoryRecursivelyGeneric(dir);
return Result.Success;
}
public void DeleteFile(string path)
public Result DeleteFile(string path)
{
path = PathTools.Normalize(path);
if (!FileTable.TryOpenFile(path, out SaveFileInfo fileInfo))
{
ThrowHelper.ThrowResult(ResultFs.PathNotFound);
return ResultFs.PathNotFound.Log();
}
if (fileInfo.StartBlock != int.MinValue)
@ -98,91 +111,110 @@ namespace LibHac.Fs.Save
}
FileTable.DeleteFile(path);
return Result.Success;
}
public IDirectory OpenDirectory(string path, OpenDirectoryMode mode)
public Result OpenDirectory(out IDirectory directory, string path, OpenDirectoryMode mode)
{
directory = default;
path = PathTools.Normalize(path);
if (!FileTable.TryOpenDirectory(path, out SaveFindPosition position))
{
ThrowHelper.ThrowResult(ResultFs.PathNotFound);
return ResultFs.PathNotFound.Log();
}
return new SaveDataDirectory(this, path, position, mode);
directory = new SaveDataDirectory(this, path, position, mode);
return Result.Success;
}
public IFile OpenFile(string path, OpenMode mode)
public Result OpenFile(out IFile file, string path, OpenMode mode)
{
file = default;
path = PathTools.Normalize(path);
if (!FileTable.TryOpenFile(path, out SaveFileInfo file))
if (!FileTable.TryOpenFile(path, out SaveFileInfo fileInfo))
{
ThrowHelper.ThrowResult(ResultFs.PathNotFound);
return ResultFs.PathNotFound.Log();
}
AllocationTableStorage storage = OpenFatStorage(file.StartBlock);
AllocationTableStorage storage = OpenFatStorage(fileInfo.StartBlock);
return new SaveDataFile(storage, path, FileTable, file.Length, mode);
file = new SaveDataFile(storage, path, FileTable, fileInfo.Length, mode);
return Result.Success;
}
public void RenameDirectory(string srcPath, string dstPath)
public Result RenameDirectory(string oldPath, string newPath)
{
srcPath = PathTools.Normalize(srcPath);
dstPath = PathTools.Normalize(dstPath);
oldPath = PathTools.Normalize(oldPath);
newPath = PathTools.Normalize(newPath);
FileTable.RenameDirectory(srcPath, dstPath);
return FileTable.RenameDirectory(oldPath, newPath);
}
public void RenameFile(string srcPath, string dstPath)
public Result RenameFile(string oldPath, string newPath)
{
srcPath = PathTools.Normalize(srcPath);
dstPath = PathTools.Normalize(dstPath);
oldPath = PathTools.Normalize(oldPath);
newPath = PathTools.Normalize(newPath);
FileTable.RenameFile(srcPath, dstPath);
FileTable.RenameFile(oldPath, newPath);
return Result.Success;
}
public DirectoryEntryType GetEntryType(string path)
public Result GetEntryType(out DirectoryEntryType entryType, string path)
{
path = PathTools.Normalize(path);
if (FileTable.TryOpenFile(path, out SaveFileInfo _))
{
return DirectoryEntryType.File;
entryType = DirectoryEntryType.File;
return Result.Success;
}
if (FileTable.TryOpenDirectory(path, out SaveFindPosition _))
{
return DirectoryEntryType.Directory;
entryType = DirectoryEntryType.Directory;
return Result.Success;
}
return DirectoryEntryType.NotFound;
entryType = DirectoryEntryType.NotFound;
return ResultFs.PathNotFound.Log();
}
public long GetFreeSpaceSize(string path)
public Result GetFreeSpaceSize(out long freeSpace, string path)
{
int freeBlockCount = AllocationTable.GetFreeListLength();
return Header.BlockSize * freeBlockCount;
freeSpace = Header.BlockSize * freeBlockCount;
return Result.Success;
}
public long GetTotalSpaceSize(string path)
public Result GetTotalSpaceSize(out long totalSpace, string path)
{
return Header.BlockSize * Header.BlockCount;
totalSpace = Header.BlockSize * Header.BlockCount;
return Result.Success;
}
public void Commit()
public Result Commit()
{
return Result.Success;
}
public FileTimeStampRaw GetFileTimeStampRaw(string path)
public Result GetFileTimeStampRaw(out FileTimeStampRaw timeStamp, string path)
{
ThrowHelper.ThrowResult(ResultFs.NotImplemented);
return default;
timeStamp = default;
return ResultFs.NotImplemented.Log();
}
public void QueryEntry(Span<byte> outBuffer, ReadOnlySpan<byte> inBuffer, string path, QueryId queryId) =>
ThrowHelper.ThrowResult(ResultFs.NotImplemented);
public Result QueryEntry(Span<byte> outBuffer, ReadOnlySpan<byte> inBuffer, QueryId queryId, string path)
{
return ResultFs.NotImplemented.Log();
}
public IStorage GetBaseStorage() => BaseStorage.AsReadOnly();
public IStorage GetHeaderStorage() => HeaderStorage.AsReadOnly();

View File

@ -18,118 +18,119 @@ namespace LibHac.Fs
RootPath = PathTools.Normalize(rootPath);
}
public void CreateDirectory(string path)
public Result CreateDirectory(string path)
{
path = PathTools.Normalize(path);
ParentFileSystem.CreateDirectory(ResolveFullPath(path));
return ParentFileSystem.CreateDirectory(ResolveFullPath(path));
}
public void CreateFile(string path, long size, CreateFileOptions options)
public Result CreateFile(string path, long size, CreateFileOptions options)
{
path = PathTools.Normalize(path);
ParentFileSystem.CreateFile(ResolveFullPath(path), size, options);
return ParentFileSystem.CreateFile(ResolveFullPath(path), size, options);
}
public void DeleteDirectory(string path)
public Result DeleteDirectory(string path)
{
path = PathTools.Normalize(path);
ParentFileSystem.DeleteDirectory(ResolveFullPath(path));
return ParentFileSystem.DeleteDirectory(ResolveFullPath(path));
}
public void DeleteDirectoryRecursively(string path)
public Result DeleteDirectoryRecursively(string path)
{
path = PathTools.Normalize(path);
ParentFileSystem.DeleteDirectoryRecursively(ResolveFullPath(path));
return ParentFileSystem.DeleteDirectoryRecursively(ResolveFullPath(path));
}
public void CleanDirectoryRecursively(string path)
public Result CleanDirectoryRecursively(string path)
{
path = PathTools.Normalize(path);
ParentFileSystem.CleanDirectoryRecursively(ResolveFullPath(path));
return ParentFileSystem.CleanDirectoryRecursively(ResolveFullPath(path));
}
public void DeleteFile(string path)
public Result DeleteFile(string path)
{
path = PathTools.Normalize(path);
ParentFileSystem.DeleteFile(ResolveFullPath(path));
return ParentFileSystem.DeleteFile(ResolveFullPath(path));
}
public IDirectory OpenDirectory(string path, OpenDirectoryMode mode)
public Result OpenDirectory(out IDirectory directory, string path, OpenDirectoryMode mode)
{
path = PathTools.Normalize(path);
IDirectory baseDir = ParentFileSystem.OpenDirectory(ResolveFullPath(path), mode);
ParentFileSystem.OpenDirectory(out IDirectory baseDir, ResolveFullPath(path), mode);
return new SubdirectoryFileSystemDirectory(this, baseDir, path, mode);
directory = new SubdirectoryFileSystemDirectory(this, baseDir, path, mode);
return Result.Success;
}
public IFile OpenFile(string path, OpenMode mode)
public Result OpenFile(out IFile file, string path, OpenMode mode)
{
path = PathTools.Normalize(path);
return ParentFileSystem.OpenFile(ResolveFullPath(path), mode);
return ParentFileSystem.OpenFile(out file, ResolveFullPath(path), mode);
}
public void RenameDirectory(string srcPath, string dstPath)
public Result RenameDirectory(string oldPath, string newPath)
{
srcPath = PathTools.Normalize(srcPath);
dstPath = PathTools.Normalize(dstPath);
oldPath = PathTools.Normalize(oldPath);
newPath = PathTools.Normalize(newPath);
ParentFileSystem.RenameDirectory(ResolveFullPath(srcPath), ResolveFullPath(dstPath));
return ParentFileSystem.RenameDirectory(oldPath, newPath);
}
public void RenameFile(string srcPath, string dstPath)
public Result RenameFile(string oldPath, string newPath)
{
srcPath = PathTools.Normalize(srcPath);
dstPath = PathTools.Normalize(dstPath);
oldPath = PathTools.Normalize(oldPath);
newPath = PathTools.Normalize(newPath);
ParentFileSystem.RenameFile(ResolveFullPath(srcPath), ResolveFullPath(dstPath));
return ParentFileSystem.RenameFile(oldPath, newPath);
}
public DirectoryEntryType GetEntryType(string path)
public Result GetEntryType(out DirectoryEntryType entryType, string path)
{
path = PathTools.Normalize(path);
return ParentFileSystem.GetEntryType(ResolveFullPath(path));
return ParentFileSystem.GetEntryType(out entryType, ResolveFullPath(path));
}
public void Commit()
public Result Commit()
{
ParentFileSystem.Commit();
return ParentFileSystem.Commit();
}
public long GetFreeSpaceSize(string path)
public Result GetFreeSpaceSize(out long freeSpace, string path)
{
path = PathTools.Normalize(path);
return ParentFileSystem.GetFreeSpaceSize(ResolveFullPath(path));
return ParentFileSystem.GetFreeSpaceSize(out freeSpace, ResolveFullPath(path));
}
public long GetTotalSpaceSize(string path)
public Result GetTotalSpaceSize(out long totalSpace, string path)
{
path = PathTools.Normalize(path);
return ParentFileSystem.GetTotalSpaceSize(ResolveFullPath(path));
return ParentFileSystem.GetTotalSpaceSize(out totalSpace, ResolveFullPath(path));
}
public FileTimeStampRaw GetFileTimeStampRaw(string path)
public Result GetFileTimeStampRaw(out FileTimeStampRaw timeStamp, string path)
{
path = PathTools.Normalize(path);
return ParentFileSystem.GetFileTimeStampRaw(ResolveFullPath(path));
return ParentFileSystem.GetFileTimeStampRaw(out timeStamp, ResolveFullPath(path));
}
public void QueryEntry(Span<byte> outBuffer, ReadOnlySpan<byte> inBuffer, string path, QueryId queryId)
public Result QueryEntry(Span<byte> outBuffer, ReadOnlySpan<byte> inBuffer, QueryId queryId, string path)
{
path = PathTools.Normalize(path);
ParentFileSystem.QueryEntry(outBuffer, inBuffer, ResolveFullPath(path), queryId);
return ParentFileSystem.QueryEntry(outBuffer, inBuffer, queryId, ResolveFullPath(path));
}
}
}

View File

@ -26,117 +26,125 @@ namespace LibHac.FsClient.Accessors
FsManager = fsManager;
}
public void CreateDirectory(string path)
public Result CreateDirectory(string path)
{
FileSystem.CreateDirectory(path);
return FileSystem.CreateDirectory(path);
}
public void CreateFile(string path, long size, CreateFileOptions options)
public Result CreateFile(string path, long size, CreateFileOptions options)
{
FileSystem.CreateFile(path, size, options);
return FileSystem.CreateFile(path, size, options);
}
public void DeleteDirectory(string path)
public Result DeleteDirectory(string path)
{
FileSystem.DeleteDirectory(path);
return FileSystem.DeleteDirectory(path);
}
public void DeleteDirectoryRecursively(string path)
public Result DeleteDirectoryRecursively(string path)
{
FileSystem.DeleteDirectoryRecursively(path);
return FileSystem.DeleteDirectoryRecursively(path);
}
public void CleanDirectoryRecursively(string path)
public Result CleanDirectoryRecursively(string path)
{
FileSystem.CleanDirectoryRecursively(path);
return FileSystem.CleanDirectoryRecursively(path);
}
public void DeleteFile(string path)
public Result DeleteFile(string path)
{
FileSystem.DeleteFile(path);
return FileSystem.DeleteFile(path);
}
public DirectoryAccessor OpenDirectory(string path, OpenDirectoryMode mode)
public Result OpenDirectory(out DirectoryAccessor directory, string path, OpenDirectoryMode mode)
{
IDirectory dir = FileSystem.OpenDirectory(path, mode);
directory = default;
var accessor = new DirectoryAccessor(dir, this);
Result rc = FileSystem.OpenDirectory(out IDirectory rawDirectory, path, mode);
if (rc.IsFailure()) return rc;
var accessor = new DirectoryAccessor(rawDirectory, this);
lock (_locker)
{
OpenDirectories.Add(accessor);
}
return accessor;
directory = accessor;
return Result.Success;
}
public FileAccessor OpenFile(string path, OpenMode mode)
public Result OpenFile(out FileAccessor file, string path, OpenMode mode)
{
IFile file = FileSystem.OpenFile(path, mode);
file = default;
var accessor = new FileAccessor(file, this, mode);
Result rc = FileSystem.OpenFile(out IFile rawFile, path, mode);
if (rc.IsFailure()) return rc;
var accessor = new FileAccessor(rawFile, this, mode);
lock (_locker)
{
OpenFiles.Add(accessor);
}
return accessor;
file = accessor;
return Result.Success;
}
public void RenameDirectory(string srcPath, string dstPath)
public Result RenameDirectory(string oldPath, string newPath)
{
FileSystem.RenameDirectory(srcPath, dstPath);
return FileSystem.RenameDirectory(oldPath, newPath);
}
public void RenameFile(string srcPath, string dstPath)
public Result RenameFile(string oldPath, string newPath)
{
FileSystem.RenameFile(srcPath, dstPath);
return FileSystem.RenameFile(oldPath, newPath);
}
public void DirectoryExists(string path)
public bool DirectoryExists(string path)
{
FileSystem.DirectoryExists(path);
return FileSystem.DirectoryExists(path);
}
public void FileExists(string path)
public bool FileExists(string path)
{
FileSystem.FileExists(path);
return FileSystem.FileExists(path);
}
public DirectoryEntryType GetEntryType(string path)
public Result GetEntryType(out DirectoryEntryType type, string path)
{
return FileSystem.GetEntryType(path);
return FileSystem.GetEntryType(out type, path);
}
public long GetFreeSpaceSize(string path)
public Result GetFreeSpaceSize(out long freeSpace, string path)
{
return FileSystem.GetFreeSpaceSize(path);
return FileSystem.GetFreeSpaceSize(out freeSpace, path);
}
public long GetTotalSpaceSize(string path)
public Result GetTotalSpaceSize(out long totalSpace, string path)
{
return FileSystem.GetTotalSpaceSize(path);
return FileSystem.GetTotalSpaceSize(out totalSpace, path);
}
public FileTimeStampRaw GetFileTimeStampRaw(string path)
public Result GetFileTimeStampRaw(out FileTimeStampRaw timeStamp, string path)
{
return FileSystem.GetFileTimeStampRaw(path);
return FileSystem.GetFileTimeStampRaw(out timeStamp, path);
}
public void Commit()
public Result Commit()
{
if (OpenFiles.Any(x => (x.OpenMode & OpenMode.Write) != 0))
{
ThrowHelper.ThrowResult(ResultFs.WritableFileOpen);
return ResultFs.WritableFileOpen.Log();
}
FileSystem.Commit();
return FileSystem.Commit();
}
public void QueryEntry(Span<byte> outBuffer, ReadOnlySpan<byte> inBuffer, string path, QueryId queryId)
public Result QueryEntry(Span<byte> outBuffer, ReadOnlySpan<byte> inBuffer, string path, QueryId queryId)
{
FileSystem.QueryEntry(outBuffer, inBuffer, path, queryId);
return FileSystem.QueryEntry(outBuffer, inBuffer, queryId, path);
}
internal void NotifyCloseFile(FileAccessor file)

View File

@ -12,7 +12,7 @@ namespace LibHac.FsClient
Directory = directory;
}
public int GetId() => Directory.GetHashCode();
public int GetId() => Directory?.GetHashCode() ?? 0;
public void Dispose()
{

View File

@ -12,7 +12,7 @@ namespace LibHac.FsClient
File = file;
}
public int GetId() => File.GetHashCode();
public int GetId() => File?.GetHashCode() ?? 0;
public void Dispose()
{

View File

@ -55,12 +55,12 @@ namespace LibHac.FsClient
throw new NotImplementedException();
}
public FileHandle OpenFile(out FileHandle handle, string path, OpenMode mode)
public Result OpenFile(out FileHandle handle, string path, OpenMode mode)
{
throw new NotImplementedException();
}
public DirectoryHandle OpenDirectory(out DirectoryHandle handle, string path, OpenDirectoryMode mode)
public Result OpenDirectory(out DirectoryHandle handle, string path, OpenDirectoryMode mode)
{
throw new NotImplementedException();
}

View File

@ -6,6 +6,7 @@ using LibHac.FsClient.Accessors;
namespace LibHac.FsClient
{
// Todo: Access log for FindFileSystem
public class FileSystemManager
{
internal Horizon Os { get; }
@ -66,213 +67,229 @@ namespace LibHac.FsClient
if (accessLog != null) AccessLog = accessLog;
}
public void CreateDirectory(string path)
public Result CreateDirectory(string path)
{
FindFileSystem(path.AsSpan(), out FileSystemAccessor fileSystem, out ReadOnlySpan<char> subPath)
.ThrowIfFailure();
Result rc = FindFileSystem(path.AsSpan(), out FileSystemAccessor fileSystem, out ReadOnlySpan<char> subPath);
if (rc.IsFailure()) return rc;
if (IsEnabledAccessLog() && fileSystem.IsAccessLogEnabled)
{
TimeSpan startTime = Time.GetCurrent();
fileSystem.CreateDirectory(subPath.ToString());
rc = fileSystem.CreateDirectory(subPath.ToString());
TimeSpan endTime = Time.GetCurrent();
OutputAccessLog(startTime, endTime, $", path: \"{path}\"");
}
else
{
fileSystem.CreateDirectory(subPath.ToString());
rc = fileSystem.CreateDirectory(subPath.ToString());
}
return rc;
}
public void CreateFile(string path, long size)
public Result CreateFile(string path, long size)
{
CreateFile(path, size, CreateFileOptions.None);
return CreateFile(path, size, CreateFileOptions.None);
}
public void CreateFile(string path, long size, CreateFileOptions options)
public Result CreateFile(string path, long size, CreateFileOptions options)
{
FindFileSystem(path.AsSpan(), out FileSystemAccessor fileSystem, out ReadOnlySpan<char> subPath)
.ThrowIfFailure();
Result rc = FindFileSystem(path.AsSpan(), out FileSystemAccessor fileSystem, out ReadOnlySpan<char> subPath);
if (rc.IsFailure()) return rc;
if (IsEnabledAccessLog() && fileSystem.IsAccessLogEnabled)
{
TimeSpan startTime = Time.GetCurrent();
fileSystem.CreateFile(subPath.ToString(), size, options);
rc = fileSystem.CreateFile(subPath.ToString(), size, options);
TimeSpan endTime = Time.GetCurrent();
OutputAccessLog(startTime, endTime, $", path: \"{path}\", size: {size}");
}
else
{
fileSystem.CreateFile(subPath.ToString(), size, options);
rc = fileSystem.CreateFile(subPath.ToString(), size, options);
}
return rc;
}
public void DeleteDirectory(string path)
public Result DeleteDirectory(string path)
{
FindFileSystem(path.AsSpan(), out FileSystemAccessor fileSystem, out ReadOnlySpan<char> subPath)
.ThrowIfFailure();
Result rc = FindFileSystem(path.AsSpan(), out FileSystemAccessor fileSystem, out ReadOnlySpan<char> subPath);
if (rc.IsFailure()) return rc;
if (IsEnabledAccessLog() && fileSystem.IsAccessLogEnabled)
{
TimeSpan startTime = Time.GetCurrent();
fileSystem.DeleteDirectory(subPath.ToString());
rc = fileSystem.DeleteDirectory(subPath.ToString());
TimeSpan endTime = Time.GetCurrent();
OutputAccessLog(startTime, endTime, $", path: \"{path}\"");
}
else
{
fileSystem.DeleteDirectory(subPath.ToString());
rc = fileSystem.DeleteDirectory(subPath.ToString());
}
return rc;
}
public void DeleteDirectoryRecursively(string path)
public Result DeleteDirectoryRecursively(string path)
{
FindFileSystem(path.AsSpan(), out FileSystemAccessor fileSystem, out ReadOnlySpan<char> subPath)
.ThrowIfFailure();
Result rc = FindFileSystem(path.AsSpan(), out FileSystemAccessor fileSystem, out ReadOnlySpan<char> subPath);
if (rc.IsFailure()) return rc;
if (IsEnabledAccessLog() && fileSystem.IsAccessLogEnabled)
{
TimeSpan startTime = Time.GetCurrent();
fileSystem.DeleteDirectoryRecursively(subPath.ToString());
rc = fileSystem.DeleteDirectoryRecursively(subPath.ToString());
TimeSpan endTime = Time.GetCurrent();
OutputAccessLog(startTime, endTime, $", path: \"{path}\"");
}
else
{
fileSystem.DeleteDirectoryRecursively(subPath.ToString());
rc = fileSystem.DeleteDirectoryRecursively(subPath.ToString());
}
return rc;
}
public void CleanDirectoryRecursively(string path)
public Result CleanDirectoryRecursively(string path)
{
FindFileSystem(path.AsSpan(), out FileSystemAccessor fileSystem, out ReadOnlySpan<char> subPath)
.ThrowIfFailure();
Result rc = FindFileSystem(path.AsSpan(), out FileSystemAccessor fileSystem, out ReadOnlySpan<char> subPath);
if (rc.IsFailure()) return rc;
if (IsEnabledAccessLog() && fileSystem.IsAccessLogEnabled)
{
TimeSpan startTime = Time.GetCurrent();
fileSystem.CleanDirectoryRecursively(subPath.ToString());
rc = fileSystem.CleanDirectoryRecursively(subPath.ToString());
TimeSpan endTime = Time.GetCurrent();
OutputAccessLog(startTime, endTime, $", path: \"{path}\"");
}
else
{
fileSystem.CleanDirectoryRecursively(subPath.ToString());
rc = fileSystem.CleanDirectoryRecursively(subPath.ToString());
}
return rc;
}
public void DeleteFile(string path)
public Result DeleteFile(string path)
{
FindFileSystem(path.AsSpan(), out FileSystemAccessor fileSystem, out ReadOnlySpan<char> subPath)
.ThrowIfFailure();
Result rc = FindFileSystem(path.AsSpan(), out FileSystemAccessor fileSystem, out ReadOnlySpan<char> subPath);
if (rc.IsFailure()) return rc;
if (IsEnabledAccessLog() && fileSystem.IsAccessLogEnabled)
{
TimeSpan startTime = Time.GetCurrent();
fileSystem.DeleteFile(subPath.ToString());
rc = fileSystem.DeleteFile(subPath.ToString());
TimeSpan endTime = Time.GetCurrent();
OutputAccessLog(startTime, endTime, $", path: \"{path}\"");
}
else
{
fileSystem.DeleteFile(subPath.ToString());
rc = fileSystem.DeleteFile(subPath.ToString());
}
return rc;
}
public void RenameDirectory(string oldPath, string newPath)
public Result RenameDirectory(string oldPath, string newPath)
{
FindFileSystem(oldPath.AsSpan(), out FileSystemAccessor oldFileSystem, out ReadOnlySpan<char> oldSubPath)
.ThrowIfFailure();
Result rc = FindFileSystem(oldPath.AsSpan(), out FileSystemAccessor oldFileSystem, out ReadOnlySpan<char> oldSubPath);
if (rc.IsFailure()) return rc;
FindFileSystem(newPath.AsSpan(), out FileSystemAccessor newFileSystem, out ReadOnlySpan<char> newSubPath)
.ThrowIfFailure();
rc = FindFileSystem(newPath.AsSpan(), out FileSystemAccessor newFileSystem, out ReadOnlySpan<char> newSubPath);
if (rc.IsFailure()) return rc;
if (oldFileSystem != newFileSystem)
{
ThrowHelper.ThrowResult(ResultFs.DifferentDestFileSystem);
return ResultFs.DifferentDestFileSystem.Log();
}
if (IsEnabledAccessLog() && oldFileSystem.IsAccessLogEnabled)
{
TimeSpan startTime = Time.GetCurrent();
oldFileSystem.RenameDirectory(oldSubPath.ToString(), newSubPath.ToString());
rc = oldFileSystem.RenameDirectory(oldSubPath.ToString(), newSubPath.ToString());
TimeSpan endTime = Time.GetCurrent();
OutputAccessLog(startTime, endTime, $", path: \"{oldPath}\", new_path: \"{newPath}\"");
}
else
{
oldFileSystem.RenameDirectory(oldSubPath.ToString(), newSubPath.ToString());
rc = oldFileSystem.RenameDirectory(oldSubPath.ToString(), newSubPath.ToString());
}
return rc;
}
public void RenameFile(string oldPath, string newPath)
public Result RenameFile(string oldPath, string newPath)
{
FindFileSystem(oldPath.AsSpan(), out FileSystemAccessor oldFileSystem, out ReadOnlySpan<char> oldSubPath)
.ThrowIfFailure();
Result rc = FindFileSystem(oldPath.AsSpan(), out FileSystemAccessor oldFileSystem, out ReadOnlySpan<char> oldSubPath);
if (rc.IsFailure()) return rc;
FindFileSystem(newPath.AsSpan(), out FileSystemAccessor newFileSystem, out ReadOnlySpan<char> newSubPath)
.ThrowIfFailure();
rc = FindFileSystem(newPath.AsSpan(), out FileSystemAccessor newFileSystem, out ReadOnlySpan<char> newSubPath);
if (rc.IsFailure()) return rc;
if (oldFileSystem != newFileSystem)
{
ThrowHelper.ThrowResult(ResultFs.DifferentDestFileSystem);
return ResultFs.DifferentDestFileSystem.Log();
}
if (IsEnabledAccessLog() && oldFileSystem.IsAccessLogEnabled)
{
TimeSpan startTime = Time.GetCurrent();
oldFileSystem.RenameFile(oldSubPath.ToString(), newSubPath.ToString());
rc = oldFileSystem.RenameFile(oldSubPath.ToString(), newSubPath.ToString());
TimeSpan endTime = Time.GetCurrent();
OutputAccessLog(startTime, endTime, $", path: \"{oldPath}\", new_path: \"{newPath}\"");
}
else
{
oldFileSystem.RenameFile(oldSubPath.ToString(), newSubPath.ToString());
rc = oldFileSystem.RenameFile(oldSubPath.ToString(), newSubPath.ToString());
}
return rc;
}
public DirectoryEntryType GetEntryType(string path)
public Result GetEntryType(out DirectoryEntryType type, string path)
{
FindFileSystem(path.AsSpan(), out FileSystemAccessor fileSystem, out ReadOnlySpan<char> subPath)
.ThrowIfFailure();
type = default;
DirectoryEntryType type;
Result rc = FindFileSystem(path.AsSpan(), out FileSystemAccessor fileSystem, out ReadOnlySpan<char> subPath);
if (rc.IsFailure()) return rc;
if (IsEnabledAccessLog() && fileSystem.IsAccessLogEnabled)
{
TimeSpan startTime = Time.GetCurrent();
type = fileSystem.GetEntryType(subPath.ToString());
rc = fileSystem.GetEntryType(out type, subPath.ToString());
TimeSpan endTime = Time.GetCurrent();
OutputAccessLog(startTime, endTime, $", path: \"{path}\"");
}
else
{
type = fileSystem.GetEntryType(subPath.ToString());
rc = fileSystem.GetEntryType(out type, subPath.ToString());
}
return type;
return rc;
}
public FileHandle OpenFile(string path, OpenMode mode)
public Result OpenFile(out FileHandle handle, string path, OpenMode mode)
{
FindFileSystem(path.AsSpan(), out FileSystemAccessor fileSystem, out ReadOnlySpan<char> subPath)
.ThrowIfFailure();
handle = default;
FileHandle handle;
Result rc = FindFileSystem(path.AsSpan(), out FileSystemAccessor fileSystem, out ReadOnlySpan<char> subPath);
if (rc.IsFailure()) return rc;
if (IsEnabledAccessLog() && fileSystem.IsAccessLogEnabled)
{
TimeSpan startTime = Time.GetCurrent();
FileAccessor file = fileSystem.OpenFile(subPath.ToString(), mode);
rc = fileSystem.OpenFile(out FileAccessor file, subPath.ToString(), mode);
handle = new FileHandle(file);
TimeSpan endTime = Time.GetCurrent();
@ -280,24 +297,24 @@ namespace LibHac.FsClient
}
else
{
FileAccessor file = fileSystem.OpenFile(subPath.ToString(), mode);
rc = fileSystem.OpenFile(out FileAccessor file, subPath.ToString(), mode);
handle = new FileHandle(file);
}
return handle;
return rc;
}
public DirectoryHandle OpenDirectory(string path, OpenDirectoryMode mode)
public Result OpenDirectory(out DirectoryHandle handle, string path, OpenDirectoryMode mode)
{
FindFileSystem(path.AsSpan(), out FileSystemAccessor fileSystem, out ReadOnlySpan<char> subPath)
.ThrowIfFailure();
handle = default;
DirectoryHandle handle;
Result rc = FindFileSystem(path.AsSpan(), out FileSystemAccessor fileSystem, out ReadOnlySpan<char> subPath);
if (rc.IsFailure()) return rc;
if (IsEnabledAccessLog() && fileSystem.IsAccessLogEnabled)
{
TimeSpan startTime = Time.GetCurrent();
DirectoryAccessor dir = fileSystem.OpenDirectory(subPath.ToString(), mode);
rc = fileSystem.OpenDirectory(out DirectoryAccessor dir, subPath.ToString(), mode);
handle = new DirectoryHandle(dir);
TimeSpan endTime = Time.GetCurrent();
@ -305,94 +322,105 @@ namespace LibHac.FsClient
}
else
{
DirectoryAccessor dir = fileSystem.OpenDirectory(subPath.ToString(), mode);
rc = fileSystem.OpenDirectory(out DirectoryAccessor dir, subPath.ToString(), mode);
handle = new DirectoryHandle(dir);
}
return handle;
return rc;
}
public long GetFreeSpaceSize(string path)
public Result GetFreeSpaceSize(out long freeSpace, string path)
{
FindFileSystem(path.AsSpan(), out FileSystemAccessor fileSystem, out ReadOnlySpan<char> subPath)
.ThrowIfFailure();
freeSpace = default;
return fileSystem.GetFreeSpaceSize(subPath.ToString());
Result rc = FindFileSystem(path.AsSpan(), out FileSystemAccessor fileSystem, out ReadOnlySpan<char> subPath);
if (rc.IsFailure()) return rc;
return fileSystem.GetFreeSpaceSize(out freeSpace, subPath.ToString());
}
public long GetTotalSpaceSize(string path)
public Result GetTotalSpaceSize(out long totalSpace, string path)
{
FindFileSystem(path.AsSpan(), out FileSystemAccessor fileSystem, out ReadOnlySpan<char> subPath)
.ThrowIfFailure();
totalSpace = default;
return fileSystem.GetTotalSpaceSize(subPath.ToString());
Result rc = FindFileSystem(path.AsSpan(), out FileSystemAccessor fileSystem, out ReadOnlySpan<char> subPath);
if (rc.IsFailure()) return rc;
return fileSystem.GetTotalSpaceSize(out totalSpace, subPath.ToString());
}
public FileTimeStampRaw GetFileTimeStamp(string path)
public Result GetFileTimeStamp(out FileTimeStampRaw timeStamp, string path)
{
FindFileSystem(path.AsSpan(), out FileSystemAccessor fileSystem, out ReadOnlySpan<char> subPath)
.ThrowIfFailure();
timeStamp = default;
return fileSystem.GetFileTimeStampRaw(subPath.ToString());
Result rc = FindFileSystem(path.AsSpan(), out FileSystemAccessor fileSystem, out ReadOnlySpan<char> subPath);
if (rc.IsFailure()) return rc;
return fileSystem.GetFileTimeStampRaw(out timeStamp, subPath.ToString());
}
public void Commit(string mountName)
public Result Commit(string mountName)
{
MountTable.Find(mountName, out FileSystemAccessor fileSystem).ThrowIfFailure();
Result rc = MountTable.Find(mountName, out FileSystemAccessor fileSystem);
if (rc.IsFailure()) return rc;
if (IsEnabledAccessLog() && fileSystem.IsAccessLogEnabled)
{
TimeSpan startTime = Time.GetCurrent();
fileSystem.Commit();
rc = fileSystem.Commit();
TimeSpan endTime = Time.GetCurrent();
OutputAccessLog(startTime, endTime, $", name: \"{mountName}\"");
}
else
{
fileSystem.Commit();
rc = fileSystem.Commit();
}
return rc;
}
// ==========================
// Operations on file handles
// ==========================
public int ReadFile(FileHandle handle, Span<byte> destination, long offset)
public Result ReadFile(out long bytesRead, FileHandle handle, Span<byte> destination, long offset)
{
return ReadFile(handle, destination, offset, ReadOption.None);
return ReadFile(out bytesRead, handle, destination, offset, ReadOption.None);
}
public int ReadFile(FileHandle handle, Span<byte> destination, long offset, ReadOption option)
public Result ReadFile(out long bytesRead, FileHandle handle, Span<byte> destination, long offset, ReadOption option)
{
long bytesRead;
Result rc;
if (IsEnabledAccessLog() && IsEnabledHandleAccessLog(handle))
{
TimeSpan startTime = Time.GetCurrent();
handle.File.Read(out bytesRead, offset, destination, option).ThrowIfFailure();
rc = handle.File.Read(out bytesRead, offset, destination, option);
TimeSpan endTime = Time.GetCurrent();
OutputAccessLog(startTime, endTime, handle, $", offset: {offset}, size: {destination.Length}");
}
else
{
handle.File.Read(out bytesRead, offset, destination, option).ThrowIfFailure();
rc = handle.File.Read(out bytesRead, offset, destination, option);
}
return (int)bytesRead;
return rc;
}
public void WriteFile(FileHandle handle, ReadOnlySpan<byte> source, long offset)
public Result WriteFile(FileHandle handle, ReadOnlySpan<byte> source, long offset)
{
WriteFile(handle, source, offset, WriteOption.None);
return WriteFile(handle, source, offset, WriteOption.None);
}
public void WriteFile(FileHandle handle, ReadOnlySpan<byte> source, long offset, WriteOption option)
public Result WriteFile(FileHandle handle, ReadOnlySpan<byte> source, long offset, WriteOption option)
{
Result rc;
if (IsEnabledAccessLog() && IsEnabledHandleAccessLog(handle))
{
TimeSpan startTime = Time.GetCurrent();
handle.File.Write(offset, source, option);
rc = handle.File.Write(offset, source, option);
TimeSpan endTime = Time.GetCurrent();
string optionString = (option & WriteOption.Flush) == 0 ? "" : $", write_option: {option}";
@ -401,47 +429,55 @@ namespace LibHac.FsClient
}
else
{
handle.File.Write(offset, source, option);
rc = handle.File.Write(offset, source, option);
}
return rc;
}
public void FlushFile(FileHandle handle)
public Result FlushFile(FileHandle handle)
{
Result rc;
if (IsEnabledAccessLog() && IsEnabledHandleAccessLog(handle))
{
TimeSpan startTime = Time.GetCurrent();
handle.File.Flush();
rc = handle.File.Flush();
TimeSpan endTime = Time.GetCurrent();
OutputAccessLog(startTime, endTime, handle, string.Empty);
}
else
{
handle.File.Flush();
rc = handle.File.Flush();
}
return rc;
}
public long GetFileSize(FileHandle handle)
public Result GetFileSize(out long fileSize, FileHandle handle)
{
handle.File.GetSize(out long fileSize).ThrowIfFailure();
return fileSize;
return handle.File.GetSize(out fileSize);
}
public void SetFileSize(FileHandle handle, long size)
public Result SetFileSize(FileHandle handle, long size)
{
Result rc;
if (IsEnabledAccessLog() && IsEnabledHandleAccessLog(handle))
{
TimeSpan startTime = Time.GetCurrent();
handle.File.SetSize(size);
rc = handle.File.SetSize(size);
TimeSpan endTime = Time.GetCurrent();
OutputAccessLog(startTime, endTime, handle, $", size: {size}");
}
else
{
handle.File.SetSize(size);
rc = handle.File.SetSize(size);
}
return rc;
}
public OpenMode GetFileOpenMode(FileHandle handle)
@ -468,9 +504,11 @@ namespace LibHac.FsClient
// ==========================
// Operations on directory handles
// ==========================
public int GetDirectoryEntryCount(DirectoryHandle handle)
public Result GetDirectoryEntryCount(out long count, DirectoryHandle handle)
{
return handle.Directory.GetEntryCount();
count = handle.Directory.GetEntryCount();
return Result.Success;
}
public IEnumerable<DirectoryEntry> ReadDirectory(DirectoryHandle handle)

View File

@ -7,10 +7,13 @@ namespace LibHac.FsClient
{
public static class FileSystemManagerUtils
{
public static void CopyDirectory(this FileSystemManager fs, string sourcePath, string destPath,
public static Result CopyDirectory(this FileSystemManager fs, string sourcePath, string destPath,
CreateFileOptions options = CreateFileOptions.None, IProgressReport logger = null)
{
using (DirectoryHandle sourceHandle = fs.OpenDirectory(sourcePath, OpenDirectoryMode.All))
Result rc = fs.OpenDirectory(out DirectoryHandle sourceHandle, sourcePath, OpenDirectoryMode.All);
if (rc.IsFailure()) return rc;
using (sourceHandle)
{
foreach (DirectoryEntry entry in fs.ReadDirectory(sourceHandle))
{
@ -21,7 +24,8 @@ namespace LibHac.FsClient
{
fs.EnsureDirectoryExists(subDstPath);
fs.CopyDirectory(subSrcPath, subDstPath, options, logger);
rc = fs.CopyDirectory(subSrcPath, subDstPath, options, logger);
if (rc.IsFailure()) return rc;
}
if (entry.Type == DirectoryEntryType.File)
@ -29,46 +33,65 @@ namespace LibHac.FsClient
logger?.LogMessage(subSrcPath);
fs.CreateOrOverwriteFile(subDstPath, entry.Size, options);
fs.CopyFile(subSrcPath, subDstPath, logger);
rc = fs.CopyFile(subSrcPath, subDstPath, logger);
if (rc.IsFailure()) return rc;
}
}
}
return Result.Success;
}
public static void CopyFile(this FileSystemManager fs, string sourcePath, string destPath, IProgressReport logger = null)
public static Result CopyFile(this FileSystemManager fs, string sourcePath, string destPath, IProgressReport logger = null)
{
using (FileHandle sourceHandle = fs.OpenFile(sourcePath, OpenMode.Read))
using (FileHandle destHandle = fs.OpenFile(destPath, OpenMode.Write | OpenMode.AllowAppend))
Result rc = fs.OpenFile(out FileHandle sourceHandle, sourcePath, OpenMode.Read);
if (rc.IsFailure()) return rc;
using (sourceHandle)
{
const int maxBufferSize = 0x10000;
rc = fs.OpenFile(out FileHandle destHandle, destPath, OpenMode.Write | OpenMode.AllowAppend);
if (rc.IsFailure()) return rc;
long fileSize = fs.GetFileSize(sourceHandle);
int bufferSize = (int)Math.Min(maxBufferSize, fileSize);
logger?.SetTotal(fileSize);
byte[] buffer = ArrayPool<byte>.Shared.Rent(bufferSize);
try
using (destHandle)
{
for (long offset = 0; offset < fileSize; offset += bufferSize)
const int maxBufferSize = 0x10000;
rc = fs.GetFileSize(out long fileSize, sourceHandle);
if (rc.IsFailure()) return rc;
int bufferSize = (int)Math.Min(maxBufferSize, fileSize);
logger?.SetTotal(fileSize);
byte[] buffer = ArrayPool<byte>.Shared.Rent(bufferSize);
try
{
int toRead = (int)Math.Min(fileSize - offset, bufferSize);
Span<byte> buf = buffer.AsSpan(0, toRead);
for (long offset = 0; offset < fileSize; offset += bufferSize)
{
int toRead = (int)Math.Min(fileSize - offset, bufferSize);
Span<byte> buf = buffer.AsSpan(0, toRead);
fs.ReadFile(sourceHandle, buf, offset);
fs.WriteFile(destHandle, buf, offset);
rc = fs.ReadFile(out long _, sourceHandle, buf, offset);
if (rc.IsFailure()) return rc;
logger?.ReportAdd(toRead);
rc = fs.WriteFile(destHandle, buf, offset);
if (rc.IsFailure()) return rc;
logger?.ReportAdd(toRead);
}
}
finally
{
ArrayPool<byte>.Shared.Return(buffer);
logger?.SetTotal(0);
}
}
finally
{
ArrayPool<byte>.Shared.Return(buffer);
logger?.SetTotal(0);
}
fs.FlushFile(destHandle);
rc = fs.FlushFile(destHandle);
if (rc.IsFailure()) return rc;
}
}
return Result.Success;
}
public static IEnumerable<DirectoryEntry> EnumerateEntries(this FileSystemManager fs, string path)
@ -86,7 +109,9 @@ namespace LibHac.FsClient
bool ignoreCase = searchOptions.HasFlag(SearchOptions.CaseInsensitive);
bool recurse = searchOptions.HasFlag(SearchOptions.RecurseSubdirectories);
using (DirectoryHandle sourceHandle = fs.OpenDirectory(path, OpenDirectoryMode.All))
fs.OpenDirectory(out DirectoryHandle sourceHandle, path, OpenDirectoryMode.All).ThrowIfFailure();
using (sourceHandle)
{
foreach (DirectoryEntry entry in fs.ReadDirectory(sourceHandle))
{
@ -112,12 +137,16 @@ namespace LibHac.FsClient
public static bool DirectoryExists(this FileSystemManager fs, string path)
{
return fs.GetEntryType(path) == DirectoryEntryType.Directory;
Result rc = fs.GetEntryType(out DirectoryEntryType type, path);
return (rc.IsSuccess() && type == DirectoryEntryType.Directory);
}
public static bool FileExists(this FileSystemManager fs, string path)
{
return fs.GetEntryType(path) == DirectoryEntryType.File;
Result rc = fs.GetEntryType(out DirectoryEntryType type, path);
return (rc.IsSuccess() && type == DirectoryEntryType.File);
}
public static void EnsureDirectoryExists(this FileSystemManager fs, string path)

View File

@ -5,6 +5,6 @@ namespace LibHac.FsService.Creators
public interface IHostFileSystemCreator
{
Result Create(out IFileSystem fileSystem, bool someBool);
Result Create(out IFileSystem fileSystem, string path, bool someBool);
Result Create(out IFileSystem fileSystem, string path, bool openCaseSensitive);
}
}

View File

@ -4,7 +4,7 @@ namespace LibHac.FsService.Creators
{
public interface ITargetManagerFileSystemCreator
{
Result Create(out IFileSystem fileSystem, bool someBool);
Result Create(out IFileSystem fileSystem, bool openCaseSensitive);
Result GetCaseSensitivePath(out bool isSuccess, ref string path);
}
}

View File

@ -6,16 +6,10 @@ namespace LibHac.FsService.Creators
{
public Result Create(out IFileSystem subDirFileSystem, IFileSystem baseFileSystem, string path)
{
try
{
baseFileSystem.OpenDirectory(path, OpenDirectoryMode.Directory);
}
catch (HorizonResultException ex)
{
subDirFileSystem = default;
subDirFileSystem = default;
return ex.ResultValue;
}
Result rc = baseFileSystem.OpenDirectory(out IDirectory _, path, OpenDirectoryMode.Directory);
if (rc.IsFailure()) return rc;
subDirFileSystem = new SubdirectoryFileSystem(baseFileSystem, path);

View File

@ -11,11 +11,13 @@ namespace LibHac.FsService
if (!createPathIfMissing)
{
if (path == null) return ResultFs.NullArgument;
if (path == null) return ResultFs.NullArgument.Log();
if (baseFileSystem.GetEntryType(path) != DirectoryEntryType.Directory)
Result rc = baseFileSystem.GetEntryType(out DirectoryEntryType entryType, path);
if (rc.IsFailure() || entryType != DirectoryEntryType.Directory)
{
return ResultFs.PathNotFound;
return ResultFs.PathNotFound.Log();
}
}
@ -28,16 +30,10 @@ namespace LibHac.FsService
{
subFileSystem = default;
if (path == null) return ResultFs.NullArgument;
if (path == null) return ResultFs.NullArgument.Log();
try
{
baseFileSystem.OpenDirectory(path, OpenDirectoryMode.Directory);
}
catch (HorizonResultException ex)
{
return ex.ResultValue;
}
Result rc = baseFileSystem.OpenDirectory(out IDirectory _, path, OpenDirectoryMode.Directory);
if (rc.IsFailure()) return rc;
subFileSystem = new SubdirectoryFileSystem(baseFileSystem, path);

View File

@ -67,9 +67,10 @@ namespace LibHac
private void OpenAllNcas()
{
ContentFs.OpenDirectory(out IDirectory rootDir, "/", OpenDirectoryMode.All).ThrowIfFailure();
// Todo: give warning if directories named "*.nca" are found or manually fix the archive bit
IEnumerable<DirectoryEntry> files = ContentFs.OpenDirectory("/", OpenDirectoryMode.All)
.EnumerateEntries("*.nca", SearchOptions.RecurseSubdirectories)
IEnumerable<DirectoryEntry> files = rootDir.EnumerateEntries("*.nca", SearchOptions.RecurseSubdirectories)
.Where(x => x.Type == DirectoryEntryType.File);
foreach (DirectoryEntry fileEntry in files)
@ -77,9 +78,9 @@ namespace LibHac
SwitchFsNca nca = null;
try
{
IStorage storage = ContentFs.OpenFile(fileEntry.FullPath, OpenMode.Read).AsStorage();
ContentFs.OpenFile(out IFile ncaFile, fileEntry.FullPath, OpenMode.Read).ThrowIfFailure();
nca = new SwitchFsNca(new Nca(Keyset, storage));
nca = new SwitchFsNca(new Nca(Keyset, ncaFile.AsStorage()));
nca.NcaId = GetNcaFilename(fileEntry.Name, nca);
string extension = nca.Nca.Header.ContentType == NcaContentType.Meta ? ".cnmt.nca" : ".nca";
@ -115,7 +116,8 @@ namespace LibHac
try
{
IFile file = SaveFs.OpenFile(fileEntry.FullPath, OpenMode.Read);
SaveFs.OpenFile(out IFile file, fileEntry.FullPath, OpenMode.Read).ThrowIfFailure();
save = new SaveDataFileSystem(Keyset, file.AsStorage(), IntegrityCheckLevel.None, true);
}
catch (Exception ex)
@ -141,7 +143,7 @@ namespace LibHac
IFileSystem fs = nca.OpenFileSystem(NcaSectionType.Data, IntegrityCheckLevel.ErrorOnInvalid);
string cnmtPath = fs.EnumerateEntries("*.cnmt").Single().FullPath;
IFile file = fs.OpenFile(cnmtPath, OpenMode.Read);
fs.OpenFile(out IFile file, cnmtPath, OpenMode.Read).ThrowIfFailure();
var metadata = new Cnmt(file.AsStream());
title.Id = metadata.TitleId;
@ -185,7 +187,7 @@ namespace LibHac
foreach (Title title in Titles.Values.Where(x => x.ControlNca != null))
{
IFileSystem romfs = title.ControlNca.OpenFileSystem(NcaSectionType.Data, IntegrityCheckLevel.ErrorOnInvalid);
IFile control = romfs.OpenFile("control.nacp", OpenMode.Read);
romfs.OpenFile(out IFile control, "control.nacp", OpenMode.Read).ThrowIfFailure();
title.Control = new Nacp(control.AsStream());

View File

@ -28,8 +28,8 @@ namespace LibHac
XciPartition root = GetRootPartition();
if (type == XciPartitionType.Root) return root;
IStorage partitionStorage = root.OpenFile(type.GetFileName(), OpenMode.Read).AsStorage();
return new XciPartition(partitionStorage);
root.OpenFile(out IFile partitionFile, type.GetFileName(), OpenMode.Read).ThrowIfFailure();
return new XciPartition(partitionFile.AsStorage());
}
private XciPartition GetRootPartition()

View File

@ -26,7 +26,9 @@ namespace hactoolnet
private static void CopyDirectoryWithProgressInternal(FileSystemManager fs, string sourcePath, string destPath,
CreateFileOptions options, IProgressReport logger)
{
using (DirectoryHandle sourceHandle = fs.OpenDirectory(sourcePath, OpenDirectoryMode.All))
fs.OpenDirectory(out DirectoryHandle sourceHandle, sourcePath, OpenDirectoryMode.All).ThrowIfFailure();
using (sourceHandle)
{
foreach (DirectoryEntry entry in fs.ReadDirectory(sourceHandle))
{
@ -63,37 +65,53 @@ namespace hactoolnet
return size;
}
public static void CopyFileWithProgress(FileSystemManager fs, string sourcePath, string destPath, IProgressReport logger = null)
public static Result CopyFileWithProgress(FileSystemManager fs, string sourcePath, string destPath, IProgressReport logger = null)
{
using (FileHandle sourceHandle = fs.OpenFile(sourcePath, OpenMode.Read))
using (FileHandle destHandle = fs.OpenFile(destPath, OpenMode.Write | OpenMode.AllowAppend))
Result rc = fs.OpenFile(out FileHandle sourceHandle, sourcePath, OpenMode.Read);
if (rc.IsFailure()) return rc;
using (sourceHandle)
{
const int maxBufferSize = 1024 * 1024;
rc = fs.OpenFile(out FileHandle destHandle, destPath, OpenMode.Write | OpenMode.AllowAppend);
if (rc.IsFailure()) return rc;
long fileSize = fs.GetFileSize(sourceHandle);
int bufferSize = (int)Math.Min(maxBufferSize, fileSize);
byte[] buffer = ArrayPool<byte>.Shared.Rent(bufferSize);
try
using (destHandle)
{
for (long offset = 0; offset < fileSize; offset += bufferSize)
const int maxBufferSize = 1024 * 1024;
rc = fs.GetFileSize(out long fileSize, sourceHandle);
if (rc.IsFailure()) return rc;
int bufferSize = (int)Math.Min(maxBufferSize, fileSize);
byte[] buffer = ArrayPool<byte>.Shared.Rent(bufferSize);
try
{
int toRead = (int)Math.Min(fileSize - offset, bufferSize);
Span<byte> buf = buffer.AsSpan(0, toRead);
for (long offset = 0; offset < fileSize; offset += bufferSize)
{
int toRead = (int)Math.Min(fileSize - offset, bufferSize);
Span<byte> buf = buffer.AsSpan(0, toRead);
fs.ReadFile(sourceHandle, buf, offset);
fs.WriteFile(destHandle, buf, offset);
rc = fs.ReadFile(out long _, sourceHandle, buf, offset);
if (rc.IsFailure()) return rc;
logger?.ReportAdd(toRead);
rc = fs.WriteFile(destHandle, buf, offset);
if (rc.IsFailure()) return rc;
logger?.ReportAdd(toRead);
}
}
finally
{
ArrayPool<byte>.Shared.Return(buffer);
}
}
finally
{
ArrayPool<byte>.Shared.Return(buffer);
}
fs.FlushFile(destHandle);
rc = fs.FlushFile(destHandle);
if (rc.IsFailure()) return rc;
}
}
return Result.Success;
}
}
}

View File

@ -33,7 +33,9 @@ namespace hactoolnet
throw new FileNotFoundException("Specified NCA does not contain a delta fragment");
}
deltaStorage = fs.OpenFile(FragmentFileName, OpenMode.Read).AsStorage();
fs.OpenFile(out IFile deltaFragmentFile, FragmentFileName, OpenMode.Read).ThrowIfFailure();
deltaStorage = deltaFragmentFile.AsStorage();
}
catch (InvalidDataException) { } // Ignore non-NCA3 files
}

View File

@ -210,8 +210,8 @@ namespace hactoolnet
IFileSystem pfs = nca.OpenFileSystem(NcaSectionType.Code, IntegrityCheckLevel.ErrorOnInvalid);
if (!pfs.FileExists("main.npdm")) return Validity.Unchecked;
IFile npdmStorage = pfs.OpenFile("main.npdm", OpenMode.Read);
var npdm = new NpdmBinary(npdmStorage.AsStream());
pfs.OpenFile(out IFile npdmFile, "main.npdm", OpenMode.Read).ThrowIfFailure();
var npdm = new NpdmBinary(npdmFile.AsStream());
return nca.Header.VerifySignature2(npdm.AciD.Rsa2048Modulus);
}

View File

@ -62,7 +62,9 @@ namespace hactoolnet
using (IFile inFile = new LocalFile(ctx.Options.ReplaceFileSource, OpenMode.Read))
{
using (IFile outFile = save.OpenFile(destFilename, OpenMode.ReadWrite))
save.OpenFile(out IFile outFile, destFilename, OpenMode.ReadWrite).ThrowIfFailure();
using (outFile)
{
inFile.GetSize(out long inFileSize).ThrowIfFailure();
outFile.GetSize(out long outFileSize).ThrowIfFailure();
@ -100,7 +102,7 @@ namespace hactoolnet
{
if (signNeeded)
{
save.Commit(ctx.Keyset);
save.Commit(ctx.Keyset).ThrowIfFailure();
signNeeded = false;
}
}
@ -114,7 +116,7 @@ namespace hactoolnet
if (signNeeded)
{
if (save.Commit(ctx.Keyset))
if (save.Commit(ctx.Keyset).IsSuccess())
{
ctx.Logger.LogMessage("Successfully signed save file");
}
@ -310,7 +312,7 @@ namespace hactoolnet
var sb = new StringBuilder();
sb.AppendLine();
long freeSpace = save.GetFreeSpaceSize("");
save.GetFreeSpaceSize(out long freeSpace, "").ThrowIfFailure();
sb.AppendLine("Savefile:");
PrintItem(sb, colLen, $"CMAC Signature{save.Header.SignatureValidity.GetValidityString()}:", save.Header.Cmac);