Change IDirectory to match the interface in FS

This commit is contained in:
Alex Barney 2019-09-14 18:35:25 -05:00
parent 104312bf06
commit 734d86d336
42 changed files with 738 additions and 395 deletions

View file

@ -0,0 +1,52 @@
using System;
using System.Diagnostics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace LibHac.Common
{
public ref struct BlitStruct<T> where T : unmanaged
{
private readonly Span<T> _buffer;
public int Length => _buffer.Length;
public ref T Value => ref _buffer[0];
public ref T this[int index] => ref _buffer[index];
public BlitStruct(Span<T> data)
{
_buffer = data;
Debug.Assert(_buffer.Length != 0);
}
public BlitStruct(Span<byte> data)
{
_buffer = MemoryMarshal.Cast<byte, T>(data);
Debug.Assert(_buffer.Length != 0);
}
public BlitStruct(ref T data)
{
_buffer = SpanHelpers.AsSpan(ref data);
}
public Span<byte> GetByteSpan()
{
return MemoryMarshal.Cast<T, byte>(_buffer);
}
public Span<byte> GetByteSpan(int elementIndex)
{
Span<T> element = _buffer.Slice(elementIndex, 1);
return MemoryMarshal.Cast<T, byte>(element);
}
public static int QueryByteLength(int elementCount)
{
return Unsafe.SizeOf<T>() * elementCount;
}
}
}

View file

@ -0,0 +1,35 @@
using System;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace LibHac.Common
{
public static class SpanHelpers
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
#if NETCOREAPP
public static Span<T> CreateSpan<T>(ref T reference, int length)
{
return MemoryMarshal.CreateSpan(ref reference, length);
}
#else
public static unsafe Span<T> CreateSpan<T>(ref T reference, int length)
{
return new Span<T>(Unsafe.AsPointer(ref reference), length);
}
#endif
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Span<T> AsSpan<T>(ref T reference) where T : unmanaged
{
return CreateSpan(ref reference, 1);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Span<byte> AsByteSpan<T>(ref T reference) where T : unmanaged
{
Span<T> span = AsSpan(ref reference);
return MemoryMarshal.Cast<T, byte>(span);
}
}
}

View file

@ -0,0 +1,85 @@
using System;
using System.Text;
namespace LibHac.Common
{
public static class StringUtils
{
public static int Copy(Span<byte> dest, ReadOnlySpan<byte> source)
{
int maxLen = Math.Min(dest.Length, source.Length);
int i;
for (i = 0; i < maxLen && source[i] != 0; i++)
dest[i] = source[i];
if (i < dest.Length)
{
dest[i] = 0;
}
return i;
}
public static int GetLength(ReadOnlySpan<byte> s)
{
int i = 0;
while (i < s.Length && s[i] != 0)
{
i++;
}
return i;
}
/// <summary>
/// Concatenates 2 byte strings.
/// </summary>
/// <param name="dest"></param>
/// <param name="source"></param>
/// <returns>The length of the resulting string.</returns>
/// <remarks>This function appends the source string to the end of the null-terminated destination string.
/// If the destination buffer is not large enough to contain the resulting string,
/// bytes from the source string will be appended to the destination string util the buffer is full.
/// If the length of the final string is the same length of the destination buffer,
/// no null terminating byte will be written to the end of the string.</remarks>
public static int Concat(Span<byte> dest, ReadOnlySpan<byte> source)
{
return Concat(dest, GetLength(dest), source);
}
public static int Concat(Span<byte> dest, int destLength, ReadOnlySpan<byte> source)
{
int iDest = destLength;
for (int i = 0; iDest < dest.Length && i < source.Length && source[i] != 0; i++, iDest++)
{
dest[iDest] = source[i];
}
if (iDest < dest.Length)
{
dest[iDest] = 0;
}
return iDest;
}
public static string FromUtf8Z(this Span<byte> value) => FromUtf8Z((ReadOnlySpan<byte>)value);
public static string FromUtf8Z(this ReadOnlySpan<byte> value)
{
int i;
for (i = 0; i < value.Length && value[i] != 0; i++) { }
value = value.Slice(0, i);
#if STRING_SPAN
return Encoding.UTF8.GetString(value);
#else
return Encoding.UTF8.GetString(value.ToArray());
#endif
}
}
}

View file

