Use more result codes in FS code

This commit is contained in:
Alex Barney 2019-06-28 19:41:09 -05:00
parent 46d4274686
commit 8f37b2b1c4
30 changed files with 325 additions and 140 deletions

View file

@ -2,8 +2,6 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using static LibHac.Fs.ResultsFs;
namespace LibHac.Fs.Accessors namespace LibHac.Fs.Accessors
{ {
public class FileSystemAccessor public class FileSystemAccessor
@ -129,7 +127,7 @@ namespace LibHac.Fs.Accessors
{ {
if (OpenFiles.Any(x => (x.OpenMode & OpenMode.Write) != 0)) if (OpenFiles.Any(x => (x.OpenMode & OpenMode.Write) != 0))
{ {
ThrowHelper.ThrowResult(ResultFsWritableFileOpen); ThrowHelper.ThrowResult(ResultFs.WritableFileOpen);
} }
FileSystem.Commit(); FileSystem.Commit();

View file

@ -1,7 +1,6 @@
using System.Collections.Generic; using System.Collections.Generic;
using static LibHac.Results; using static LibHac.Results;
using static LibHac.Fs.ResultsFs;
namespace LibHac.Fs.Accessors namespace LibHac.Fs.Accessors
{ {
@ -19,7 +18,7 @@ namespace LibHac.Fs.Accessors
if (Table.ContainsKey(mountName)) if (Table.ContainsKey(mountName))
{ {
return ResultFsMountNameAlreadyExists; return ResultFs.MountNameAlreadyExists;
} }
Table.Add(mountName, fileSystem); Table.Add(mountName, fileSystem);
@ -34,7 +33,7 @@ namespace LibHac.Fs.Accessors
{ {
if (!Table.TryGetValue(name, out fileSystem)) if (!Table.TryGetValue(name, out fileSystem))
{ {
return ResultFsMountNameNotFound; return ResultFs.MountNameNotFound;
} }
return ResultSuccess; return ResultSuccess;
@ -47,7 +46,7 @@ namespace LibHac.Fs.Accessors
{ {
if (!Table.TryGetValue(name, out FileSystemAccessor fsAccessor)) if (!Table.TryGetValue(name, out FileSystemAccessor fsAccessor))
{ {
return ResultFsMountNameNotFound; return ResultFs.MountNameNotFound;
} }
Table.Remove(name); Table.Remove(name);

View file

@ -28,7 +28,12 @@ namespace LibHac.Fs
if (!Header.TryDecryptHeader(Path, KekSeed, VerificationKey)) if (!Header.TryDecryptHeader(Path, KekSeed, VerificationKey))
{ {
throw new ArgumentException("NAX0 key derivation failed."); ThrowHelper.ThrowResult(ResultFs.AesXtsFileHeaderInvalidKeys, "NAX0 key derivation failed.");
}
if (HeaderLength + Util.AlignUp(Header.Size, 0x10) > baseFile.GetSize())
{
ThrowHelper.ThrowResult(ResultFs.AesXtsFileTooShort, "NAX0 key derivation failed.");
} }
IStorage encStorage = BaseFile.AsStorage().Slice(HeaderLength, Util.AlignUp(Header.Size, 0x10)); IStorage encStorage = BaseFile.AsStorage().Slice(HeaderLength, Util.AlignUp(Header.Size, 0x10));

View file

@ -1,5 +1,4 @@
using System; using System;
using System.IO;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using System.Security.Cryptography; using System.Security.Cryptography;
using System.Text; using System.Text;
@ -22,6 +21,11 @@ namespace LibHac.Fs
public AesXtsFileHeader(IFile aesXtsFile) public AesXtsFileHeader(IFile aesXtsFile)
{ {
if (aesXtsFile.GetSize() < 0x80)
{
ThrowHelper.ThrowResult(ResultFs.AesXtsFileHeaderTooShort);
}
var reader = new FileReader(aesXtsFile); var reader = new FileReader(aesXtsFile);
reader.ReadBytes(Signature); reader.ReadBytes(Signature);
@ -33,7 +37,7 @@ namespace LibHac.Fs
if (Magic != AesXtsFileMagic) if (Magic != AesXtsFileMagic)
{ {
throw new InvalidDataException("Invalid NAX0 magic value"); ThrowHelper.ThrowResult(ResultFs.AesXtsFileHeaderInvalidMagic, "Invalid NAX0 magic value");
} }
} }

View file

@ -1,6 +1,5 @@
using System; using System;
using System.Diagnostics; using System.Diagnostics;
using System.IO;
namespace LibHac.Fs namespace LibHac.Fs
{ {
@ -218,7 +217,7 @@ namespace LibHac.Fs
{ {
if (!TryReadXtsHeader(filePath, keyPath, out AesXtsFileHeader header)) if (!TryReadXtsHeader(filePath, keyPath, out AesXtsFileHeader header))
{ {
throw new InvalidDataException("Could not decrypt AES-XTS keys"); ThrowHelper.ThrowResult(ResultFs.AesXtsFileHeaderInvalidKeysInRenameFile, "Could not decrypt AES-XTS keys");
} }
return header; return header;

View file

@ -1,6 +1,5 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO;
namespace LibHac.Fs namespace LibHac.Fs
{ {
@ -38,10 +37,12 @@ namespace LibHac.Fs
public void CreateDirectory(string path) public void CreateDirectory(string path)
{ {
path = PathTools.Normalize(path); path = PathTools.Normalize(path);
string parent = PathTools.GetParentDirectory(path);
if (FileExists(path)) if (IsConcatenationFile(parent))
{ {
throw new IOException("Cannot create directory because a file with this name already exists."); ThrowHelper.ThrowResult(ResultFs.PathNotFound,
"Cannot create a directory inside of a concatenation file");
} }
BaseFileSystem.CreateDirectory(path); BaseFileSystem.CreateDirectory(path);
@ -61,7 +62,12 @@ namespace LibHac.Fs
// A concatenation file directory can't contain normal files // A concatenation file directory can't contain normal files
string parentDir = PathTools.GetParentDirectory(path); string parentDir = PathTools.GetParentDirectory(path);
if (IsConcatenationFile(parentDir)) throw new IOException("Cannot create files inside of a concatenation file");
if (IsConcatenationFile(parentDir))
{
ThrowHelper.ThrowResult(ResultFs.PathNotFound,
"Cannot create a concatenation file inside of a concatenation file");
}
BaseFileSystem.CreateDirectory(path); BaseFileSystem.CreateDirectory(path);
SetConcatenationFileAttribute(path); SetConcatenationFileAttribute(path);
@ -85,7 +91,7 @@ namespace LibHac.Fs
if (IsConcatenationFile(path)) if (IsConcatenationFile(path))
{ {
throw new DirectoryNotFoundException(path); ThrowHelper.ThrowResult(ResultFs.PathNotFound);
} }
BaseFileSystem.DeleteDirectory(path); BaseFileSystem.DeleteDirectory(path);
@ -95,7 +101,7 @@ namespace LibHac.Fs
{ {
path = PathTools.Normalize(path); path = PathTools.Normalize(path);
if (IsConcatenationFile(path)) throw new DirectoryNotFoundException(); if (IsConcatenationFile(path)) ThrowHelper.ThrowResult(ResultFs.PathNotFound);
BaseFileSystem.DeleteDirectoryRecursively(path); BaseFileSystem.DeleteDirectoryRecursively(path);
} }
@ -104,7 +110,7 @@ namespace LibHac.Fs
{ {
path = PathTools.Normalize(path); path = PathTools.Normalize(path);
if (IsConcatenationFile(path)) throw new DirectoryNotFoundException(); if (IsConcatenationFile(path)) ThrowHelper.ThrowResult(ResultFs.PathNotFound);
BaseFileSystem.CleanDirectoryRecursively(path); BaseFileSystem.CleanDirectoryRecursively(path);
} }
@ -134,7 +140,7 @@ namespace LibHac.Fs
if (IsConcatenationFile(path)) if (IsConcatenationFile(path))
{ {
throw new DirectoryNotFoundException(path); ThrowHelper.ThrowResult(ResultFs.PathNotFound);
} }
IDirectory parentDir = BaseFileSystem.OpenDirectory(path, OpenDirectoryMode.All); IDirectory parentDir = BaseFileSystem.OpenDirectory(path, OpenDirectoryMode.All);
@ -172,7 +178,7 @@ namespace LibHac.Fs
if (IsConcatenationFile(srcPath)) if (IsConcatenationFile(srcPath))
{ {
throw new DirectoryNotFoundException(); ThrowHelper.ThrowResult(ResultFs.PathNotFound);
} }
BaseFileSystem.RenameDirectory(srcPath, dstPath); BaseFileSystem.RenameDirectory(srcPath, dstPath);
@ -238,7 +244,7 @@ namespace LibHac.Fs
public void QueryEntry(Span<byte> outBuffer, ReadOnlySpan<byte> inBuffer, string path, QueryId queryId) public void QueryEntry(Span<byte> outBuffer, ReadOnlySpan<byte> inBuffer, string path, QueryId queryId)
{ {
if (queryId != QueryId.MakeConcatFile) throw new NotSupportedException(); if (queryId != QueryId.MakeConcatFile) ThrowHelper.ThrowResult(ResultFs.UnsupportedOperationInConcatFsQueryEntry);
SetConcatenationFileAttribute(path); SetConcatenationFileAttribute(path);
} }

View file

@ -175,24 +175,28 @@ namespace LibHac.Fs
public long GetFreeSpaceSize(string path) public long GetFreeSpaceSize(string path)
{ {
throw new NotSupportedException(); ThrowHelper.ThrowResult(ResultFs.NotImplemented);
return default;
} }
public long GetTotalSpaceSize(string path) public long GetTotalSpaceSize(string path)
{ {
throw new NotSupportedException(); ThrowHelper.ThrowResult(ResultFs.NotImplemented);
return default;
} }
public FileTimeStampRaw GetFileTimeStampRaw(string path) public FileTimeStampRaw GetFileTimeStampRaw(string path)
{ {
throw new NotSupportedException(); ThrowHelper.ThrowResult(ResultFs.NotImplemented);
return default;
} }
public void Commit() public void Commit()
{ {
if (OpenWritableFileCount > 0) if (OpenWritableFileCount > 0)
{ {
throw new InvalidOperationException("All files must be closed before commiting save data."); ThrowHelper.ThrowResult(ResultFs.WritableFileOpen,
"All files must be closed before commiting save data.");
} }
SynchronizeDirectory(SyncDir, WorkingDir); SynchronizeDirectory(SyncDir, WorkingDir);
@ -204,7 +208,7 @@ namespace LibHac.Fs
public void QueryEntry(Span<byte> outBuffer, ReadOnlySpan<byte> inBuffer, string path, QueryId queryId) public void QueryEntry(Span<byte> outBuffer, ReadOnlySpan<byte> inBuffer, string path, QueryId queryId)
{ {
throw new NotSupportedException(); ThrowHelper.ThrowResult(ResultFs.NotImplemented);
} }
private string GetFullPath(string path) private string GetFullPath(string path)

View file

@ -20,14 +20,14 @@ namespace LibHac.Fs
{ {
if (IsDisposed) throw new ObjectDisposedException(null); if (IsDisposed) throw new ObjectDisposedException(null);
if ((Mode & OpenMode.Read) == 0) throw new NotSupportedException("File does not allow reading."); if ((Mode & OpenMode.Read) == 0) ThrowHelper.ThrowResult(ResultFs.InvalidOpenModeOperation, "File does not allow reading.");
if (span == null) throw new ArgumentNullException(nameof(span)); if (span == null) throw new ArgumentNullException(nameof(span));
if (offset < 0) throw new ArgumentOutOfRangeException(nameof(offset), "Argument must be non-negative."); if (offset < 0) ThrowHelper.ThrowResult(ResultFs.ValueOutOfRange, "Offset must be non-negative.");
long fileSize = GetSize(); long fileSize = GetSize();
int size = span.Length; int size = span.Length;
if (offset > fileSize) throw new ArgumentOutOfRangeException(nameof(offset), "Offset must be less than the file size."); if (offset > fileSize) ThrowHelper.ThrowResult(ResultFs.ValueOutOfRange, "Offset must be less than the file size.");
return (int)Math.Min(fileSize - offset, size); return (int)Math.Min(fileSize - offset, size);
} }
@ -36,10 +36,10 @@ namespace LibHac.Fs
{ {
if (IsDisposed) throw new ObjectDisposedException(null); if (IsDisposed) throw new ObjectDisposedException(null);
if ((Mode & OpenMode.Write) == 0) throw new NotSupportedException("File does not allow writing."); if ((Mode & OpenMode.Write) == 0) ThrowHelper.ThrowResult(ResultFs.InvalidOpenModeOperation, "File does not allow writing.");
if (span == null) throw new ArgumentNullException(nameof(span)); if (span == null) throw new ArgumentNullException(nameof(span));
if (offset < 0) throw new ArgumentOutOfRangeException(nameof(offset), "Argument must be non-negative."); if (offset < 0) ThrowHelper.ThrowResult(ResultFs.ValueOutOfRange, "Offset must be non-negative.");
long fileSize = GetSize(); long fileSize = GetSize();
int size = span.Length; int size = span.Length;
@ -48,7 +48,7 @@ namespace LibHac.Fs
{ {
if ((Mode & OpenMode.Append) == 0) if ((Mode & OpenMode.Append) == 0)
{ {
throw new NotSupportedException("File does not allow appending."); ThrowHelper.ThrowResult(ResultFs.AllowAppendRequiredForImplicitExtension);
} }
SetSize(offset + size); SetSize(offset + size);

View file

@ -4,7 +4,6 @@ using System.Runtime.CompilerServices;
using LibHac.Fs.Accessors; using LibHac.Fs.Accessors;
using static LibHac.Results; using static LibHac.Results;
using static LibHac.Fs.ResultsFs;
namespace LibHac.Fs namespace LibHac.Fs
{ {
@ -192,7 +191,7 @@ namespace LibHac.Fs
if (oldFileSystem != newFileSystem) if (oldFileSystem != newFileSystem)
{ {
ThrowHelper.ThrowResult(ResultFsDifferentDestFileSystem); ThrowHelper.ThrowResult(ResultFs.DifferentDestFileSystem);
} }
if (IsEnabledAccessLog() && oldFileSystem.IsAccessLogEnabled) if (IsEnabledAccessLog() && oldFileSystem.IsAccessLogEnabled)
@ -219,7 +218,7 @@ namespace LibHac.Fs
if (oldFileSystem != newFileSystem) if (oldFileSystem != newFileSystem)
{ {
ThrowHelper.ThrowResult(ResultFsDifferentDestFileSystem); ThrowHelper.ThrowResult(ResultFs.DifferentDestFileSystem);
} }
if (IsEnabledAccessLog() && oldFileSystem.IsAccessLogEnabled) if (IsEnabledAccessLog() && oldFileSystem.IsAccessLogEnabled)
@ -531,7 +530,7 @@ namespace LibHac.Fs
mountName = default; mountName = default;
subPath = default; subPath = default;
return ResultFsInvalidMountName; return ResultFs.InvalidMountName;
} }
mountName = path.Slice(0, mountLen); mountName = path.Slice(0, mountLen);

View file

@ -91,7 +91,6 @@ namespace LibHac.Fs
/// </summary> /// </summary>
/// <param name="srcPath">The full path of the file to rename.</param> /// <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="dstPath">The new full path of the file.</param>
/// <exception cref="FileNotFoundException">The specified file does not exist.</exception>
/// <exception cref="IOException">An I/O error occurred while deleting the file.</exception> /// <exception cref="IOException">An I/O error occurred while deleting the file.</exception>
void RenameFile(string srcPath, string dstPath); void RenameFile(string srcPath, string dstPath);
@ -114,7 +113,6 @@ namespace LibHac.Fs
/// </summary> /// </summary>
/// <param name="path">The full path to check.</param> /// <param name="path">The full path to check.</param>
/// <returns>The <see cref="DirectoryEntryType"/> of the file.</returns> /// <returns>The <see cref="DirectoryEntryType"/> of the file.</returns>
/// <exception cref="FileNotFoundException">The specified path does not exist.</exception>
DirectoryEntryType GetEntryType(string path); DirectoryEntryType GetEntryType(string path);
/// <summary> /// <summary>

View file

@ -31,6 +31,11 @@ namespace LibHac.Fs
{ {
RelocationEntry entry = GetRelocationEntry(offset); RelocationEntry entry = GetRelocationEntry(offset);
if (entry.SourceIndex > Sources.Count)
{
ThrowHelper.ThrowResult(ResultFs.InvalidIndirectStorageSource);
}
long inPos = offset; long inPos = offset;
int outPos = 0; int outPos = 0;
int remaining = destination.Length; int remaining = destination.Length;
@ -55,16 +60,18 @@ namespace LibHac.Fs
protected override void WriteImpl(ReadOnlySpan<byte> source, long offset) protected override void WriteImpl(ReadOnlySpan<byte> source, long offset)
{ {
throw new NotImplementedException(); ThrowHelper.ThrowResult(ResultFs.UnsupportedOperationInIndirectStorageWrite);
} }
public override void Flush() public override void Flush() { }
{
throw new NotImplementedException();
}
public override long GetSize() => _length; public override long GetSize() => _length;
public override void SetSize(long size)
{
ThrowHelper.ThrowResult(ResultFs.UnsupportedOperationInIndirectStorageSetSize);
}
private RelocationEntry GetRelocationEntry(long offset) private RelocationEntry GetRelocationEntry(long offset)
{ {
int index = RelocationOffsets.BinarySearch(offset); int index = RelocationOffsets.BinarySearch(offset);

View file

@ -43,7 +43,8 @@ namespace LibHac.Fs
if (BlockValidities[blockIndex] == Validity.Invalid && integrityCheckLevel == IntegrityCheckLevel.ErrorOnInvalid) if (BlockValidities[blockIndex] == Validity.Invalid && integrityCheckLevel == IntegrityCheckLevel.ErrorOnInvalid)
{ {
throw new InvalidDataException("Hash error!"); // Todo: Differentiate between the top and lower layers
ThrowHelper.ThrowResult(ResultFs.InvalidHashInIvfc, "Hash error!");
} }
bool needsHashCheck = integrityCheckLevel != IntegrityCheckLevel.None && bool needsHashCheck = integrityCheckLevel != IntegrityCheckLevel.None &&
@ -104,7 +105,7 @@ namespace LibHac.Fs
if (validity == Validity.Invalid && integrityCheckLevel == IntegrityCheckLevel.ErrorOnInvalid) if (validity == Validity.Invalid && integrityCheckLevel == IntegrityCheckLevel.ErrorOnInvalid)
{ {
throw new InvalidDataException("Hash error!"); ThrowHelper.ThrowResult(ResultFs.InvalidHashInIvfc, "Hash error!");
} }
} }
finally finally

View file

@ -1,6 +1,5 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO;
namespace LibHac.Fs namespace LibHac.Fs
{ {
@ -44,7 +43,8 @@ namespace LibHac.Fs
} }
} }
throw new FileNotFoundException(); ThrowHelper.ThrowResult(ResultFs.PathNotFound);
return default;
} }
public bool DirectoryExists(string path) public bool DirectoryExists(string path)
@ -94,7 +94,8 @@ namespace LibHac.Fs
} }
} }
throw new FileNotFoundException(path); ThrowHelper.ThrowResult(ResultFs.PathNotFound);
return DirectoryEntryType.NotFound;
} }
public FileTimeStampRaw GetFileTimeStampRaw(string path) public FileTimeStampRaw GetFileTimeStampRaw(string path)
@ -109,7 +110,8 @@ namespace LibHac.Fs
} }
} }
throw new FileNotFoundException(path); ThrowHelper.ThrowResult(ResultFs.PathNotFound);
return default;
} }
public void QueryEntry(Span<byte> outBuffer, ReadOnlySpan<byte> inBuffer, string path, QueryId queryId) public void QueryEntry(Span<byte> outBuffer, ReadOnlySpan<byte> inBuffer, string path, QueryId queryId)
@ -125,20 +127,30 @@ namespace LibHac.Fs
} }
} }
throw new FileNotFoundException(path); ThrowHelper.ThrowResult(ResultFs.PathNotFound);
} }
public void Commit() { } public void Commit() { }
public void CreateDirectory(string path) => throw new NotSupportedException(); public void CreateDirectory(string path) => ThrowHelper.ThrowResult(ResultFs.UnsupportedOperation);
public void CreateFile(string path, long size, CreateFileOptions options) => throw new NotSupportedException(); public void CreateFile(string path, long size, CreateFileOptions options) => ThrowHelper.ThrowResult(ResultFs.UnsupportedOperation);
public void DeleteDirectory(string path) => throw new NotSupportedException(); public void DeleteDirectory(string path) => ThrowHelper.ThrowResult(ResultFs.UnsupportedOperation);
public void DeleteDirectoryRecursively(string path) => throw new NotSupportedException(); public void DeleteDirectoryRecursively(string path) => ThrowHelper.ThrowResult(ResultFs.UnsupportedOperation);
public void CleanDirectoryRecursively(string path) => throw new NotSupportedException(); public void CleanDirectoryRecursively(string path) => ThrowHelper.ThrowResult(ResultFs.UnsupportedOperation);
public void DeleteFile(string path) => throw new NotSupportedException(); public void DeleteFile(string path) => ThrowHelper.ThrowResult(ResultFs.UnsupportedOperation);
public void RenameDirectory(string srcPath, string dstPath) => throw new NotSupportedException(); public void RenameDirectory(string srcPath, string dstPath) => ThrowHelper.ThrowResult(ResultFs.UnsupportedOperation);
public void RenameFile(string srcPath, string dstPath) => throw new NotSupportedException(); public void RenameFile(string srcPath, string dstPath) => ThrowHelper.ThrowResult(ResultFs.UnsupportedOperation);
public long GetFreeSpaceSize(string path) => throw new NotSupportedException();
public long GetTotalSpaceSize(string path) => throw new NotSupportedException(); public long GetFreeSpaceSize(string path)
{
ThrowHelper.ThrowResult(ResultFs.UnsupportedOperation);
return default;
}
public long GetTotalSpaceSize(string path)
{
ThrowHelper.ThrowResult(ResultFs.UnsupportedOperation);
return default;
}
} }
} }