@ -1,85 +1,90 @@
using System;
using System.Collections.Generic;
using LibHac.Common;
namespace LibHac.Fs
{
public class AesXtsDirectory : IDirectory
{
IFileSystem IDirectory.ParentFileSystem => ParentFileSystem;
public AesXtsFileSystem ParentFileSystem { get; }
public string FullPath { get; }
public OpenDirectoryMode Mode { get; }
private string Path { get; }
private OpenDirectoryMode Mode { get; }
private IFileSystem BaseFileSystem { get; }
private IDirectory BaseDirectory { get; }
public AesXtsDirectory(AesXtsFileSystem parentFs, IDirectory baseDir, OpenDirectoryMode mode)
public AesXtsDirectory(IFileSystem baseFs, IDirectory baseDir, string path, OpenDirectoryMode mode)
{
ParentFileSystem = parentFs;
BaseFileSystem = baseFs;
BaseDirectory = baseDir;
Mode = mode;
BaseFileSystem = BaseDirectory.ParentFileSystem;
FullPath = BaseDirectory.FullPath;
Path = path;
}
public IEnumerable<DirectoryEntry> Read()
public Result Read(out long entriesRead, Span<DirectoryEntry> entryBuffer)
{
foreach (DirectoryEntry entry in BaseDirectory.Read())
{
if (entry.Type == DirectoryEntryType.Directory)
{
yield return entry;
}
else
{
// todo: FS returns invalid file entries with a size of 0
long size = GetAesXtsFileSize(entry.FullPath);
if (size == -1) continue;
Result rc = BaseDirectory.Read(out entriesRead, entryBuffer);
if (rc.IsFailure()) return rc;
yield return new DirectoryEntry(entry.Name, entry.FullPath, entry.Type, size);
for (int i = 0; i < entriesRead; i++)
{
ref DirectoryEntry entry = ref entryBuffer[i];
if (entry.Type == DirectoryEntryType.File)
{
if (Mode.HasFlag(OpenDirectoryMode.NoFileSize))
{
entry.Size = 0;
}
else
{
string entryName = Util.GetUtf8StringNullTerminated(entry.Name);
entry.Size = GetAesXtsFileSize(PathTools.Combine(Path, entryName));
}
}
}
return Result.Success;
}
public int GetEntryCount()
public Result GetEntryCount(out long entryCount)
{
return BaseDirectory.GetEntryCount();
return BaseDirectory.GetEntryCount(out entryCount);
}
/// <summary>
/// Reads the size of a NAX0 file from its header. Returns -1 on error.
/// Reads the size of a NAX0 file from its header. Returns 0 on error.
/// </summary>
/// <param name="path"></param>
/// <returns></returns>
private long GetAesXtsFileSize(string path)
{
const long magicOffset = 0x20;
const long fileSizeOffset = 0x48;
// Todo: Remove try/catch when more code uses Result
try
{
BaseFileSystem.OpenFile(out IFile file, path, OpenMode.Read).ThrowIfFailure();
Result rc = BaseFileSystem.OpenFile(out IFile file, path, OpenMode.Read);
if (rc.IsFailure()) return 0;
using (file)
{
file.GetSize(out long fileSize).ThrowIfFailure();
uint magic = 0;
long fileSize = 0;
long bytesRead;
if (fileSize < 0x50)
{
return -1;
}
file.Read(out bytesRead, magicOffset, SpanHelpers.AsByteSpan(ref magic), ReadOption.None);
if (bytesRead != sizeof(uint) || magic != AesXtsFileHeader.AesXtsFileMagic) return 0;
// todo: Use result codes
var buffer = new byte[8];
file.Read(out bytesRead, fileSizeOffset, SpanHelpers.AsByteSpan(ref fileSize), ReadOption.None);
if (bytesRead != sizeof(long) || magic != AesXtsFileHeader.AesXtsFileMagic) return 0;
file.Read(out long _, 0x20, buffer);
if (BitConverter.ToUInt32(buffer, 0) != 0x3058414E) return -1;
file.Read(out long _, 0x48, buffer);
return BitConverter.ToInt64(buffer, 0);
return fileSize;
}
}
catch (ArgumentOutOfRangeException)
catch (Exception)
{
return -1;
return 0;
}
}
}

View file

@ -7,7 +7,7 @@ namespace LibHac.Fs
{
public class AesXtsFileHeader
{
private const uint AesXtsFileMagic = 0x3058414E;
internal const uint AesXtsFileMagic = 0x3058414E;
public byte[] Signature { get; set; } = new byte[0x20];
public uint Magic { get; }
public byte[] EncryptedKey1 { get; } = new byte[0x10];

View file

@ -92,7 +92,7 @@ namespace LibHac.Fs
Result rc = BaseFileSystem.OpenDirectory(out IDirectory baseDir, path, mode);
if (rc.IsFailure()) return rc;
directory = new AesXtsDirectory(this, baseDir, mode);
directory = new AesXtsDirectory(BaseFileSystem, baseDir, path, mode);
return Result.Success;
}
@ -147,9 +147,7 @@ namespace LibHac.Fs
private void RenameDirectoryImpl(string srcDir, string dstDir, bool doRollback)
{
OpenDirectory(out IDirectory dir, dstDir, OpenDirectoryMode.All);
foreach (DirectoryEntry entry in dir.Read())
foreach (DirectoryEntryEx entry in this.EnumerateEntries(srcDir, "*"))
{
string subSrcPath = $"{srcDir}/{entry.Name}";
string subDstPath = $"{dstDir}/{entry.Name}";

View file

@ -1,4 +1,5 @@
using System.Collections.Generic;
using System;
using LibHac.Common;
#if CROSS_PLATFORM
using System.Runtime.InteropServices;
@ -8,58 +9,92 @@ namespace LibHac.Fs
{
public class ConcatenationDirectory : IDirectory
{
IFileSystem IDirectory.ParentFileSystem => ParentFileSystem;
public string FullPath { get; }
public OpenDirectoryMode Mode { get; }
private string Path { get; }
private OpenDirectoryMode Mode { get; }
private ConcatenationFileSystem ParentFileSystem { get; }
private IFileSystem BaseFileSystem { get; }
private IDirectory ParentDirectory { get; }
public ConcatenationDirectory(ConcatenationFileSystem fs, IDirectory parentDirectory, OpenDirectoryMode mode)
public ConcatenationDirectory(ConcatenationFileSystem fs, IFileSystem baseFs, IDirectory parentDirectory, OpenDirectoryMode mode, string path)
{
ParentFileSystem = fs;
BaseFileSystem = baseFs;
ParentDirectory = parentDirectory;
Mode = mode;
FullPath = parentDirectory.FullPath;
Path = path;
}
public IEnumerable<DirectoryEntry> Read()
public Result Read(out long entriesRead, Span<DirectoryEntry> entryBuffer)
{
foreach (DirectoryEntry entry in ParentDirectory.Read())
entriesRead = 0;
var entry = new DirectoryEntry();
Span<DirectoryEntry> entrySpan = SpanHelpers.AsSpan(ref entry);
int i;
for (i = 0; i < entryBuffer.Length; i++)
{
bool isSplit = IsConcatenationFile(entry);
Result rc = ParentDirectory.Read(out long baseEntriesRead, entrySpan);
if (rc.IsFailure()) return rc;
if (!CanReturnEntry(entry, isSplit)) continue;
if (baseEntriesRead == 0) break;
if (isSplit)
// Check if the current open mode says we should return the entry
bool isConcatFile = IsConcatenationFile(entry);
if (!CanReturnEntry(entry, isConcatFile)) continue;
if (isConcatFile)
{
entry.Type = DirectoryEntryType.File;
entry.Size = ParentFileSystem.GetConcatenationFileSize(entry.FullPath);
entry.Attributes = NxFileAttributes.None;
if (!Mode.HasFlag(OpenDirectoryMode.NoFileSize))
{
string entryName = Util.GetUtf8StringNullTerminated(entry.Name);
string entryFullPath = PathTools.Combine(Path, entryName);
entry.Size = ParentFileSystem.GetConcatenationFileSize(entryFullPath);
}
}
yield return entry;
entry.Attributes = NxFileAttributes.None;
entryBuffer[i] = entry;
}
entriesRead = i;
return Result.Success;
}
public int GetEntryCount()
public Result GetEntryCount(out long entryCount)
{
int count = 0;
entryCount = 0;
long count = 0;
foreach (DirectoryEntry entry in ParentDirectory.Read())
Result rc = BaseFileSystem.OpenDirectory(out IDirectory _, Path,
OpenDirectoryMode.All | OpenDirectoryMode.NoFileSize);
if (rc.IsFailure()) return rc;
var entry = new DirectoryEntry();
Span<DirectoryEntry> entrySpan = SpanHelpers.AsSpan(ref entry);
while (true)
{
bool isSplit = IsConcatenationFile(entry);
rc = ParentDirectory.Read(out long baseEntriesRead, entrySpan);
if (rc.IsFailure()) return rc;
if (CanReturnEntry(entry, isSplit)) count++;
if (baseEntriesRead == 0) break;
if (CanReturnEntry(entry, IsConcatenationFile(entry))) count++;
}
return count;
entryCount = count;
return Result.Success;
}
private bool CanReturnEntry(DirectoryEntry entry, bool isSplit)
private bool CanReturnEntry(DirectoryEntry entry, bool isConcatFile)
{
return Mode.HasFlag(OpenDirectoryMode.File) && (entry.Type == DirectoryEntryType.File || isSplit) ||
Mode.HasFlag(OpenDirectoryMode.Directory) && entry.Type == DirectoryEntryType.Directory && !isSplit;
return Mode.HasFlag(OpenDirectoryMode.File) && (entry.Type == DirectoryEntryType.File || isConcatFile) ||
Mode.HasFlag(OpenDirectoryMode.Directory) && entry.Type == DirectoryEntryType.Directory && !isConcatFile;
}
private bool IsConcatenationFile(DirectoryEntry entry)
@ -71,7 +106,10 @@ namespace LibHac.Fs
}
else
{
return ParentFileSystem.IsConcatenationFile(entry.FullPath);
string name = Util.GetUtf8StringNullTerminated(entry.Name);
string fullPath = PathTools.Combine(Path, name);
return ParentFileSystem.IsConcatenationFile(fullPath);
}
#else
return ConcatenationFileSystem.HasConcatenationFileAttribute(entry.Attributes);

View file

@ -80,7 +80,8 @@ namespace LibHac.Fs
Result rc = BaseFileSystem.OpenDirectory(out IDirectory dir, path, OpenDirectoryMode.Directory);
if (rc.IsFailure()) return false;
if (dir.GetEntryCount() > 0) return false;
rc = dir.GetEntryCount(out long subDirCount);
if (rc.IsFailure() || subDirCount > 0) return false;
// Should be enough checks to avoid most false positives. Maybe
return true;
@ -222,7 +223,7 @@ namespace LibHac.Fs
Result rc = BaseFileSystem.OpenDirectory(out IDirectory parentDir, path, OpenDirectoryMode.All);
if (rc.IsFailure()) return rc;
directory = new ConcatenationDirectory(this, parentDir, mode);
directory = new ConcatenationDirectory(this, BaseFileSystem, parentDir, mode, path);
return Result.Success;
}

View file

@ -1,8 +1,10 @@
using System;
using System.Runtime.InteropServices;
using LibHac.Common;
namespace LibHac.Fs
{
public class DirectoryEntry
public class DirectoryEntryEx
{
public string Name { get; set; }
public string FullPath { get; set; }
@ -10,7 +12,7 @@ namespace LibHac.Fs
public DirectoryEntryType Type { get; set; }
public long Size { get; set; }
public DirectoryEntry(string name, string fullPath, DirectoryEntryType type, long size)
public DirectoryEntryEx(string name, string fullPath, DirectoryEntryType type, long size)
{
Name = name;
FullPath = PathTools.Normalize(fullPath);
@ -19,7 +21,18 @@ namespace LibHac.Fs
}
}
public enum DirectoryEntryType
[StructLayout(LayoutKind.Explicit)]
public struct DirectoryEntry
{
[FieldOffset(0)] private byte _name;
[FieldOffset(0x301)] public NxFileAttributes Attributes;
[FieldOffset(0x304)] public DirectoryEntryType Type;
[FieldOffset(0x308)] public long Size;
public Span<byte> Name => SpanHelpers.CreateSpan(ref _name, PathTools.MaxPathLength + 1);
}
public enum DirectoryEntryType : byte
{
Directory,
File,
@ -27,7 +40,7 @@ namespace LibHac.Fs
}
[Flags]
public enum NxFileAttributes
public enum NxFileAttributes : byte
{
None = 0,
Directory = 1 << 0,

View file

@ -212,13 +212,7 @@ namespace LibHac.Fs
rc = BaseFs.CreateDirectory(dest);
if (rc.IsFailure()) return rc;
rc = BaseFs.OpenDirectory(out IDirectory sourceDir, src, OpenDirectoryMode.All);
if (rc.IsFailure()) return rc;
rc = BaseFs.OpenDirectory(out IDirectory destDir, dest, OpenDirectoryMode.All);
if (rc.IsFailure()) return rc;
return sourceDir.CopyDirectory(destDir);
return this.CopyDirectory(this, src, dest);
}
internal void NotifyCloseWritableFile()

View file

@ -0,0 +1,85 @@
using System;
using LibHac.Common;
namespace LibHac.Fs
{
public static class DirectoryUtils
{
public delegate Result Blah(ReadOnlySpan<byte> path, ref DirectoryEntry entry);
public static Result IterateDirectoryRecursivelyInternal(IFileSystem fs, Span<byte> workPath,
ref DirectoryEntry entry, Blah onEnterDir, Blah onExitDir, Blah onFile)
{
string currentPath = Util.GetUtf8StringNullTerminated(workPath);
Result rc = fs.OpenDirectory(out IDirectory _, currentPath, OpenDirectoryMode.All);
if (rc.IsFailure()) return rc;
onFile(workPath, ref entry);
return Result.Success;
}
public static Result IterateDirectoryRecursively(IFileSystem fs, ReadOnlySpan<byte> path, Blah onEnterDir, Blah onExitDir, Blah onFile)
{
return Result.Success;
}
public static Result CopyDirectoryRecursively(IFileSystem sourceFs, IFileSystem destFs, string sourcePath,
string destPath)
{
return Result.Success;
}
public static Result CopyFile(IFileSystem destFs, IFileSystem sourceFs, ReadOnlySpan<byte> destParentPath,
ReadOnlySpan<byte> sourcePath, ref DirectoryEntry dirEntry, Span<byte> copyBuffer)
{
IFile srcFile = null;
IFile dstFile = null;
try
{
Result rc = sourceFs.OpenFile(out srcFile, sourcePath.FromUtf8Z(), OpenMode.Read);
if (rc.IsFailure()) return rc;
FsPath dstPath = default;
int dstPathLen = StringUtils.Concat(dstPath.Str, destParentPath);
dstPathLen = StringUtils.Concat(dstPath.Str, dstPathLen, dirEntry.Name);
if (dstPathLen > FsPath.MaxLength)
{
throw new ArgumentException();
}
string dstPathStr = dstPath.Str.FromUtf8Z();
rc = destFs.CreateFile(dstPathStr, dirEntry.Size, CreateFileOptions.None);
if (rc.IsFailure()) return rc;
rc = destFs.OpenFile(out dstFile, dstPathStr, OpenMode.Write);
if (rc.IsFailure()) return rc;
long fileSize = dirEntry.Size;
long offset = 0;
while (offset < fileSize)
{
rc = srcFile.Read(out long bytesRead, offset, copyBuffer, ReadOption.None);
if (rc.IsFailure()) return rc;
rc = dstFile.Write(offset, copyBuffer.Slice(0, (int)bytesRead), WriteOption.None);
if (rc.IsFailure()) return rc;
offset += bytesRead;
}
return Result.Success;
}
finally
{
srcFile?.Dispose();
dstFile?.Dispose();
}
}
}
}

View file

@ -2,33 +2,27 @@
using System.Buffers;
using System.Collections.Generic;
using System.IO;
using LibHac.Common;
namespace LibHac.Fs
{
public static class FileSystemExtensions
{
public static Result CopyDirectory(this IDirectory source, IDirectory dest, IProgressReport logger = null, CreateFileOptions options = CreateFileOptions.None)
public static Result CopyDirectory(this IFileSystem sourceFs, IFileSystem destFs, string sourcePath, string destPath,
IProgressReport logger = null, CreateFileOptions options = CreateFileOptions.None)
{
IFileSystem sourceFs = source.ParentFileSystem;
IFileSystem destFs = dest.ParentFileSystem;
Result rc;
foreach (DirectoryEntry entry in source.Read())
foreach (DirectoryEntryEx entry in sourceFs.EnumerateEntries())
{
string subSrcPath = PathTools.Normalize(PathTools.Combine(source.FullPath, entry.Name));
string subDstPath = PathTools.Normalize(PathTools.Combine(dest.FullPath, entry.Name));
string subSrcPath = PathTools.Normalize(PathTools.Combine(sourcePath, entry.Name));
string subDstPath = PathTools.Normalize(PathTools.Combine(destPath, entry.Name));
if (entry.Type == DirectoryEntryType.Directory)
{
destFs.EnsureDirectoryExists(subDstPath);
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);
rc = sourceFs.CopyDirectory(destFs, subSrcPath, subDstPath, logger, options);
if (rc.IsFailure()) return rc;
}
@ -56,52 +50,45 @@ namespace LibHac.Fs
return Result.Success;
}
public static void CopyFileSystem(this IFileSystem source, IFileSystem dest, IProgressReport logger = null, CreateFileOptions options = CreateFileOptions.None)
{
source.OpenDirectory(out IDirectory sourceRoot, "/", OpenDirectoryMode.All).ThrowIfFailure();
dest.OpenDirectory(out IDirectory destRoot, "/", OpenDirectoryMode.All).ThrowIfFailure();
sourceRoot.CopyDirectory(destRoot, logger, options).ThrowIfFailure();
}
public static void Extract(this IFileSystem source, string destinationPath, IProgressReport logger = null)
{
var destFs = new LocalFileSystem(destinationPath);
source.CopyFileSystem(destFs, logger);
source.CopyDirectory(destFs, "/", "/", logger);
}
public static IEnumerable<DirectoryEntry> EnumerateEntries(this IFileSystem fileSystem)
public static IEnumerable<DirectoryEntryEx> EnumerateEntries(this IFileSystem fileSystem)
{
return fileSystem.EnumerateEntries("*");
return fileSystem.EnumerateEntries("/", "*");
}
public static IEnumerable<DirectoryEntry> EnumerateEntries(this IFileSystem fileSystem, string searchPattern)
public static IEnumerable<DirectoryEntryEx> EnumerateEntries(this IFileSystem fileSystem, string path, string searchPattern)
{
return fileSystem.EnumerateEntries(searchPattern, SearchOptions.RecurseSubdirectories);
return fileSystem.EnumerateEntries(path, searchPattern, SearchOptions.RecurseSubdirectories);
}
public static IEnumerable<DirectoryEntry> EnumerateEntries(this IFileSystem fileSystem, string searchPattern, SearchOptions searchOptions)
public static IEnumerable<DirectoryEntryEx> EnumerateEntries(this IFileSystem fileSystem, string searchPattern, SearchOptions searchOptions)
{
fileSystem.OpenDirectory(out IDirectory rootDir, "/", OpenDirectoryMode.All).ThrowIfFailure();
return rootDir.EnumerateEntries(searchPattern, searchOptions);
return EnumerateEntries(fileSystem, "/", searchPattern, searchOptions);
}
public static IEnumerable<DirectoryEntry> EnumerateEntries(this IDirectory directory)
{
return directory.EnumerateEntries("*", SearchOptions.Default);
}
public static IEnumerable<DirectoryEntry> EnumerateEntries(this IDirectory directory, string searchPattern, SearchOptions searchOptions)
public static IEnumerable<DirectoryEntryEx> EnumerateEntries(this IFileSystem fileSystem, string path, string searchPattern, SearchOptions searchOptions)
{
bool ignoreCase = searchOptions.HasFlag(SearchOptions.CaseInsensitive);
bool recurse = searchOptions.HasFlag(SearchOptions.RecurseSubdirectories);
IFileSystem fs = directory.ParentFileSystem;
IFileSystem fs = fileSystem;
DirectoryEntry dirEntry = default;
foreach (DirectoryEntry entry in directory.Read())
fileSystem.OpenDirectory(out IDirectory directory, path, OpenDirectoryMode.All).ThrowIfFailure();
while(true)
{
directory.Read(out long entriesRead, SpanHelpers.AsSpan(ref dirEntry)).ThrowIfFailure();
if (entriesRead == 0) break;
DirectoryEntryEx entry = GetDirectoryEntryEx(ref dirEntry, path);
if (PathTools.MatchesPattern(searchPattern, entry.Name, ignoreCase))
{
yield return entry;
@ -109,15 +96,28 @@ namespace LibHac.Fs
if (entry.Type != DirectoryEntryType.Directory || !recurse) continue;
fs.OpenDirectory(out IDirectory subDir, PathTools.Combine(directory.FullPath, entry.Name), OpenDirectoryMode.All).ThrowIfFailure();
IEnumerable<DirectoryEntryEx> subEntries =
fs.EnumerateEntries(PathTools.Combine(path, entry.Name), searchPattern,
searchOptions);
foreach (DirectoryEntry subEntry in subDir.EnumerateEntries(searchPattern, searchOptions))
foreach (DirectoryEntryEx subEntry in subEntries)
{
yield return subEntry;
}
}
}
private static DirectoryEntryEx GetDirectoryEntryEx(ref DirectoryEntry entry, string parentPath)
{
string name = entry.Name.FromUtf8Z();
string path = PathTools.Combine(parentPath, name);
var entryEx = new DirectoryEntryEx(name, path, entry.Type, entry.Size);
entryEx.Attributes = entry.Attributes;
return entryEx;
}
public static void CopyTo(this IFile file, IFile dest, IProgressReport logger = null)
{
const int bufferSize = 0x8000;
@ -157,16 +157,14 @@ namespace LibHac.Fs
public static int GetEntryCount(this IFileSystem fs, OpenDirectoryMode mode)
{
fs.OpenDirectory(out IDirectory rootDir, "/", OpenDirectoryMode.All).ThrowIfFailure();
return rootDir.GetEntryCountRecursive(mode);
return GetEntryCountRecursive(fs, "/", mode);
}
public static int GetEntryCountRecursive(this IDirectory directory, OpenDirectoryMode mode)
public static int GetEntryCountRecursive(this IFileSystem fs, string path, OpenDirectoryMode mode)
{
int count = 0;
foreach (DirectoryEntry entry in directory.EnumerateEntries())
foreach (DirectoryEntryEx entry in fs.EnumerateEntries(path, "*"))
{
if (entry.Type == DirectoryEntryType.Directory && (mode & OpenDirectoryMode.Directory) != 0 ||
entry.Type == DirectoryEntryType.File && (mode & OpenDirectoryMode.File) != 0)
@ -194,19 +192,17 @@ namespace LibHac.Fs
fs.QueryEntry(Span<byte>.Empty, Span<byte>.Empty, QueryId.MakeConcatFile, path);
}
public static void CleanDirectoryRecursivelyGeneric(IDirectory directory)
public static void CleanDirectoryRecursivelyGeneric(IFileSystem fileSystem, string path)
{
IFileSystem fs = directory.ParentFileSystem;
IFileSystem fs = fileSystem;
foreach (DirectoryEntry entry in directory.Read())
foreach (DirectoryEntryEx entry in fileSystem.EnumerateEntries(path, "*"))
{
string subPath = PathTools.Combine(directory.FullPath, entry.Name);
string subPath = PathTools.Combine(path, entry.Name);
if (entry.Type == DirectoryEntryType.Directory)
{
fs.OpenDirectory(out IDirectory subDir, subPath, OpenDirectoryMode.All).ThrowIfFailure();
CleanDirectoryRecursivelyGeneric(subDir);
CleanDirectoryRecursivelyGeneric(fileSystem, subPath);
fs.DeleteDirectory(subPath);
}
else if (entry.Type == DirectoryEntryType.File)

16
src/LibHac/Fs/FsPath.cs Normal file
View file

@ -0,0 +1,16 @@
using System;
using System.Runtime.InteropServices;
using LibHac.Common;
namespace LibHac.Fs
{
[StructLayout(LayoutKind.Explicit, Size = MaxLength + 1)]
public struct FsPath
{
internal const int MaxLength = 0x300;
[FieldOffset(0)] private byte _str;
public Span<byte> Str => SpanHelpers.CreateSpan(ref _str, MaxLength + 1);
}
}

View file

@ -1,4 +1,4 @@
using System.Collections.Generic;
using System;
namespace LibHac.Fs
{
@ -8,32 +8,24 @@ namespace LibHac.Fs
public interface IDirectory
{
/// <summary>
/// The <see cref="IFileSystem"/> that contains the current <see cref="IDirectory"/>.
/// Retrieves the next entries that this directory contains. Does not search subdirectories.
/// </summary>
IFileSystem ParentFileSystem { get; }
/// <param name="entriesRead">The number of <see cref="DirectoryEntry"/>s that
/// were read into <paramref name="entryBuffer"/>.</param>
/// <param name="entryBuffer">The buffer the entries will be read into.</param>
/// <returns>The <see cref="Result"/> of the requested operation.</returns>
/// <remarks>With each call of <see cref="Read"/>, the <see cref="IDirectory"/> object will
/// continue to iterate through all the entries it contains.
/// Each call will attempt to read as many entries as the buffer can contain.
/// Once all the entries have been read, all subsequent calls to <see cref="Read"/> will
/// read 0 entries into the buffer.</remarks>
Result Read(out long entriesRead, Span<DirectoryEntry> entryBuffer);
/// <summary>
/// The full path of the current <see cref="IDirectory"/> in its <see cref="ParentFileSystem"/>.
/// Retrieves the number of file system entries that this directory contains. Does not search subdirectories.
/// </summary>
string FullPath { get; }
/// <summary>
/// Specifies which types of entries will be enumerated when <see cref="Read"/> is called.
/// </summary>
OpenDirectoryMode Mode { get; }
/// <summary>
/// Returns an enumerable collection the file system entries of the types specified by
/// <see cref="Mode"/> that this directory contains. Does not search subdirectories.
/// </summary>
/// <returns>An enumerable collection of file system entries in this directory.</returns>
IEnumerable<DirectoryEntry> Read();
/// <summary>
/// Returns the number of file system entries of the types specified by
/// <see cref="Mode"/> that this directory contains. Does not search subdirectories.
/// </summary>
/// <returns>The number of child entries the directory contains.</returns>
int GetEntryCount();
/// <param name="entryCount">The number of child entries the directory contains.</param>
/// <returns>The <see cref="Result"/> of the requested operation.</returns>
Result GetEntryCount(out long entryCount);
}
}

View file

@ -221,8 +221,9 @@ namespace LibHac.Fs
[Flags]
public enum OpenDirectoryMode
{
Directory = 1,
File = 2,
Directory = 1 << 0,
File = 1 << 1,
NoFileSize = 1 << 31,
All = Directory | File
}

View file

@ -38,7 +38,7 @@ namespace LibHac.Fs
}
}
directory = new LayeredFileSystemDirectory(this, dirs, path, mode);
directory = new LayeredFileSystemDirectory(dirs);
return Result.Success;
}

View file

@ -1,44 +1,51 @@
using System.Collections.Generic;
using System.Linq;
using System;
using System.Collections.Generic;
namespace LibHac.Fs
{
public class LayeredFileSystemDirectory : IDirectory
{
public IFileSystem ParentFileSystem { get; }
public string FullPath { get; }
public OpenDirectoryMode Mode { get; }
private List<IDirectory> Sources { get; }
public LayeredFileSystemDirectory(IFileSystem fs, List<IDirectory> sources, string path, OpenDirectoryMode mode)
public LayeredFileSystemDirectory(List<IDirectory> sources)
{
ParentFileSystem = fs;
Sources = sources;
FullPath = path;
Mode = mode;
}
public IEnumerable<DirectoryEntry> Read()
// Todo: Don't return duplicate entries
public Result Read(out long entriesRead, Span<DirectoryEntry> entryBuffer)
{
var returnedFiles = new HashSet<string>();
entriesRead = 0;
int entryIndex = 0;
foreach (IDirectory source in Sources)
for (int i = 0; i < Sources.Count && entryIndex < entryBuffer.Length; i++)
{
foreach (DirectoryEntry entry in source.Read())
{
if (returnedFiles.Contains(entry.FullPath)) continue;
Result rs = Sources[i].Read(out long subEntriesRead, entryBuffer.Slice(entryIndex));
if (rs.IsFailure()) return rs;
returnedFiles.Add(entry.FullPath);
yield return entry;
}
entryIndex += (int)subEntriesRead;
}
entriesRead = entryIndex;
return Result.Success;
}
public int GetEntryCount()
// Todo: Don't count duplicate entries
public Result GetEntryCount(out long entryCount)
{
return Read().Count();
entryCount = 0;
long totalEntryCount = 0;
foreach (IDirectory dir in Sources)
{
Result rc = dir.GetEntryCount(out long subEntryCount);
if (rc.IsFailure()) return rc;
totalEntryCount += subEntryCount;
}
entryCount = totalEntryCount;
return Result.Success;
}
}
}

View file

@ -2,22 +2,19 @@
using System.Collections.Generic;
using System.IO;
using System.Runtime.CompilerServices;
using LibHac.Common;
namespace LibHac.Fs
{
public class LocalDirectory : IDirectory
{
public IFileSystem ParentFileSystem { get; }
public string FullPath { get; }
private string LocalPath { get; }
public OpenDirectoryMode Mode { get; }
private OpenDirectoryMode Mode { get; }
private DirectoryInfo DirInfo { get; }
private IEnumerator<FileSystemInfo> EntryEnumerator { get; }
public LocalDirectory(LocalFileSystem fs, string path, OpenDirectoryMode mode)
{
ParentFileSystem = fs;
FullPath = path;
LocalPath = fs.ResolveLocalPath(path);
Mode = mode;
@ -36,27 +33,42 @@ namespace LibHac.Fs
{
ThrowHelper.ThrowResult(ResultFs.PathNotFound);
}
EntryEnumerator = DirInfo.EnumerateFileSystemInfos().GetEnumerator();
}
public IEnumerable<DirectoryEntry> Read()
public Result Read(out long entriesRead, Span<DirectoryEntry> entryBuffer)
{
foreach (FileSystemInfo entry in DirInfo.EnumerateFileSystemInfos())
int i = 0;
while (i < entryBuffer.Length && EntryEnumerator.MoveNext())
{
bool isDir = (entry.Attributes & FileAttributes.Directory) != 0;
FileSystemInfo localEntry = EntryEnumerator.Current;
if (localEntry == null) break;
bool isDir = localEntry.Attributes.HasFlag(FileAttributes.Directory);
if (!CanReturnEntry(isDir, Mode)) continue;
ReadOnlySpan<byte> name = Util.GetUtf8Bytes(localEntry.Name);
DirectoryEntryType type = isDir ? DirectoryEntryType.Directory : DirectoryEntryType.File;
long length = isDir ? 0 : ((FileInfo)entry).Length;
long length = isDir ? 0 : ((FileInfo)localEntry).Length;
yield return new DirectoryEntry(entry.Name, PathTools.Combine(FullPath, entry.Name), type, length)
{
Attributes = entry.Attributes.ToNxAttributes()
};
StringUtils.Copy(entryBuffer[i].Name, name);
entryBuffer[i].Name[PathTools.MaxPathLength] = 0;
entryBuffer[i].Attributes = localEntry.Attributes.ToNxAttributes();
entryBuffer[i].Type = type;
entryBuffer[i].Size = length;
i++;
}
entriesRead = i;
return Result.Success;
}
public int GetEntryCount()
public Result GetEntryCount(out long entryCount)
{
int count = 0;
@ -67,7 +79,8 @@ namespace LibHac.Fs
if (CanReturnEntry(isDir, Mode)) count++;
}
return count;
entryCount = count;
return Result.Success;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]

View file

@ -1,15 +1,15 @@
using System.Collections.Generic;
using System;
using System.IO;
using System.Text;
using LibHac.Common;
namespace LibHac.Fs
{
public class PartitionDirectory : IDirectory
{
IFileSystem IDirectory.ParentFileSystem => ParentFileSystem;
public PartitionFileSystem ParentFileSystem { get; }
public string FullPath { get; }
public OpenDirectoryMode Mode { get; }
private PartitionFileSystem ParentFileSystem { get; }
private OpenDirectoryMode Mode { get; }
private int CurrentIndex { get; set; }
public PartitionDirectory(PartitionFileSystem fs, string path, OpenDirectoryMode mode)
{
@ -18,23 +18,43 @@ namespace LibHac.Fs
if (path != "/") throw new DirectoryNotFoundException();
ParentFileSystem = fs;
FullPath = path;
Mode = mode;
CurrentIndex = 0;
}
public IEnumerable<DirectoryEntry> Read()
public Result Read(out long entriesRead, Span<DirectoryEntry> entryBuffer)
{
if (Mode.HasFlag(OpenDirectoryMode.File))
if (!Mode.HasFlag(OpenDirectoryMode.File))
{
foreach (PartitionFileEntry entry in ParentFileSystem.Files)
{
yield return new DirectoryEntry(entry.Name, '/' + entry.Name, DirectoryEntryType.File, entry.Size);
}
entriesRead = 0;
return Result.Success;
}
int entriesRemaining = ParentFileSystem.Files.Length - CurrentIndex;
int toRead = Math.Min(entriesRemaining, entryBuffer.Length);
for (int i = 0; i < toRead; i++)
{
PartitionFileEntry fileEntry = ParentFileSystem.Files[CurrentIndex];
ref DirectoryEntry entry = ref entryBuffer[i];
Span<byte> nameUtf8 = Encoding.UTF8.GetBytes(fileEntry.Name);
entry.Type = DirectoryEntryType.File;
entry.Size = fileEntry.Size;
StringUtils.Copy(entry.Name, nameUtf8);
entry.Name[PathTools.MaxPathLength] = 0;
CurrentIndex++;
}
entriesRead = toRead;
return Result.Success;
}
public int GetEntryCount()
public Result GetEntryCount(out long entryCount)
{
int count = 0;
@ -43,7 +63,8 @@ namespace LibHac.Fs
count += ParentFileSystem.Files.Length;
}
return count;
entryCount = count;
return Result.Success;
}
}
}

View file

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

View file

@ -13,6 +13,8 @@ namespace LibHac.Fs
public static readonly char DirectorySeparator = '/';
public static readonly char MountSeparator = ':';
internal const int MountNameLength = 0xF;
// Todo: Remove
internal const int MaxPathLength = 0x300;
public static string Normalize(string inPath)

View file

@ -1,22 +0,0 @@
using System.Collections.Generic;
namespace LibHac.Fs
{
public class ReadOnlyDirectory : IDirectory
{
private IDirectory BaseDir { get; }
public IFileSystem ParentFileSystem { get; }
public string FullPath => BaseDir.FullPath;
public OpenDirectoryMode Mode => BaseDir.Mode;
public ReadOnlyDirectory(IFileSystem parentFileSystem, IDirectory baseDirectory)
{
ParentFileSystem = parentFileSystem;
BaseDir = baseDirectory;
}
public IEnumerable<DirectoryEntry> Read() => BaseDir.Read();
public int GetEntryCount() => BaseDir.GetEntryCount();
}
}

View file

@ -13,13 +13,7 @@ namespace LibHac.Fs
public Result OpenDirectory(out IDirectory directory, string path, OpenDirectoryMode mode)
{
directory = default;
Result rc = BaseFs.OpenDirectory(out IDirectory baseDir, path, mode);
if (rc.IsFailure()) return rc;
directory = new ReadOnlyDirectory(this, baseDir);
return Result.Success;
return BaseFs.OpenDirectory(out directory, path, mode);
}
public Result OpenFile(out IFile file, string path, OpenMode mode)

View file

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

View file

@ -1,71 +1,87 @@
using System.Collections.Generic;
using System;
using System.Text;
using LibHac.Common;
namespace LibHac.Fs.RomFs
{
public class RomFsDirectory : IDirectory
{
IFileSystem IDirectory.ParentFileSystem => ParentFileSystem;
public RomFsFileSystem ParentFileSystem { get; }
public string FullPath { get; }
private RomFsFileSystem ParentFileSystem { get; }
public OpenDirectoryMode Mode { get; }
private OpenDirectoryMode Mode { get; }
private FindPosition InitialPosition { get; }
private FindPosition _currentPosition;
public RomFsDirectory(RomFsFileSystem fs, string path, FindPosition position, OpenDirectoryMode mode)
public RomFsDirectory(RomFsFileSystem fs, FindPosition position, OpenDirectoryMode mode)
{
ParentFileSystem = fs;
InitialPosition = position;
FullPath = path;
_currentPosition = position;
Mode = mode;
}
public IEnumerable<DirectoryEntry> Read()
public Result Read(out long entriesRead, Span<DirectoryEntry> entryBuffer)
{
FindPosition position = InitialPosition;
HierarchicalRomFileTable<RomFileInfo> tab = ParentFileSystem.FileTable;
if (Mode.HasFlag(OpenDirectoryMode.Directory))
{
while (tab.FindNextDirectory(ref position, out string name))
{
yield return new DirectoryEntry(name, PathTools.Combine(FullPath, name), DirectoryEntryType.Directory, 0);
}
}
if (Mode.HasFlag(OpenDirectoryMode.File))
{
while (tab.FindNextFile(ref position, out RomFileInfo info, out string name))
{
yield return new DirectoryEntry(name, PathTools.Combine(FullPath, name), DirectoryEntryType.File, info.Length);
}
}
return ReadImpl(out entriesRead, ref _currentPosition, entryBuffer);
}
public int GetEntryCount()
public Result GetEntryCount(out long entryCount)
{
int count = 0;
FindPosition position = InitialPosition;
return ReadImpl(out entryCount, ref position, Span<DirectoryEntry>.Empty);
}
private Result ReadImpl(out long entriesRead, ref FindPosition position, Span<DirectoryEntry> entryBuffer)
{
HierarchicalRomFileTable<RomFileInfo> tab = ParentFileSystem.FileTable;
int i = 0;
if (Mode.HasFlag(OpenDirectoryMode.Directory))
{
while (tab.FindNextDirectory(ref position, out string _))
while ((entryBuffer.IsEmpty || i < entryBuffer.Length) && tab.FindNextDirectory(ref position, out string name))
{
count++;
if (!entryBuffer.IsEmpty)
{
ref DirectoryEntry entry = ref entryBuffer[i];
Span<byte> nameUtf8 = Encoding.UTF8.GetBytes(name);
StringUtils.Copy(entry.Name, nameUtf8);
entry.Name[PathTools.MaxPathLength] = 0;
entry.Type = DirectoryEntryType.Directory;
entry.Size = 0;
}
i++;
}
}
if (Mode.HasFlag(OpenDirectoryMode.File))
{
while (tab.FindNextFile(ref position, out RomFileInfo _, out string _))
while ((entryBuffer.IsEmpty || i < entryBuffer.Length) && tab.FindNextFile(ref position, out RomFileInfo info, out string name))
{
count++;
if (!entryBuffer.IsEmpty)
{
ref DirectoryEntry entry = ref entryBuffer[i];
Span<byte> nameUtf8 = Encoding.UTF8.GetBytes(name);
StringUtils.Copy(entry.Name, nameUtf8);
entry.Name[PathTools.MaxPathLength] = 0;
entry.Type = DirectoryEntryType.File;
entry.Size = info.Length;
}
i++;
}
}
return count;
entriesRead = i;
return Result.Success;
}
}
}

View file

@ -57,7 +57,7 @@ namespace LibHac.Fs.RomFs
return ResultFs.PathNotFound.Log();
}
directory = new RomFsDirectory(this, path, position, mode);
directory = new RomFsDirectory(this, position, mode);
return Result.Success;
}

View file

@ -1,71 +1,87 @@
using System.Collections.Generic;
using System;
using System.Text;
using LibHac.Common;
namespace LibHac.Fs.Save
{
public class SaveDataDirectory : IDirectory
{
IFileSystem IDirectory.ParentFileSystem => ParentFileSystem;
public SaveDataFileSystemCore ParentFileSystem { get; }
public string FullPath { get; }
private SaveDataFileSystemCore ParentFileSystem { get; }
public OpenDirectoryMode Mode { get; }
private OpenDirectoryMode Mode { get; }
private SaveFindPosition InitialPosition { get; }
private SaveFindPosition _currentPosition;
public SaveDataDirectory(SaveDataFileSystemCore fs, string path, SaveFindPosition position, OpenDirectoryMode mode)
public SaveDataDirectory(SaveDataFileSystemCore fs, SaveFindPosition position, OpenDirectoryMode mode)
{
ParentFileSystem = fs;
InitialPosition = position;
FullPath = path;
_currentPosition = position;
Mode = mode;
}
public IEnumerable<DirectoryEntry> Read()
public Result Read(out long entriesRead, Span<DirectoryEntry> entryBuffer)
{
SaveFindPosition position = InitialPosition;
HierarchicalSaveFileTable tab = ParentFileSystem.FileTable;
if (Mode.HasFlag(OpenDirectoryMode.Directory))
{
while (tab.FindNextDirectory(ref position, out string name))
{
yield return new DirectoryEntry(name, PathTools.Combine(FullPath, name), DirectoryEntryType.Directory, 0);
}
}
if (Mode.HasFlag(OpenDirectoryMode.File))
{
while (tab.FindNextFile(ref position, out SaveFileInfo info, out string name))
{
yield return new DirectoryEntry(name, PathTools.Combine(FullPath, name), DirectoryEntryType.File, info.Length);
}
}
return ReadImpl(out entriesRead, ref _currentPosition, entryBuffer);
}
public int GetEntryCount()
public Result GetEntryCount(out long entryCount)
{
int count = 0;
SaveFindPosition position = InitialPosition;
return ReadImpl(out entryCount, ref position, Span<DirectoryEntry>.Empty);
}
private Result ReadImpl(out long entriesRead, ref SaveFindPosition position, Span<DirectoryEntry> entryBuffer)
{
HierarchicalSaveFileTable tab = ParentFileSystem.FileTable;
int i = 0;
if (Mode.HasFlag(OpenDirectoryMode.Directory))
{
while (tab.FindNextDirectory(ref position, out string _))
while ((entryBuffer.IsEmpty || i < entryBuffer.Length) && tab.FindNextDirectory(ref position, out string name))
{
count++;
if (!entryBuffer.IsEmpty)
{
ref DirectoryEntry entry = ref entryBuffer[i];
Span<byte> nameUtf8 = Encoding.UTF8.GetBytes(name);
StringUtils.Copy(entry.Name, nameUtf8);
entry.Name[64] = 0;
entry.Type = DirectoryEntryType.Directory;
entry.Size = 0;
}
i++;
}
}
if (Mode.HasFlag(OpenDirectoryMode.File))
{
while (tab.FindNextFile(ref position, out SaveFileInfo _, out string _))
while ((entryBuffer.IsEmpty || i < entryBuffer.Length) && tab.FindNextFile(ref position, out SaveFileInfo info, out string name))
{
count++;
if (!entryBuffer.IsEmpty)
{
ref DirectoryEntry entry = ref entryBuffer[i];
Span<byte> nameUtf8 = Encoding.UTF8.GetBytes(name);
StringUtils.Copy(entry.Name, nameUtf8);
entry.Name[64] = 0;
entry.Type = DirectoryEntryType.File;
entry.Size = info.Length;
}
i++;
}
}
return count;
entriesRead = i;
return Result.Success;
}
}
}

View file

@ -88,10 +88,7 @@ namespace LibHac.Fs.Save
{
path = PathTools.Normalize(path);
Result rc = OpenDirectory(out IDirectory dir, path, OpenDirectoryMode.All);
if (rc.IsFailure()) return rc;
FileSystemExtensions.CleanDirectoryRecursivelyGeneric(dir);
FileSystemExtensions.CleanDirectoryRecursivelyGeneric(this, path);
return Result.Success;
}
@ -125,7 +122,7 @@ namespace LibHac.Fs.Save
return ResultFs.PathNotFound.Log();
}
directory = new SaveDataDirectory(this, path, position, mode);
directory = new SaveDataDirectory(this, position, mode);
return Result.Success;
}
@ -223,7 +220,7 @@ namespace LibHac.Fs.Save
{
AllocationTable.FsTrim();
foreach (DirectoryEntry file in this.EnumerateEntries("*", SearchOptions.RecurseSubdirectories))
foreach (DirectoryEntryEx file in this.EnumerateEntries("*", SearchOptions.RecurseSubdirectories))
{
if (FileTable.TryOpenFile(file.FullPath, out SaveFileInfo fileInfo) && fileInfo.StartBlock >= 0)
{

View file

@ -64,10 +64,7 @@ namespace LibHac.Fs
{
path = PathTools.Normalize(path);
ParentFileSystem.OpenDirectory(out IDirectory baseDir, ResolveFullPath(path), mode);
directory = new SubdirectoryFileSystemDirectory(this, baseDir, path, mode);
return Result.Success;
return ParentFileSystem.OpenDirectory(out directory, ResolveFullPath(path), mode);
}
public Result OpenFile(out IFile file, string path, OpenMode mode)

View file

@ -1,34 +0,0 @@
using System.Collections.Generic;
namespace LibHac.Fs
{
public class SubdirectoryFileSystemDirectory : IDirectory
{
public SubdirectoryFileSystemDirectory(SubdirectoryFileSystem fs, IDirectory baseDir, string path, OpenDirectoryMode mode)
{
ParentFileSystem = fs;
BaseDirectory = baseDir;
FullPath = path;
Mode = mode;
}
public IFileSystem ParentFileSystem { get; }
public string FullPath { get; }
public OpenDirectoryMode Mode { get; }
private IDirectory BaseDirectory { get; }
public IEnumerable<DirectoryEntry> Read()
{
foreach (DirectoryEntry entry in BaseDirectory.Read())
{
yield return new DirectoryEntry(entry.Name, PathTools.Combine(FullPath, entry.Name), entry.Type, entry.Size);
}
}
public int GetEntryCount()
{
return BaseDirectory.GetEntryCount();
}
}
}

View file

@ -10,24 +10,34 @@ namespace LibHac.FsClient.Accessors
public FileSystemAccessor Parent { get; }
public DirectoryAccessor(IDirectory baseDirectory, FileSystemAccessor parent)
private IFileSystem ParentFs { get; }
private string Path { get; }
public DirectoryAccessor(IDirectory baseDirectory, FileSystemAccessor parent, IFileSystem parentFs, string path)
{
Directory = baseDirectory;
Parent = parent;
ParentFs = parentFs;
Path = path;
}
public IEnumerable<DirectoryEntry> Read()
public IEnumerable<DirectoryEntryEx> Read()
{
CheckIfDisposed();
return Directory.Read();
return ParentFs.EnumerateEntries(Path, "*", SearchOptions.Default);
}
public int GetEntryCount()
public Result Read(out long entriesRead, Span<DirectoryEntry> entryBuffer)
{
return Directory.Read(out entriesRead, entryBuffer);
}
public Result GetEntryCount(out long entryCount)
{
CheckIfDisposed();
return Directory.GetEntryCount();
return Directory.GetEntryCount(out entryCount);
}
public void Dispose()

View file

@ -63,7 +63,7 @@ namespace LibHac.FsClient.Accessors
Result rc = FileSystem.OpenDirectory(out IDirectory rawDirectory, path, mode);
if (rc.IsFailure()) return rc;
var accessor = new DirectoryAccessor(rawDirectory, this);
var accessor = new DirectoryAccessor(rawDirectory, this, FileSystem, path);
lock (_locker)
{

View file

@ -12,7 +12,7 @@ namespace LibHac.FsClient
}
// todo: change to not use IEnumerable
public IEnumerable<DirectoryEntry> ReadDirectory(DirectoryHandle handle)
public IEnumerable<DirectoryEntryEx> ReadDirectory(DirectoryHandle handle)
{
throw new NotImplementedException();
}

View file

@ -509,17 +509,15 @@ namespace LibHac.FsClient
// ==========================
public Result GetDirectoryEntryCount(out long count, DirectoryHandle handle)
{
count = handle.Directory.GetEntryCount();
return Result.Success;
return handle.Directory.GetEntryCount(out count);
}
public IEnumerable<DirectoryEntry> ReadDirectory(DirectoryHandle handle)
public IEnumerable<DirectoryEntryEx> ReadDirectory(DirectoryHandle handle)
{
if (IsEnabledAccessLog() && IsEnabledHandleAccessLog(handle))
{
TimeSpan startTime = Time.GetCurrent();
IEnumerable<DirectoryEntry> entries = handle.Directory.Read();
IEnumerable<DirectoryEntryEx> entries = handle.Directory.Read();
TimeSpan endTime = Time.GetCurrent();
OutputAccessLog(Result.Success, startTime, endTime, handle, string.Empty);
@ -529,6 +527,26 @@ namespace LibHac.FsClient
return handle.Directory.Read();
}
public Result ReadDirectory2(out long entriesRead, Span<DirectoryEntry> entryBuffer, DirectoryHandle handle)
{
Result rc;
if (IsEnabledAccessLog() && IsEnabledHandleAccessLog(handle))
{
TimeSpan startTime = Time.GetCurrent();
rc = handle.Directory.Read(out entriesRead, entryBuffer);
TimeSpan endTime = Time.GetCurrent();
OutputAccessLog(rc, startTime, endTime, handle, string.Empty);
}
else
{
rc = handle.Directory.Read(out entriesRead, entryBuffer);
}
return rc;
}
public void CloseDirectory(DirectoryHandle handle)
{
if (IsEnabledAccessLog() && IsEnabledHandleAccessLog(handle))

View file

@ -16,7 +16,7 @@ namespace LibHac.FsClient
using (sourceHandle)
{
foreach (DirectoryEntry entry in fs.ReadDirectory(sourceHandle))
foreach (DirectoryEntryEx entry in fs.ReadDirectory(sourceHandle))
{
string subSrcPath = PathTools.Normalize(PathTools.Combine(sourcePath, entry.Name));
string subDstPath = PathTools.Normalize(PathTools.Combine(destPath, entry.Name));
@ -95,17 +95,17 @@ namespace LibHac.FsClient
return Result.Success;
}
public static IEnumerable<DirectoryEntry> EnumerateEntries(this FileSystemManager fs, string path)
public static IEnumerable<DirectoryEntryEx> EnumerateEntries(this FileSystemManager fs, string path)
{
return fs.EnumerateEntries(path, "*");
}
public static IEnumerable<DirectoryEntry> EnumerateEntries(this FileSystemManager fs, string path, string searchPattern)
public static IEnumerable<DirectoryEntryEx> EnumerateEntries(this FileSystemManager fs, string path, string searchPattern)
{
return fs.EnumerateEntries(path, searchPattern, SearchOptions.RecurseSubdirectories);
}
public static IEnumerable<DirectoryEntry> EnumerateEntries(this FileSystemManager fs, string path, string searchPattern, SearchOptions searchOptions)
public static IEnumerable<DirectoryEntryEx> EnumerateEntries(this FileSystemManager fs, string path, string searchPattern, SearchOptions searchOptions)
{
bool ignoreCase = searchOptions.HasFlag(SearchOptions.CaseInsensitive);
bool recurse = searchOptions.HasFlag(SearchOptions.RecurseSubdirectories);
@ -114,7 +114,7 @@ namespace LibHac.FsClient
using (sourceHandle)
{
foreach (DirectoryEntry entry in fs.ReadDirectory(sourceHandle))
foreach (DirectoryEntryEx entry in fs.ReadDirectory(sourceHandle))
{
if (PathTools.MatchesPattern(searchPattern, entry.Name, ignoreCase))
{
@ -125,9 +125,9 @@ namespace LibHac.FsClient
string subPath = PathTools.Normalize(PathTools.Combine(path, entry.Name));
IEnumerable<DirectoryEntry> subEntries = fs.EnumerateEntries(subPath, searchPattern, searchOptions);
IEnumerable<DirectoryEntryEx> subEntries = fs.EnumerateEntries(subPath, searchPattern, searchOptions);
foreach (DirectoryEntry subEntry in subEntries)
foreach (DirectoryEntryEx subEntry in subEntries)
{
subEntry.FullPath = PathTools.Combine(path, subEntry.Name);
yield return subEntry;

View file

@ -23,6 +23,7 @@
<IncludeSource>true</IncludeSource>
<NoWarn>$(NoWarn);1591;NU5105</NoWarn>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
<PropertyGroup Condition=" '$(TargetFramework)' == 'net46' ">

View file

@ -67,13 +67,11 @@ 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 = rootDir.EnumerateEntries("*.nca", SearchOptions.RecurseSubdirectories)
IEnumerable<DirectoryEntryEx> files = ContentFs.EnumerateEntries("*.nca", SearchOptions.RecurseSubdirectories)
.Where(x => x.Type == DirectoryEntryType.File);
foreach (DirectoryEntry fileEntry in files)
foreach (DirectoryEntryEx fileEntry in files)
{
SwitchFsNca nca = null;
try
@ -109,7 +107,7 @@ namespace LibHac
{
if (SaveFs == null) return;
foreach (DirectoryEntry fileEntry in SaveFs.EnumerateEntries().Where(x => x.Type == DirectoryEntryType.File))
foreach (DirectoryEntryEx fileEntry in SaveFs.EnumerateEntries().Where(x => x.Type == DirectoryEntryType.File))
{
SaveDataFileSystem save = null;
string saveName = Path.GetFileNameWithoutExtension(fileEntry.Name);
@ -141,7 +139,7 @@ namespace LibHac
var title = new Title();
IFileSystem fs = nca.OpenFileSystem(NcaSectionType.Data, IntegrityCheckLevel.ErrorOnInvalid);
string cnmtPath = fs.EnumerateEntries("*.cnmt").Single().FullPath;
string cnmtPath = fs.EnumerateEntries("/", "*.cnmt").Single().FullPath;
fs.OpenFile(out IFile file, cnmtPath, OpenMode.Read).ThrowIfFailure();

View file

@ -30,7 +30,7 @@ namespace hactoolnet
using (sourceHandle)
{
foreach (DirectoryEntry entry in fs.ReadDirectory(sourceHandle))
foreach (DirectoryEntryEx entry in fs.ReadDirectory(sourceHandle))
{
string subSrcPath = PathTools.Normalize(PathTools.Combine(sourcePath, entry.Name));
string subDstPath = PathTools.Normalize(PathTools.Combine(destPath, entry.Name));
@ -57,7 +57,7 @@ namespace hactoolnet
{
long size = 0;
foreach (DirectoryEntry entry in fs.EnumerateEntries(path, searchPattern))
foreach (DirectoryEntryEx entry in fs.EnumerateEntries(path, searchPattern))
{
size += entry.Size;
}

View file

@ -73,7 +73,7 @@ namespace hactoolnet
{
IFileSystem romfs = OpenFileSystemByType(NcaSectionType.Data);
foreach (DirectoryEntry entry in romfs.EnumerateEntries())
foreach (DirectoryEntryEx entry in romfs.EnumerateEntries())
{
ctx.Logger.LogMessage(entry.FullPath);
}

View file

@ -20,7 +20,7 @@ namespace hactoolnet
if (ctx.Options.ListRomFs)
{
foreach (DirectoryEntry entry in romfs.EnumerateEntries())
foreach (DirectoryEntryEx entry in romfs.EnumerateEntries())
{
ctx.Logger.LogMessage(entry.FullPath);
}

View file

@ -131,7 +131,7 @@ namespace hactoolnet
if (ctx.Options.ListFiles)
{
foreach (DirectoryEntry entry in save.EnumerateEntries())
foreach (DirectoryEntryEx entry in save.EnumerateEntries())
{
ctx.Logger.LogMessage(entry.FullPath);
}
@ -228,7 +228,7 @@ namespace hactoolnet
{
var sb = new StringBuilder();
foreach (DirectoryEntry entry in save.EnumerateEntries().Where(x => x.Type == DirectoryEntryType.File))
foreach (DirectoryEntryEx entry in save.EnumerateEntries().Where(x => x.Type == DirectoryEntryType.File))
{
save.FileTable.TryOpenFile(entry.FullPath, out SaveFileInfo fileInfo);
if (fileInfo.StartBlock < 0) continue;

View file

@ -316,7 +316,7 @@ namespace hactoolnet
IFileSystem fs = switchFs.ContentFs;
DirectoryEntry[] ncaDirs = fs.EnumerateEntries("*.nca", SearchOptions.RecurseSubdirectories)
DirectoryEntryEx[] ncaDirs = fs.EnumerateEntries("*.nca", SearchOptions.RecurseSubdirectories)
.Where(x => x.Type == DirectoryEntryType.Directory)
.Where(x => fs.FileExists($"{x.FullPath}/00"))
.ToArray();
@ -326,7 +326,7 @@ namespace hactoolnet
ctx.Logger.LogMessage("Warning: NCA folders without the archive flag were found. Fixing...");
}
foreach (DirectoryEntry file in ncaDirs)
foreach (DirectoryEntryEx file in ncaDirs)
{
fs.SetConcatenationFileAttribute(file.FullPath);
ctx.Logger.LogMessage($"{file.FullPath}");