View file

@ -179,7 +179,8 @@ namespace LibHac.Fs
return DirectoryEntryType.File; return DirectoryEntryType.File;
} }
throw new FileNotFoundException(path); ThrowHelper.ThrowResult(ResultFs.PathNotFound);
return DirectoryEntryType.NotFound;
} }
public FileTimeStampRaw GetFileTimeStampRaw(string path) public FileTimeStampRaw GetFileTimeStampRaw(string path)
@ -208,6 +209,7 @@ namespace LibHac.Fs
public void Commit() { } public void Commit() { }
public void QueryEntry(Span<byte> outBuffer, ReadOnlySpan<byte> inBuffer, string path, QueryId queryId) => throw new NotSupportedException(); public void QueryEntry(Span<byte> outBuffer, ReadOnlySpan<byte> inBuffer, string path, QueryId queryId) =>
ThrowHelper.ThrowResult(ResultFs.UnsupportedOperation);
} }
} }

View file

@ -95,5 +95,10 @@ namespace LibHac.Fs
public override void Flush() { } public override void Flush() { }
public override long GetSize() => _length; public override long GetSize() => _length;
public override void SetSize(long size)
{
ThrowHelper.ThrowResult(ResultFs.UnsupportedOperationInMemoryStorageSetSize);
}
} }
} }

View file

@ -150,8 +150,6 @@ namespace LibHac.Fs.NcaUtils
{ {
const int sectorSize = 0x200; const int sectorSize = 0x200;
NcaFsHeader fsHeader = Header.GetFsHeader(index);
byte[] key0 = GetContentKey(NcaKeyType.AesXts0); byte[] key0 = GetContentKey(NcaKeyType.AesXts0);
byte[] key1 = GetContentKey(NcaKeyType.AesXts1); byte[] key1 = GetContentKey(NcaKeyType.AesXts1);

View file

@ -28,11 +28,22 @@ namespace LibHac.Fs
public override void Write(ReadOnlySpan<byte> source, long offset, WriteOption options) public override void Write(ReadOnlySpan<byte> source, long offset, WriteOption options)
{ {
throw new NotImplementedException(); ValidateWriteParams(source, offset);
BaseStorage.Write(source, offset);
if ((options & WriteOption.Flush) != 0)
{
BaseStorage.Flush();
}
} }
public override void Flush() public override void Flush()
{ {
if ((Mode & OpenMode.Write) != 0)
{
BaseStorage.Flush();
}
} }
public override long GetSize() public override long GetSize()
@ -42,7 +53,7 @@ namespace LibHac.Fs
public override void SetSize(long size) public override void SetSize(long size)
{ {
throw new NotSupportedException(); ThrowHelper.ThrowResult(ResultFs.UnsupportedOperationInPartitionFileSetSize);
} }
} }
} }

View file

@ -40,7 +40,7 @@ namespace LibHac.Fs
if (!FileDict.TryGetValue(path, out PartitionFileEntry entry)) if (!FileDict.TryGetValue(path, out PartitionFileEntry entry))
{ {
throw new FileNotFoundException(); ThrowHelper.ThrowResult(ResultFs.PathNotFound);
} }
return OpenFile(entry, mode); return OpenFile(entry, mode);
@ -75,19 +75,35 @@ namespace LibHac.Fs
throw new FileNotFoundException(path); throw new FileNotFoundException(path);
} }
public void CreateDirectory(string path) => throw new NotSupportedException(); public void CreateDirectory(string path) => ThrowHelper.ThrowResult(ResultFs.UnsupportedOperationModifyPartitionFileSystem);
public void CreateFile(string path, long size, CreateFileOptions options) => throw new NotSupportedException(); public void CreateFile(string path, long size, CreateFileOptions options) => ThrowHelper.ThrowResult(ResultFs.UnsupportedOperationModifyPartitionFileSystem);
public void DeleteDirectory(string path) => throw new NotSupportedException(); public void DeleteDirectory(string path) => ThrowHelper.ThrowResult(ResultFs.UnsupportedOperationModifyPartitionFileSystem);
public void DeleteDirectoryRecursively(string path) => throw new NotSupportedException(); public void DeleteDirectoryRecursively(string path) => ThrowHelper.ThrowResult(ResultFs.UnsupportedOperationModifyPartitionFileSystem);
public void CleanDirectoryRecursively(string path) => throw new NotSupportedException(); public void CleanDirectoryRecursively(string path) => ThrowHelper.ThrowResult(ResultFs.UnsupportedOperationModifyPartitionFileSystem);
public void DeleteFile(string path) => throw new NotSupportedException(); public void DeleteFile(string path) => ThrowHelper.ThrowResult(ResultFs.UnsupportedOperationModifyPartitionFileSystem);
public void RenameDirectory(string srcPath, string dstPath) => throw new NotSupportedException(); public void RenameDirectory(string srcPath, string dstPath) => ThrowHelper.ThrowResult(ResultFs.UnsupportedOperationModifyPartitionFileSystem);
public void RenameFile(string srcPath, string dstPath) => throw new NotSupportedException(); public void RenameFile(string srcPath, string dstPath) => ThrowHelper.ThrowResult(ResultFs.UnsupportedOperationModifyPartitionFileSystem);
public long GetFreeSpaceSize(string path) => throw new NotSupportedException();
public long GetTotalSpaceSize(string path) => throw new NotSupportedException(); public long GetFreeSpaceSize(string path)
public FileTimeStampRaw GetFileTimeStampRaw(string path) => throw new NotSupportedException(); {
ThrowHelper.ThrowResult(ResultFs.NotImplemented);
return default;
}
public long GetTotalSpaceSize(string path)
{
ThrowHelper.ThrowResult(ResultFs.NotImplemented);
return default;
}
public FileTimeStampRaw GetFileTimeStampRaw(string path)
{
ThrowHelper.ThrowResult(ResultFs.NotImplemented);
return default;
}
public void Commit() { } public void Commit() { }
public void QueryEntry(Span<byte> outBuffer, ReadOnlySpan<byte> inBuffer, string path, QueryId queryId) => throw new NotSupportedException(); public void QueryEntry(Span<byte> outBuffer, ReadOnlySpan<byte> inBuffer, string path, QueryId queryId) => ThrowHelper.ThrowResult(ResultFs.NotImplemented);
} }
public enum PartitionFileSystemType public enum PartitionFileSystemType
@ -122,7 +138,8 @@ namespace LibHac.Fs
Type = PartitionFileSystemType.Hashed; Type = PartitionFileSystemType.Hashed;
break; break;
default: default:
throw new InvalidDataException($"Invalid Partition FS type \"{Magic}\""); ThrowHelper.ThrowResult(ResultFs.InvalidPartitionFileSystemMagic, $"Invalid Partition FS type \"{Magic}\"");
break;
} }
int entrySize = PartitionFileEntry.GetEntrySize(Type); int entrySize = PartitionFileEntry.GetEntrySize(Type);

View file

@ -3,7 +3,6 @@ using System.Diagnostics;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using static LibHac.Results; using static LibHac.Results;
using static LibHac.Fs.ResultsFs;
#if !NETFRAMEWORK #if !NETFRAMEWORK
using System.IO.Enumeration; using System.IO.Enumeration;
@ -382,7 +381,7 @@ namespace LibHac.Fs
} }
mountName = default; mountName = default;
return ResultFsInvalidMountName; return ResultFs.InvalidMountName;
} }
public static bool MatchesPattern(string searchPattern, string name, bool ignoreCase) public static bool MatchesPattern(string searchPattern, string name, bool ignoreCase)

View file

@ -22,7 +22,11 @@ namespace LibHac.Fs
} }
public override void Flush() { } public override void Flush() { }
public override void Write(ReadOnlySpan<byte> source, long offset, WriteOption options) => throw new NotSupportedException();
public override void SetSize(long size) => throw new NotSupportedException(); public override void Write(ReadOnlySpan<byte> source, long offset, WriteOption options) =>
ThrowHelper.ThrowResult(ResultFs.UnsupportedOperationModifyReadOnlyFile);
public override void SetSize(long size) =>
ThrowHelper.ThrowResult(ResultFs.UnsupportedOperationModifyReadOnlyFile);
} }
} }

View file

@ -63,13 +63,29 @@ namespace LibHac.Fs
BaseFs.QueryEntry(outBuffer, inBuffer, path, queryId); BaseFs.QueryEntry(outBuffer, inBuffer, path, queryId);
} }
public void CreateDirectory(string path) => throw new NotSupportedException(); public void CreateDirectory(string path) =>
public void CreateFile(string path, long size, CreateFileOptions options) => throw new NotSupportedException(); ThrowHelper.ThrowResult(ResultFs.UnsupportedOperationModifyReadOnlyFileSystem);
public void DeleteDirectory(string path) => throw new NotSupportedException();
public void DeleteDirectoryRecursively(string path) => throw new NotSupportedException(); public void CreateFile(string path, long size, CreateFileOptions options) =>
public void CleanDirectoryRecursively(string path) => throw new NotSupportedException(); ThrowHelper.ThrowResult(ResultFs.UnsupportedOperationModifyReadOnlyFileSystem);
public void DeleteFile(string path) => throw new NotSupportedException();
public void RenameDirectory(string srcPath, string dstPath) => throw new NotSupportedException(); public void DeleteDirectory(string path) =>
public void RenameFile(string srcPath, string dstPath) => throw new NotSupportedException(); ThrowHelper.ThrowResult(ResultFs.UnsupportedOperationModifyReadOnlyFileSystem);
public void DeleteDirectoryRecursively(string path) =>
ThrowHelper.ThrowResult(ResultFs.UnsupportedOperationModifyReadOnlyFileSystem);
public void CleanDirectoryRecursively(string path) =>
ThrowHelper.ThrowResult(ResultFs.UnsupportedOperationModifyReadOnlyFileSystem);
public void DeleteFile(string path) =>
ThrowHelper.ThrowResult(ResultFs.UnsupportedOperationModifyReadOnlyFileSystem);
public void RenameDirectory(string srcPath, string dstPath) =>
ThrowHelper.ThrowResult(ResultFs.UnsupportedOperationModifyReadOnlyFileSystem);
public void RenameFile(string srcPath, string dstPath) =>
ThrowHelper.ThrowResult(ResultFs.UnsupportedOperationModifyReadOnlyFileSystem);
} }
} }

71
src/LibHac/Fs/ResultFs.cs Normal file
View file

@ -0,0 +1,71 @@
namespace LibHac.Fs
{
public class ResultFs
{
public const int ModuleFs = 2;
public static Result PathNotFound => new Result(ModuleFs, 1);
public static Result MountNameAlreadyExists => new Result(ModuleFs, 60);
public static Result NotImplemented => new Result(ModuleFs, 3001);
public static Result ValueOutOfRange => new Result(ModuleFs, 3005);
public static Result AesXtsFileFileStorageAllocationError => new Result(ModuleFs, 3312);
public static Result AesXtsFileXtsStorageAllocationError => new Result(ModuleFs, 3313);
public static Result AesXtsFileAlignmentStorageAllocationError => new Result(ModuleFs, 3314);
public static Result AesXtsFileStorageFileAllocationError => new Result(ModuleFs, 3315);
public static Result AesXtsFileSubStorageAllocationError => new Result(ModuleFs, 3383);
public static Result InvalidIndirectStorageSource => new Result(ModuleFs, 4023);
public static Result InvalidSaveDataHeader => new Result(ModuleFs, 4315);
public static Result InvalidHashInIvfc => new Result(ModuleFs, 4604);
public static Result IvfcHashIsEmpty => new Result(ModuleFs, 4612);
public static Result InvalidHashInIvfcTopLayer => new Result(ModuleFs, 4613);
public static Result InvalidPartitionFileSystemMagic => new Result(ModuleFs, 4644);
public static Result InvalidHashedPartitionFileSystemMagic => new Result(ModuleFs, 4645);
public static Result AesXtsFileHeaderTooShort => new Result(ModuleFs, 4742);
public static Result AesXtsFileHeaderInvalidKeys => new Result(ModuleFs, 4743);
public static Result AesXtsFileHeaderInvalidMagic => new Result(ModuleFs, 4744);
public static Result AesXtsFileTooShort => new Result(ModuleFs, 4745);
public static Result AesXtsFileHeaderTooShortInSetSize => new Result(ModuleFs, 4746);
public static Result AesXtsFileHeaderInvalidKeysInRenameFile => new Result(ModuleFs, 4747);
public static Result AesXtsFileHeaderInvalidKeysInSetSize => new Result(ModuleFs, 4748);
public static Result InvalidInput => new Result(ModuleFs, 6001);
public static Result DifferentDestFileSystem => new Result(ModuleFs, 6034);
public static Result InvalidOffset => new Result(ModuleFs, 6061);
public static Result InvalidSize => new Result(ModuleFs, 6062);
public static Result NullArgument => new Result(ModuleFs, 6063);
public static Result InvalidMountName => new Result(ModuleFs, 6065);
public static Result InvalidOpenModeOperation => new Result(ModuleFs, 6200);
public static Result AllowAppendRequiredForImplicitExtension => new Result(ModuleFs, 6201);
public static Result UnsupportedOperation => new Result(ModuleFs, 6300);
public static Result UnsupportedOperationInMemoryStorageSetSize => new Result(ModuleFs, 6316);
public static Result UnsupportedOperationInHierarchicalIvfcStorageSetSize => new Result(ModuleFs, 6304);
public static Result UnsupportedOperationInIndirectStorageWrite => new Result(ModuleFs, 6324);
public static Result UnsupportedOperationInIndirectStorageSetSize => new Result(ModuleFs, 6325);
public static Result UnsupportedOperationInConcatFsQueryEntry => new Result(ModuleFs, 6359);
public static Result UnsupportedOperationModifyRomFsFileSystem => new Result(ModuleFs, 6364);
public static Result UnsupportedOperationRomFsFileSystemGetSpace => new Result(ModuleFs, 6366);
public static Result UnsupportedOperationModifyRomFsFile => new Result(ModuleFs, 6367);
public static Result UnsupportedOperationModifyReadOnlyFileSystem => new Result(ModuleFs, 6369);
public static Result UnsupportedOperationReadOnlyFileSystemGetSpace => new Result(ModuleFs, 6371);
public static Result UnsupportedOperationModifyReadOnlyFile => new Result(ModuleFs, 6372);
public static Result UnsupportedOperationModifyPartitionFileSystem => new Result(ModuleFs, 6374);
public static Result UnsupportedOperationInPartitionFileSetSize => new Result(ModuleFs, 6376);
public static Result WriteStateUnflushed => new Result(ModuleFs, 6454);
public static Result WritableFileOpen => new Result(ModuleFs, 6457);
public static Result AllocationTableInsufficientFreeBlocks => new Result(ModuleFs, 6707);
public static Result MountNameNotFound => new Result(ModuleFs, 6905);
}
}

View file

@ -1,16 +0,0 @@
namespace LibHac.Fs
{
public class ResultsFs
{
public const int ModuleFs = 2;
public static Result ResultFsPathNotFound => new Result(ModuleFs, 1);
public static Result ResultFsMountNameAlreadyExists => new Result(ModuleFs, 60);
public static Result ResultFsDifferentDestFileSystem => new Result(ModuleFs, 6034);
public static Result ResultFsNullArgument => new Result(ModuleFs, 6063);
public static Result ResultFsInvalidMountName => new Result(ModuleFs, 6065);
public static Result ResultFsWriteStateUnflushed => new Result(ModuleFs, 6454);
public static Result ResultFsWritableFileOpen => new Result(ModuleFs, 6457);
public static Result ResultFsMountNameNotFound => new Result(ModuleFs, 6905);
}
}

View file

@ -28,7 +28,7 @@ namespace LibHac.Fs.RomFs
public override void Write(ReadOnlySpan<byte> source, long offset, WriteOption options) public override void Write(ReadOnlySpan<byte> source, long offset, WriteOption options)
{ {
throw new NotImplementedException(); ThrowHelper.ThrowResult(ResultFs.UnsupportedOperationModifyRomFsFile);
} }
public override void Flush() public override void Flush()
@ -42,7 +42,7 @@ namespace LibHac.Fs.RomFs
public override void SetSize(long size) public override void SetSize(long size)
{ {
throw new NotSupportedException(); ThrowHelper.ThrowResult(ResultFs.UnsupportedOperationModifyRomFsFile);
} }
} }
} }

View file

@ -1,5 +1,4 @@
using System; using System;
using System.IO;
namespace LibHac.Fs.RomFs namespace LibHac.Fs.RomFs
{ {
@ -30,7 +29,8 @@ namespace LibHac.Fs.RomFs
if (FileExists(path)) return DirectoryEntryType.File; if (FileExists(path)) return DirectoryEntryType.File;
if (DirectoryExists(path)) return DirectoryEntryType.Directory; if (DirectoryExists(path)) return DirectoryEntryType.Directory;
throw new FileNotFoundException(path); ThrowHelper.ThrowResult(ResultFs.PathNotFound);
return DirectoryEntryType.NotFound;
} }
public void Commit() { } public void Commit() { }
@ -41,7 +41,7 @@ namespace LibHac.Fs.RomFs
if (!FileTable.TryOpenDirectory(path, out FindPosition position)) if (!FileTable.TryOpenDirectory(path, out FindPosition position))
{ {
throw new DirectoryNotFoundException(); ThrowHelper.ThrowResult(ResultFs.PathNotFound);
} }
return new RomFsDirectory(this, path, position, mode); return new RomFsDirectory(this, path, position, mode);
@ -53,12 +53,12 @@ namespace LibHac.Fs.RomFs
if (!FileTable.TryOpenFile(path, out RomFileInfo info)) if (!FileTable.TryOpenFile(path, out RomFileInfo info))
{ {
throw new FileNotFoundException(); ThrowHelper.ThrowResult(ResultFs.PathNotFound);
} }
if (mode != OpenMode.Read) if (mode != OpenMode.Read)
{ {
throw new ArgumentOutOfRangeException(nameof(mode), "RomFs files must be opened read-only."); ThrowHelper.ThrowResult(ResultFs.InvalidInput, "RomFs files must be opened read-only.");
} }
return new RomFsFile(BaseStorage, Header.DataOffset + info.Offset, info.Length); return new RomFsFile(BaseStorage, Header.DataOffset + info.Offset, info.Length);
@ -83,18 +83,50 @@ namespace LibHac.Fs.RomFs
return BaseStorage; return BaseStorage;
} }
public void CreateDirectory(string path) => throw new NotSupportedException(); public void CreateDirectory(string path) =>
public void CreateFile(string path, long size, CreateFileOptions options) => throw new NotSupportedException(); ThrowHelper.ThrowResult(ResultFs.UnsupportedOperationModifyRomFsFileSystem);
public void DeleteDirectory(string path) => throw new NotSupportedException();
public void DeleteDirectoryRecursively(string path) => throw new NotSupportedException(); public void CreateFile(string path, long size, CreateFileOptions options) =>
public void CleanDirectoryRecursively(string path) => throw new NotSupportedException(); ThrowHelper.ThrowResult(ResultFs.UnsupportedOperationModifyRomFsFileSystem);
public void DeleteFile(string path) => throw new NotSupportedException();
public void RenameDirectory(string srcPath, string dstPath) => throw new NotSupportedException(); public void DeleteDirectory(string path) =>
public void RenameFile(string srcPath, string dstPath) => throw new NotSupportedException(); ThrowHelper.ThrowResult(ResultFs.UnsupportedOperationModifyRomFsFileSystem);
public long GetFreeSpaceSize(string path) => throw new NotSupportedException();
public long GetTotalSpaceSize(string path) => throw new NotSupportedException(); public void DeleteDirectoryRecursively(string path) =>
public FileTimeStampRaw GetFileTimeStampRaw(string path) => throw new NotSupportedException(); ThrowHelper.ThrowResult(ResultFs.UnsupportedOperationModifyRomFsFileSystem);
public void QueryEntry(Span<byte> outBuffer, ReadOnlySpan<byte> inBuffer, string path, QueryId queryId) => throw new NotSupportedException();
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)
{
ThrowHelper.ThrowResult(ResultFs.UnsupportedOperationRomFsFileSystemGetSpace);
return default;
}
public long GetTotalSpaceSize(string path)
{
ThrowHelper.ThrowResult(ResultFs.UnsupportedOperationRomFsFileSystemGetSpace);
return default;
}
public FileTimeStampRaw GetFileTimeStampRaw(string path)
{
ThrowHelper.ThrowResult(ResultFs.NotImplemented);
return default;
}
public void QueryEntry(Span<byte> outBuffer, ReadOnlySpan<byte> inBuffer, string path, QueryId queryId) =>
ThrowHelper.ThrowResult(ResultFs.NotImplemented);
} }
public class RomfsHeader public class RomfsHeader

View file

@ -46,7 +46,7 @@ namespace LibHac.Fs.Save
} }
else else
{ {
throw new InvalidDataException("Savedata header is not valid."); ThrowHelper.ThrowResult(ResultFs.InvalidSaveDataHeader, "Savedata header is not valid.");
} }
Header = IsFirstHeaderInUse ? headerA : headerB; Header = IsFirstHeaderInUse ? headerA : headerB;
@ -215,8 +215,14 @@ namespace LibHac.Fs.Save
Commit(Keyset); Commit(Keyset);
} }
public FileTimeStampRaw GetFileTimeStampRaw(string path) => throw new NotSupportedException(); public FileTimeStampRaw GetFileTimeStampRaw(string path)
public void QueryEntry(Span<byte> outBuffer, ReadOnlySpan<byte> inBuffer, string path, QueryId queryId) => throw new NotSupportedException(); {
ThrowHelper.ThrowResult(ResultFs.NotImplemented);
return default;
}
public void QueryEntry(Span<byte> outBuffer, ReadOnlySpan<byte> inBuffer, string path, QueryId queryId) =>
ThrowHelper.ThrowResult(ResultFs.NotImplemented);
public bool Commit(Keyset keyset) public bool Commit(Keyset keyset)
{ {

View file

@ -51,7 +51,8 @@ namespace LibHac.Fs.Save
if (startBlock == -1) if (startBlock == -1)
{ {
throw new IOException("Not enough available space to create file."); ThrowHelper.ThrowResult(ResultFs.AllocationTableInsufficientFreeBlocks,
"Not enough available space to create file.");
} }
var fileEntry = new SaveFileInfo { StartBlock = startBlock, Length = size }; var fileEntry = new SaveFileInfo { StartBlock = startBlock, Length = size };
@ -88,7 +89,7 @@ namespace LibHac.Fs.Save
if (!FileTable.TryOpenFile(path, out SaveFileInfo fileInfo)) if (!FileTable.TryOpenFile(path, out SaveFileInfo fileInfo))
{ {
throw new FileNotFoundException(); ThrowHelper.ThrowResult(ResultFs.PathNotFound);
} }
if (fileInfo.StartBlock != int.MinValue) if (fileInfo.StartBlock != int.MinValue)
@ -105,7 +106,7 @@ namespace LibHac.Fs.Save
if (!FileTable.TryOpenDirectory(path, out SaveFindPosition position)) if (!FileTable.TryOpenDirectory(path, out SaveFindPosition position))
{ {
throw new DirectoryNotFoundException(); ThrowHelper.ThrowResult(ResultFs.PathNotFound);
} }
return new SaveDataDirectory(this, path, position, mode); return new SaveDataDirectory(this, path, position, mode);
@ -117,7 +118,7 @@ namespace LibHac.Fs.Save
if (!FileTable.TryOpenFile(path, out SaveFileInfo file)) if (!FileTable.TryOpenFile(path, out SaveFileInfo file))
{ {
throw new FileNotFoundException(); ThrowHelper.ThrowResult(ResultFs.PathNotFound);
} }
AllocationTableStorage storage = OpenFatStorage(file.StartBlock); AllocationTableStorage storage = OpenFatStorage(file.StartBlock);
@ -162,7 +163,8 @@ namespace LibHac.Fs.Save
if (FileExists(path)) return DirectoryEntryType.File; if (FileExists(path)) return DirectoryEntryType.File;
if (DirectoryExists(path)) return DirectoryEntryType.Directory; if (DirectoryExists(path)) return DirectoryEntryType.Directory;
throw new FileNotFoundException(path); ThrowHelper.ThrowResult(ResultFs.PathNotFound);
return DirectoryEntryType.NotFound;
} }
public long GetFreeSpaceSize(string path) public long GetFreeSpaceSize(string path)
@ -181,8 +183,14 @@ namespace LibHac.Fs.Save
} }
public FileTimeStampRaw GetFileTimeStampRaw(string path) => throw new NotSupportedException(); public FileTimeStampRaw GetFileTimeStampRaw(string path)
public void QueryEntry(Span<byte> outBuffer, ReadOnlySpan<byte> inBuffer, string path, QueryId queryId) => throw new NotSupportedException(); {
ThrowHelper.ThrowResult(ResultFs.NotImplemented);
return default;
}
public void QueryEntry(Span<byte> outBuffer, ReadOnlySpan<byte> inBuffer, string path, QueryId queryId) =>
ThrowHelper.ThrowResult(ResultFs.NotImplemented);
public IStorage GetBaseStorage() => BaseStorage.AsReadOnly(); public IStorage GetBaseStorage() => BaseStorage.AsReadOnly();
public IStorage GetHeaderStorage() => HeaderStorage.AsReadOnly(); public IStorage GetHeaderStorage() => HeaderStorage.AsReadOnly();

View file

@ -28,7 +28,7 @@ namespace LibHac.Fs
public virtual void SetSize(long size) public virtual void SetSize(long size)
{ {
throw new NotSupportedException(); ThrowHelper.ThrowResult(ResultFs.NotImplemented);
} }
protected virtual void Dispose(bool disposing) protected virtual void Dispose(bool disposing)

View file

@ -53,6 +53,7 @@ namespace LibHac.Fs
return storage.AsReadOnly(true); return storage.AsReadOnly(true);
} }
// Todo: Move out of SubStorage
public static IStorage AsReadOnly(this IStorage storage, bool leaveOpen) public static IStorage AsReadOnly(this IStorage storage, bool leaveOpen)
{ {
return new SubStorage(storage, 0, storage.GetSize(), leaveOpen, FileAccess.Read); return new SubStorage(storage, 0, storage.GetSize(), leaveOpen, FileAccess.Read);

View file

@ -3,7 +3,6 @@ using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Linq.Expressions;
using LibHac.Fs; using LibHac.Fs;
using LibHac.Fs.NcaUtils; using LibHac.Fs.NcaUtils;
using LibHac.Fs.Save; using LibHac.Fs.Save;