mirror of
https://github.com/Thealexbarney/LibHac.git
synced 2025-02-09 13:14:46 +01:00
Merge pull request #83 from Thealexbarney/fssrv
Add the base/beginnings of a FileSystem service
This commit is contained in:
commit
db88a46d07
@ -1,6 +1,8 @@
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using LibHac.Fs.NcaUtils;
|
||||
using LibHac.FsSystem.NcaUtils;
|
||||
using LibHac.Ncm;
|
||||
using ContentType = LibHac.Ncm.ContentType;
|
||||
|
||||
namespace LibHac
|
||||
{
|
||||
@ -8,7 +10,7 @@ namespace LibHac
|
||||
{
|
||||
public ulong TitleId { get; }
|
||||
public TitleVersion TitleVersion { get; }
|
||||
public TitleType Type { get; }
|
||||
public ContentMetaType Type { get; }
|
||||
public byte FieldD { get; }
|
||||
public int TableOffset { get; }
|
||||
public int ContentEntryCount { get; }
|
||||
@ -34,8 +36,8 @@ namespace LibHac
|
||||
{
|
||||
TitleId = reader.ReadUInt64();
|
||||
uint version = reader.ReadUInt32();
|
||||
Type = (TitleType)reader.ReadByte();
|
||||
TitleVersion = new TitleVersion(version, Type < TitleType.Application);
|
||||
Type = (ContentMetaType)reader.ReadByte();
|
||||
TitleVersion = new TitleVersion(version, Type < ContentMetaType.Application);
|
||||
FieldD = reader.ReadByte();
|
||||
TableOffset = reader.ReadUInt16();
|
||||
ContentEntryCount = reader.ReadUInt16();
|
||||
@ -44,16 +46,16 @@ namespace LibHac
|
||||
|
||||
switch (Type)
|
||||
{
|
||||
case TitleType.Application:
|
||||
case ContentMetaType.Application:
|
||||
ApplicationTitleId = TitleId;
|
||||
PatchTitleId = reader.ReadUInt64();
|
||||
MinimumSystemVersion = new TitleVersion(reader.ReadUInt32(), true);
|
||||
break;
|
||||
case TitleType.Patch:
|
||||
case ContentMetaType.Patch:
|
||||
ApplicationTitleId = reader.ReadUInt64();
|
||||
MinimumSystemVersion = new TitleVersion(reader.ReadUInt32(), true);
|
||||
break;
|
||||
case TitleType.AddOnContent:
|
||||
case ContentMetaType.AddOnContent:
|
||||
ApplicationTitleId = reader.ReadUInt64();
|
||||
MinimumApplicationVersion = new TitleVersion(reader.ReadUInt32());
|
||||
break;
|
||||
@ -74,7 +76,7 @@ namespace LibHac
|
||||
MetaEntries[i] = new CnmtContentMetaEntry(reader);
|
||||
}
|
||||
|
||||
if (Type == TitleType.Patch)
|
||||
if (Type == ContentMetaType.Patch)
|
||||
{
|
||||
ExtendedData = new CnmtExtended(reader);
|
||||
}
|
||||
@ -89,7 +91,7 @@ namespace LibHac
|
||||
public byte[] Hash { get; set; }
|
||||
public byte[] NcaId { get; set; }
|
||||
public long Size { get; set; }
|
||||
public CnmtContentType Type { get; set; }
|
||||
public ContentType Type { get; set; }
|
||||
|
||||
public CnmtContentEntry() { }
|
||||
|
||||
@ -99,7 +101,7 @@ namespace LibHac
|
||||
NcaId = reader.ReadBytes(0x10);
|
||||
Size = reader.ReadUInt32();
|
||||
Size |= (long)reader.ReadUInt16() << 32;
|
||||
Type = (CnmtContentType)reader.ReadByte();
|
||||
Type = (ContentType)reader.ReadByte();
|
||||
reader.BaseStream.Position += 1;
|
||||
}
|
||||
}
|
||||
@ -108,7 +110,7 @@ namespace LibHac
|
||||
{
|
||||
public ulong TitleId { get; }
|
||||
public TitleVersion Version { get; }
|
||||
public CnmtContentType Type { get; }
|
||||
public ContentType Type { get; }
|
||||
|
||||
public CnmtContentMetaEntry() { }
|
||||
|
||||
@ -116,7 +118,7 @@ namespace LibHac
|
||||
{
|
||||
TitleId = reader.ReadUInt64();
|
||||
Version = new TitleVersion(reader.ReadUInt32(), true);
|
||||
Type = (CnmtContentType)reader.ReadByte();
|
||||
Type = (ContentType)reader.ReadByte();
|
||||
reader.BaseStream.Position += 3;
|
||||
}
|
||||
}
|
||||
@ -199,7 +201,7 @@ namespace LibHac
|
||||
{
|
||||
public ulong TitleId { get; }
|
||||
public TitleVersion Version { get; }
|
||||
public TitleType Type { get; }
|
||||
public ContentMetaType Type { get; }
|
||||
public byte[] Hash { get; }
|
||||
public short ContentCount { get; }
|
||||
public short CnmtPrevMetaEntryField32 { get; }
|
||||
@ -209,7 +211,7 @@ namespace LibHac
|
||||
{
|
||||
TitleId = reader.ReadUInt64();
|
||||
Version = new TitleVersion(reader.ReadUInt32());
|
||||
Type = (TitleType)reader.ReadByte();
|
||||
Type = (ContentMetaType)reader.ReadByte();
|
||||
reader.BaseStream.Position += 3;
|
||||
Hash = reader.ReadBytes(0x20);
|
||||
ContentCount = reader.ReadInt16();
|
||||
@ -265,8 +267,8 @@ namespace LibHac
|
||||
public long SizeOld { get; }
|
||||
public long SizeNew { get; }
|
||||
public short FragmentCount { get; }
|
||||
public CnmtContentType Type { get; }
|
||||
public CnmtDeltaType DeltaType { get; }
|
||||
public ContentType Type { get; }
|
||||
public UpdateType DeltaType { get; }
|
||||
public int FragmentSetInfoField30 { get; }
|
||||
|
||||
|
||||
@ -281,8 +283,8 @@ namespace LibHac
|
||||
SizeNew = reader.ReadUInt32();
|
||||
|
||||
FragmentCount = reader.ReadInt16();
|
||||
Type = (CnmtContentType)reader.ReadByte();
|
||||
DeltaType = (CnmtDeltaType)reader.ReadByte();
|
||||
Type = (ContentType)reader.ReadByte();
|
||||
DeltaType = (UpdateType)reader.ReadByte();
|
||||
FragmentSetInfoField30 = reader.ReadInt32();
|
||||
}
|
||||
}
|
||||
@ -291,14 +293,14 @@ namespace LibHac
|
||||
{
|
||||
public byte[] NcaId { get; }
|
||||
public long Size { get; }
|
||||
public CnmtContentType Type { get; }
|
||||
public ContentType Type { get; }
|
||||
|
||||
public CnmtPrevContent(BinaryReader reader)
|
||||
{
|
||||
NcaId = reader.ReadBytes(0x10);
|
||||
Size = reader.ReadUInt32();
|
||||
Size |= (long)reader.ReadUInt16() << 32;
|
||||
Type = (CnmtContentType)reader.ReadByte();
|
||||
Type = (ContentType)reader.ReadByte();
|
||||
reader.BaseStream.Position += 1;
|
||||
}
|
||||
}
|
||||
@ -314,35 +316,4 @@ namespace LibHac
|
||||
FragmentIndex = reader.ReadInt16();
|
||||
}
|
||||
}
|
||||
|
||||
public enum CnmtContentType
|
||||
{
|
||||
Meta,
|
||||
Program,
|
||||
Data,
|
||||
Control,
|
||||
HtmlDocument,
|
||||
LegalInformation,
|
||||
DeltaFragment
|
||||
}
|
||||
|
||||
public enum CnmtDeltaType
|
||||
{
|
||||
Delta,
|
||||
Replace,
|
||||
NewContent
|
||||
}
|
||||
|
||||
public enum TitleType
|
||||
{
|
||||
SystemProgram = 1,
|
||||
SystemData,
|
||||
SystemUpdate,
|
||||
BootImagePackage,
|
||||
BootImagePackageSafe,
|
||||
Application = 0x80,
|
||||
Patch,
|
||||
AddOnContent,
|
||||
Delta
|
||||
}
|
||||
}
|
||||
|
52
src/LibHac/Common/BlitStruct.cs
Normal file
52
src/LibHac/Common/BlitStruct.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
44
src/LibHac/Common/HResult.cs
Normal file
44
src/LibHac/Common/HResult.cs
Normal file
@ -0,0 +1,44 @@
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using LibHac.Fs;
|
||||
|
||||
namespace LibHac.Common
|
||||
{
|
||||
[SuppressMessage("ReSharper", "InconsistentNaming")]
|
||||
internal static class HResult
|
||||
{
|
||||
public const int ERROR_FILE_NOT_FOUND = unchecked((int)0x80070002);
|
||||
public const int ERROR_PATH_NOT_FOUND = unchecked((int)0x80070003);
|
||||
public const int ERROR_ACCESS_DENIED = unchecked((int)0x80070005);
|
||||
public const int ERROR_SHARING_VIOLATION = unchecked((int)0x80070020);
|
||||
public const int ERROR_HANDLE_EOF = unchecked((int)0x80070026);
|
||||
public const int ERROR_HANDLE_DISK_FULL = unchecked((int)0x80070027);
|
||||
public const int ERROR_FILE_EXISTS = unchecked((int)0x80070050);
|
||||
public const int ERROR_DISK_FULL = unchecked((int)0x80070070);
|
||||
public const int ERROR_INVALID_NAME = unchecked((int)0x8007007B);
|
||||
public const int ERROR_DIR_NOT_EMPTY = unchecked((int)0x80070091);
|
||||
public const int ERROR_ALREADY_EXISTS = unchecked((int)0x800700B7);
|
||||
public const int ERROR_DIRECTORY = unchecked((int)0x8007010B);
|
||||
public const int ERROR_SPACES_NOT_ENOUGH_DRIVES = unchecked((int)0x80E7000B);
|
||||
|
||||
public static Result HResultToHorizonResult(int hResult)
|
||||
{
|
||||
return hResult switch
|
||||
{
|
||||
ERROR_FILE_NOT_FOUND => ResultFs.PathNotFound,
|
||||
ERROR_PATH_NOT_FOUND => ResultFs.PathNotFound,
|
||||
ERROR_ACCESS_DENIED => ResultFs.TargetLocked,
|
||||
ERROR_SHARING_VIOLATION => ResultFs.TargetLocked,
|
||||
ERROR_HANDLE_EOF => ResultFs.ValueOutOfRange,
|
||||
ERROR_HANDLE_DISK_FULL => ResultFs.InsufficientFreeSpace,
|
||||
ERROR_FILE_EXISTS => ResultFs.PathAlreadyExists,
|
||||
ERROR_DISK_FULL => ResultFs.InsufficientFreeSpace,
|
||||
ERROR_INVALID_NAME => ResultFs.PathNotFound,
|
||||
ERROR_DIR_NOT_EMPTY => ResultFs.DirectoryNotEmpty,
|
||||
ERROR_ALREADY_EXISTS => ResultFs.PathAlreadyExists,
|
||||
ERROR_DIRECTORY => ResultFs.PathNotFound,
|
||||
ERROR_SPACES_NOT_ENOUGH_DRIVES => ResultFs.InsufficientFreeSpace,
|
||||
_ => ResultFs.UnknownHostFileSystemError
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
9
src/LibHac/Common/ITimeStampGenerator.cs
Normal file
9
src/LibHac/Common/ITimeStampGenerator.cs
Normal file
@ -0,0 +1,9 @@
|
||||
using System;
|
||||
|
||||
namespace LibHac.Common
|
||||
{
|
||||
public interface ITimeStampGenerator
|
||||
{
|
||||
DateTimeOffset Generate();
|
||||
}
|
||||
}
|
87
src/LibHac/Common/Id128.cs
Normal file
87
src/LibHac/Common/Id128.cs
Normal file
@ -0,0 +1,87 @@
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace LibHac.Common
|
||||
{
|
||||
/// <summary>
|
||||
/// A generic 128-bit ID value.
|
||||
/// </summary>
|
||||
[DebuggerDisplay("{ToString()}")]
|
||||
[StructLayout(LayoutKind.Sequential, Size = 0x10)]
|
||||
public struct Id128 : IEquatable<Id128>, IComparable<Id128>, IComparable
|
||||
{
|
||||
public readonly ulong High;
|
||||
public readonly ulong Low;
|
||||
|
||||
public static Id128 Zero => default;
|
||||
|
||||
public Id128(ulong high, ulong low)
|
||||
{
|
||||
High = high;
|
||||
Low = low;
|
||||
}
|
||||
|
||||
public Id128(ReadOnlySpan<byte> uid)
|
||||
{
|
||||
ReadOnlySpan<ulong> longs = MemoryMarshal.Cast<byte, ulong>(uid);
|
||||
|
||||
High = longs[0];
|
||||
Low = longs[1];
|
||||
}
|
||||
|
||||
public override string ToString() => AsBytes().ToHexString();
|
||||
|
||||
public bool Equals(Id128 other)
|
||||
{
|
||||
return High == other.High && Low == other.Low;
|
||||
}
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
return obj is Id128 other && Equals(other);
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
unchecked
|
||||
{
|
||||
return (High.GetHashCode() * 397) ^ Low.GetHashCode();
|
||||
}
|
||||
}
|
||||
|
||||
public int CompareTo(Id128 other)
|
||||
{
|
||||
int highComparison = High.CompareTo(other.High);
|
||||
if (highComparison != 0) return highComparison;
|
||||
return Low.CompareTo(other.Low);
|
||||
}
|
||||
|
||||
public int CompareTo(object obj)
|
||||
{
|
||||
if (obj is null) return 1;
|
||||
return obj is Id128 other ? CompareTo(other) : throw new ArgumentException($"Object must be of type {nameof(Id128)}");
|
||||
}
|
||||
|
||||
public readonly void ToBytes(Span<byte> output)
|
||||
{
|
||||
Span<ulong> longs = MemoryMarshal.Cast<byte, ulong>(output);
|
||||
|
||||
longs[0] = High;
|
||||
longs[1] = Low;
|
||||
}
|
||||
|
||||
public ReadOnlySpan<byte> AsBytes()
|
||||
{
|
||||
return SpanHelpers.AsByteSpan(ref this);
|
||||
}
|
||||
|
||||
public static bool operator ==(Id128 left, Id128 right) => left.Equals(right);
|
||||
public static bool operator !=(Id128 left, Id128 right) => !left.Equals(right);
|
||||
|
||||
public static bool operator <(Id128 left, Id128 right) => left.CompareTo(right) < 0;
|
||||
public static bool operator >(Id128 left, Id128 right) => left.CompareTo(right) > 0;
|
||||
public static bool operator <=(Id128 left, Id128 right) => left.CompareTo(right) <= 0;
|
||||
public static bool operator >=(Id128 left, Id128 right) => left.CompareTo(right) >= 0;
|
||||
}
|
||||
}
|
48
src/LibHac/Common/Key128.cs
Normal file
48
src/LibHac/Common/Key128.cs
Normal file
@ -0,0 +1,48 @@
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace LibHac.Common
|
||||
{
|
||||
[DebuggerDisplay("{ToString()}")]
|
||||
[StructLayout(LayoutKind.Sequential, Size = 0x10)]
|
||||
public struct Key128 : IEquatable<Key128>
|
||||
{
|
||||
private readonly ulong _dummy1;
|
||||
private readonly ulong _dummy2;
|
||||
|
||||
public Span<byte> Value => SpanHelpers.AsByteSpan(ref this);
|
||||
|
||||
public Key128(ReadOnlySpan<byte> bytes)
|
||||
{
|
||||
ReadOnlySpan<ulong> longs = MemoryMarshal.Cast<byte, ulong>(bytes);
|
||||
|
||||
_dummy1 = longs[0];
|
||||
_dummy2 = longs[1];
|
||||
}
|
||||
|
||||
public override string ToString() => Value.ToHexString();
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
return obj is Key128 key && Equals(key);
|
||||
}
|
||||
|
||||
public bool Equals(Key128 other)
|
||||
{
|
||||
return _dummy1 == other._dummy1 &&
|
||||
_dummy2 == other._dummy2;
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
int hashCode = -1653217991;
|
||||
hashCode = hashCode * -1521134295 + _dummy1.GetHashCode();
|
||||
hashCode = hashCode * -1521134295 + _dummy2.GetHashCode();
|
||||
return hashCode;
|
||||
}
|
||||
|
||||
public static bool operator ==(Key128 left, Key128 right) => left.Equals(right);
|
||||
public static bool operator !=(Key128 left, Key128 right) => !(left == right);
|
||||
}
|
||||
}
|
38
src/LibHac/Common/PaddingStructs.cs
Normal file
38
src/LibHac/Common/PaddingStructs.cs
Normal file
@ -0,0 +1,38 @@
|
||||
using System.Diagnostics;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace LibHac.Common
|
||||
{
|
||||
// In order for the Visual Studio debugger to accurately display a struct, every offset
|
||||
// in the struct that is used for the debugger display must be part of a field.
|
||||
// These padding structs make it easier to accomplish that.
|
||||
[StructLayout(LayoutKind.Sequential, Size = 0x20)]
|
||||
internal struct Padding20
|
||||
{
|
||||
[DebuggerBrowsable(DebuggerBrowsableState.Never)] private readonly ulong Padding00;
|
||||
[DebuggerBrowsable(DebuggerBrowsableState.Never)] private readonly ulong Padding08;
|
||||
[DebuggerBrowsable(DebuggerBrowsableState.Never)] private readonly ulong Padding10;
|
||||
[DebuggerBrowsable(DebuggerBrowsableState.Never)] private readonly ulong Padding18;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential, Size = 0x40)]
|
||||
internal struct Padding40
|
||||
{
|
||||
[DebuggerBrowsable(DebuggerBrowsableState.Never)] private readonly Padding20 Padding00;
|
||||
[DebuggerBrowsable(DebuggerBrowsableState.Never)] private readonly Padding20 Padding20;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential, Size = 0x80)]
|
||||
internal struct Padding80
|
||||
{
|
||||
[DebuggerBrowsable(DebuggerBrowsableState.Never)] private readonly Padding40 Padding00;
|
||||
[DebuggerBrowsable(DebuggerBrowsableState.Never)] private readonly Padding40 Padding40;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential, Size = 0x100)]
|
||||
internal struct Padding100
|
||||
{
|
||||
[DebuggerBrowsable(DebuggerBrowsableState.Never)] private readonly Padding80 Padding00;
|
||||
[DebuggerBrowsable(DebuggerBrowsableState.Never)] private readonly Padding80 Padding80;
|
||||
}
|
||||
}
|
35
src/LibHac/Common/SpanHelpers.cs
Normal file
35
src/LibHac/Common/SpanHelpers.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
95
src/LibHac/Common/StringUtils.cs
Normal file
95
src/LibHac/Common/StringUtils.cs
Normal file
@ -0,0 +1,95 @@
|
||||
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;
|
||||
}
|
||||
|
||||
public static int GetLength(ReadOnlySpan<byte> s, int maxLen)
|
||||
{
|
||||
int i = 0;
|
||||
|
||||
while (i < maxLen && 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 Utf8ToString(ReadOnlySpan<byte> value)
|
||||
{
|
||||
#if STRING_SPAN
|
||||
return Encoding.UTF8.GetString(value);
|
||||
#else
|
||||
return Encoding.UTF8.GetString(value.ToArray());
|
||||
#endif
|
||||
}
|
||||
|
||||
public static string Utf8ZToString(ReadOnlySpan<byte> value)
|
||||
{
|
||||
return Utf8ToString(value.Slice(0, GetLength(value)));
|
||||
}
|
||||
}
|
||||
}
|
42
src/LibHac/Common/U8Span.cs
Normal file
42
src/LibHac/Common/U8Span.cs
Normal file
@ -0,0 +1,42 @@
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Text;
|
||||
|
||||
namespace LibHac.Common
|
||||
{
|
||||
[DebuggerDisplay("{ToString()}")]
|
||||
public ref struct U8Span
|
||||
{
|
||||
private readonly ReadOnlySpan<byte> _buffer;
|
||||
|
||||
public ReadOnlySpan<byte> Value => _buffer;
|
||||
public int Length => _buffer.Length;
|
||||
|
||||
public byte this[int i]
|
||||
{
|
||||
get => _buffer[i];
|
||||
}
|
||||
|
||||
public U8Span(ReadOnlySpan<byte> value)
|
||||
{
|
||||
_buffer = value;
|
||||
}
|
||||
|
||||
public U8Span(string value)
|
||||
{
|
||||
_buffer = Encoding.UTF8.GetBytes(value);
|
||||
}
|
||||
|
||||
public static implicit operator ReadOnlySpan<byte>(U8Span value) => value.Value;
|
||||
|
||||
public static explicit operator string(U8Span value) => value.ToString();
|
||||
public static explicit operator U8Span(string value) => new U8Span(value);
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return StringUtils.Utf8ToString(_buffer);
|
||||
}
|
||||
|
||||
public bool IsNull() => _buffer == default;
|
||||
}
|
||||
}
|
46
src/LibHac/Common/U8SpanMutable.cs
Normal file
46
src/LibHac/Common/U8SpanMutable.cs
Normal file
@ -0,0 +1,46 @@
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Text;
|
||||
|
||||
namespace LibHac.Common
|
||||
{
|
||||
[DebuggerDisplay("{ToString()}")]
|
||||
public ref struct U8SpanMutable
|
||||
{
|
||||
private readonly Span<byte> _buffer;
|
||||
|
||||
public Span<byte> Value => _buffer;
|
||||
public int Length => _buffer.Length;
|
||||
|
||||
public byte this[int i]
|
||||
{
|
||||
get => _buffer[i];
|
||||
set => _buffer[i] = value;
|
||||
}
|
||||
|
||||
public U8SpanMutable(Span<byte> value)
|
||||
{
|
||||
_buffer = value;
|
||||
}
|
||||
|
||||
public U8SpanMutable(string value)
|
||||
{
|
||||
_buffer = Encoding.UTF8.GetBytes(value);
|
||||
}
|
||||
|
||||
public static implicit operator U8Span(U8SpanMutable value) => new U8Span(value._buffer);
|
||||
|
||||
public static implicit operator ReadOnlySpan<byte>(U8SpanMutable value) => value.Value;
|
||||
public static implicit operator Span<byte>(U8SpanMutable value) => value.Value;
|
||||
|
||||
public static explicit operator string(U8SpanMutable value) => value.ToString();
|
||||
public static explicit operator U8SpanMutable(string value) => new U8SpanMutable(value);
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return StringUtils.Utf8ZToString(_buffer);
|
||||
}
|
||||
|
||||
public bool IsNull() => _buffer == default;
|
||||
}
|
||||
}
|
41
src/LibHac/Common/U8String.cs
Normal file
41
src/LibHac/Common/U8String.cs
Normal file
@ -0,0 +1,41 @@
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Text;
|
||||
|
||||
namespace LibHac.Common
|
||||
{
|
||||
[DebuggerDisplay("{ToString()}")]
|
||||
public struct U8String
|
||||
{
|
||||
private readonly byte[] _buffer;
|
||||
|
||||
public ReadOnlySpan<byte> Value => _buffer;
|
||||
public int Length => _buffer.Length;
|
||||
|
||||
public byte this[int i] => _buffer[i];
|
||||
|
||||
public U8String(byte[] value)
|
||||
{
|
||||
_buffer = value;
|
||||
}
|
||||
|
||||
public U8String(string value)
|
||||
{
|
||||
_buffer = Encoding.UTF8.GetBytes(value);
|
||||
}
|
||||
|
||||
public static implicit operator U8Span(U8String value) => new U8Span(value._buffer);
|
||||
|
||||
public static implicit operator ReadOnlySpan<byte>(U8String value) => value.Value;
|
||||
|
||||
public static explicit operator string(U8String value) => value.ToString();
|
||||
public static explicit operator U8String(string value) => new U8String(value);
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return StringUtils.Utf8ToString(_buffer);
|
||||
}
|
||||
|
||||
public bool IsNull() => _buffer == null;
|
||||
}
|
||||
}
|
86
src/LibHac/Common/U8StringBuilder.cs
Normal file
86
src/LibHac/Common/U8StringBuilder.cs
Normal file
@ -0,0 +1,86 @@
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace LibHac.Common
|
||||
{
|
||||
[DebuggerDisplay("{ToString()}")]
|
||||
public ref struct U8StringBuilder
|
||||
{
|
||||
private const int NullTerminatorLength = 1;
|
||||
|
||||
private readonly Span<byte> _buffer;
|
||||
private int _length;
|
||||
|
||||
public bool Overflowed { get; private set; }
|
||||
public int Capacity => _buffer.Length - NullTerminatorLength;
|
||||
|
||||
public U8StringBuilder(Span<byte> buffer)
|
||||
{
|
||||
_buffer = buffer;
|
||||
_length = 0;
|
||||
Overflowed = false;
|
||||
|
||||
ThrowIfBufferLengthIsZero();
|
||||
|
||||
AddNullTerminator();
|
||||
}
|
||||
|
||||
public U8StringBuilder Append(ReadOnlySpan<byte> value)
|
||||
{
|
||||
if (Overflowed) return this;
|
||||
|
||||
int valueLength = StringUtils.GetLength(value);
|
||||
|
||||
if (!HasAdditionalCapacity(valueLength))
|
||||
{
|
||||
Overflowed = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
value.Slice(0, valueLength).CopyTo(_buffer.Slice(_length));
|
||||
_length += valueLength;
|
||||
AddNullTerminator();
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public U8StringBuilder Append(byte value)
|
||||
{
|
||||
if (Overflowed) return this;
|
||||
|
||||
if (!HasAdditionalCapacity(1))
|
||||
{
|
||||
Overflowed = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
_buffer[_length] = value;
|
||||
_length++;
|
||||
AddNullTerminator();
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
private bool HasCapacity(int requiredCapacity)
|
||||
{
|
||||
return requiredCapacity <= Capacity;
|
||||
}
|
||||
|
||||
private bool HasAdditionalCapacity(int requiredAdditionalCapacity)
|
||||
{
|
||||
return HasCapacity(_length + requiredAdditionalCapacity);
|
||||
}
|
||||
|
||||
private void AddNullTerminator()
|
||||
{
|
||||
_buffer[_length] = 0;
|
||||
}
|
||||
|
||||
private void ThrowIfBufferLengthIsZero()
|
||||
{
|
||||
if (_buffer.Length == 0) throw new ArgumentException("Buffer length must be greater than 0.");
|
||||
}
|
||||
|
||||
public override string ToString() => StringUtils.Utf8ZToString(_buffer);
|
||||
}
|
||||
}
|
15
src/LibHac/Common/U8StringHelpers.cs
Normal file
15
src/LibHac/Common/U8StringHelpers.cs
Normal file
@ -0,0 +1,15 @@
|
||||
namespace LibHac.Common
|
||||
{
|
||||
public static class U8StringHelpers
|
||||
{
|
||||
public static U8String ToU8String(this string value)
|
||||
{
|
||||
return new U8String(value);
|
||||
}
|
||||
|
||||
public static U8Span ToU8Span(this string value)
|
||||
{
|
||||
return new U8Span(value);
|
||||
}
|
||||
}
|
||||
}
|
48
src/LibHac/Common/U8StringMutable.cs
Normal file
48
src/LibHac/Common/U8StringMutable.cs
Normal file
@ -0,0 +1,48 @@
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Text;
|
||||
|
||||
namespace LibHac.Common
|
||||
{
|
||||
[DebuggerDisplay("{ToString()}")]
|
||||
public struct U8StringMutable
|
||||
{
|
||||
private readonly byte[] _buffer;
|
||||
|
||||
public Span<byte> Value => _buffer;
|
||||
public int Length => _buffer.Length;
|
||||
|
||||
public byte this[int i]
|
||||
{
|
||||
get => _buffer[i];
|
||||
set => _buffer[i] = value;
|
||||
}
|
||||
|
||||
public U8StringMutable(byte[] value)
|
||||
{
|
||||
_buffer = value;
|
||||
}
|
||||
|
||||
public U8StringMutable(string value)
|
||||
{
|
||||
_buffer = Encoding.UTF8.GetBytes(value);
|
||||
}
|
||||
|
||||
public static implicit operator U8String(U8StringMutable value) => new U8String(value._buffer);
|
||||
public static implicit operator U8SpanMutable(U8StringMutable value) => new U8SpanMutable(value._buffer);
|
||||
public static implicit operator U8Span(U8StringMutable value) => new U8Span(value._buffer);
|
||||
|
||||
public static implicit operator ReadOnlySpan<byte>(U8StringMutable value) => value.Value;
|
||||
public static implicit operator Span<byte>(U8StringMutable value) => value.Value;
|
||||
|
||||
public static explicit operator string(U8StringMutable value) => value.ToString();
|
||||
public static explicit operator U8StringMutable(string value) => new U8StringMutable(value);
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return StringUtils.Utf8ZToString(_buffer);
|
||||
}
|
||||
|
||||
public bool IsNull() => _buffer == null;
|
||||
}
|
||||
}
|
@ -2,7 +2,7 @@
|
||||
using System.IO;
|
||||
using System.Numerics;
|
||||
using System.Security.Cryptography;
|
||||
using LibHac.Fs;
|
||||
using LibHac.FsSystem;
|
||||
|
||||
namespace LibHac
|
||||
{
|
||||
|
14
src/LibHac/Fs/AccessLogHelpers.cs
Normal file
14
src/LibHac/Fs/AccessLogHelpers.cs
Normal file
@ -0,0 +1,14 @@
|
||||
using System;
|
||||
|
||||
namespace LibHac.Fs
|
||||
{
|
||||
public static class AccessLogHelpers
|
||||
{
|
||||
public static string BuildDefaultLogLine(Result result, TimeSpan startTime, TimeSpan endTime, int handleId,
|
||||
string message, string caller)
|
||||
{
|
||||
return
|
||||
$"FS_ACCESS: {{ start: {(long) startTime.TotalMilliseconds,9}, end: {(long) endTime.TotalMilliseconds,9}, result: 0x{result.Value:x8}, handle: 0x{handleId:x8}, function: \"{caller}\"{message} }}";
|
||||
}
|
||||
}
|
||||
}
|
@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using LibHac.FsSystem;
|
||||
|
||||
namespace LibHac.Fs.Accessors
|
||||
{
|
||||
@ -9,24 +10,34 @@ namespace LibHac.Fs.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()
|
||||
|
@ -2,7 +2,7 @@
|
||||
|
||||
namespace LibHac.Fs.Accessors
|
||||
{
|
||||
public class FileAccessor : IFile
|
||||
public class FileAccessor : FileBase
|
||||
{
|
||||
private IFile File { get; set; }
|
||||
|
||||
@ -10,9 +10,6 @@ namespace LibHac.Fs.Accessors
|
||||
public WriteState WriteState { get; private set; }
|
||||
public OpenMode OpenMode { get; }
|
||||
|
||||
// Todo: Consider removing Mode from interface because OpenMode is in FileAccessor
|
||||
OpenMode IFile.Mode => OpenMode;
|
||||
|
||||
public FileAccessor(IFile baseFile, FileSystemAccessor parent, OpenMode mode)
|
||||
{
|
||||
File = baseFile;
|
||||
@ -20,14 +17,14 @@ namespace LibHac.Fs.Accessors
|
||||
OpenMode = mode;
|
||||
}
|
||||
|
||||
public int Read(Span<byte> destination, long offset, ReadOption options)
|
||||
protected override Result ReadImpl(out long bytesRead, long offset, Span<byte> destination, ReadOption options)
|
||||
{
|
||||
CheckIfDisposed();
|
||||
|
||||
return File.Read(destination, offset, options);
|
||||
return File.Read(out bytesRead, offset, destination, options);
|
||||
}
|
||||
|
||||
public void Write(ReadOnlySpan<byte> source, long offset, WriteOption options)
|
||||
protected override Result WriteImpl(long offset, ReadOnlySpan<byte> source, WriteOption options)
|
||||
{
|
||||
CheckIfDisposed();
|
||||
|
||||
@ -35,38 +32,48 @@ namespace LibHac.Fs.Accessors
|
||||
{
|
||||
WriteState = (WriteState)(~options & WriteOption.Flush);
|
||||
|
||||
return;
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
File.Write(source, offset, options);
|
||||
Result rc = File.Write(offset, source, options);
|
||||
|
||||
WriteState = (WriteState)(~options & WriteOption.Flush);
|
||||
if (rc.IsSuccess())
|
||||
{
|
||||
WriteState = (WriteState)(~options & WriteOption.Flush);
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
public void Flush()
|
||||
protected override Result FlushImpl()
|
||||
{
|
||||
CheckIfDisposed();
|
||||
|
||||
File.Flush();
|
||||
Result rc = File.Flush();
|
||||
|
||||
WriteState = WriteState.None;
|
||||
if (rc.IsSuccess())
|
||||
{
|
||||
WriteState = WriteState.None;
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
public long GetSize()
|
||||
protected override Result GetSizeImpl(out long size)
|
||||
{
|
||||
CheckIfDisposed();
|
||||
|
||||
return File.GetSize();
|
||||
return File.GetSize(out size);
|
||||
}
|
||||
|
||||
public void SetSize(long size)
|
||||
protected override Result SetSizeImpl(long size)
|
||||
{
|
||||
CheckIfDisposed();
|
||||
|
||||
File.SetSize(size);
|
||||
return File.SetSize(size);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
if (File == null) return;
|
||||
|
||||
@ -74,7 +81,7 @@ namespace LibHac.Fs.Accessors
|
||||
{
|
||||
// Original FS code would return an error:
|
||||
// ThrowHelper.ThrowResult(ResultsFs.ResultFsWriteStateUnflushed);
|
||||
|
||||
|
||||
Flush();
|
||||
}
|
||||
|
||||
|
@ -1,6 +1,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using LibHac.FsSystem;
|
||||
|
||||
namespace LibHac.Fs.Accessors
|
||||
{
|
||||
@ -9,7 +10,8 @@ namespace LibHac.Fs.Accessors
|
||||
public string Name { get; }
|
||||
|
||||
private IFileSystem FileSystem { get; }
|
||||
internal FileSystemManager FsManager { get; }
|
||||
internal FileSystemClient FsClient { get; }
|
||||
private ICommonMountNameGenerator MountNameGenerator { get; }
|
||||
|
||||
private HashSet<FileAccessor> OpenFiles { get; } = new HashSet<FileAccessor>();
|
||||
private HashSet<DirectoryAccessor> OpenDirectories { get; } = new HashSet<DirectoryAccessor>();
|
||||
@ -18,124 +20,140 @@ namespace LibHac.Fs.Accessors
|
||||
|
||||
internal bool IsAccessLogEnabled { get; set; }
|
||||
|
||||
public FileSystemAccessor(string name, IFileSystem baseFileSystem, FileSystemManager fsManager)
|
||||
public FileSystemAccessor(string name, IFileSystem baseFileSystem, FileSystemClient fsClient, ICommonMountNameGenerator nameGenerator)
|
||||
{
|
||||
Name = name;
|
||||
FileSystem = baseFileSystem;
|
||||
FsManager = fsManager;
|
||||
FsClient = fsClient;
|
||||
MountNameGenerator = nameGenerator;
|
||||
}
|
||||
|
||||
public void CreateDirectory(string path)
|
||||
public Result CreateDirectory(string path)
|
||||
{
|
||||
FileSystem.CreateDirectory(path);
|
||||
return FileSystem.CreateDirectory(path);
|
||||
}
|
||||
|
||||
public void CreateFile(string path, long size, CreateFileOptions options)
|
||||
public Result CreateFile(string path, long size, CreateFileOptions options)
|
||||
{
|
||||
FileSystem.CreateFile(path, size, options);
|
||||
return FileSystem.CreateFile(path, size, options);
|
||||
}
|
||||
|
||||
public void DeleteDirectory(string path)
|
||||
public Result DeleteDirectory(string path)
|
||||
{
|
||||
FileSystem.DeleteDirectory(path);
|
||||
return FileSystem.DeleteDirectory(path);
|
||||
}
|
||||
|
||||
public void DeleteDirectoryRecursively(string path)
|
||||
public Result DeleteDirectoryRecursively(string path)
|
||||
{
|
||||
FileSystem.DeleteDirectoryRecursively(path);
|
||||
return FileSystem.DeleteDirectoryRecursively(path);
|
||||
}
|
||||
|
||||
public void CleanDirectoryRecursively(string path)
|
||||
public Result CleanDirectoryRecursively(string path)
|
||||
{
|
||||
FileSystem.CleanDirectoryRecursively(path);
|
||||
return FileSystem.CleanDirectoryRecursively(path);
|
||||
}
|
||||
|
||||
public void DeleteFile(string path)
|
||||
public Result DeleteFile(string path)
|
||||
{
|
||||
FileSystem.DeleteFile(path);
|
||||
return FileSystem.DeleteFile(path);
|
||||
}
|
||||
|
||||
public DirectoryAccessor OpenDirectory(string path, OpenDirectoryMode mode)
|
||||
public Result OpenDirectory(out DirectoryAccessor directory, string path, OpenDirectoryMode mode)
|
||||
{
|
||||
IDirectory dir = FileSystem.OpenDirectory(path, mode);
|
||||
directory = default;
|
||||
|
||||
var accessor = new DirectoryAccessor(dir, this);
|
||||
Result rc = FileSystem.OpenDirectory(out IDirectory rawDirectory, path, mode);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
var accessor = new DirectoryAccessor(rawDirectory, this, FileSystem, path);
|
||||
|
||||
lock (_locker)
|
||||
{
|
||||
OpenDirectories.Add(accessor);
|
||||
}
|
||||
|
||||
return accessor;
|
||||
directory = accessor;
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
public FileAccessor OpenFile(string path, OpenMode mode)
|
||||
public Result OpenFile(out FileAccessor file, string path, OpenMode mode)
|
||||
{
|
||||
IFile file = FileSystem.OpenFile(path, mode);
|
||||
file = default;
|
||||
|
||||
var accessor = new FileAccessor(file, this, mode);
|
||||
Result rc = FileSystem.OpenFile(out IFile rawFile, path, mode);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
var accessor = new FileAccessor(rawFile, this, mode);
|
||||
|
||||
lock (_locker)
|
||||
{
|
||||
OpenFiles.Add(accessor);
|
||||
}
|
||||
|
||||
return accessor;
|
||||
file = accessor;
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
public void RenameDirectory(string srcPath, string dstPath)
|
||||
public Result RenameDirectory(string oldPath, string newPath)
|
||||
{
|
||||
FileSystem.RenameDirectory(srcPath, dstPath);
|
||||
return FileSystem.RenameDirectory(oldPath, newPath);
|
||||
}
|
||||
|
||||
public void RenameFile(string srcPath, string dstPath)
|
||||
public Result RenameFile(string oldPath, string newPath)
|
||||
{
|
||||
FileSystem.RenameFile(srcPath, dstPath);
|
||||
return FileSystem.RenameFile(oldPath, newPath);
|
||||
}
|
||||
|
||||
public void DirectoryExists(string path)
|
||||
public bool DirectoryExists(string path)
|
||||
{
|
||||
FileSystem.DirectoryExists(path);
|
||||
return FileSystem.DirectoryExists(path);
|
||||
}
|
||||
|
||||
public void FileExists(string path)
|
||||
public bool FileExists(string path)
|
||||
{
|
||||
FileSystem.FileExists(path);
|
||||
return FileSystem.FileExists(path);
|
||||
}
|
||||
|
||||
public DirectoryEntryType GetEntryType(string path)
|
||||
public Result GetEntryType(out DirectoryEntryType type, string path)
|
||||
{
|
||||
return FileSystem.GetEntryType(path);
|
||||
return FileSystem.GetEntryType(out type, path);
|
||||
}
|
||||
|
||||
public long GetFreeSpaceSize(string path)
|
||||
public Result GetFreeSpaceSize(out long freeSpace, string path)
|
||||
{
|
||||
return FileSystem.GetFreeSpaceSize(path);
|
||||
return FileSystem.GetFreeSpaceSize(out freeSpace, path);
|
||||
}
|
||||
|
||||
public long GetTotalSpaceSize(string path)
|
||||
public Result GetTotalSpaceSize(out long totalSpace, string path)
|
||||
{
|
||||
return FileSystem.GetTotalSpaceSize(path);
|
||||
return FileSystem.GetTotalSpaceSize(out totalSpace, path);
|
||||
}
|
||||
|
||||
public FileTimeStampRaw GetFileTimeStampRaw(string path)
|
||||
public Result GetFileTimeStampRaw(out FileTimeStampRaw timeStamp, string path)
|
||||
{
|
||||
return FileSystem.GetFileTimeStampRaw(path);
|
||||
return FileSystem.GetFileTimeStampRaw(out timeStamp, path);
|
||||
}
|
||||
|
||||
public void Commit()
|
||||
public Result Commit()
|
||||
{
|
||||
if (OpenFiles.Any(x => (x.OpenMode & OpenMode.Write) != 0))
|
||||
{
|
||||
ThrowHelper.ThrowResult(ResultFs.WritableFileOpen);
|
||||
return ResultFs.WritableFileOpen.Log();
|
||||
}
|
||||
|
||||
FileSystem.Commit();
|
||||
return FileSystem.Commit();
|
||||
}
|
||||
|
||||
public void QueryEntry(Span<byte> outBuffer, ReadOnlySpan<byte> inBuffer, string path, QueryId queryId)
|
||||
public Result QueryEntry(Span<byte> outBuffer, ReadOnlySpan<byte> inBuffer, string path, QueryId queryId)
|
||||
{
|
||||
FileSystem.QueryEntry(outBuffer, inBuffer, path, queryId);
|
||||
return FileSystem.QueryEntry(outBuffer, inBuffer, queryId, path);
|
||||
}
|
||||
|
||||
public Result GetCommonMountName(Span<byte> nameBuffer)
|
||||
{
|
||||
if (MountNameGenerator == null) return ResultFs.PreconditionViolation;
|
||||
|
||||
return MountNameGenerator.Generate(nameBuffer);
|
||||
}
|
||||
|
||||
internal void NotifyCloseFile(FileAccessor file)
|
||||
|
@ -1,10 +0,0 @@
|
||||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace LibHac.Fs.Accessors
|
||||
{
|
||||
public interface IAccessLog
|
||||
{
|
||||
void Log(TimeSpan startTime, TimeSpan endTime, int handleId, string message, [CallerMemberName] string caller = "");
|
||||
}
|
||||
}
|
@ -1,80 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace LibHac.Fs
|
||||
{
|
||||
public class AesXtsDirectory : IDirectory
|
||||
{
|
||||
IFileSystem IDirectory.ParentFileSystem => ParentFileSystem;
|
||||
public AesXtsFileSystem ParentFileSystem { get; }
|
||||
|
||||
public string FullPath { get; }
|
||||
public OpenDirectoryMode Mode { get; }
|
||||
|
||||
private IFileSystem BaseFileSystem { get; }
|
||||
private IDirectory BaseDirectory { get; }
|
||||
|
||||
public AesXtsDirectory(AesXtsFileSystem parentFs, IDirectory baseDir, OpenDirectoryMode mode)
|
||||
{
|
||||
ParentFileSystem = parentFs;
|
||||
BaseDirectory = baseDir;
|
||||
Mode = mode;
|
||||
BaseFileSystem = BaseDirectory.ParentFileSystem;
|
||||
FullPath = BaseDirectory.FullPath;
|
||||
}
|
||||
|
||||
public IEnumerable<DirectoryEntry> Read()
|
||||
{
|
||||
foreach (DirectoryEntry entry in BaseDirectory.Read())
|
||||
{
|
||||
if (entry.Type == DirectoryEntryType.Directory)
|
||||
{
|
||||
yield return entry;
|
||||
}
|
||||
else
|
||||
{
|
||||
long size = GetAesXtsFileSize(entry.FullPath);
|
||||
if (size == -1) continue;
|
||||
|
||||
yield return new DirectoryEntry(entry.Name, entry.FullPath, entry.Type, size);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public int GetEntryCount()
|
||||
{
|
||||
return BaseDirectory.GetEntryCount();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads the size of a NAX0 file from its header. Returns -1 on error.
|
||||
/// </summary>
|
||||
/// <param name="path"></param>
|
||||
/// <returns></returns>
|
||||
private long GetAesXtsFileSize(string path)
|
||||
{
|
||||
try
|
||||
{
|
||||
using (IFile file = BaseFileSystem.OpenFile(path, OpenMode.Read))
|
||||
{
|
||||
if (file.GetSize() < 0x50)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
var buffer = new byte[8];
|
||||
|
||||
file.Read(buffer, 0x20);
|
||||
if (BitConverter.ToUInt32(buffer, 0) != 0x3058414E) return 0;
|
||||
|
||||
file.Read(buffer, 0x48);
|
||||
return BitConverter.ToInt64(buffer, 0);
|
||||
}
|
||||
}
|
||||
catch (ArgumentOutOfRangeException)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,242 +0,0 @@
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace LibHac.Fs
|
||||
{
|
||||
public class AesXtsFileSystem : IFileSystem
|
||||
{
|
||||
public int BlockSize { get; }
|
||||
|
||||
private IFileSystem BaseFileSystem { get; }
|
||||
private byte[] KekSource { get; }
|
||||
private byte[] ValidationKey { get; }
|
||||
|
||||
public AesXtsFileSystem(IFileSystem fs, byte[] kekSource, byte[] validationKey, int blockSize)
|
||||
{
|
||||
BaseFileSystem = fs;
|
||||
KekSource = kekSource;
|
||||
ValidationKey = validationKey;
|
||||
BlockSize = blockSize;
|
||||
}
|
||||
|
||||
public AesXtsFileSystem(IFileSystem fs, byte[] keys, int blockSize)
|
||||
{
|
||||
BaseFileSystem = fs;
|
||||
KekSource = keys.AsSpan(0, 0x10).ToArray();
|
||||
ValidationKey = keys.AsSpan(0x10, 0x10).ToArray();
|
||||
BlockSize = blockSize;
|
||||
}
|
||||
|
||||
public void CreateDirectory(string path)
|
||||
{
|
||||
BaseFileSystem.CreateDirectory(path);
|
||||
}
|
||||
|
||||
public void CreateFile(string path, long size, CreateFileOptions options)
|
||||
{
|
||||
CreateFile(path, size, options, new byte[0x20]);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new <see cref="AesXtsFile"/> using the provided key.
|
||||
/// </summary>
|
||||
/// <param name="path">The full path of the file to create.</param>
|
||||
/// <param name="size">The initial size of the created file.</param>
|
||||
/// <param name="options">Flags to control how the file is created.
|
||||
/// Should usually be <see cref="CreateFileOptions.None"/></param>
|
||||
/// <param name="key">The 256-bit key containing a 128-bit data key followed by a 128-bit tweak key.</param>
|
||||
public void CreateFile(string path, long size, CreateFileOptions options, byte[] key)
|
||||
{
|
||||
long containerSize = AesXtsFile.HeaderLength + Util.AlignUp(size, 0x10);
|
||||
BaseFileSystem.CreateFile(path, containerSize, options);
|
||||
|
||||
var header = new AesXtsFileHeader(key, size, path, KekSource, ValidationKey);
|
||||
|
||||
using (IFile baseFile = BaseFileSystem.OpenFile(path, OpenMode.Write))
|
||||
{
|
||||
baseFile.Write(header.ToBytes(false), 0);
|
||||
}
|
||||
}
|
||||
|
||||
public void DeleteDirectory(string path)
|
||||
{
|
||||
BaseFileSystem.DeleteDirectory(path);
|
||||
}
|
||||
|
||||
public void DeleteDirectoryRecursively(string path)
|
||||
{
|
||||
BaseFileSystem.DeleteDirectoryRecursively(path);
|
||||
}
|
||||
|
||||
public void CleanDirectoryRecursively(string path)
|
||||
{
|
||||
BaseFileSystem.CleanDirectoryRecursively(path);
|
||||
}
|
||||
|
||||
public void DeleteFile(string path)
|
||||
{
|
||||
BaseFileSystem.DeleteFile(path);
|
||||
}
|
||||
|
||||
public IDirectory OpenDirectory(string path, OpenDirectoryMode mode)
|
||||
{
|
||||
path = PathTools.Normalize(path);
|
||||
|
||||
IDirectory baseDir = BaseFileSystem.OpenDirectory(path, mode);
|
||||
|
||||
var dir = new AesXtsDirectory(this, baseDir, mode);
|
||||
return dir;
|
||||
}
|
||||
|
||||
public IFile OpenFile(string path, OpenMode mode)
|
||||
{
|
||||
path = PathTools.Normalize(path);
|
||||
|
||||
IFile baseFile = BaseFileSystem.OpenFile(path, mode | OpenMode.Read);
|
||||
var file = new AesXtsFile(mode, baseFile, path, KekSource, ValidationKey, BlockSize);
|
||||
|
||||
file.ToDispose.Add(baseFile);
|
||||
return file;
|
||||
}
|
||||
|
||||
public void RenameDirectory(string srcPath, string dstPath)
|
||||
{
|
||||
srcPath = PathTools.Normalize(srcPath);
|
||||
dstPath = PathTools.Normalize(dstPath);
|
||||
|
||||
BaseFileSystem.RenameDirectory(srcPath, dstPath);
|
||||
|
||||
try
|
||||
{
|
||||
RenameDirectoryImpl(srcPath, dstPath, false);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
RenameDirectoryImpl(srcPath, dstPath, true);
|
||||
BaseFileSystem.RenameDirectory(dstPath, srcPath);
|
||||
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
private void RenameDirectoryImpl(string srcDir, string dstDir, bool doRollback)
|
||||
{
|
||||
IDirectory dir = OpenDirectory(dstDir, OpenDirectoryMode.All);
|
||||
|
||||
foreach (DirectoryEntry entry in dir.Read())
|
||||
{
|
||||
string subSrcPath = $"{srcDir}/{entry.Name}";
|
||||
string subDstPath = $"{dstDir}/{entry.Name}";
|
||||
|
||||
if (entry.Type == DirectoryEntryType.Directory)
|
||||
{
|
||||
RenameDirectoryImpl(subSrcPath, subDstPath, doRollback);
|
||||
}
|
||||
|
||||
if (entry.Type == DirectoryEntryType.File)
|
||||
{
|
||||
if (doRollback)
|
||||
{
|
||||
if (TryReadXtsHeader(subDstPath, subDstPath, out AesXtsFileHeader header))
|
||||
{
|
||||
WriteXtsHeader(header, subDstPath, subSrcPath);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
AesXtsFileHeader header = ReadXtsHeader(subDstPath, subSrcPath);
|
||||
WriteXtsHeader(header, subDstPath, subDstPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void RenameFile(string srcPath, string dstPath)
|
||||
{
|
||||
srcPath = PathTools.Normalize(srcPath);
|
||||
dstPath = PathTools.Normalize(dstPath);
|
||||
|
||||
AesXtsFileHeader header = ReadXtsHeader(srcPath, srcPath);
|
||||
|
||||
BaseFileSystem.RenameFile(srcPath, dstPath);
|
||||
|
||||
try
|
||||
{
|
||||
WriteXtsHeader(header, dstPath, dstPath);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
BaseFileSystem.RenameFile(dstPath, srcPath);
|
||||
WriteXtsHeader(header, srcPath, srcPath);
|
||||
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
public DirectoryEntryType GetEntryType(string path)
|
||||
{
|
||||
return BaseFileSystem.GetEntryType(path);
|
||||
}
|
||||
|
||||
public FileTimeStampRaw GetFileTimeStampRaw(string path)
|
||||
{
|
||||
return BaseFileSystem.GetFileTimeStampRaw(path);
|
||||
}
|
||||
|
||||
public long GetFreeSpaceSize(string path)
|
||||
{
|
||||
return BaseFileSystem.GetFreeSpaceSize(path);
|
||||
}
|
||||
|
||||
public long GetTotalSpaceSize(string path)
|
||||
{
|
||||
return BaseFileSystem.GetTotalSpaceSize(path);
|
||||
}
|
||||
|
||||
public void Commit()
|
||||
{
|
||||
BaseFileSystem.Commit();
|
||||
}
|
||||
|
||||
public void QueryEntry(Span<byte> outBuffer, ReadOnlySpan<byte> inBuffer, string path, QueryId queryId)
|
||||
{
|
||||
BaseFileSystem.QueryEntry(outBuffer, inBuffer, path, queryId);
|
||||
}
|
||||
|
||||
private AesXtsFileHeader ReadXtsHeader(string filePath, string keyPath)
|
||||
{
|
||||
if (!TryReadXtsHeader(filePath, keyPath, out AesXtsFileHeader header))
|
||||
{
|
||||
ThrowHelper.ThrowResult(ResultFs.AesXtsFileHeaderInvalidKeysInRenameFile, "Could not decrypt AES-XTS keys");
|
||||
}
|
||||
|
||||
return header;
|
||||
}
|
||||
|
||||
private bool TryReadXtsHeader(string filePath, string keyPath, out AesXtsFileHeader header)
|
||||
{
|
||||
Debug.Assert(PathTools.IsNormalized(filePath.AsSpan()));
|
||||
Debug.Assert(PathTools.IsNormalized(keyPath.AsSpan()));
|
||||
|
||||
using (IFile file = BaseFileSystem.OpenFile(filePath, OpenMode.Read))
|
||||
{
|
||||
header = new AesXtsFileHeader(file);
|
||||
|
||||
return header.TryDecryptHeader(keyPath, KekSource, ValidationKey);
|
||||
}
|
||||
}
|
||||
|
||||
private void WriteXtsHeader(AesXtsFileHeader header, string filePath, string keyPath)
|
||||
{
|
||||
Debug.Assert(PathTools.IsNormalized(filePath.AsSpan()));
|
||||
Debug.Assert(PathTools.IsNormalized(keyPath.AsSpan()));
|
||||
|
||||
header.EncryptHeader(keyPath, KekSource, ValidationKey);
|
||||
|
||||
using (IFile file = BaseFileSystem.OpenFile(filePath, OpenMode.ReadWrite))
|
||||
{
|
||||
file.Write(header.ToBytes(false), 0, WriteOption.Flush);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,81 +0,0 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
#if CROSS_PLATFORM
|
||||
using System.Runtime.InteropServices;
|
||||
#endif
|
||||
|
||||
namespace LibHac.Fs
|
||||
{
|
||||
public class ConcatenationDirectory : IDirectory
|
||||
{
|
||||
IFileSystem IDirectory.ParentFileSystem => ParentFileSystem;
|
||||
public string FullPath { get; }
|
||||
public OpenDirectoryMode Mode { get; }
|
||||
|
||||
private ConcatenationFileSystem ParentFileSystem { get; }
|
||||
private IDirectory ParentDirectory { get; }
|
||||
|
||||
public ConcatenationDirectory(ConcatenationFileSystem fs, IDirectory parentDirectory, OpenDirectoryMode mode)
|
||||
{
|
||||
ParentFileSystem = fs;
|
||||
ParentDirectory = parentDirectory;
|
||||
Mode = mode;
|
||||
FullPath = parentDirectory.FullPath;
|
||||
}
|
||||
|
||||
public IEnumerable<DirectoryEntry> Read()
|
||||
{
|
||||
foreach (DirectoryEntry entry in ParentDirectory.Read())
|
||||
{
|
||||
bool isSplit = IsConcatenationFile(entry);
|
||||
|
||||
if (!CanReturnEntry(entry, isSplit)) continue;
|
||||
|
||||
if (isSplit)
|
||||
{
|
||||
entry.Type = DirectoryEntryType.File;
|
||||
entry.Size = ParentFileSystem.GetConcatenationFileSize(entry.FullPath);
|
||||
entry.Attributes = NxFileAttributes.None;
|
||||
}
|
||||
|
||||
yield return entry;
|
||||
}
|
||||
}
|
||||
|
||||
public int GetEntryCount()
|
||||
{
|
||||
int count = 0;
|
||||
|
||||
foreach (DirectoryEntry entry in ParentDirectory.Read())
|
||||
{
|
||||
bool isSplit = IsConcatenationFile(entry);
|
||||
|
||||
if (CanReturnEntry(entry, isSplit)) count++;
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
private bool CanReturnEntry(DirectoryEntry entry, bool isSplit)
|
||||
{
|
||||
return Mode.HasFlag(OpenDirectoryMode.Files) && (entry.Type == DirectoryEntryType.File || isSplit) ||
|
||||
Mode.HasFlag(OpenDirectoryMode.Directories) && entry.Type == DirectoryEntryType.Directory && !isSplit;
|
||||
}
|
||||
|
||||
private bool IsConcatenationFile(DirectoryEntry entry)
|
||||
{
|
||||
#if CROSS_PLATFORM
|
||||
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
||||
{
|
||||
return ConcatenationFileSystem.HasConcatenationFileAttribute(entry.Attributes);
|
||||
}
|
||||
else
|
||||
{
|
||||
return ParentFileSystem.IsConcatenationFile(entry.FullPath);
|
||||
}
|
||||
#else
|
||||
return ConcatenationFileSystem.HasConcatenationFileAttribute(entry.Attributes);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
@ -1,325 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
#if CROSS_PLATFORM
|
||||
using System.Runtime.InteropServices;
|
||||
#endif
|
||||
|
||||
namespace LibHac.Fs
|
||||
{
|
||||
/// <summary>
|
||||
/// An <see cref="IFileSystem"/> that stores large files as smaller, separate sub-files.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This filesystem is mainly used to allow storing large files on filesystems that have low
|
||||
/// limits on file size such as FAT filesystems. The underlying base filesystem must have
|
||||
/// support for the "Archive" file attribute found in FAT or NTFS filesystems.
|
||||
///
|
||||
/// A <see cref="ConcatenationFileSystem"/> may contain both standard files or Concatenation files.
|
||||
/// If a directory has the archive attribute set, its contents will be concatenated and treated
|
||||
/// as a single file. These sub-files must follow the naming scheme "00", "01", "02", ...
|
||||
/// Each sub-file except the final one must have the size <see cref="SubFileSize"/> that was specified
|
||||
/// at the creation of the <see cref="ConcatenationFileSystem"/>.
|
||||
/// </remarks>
|
||||
public class ConcatenationFileSystem : IFileSystem
|
||||
{
|
||||
private const long DefaultSubFileSize = 0xFFFF0000; // Hard-coded value used by FS
|
||||
private IAttributeFileSystem BaseFileSystem { get; }
|
||||
private long SubFileSize { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new <see cref="ConcatenationFileSystem"/>.
|
||||
/// </summary>
|
||||
/// <param name="baseFileSystem">The base <see cref="IAttributeFileSystem"/> for the
|
||||
/// new <see cref="ConcatenationFileSystem"/>.</param>
|
||||
public ConcatenationFileSystem(IAttributeFileSystem baseFileSystem) : this(baseFileSystem, DefaultSubFileSize) { }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new <see cref="ConcatenationFileSystem"/>.
|
||||
/// </summary>
|
||||
/// <param name="baseFileSystem">The base <see cref="IAttributeFileSystem"/> for the
|
||||
/// new <see cref="ConcatenationFileSystem"/>.</param>
|
||||
/// <param name="subFileSize">The size of each sub-file. Once a file exceeds this size, a new sub-file will be created</param>
|
||||
public ConcatenationFileSystem(IAttributeFileSystem baseFileSystem, long subFileSize)
|
||||
{
|
||||
BaseFileSystem = baseFileSystem;
|
||||
SubFileSize = subFileSize;
|
||||
}
|
||||
|
||||
// .NET Core on platforms other than Windows doesn't support getting the
|
||||
// archive flag in FAT file systems. Try to work around that for now for reading,
|
||||
// but writing still won't work properly on those platforms
|
||||
internal bool IsConcatenationFile(string path)
|
||||
{
|
||||
#if CROSS_PLATFORM
|
||||
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
||||
{
|
||||
return HasConcatenationFileAttribute(BaseFileSystem.GetFileAttributes(path));
|
||||
}
|
||||
else
|
||||
{
|
||||
return IsConcatenationFileHeuristic(path);
|
||||
}
|
||||
#else
|
||||
return HasConcatenationFileAttribute(BaseFileSystem.GetFileAttributes(path));
|
||||
#endif
|
||||
}
|
||||
|
||||
#if CROSS_PLATFORM
|
||||
private bool IsConcatenationFileHeuristic(string path)
|
||||
{
|
||||
if (BaseFileSystem.GetEntryType(path) != DirectoryEntryType.Directory) return false;
|
||||
|
||||
if (BaseFileSystem.GetEntryType(PathTools.Combine(path, "00")) != DirectoryEntryType.File) return false;
|
||||
|
||||
if (BaseFileSystem.OpenDirectory(path, OpenDirectoryMode.Directories).GetEntryCount() > 0) return false;
|
||||
|
||||
// Should be enough checks to avoid most false positives. Maybe
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
|
||||
internal static bool HasConcatenationFileAttribute(NxFileAttributes attributes)
|
||||
{
|
||||
return (attributes & NxFileAttributes.Directory) != 0 && (attributes & NxFileAttributes.Archive) != 0;
|
||||
}
|
||||
|
||||
private void SetConcatenationFileAttribute(string path)
|
||||
{
|
||||
NxFileAttributes attributes = BaseFileSystem.GetFileAttributes(path);
|
||||
attributes |= NxFileAttributes.Archive;
|
||||
BaseFileSystem.SetFileAttributes(path, attributes);
|
||||
}
|
||||
|
||||
public void CreateDirectory(string path)
|
||||
{
|
||||
path = PathTools.Normalize(path);
|
||||
string parent = PathTools.GetParentDirectory(path);
|
||||
|
||||
if (IsConcatenationFile(parent))
|
||||
{
|
||||
ThrowHelper.ThrowResult(ResultFs.PathNotFound,
|
||||
"Cannot create a directory inside of a concatenation file");
|
||||
}
|
||||
|
||||
BaseFileSystem.CreateDirectory(path);
|
||||
}
|
||||
|
||||
public void CreateFile(string path, long size, CreateFileOptions options)
|
||||
{
|
||||
path = PathTools.Normalize(path);
|
||||
|
||||
CreateFileOptions newOptions = options & ~CreateFileOptions.CreateConcatenationFile;
|
||||
|
||||
if (!options.HasFlag(CreateFileOptions.CreateConcatenationFile))
|
||||
{
|
||||
BaseFileSystem.CreateFile(path, size, newOptions);
|
||||
return;
|
||||
}
|
||||
|
||||
// A concatenation file directory can't contain normal files
|
||||
string parentDir = PathTools.GetParentDirectory(path);
|
||||
|
||||
if (IsConcatenationFile(parentDir))
|
||||
{
|
||||
ThrowHelper.ThrowResult(ResultFs.PathNotFound,
|
||||
"Cannot create a concatenation file inside of a concatenation file");
|
||||
}
|
||||
|
||||
BaseFileSystem.CreateDirectory(path);
|
||||
SetConcatenationFileAttribute(path);
|
||||
|
||||
long remaining = size;
|
||||
|
||||
for (int i = 0; remaining > 0; i++)
|
||||
{
|
||||
long fileSize = Math.Min(SubFileSize, remaining);
|
||||
string fileName = GetSubFilePath(path, i);
|
||||
|
||||
BaseFileSystem.CreateFile(fileName, fileSize, CreateFileOptions.None);
|
||||
|
||||
remaining -= fileSize;
|
||||
}
|
||||
}
|
||||
|
||||
public void DeleteDirectory(string path)
|
||||
{
|
||||
path = PathTools.Normalize(path);
|
||||
|
||||
if (IsConcatenationFile(path))
|
||||
{
|
||||
ThrowHelper.ThrowResult(ResultFs.PathNotFound);
|
||||
}
|
||||
|
||||
BaseFileSystem.DeleteDirectory(path);
|
||||
}
|
||||
|
||||
public void DeleteDirectoryRecursively(string path)
|
||||
{
|
||||
path = PathTools.Normalize(path);
|
||||
|
||||
if (IsConcatenationFile(path)) ThrowHelper.ThrowResult(ResultFs.PathNotFound);
|
||||
|
||||
BaseFileSystem.DeleteDirectoryRecursively(path);
|
||||
}
|
||||
|
||||
public void CleanDirectoryRecursively(string path)
|
||||
{
|
||||
path = PathTools.Normalize(path);
|
||||
|
||||
if (IsConcatenationFile(path)) ThrowHelper.ThrowResult(ResultFs.PathNotFound);
|
||||
|
||||
BaseFileSystem.CleanDirectoryRecursively(path);
|
||||
}
|
||||
|
||||
public void DeleteFile(string path)
|
||||
{
|
||||
path = PathTools.Normalize(path);
|
||||
|
||||
if (!IsConcatenationFile(path))
|
||||
{
|
||||
BaseFileSystem.DeleteFile(path);
|
||||
}
|
||||
|
||||
int count = GetSubFileCount(path);
|
||||
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
BaseFileSystem.DeleteFile(GetSubFilePath(path, i));
|
||||
}
|
||||
|
||||
BaseFileSystem.DeleteDirectory(path);
|
||||
}
|
||||
|
||||
public IDirectory OpenDirectory(string path, OpenDirectoryMode mode)
|
||||
{
|
||||
path = PathTools.Normalize(path);
|
||||
|
||||
if (IsConcatenationFile(path))
|
||||
{
|
||||
ThrowHelper.ThrowResult(ResultFs.PathNotFound);
|
||||
}
|
||||
|
||||
IDirectory parentDir = BaseFileSystem.OpenDirectory(path, OpenDirectoryMode.All);
|
||||
var dir = new ConcatenationDirectory(this, parentDir, mode);
|
||||
return dir;
|
||||
}
|
||||
|
||||
public IFile OpenFile(string path, OpenMode mode)
|
||||
{
|
||||
path = PathTools.Normalize(path);
|
||||
|
||||
if (!IsConcatenationFile(path))
|
||||
{
|
||||
return BaseFileSystem.OpenFile(path, mode);
|
||||
}
|
||||
|
||||
int fileCount = GetSubFileCount(path);
|
||||
|
||||
var files = new List<IFile>();
|
||||
|
||||
for (int i = 0; i < fileCount; i++)
|
||||
{
|
||||
string filePath = GetSubFilePath(path, i);
|
||||
IFile file = BaseFileSystem.OpenFile(filePath, mode);
|
||||
files.Add(file);
|
||||
}
|
||||
|
||||
return new ConcatenationFile(BaseFileSystem, path, files, SubFileSize, mode);
|
||||
}
|
||||
|
||||
public void RenameDirectory(string srcPath, string dstPath)
|
||||
{
|
||||
srcPath = PathTools.Normalize(srcPath);
|
||||
dstPath = PathTools.Normalize(dstPath);
|
||||
|
||||
if (IsConcatenationFile(srcPath))
|
||||
{
|
||||
ThrowHelper.ThrowResult(ResultFs.PathNotFound);
|
||||
}
|
||||
|
||||
BaseFileSystem.RenameDirectory(srcPath, dstPath);
|
||||
}
|
||||
|
||||
public void RenameFile(string srcPath, string dstPath)
|
||||
{
|
||||
srcPath = PathTools.Normalize(srcPath);
|
||||
dstPath = PathTools.Normalize(dstPath);
|
||||
|
||||
if (IsConcatenationFile(srcPath))
|
||||
{
|
||||
BaseFileSystem.RenameDirectory(srcPath, dstPath);
|
||||
}
|
||||
else
|
||||
{
|
||||
BaseFileSystem.RenameFile(srcPath, dstPath);
|
||||
}
|
||||
}
|
||||
|
||||
public DirectoryEntryType GetEntryType(string path)
|
||||
{
|
||||
path = PathTools.Normalize(path);
|
||||
|
||||
if (IsConcatenationFile(path)) return DirectoryEntryType.File;
|
||||
|
||||
return BaseFileSystem.GetEntryType(path);
|
||||
}
|
||||
|
||||
public long GetFreeSpaceSize(string path)
|
||||
{
|
||||
return BaseFileSystem.GetFreeSpaceSize(path);
|
||||
}
|
||||
|
||||
public long GetTotalSpaceSize(string path)
|
||||
{
|
||||
return BaseFileSystem.GetTotalSpaceSize(path);
|
||||
}
|
||||
|
||||
public FileTimeStampRaw GetFileTimeStampRaw(string path)
|
||||
{
|
||||
return BaseFileSystem.GetFileTimeStampRaw(path);
|
||||
}
|
||||
|
||||
public void Commit()
|
||||
{
|
||||
BaseFileSystem.Commit();
|
||||
}
|
||||
|
||||
public void QueryEntry(Span<byte> outBuffer, ReadOnlySpan<byte> inBuffer, string path, QueryId queryId)
|
||||
{
|
||||
if (queryId != QueryId.MakeConcatFile) ThrowHelper.ThrowResult(ResultFs.UnsupportedOperationInConcatFsQueryEntry);
|
||||
|
||||
SetConcatenationFileAttribute(path);
|
||||
}
|
||||
|
||||
private int GetSubFileCount(string dirPath)
|
||||
{
|
||||
int count = 0;
|
||||
|
||||
while (BaseFileSystem.FileExists(GetSubFilePath(dirPath, count)))
|
||||
{
|
||||
count++;
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
internal static string GetSubFilePath(string dirPath, int index)
|
||||
{
|
||||
return $"{dirPath}/{index:D2}";
|
||||
}
|
||||
|
||||
internal long GetConcatenationFileSize(string path)
|
||||
{
|
||||
int fileCount = GetSubFileCount(path);
|
||||
long size = 0;
|
||||
|
||||
for (int i = 0; i < fileCount; i++)
|
||||
{
|
||||
size += BaseFileSystem.GetFileSize(GetSubFilePath(path, i));
|
||||
}
|
||||
|
||||
return size;
|
||||
}
|
||||
}
|
||||
}
|
69
src/LibHac/Fs/ContentStorage.cs
Normal file
69
src/LibHac/Fs/ContentStorage.cs
Normal file
@ -0,0 +1,69 @@
|
||||
using System;
|
||||
using LibHac.Common;
|
||||
using LibHac.FsService;
|
||||
|
||||
namespace LibHac.Fs
|
||||
{
|
||||
public static class ContentStorage
|
||||
{
|
||||
private static readonly U8String ContentStorageMountNameSystem = new U8String("@SystemContent");
|
||||
private static readonly U8String ContentStorageMountNameUser = new U8String("@UserContent");
|
||||
private static readonly U8String ContentStorageMountNameSdCard = new U8String("@SdCardContent");
|
||||
|
||||
public static Result MountContentStorage(this FileSystemClient fs, ContentStorageId storageId)
|
||||
{
|
||||
return MountContentStorage(fs, GetContentStorageMountName(storageId), storageId);
|
||||
}
|
||||
|
||||
public static Result MountContentStorage(this FileSystemClient fs, U8Span mountName, ContentStorageId storageId)
|
||||
{
|
||||
Result rc = MountHelpers.CheckMountNameAcceptingReservedMountName(mountName);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
IFileSystemProxy fsProxy = fs.GetFileSystemProxyServiceObject();
|
||||
|
||||
rc = fsProxy.OpenContentStorageFileSystem(out IFileSystem contentFs, storageId);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
var mountNameGenerator = new ContentStorageCommonMountNameGenerator(storageId);
|
||||
|
||||
return fs.Register(mountName, contentFs, mountNameGenerator);
|
||||
}
|
||||
|
||||
public static U8String GetContentStorageMountName(ContentStorageId storageId)
|
||||
{
|
||||
switch (storageId)
|
||||
{
|
||||
case ContentStorageId.System:
|
||||
return ContentStorageMountNameSystem;
|
||||
case ContentStorageId.User:
|
||||
return ContentStorageMountNameUser;
|
||||
case ContentStorageId.SdCard:
|
||||
return ContentStorageMountNameSdCard;
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException(nameof(storageId), storageId, null);
|
||||
}
|
||||
}
|
||||
|
||||
private class ContentStorageCommonMountNameGenerator : ICommonMountNameGenerator
|
||||
{
|
||||
private ContentStorageId StorageId { get; }
|
||||
|
||||
public ContentStorageCommonMountNameGenerator(ContentStorageId storageId)
|
||||
{
|
||||
StorageId = storageId;
|
||||
}
|
||||
|
||||
public Result Generate(Span<byte> nameBuffer)
|
||||
{
|
||||
U8String mountName = GetContentStorageMountName(StorageId);
|
||||
|
||||
int length = StringUtils.Copy(nameBuffer, mountName);
|
||||
nameBuffer[length] = (byte)':';
|
||||
nameBuffer[length + 1] = 0;
|
||||
|
||||
return Result.Success;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
34
src/LibHac/Fs/CustomStorage.cs
Normal file
34
src/LibHac/Fs/CustomStorage.cs
Normal file
@ -0,0 +1,34 @@
|
||||
using System;
|
||||
using LibHac.Common;
|
||||
using LibHac.FsService;
|
||||
|
||||
namespace LibHac.Fs
|
||||
{
|
||||
public static class CustomStorage
|
||||
{
|
||||
public static Result MountCustomStorage(this FileSystemClient fs, U8Span mountName, CustomStorageId storageId)
|
||||
{
|
||||
Result rc = MountHelpers.CheckMountName(mountName);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
IFileSystemProxy fsProxy = fs.GetFileSystemProxyServiceObject();
|
||||
|
||||
rc = fsProxy.OpenCustomStorageFileSystem(out IFileSystem customFs, storageId);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
return fs.Register(mountName, customFs);
|
||||
}
|
||||
|
||||
public static string GetCustomStorageDirectoryName(CustomStorageId storageId)
|
||||
{
|
||||
switch (storageId)
|
||||
{
|
||||
case CustomStorageId.User:
|
||||
case CustomStorageId.SdCard:
|
||||
return "CustomStorage0";
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException(nameof(storageId), storageId, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,8 +1,11 @@
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
using LibHac.Common;
|
||||
using LibHac.FsSystem;
|
||||
|
||||
namespace LibHac.Fs
|
||||
{
|
||||
public class DirectoryEntry
|
||||
public class DirectoryEntryEx
|
||||
{
|
||||
public string Name { get; set; }
|
||||
public string FullPath { get; set; }
|
||||
@ -10,7 +13,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 +22,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 +41,7 @@ namespace LibHac.Fs
|
||||
}
|
||||
|
||||
[Flags]
|
||||
public enum NxFileAttributes
|
||||
public enum NxFileAttributes : byte
|
||||
{
|
||||
None = 0,
|
||||
Directory = 1 << 0,
|
||||
|
@ -1,6 +1,7 @@
|
||||
using System;
|
||||
using LibHac.Fs.Accessors;
|
||||
|
||||
namespace LibHac.Fs.Accessors
|
||||
namespace LibHac.Fs
|
||||
{
|
||||
public struct DirectoryHandle : IDisposable
|
||||
{
|
||||
@ -11,11 +12,11 @@ namespace LibHac.Fs.Accessors
|
||||
Directory = directory;
|
||||
}
|
||||
|
||||
public int GetId() => Directory.GetHashCode();
|
||||
public int GetId() => Directory?.GetHashCode() ?? 0;
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Directory.Parent.FsManager.CloseDirectory(this);
|
||||
Directory.Parent.FsClient.CloseDirectory(this);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,59 +0,0 @@
|
||||
using System;
|
||||
|
||||
namespace LibHac.Fs
|
||||
{
|
||||
public class DirectorySaveDataFile : FileBase
|
||||
{
|
||||
private IFile BaseFile { get; }
|
||||
private DirectorySaveDataFileSystem ParentFs { get; }
|
||||
private object DisposeLocker { get; } = new object();
|
||||
|
||||
public DirectorySaveDataFile(DirectorySaveDataFileSystem parentFs, IFile baseFile)
|
||||
{
|
||||
ParentFs = parentFs;
|
||||
BaseFile = baseFile;
|
||||
Mode = BaseFile.Mode;
|
||||
ToDispose.Add(BaseFile);
|
||||
}
|
||||
|
||||
public override int Read(Span<byte> destination, long offset, ReadOption options)
|
||||
{
|
||||
return BaseFile.Read(destination, offset, options);
|
||||
}
|
||||
|
||||
public override void Write(ReadOnlySpan<byte> source, long offset, WriteOption options)
|
||||
{
|
||||
BaseFile.Write(source, offset, options);
|
||||
}
|
||||
|
||||
public override void Flush()
|
||||
{
|
||||
BaseFile.Flush();
|
||||
}
|
||||
|
||||
public override long GetSize()
|
||||
{
|
||||
return BaseFile.GetSize();
|
||||
}
|
||||
|
||||
public override void SetSize(long size)
|
||||
{
|
||||
BaseFile.SetSize(size);
|
||||
}
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
lock (DisposeLocker)
|
||||
{
|
||||
if (IsDisposed) return;
|
||||
|
||||
base.Dispose(disposing);
|
||||
|
||||
if (Mode.HasFlag(OpenMode.Write))
|
||||
{
|
||||
ParentFs.NotifyCloseWritableFile();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,222 +0,0 @@
|
||||
using System;
|
||||
|
||||
namespace LibHac.Fs
|
||||
{
|
||||
public class DirectorySaveDataFileSystem : IFileSystem
|
||||
{
|
||||
private const string CommittedDir = "/0/";
|
||||
private const string WorkingDir = "/1/";
|
||||
private const string SyncDir = "/_/";
|
||||
|
||||
private IFileSystem BaseFs { get; }
|
||||
private object Locker { get; } = new object();
|
||||
private int OpenWritableFileCount { get; set; }
|
||||
|
||||
public DirectorySaveDataFileSystem(IFileSystem baseFileSystem)
|
||||
{
|
||||
BaseFs = baseFileSystem;
|
||||
|
||||
if (!BaseFs.DirectoryExists(WorkingDir))
|
||||
{
|
||||
BaseFs.CreateDirectory(WorkingDir);
|
||||
BaseFs.EnsureDirectoryExists(CommittedDir);
|
||||
}
|
||||
|
||||
if (BaseFs.DirectoryExists(CommittedDir))
|
||||
{
|
||||
SynchronizeDirectory(WorkingDir, CommittedDir);
|
||||
}
|
||||
else
|
||||
{
|
||||
SynchronizeDirectory(SyncDir, WorkingDir);
|
||||
BaseFs.RenameDirectory(SyncDir, CommittedDir);
|
||||
}
|
||||
}
|
||||
|
||||
public void CreateDirectory(string path)
|
||||
{
|
||||
string fullPath = GetFullPath(PathTools.Normalize(path));
|
||||
|
||||
lock (Locker)
|
||||
{
|
||||
BaseFs.CreateDirectory(fullPath);
|
||||
}
|
||||
}
|
||||
|
||||
public void CreateFile(string path, long size, CreateFileOptions options)
|
||||
{
|
||||
string fullPath = GetFullPath(PathTools.Normalize(path));
|
||||
|
||||
lock (Locker)
|
||||
{
|
||||
BaseFs.CreateFile(fullPath, size, options);
|
||||
}
|
||||
}
|
||||
|
||||
public void DeleteDirectory(string path)
|
||||
{
|
||||
string fullPath = GetFullPath(PathTools.Normalize(path));
|
||||
|
||||
lock (Locker)
|
||||
{
|
||||
BaseFs.DeleteDirectory(fullPath);
|
||||
}
|
||||
}
|
||||
|
||||
public void DeleteDirectoryRecursively(string path)
|
||||
{
|
||||
string fullPath = GetFullPath(PathTools.Normalize(path));
|
||||
|
||||
lock (Locker)
|
||||
{
|
||||
BaseFs.DeleteDirectoryRecursively(fullPath);
|
||||
}
|
||||
}
|
||||
|
||||
public void CleanDirectoryRecursively(string path)
|
||||
{
|
||||
string fullPath = GetFullPath(PathTools.Normalize(path));
|
||||
|
||||
lock (Locker)
|
||||
{
|
||||
BaseFs.CleanDirectoryRecursively(fullPath);
|
||||
}
|
||||
}
|
||||
|
||||
public void DeleteFile(string path)
|
||||
{
|
||||
string fullPath = GetFullPath(PathTools.Normalize(path));
|
||||
|
||||
lock (Locker)
|
||||
{
|
||||
BaseFs.DeleteFile(fullPath);
|
||||
}
|
||||
}
|
||||
|
||||
public IDirectory OpenDirectory(string path, OpenDirectoryMode mode)
|
||||
{
|
||||
string fullPath = GetFullPath(PathTools.Normalize(path));
|
||||
|
||||
lock (Locker)
|
||||
{
|
||||
return BaseFs.OpenDirectory(fullPath, mode);
|
||||
}
|
||||
}
|
||||
|
||||
public IFile OpenFile(string path, OpenMode mode)
|
||||
{
|
||||
string fullPath = GetFullPath(PathTools.Normalize(path));
|
||||
|
||||
lock (Locker)
|
||||
{
|
||||
IFile baseFile = BaseFs.OpenFile(fullPath, mode);
|
||||
var file = new DirectorySaveDataFile(this, baseFile);
|
||||
|
||||
if (mode.HasFlag(OpenMode.Write))
|
||||
{
|
||||
OpenWritableFileCount++;
|
||||
}
|
||||
|
||||
return file;
|
||||
}
|
||||
}
|
||||
|
||||
public void RenameDirectory(string srcPath, string dstPath)
|
||||
{
|
||||
string fullSrcPath = GetFullPath(PathTools.Normalize(srcPath));
|
||||
string fullDstPath = GetFullPath(PathTools.Normalize(dstPath));
|
||||
|
||||
lock (Locker)
|
||||
{
|
||||
BaseFs.RenameDirectory(fullSrcPath, fullDstPath);
|
||||
}
|
||||
}
|
||||
|
||||
public void RenameFile(string srcPath, string dstPath)
|
||||
{
|
||||
string fullSrcPath = GetFullPath(PathTools.Normalize(srcPath));
|
||||
string fullDstPath = GetFullPath(PathTools.Normalize(dstPath));
|
||||
|
||||
lock (Locker)
|
||||
{
|
||||
BaseFs.RenameFile(fullSrcPath, fullDstPath);
|
||||
}
|
||||
}
|
||||
|
||||
public DirectoryEntryType GetEntryType(string path)
|
||||
{
|
||||
string fullPath = GetFullPath(PathTools.Normalize(path));
|
||||
|
||||
lock (Locker)
|
||||
{
|
||||
return BaseFs.GetEntryType(fullPath);
|
||||
}
|
||||
}
|
||||
|
||||
public long GetFreeSpaceSize(string path)
|
||||
{
|
||||
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()
|
||||
{
|
||||
if (OpenWritableFileCount > 0)
|
||||
{
|
||||
ThrowHelper.ThrowResult(ResultFs.WritableFileOpen,
|
||||
"All files must be closed before commiting save data.");
|
||||
}
|
||||
|
||||
SynchronizeDirectory(SyncDir, WorkingDir);
|
||||
|
||||
BaseFs.DeleteDirectoryRecursively(CommittedDir);
|
||||
|
||||
BaseFs.RenameDirectory(SyncDir, CommittedDir);
|
||||
}
|
||||
|
||||
public void QueryEntry(Span<byte> outBuffer, ReadOnlySpan<byte> inBuffer, string path, QueryId queryId)
|
||||
{
|
||||
ThrowHelper.ThrowResult(ResultFs.NotImplemented);
|
||||
}
|
||||
|
||||
private string GetFullPath(string path)
|
||||
{
|
||||
return PathTools.Normalize(PathTools.Combine(WorkingDir, path));
|
||||
}
|
||||
|
||||
private void SynchronizeDirectory(string dest, string src)
|
||||
{
|
||||
if (BaseFs.DirectoryExists(dest))
|
||||
{
|
||||
BaseFs.DeleteDirectoryRecursively(dest);
|
||||
}
|
||||
|
||||
BaseFs.CreateDirectory(dest);
|
||||
|
||||
IDirectory sourceDir = BaseFs.OpenDirectory(src, OpenDirectoryMode.All);
|
||||
IDirectory destDir = BaseFs.OpenDirectory(dest, OpenDirectoryMode.All);
|
||||
|
||||
sourceDir.CopyDirectory(destDir);
|
||||
}
|
||||
|
||||
internal void NotifyCloseWritableFile()
|
||||
{
|
||||
lock (Locker)
|
||||
{
|
||||
OpenWritableFileCount--;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
65
src/LibHac/Fs/ExternalKeys.cs
Normal file
65
src/LibHac/Fs/ExternalKeys.cs
Normal file
@ -0,0 +1,65 @@
|
||||
using LibHac.Common;
|
||||
using LibHac.FsService;
|
||||
using LibHac.FsSystem;
|
||||
using LibHac.Ncm;
|
||||
using LibHac.Spl;
|
||||
|
||||
namespace LibHac.Fs
|
||||
{
|
||||
public static class ExternalKeys
|
||||
{
|
||||
public static Result GetRightsId(this FileSystemClient fs, out RightsId rightsId, TitleId programId,
|
||||
StorageId storageId)
|
||||
{
|
||||
IFileSystemProxy fsProxy = fs.GetFileSystemProxyServiceObject();
|
||||
|
||||
return fsProxy.GetRightsId(out rightsId, programId, storageId);
|
||||
}
|
||||
|
||||
public static Result GetRightsId(this FileSystemClient fs, out RightsId rightsId, U8Span path)
|
||||
{
|
||||
rightsId = default;
|
||||
|
||||
IFileSystemProxy fsProxy = fs.GetFileSystemProxyServiceObject();
|
||||
|
||||
Result rc = FsPath.FromSpan(out FsPath fsPath, path);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
return fsProxy.GetRightsIdByPath(out rightsId, ref fsPath);
|
||||
}
|
||||
|
||||
public static Result GetRightsId(this FileSystemClient fs, out RightsId rightsId, out byte keyGeneration, U8Span path)
|
||||
{
|
||||
rightsId = default;
|
||||
keyGeneration = default;
|
||||
|
||||
IFileSystemProxy fsProxy = fs.GetFileSystemProxyServiceObject();
|
||||
|
||||
Result rc = FsPath.FromSpan(out FsPath fsPath, path);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
return fsProxy.GetRightsIdAndKeyGenerationByPath(out rightsId, out keyGeneration, ref fsPath);
|
||||
}
|
||||
|
||||
public static Result RegisterExternalKey(this FileSystemClient fs, ref RightsId rightsId, ref AccessKey key)
|
||||
{
|
||||
IFileSystemProxy fsProxy = fs.GetFileSystemProxyServiceObject();
|
||||
|
||||
return fsProxy.RegisterExternalKey(ref rightsId, ref key);
|
||||
}
|
||||
|
||||
public static Result UnregisterExternalKey(this FileSystemClient fs, ref RightsId rightsId)
|
||||
{
|
||||
IFileSystemProxy fsProxy = fs.GetFileSystemProxyServiceObject();
|
||||
|
||||
return fsProxy.UnregisterExternalKey(ref rightsId);
|
||||
}
|
||||
|
||||
public static Result UnregisterAllExternalKey(this FileSystemClient fs)
|
||||
{
|
||||
IFileSystemProxy fsProxy = fs.GetFileSystemProxyServiceObject();
|
||||
|
||||
return fsProxy.UnregisterAllExternalKey();
|
||||
}
|
||||
}
|
||||
}
|
@ -1,106 +1,148 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
|
||||
namespace LibHac.Fs
|
||||
{
|
||||
public abstract class FileBase : IFile
|
||||
{
|
||||
protected bool IsDisposed { get; private set; }
|
||||
internal List<IDisposable> ToDispose { get; } = new List<IDisposable>();
|
||||
// 0 = not disposed; 1 = disposed
|
||||
private int _disposedState;
|
||||
private bool IsDisposed => _disposedState != 0;
|
||||
|
||||
public abstract int Read(Span<byte> destination, long offset, ReadOption options);
|
||||
public abstract void Write(ReadOnlySpan<byte> source, long offset, WriteOption options);
|
||||
public abstract void Flush();
|
||||
public abstract long GetSize();
|
||||
public abstract void SetSize(long size);
|
||||
protected abstract Result ReadImpl(out long bytesRead, long offset, Span<byte> destination, ReadOption options);
|
||||
protected abstract Result WriteImpl(long offset, ReadOnlySpan<byte> source, WriteOption options);
|
||||
protected abstract Result FlushImpl();
|
||||
protected abstract Result SetSizeImpl(long size);
|
||||
protected abstract Result GetSizeImpl(out long size);
|
||||
|
||||
public OpenMode Mode { get; protected set; }
|
||||
|
||||
protected int ValidateReadParamsAndGetSize(ReadOnlySpan<byte> span, long offset)
|
||||
protected virtual Result OperateRangeImpl(Span<byte> outBuffer, OperationId operationId, long offset, long size,
|
||||
ReadOnlySpan<byte> inBuffer)
|
||||
{
|
||||
if (IsDisposed) throw new ObjectDisposedException(null);
|
||||
|
||||
if ((Mode & OpenMode.Read) == 0) ThrowHelper.ThrowResult(ResultFs.InvalidOpenModeForRead, "File does not allow reading.");
|
||||
if (span == null) throw new ArgumentNullException(nameof(span));
|
||||
if (offset < 0) ThrowHelper.ThrowResult(ResultFs.ValueOutOfRange, "Offset must be non-negative.");
|
||||
|
||||
long fileSize = GetSize();
|
||||
int size = span.Length;
|
||||
|
||||
if (offset > fileSize) ThrowHelper.ThrowResult(ResultFs.ValueOutOfRange, "Offset must be less than the file size.");
|
||||
|
||||
return (int)Math.Min(fileSize - offset, size);
|
||||
return ResultFs.NotImplemented.Log();
|
||||
}
|
||||
|
||||
protected void ValidateWriteParams(ReadOnlySpan<byte> span, long offset)
|
||||
public Result Read(out long bytesRead, long offset, Span<byte> destination, ReadOption options)
|
||||
{
|
||||
if (IsDisposed) throw new ObjectDisposedException(null);
|
||||
bytesRead = default;
|
||||
|
||||
if ((Mode & OpenMode.Write) == 0) ThrowHelper.ThrowResult(ResultFs.InvalidOpenModeForWrite, "File does not allow writing.");
|
||||
if (IsDisposed) return ResultFs.PreconditionViolation.Log();
|
||||
|
||||
if (span == null) throw new ArgumentNullException(nameof(span));
|
||||
if (offset < 0) ThrowHelper.ThrowResult(ResultFs.ValueOutOfRange, "Offset must be non-negative.");
|
||||
if (destination.Length == 0) return Result.Success;
|
||||
if (offset < 0) return ResultFs.ValueOutOfRange.Log();
|
||||
|
||||
long fileSize = GetSize();
|
||||
int size = span.Length;
|
||||
return ReadImpl(out bytesRead, offset, destination, options);
|
||||
}
|
||||
|
||||
if (offset + size > fileSize)
|
||||
public Result Write(long offset, ReadOnlySpan<byte> source, WriteOption options)
|
||||
{
|
||||
if (IsDisposed) return ResultFs.PreconditionViolation.Log();
|
||||
|
||||
if (source.Length == 0)
|
||||
{
|
||||
if ((Mode & OpenMode.Append) == 0)
|
||||
if (options.HasFlag(WriteOption.Flush))
|
||||
{
|
||||
ThrowHelper.ThrowResult(ResultFs.AllowAppendRequiredForImplicitExtension);
|
||||
return Flush();
|
||||
}
|
||||
|
||||
SetSize(offset + size);
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
if (offset < 0) return ResultFs.ValueOutOfRange.Log();
|
||||
|
||||
return WriteImpl(offset, source, options);
|
||||
}
|
||||
|
||||
public Result Flush()
|
||||
{
|
||||
if (IsDisposed) return ResultFs.PreconditionViolation.Log();
|
||||
|
||||
return FlushImpl();
|
||||
}
|
||||
|
||||
public Result SetSize(long size)
|
||||
{
|
||||
if (IsDisposed) return ResultFs.PreconditionViolation.Log();
|
||||
if (size < 0) return ResultFs.ValueOutOfRange.Log();
|
||||
|
||||
return SetSizeImpl(size);
|
||||
}
|
||||
|
||||
public Result GetSize(out long size)
|
||||
{
|
||||
if (IsDisposed)
|
||||
{
|
||||
size = default;
|
||||
return ResultFs.PreconditionViolation.Log();
|
||||
}
|
||||
|
||||
return GetSizeImpl(out size);
|
||||
}
|
||||
|
||||
public Result OperateRange(Span<byte> outBuffer, OperationId operationId, long offset, long size,
|
||||
ReadOnlySpan<byte> inBuffer)
|
||||
{
|
||||
if (IsDisposed) return ResultFs.PreconditionViolation.Log();
|
||||
|
||||
return OperateRange(outBuffer, operationId, offset, size, inBuffer);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
// Make sure Dispose is only called once
|
||||
if (Interlocked.CompareExchange(ref _disposedState, 1, 0) == 0)
|
||||
{
|
||||
Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual void Dispose(bool disposing)
|
||||
protected virtual void Dispose(bool disposing) { }
|
||||
|
||||
protected Result ValidateReadParams(out long bytesToRead, long offset, int size, OpenMode openMode)
|
||||
{
|
||||
if (IsDisposed) return;
|
||||
bytesToRead = default;
|
||||
|
||||
if (disposing)
|
||||
if (!openMode.HasFlag(OpenMode.Read))
|
||||
{
|
||||
Flush();
|
||||
return ResultFs.InvalidOpenModeForRead.Log();
|
||||
}
|
||||
|
||||
foreach (IDisposable item in ToDispose)
|
||||
Result rc = GetSize(out long fileSize);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
if (offset > fileSize)
|
||||
{
|
||||
return ResultFs.ValueOutOfRange.Log();
|
||||
}
|
||||
|
||||
bytesToRead = Math.Min(fileSize - offset, size);
|
||||
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
protected Result ValidateWriteParams(long offset, int size, OpenMode openMode, out bool isResizeNeeded)
|
||||
{
|
||||
isResizeNeeded = false;
|
||||
|
||||
if (!openMode.HasFlag(OpenMode.Write))
|
||||
{
|
||||
return ResultFs.InvalidOpenModeForWrite.Log();
|
||||
}
|
||||
|
||||
Result rc = GetSize(out long fileSize);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
if (offset + size > fileSize)
|
||||
{
|
||||
isResizeNeeded = true;
|
||||
|
||||
if (!openMode.HasFlag(OpenMode.AllowAppend))
|
||||
{
|
||||
item?.Dispose();
|
||||
return ResultFs.FileExtensionWithoutOpenModeAllowAppend.Log();
|
||||
}
|
||||
}
|
||||
|
||||
IsDisposed = true;
|
||||
return Result.Success;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Specifies which operations are available on an <see cref="IFile"/>.
|
||||
/// </summary>
|
||||
[Flags]
|
||||
public enum OpenMode
|
||||
{
|
||||
Read = 1,
|
||||
Write = 2,
|
||||
Append = 4,
|
||||
ReadWrite = Read | Write
|
||||
}
|
||||
|
||||
[Flags]
|
||||
public enum ReadOption
|
||||
{
|
||||
None = 0
|
||||
}
|
||||
|
||||
[Flags]
|
||||
public enum WriteOption
|
||||
{
|
||||
None = 0,
|
||||
Flush = 1
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
using System;
|
||||
using LibHac.Fs.Accessors;
|
||||
|
||||
namespace LibHac.Fs.Accessors
|
||||
namespace LibHac.Fs
|
||||
{
|
||||
public struct FileHandle : IDisposable
|
||||
{
|
||||
@ -11,11 +12,11 @@ namespace LibHac.Fs.Accessors
|
||||
File = file;
|
||||
}
|
||||
|
||||
public int GetId() => File.GetHashCode();
|
||||
public int GetId() => File?.GetHashCode() ?? 0;
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
File.Parent.FsManager.CloseFile(this);
|
||||
File.Parent.FsClient.CloseFile(this);
|
||||
}
|
||||
}
|
||||
}
|
96
src/LibHac/Fs/FileHandleStorage.cs
Normal file
96
src/LibHac/Fs/FileHandleStorage.cs
Normal file
@ -0,0 +1,96 @@
|
||||
using System;
|
||||
|
||||
namespace LibHac.Fs
|
||||
{
|
||||
public class FileHandleStorage : StorageBase
|
||||
{
|
||||
private const long InvalidSize = -1;
|
||||
private readonly object _locker = new object();
|
||||
|
||||
private FileSystemClient FsClient { get; }
|
||||
private FileHandle Handle { get; }
|
||||
private long FileSize { get; set; } = InvalidSize;
|
||||
private bool CloseHandle { get; }
|
||||
|
||||
public FileHandleStorage(FileHandle handle) : this(handle, false) { }
|
||||
|
||||
public FileHandleStorage(FileHandle handle, bool closeHandleOnDispose)
|
||||
{
|
||||
Handle = handle;
|
||||
CloseHandle = closeHandleOnDispose;
|
||||
FsClient = Handle.File.Parent.FsClient;
|
||||
}
|
||||
|
||||
protected override Result ReadImpl(long offset, Span<byte> destination)
|
||||
{
|
||||
lock (_locker)
|
||||
{
|
||||
if (destination.Length == 0) return Result.Success;
|
||||
|
||||
Result rc = UpdateSize();
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
if (!IsRangeValid(offset, destination.Length, FileSize)) return ResultFs.ValueOutOfRange.Log();
|
||||
|
||||
return FsClient.ReadFile(Handle, offset, destination);
|
||||
}
|
||||
}
|
||||
|
||||
protected override Result WriteImpl(long offset, ReadOnlySpan<byte> source)
|
||||
{
|
||||
lock (_locker)
|
||||
{
|
||||
if (source.Length == 0) return Result.Success;
|
||||
|
||||
Result rc = UpdateSize();
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
if (!IsRangeValid(offset, source.Length, FileSize)) return ResultFs.ValueOutOfRange.Log();
|
||||
|
||||
return FsClient.WriteFile(Handle, offset, source);
|
||||
}
|
||||
}
|
||||
|
||||
protected override Result FlushImpl()
|
||||
{
|
||||
return FsClient.FlushFile(Handle);
|
||||
}
|
||||
|
||||
protected override Result SetSizeImpl(long size)
|
||||
{
|
||||
FileSize = InvalidSize;
|
||||
|
||||
return FsClient.SetFileSize(Handle, size);
|
||||
}
|
||||
|
||||
protected override Result GetSizeImpl(out long size)
|
||||
{
|
||||
size = default;
|
||||
|
||||
Result rc = UpdateSize();
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
size = FileSize;
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
private Result UpdateSize()
|
||||
{
|
||||
if (FileSize != InvalidSize) return Result.Success;
|
||||
|
||||
Result rc = FsClient.GetFileSize(out long fileSize, Handle);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
FileSize = fileSize;
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
if (CloseHandle)
|
||||
{
|
||||
FsClient.CloseFile(Handle);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,36 +0,0 @@
|
||||
using System;
|
||||
|
||||
namespace LibHac.Fs
|
||||
{
|
||||
public class FileStorage : StorageBase
|
||||
{
|
||||
private IFile BaseFile { get; }
|
||||
|
||||
public FileStorage(IFile baseFile)
|
||||
{
|
||||
BaseFile = baseFile;
|
||||
}
|
||||
|
||||
protected override void ReadImpl(Span<byte> destination, long offset)
|
||||
{
|
||||
BaseFile.Read(destination, offset);
|
||||
}
|
||||
|
||||
protected override void WriteImpl(ReadOnlySpan<byte> source, long offset)
|
||||
{
|
||||
BaseFile.Write(source, offset);
|
||||
}
|
||||
|
||||
public override void Flush()
|
||||
{
|
||||
BaseFile.Flush();
|
||||
}
|
||||
|
||||
public override long GetSize() => BaseFile.GetSize();
|
||||
|
||||
public override void SetSize(long size)
|
||||
{
|
||||
BaseFile.SetSize(size);
|
||||
}
|
||||
}
|
||||
}
|
196
src/LibHac/Fs/FileSystemBase.cs
Normal file
196
src/LibHac/Fs/FileSystemBase.cs
Normal file
@ -0,0 +1,196 @@
|
||||
using System;
|
||||
using System.Threading;
|
||||
|
||||
namespace LibHac.Fs
|
||||
{
|
||||
public abstract class FileSystemBase : IFileSystem
|
||||
{
|
||||
// 0 = not disposed; 1 = disposed
|
||||
private int _disposedState;
|
||||
private bool IsDisposed => _disposedState != 0;
|
||||
|
||||
protected abstract Result CreateDirectoryImpl(string path);
|
||||
protected abstract Result CreateFileImpl(string path, long size, CreateFileOptions options);
|
||||
protected abstract Result DeleteDirectoryImpl(string path);
|
||||
protected abstract Result DeleteDirectoryRecursivelyImpl(string path);
|
||||
protected abstract Result CleanDirectoryRecursivelyImpl(string path);
|
||||
protected abstract Result DeleteFileImpl(string path);
|
||||
protected abstract Result OpenDirectoryImpl(out IDirectory directory, string path, OpenDirectoryMode mode);
|
||||
protected abstract Result OpenFileImpl(out IFile file, string path, OpenMode mode);
|
||||
protected abstract Result RenameDirectoryImpl(string oldPath, string newPath);
|
||||
protected abstract Result RenameFileImpl(string oldPath, string newPath);
|
||||
protected abstract Result GetEntryTypeImpl(out DirectoryEntryType entryType, string path);
|
||||
protected abstract Result CommitImpl();
|
||||
|
||||
protected virtual Result GetFreeSpaceSizeImpl(out long freeSpace, string path)
|
||||
{
|
||||
freeSpace = default;
|
||||
return ResultFs.NotImplemented.Log();
|
||||
}
|
||||
|
||||
protected virtual Result GetTotalSpaceSizeImpl(out long totalSpace, string path)
|
||||
{
|
||||
totalSpace = default;
|
||||
return ResultFs.NotImplemented.Log();
|
||||
}
|
||||
|
||||
protected virtual Result GetFileTimeStampRawImpl(out FileTimeStampRaw timeStamp, string path)
|
||||
{
|
||||
timeStamp = default;
|
||||
return ResultFs.NotImplemented.Log();
|
||||
}
|
||||
|
||||
protected virtual Result QueryEntryImpl(Span<byte> outBuffer, ReadOnlySpan<byte> inBuffer, QueryId queryId, string path)
|
||||
{
|
||||
return ResultFs.NotImplemented.Log();
|
||||
}
|
||||
|
||||
public Result CreateDirectory(string path)
|
||||
{
|
||||
if (IsDisposed) return ResultFs.PreconditionViolation.Log();
|
||||
|
||||
return CreateDirectoryImpl(path);
|
||||
}
|
||||
|
||||
public Result CreateFile(string path, long size, CreateFileOptions options)
|
||||
{
|
||||
if (IsDisposed) return ResultFs.PreconditionViolation.Log();
|
||||
|
||||
return CreateFileImpl(path, size, options);
|
||||
}
|
||||
|
||||
public Result DeleteDirectory(string path)
|
||||
{
|
||||
if (IsDisposed) return ResultFs.PreconditionViolation.Log();
|
||||
|
||||
return DeleteDirectoryImpl(path);
|
||||
}
|
||||
|
||||
public Result DeleteDirectoryRecursively(string path)
|
||||
{
|
||||
if (IsDisposed) return ResultFs.PreconditionViolation.Log();
|
||||
|
||||
return DeleteDirectoryRecursivelyImpl(path);
|
||||
}
|
||||
|
||||
public Result CleanDirectoryRecursively(string path)
|
||||
{
|
||||
if (IsDisposed) return ResultFs.PreconditionViolation.Log();
|
||||
|
||||
return CleanDirectoryRecursivelyImpl(path);
|
||||
}
|
||||
|
||||
public Result DeleteFile(string path)
|
||||
{
|
||||
if (IsDisposed) return ResultFs.PreconditionViolation.Log();
|
||||
|
||||
return DeleteFileImpl(path);
|
||||
}
|
||||
|
||||
public Result OpenDirectory(out IDirectory directory, string path, OpenDirectoryMode mode)
|
||||
{
|
||||
if (IsDisposed)
|
||||
{
|
||||
directory = default;
|
||||
return ResultFs.PreconditionViolation.Log();
|
||||
}
|
||||
|
||||
return OpenDirectoryImpl(out directory, path, mode);
|
||||
}
|
||||
|
||||
public Result OpenFile(out IFile file, string path, OpenMode mode)
|
||||
{
|
||||
if (IsDisposed)
|
||||
{
|
||||
file = default;
|
||||
return ResultFs.PreconditionViolation.Log();
|
||||
}
|
||||
|
||||
return OpenFileImpl(out file, path, mode);
|
||||
}
|
||||
|
||||
public Result RenameDirectory(string oldPath, string newPath)
|
||||
{
|
||||
if (IsDisposed) return ResultFs.PreconditionViolation.Log();
|
||||
|
||||
return RenameDirectoryImpl(oldPath, newPath);
|
||||
}
|
||||
|
||||
public Result RenameFile(string oldPath, string newPath)
|
||||
{
|
||||
if (IsDisposed) return ResultFs.PreconditionViolation.Log();
|
||||
|
||||
return RenameFileImpl(oldPath, newPath);
|
||||
}
|
||||
|
||||
public Result GetEntryType(out DirectoryEntryType entryType, string path)
|
||||
{
|
||||
if (IsDisposed)
|
||||
{
|
||||
entryType = default;
|
||||
return ResultFs.PreconditionViolation.Log();
|
||||
}
|
||||
|
||||
return GetEntryTypeImpl(out entryType, path);
|
||||
}
|
||||
|
||||
public Result GetFreeSpaceSize(out long freeSpace, string path)
|
||||
{
|
||||
if (IsDisposed)
|
||||
{
|
||||
freeSpace = default;
|
||||
return ResultFs.PreconditionViolation.Log();
|
||||
}
|
||||
|
||||
return GetFreeSpaceSizeImpl(out freeSpace, path);
|
||||
}
|
||||
|
||||
public Result GetTotalSpaceSize(out long totalSpace, string path)
|
||||
{
|
||||
if (IsDisposed)
|
||||
{
|
||||
totalSpace = default;
|
||||
return ResultFs.PreconditionViolation.Log();
|
||||
}
|
||||
|
||||
return GetTotalSpaceSizeImpl(out totalSpace, path);
|
||||
}
|
||||
|
||||
public Result GetFileTimeStampRaw(out FileTimeStampRaw timeStamp, string path)
|
||||
{
|
||||
if (IsDisposed)
|
||||
{
|
||||
timeStamp = default;
|
||||
return ResultFs.PreconditionViolation.Log();
|
||||
}
|
||||
|
||||
return GetFileTimeStampRawImpl(out timeStamp, path);
|
||||
}
|
||||
|
||||
public Result Commit()
|
||||
{
|
||||
if (IsDisposed) return ResultFs.PreconditionViolation.Log();
|
||||
|
||||
return CommitImpl();
|
||||
}
|
||||
|
||||
public Result QueryEntry(Span<byte> outBuffer, ReadOnlySpan<byte> inBuffer, QueryId queryId, string path)
|
||||
{
|
||||
if (IsDisposed) return ResultFs.PreconditionViolation.Log();
|
||||
|
||||
return QueryEntryImpl(outBuffer, inBuffer, queryId, path);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
// Make sure Dispose is only called once
|
||||
if (Interlocked.CompareExchange(ref _disposedState, 1, 0) == 0)
|
||||
{
|
||||
Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual void Dispose(bool disposing) { }
|
||||
}
|
||||
}
|
220
src/LibHac/Fs/FileSystemClient.AccessLog.cs
Normal file
220
src/LibHac/Fs/FileSystemClient.AccessLog.cs
Normal file
@ -0,0 +1,220 @@
|
||||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
using LibHac.Common;
|
||||
using LibHac.Fs.Accessors;
|
||||
using LibHac.FsService;
|
||||
|
||||
namespace LibHac.Fs
|
||||
{
|
||||
public partial class FileSystemClient
|
||||
{
|
||||
private GlobalAccessLogMode GlobalAccessLogMode { get; set; }
|
||||
private LocalAccessLogMode LocalAccessLogMode { get; set; }
|
||||
private bool AccessLogInitialized { get; set; }
|
||||
|
||||
private readonly object _accessLogInitLocker = new object();
|
||||
|
||||
public Result GetGlobalAccessLogMode(out GlobalAccessLogMode mode)
|
||||
{
|
||||
if (HasFileSystemServer())
|
||||
{
|
||||
IFileSystemProxy fsProxy = GetFileSystemProxyServiceObject();
|
||||
|
||||
return fsProxy.GetGlobalAccessLogMode(out mode);
|
||||
}
|
||||
|
||||
mode = GlobalAccessLogMode;
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
public Result SetGlobalAccessLogMode(GlobalAccessLogMode mode)
|
||||
{
|
||||
if (HasFileSystemServer())
|
||||
{
|
||||
IFileSystemProxy fsProxy = GetFileSystemProxyServiceObject();
|
||||
|
||||
return fsProxy.SetGlobalAccessLogMode(mode);
|
||||
}
|
||||
|
||||
GlobalAccessLogMode = mode;
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
public void SetLocalAccessLogMode(LocalAccessLogMode mode)
|
||||
{
|
||||
LocalAccessLogMode = mode;
|
||||
}
|
||||
|
||||
public void SetAccessLogObject(IAccessLog accessLog)
|
||||
{
|
||||
AccessLog = accessLog;
|
||||
}
|
||||
|
||||
internal bool IsEnabledAccessLog(LocalAccessLogMode mode)
|
||||
{
|
||||
if ((LocalAccessLogMode & mode) == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (AccessLogInitialized)
|
||||
{
|
||||
return GlobalAccessLogMode != GlobalAccessLogMode.None;
|
||||
}
|
||||
|
||||
lock (_accessLogInitLocker)
|
||||
{
|
||||
if (!AccessLogInitialized)
|
||||
{
|
||||
if (HasFileSystemServer())
|
||||
{
|
||||
IFileSystemProxy fsProxy = GetFileSystemProxyServiceObject();
|
||||
|
||||
Result rc = fsProxy.GetGlobalAccessLogMode(out GlobalAccessLogMode globalMode);
|
||||
GlobalAccessLogMode = globalMode;
|
||||
|
||||
if (rc.IsFailure())
|
||||
{
|
||||
throw new LibHacException("Abort");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
GlobalAccessLogMode = GlobalAccessLogMode.Log;
|
||||
}
|
||||
|
||||
if (GlobalAccessLogMode != GlobalAccessLogMode.None)
|
||||
{
|
||||
InitAccessLog();
|
||||
}
|
||||
|
||||
AccessLogInitialized = true;
|
||||
}
|
||||
}
|
||||
|
||||
return GlobalAccessLogMode != GlobalAccessLogMode.None;
|
||||
}
|
||||
|
||||
private void InitAccessLog()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
internal bool IsEnabledAccessLog()
|
||||
{
|
||||
return IsEnabledAccessLog(LocalAccessLogMode.All);
|
||||
}
|
||||
|
||||
internal bool IsEnabledFileSystemAccessorAccessLog(string mountName)
|
||||
{
|
||||
if (MountTable.Find(mountName, out FileSystemAccessor accessor).IsFailure())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return accessor.IsAccessLogEnabled;
|
||||
}
|
||||
|
||||
internal bool IsEnabledHandleAccessLog(FileHandle handle)
|
||||
{
|
||||
return handle.File.Parent.IsAccessLogEnabled;
|
||||
}
|
||||
|
||||
internal bool IsEnabledHandleAccessLog(DirectoryHandle handle)
|
||||
{
|
||||
return handle.Directory.Parent.IsAccessLogEnabled;
|
||||
}
|
||||
|
||||
internal void OutputAccessLog(Result result, TimeSpan startTime, TimeSpan endTime, string message, [CallerMemberName] string caller = "")
|
||||
{
|
||||
OutputAccessLogImpl(result, startTime, endTime, 0, message, caller);
|
||||
}
|
||||
|
||||
internal void OutputAccessLog(Result result, TimeSpan startTime, TimeSpan endTime, FileHandle handle, string message, [CallerMemberName] string caller = "")
|
||||
{
|
||||
OutputAccessLogImpl(result, startTime, endTime, handle.GetId(), message, caller);
|
||||
}
|
||||
|
||||
internal void OutputAccessLog(Result result, TimeSpan startTime, TimeSpan endTime, DirectoryHandle handle, string message, [CallerMemberName] string caller = "")
|
||||
{
|
||||
OutputAccessLogImpl(result, startTime, endTime, handle.GetId(), message, caller);
|
||||
}
|
||||
|
||||
internal void OutputAccessLogImpl(Result result, TimeSpan startTime, TimeSpan endTime, int handleId,
|
||||
string message, [CallerMemberName] string caller = "")
|
||||
{
|
||||
if (GlobalAccessLogMode.HasFlag(GlobalAccessLogMode.Log))
|
||||
{
|
||||
AccessLog?.Log(result, startTime, endTime, handleId, message, caller);
|
||||
}
|
||||
|
||||
if (GlobalAccessLogMode.HasFlag(GlobalAccessLogMode.SdCard))
|
||||
{
|
||||
string logString = AccessLogHelpers.BuildDefaultLogLine(result, startTime, endTime, handleId, message, caller);
|
||||
|
||||
IFileSystemProxy fsProxy = GetFileSystemProxyServiceObject();
|
||||
fsProxy.OutputAccessLogToSdCard(logString.ToU8Span());
|
||||
}
|
||||
}
|
||||
|
||||
public Result RunOperationWithAccessLog(LocalAccessLogMode logType, Func<Result> operation,
|
||||
Func<string> textGenerator, [CallerMemberName] string caller = "")
|
||||
{
|
||||
Result rc;
|
||||
|
||||
if (IsEnabledAccessLog(logType))
|
||||
{
|
||||
TimeSpan startTime = Time.GetCurrent();
|
||||
rc = operation();
|
||||
TimeSpan endTime = Time.GetCurrent();
|
||||
|
||||
OutputAccessLog(rc, startTime, endTime, textGenerator(), caller);
|
||||
}
|
||||
else
|
||||
{
|
||||
rc = operation();
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
public Result RunOperationWithAccessLog(LocalAccessLogMode logType, FileHandle handle, Func<Result> operation,
|
||||
Func<string> textGenerator, [CallerMemberName] string caller = "")
|
||||
{
|
||||
Result rc;
|
||||
|
||||
if (IsEnabledAccessLog(logType) && handle.File.Parent.IsAccessLogEnabled)
|
||||
{
|
||||
TimeSpan startTime = Time.GetCurrent();
|
||||
rc = operation();
|
||||
TimeSpan endTime = Time.GetCurrent();
|
||||
|
||||
OutputAccessLog(rc, startTime, endTime, handle, textGenerator(), caller);
|
||||
}
|
||||
else
|
||||
{
|
||||
rc = operation();
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
||||
}
|
||||
|
||||
[Flags]
|
||||
public enum LocalAccessLogMode
|
||||
{
|
||||
None = 0,
|
||||
Application = 1 << 0,
|
||||
System = 1 << 1,
|
||||
All = Application | System
|
||||
}
|
||||
|
||||
[Flags]
|
||||
public enum GlobalAccessLogMode
|
||||
{
|
||||
None = 0,
|
||||
Log = 1 << 0,
|
||||
SdCard = 1 << 1,
|
||||
All = Log | SdCard
|
||||
}
|
||||
}
|
48
src/LibHac/Fs/FileSystemClient.Directory.cs
Normal file
48
src/LibHac/Fs/FileSystemClient.Directory.cs
Normal file
@ -0,0 +1,48 @@
|
||||
using System;
|
||||
|
||||
namespace LibHac.Fs
|
||||
{
|
||||
public partial class FileSystemClient
|
||||
{
|
||||
public Result GetDirectoryEntryCount(out long count, DirectoryHandle handle)
|
||||
{
|
||||
return handle.Directory.GetEntryCount(out count);
|
||||
}
|
||||
|
||||
public Result ReadDirectory(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))
|
||||
{
|
||||
TimeSpan startTime = Time.GetCurrent();
|
||||
handle.Directory.Dispose();
|
||||
TimeSpan endTime = Time.GetCurrent();
|
||||
|
||||
OutputAccessLog(Result.Success, startTime, endTime, handle, string.Empty);
|
||||
}
|
||||
else
|
||||
{
|
||||
handle.Directory.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
109
src/LibHac/Fs/FileSystemClient.File.cs
Normal file
109
src/LibHac/Fs/FileSystemClient.File.cs
Normal file
@ -0,0 +1,109 @@
|
||||
using System;
|
||||
|
||||
namespace LibHac.Fs
|
||||
{
|
||||
public partial class FileSystemClient
|
||||
{
|
||||
public Result ReadFile(FileHandle handle, long offset, Span<byte> destination)
|
||||
{
|
||||
return ReadFile(handle, offset, destination, ReadOption.None);
|
||||
}
|
||||
|
||||
public Result ReadFile(FileHandle handle, long offset, Span<byte> destination, ReadOption option)
|
||||
{
|
||||
Result rc = ReadFile(out long bytesRead, handle, offset, destination, option);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
if (bytesRead == destination.Length) return Result.Success;
|
||||
|
||||
return ResultFs.ValueOutOfRange.Log();
|
||||
}
|
||||
|
||||
public Result ReadFile(out long bytesRead, FileHandle handle, long offset, Span<byte> destination)
|
||||
{
|
||||
return ReadFile(out bytesRead, handle, offset, destination, ReadOption.None);
|
||||
}
|
||||
|
||||
public Result ReadFile(out long bytesRead, FileHandle handle, long offset, Span<byte> destination, ReadOption option)
|
||||
{
|
||||
Result rc;
|
||||
|
||||
if (IsEnabledAccessLog() && IsEnabledHandleAccessLog(handle))
|
||||
{
|
||||
TimeSpan startTime = Time.GetCurrent();
|
||||
rc = handle.File.Read(out bytesRead, offset, destination, option);
|
||||
TimeSpan endTime = Time.GetCurrent();
|
||||
|
||||
OutputAccessLog(rc, startTime, endTime, handle, $", offset: {offset}, size: {destination.Length}");
|
||||
}
|
||||
else
|
||||
{
|
||||
rc = handle.File.Read(out bytesRead, offset, destination, option);
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
public Result WriteFile(FileHandle handle, long offset, ReadOnlySpan<byte> source)
|
||||
{
|
||||
return WriteFile(handle, offset, source, WriteOption.None);
|
||||
}
|
||||
|
||||
public Result WriteFile(FileHandle handle, long offset, ReadOnlySpan<byte> source, WriteOption option)
|
||||
{
|
||||
Result rc;
|
||||
|
||||
if (IsEnabledAccessLog() && IsEnabledHandleAccessLog(handle))
|
||||
{
|
||||
TimeSpan startTime = Time.GetCurrent();
|
||||
rc = handle.File.Write(offset, source, option);
|
||||
TimeSpan endTime = Time.GetCurrent();
|
||||
|
||||
string optionString = (option & WriteOption.Flush) == 0 ? "" : $", write_option: {option}";
|
||||
|
||||
OutputAccessLog(rc, startTime, endTime, handle, $", offset: {offset}, size: {source.Length}{optionString}");
|
||||
}
|
||||
else
|
||||
{
|
||||
rc = handle.File.Write(offset, source, option);
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
public Result FlushFile(FileHandle handle)
|
||||
{
|
||||
return RunOperationWithAccessLog(LocalAccessLogMode.All, handle,
|
||||
() => handle.File.Flush(),
|
||||
() => string.Empty);
|
||||
}
|
||||
|
||||
public Result GetFileSize(out long fileSize, FileHandle handle)
|
||||
{
|
||||
return handle.File.GetSize(out fileSize);
|
||||
}
|
||||
|
||||
public Result SetFileSize(FileHandle handle, long size)
|
||||
{
|
||||
return RunOperationWithAccessLog(LocalAccessLogMode.All, handle,
|
||||
() => handle.File.SetSize(size),
|
||||
() => $", size: {size}");
|
||||
}
|
||||
|
||||
public OpenMode GetFileOpenMode(FileHandle handle)
|
||||
{
|
||||
return handle.File.OpenMode;
|
||||
}
|
||||
|
||||
public void CloseFile(FileHandle handle)
|
||||
{
|
||||
RunOperationWithAccessLog(LocalAccessLogMode.All, handle,
|
||||
() =>
|
||||
{
|
||||
handle.File.Dispose();
|
||||
return Result.Success;
|
||||
},
|
||||
() => string.Empty);
|
||||
}
|
||||
}
|
||||
}
|
321
src/LibHac/Fs/FileSystemClient.FileSystem.cs
Normal file
321
src/LibHac/Fs/FileSystemClient.FileSystem.cs
Normal file
@ -0,0 +1,321 @@
|
||||
using System;
|
||||
using LibHac.Fs.Accessors;
|
||||
|
||||
namespace LibHac.Fs
|
||||
{
|
||||
public partial class FileSystemClient
|
||||
{
|
||||
public Result CreateDirectory(string path)
|
||||
{
|
||||
Result rc = FindFileSystem(path.AsSpan(), out FileSystemAccessor fileSystem, out ReadOnlySpan<char> subPath);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
if (IsEnabledAccessLog() && fileSystem.IsAccessLogEnabled)
|
||||
{
|
||||
TimeSpan startTime = Time.GetCurrent();
|
||||
rc = fileSystem.CreateDirectory(subPath.ToString());
|
||||
TimeSpan endTime = Time.GetCurrent();
|
||||
|
||||
OutputAccessLog(rc, startTime, endTime, $", path: \"{path}\"");
|
||||
}
|
||||
else
|
||||
{
|
||||
rc = fileSystem.CreateDirectory(subPath.ToString());
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
public Result CreateFile(string path, long size)
|
||||
{
|
||||
return CreateFile(path, size, CreateFileOptions.None);
|
||||
}
|
||||
|
||||
public Result CreateFile(string path, long size, CreateFileOptions options)
|
||||
{
|
||||
Result rc = FindFileSystem(path.AsSpan(), out FileSystemAccessor fileSystem, out ReadOnlySpan<char> subPath);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
if (IsEnabledAccessLog() && fileSystem.IsAccessLogEnabled)
|
||||
{
|
||||
TimeSpan startTime = Time.GetCurrent();
|
||||
rc = fileSystem.CreateFile(subPath.ToString(), size, options);
|
||||
TimeSpan endTime = Time.GetCurrent();
|
||||
|
||||
OutputAccessLog(rc, startTime, endTime, $", path: \"{path}\", size: {size}");
|
||||
}
|
||||
else
|
||||
{
|
||||
rc = fileSystem.CreateFile(subPath.ToString(), size, options);
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
public Result DeleteDirectory(string path)
|
||||
{
|
||||
Result rc = FindFileSystem(path.AsSpan(), out FileSystemAccessor fileSystem, out ReadOnlySpan<char> subPath);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
if (IsEnabledAccessLog() && fileSystem.IsAccessLogEnabled)
|
||||
{
|
||||
TimeSpan startTime = Time.GetCurrent();
|
||||
rc = fileSystem.DeleteDirectory(subPath.ToString());
|
||||
TimeSpan endTime = Time.GetCurrent();
|
||||
|
||||
OutputAccessLog(rc, startTime, endTime, $", path: \"{path}\"");
|
||||
}
|
||||
else
|
||||
{
|
||||
rc = fileSystem.DeleteDirectory(subPath.ToString());
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
public Result DeleteDirectoryRecursively(string path)
|
||||
{
|
||||
Result rc = FindFileSystem(path.AsSpan(), out FileSystemAccessor fileSystem, out ReadOnlySpan<char> subPath);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
if (IsEnabledAccessLog() && fileSystem.IsAccessLogEnabled)
|
||||
{
|
||||
TimeSpan startTime = Time.GetCurrent();
|
||||
rc = fileSystem.DeleteDirectoryRecursively(subPath.ToString());
|
||||
TimeSpan endTime = Time.GetCurrent();
|
||||
|
||||
OutputAccessLog(rc, startTime, endTime, $", path: \"{path}\"");
|
||||
}
|
||||
else
|
||||
{
|
||||
rc = fileSystem.DeleteDirectoryRecursively(subPath.ToString());
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
public Result CleanDirectoryRecursively(string path)
|
||||
{
|
||||
Result rc = FindFileSystem(path.AsSpan(), out FileSystemAccessor fileSystem, out ReadOnlySpan<char> subPath);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
if (IsEnabledAccessLog() && fileSystem.IsAccessLogEnabled)
|
||||
{
|
||||
TimeSpan startTime = Time.GetCurrent();
|
||||
rc = fileSystem.CleanDirectoryRecursively(subPath.ToString());
|
||||
TimeSpan endTime = Time.GetCurrent();
|
||||
|
||||
OutputAccessLog(rc, startTime, endTime, $", path: \"{path}\"");
|
||||
}
|
||||
else
|
||||
{
|
||||
rc = fileSystem.CleanDirectoryRecursively(subPath.ToString());
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
public Result DeleteFile(string path)
|
||||
{
|
||||
Result rc = FindFileSystem(path.AsSpan(), out FileSystemAccessor fileSystem, out ReadOnlySpan<char> subPath);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
if (IsEnabledAccessLog() && fileSystem.IsAccessLogEnabled)
|
||||
{
|
||||
TimeSpan startTime = Time.GetCurrent();
|
||||
rc = fileSystem.DeleteFile(subPath.ToString());
|
||||
TimeSpan endTime = Time.GetCurrent();
|
||||
|
||||
OutputAccessLog(rc, startTime, endTime, $", path: \"{path}\"");
|
||||
}
|
||||
else
|
||||
{
|
||||
rc = fileSystem.DeleteFile(subPath.ToString());
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
public Result RenameDirectory(string oldPath, string newPath)
|
||||
{
|
||||
Result rc = FindFileSystem(oldPath.AsSpan(), out FileSystemAccessor oldFileSystem, out ReadOnlySpan<char> oldSubPath);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
rc = FindFileSystem(newPath.AsSpan(), out FileSystemAccessor newFileSystem, out ReadOnlySpan<char> newSubPath);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
if (oldFileSystem != newFileSystem)
|
||||
{
|
||||
return ResultFs.DifferentDestFileSystem.Log();
|
||||
}
|
||||
|
||||
if (IsEnabledAccessLog() && oldFileSystem.IsAccessLogEnabled)
|
||||
{
|
||||
TimeSpan startTime = Time.GetCurrent();
|
||||
rc = oldFileSystem.RenameDirectory(oldSubPath.ToString(), newSubPath.ToString());
|
||||
TimeSpan endTime = Time.GetCurrent();
|
||||
|
||||
OutputAccessLog(rc, startTime, endTime, $", path: \"{oldPath}\", new_path: \"{newPath}\"");
|
||||
}
|
||||
else
|
||||
{
|
||||
rc = oldFileSystem.RenameDirectory(oldSubPath.ToString(), newSubPath.ToString());
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
public Result RenameFile(string oldPath, string newPath)
|
||||
{
|
||||
Result rc = FindFileSystem(oldPath.AsSpan(), out FileSystemAccessor oldFileSystem, out ReadOnlySpan<char> oldSubPath);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
rc = FindFileSystem(newPath.AsSpan(), out FileSystemAccessor newFileSystem, out ReadOnlySpan<char> newSubPath);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
if (oldFileSystem != newFileSystem)
|
||||
{
|
||||
return ResultFs.DifferentDestFileSystem.Log();
|
||||
}
|
||||
|
||||
if (IsEnabledAccessLog() && oldFileSystem.IsAccessLogEnabled)
|
||||
{
|
||||
TimeSpan startTime = Time.GetCurrent();
|
||||
rc = oldFileSystem.RenameFile(oldSubPath.ToString(), newSubPath.ToString());
|
||||
TimeSpan endTime = Time.GetCurrent();
|
||||
|
||||
OutputAccessLog(rc, startTime, endTime, $", path: \"{oldPath}\", new_path: \"{newPath}\"");
|
||||
}
|
||||
else
|
||||
{
|
||||
rc = oldFileSystem.RenameFile(oldSubPath.ToString(), newSubPath.ToString());
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
public Result GetEntryType(out DirectoryEntryType type, string path)
|
||||
{
|
||||
type = default;
|
||||
|
||||
Result rc = FindFileSystem(path.AsSpan(), out FileSystemAccessor fileSystem, out ReadOnlySpan<char> subPath);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
if (IsEnabledAccessLog() && fileSystem.IsAccessLogEnabled)
|
||||
{
|
||||
TimeSpan startTime = Time.GetCurrent();
|
||||
rc = fileSystem.GetEntryType(out type, subPath.ToString());
|
||||
TimeSpan endTime = Time.GetCurrent();
|
||||
|
||||
OutputAccessLog(rc, startTime, endTime, $", path: \"{path}\"");
|
||||
}
|
||||
else
|
||||
{
|
||||
rc = fileSystem.GetEntryType(out type, subPath.ToString());
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
public Result OpenFile(out FileHandle handle, string path, OpenMode mode)
|
||||
{
|
||||
handle = default;
|
||||
|
||||
Result rc = FindFileSystem(path.AsSpan(), out FileSystemAccessor fileSystem, out ReadOnlySpan<char> subPath);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
if (IsEnabledAccessLog() && fileSystem.IsAccessLogEnabled)
|
||||
{
|
||||
TimeSpan startTime = Time.GetCurrent();
|
||||
rc = fileSystem.OpenFile(out FileAccessor file, subPath.ToString(), mode);
|
||||
handle = new FileHandle(file);
|
||||
TimeSpan endTime = Time.GetCurrent();
|
||||
|
||||
OutputAccessLog(rc, startTime, endTime, handle, $", path: \"{path}\", open_mode: {mode}");
|
||||
}
|
||||
else
|
||||
{
|
||||
rc = fileSystem.OpenFile(out FileAccessor file, subPath.ToString(), mode);
|
||||
handle = new FileHandle(file);
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
public Result OpenDirectory(out DirectoryHandle handle, string path, OpenDirectoryMode mode)
|
||||
{
|
||||
handle = default;
|
||||
|
||||
Result rc = FindFileSystem(path.AsSpan(), out FileSystemAccessor fileSystem, out ReadOnlySpan<char> subPath);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
if (IsEnabledAccessLog() && fileSystem.IsAccessLogEnabled)
|
||||
{
|
||||
TimeSpan startTime = Time.GetCurrent();
|
||||
rc = fileSystem.OpenDirectory(out DirectoryAccessor dir, subPath.ToString(), mode);
|
||||
handle = new DirectoryHandle(dir);
|
||||
TimeSpan endTime = Time.GetCurrent();
|
||||
|
||||
OutputAccessLog(rc, startTime, endTime, handle, $", path: \"{path}\", open_mode: {mode}");
|
||||
}
|
||||
else
|
||||
{
|
||||
rc = fileSystem.OpenDirectory(out DirectoryAccessor dir, subPath.ToString(), mode);
|
||||
handle = new DirectoryHandle(dir);
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
public Result GetFreeSpaceSize(out long freeSpace, string path)
|
||||
{
|
||||
freeSpace = default;
|
||||
|
||||
Result rc = FindFileSystem(path.AsSpan(), out FileSystemAccessor fileSystem, out ReadOnlySpan<char> subPath);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
return fileSystem.GetFreeSpaceSize(out freeSpace, subPath.ToString());
|
||||
}
|
||||
|
||||
public Result GetTotalSpaceSize(out long totalSpace, string path)
|
||||
{
|
||||
totalSpace = default;
|
||||
|
||||
Result rc = FindFileSystem(path.AsSpan(), out FileSystemAccessor fileSystem, out ReadOnlySpan<char> subPath);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
return fileSystem.GetTotalSpaceSize(out totalSpace, subPath.ToString());
|
||||
}
|
||||
|
||||
public Result GetFileTimeStamp(out FileTimeStampRaw timeStamp, string path)
|
||||
{
|
||||
timeStamp = default;
|
||||
|
||||
Result rc = FindFileSystem(path.AsSpan(), out FileSystemAccessor fileSystem, out ReadOnlySpan<char> subPath);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
return fileSystem.GetFileTimeStampRaw(out timeStamp, subPath.ToString());
|
||||
}
|
||||
|
||||
public Result Commit(string mountName)
|
||||
{
|
||||
Result rc = MountTable.Find(mountName, out FileSystemAccessor fileSystem);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
if (IsEnabledAccessLog() && fileSystem.IsAccessLogEnabled)
|
||||
{
|
||||
TimeSpan startTime = Time.GetCurrent();
|
||||
rc = fileSystem.Commit();
|
||||
TimeSpan endTime = Time.GetCurrent();
|
||||
|
||||
OutputAccessLog(rc, startTime, endTime, $", name: \"{mountName}\"");
|
||||
}
|
||||
else
|
||||
{
|
||||
rc = fileSystem.Commit();
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
||||
}
|
||||
}
|
142
src/LibHac/Fs/FileSystemClient.cs
Normal file
142
src/LibHac/Fs/FileSystemClient.cs
Normal file
@ -0,0 +1,142 @@
|
||||
using System;
|
||||
using LibHac.Common;
|
||||
using LibHac.Fs.Accessors;
|
||||
using LibHac.FsService;
|
||||
using LibHac.FsSystem;
|
||||
|
||||
namespace LibHac.Fs
|
||||
{
|
||||
public partial class FileSystemClient
|
||||
{
|
||||
private FileSystemServer FsSrv { get; }
|
||||
private IFileSystemProxy FsProxy { get; set; }
|
||||
|
||||
private readonly object _fspInitLocker = new object();
|
||||
|
||||
internal ITimeSpanGenerator Time { get; }
|
||||
private IAccessLog AccessLog { get; set; }
|
||||
|
||||
internal MountTable MountTable { get; } = new MountTable();
|
||||
|
||||
public FileSystemClient(ITimeSpanGenerator timer)
|
||||
{
|
||||
Time = timer ?? new StopWatchTimeSpanGenerator();
|
||||
}
|
||||
|
||||
public FileSystemClient(FileSystemServer fsServer, ITimeSpanGenerator timer)
|
||||
{
|
||||
FsSrv = fsServer;
|
||||
Time = timer ?? new StopWatchTimeSpanGenerator();
|
||||
}
|
||||
|
||||
public bool HasFileSystemServer()
|
||||
{
|
||||
return FsSrv != null;
|
||||
}
|
||||
|
||||
public IFileSystemProxy GetFileSystemProxyServiceObject()
|
||||
{
|
||||
if (FsProxy != null) return FsProxy;
|
||||
|
||||
lock (_fspInitLocker)
|
||||
{
|
||||
if (FsProxy != null) return FsProxy;
|
||||
|
||||
if (!HasFileSystemServer())
|
||||
{
|
||||
throw new InvalidOperationException("Client was not initialized with a server object.");
|
||||
}
|
||||
|
||||
FsProxy = FsSrv.CreateFileSystemProxyService();
|
||||
|
||||
return FsProxy;
|
||||
}
|
||||
}
|
||||
|
||||
public Result Register(U8Span mountName, IFileSystem fileSystem)
|
||||
{
|
||||
return Register(mountName, fileSystem, null);
|
||||
}
|
||||
|
||||
public Result Register(U8Span mountName, IFileSystem fileSystem, ICommonMountNameGenerator nameGenerator)
|
||||
{
|
||||
var accessor = new FileSystemAccessor(mountName.ToString(), fileSystem, this, nameGenerator);
|
||||
|
||||
Result rc = MountTable.Mount(accessor);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
accessor.IsAccessLogEnabled = IsEnabledAccessLog();
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
public void Unmount(string mountName)
|
||||
{
|
||||
Result rc;
|
||||
|
||||
if (IsEnabledAccessLog() && IsEnabledFileSystemAccessorAccessLog(mountName))
|
||||
{
|
||||
TimeSpan startTime = Time.GetCurrent();
|
||||
|
||||
rc = MountTable.Unmount(mountName);
|
||||
|
||||
TimeSpan endTime = Time.GetCurrent();
|
||||
OutputAccessLog(rc, startTime, endTime, $", name: \"{mountName}\"");
|
||||
}
|
||||
else
|
||||
{
|
||||
rc = MountTable.Unmount(mountName);
|
||||
}
|
||||
|
||||
rc.ThrowIfFailure();
|
||||
}
|
||||
|
||||
internal Result FindFileSystem(ReadOnlySpan<char> path, out FileSystemAccessor fileSystem, out ReadOnlySpan<char> subPath)
|
||||
{
|
||||
fileSystem = default;
|
||||
|
||||
Result rc = GetMountName(path, out ReadOnlySpan<char> mountName, out subPath);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
rc = MountTable.Find(mountName.ToString(), out fileSystem);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
internal static Result GetMountName(ReadOnlySpan<char> path, out ReadOnlySpan<char> mountName, out ReadOnlySpan<char> subPath)
|
||||
{
|
||||
int mountLen = 0;
|
||||
int maxMountLen = Math.Min(path.Length, PathTools.MountNameLength);
|
||||
|
||||
for (int i = 0; i < maxMountLen; i++)
|
||||
{
|
||||
if (path[i] == PathTools.MountSeparator)
|
||||
{
|
||||
mountLen = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (mountLen == 0)
|
||||
{
|
||||
mountName = default;
|
||||
subPath = default;
|
||||
|
||||
return ResultFs.InvalidMountName;
|
||||
}
|
||||
|
||||
mountName = path.Slice(0, mountLen);
|
||||
|
||||
if (mountLen + 1 < path.Length)
|
||||
{
|
||||
subPath = path.Slice(mountLen + 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
subPath = default;
|
||||
}
|
||||
|
||||
return Result.Success;
|
||||
}
|
||||
}
|
||||
}
|
222
src/LibHac/Fs/FileSystemClientUtils.cs
Normal file
222
src/LibHac/Fs/FileSystemClientUtils.cs
Normal file
@ -0,0 +1,222 @@
|
||||
using System;
|
||||
using System.Buffers;
|
||||
using System.Collections.Generic;
|
||||
using LibHac.Common;
|
||||
using LibHac.Fs.Accessors;
|
||||
using LibHac.FsSystem;
|
||||
|
||||
namespace LibHac.Fs
|
||||
{
|
||||
public static class FileSystemClientUtils
|
||||
{
|
||||
public static Result CopyDirectory(this FileSystemClient fs, string sourcePath, string destPath,
|
||||
CreateFileOptions options = CreateFileOptions.None, IProgressReport logger = null)
|
||||
{
|
||||
Result rc = fs.OpenDirectory(out DirectoryHandle sourceHandle, sourcePath, OpenDirectoryMode.All);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
using (sourceHandle)
|
||||
{
|
||||
foreach (DirectoryEntryEx entry in fs.EnumerateEntries(sourcePath, "*", SearchOptions.Default))
|
||||
{
|
||||
string subSrcPath = PathTools.Normalize(PathTools.Combine(sourcePath, entry.Name));
|
||||
string subDstPath = PathTools.Normalize(PathTools.Combine(destPath, entry.Name));
|
||||
|
||||
if (entry.Type == DirectoryEntryType.Directory)
|
||||
{
|
||||
fs.EnsureDirectoryExists(subDstPath);
|
||||
|
||||
rc = fs.CopyDirectory(subSrcPath, subDstPath, options, logger);
|
||||
if (rc.IsFailure()) return rc;
|
||||
}
|
||||
|
||||
if (entry.Type == DirectoryEntryType.File)
|
||||
{
|
||||
logger?.LogMessage(subSrcPath);
|
||||
fs.CreateOrOverwriteFile(subDstPath, entry.Size, options);
|
||||
|
||||
rc = fs.CopyFile(subSrcPath, subDstPath, logger);
|
||||
if (rc.IsFailure()) return rc;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
public static Result CopyFile(this FileSystemClient fs, string sourcePath, string destPath, IProgressReport logger = null)
|
||||
{
|
||||
Result rc = fs.OpenFile(out FileHandle sourceHandle, sourcePath, OpenMode.Read);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
using (sourceHandle)
|
||||
{
|
||||
rc = fs.OpenFile(out FileHandle destHandle, destPath, OpenMode.Write | OpenMode.AllowAppend);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
using (destHandle)
|
||||
{
|
||||
const int maxBufferSize = 0x10000;
|
||||
|
||||
rc = fs.GetFileSize(out long fileSize, sourceHandle);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
int bufferSize = (int)Math.Min(maxBufferSize, fileSize);
|
||||
|
||||
logger?.SetTotal(fileSize);
|
||||
|
||||
byte[] buffer = ArrayPool<byte>.Shared.Rent(bufferSize);
|
||||
try
|
||||
{
|
||||
for (long offset = 0; offset < fileSize; offset += bufferSize)
|
||||
{
|
||||
int toRead = (int)Math.Min(fileSize - offset, bufferSize);
|
||||
Span<byte> buf = buffer.AsSpan(0, toRead);
|
||||
|
||||
rc = fs.ReadFile(out long _, sourceHandle, offset, buf);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
rc = fs.WriteFile(destHandle, offset, buf);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
logger?.ReportAdd(toRead);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
ArrayPool<byte>.Shared.Return(buffer);
|
||||
logger?.SetTotal(0);
|
||||
}
|
||||
|
||||
rc = fs.FlushFile(destHandle);
|
||||
if (rc.IsFailure()) return rc;
|
||||
}
|
||||
}
|
||||
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
public static IEnumerable<DirectoryEntryEx> EnumerateEntries(this FileSystemClient fs, string path)
|
||||
{
|
||||
return fs.EnumerateEntries(path, "*");
|
||||
}
|
||||
|
||||
public static IEnumerable<DirectoryEntryEx> EnumerateEntries(this FileSystemClient fs, string path, string searchPattern)
|
||||
{
|
||||
return fs.EnumerateEntries(path, searchPattern, SearchOptions.RecurseSubdirectories);
|
||||
}
|
||||
|
||||
public static IEnumerable<DirectoryEntryEx> EnumerateEntries(this FileSystemClient fs, string path, string searchPattern, SearchOptions searchOptions)
|
||||
{
|
||||
bool ignoreCase = searchOptions.HasFlag(SearchOptions.CaseInsensitive);
|
||||
bool recurse = searchOptions.HasFlag(SearchOptions.RecurseSubdirectories);
|
||||
|
||||
DirectoryEntry dirEntry = default;
|
||||
fs.OpenDirectory(out DirectoryHandle sourceHandle, path, OpenDirectoryMode.All).ThrowIfFailure();
|
||||
|
||||
using (sourceHandle)
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
fs.ReadDirectory(out long entriesRead, SpanHelpers.AsSpan(ref dirEntry), sourceHandle);
|
||||
if (entriesRead == 0) break;
|
||||
|
||||
DirectoryEntryEx entry = FileSystemExtensions.GetDirectoryEntryEx(ref dirEntry, path);
|
||||
|
||||
if (PathTools.MatchesPattern(searchPattern, entry.Name, ignoreCase))
|
||||
{
|
||||
yield return entry;
|
||||
}
|
||||
|
||||
if (entry.Type != DirectoryEntryType.Directory || !recurse) continue;
|
||||
|
||||
IEnumerable<DirectoryEntryEx> subEntries =
|
||||
fs.EnumerateEntries(PathTools.Combine(path, entry.Name), searchPattern, searchOptions);
|
||||
|
||||
foreach (DirectoryEntryEx subEntry in subEntries)
|
||||
{
|
||||
yield return subEntry;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static bool DirectoryExists(this FileSystemClient fs, string path)
|
||||
{
|
||||
Result rc = fs.GetEntryType(out DirectoryEntryType type, path);
|
||||
|
||||
return (rc.IsSuccess() && type == DirectoryEntryType.Directory);
|
||||
}
|
||||
|
||||
public static bool FileExists(this FileSystemClient fs, string path)
|
||||
{
|
||||
Result rc = fs.GetEntryType(out DirectoryEntryType type, path);
|
||||
|
||||
return (rc.IsSuccess() && type == DirectoryEntryType.File);
|
||||
}
|
||||
|
||||
public static void EnsureDirectoryExists(this FileSystemClient fs, string path)
|
||||
{
|
||||
path = PathTools.Normalize(path);
|
||||
if (fs.DirectoryExists(path)) return;
|
||||
|
||||
PathTools.GetMountNameLength(path, out int mountNameLength).ThrowIfFailure();
|
||||
|
||||
// Find the first subdirectory in the path that doesn't exist
|
||||
int i;
|
||||
for (i = path.Length - 1; i > mountNameLength + 2; i--)
|
||||
{
|
||||
if (path[i] == '/')
|
||||
{
|
||||
string subPath = path.Substring(0, i);
|
||||
|
||||
if (fs.DirectoryExists(subPath))
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// path[i] will be a '/', so skip that character
|
||||
i++;
|
||||
|
||||
// loop until `path.Length - 1` so CreateDirectory won't be called multiple
|
||||
// times on path if the last character in the path is a '/'
|
||||
for (; i < path.Length - 1; i++)
|
||||
{
|
||||
if (path[i] == '/')
|
||||
{
|
||||
string subPath = path.Substring(0, i);
|
||||
|
||||
fs.CreateDirectory(subPath);
|
||||
}
|
||||
}
|
||||
|
||||
fs.CreateDirectory(path);
|
||||
}
|
||||
|
||||
public static void CreateOrOverwriteFile(this FileSystemClient fs, string path, long size)
|
||||
{
|
||||
fs.CreateOrOverwriteFile(path, size, CreateFileOptions.None);
|
||||
}
|
||||
|
||||
public static void CreateOrOverwriteFile(this FileSystemClient fs, string path, long size, CreateFileOptions options)
|
||||
{
|
||||
path = PathTools.Normalize(path);
|
||||
|
||||
if (fs.FileExists(path)) fs.DeleteFile(path);
|
||||
|
||||
fs.CreateFile(path, size, CreateFileOptions.None);
|
||||
}
|
||||
|
||||
internal static bool IsEnabledFileSystemAccessorAccessLog(this FileSystemClient fs, string mountName)
|
||||
{
|
||||
if (fs.MountTable.Find(mountName, out FileSystemAccessor accessor).IsFailure())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return accessor.IsAccessLogEnabled;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,578 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.CompilerServices;
|
||||
using LibHac.Fs.Accessors;
|
||||
|
||||
namespace LibHac.Fs
|
||||
{
|
||||
public class FileSystemManager
|
||||
{
|
||||
internal Horizon Os { get; }
|
||||
internal ITimeSpanGenerator Time { get; }
|
||||
private IAccessLog AccessLog { get; set; }
|
||||
|
||||
internal MountTable MountTable { get; } = new MountTable();
|
||||
|
||||
private bool AccessLogEnabled { get; set; }
|
||||
|
||||
public FileSystemManager(Horizon os)
|
||||
{
|
||||
Os = os;
|
||||
}
|
||||
|
||||
public FileSystemManager(Horizon os, ITimeSpanGenerator timer)
|
||||
{
|
||||
Os = os;
|
||||
Time = timer;
|
||||
}
|
||||
|
||||
public void Register(string mountName, IFileSystem fileSystem)
|
||||
{
|
||||
var accessor = new FileSystemAccessor(mountName, fileSystem, this);
|
||||
|
||||
MountTable.Mount(accessor).ThrowIfFailure();
|
||||
|
||||
accessor.IsAccessLogEnabled = IsEnabledAccessLog();
|
||||
}
|
||||
|
||||
public void Unmount(string mountName)
|
||||
{
|
||||
MountTable.Find(mountName, out FileSystemAccessor fileSystem).ThrowIfFailure();
|
||||
|
||||
if (IsEnabledAccessLog() && fileSystem.IsAccessLogEnabled)
|
||||
{
|
||||
TimeSpan startTime = Time.GetCurrent();
|
||||
MountTable.Unmount(mountName);
|
||||
TimeSpan endTime = Time.GetCurrent();
|
||||
|
||||
OutputAccessLog(startTime, endTime, $", name: \"{mountName}\"");
|
||||
}
|
||||
else
|
||||
{
|
||||
MountTable.Unmount(mountName);
|
||||
}
|
||||
}
|
||||
|
||||
public void SetAccessLog(bool isEnabled, IAccessLog accessLog = null)
|
||||
{
|
||||
AccessLogEnabled = isEnabled;
|
||||
|
||||
if (accessLog != null) AccessLog = accessLog;
|
||||
}
|
||||
|
||||
public void CreateDirectory(string path)
|
||||
{
|
||||
FindFileSystem(path.AsSpan(), out FileSystemAccessor fileSystem, out ReadOnlySpan<char> subPath)
|
||||
.ThrowIfFailure();
|
||||
|
||||
if (IsEnabledAccessLog() && fileSystem.IsAccessLogEnabled)
|
||||
{
|
||||
TimeSpan startTime = Time.GetCurrent();
|
||||
fileSystem.CreateDirectory(subPath.ToString());
|
||||
TimeSpan endTime = Time.GetCurrent();
|
||||
|
||||
OutputAccessLog(startTime, endTime, $", path: \"{path}\"");
|
||||
}
|
||||
else
|
||||
{
|
||||
fileSystem.CreateDirectory(subPath.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
public void CreateFile(string path, long size)
|
||||
{
|
||||
CreateFile(path, size, CreateFileOptions.None);
|
||||
}
|
||||
|
||||
public void CreateFile(string path, long size, CreateFileOptions options)
|
||||
{
|
||||
FindFileSystem(path.AsSpan(), out FileSystemAccessor fileSystem, out ReadOnlySpan<char> subPath)
|
||||
.ThrowIfFailure();
|
||||
|
||||
if (IsEnabledAccessLog() && fileSystem.IsAccessLogEnabled)
|
||||
{
|
||||
TimeSpan startTime = Time.GetCurrent();
|
||||
fileSystem.CreateFile(subPath.ToString(), size, options);
|
||||
TimeSpan endTime = Time.GetCurrent();
|
||||
|
||||
OutputAccessLog(startTime, endTime, $", path: \"{path}\", size: {size}");
|
||||
}
|
||||
else
|
||||
{
|
||||
fileSystem.CreateFile(subPath.ToString(), size, options);
|
||||
}
|
||||
}
|
||||
|
||||
public void DeleteDirectory(string path)
|
||||
{
|
||||
FindFileSystem(path.AsSpan(), out FileSystemAccessor fileSystem, out ReadOnlySpan<char> subPath)
|
||||
.ThrowIfFailure();
|
||||
|
||||
if (IsEnabledAccessLog() && fileSystem.IsAccessLogEnabled)
|
||||
{
|
||||
TimeSpan startTime = Time.GetCurrent();
|
||||
fileSystem.DeleteDirectory(subPath.ToString());
|
||||
TimeSpan endTime = Time.GetCurrent();
|
||||
|
||||
OutputAccessLog(startTime, endTime, $", path: \"{path}\"");
|
||||
}
|
||||
else
|
||||
{
|
||||
fileSystem.DeleteDirectory(subPath.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
public void DeleteDirectoryRecursively(string path)
|
||||
{
|
||||
FindFileSystem(path.AsSpan(), out FileSystemAccessor fileSystem, out ReadOnlySpan<char> subPath)
|
||||
.ThrowIfFailure();
|
||||
|
||||
if (IsEnabledAccessLog() && fileSystem.IsAccessLogEnabled)
|
||||
{
|
||||
TimeSpan startTime = Time.GetCurrent();
|
||||
fileSystem.DeleteDirectoryRecursively(subPath.ToString());
|
||||
TimeSpan endTime = Time.GetCurrent();
|
||||
|
||||
OutputAccessLog(startTime, endTime, $", path: \"{path}\"");
|
||||
}
|
||||
else
|
||||
{
|
||||
fileSystem.DeleteDirectoryRecursively(subPath.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
public void CleanDirectoryRecursively(string path)
|
||||
{
|
||||
FindFileSystem(path.AsSpan(), out FileSystemAccessor fileSystem, out ReadOnlySpan<char> subPath)
|
||||
.ThrowIfFailure();
|
||||
|
||||
if (IsEnabledAccessLog() && fileSystem.IsAccessLogEnabled)
|
||||
{
|
||||
TimeSpan startTime = Time.GetCurrent();
|
||||
fileSystem.CleanDirectoryRecursively(subPath.ToString());
|
||||
TimeSpan endTime = Time.GetCurrent();
|
||||
|
||||
OutputAccessLog(startTime, endTime, $", path: \"{path}\"");
|
||||
}
|
||||
else
|
||||
{
|
||||
fileSystem.CleanDirectoryRecursively(subPath.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
public void DeleteFile(string path)
|
||||
{
|
||||
FindFileSystem(path.AsSpan(), out FileSystemAccessor fileSystem, out ReadOnlySpan<char> subPath)
|
||||
.ThrowIfFailure();
|
||||
|
||||
if (IsEnabledAccessLog() && fileSystem.IsAccessLogEnabled)
|
||||
{
|
||||
TimeSpan startTime = Time.GetCurrent();
|
||||
fileSystem.DeleteFile(subPath.ToString());
|
||||
TimeSpan endTime = Time.GetCurrent();
|
||||
|
||||
OutputAccessLog(startTime, endTime, $", path: \"{path}\"");
|
||||
}
|
||||
else
|
||||
{
|
||||
fileSystem.DeleteFile(subPath.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
public void RenameDirectory(string oldPath, string newPath)
|
||||
{
|
||||
FindFileSystem(oldPath.AsSpan(), out FileSystemAccessor oldFileSystem, out ReadOnlySpan<char> oldSubPath)
|
||||
.ThrowIfFailure();
|
||||
|
||||
FindFileSystem(newPath.AsSpan(), out FileSystemAccessor newFileSystem, out ReadOnlySpan<char> newSubPath)
|
||||
.ThrowIfFailure();
|
||||
|
||||
if (oldFileSystem != newFileSystem)
|
||||
{
|
||||
ThrowHelper.ThrowResult(ResultFs.DifferentDestFileSystem);
|
||||
}
|
||||
|
||||
if (IsEnabledAccessLog() && oldFileSystem.IsAccessLogEnabled)
|
||||
{
|
||||
TimeSpan startTime = Time.GetCurrent();
|
||||
oldFileSystem.RenameDirectory(oldSubPath.ToString(), newSubPath.ToString());
|
||||
TimeSpan endTime = Time.GetCurrent();
|
||||
|
||||
OutputAccessLog(startTime, endTime, $", path: \"{oldPath}\", new_path: \"{newPath}\"");
|
||||
}
|
||||
else
|
||||
{
|
||||
oldFileSystem.RenameDirectory(oldSubPath.ToString(), newSubPath.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
public void RenameFile(string oldPath, string newPath)
|
||||
{
|
||||
FindFileSystem(oldPath.AsSpan(), out FileSystemAccessor oldFileSystem, out ReadOnlySpan<char> oldSubPath)
|
||||
.ThrowIfFailure();
|
||||
|
||||
FindFileSystem(newPath.AsSpan(), out FileSystemAccessor newFileSystem, out ReadOnlySpan<char> newSubPath)
|
||||
.ThrowIfFailure();
|
||||
|
||||
if (oldFileSystem != newFileSystem)
|
||||
{
|
||||
ThrowHelper.ThrowResult(ResultFs.DifferentDestFileSystem);
|
||||
}
|
||||
|
||||
if (IsEnabledAccessLog() && oldFileSystem.IsAccessLogEnabled)
|
||||
{
|
||||
TimeSpan startTime = Time.GetCurrent();
|
||||
oldFileSystem.RenameFile(oldSubPath.ToString(), newSubPath.ToString());
|
||||
TimeSpan endTime = Time.GetCurrent();
|
||||
|
||||
OutputAccessLog(startTime, endTime, $", path: \"{oldPath}\", new_path: \"{newPath}\"");
|
||||
}
|
||||
else
|
||||
{
|
||||
oldFileSystem.RenameFile(oldSubPath.ToString(), newSubPath.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
public DirectoryEntryType GetEntryType(string path)
|
||||
{
|
||||
FindFileSystem(path.AsSpan(), out FileSystemAccessor fileSystem, out ReadOnlySpan<char> subPath)
|
||||
.ThrowIfFailure();
|
||||
|
||||
DirectoryEntryType type;
|
||||
|
||||
if (IsEnabledAccessLog() && fileSystem.IsAccessLogEnabled)
|
||||
{
|
||||
TimeSpan startTime = Time.GetCurrent();
|
||||
type = fileSystem.GetEntryType(subPath.ToString());
|
||||
TimeSpan endTime = Time.GetCurrent();
|
||||
|
||||
OutputAccessLog(startTime, endTime, $", path: \"{path}\"");
|
||||
}
|
||||
else
|
||||
{
|
||||
type = fileSystem.GetEntryType(subPath.ToString());
|
||||
}
|
||||
|
||||
return type;
|
||||
}
|
||||
|
||||
public FileHandle OpenFile(string path, OpenMode mode)
|
||||
{
|
||||
FindFileSystem(path.AsSpan(), out FileSystemAccessor fileSystem, out ReadOnlySpan<char> subPath)
|
||||
.ThrowIfFailure();
|
||||
|
||||
FileHandle handle;
|
||||
|
||||
if (IsEnabledAccessLog() && fileSystem.IsAccessLogEnabled)
|
||||
{
|
||||
TimeSpan startTime = Time.GetCurrent();
|
||||
FileAccessor file = fileSystem.OpenFile(subPath.ToString(), mode);
|
||||
handle = new FileHandle(file);
|
||||
TimeSpan endTime = Time.GetCurrent();
|
||||
|
||||
OutputAccessLog(startTime, endTime, handle, $", path: \"{path}\", open_mode: {mode}");
|
||||
}
|
||||
else
|
||||
{
|
||||
FileAccessor file = fileSystem.OpenFile(subPath.ToString(), mode);
|
||||
handle = new FileHandle(file);
|
||||
}
|
||||
|
||||
return handle;
|
||||
}
|
||||
|
||||
public DirectoryHandle OpenDirectory(string path, OpenDirectoryMode mode)
|
||||
{
|
||||
FindFileSystem(path.AsSpan(), out FileSystemAccessor fileSystem, out ReadOnlySpan<char> subPath)
|
||||
.ThrowIfFailure();
|
||||
|
||||
DirectoryHandle handle;
|
||||
|
||||
if (IsEnabledAccessLog() && fileSystem.IsAccessLogEnabled)
|
||||
{
|
||||
TimeSpan startTime = Time.GetCurrent();
|
||||
DirectoryAccessor dir = fileSystem.OpenDirectory(subPath.ToString(), mode);
|
||||
handle = new DirectoryHandle(dir);
|
||||
TimeSpan endTime = Time.GetCurrent();
|
||||
|
||||
OutputAccessLog(startTime, endTime, handle, $", path: \"{path}\", open_mode: {mode}");
|
||||
}
|
||||
else
|
||||
{
|
||||
DirectoryAccessor dir = fileSystem.OpenDirectory(subPath.ToString(), mode);
|
||||
handle = new DirectoryHandle(dir);
|
||||
}
|
||||
|
||||
return handle;
|
||||
}
|
||||
|
||||
public long GetFreeSpaceSize(string path)
|
||||
{
|
||||
FindFileSystem(path.AsSpan(), out FileSystemAccessor fileSystem, out ReadOnlySpan<char> subPath)
|
||||
.ThrowIfFailure();
|
||||
|
||||
return fileSystem.GetFreeSpaceSize(subPath.ToString());
|
||||
}
|
||||
|
||||
public long GetTotalSpaceSize(string path)
|
||||
{
|
||||
FindFileSystem(path.AsSpan(), out FileSystemAccessor fileSystem, out ReadOnlySpan<char> subPath)
|
||||
.ThrowIfFailure();
|
||||
|
||||
return fileSystem.GetTotalSpaceSize(subPath.ToString());
|
||||
}
|
||||
|
||||
public FileTimeStampRaw GetFileTimeStamp(string path)
|
||||
{
|
||||
FindFileSystem(path.AsSpan(), out FileSystemAccessor fileSystem, out ReadOnlySpan<char> subPath)
|
||||
.ThrowIfFailure();
|
||||
|
||||
return fileSystem.GetFileTimeStampRaw(subPath.ToString());
|
||||
}
|
||||
|
||||
public void Commit(string mountName)
|
||||
{
|
||||
MountTable.Find(mountName, out FileSystemAccessor fileSystem).ThrowIfFailure();
|
||||
|
||||
if (IsEnabledAccessLog() && fileSystem.IsAccessLogEnabled)
|
||||
{
|
||||
TimeSpan startTime = Time.GetCurrent();
|
||||
fileSystem.Commit();
|
||||
TimeSpan endTime = Time.GetCurrent();
|
||||
|
||||
OutputAccessLog(startTime, endTime, $", name: \"{mountName}\"");
|
||||
}
|
||||
else
|
||||
{
|
||||
fileSystem.Commit();
|
||||
}
|
||||
}
|
||||
|
||||
// ==========================
|
||||
// Operations on file handles
|
||||
// ==========================
|
||||
public int ReadFile(FileHandle handle, Span<byte> destination, long offset)
|
||||
{
|
||||
return ReadFile(handle, destination, offset, ReadOption.None);
|
||||
}
|
||||
|
||||
public int ReadFile(FileHandle handle, Span<byte> destination, long offset, ReadOption option)
|
||||
{
|
||||
int bytesRead;
|
||||
|
||||
if (IsEnabledAccessLog() && IsEnabledHandleAccessLog(handle))
|
||||
{
|
||||
TimeSpan startTime = Time.GetCurrent();
|
||||
bytesRead = handle.File.Read(destination, offset, option);
|
||||
TimeSpan endTime = Time.GetCurrent();
|
||||
|
||||
OutputAccessLog(startTime, endTime, handle, $", offset: {offset}, size: {destination.Length}");
|
||||
}
|
||||
else
|
||||
{
|
||||
bytesRead = handle.File.Read(destination, offset, option);
|
||||
}
|
||||
|
||||
return bytesRead;
|
||||
}
|
||||
|
||||
public void WriteFile(FileHandle handle, ReadOnlySpan<byte> source, long offset)
|
||||
{
|
||||
WriteFile(handle, source, offset, WriteOption.None);
|
||||
}
|
||||
|
||||
public void WriteFile(FileHandle handle, ReadOnlySpan<byte> source, long offset, WriteOption option)
|
||||
{
|
||||
if (IsEnabledAccessLog() && IsEnabledHandleAccessLog(handle))
|
||||
{
|
||||
TimeSpan startTime = Time.GetCurrent();
|
||||
handle.File.Write(source, offset, option);
|
||||
TimeSpan endTime = Time.GetCurrent();
|
||||
|
||||
string optionString = (option & WriteOption.Flush) == 0 ? "" : $", write_option: {option}";
|
||||
|
||||
OutputAccessLog(startTime, endTime, handle, $", offset: {offset}, size: {source.Length}{optionString}");
|
||||
}
|
||||
else
|
||||
{
|
||||
handle.File.Write(source, offset, option);
|
||||
}
|
||||
}
|
||||
|
||||
public void FlushFile(FileHandle handle)
|
||||
{
|
||||
if (IsEnabledAccessLog() && IsEnabledHandleAccessLog(handle))
|
||||
{
|
||||
TimeSpan startTime = Time.GetCurrent();
|
||||
handle.File.Flush();
|
||||
TimeSpan endTime = Time.GetCurrent();
|
||||
|
||||
OutputAccessLog(startTime, endTime, handle, string.Empty);
|
||||
}
|
||||
else
|
||||
{
|
||||
handle.File.Flush();
|
||||
}
|
||||
}
|
||||
|
||||
public long GetFileSize(FileHandle handle)
|
||||
{
|
||||
return handle.File.GetSize();
|
||||
}
|
||||
|
||||
public void SetFileSize(FileHandle handle, long size)
|
||||
{
|
||||
if (IsEnabledAccessLog() && IsEnabledHandleAccessLog(handle))
|
||||
{
|
||||
TimeSpan startTime = Time.GetCurrent();
|
||||
handle.File.SetSize(size);
|
||||
TimeSpan endTime = Time.GetCurrent();
|
||||
|
||||
OutputAccessLog(startTime, endTime, handle, $", size: {size}");
|
||||
}
|
||||
else
|
||||
{
|
||||
handle.File.SetSize(size);
|
||||
}
|
||||
}
|
||||
|
||||
public OpenMode GetFileOpenMode(FileHandle handle)
|
||||
{
|
||||
return handle.File.OpenMode;
|
||||
}
|
||||
|
||||
public void CloseFile(FileHandle handle)
|
||||
{
|
||||
if (IsEnabledAccessLog() && IsEnabledHandleAccessLog(handle))
|
||||
{
|
||||
TimeSpan startTime = Time.GetCurrent();
|
||||
handle.File.Dispose();
|
||||
TimeSpan endTime = Time.GetCurrent();
|
||||
|
||||
OutputAccessLog(startTime, endTime, handle, string.Empty);
|
||||
}
|
||||
else
|
||||
{
|
||||
handle.File.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
// ==========================
|
||||
// Operations on directory handles
|
||||
// ==========================
|
||||
public int GetDirectoryEntryCount(DirectoryHandle handle)
|
||||
{
|
||||
return handle.Directory.GetEntryCount();
|
||||
}
|
||||
|
||||
public IEnumerable<DirectoryEntry> ReadDirectory(DirectoryHandle handle)
|
||||
{
|
||||
if (IsEnabledAccessLog() && IsEnabledHandleAccessLog(handle))
|
||||
{
|
||||
TimeSpan startTime = Time.GetCurrent();
|
||||
IEnumerable<DirectoryEntry> entries = handle.Directory.Read();
|
||||
TimeSpan endTime = Time.GetCurrent();
|
||||
|
||||
OutputAccessLog(startTime, endTime, handle, string.Empty);
|
||||
return entries;
|
||||
}
|
||||
|
||||
return handle.Directory.Read();
|
||||
}
|
||||
|
||||
public void CloseDirectory(DirectoryHandle handle)
|
||||
{
|
||||
if (IsEnabledAccessLog() && IsEnabledHandleAccessLog(handle))
|
||||
{
|
||||
TimeSpan startTime = Time.GetCurrent();
|
||||
handle.Directory.Dispose();
|
||||
TimeSpan endTime = Time.GetCurrent();
|
||||
|
||||
OutputAccessLog(startTime, endTime, handle, string.Empty);
|
||||
}
|
||||
else
|
||||
{
|
||||
handle.Directory.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
internal Result FindFileSystem(ReadOnlySpan<char> path, out FileSystemAccessor fileSystem, out ReadOnlySpan<char> subPath)
|
||||
{
|
||||
fileSystem = default;
|
||||
|
||||
Result result = GetMountName(path, out ReadOnlySpan<char> mountName, out subPath);
|
||||
if (result.IsFailure()) return result;
|
||||
|
||||
result = MountTable.Find(mountName.ToString(), out fileSystem);
|
||||
if (result.IsFailure()) return result;
|
||||
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
internal static Result GetMountName(ReadOnlySpan<char> path, out ReadOnlySpan<char> mountName, out ReadOnlySpan<char> subPath)
|
||||
{
|
||||
int mountLen = 0;
|
||||
int maxMountLen = Math.Min(path.Length, PathTools.MountNameLength);
|
||||
|
||||
for (int i = 0; i < maxMountLen; i++)
|
||||
{
|
||||
if (path[i] == PathTools.MountSeparator)
|
||||
{
|
||||
mountLen = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (mountLen == 0)
|
||||
{
|
||||
mountName = default;
|
||||
subPath = default;
|
||||
|
||||
return ResultFs.InvalidMountName;
|
||||
}
|
||||
|
||||
mountName = path.Slice(0, mountLen);
|
||||
|
||||
if (mountLen + 2 < path.Length)
|
||||
{
|
||||
subPath = path.Slice(mountLen + 2);
|
||||
}
|
||||
else
|
||||
{
|
||||
subPath = default;
|
||||
}
|
||||
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
internal bool IsEnabledAccessLog()
|
||||
{
|
||||
return AccessLogEnabled && AccessLog != null && Time != null;
|
||||
}
|
||||
|
||||
internal bool IsEnabledHandleAccessLog(FileHandle handle)
|
||||
{
|
||||
return handle.File.Parent.IsAccessLogEnabled;
|
||||
}
|
||||
|
||||
internal bool IsEnabledHandleAccessLog(DirectoryHandle handle)
|
||||
{
|
||||
return handle.Directory.Parent.IsAccessLogEnabled;
|
||||
}
|
||||
|
||||
internal void OutputAccessLog(TimeSpan startTime, TimeSpan endTime, string message, [CallerMemberName] string caller = "")
|
||||
{
|
||||
AccessLog.Log(startTime, endTime, 0, message, caller);
|
||||
}
|
||||
|
||||
internal void OutputAccessLog(TimeSpan startTime, TimeSpan endTime, FileHandle handle, string message, [CallerMemberName] string caller = "")
|
||||
{
|
||||
AccessLog.Log(startTime, endTime, handle.GetId(), message, caller);
|
||||
}
|
||||
|
||||
internal void OutputAccessLog(TimeSpan startTime, TimeSpan endTime, DirectoryHandle handle, string message, [CallerMemberName] string caller = "")
|
||||
{
|
||||
AccessLog.Log(startTime, endTime, handle.GetId(), message, caller);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,177 +0,0 @@
|
||||
using System;
|
||||
using System.Buffers;
|
||||
using System.Collections.Generic;
|
||||
using LibHac.Fs.Accessors;
|
||||
|
||||
namespace LibHac.Fs
|
||||
{
|
||||
public static class FileSystemManagerUtils
|
||||
{
|
||||
public static void CopyDirectory(this FileSystemManager fs, string sourcePath, string destPath,
|
||||
CreateFileOptions options = CreateFileOptions.None, IProgressReport logger = null)
|
||||
{
|
||||
using (DirectoryHandle sourceHandle = fs.OpenDirectory(sourcePath, OpenDirectoryMode.All))
|
||||
{
|
||||
foreach (DirectoryEntry entry in fs.ReadDirectory(sourceHandle))
|
||||
{
|
||||
string subSrcPath = PathTools.Normalize(PathTools.Combine(sourcePath, entry.Name));
|
||||
string subDstPath = PathTools.Normalize(PathTools.Combine(destPath, entry.Name));
|
||||
|
||||
if (entry.Type == DirectoryEntryType.Directory)
|
||||
{
|
||||
fs.EnsureDirectoryExists(subDstPath);
|
||||
|
||||
fs.CopyDirectory(subSrcPath, subDstPath, options, logger);
|
||||
}
|
||||
|
||||
if (entry.Type == DirectoryEntryType.File)
|
||||
{
|
||||
logger?.LogMessage(subSrcPath);
|
||||
fs.CreateOrOverwriteFile(subDstPath, entry.Size, options);
|
||||
|
||||
fs.CopyFile(subSrcPath, subDstPath, logger);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void CopyFile(this FileSystemManager fs, string sourcePath, string destPath, IProgressReport logger = null)
|
||||
{
|
||||
using (FileHandle sourceHandle = fs.OpenFile(sourcePath, OpenMode.Read))
|
||||
using (FileHandle destHandle = fs.OpenFile(destPath, OpenMode.Write | OpenMode.Append))
|
||||
{
|
||||
const int maxBufferSize = 0x10000;
|
||||
|
||||
long fileSize = fs.GetFileSize(sourceHandle);
|
||||
int bufferSize = (int)Math.Min(maxBufferSize, fileSize);
|
||||
|
||||
logger?.SetTotal(fileSize);
|
||||
|
||||
byte[] buffer = ArrayPool<byte>.Shared.Rent(bufferSize);
|
||||
try
|
||||
{
|
||||
for (long offset = 0; offset < fileSize; offset += bufferSize)
|
||||
{
|
||||
int toRead = (int)Math.Min(fileSize - offset, bufferSize);
|
||||
Span<byte> buf = buffer.AsSpan(0, toRead);
|
||||
|
||||
fs.ReadFile(sourceHandle, buf, offset);
|
||||
fs.WriteFile(destHandle, buf, offset);
|
||||
|
||||
logger?.ReportAdd(toRead);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
ArrayPool<byte>.Shared.Return(buffer);
|
||||
logger?.SetTotal(0);
|
||||
}
|
||||
|
||||
fs.FlushFile(destHandle);
|
||||
}
|
||||
}
|
||||
|
||||
public static IEnumerable<DirectoryEntry> EnumerateEntries(this FileSystemManager fs, string path)
|
||||
{
|
||||
return fs.EnumerateEntries(path, "*");
|
||||
}
|
||||
|
||||
public static IEnumerable<DirectoryEntry> 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)
|
||||
{
|
||||
bool ignoreCase = searchOptions.HasFlag(SearchOptions.CaseInsensitive);
|
||||
bool recurse = searchOptions.HasFlag(SearchOptions.RecurseSubdirectories);
|
||||
|
||||
using (DirectoryHandle sourceHandle = fs.OpenDirectory(path, OpenDirectoryMode.All))
|
||||
{
|
||||
foreach (DirectoryEntry entry in fs.ReadDirectory(sourceHandle))
|
||||
{
|
||||
if (PathTools.MatchesPattern(searchPattern, entry.Name, ignoreCase))
|
||||
{
|
||||
yield return entry;
|
||||
}
|
||||
|
||||
if (entry.Type != DirectoryEntryType.Directory || !recurse) continue;
|
||||
|
||||
string subPath = PathTools.Normalize(PathTools.Combine(path, entry.Name));
|
||||
|
||||
IEnumerable<DirectoryEntry> subEntries = fs.EnumerateEntries(subPath, searchPattern, searchOptions);
|
||||
|
||||
foreach (DirectoryEntry subEntry in subEntries)
|
||||
{
|
||||
subEntry.FullPath = PathTools.Combine(path, subEntry.Name);
|
||||
yield return subEntry;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static bool DirectoryExists(this FileSystemManager fs, string path)
|
||||
{
|
||||
return fs.GetEntryType(path) == DirectoryEntryType.Directory;
|
||||
}
|
||||
|
||||
public static bool FileExists(this FileSystemManager fs, string path)
|
||||
{
|
||||
return fs.GetEntryType(path) == DirectoryEntryType.File;
|
||||
}
|
||||
|
||||
public static void EnsureDirectoryExists(this FileSystemManager fs, string path)
|
||||
{
|
||||
path = PathTools.Normalize(path);
|
||||
if (fs.DirectoryExists(path)) return;
|
||||
|
||||
PathTools.GetMountNameLength(path, out int mountNameLength).ThrowIfFailure();
|
||||
|
||||
// Find the first subdirectory in the path that doesn't exist
|
||||
int i;
|
||||
for (i = path.Length - 1; i > mountNameLength + 2; i--)
|
||||
{
|
||||
if (path[i] == '/')
|
||||
{
|
||||
string subPath = path.Substring(0, i);
|
||||
|
||||
if (fs.DirectoryExists(subPath))
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// path[i] will be a '/', so skip that character
|
||||
i++;
|
||||
|
||||
// loop until `path.Length - 1` so CreateDirectory won't be called multiple
|
||||
// times on path if the last character in the path is a '/'
|
||||
for (; i < path.Length - 1; i++)
|
||||
{
|
||||
if (path[i] == '/')
|
||||
{
|
||||
string subPath = path.Substring(0, i);
|
||||
|
||||
fs.CreateDirectory(subPath);
|
||||
}
|
||||
}
|
||||
|
||||
fs.CreateDirectory(path);
|
||||
}
|
||||
|
||||
public static void CreateOrOverwriteFile(this FileSystemManager fs, string path, long size)
|
||||
{
|
||||
fs.CreateOrOverwriteFile(path, size, CreateFileOptions.None);
|
||||
}
|
||||
|
||||
public static void CreateOrOverwriteFile(this FileSystemManager fs, string path, long size, CreateFileOptions options)
|
||||
{
|
||||
path = PathTools.Normalize(path);
|
||||
|
||||
if (fs.FileExists(path)) fs.DeleteFile(path);
|
||||
|
||||
fs.CreateFile(path, size, CreateFileOptions.None);
|
||||
}
|
||||
}
|
||||
}
|
130
src/LibHac/Fs/FsEnums.cs
Normal file
130
src/LibHac/Fs/FsEnums.cs
Normal file
@ -0,0 +1,130 @@
|
||||
using System;
|
||||
|
||||
namespace LibHac.Fs
|
||||
{
|
||||
public enum BisPartitionId
|
||||
{
|
||||
BootPartition1Root = 0,
|
||||
BootPartition2Root = 10,
|
||||
UserDataRoot = 20,
|
||||
BootConfigAndPackage2Part1 = 21,
|
||||
BootConfigAndPackage2Part2 = 22,
|
||||
BootConfigAndPackage2Part3 = 23,
|
||||
BootConfigAndPackage2Part4 = 24,
|
||||
BootConfigAndPackage2Part5 = 25,
|
||||
BootConfigAndPackage2Part6 = 26,
|
||||
CalibrationBinary = 27,
|
||||
CalibrationFile = 28,
|
||||
SafeMode = 29,
|
||||
User = 30,
|
||||
System = 31,
|
||||
SystemProperEncryption = 32,
|
||||
SystemProperPartition = 33,
|
||||
Invalid = 35
|
||||
}
|
||||
|
||||
public enum ContentStorageId
|
||||
{
|
||||
System = 0,
|
||||
User = 1,
|
||||
SdCard = 2
|
||||
}
|
||||
|
||||
public enum GameCardPartition
|
||||
{
|
||||
Update = 0,
|
||||
Normal = 1,
|
||||
Secure = 2,
|
||||
Logo = 3
|
||||
}
|
||||
|
||||
public enum GameCardPartitionRaw
|
||||
{
|
||||
Normal = 0,
|
||||
Secure = 1,
|
||||
Writable = 2
|
||||
}
|
||||
|
||||
public enum SaveDataSpaceId : byte
|
||||
{
|
||||
System = 0,
|
||||
User = 1,
|
||||
SdSystem = 2,
|
||||
TemporaryStorage = 3,
|
||||
SdCache = 4,
|
||||
ProperSystem = 100,
|
||||
Safe = 101,
|
||||
BisAuto = 127
|
||||
}
|
||||
|
||||
public enum CustomStorageId
|
||||
{
|
||||
User = 0,
|
||||
SdCard = 1
|
||||
}
|
||||
|
||||
public enum FileSystemType
|
||||
{
|
||||
Code = 0,
|
||||
Data = 1,
|
||||
Logo = 2,
|
||||
ContentControl = 3,
|
||||
ContentManual = 4,
|
||||
ContentMeta = 5,
|
||||
ContentData = 6,
|
||||
ApplicationPackage = 7,
|
||||
RegisteredUpdate = 8
|
||||
}
|
||||
|
||||
public enum SaveMetaType : byte
|
||||
{
|
||||
None = 0,
|
||||
Thumbnail = 1,
|
||||
ExtensionInfo = 2
|
||||
}
|
||||
|
||||
public enum ImageDirectoryId
|
||||
{
|
||||
Nand = 0,
|
||||
SdCard = 1
|
||||
}
|
||||
|
||||
public enum CloudBackupWorkStorageId
|
||||
{
|
||||
Nand = 0,
|
||||
SdCard = 1
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Specifies which operations are available on an <see cref="IFile"/>.
|
||||
/// </summary>
|
||||
[Flags]
|
||||
public enum OpenMode
|
||||
{
|
||||
Read = 1,
|
||||
Write = 2,
|
||||
AllowAppend = 4,
|
||||
ReadWrite = Read | Write
|
||||
}
|
||||
|
||||
[Flags]
|
||||
public enum ReadOption
|
||||
{
|
||||
None = 0
|
||||
}
|
||||
|
||||
[Flags]
|
||||
public enum WriteOption
|
||||
{
|
||||
None = 0,
|
||||
Flush = 1
|
||||
}
|
||||
|
||||
public enum OperationId
|
||||
{
|
||||
Clear = 0,
|
||||
ClearSignature = 1,
|
||||
InvalidateCache = 2,
|
||||
QueryRange = 3
|
||||
}
|
||||
}
|
130
src/LibHac/Fs/GameCard.cs
Normal file
130
src/LibHac/Fs/GameCard.cs
Normal file
@ -0,0 +1,130 @@
|
||||
using System;
|
||||
using LibHac.Common;
|
||||
using LibHac.FsService;
|
||||
|
||||
namespace LibHac.Fs
|
||||
{
|
||||
public static class GameCard
|
||||
{
|
||||
public static Result OpenGameCardPartition(this FileSystemClient fs, out IStorage storage,
|
||||
GameCardHandle handle, GameCardPartitionRaw partitionType)
|
||||
{
|
||||
IFileSystemProxy fsProxy = fs.GetFileSystemProxyServiceObject();
|
||||
|
||||
return fsProxy.OpenGameCardStorage(out storage, handle, partitionType);
|
||||
}
|
||||
|
||||
public static Result MountGameCardPartition(this FileSystemClient fs, U8Span mountName, GameCardHandle handle,
|
||||
GameCardPartition partitionId)
|
||||
{
|
||||
Result rc = MountHelpers.CheckMountNameAcceptingReservedMountName(mountName);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
IFileSystemProxy fsProxy = fs.GetFileSystemProxyServiceObject();
|
||||
|
||||
rc = fsProxy.OpenGameCardFileSystem(out IFileSystem cardFs, handle, partitionId);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
var mountNameGenerator = new GameCardCommonMountNameGenerator(handle, partitionId);
|
||||
|
||||
return fs.Register(mountName, cardFs, mountNameGenerator);
|
||||
}
|
||||
|
||||
public static Result GetGameCardHandle(this FileSystemClient fs, out GameCardHandle handle)
|
||||
{
|
||||
handle = default;
|
||||
|
||||
IFileSystemProxy fsProxy = fs.GetFileSystemProxyServiceObject();
|
||||
|
||||
Result rc = fsProxy.OpenDeviceOperator(out IDeviceOperator deviceOperator);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
return deviceOperator.GetGameCardHandle(out handle);
|
||||
}
|
||||
|
||||
public static bool IsGameCardInserted(this FileSystemClient fs)
|
||||
{
|
||||
IFileSystemProxy fsProxy = fs.GetFileSystemProxyServiceObject();
|
||||
|
||||
Result rc = fsProxy.OpenDeviceOperator(out IDeviceOperator deviceOperator);
|
||||
if (rc.IsFailure()) throw new LibHacException("Abort");
|
||||
|
||||
rc = deviceOperator.IsGameCardInserted(out bool isInserted);
|
||||
if (rc.IsFailure()) throw new LibHacException("Abort");
|
||||
|
||||
return isInserted;
|
||||
}
|
||||
|
||||
public static long GetGameCardSizeBytes(GameCardSize size)
|
||||
{
|
||||
switch (size)
|
||||
{
|
||||
case GameCardSize.Size1Gb: return 0x3B800000;
|
||||
case GameCardSize.Size2Gb: return 0x77000000;
|
||||
case GameCardSize.Size4Gb: return 0xEE000000;
|
||||
case GameCardSize.Size8Gb: return 0x1DC000000;
|
||||
case GameCardSize.Size16Gb: return 0x3B8000000;
|
||||
case GameCardSize.Size32Gb: return 0x770000000;
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException(nameof(size), size, null);
|
||||
}
|
||||
}
|
||||
|
||||
public static long CardPageToOffset(int page)
|
||||
{
|
||||
return (long)page << 9;
|
||||
}
|
||||
|
||||
private class GameCardCommonMountNameGenerator : ICommonMountNameGenerator
|
||||
{
|
||||
private GameCardHandle Handle { get; }
|
||||
private GameCardPartition PartitionId { get; }
|
||||
|
||||
public GameCardCommonMountNameGenerator(GameCardHandle handle, GameCardPartition partitionId)
|
||||
{
|
||||
Handle = handle;
|
||||
PartitionId = partitionId;
|
||||
}
|
||||
|
||||
public Result Generate(Span<byte> nameBuffer)
|
||||
{
|
||||
char letter = GetPartitionMountLetter(PartitionId);
|
||||
|
||||
string mountName = $"@Gc{letter}{Handle.Value:x8}";
|
||||
new U8Span(mountName).Value.CopyTo(nameBuffer);
|
||||
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
private static char GetPartitionMountLetter(GameCardPartition partition)
|
||||
{
|
||||
switch (partition)
|
||||
{
|
||||
case GameCardPartition.Update: return 'U';
|
||||
case GameCardPartition.Normal: return 'N';
|
||||
case GameCardPartition.Secure: return 'S';
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException(nameof(partition), partition, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public enum GameCardSize
|
||||
{
|
||||
Size1Gb = 0xFA,
|
||||
Size2Gb = 0xF8,
|
||||
Size4Gb = 0xF0,
|
||||
Size8Gb = 0xE0,
|
||||
Size16Gb = 0xE1,
|
||||
Size32Gb = 0xE2
|
||||
}
|
||||
|
||||
[Flags]
|
||||
public enum GameCardAttribute : byte
|
||||
{
|
||||
AutoBoot = 1 << 0,
|
||||
HistoryErase = 1 << 1,
|
||||
RepairTool = 1 << 2
|
||||
}
|
||||
}
|
10
src/LibHac/Fs/IAccessLog.cs
Normal file
10
src/LibHac/Fs/IAccessLog.cs
Normal file
@ -0,0 +1,10 @@
|
||||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace LibHac.Fs
|
||||
{
|
||||
public interface IAccessLog
|
||||
{
|
||||
void Log(Result result, TimeSpan startTime, TimeSpan endTime, int handleId, string message, [CallerMemberName] string caller = "");
|
||||
}
|
||||
}
|
@ -2,8 +2,9 @@
|
||||
{
|
||||
public interface IAttributeFileSystem : IFileSystem
|
||||
{
|
||||
NxFileAttributes GetFileAttributes(string path);
|
||||
void SetFileAttributes(string path, NxFileAttributes attributes);
|
||||
long GetFileSize(string path);
|
||||
Result CreateDirectory(string path, NxFileAttributes archiveAttribute);
|
||||
Result GetFileAttributes(string path, out NxFileAttributes attributes);
|
||||
Result SetFileAttributes(string path, NxFileAttributes attributes);
|
||||
Result GetFileSize(out long fileSize, string path);
|
||||
}
|
||||
}
|
||||
|
9
src/LibHac/Fs/ICommonMountNameGenerator.cs
Normal file
9
src/LibHac/Fs/ICommonMountNameGenerator.cs
Normal file
@ -0,0 +1,9 @@
|
||||
using System;
|
||||
|
||||
namespace LibHac.Fs
|
||||
{
|
||||
public interface ICommonMountNameGenerator
|
||||
{
|
||||
Result Generate(Span<byte> nameBuffer);
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -14,55 +14,60 @@ namespace LibHac.Fs
|
||||
/// or write as many bytes as it can and return that number of bytes to the caller.
|
||||
///
|
||||
/// - If <see cref="Write"/> is called on an offset past the end of the <see cref="IFile"/>,
|
||||
/// the <see cref="OpenMode.Append"/> mode is set and the file supports expansion,
|
||||
/// the <see cref="OpenMode.AllowAppend"/> mode is set and the file supports expansion,
|
||||
/// the file will be expanded so that it is large enough to contain the written data.</remarks>
|
||||
public interface IFile : IDisposable
|
||||
{
|
||||
/// <summary>
|
||||
/// The permissions mode for the current file.
|
||||
/// </summary>
|
||||
OpenMode Mode { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Reads a sequence of bytes from the current <see cref="IFile"/>.
|
||||
/// </summary>
|
||||
/// <param name="bytesRead">If the operation returns successfully, The total number of bytes read into
|
||||
/// the buffer. This can be less than the size of the buffer if the IFile is too short to fulfill the request.</param>
|
||||
/// <param name="offset">The offset in the <see cref="IFile"/> at which to begin reading.</param>
|
||||
/// <param name="destination">The buffer where the read bytes will be stored.
|
||||
/// The number of bytes read will be no larger than the length of the buffer.</param>
|
||||
/// <param name="offset">The offset in the <see cref="IFile"/> at which to begin reading.</param>
|
||||
/// <param name="options">Options for reading from the <see cref="IFile"/>.</param>
|
||||
/// <returns>The total number of bytes read into the buffer. This can be less than the
|
||||
/// size of the buffer if the IFile is too short to fulfill the request.</returns>
|
||||
/// <exception cref="ArgumentOutOfRangeException"><paramref name="offset"/> is invalid.</exception>
|
||||
/// <exception cref="NotSupportedException">The file's <see cref="OpenMode"/> does not allow reading.</exception>
|
||||
int Read(Span<byte> destination, long offset, ReadOption options);
|
||||
/// <returns>The <see cref="Result"/> of the requested operation.</returns>
|
||||
Result Read(out long bytesRead, long offset, Span<byte> destination, ReadOption options);
|
||||
|
||||
/// <summary>
|
||||
/// Writes a sequence of bytes to the current <see cref="IFile"/>.
|
||||
/// </summary>
|
||||
/// <param name="source">The buffer containing the bytes to be written.</param>
|
||||
/// <param name="offset">The offset in the <see cref="IStorage"/> at which to begin writing.</param>
|
||||
/// <param name="source">The buffer containing the bytes to be written.</param>
|
||||
/// <param name="options">Options for writing to the <see cref="IFile"/>.</param>
|
||||
/// <exception cref="ArgumentOutOfRangeException"><paramref name="offset"/> is negative.</exception>
|
||||
/// <exception cref="NotSupportedException">The file's <see cref="OpenMode"/> does not allow this request.</exception>
|
||||
void Write(ReadOnlySpan<byte> source, long offset, WriteOption options);
|
||||
/// <returns>The <see cref="Result"/> of the requested operation.</returns>
|
||||
Result Write(long offset, ReadOnlySpan<byte> source, WriteOption options);
|
||||
|
||||
/// <summary>
|
||||
/// Causes any buffered data to be written to the underlying device.
|
||||
/// </summary>
|
||||
void Flush();
|
||||
|
||||
/// <summary>
|
||||
/// Gets the number of bytes in the file.
|
||||
/// </summary>
|
||||
/// <returns>The length of the file in bytes.</returns>
|
||||
long GetSize();
|
||||
Result Flush();
|
||||
|
||||
/// <summary>
|
||||
/// Sets the size of the file in bytes.
|
||||
/// </summary>
|
||||
/// <param name="size">The desired size of the file in bytes.</param>
|
||||
/// <exception cref="NotSupportedException">If increasing the file size, The file's
|
||||
/// <see cref="OpenMode"/> does not allow this appending.</exception>
|
||||
void SetSize(long size);
|
||||
/// <returns>The <see cref="Result"/> of the requested operation.</returns>
|
||||
Result SetSize(long size);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the number of bytes in the file.
|
||||
/// </summary>
|
||||
/// <param name="size">If the operation returns successfully, the length of the file in bytes.</param>
|
||||
/// <returns>The <see cref="Result"/> of the requested operation.</returns>
|
||||
Result GetSize(out long size);
|
||||
|
||||
/// <summary>
|
||||
/// Performs various operations on the file. Used to extend the functionality of the <see cref="IFile"/> interface.
|
||||
/// </summary>
|
||||
/// <param name="outBuffer">A buffer that will contain the response from the operation.</param>
|
||||
/// <param name="operationId">The operation to be performed.</param>
|
||||
/// <param name="offset">The offset of the range to operate on.</param>
|
||||
/// <param name="size">The size of the range to operate on.</param>
|
||||
/// <param name="inBuffer">An input buffer. Size may vary depending on the operation performed.</param>
|
||||
/// <returns>The <see cref="Result"/> of the requested operation.</returns>
|
||||
Result OperateRange(Span<byte> outBuffer, OperationId operationId, long offset, long size,
|
||||
ReadOnlySpan<byte> inBuffer);
|
||||
}
|
||||
}
|
@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using LibHac.FsSystem;
|
||||
|
||||
namespace LibHac.Fs
|
||||
{
|
||||
@ -7,19 +8,6 @@ namespace LibHac.Fs
|
||||
/// </summary>
|
||||
public interface IFileSystem
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates all directories and subdirectories in the specified path unless they already exist.
|
||||
/// </summary>
|
||||
/// <param name="path">The full path of the directory to create.</param>
|
||||
/// <remarks>
|
||||
/// A <see cref="HorizonResultException"/> will be thrown with the given <see cref="Result"/> under the following conditions:
|
||||
///
|
||||
/// The parent directory of the specified path does not exist: <see cref="ResultFs.PathNotFound"/>
|
||||
/// Specified path already exists as either a file or directory: <see cref="ResultFs.PathAlreadyExists"/>
|
||||
/// Insufficient free space to create the directory: <see cref="ResultFs.InsufficientFreeSpace"/>
|
||||
/// </remarks>
|
||||
void CreateDirectory(string path);
|
||||
|
||||
/// <summary>
|
||||
/// Creates or overwrites a file at the specified path.
|
||||
/// </summary>
|
||||
@ -27,162 +15,191 @@ namespace LibHac.Fs
|
||||
/// <param name="size">The initial size of the created file.</param>
|
||||
/// <param name="options">Flags to control how the file is created.
|
||||
/// Should usually be <see cref="CreateFileOptions.None"/></param>
|
||||
/// <returns>The <see cref="Result"/> of the requested operation.</returns>
|
||||
/// <remarks>
|
||||
/// A <see cref="HorizonResultException"/> will be thrown with the given <see cref="Result"/> under the following conditions:
|
||||
/// The following <see cref="Result"/> codes may be returned under certain conditions:
|
||||
///
|
||||
/// The parent directory of the specified path does not exist: <see cref="ResultFs.PathNotFound"/>
|
||||
/// Specified path already exists as either a file or directory: <see cref="ResultFs.PathAlreadyExists"/>
|
||||
/// Insufficient free space to create the file: <see cref="ResultFs.InsufficientFreeSpace"/>
|
||||
/// </remarks>
|
||||
void CreateFile(string path, long size, CreateFileOptions options);
|
||||
|
||||
/// <summary>
|
||||
/// Deletes the specified directory.
|
||||
/// </summary>
|
||||
/// <param name="path">The full path of the directory to delete.</param>
|
||||
/// <remarks>
|
||||
/// A <see cref="HorizonResultException"/> will be thrown with the given <see cref="Result"/> under the following conditions:
|
||||
///
|
||||
/// The specified path does not exist or is a file: <see cref="ResultFs.PathNotFound"/>
|
||||
/// The specified directory is not empty: <see cref="ResultFs.DirectoryNotEmpty"/>
|
||||
/// </remarks>
|
||||
void DeleteDirectory(string path);
|
||||
|
||||
/// <summary>
|
||||
/// Deletes the specified directory and any subdirectories and files in the directory.
|
||||
/// </summary>
|
||||
/// <param name="path">The full path of the directory to delete.</param>
|
||||
/// <remarks>
|
||||
/// A <see cref="HorizonResultException"/> will be thrown with the given <see cref="Result"/> under the following conditions:
|
||||
///
|
||||
/// The specified path does not exist or is a file: <see cref="ResultFs.PathNotFound"/>
|
||||
/// </remarks>
|
||||
void DeleteDirectoryRecursively(string path);
|
||||
|
||||
/// <summary>
|
||||
/// Deletes any subdirectories and files in the specified directory.
|
||||
/// </summary>
|
||||
/// <param name="path">The full path of the directory to clean.</param>
|
||||
/// <remarks>
|
||||
/// A <see cref="HorizonResultException"/> will be thrown with the given <see cref="Result"/> under the following conditions:
|
||||
///
|
||||
/// The specified path does not exist or is a file: <see cref="ResultFs.PathNotFound"/>
|
||||
/// </remarks>
|
||||
void CleanDirectoryRecursively(string path);
|
||||
Result CreateFile(string path, long size, CreateFileOptions options);
|
||||
|
||||
/// <summary>
|
||||
/// Deletes the specified file.
|
||||
/// </summary>
|
||||
/// <param name="path">The full path of the file to delete.</param>
|
||||
/// <returns>The <see cref="Result"/> of the requested operation.</returns>
|
||||
/// <remarks>
|
||||
/// A <see cref="HorizonResultException"/> will be thrown with the given <see cref="Result"/> under the following conditions:
|
||||
/// The following <see cref="Result"/> codes may be returned under certain conditions:
|
||||
///
|
||||
/// The specified path does not exist or is a directory: <see cref="ResultFs.PathNotFound"/>
|
||||
/// </remarks>
|
||||
void DeleteFile(string path);
|
||||
Result DeleteFile(string path);
|
||||
|
||||
/// <summary>
|
||||
/// Creates an <see cref="IDirectory"/> instance for enumerating the specified directory.
|
||||
/// Creates all directories and subdirectories in the specified path unless they already exist.
|
||||
/// </summary>
|
||||
/// <param name="path">The directory's full path.</param>
|
||||
/// <param name="mode">Specifies which sub-entries should be enumerated.</param>
|
||||
/// <returns>An <see cref="IDirectory"/> instance for the specified directory.</returns>
|
||||
/// <param name="path">The full path of the directory to create.</param>
|
||||
/// <returns>The <see cref="Result"/> of the requested operation.</returns>
|
||||
/// <remarks>
|
||||
/// A <see cref="HorizonResultException"/> will be thrown with the given <see cref="Result"/> under the following conditions:
|
||||
/// The following <see cref="Result"/> codes may be returned under certain conditions:
|
||||
///
|
||||
/// The parent directory of the specified path does not exist: <see cref="ResultFs.PathNotFound"/>
|
||||
/// Specified path already exists as either a file or directory: <see cref="ResultFs.PathAlreadyExists"/>
|
||||
/// Insufficient free space to create the directory: <see cref="ResultFs.InsufficientFreeSpace"/>
|
||||
/// </remarks>
|
||||
Result CreateDirectory(string path);
|
||||
|
||||
/// <summary>
|
||||
/// Deletes the specified directory.
|
||||
/// </summary>
|
||||
/// <param name="path">The full path of the directory to delete.</param>
|
||||
/// <returns>The <see cref="Result"/> of the requested operation.</returns>
|
||||
/// <remarks>
|
||||
/// The following <see cref="Result"/> codes may be returned under certain conditions:
|
||||
///
|
||||
/// The specified path does not exist or is a file: <see cref="ResultFs.PathNotFound"/>
|
||||
/// The specified directory is not empty: <see cref="ResultFs.DirectoryNotEmpty"/>
|
||||
/// </remarks>
|
||||
Result DeleteDirectory(string path);
|
||||
|
||||
/// <summary>
|
||||
/// Deletes the specified directory and any subdirectories and files in the directory.
|
||||
/// </summary>
|
||||
/// <param name="path">The full path of the directory to delete.</param>
|
||||
/// <returns>The <see cref="Result"/> of the requested operation.</returns>
|
||||
/// <remarks>
|
||||
/// The following <see cref="Result"/> codes may be returned under certain conditions:
|
||||
///
|
||||
/// The specified path does not exist or is a file: <see cref="ResultFs.PathNotFound"/>
|
||||
/// </remarks>
|
||||
IDirectory OpenDirectory(string path, OpenDirectoryMode mode);
|
||||
Result DeleteDirectoryRecursively(string path);
|
||||
|
||||
/// <summary>
|
||||
/// Opens an <see cref="IFile"/> instance for the specified path.
|
||||
/// Deletes any subdirectories and files in the specified directory.
|
||||
/// </summary>
|
||||
/// <param name="path">The full path of the file to open.</param>
|
||||
/// <param name="mode">Specifies the access permissions of the created <see cref="IFile"/>.</param>
|
||||
/// <returns>An <see cref="IFile"/> instance for the specified path.</returns>
|
||||
/// <param name="path">The full path of the directory to clean.</param>
|
||||
/// <returns>The <see cref="Result"/> of the requested operation.</returns>
|
||||
/// <remarks>
|
||||
/// A <see cref="HorizonResultException"/> will be thrown with the given <see cref="Result"/> under the following conditions:
|
||||
/// The following <see cref="Result"/> codes may be returned under certain conditions:
|
||||
///
|
||||
/// The specified path does not exist or is a directory: <see cref="ResultFs.PathNotFound"/>
|
||||
/// The specified path does not exist or is a file: <see cref="ResultFs.PathNotFound"/>
|
||||
/// </remarks>
|
||||
IFile OpenFile(string path, OpenMode mode);
|
||||
|
||||
/// <summary>
|
||||
/// Renames or moves a directory to a new location.
|
||||
/// </summary>
|
||||
/// <param name="srcPath">The full path of the directory to rename.</param>
|
||||
/// <param name="dstPath">The new full path of the directory.</param>
|
||||
/// <returns>An <see cref="IFile"/> instance for the specified path.</returns>
|
||||
/// <remarks>
|
||||
/// If <paramref name="srcPath"/> and <paramref name="dstPath"/> are the same, this function does nothing and returns successfully.
|
||||
/// A <see cref="HorizonResultException"/> will be thrown with the given <see cref="Result"/> under the following conditions:
|
||||
///
|
||||
/// <paramref name="srcPath"/> does not exist or is a file: <see cref="ResultFs.PathNotFound"/>
|
||||
/// <paramref name="dstPath"/>'s parent directory does not exist: <see cref="ResultFs.PathNotFound"/>
|
||||
/// <paramref name="dstPath"/> already exists as either a file or directory: <see cref="ResultFs.PathAlreadyExists"/>
|
||||
/// Either <paramref name="srcPath"/> or <paramref name="dstPath"/> is a subpath of the other: <see cref="ResultFs.DestinationIsSubPathOfSource"/>
|
||||
/// </remarks>
|
||||
void RenameDirectory(string srcPath, string dstPath);
|
||||
Result CleanDirectoryRecursively(string path);
|
||||
|
||||
/// <summary>
|
||||
/// Renames or moves a file to a new location.
|
||||
/// </summary>
|
||||
/// <param name="srcPath">The full path of the file to rename.</param>
|
||||
/// <param name="dstPath">The new full path of the file.</param>
|
||||
/// <param name="oldPath">The full path of the file to rename.</param>
|
||||
/// <param name="newPath">The new full path of the file.</param>
|
||||
/// <returns>The <see cref="Result"/> of the requested operation.</returns>
|
||||
/// <remarks>
|
||||
/// If <paramref name="srcPath"/> and <paramref name="dstPath"/> are the same, this function does nothing and returns successfully.
|
||||
/// A <see cref="HorizonResultException"/> will be thrown with the given <see cref="Result"/> under the following conditions:
|
||||
/// If <paramref name="oldPath"/> and <paramref name="newPath"/> are the same, this function does nothing and returns successfully.
|
||||
/// The following <see cref="Result"/> codes may be returned under certain conditions:
|
||||
///
|
||||
/// <paramref name="srcPath"/> does not exist or is a directory: <see cref="ResultFs.PathNotFound"/>
|
||||
/// <paramref name="dstPath"/>'s parent directory does not exist: <see cref="ResultFs.PathNotFound"/>
|
||||
/// <paramref name="dstPath"/> already exists as either a file or directory: <see cref="ResultFs.PathAlreadyExists"/>
|
||||
/// <paramref name="oldPath"/> does not exist or is a directory: <see cref="ResultFs.PathNotFound"/>
|
||||
/// <paramref name="newPath"/>'s parent directory does not exist: <see cref="ResultFs.PathNotFound"/>
|
||||
/// <paramref name="newPath"/> already exists as either a file or directory: <see cref="ResultFs.PathAlreadyExists"/>
|
||||
/// </remarks>
|
||||
void RenameFile(string srcPath, string dstPath);
|
||||
Result RenameFile(string oldPath, string newPath);
|
||||
|
||||
/// <summary>
|
||||
/// Renames or moves a directory to a new location.
|
||||
/// </summary>
|
||||
/// <param name="oldPath">The full path of the directory to rename.</param>
|
||||
/// <param name="newPath">The new full path of the directory.</param>
|
||||
/// <returns>The <see cref="Result"/> of the requested operation.</returns>
|
||||
/// <remarks>
|
||||
/// If <paramref name="oldPath"/> and <paramref name="newPath"/> are the same, this function does nothing and returns <see cref="Result.Success"/>.
|
||||
/// The following <see cref="Result"/> codes may be returned under certain conditions:
|
||||
///
|
||||
/// <paramref name="oldPath"/> does not exist or is a file: <see cref="ResultFs.PathNotFound"/>
|
||||
/// <paramref name="newPath"/>'s parent directory does not exist: <see cref="ResultFs.PathNotFound"/>
|
||||
/// <paramref name="newPath"/> already exists as either a file or directory: <see cref="ResultFs.PathAlreadyExists"/>
|
||||
/// Either <paramref name="oldPath"/> or <paramref name="newPath"/> is a subpath of the other: <see cref="ResultFs.DestinationIsSubPathOfSource"/>
|
||||
/// </remarks>
|
||||
Result RenameDirectory(string oldPath, string newPath);
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the specified path is a file or directory, or does not exist.
|
||||
/// </summary>
|
||||
/// <param name="entryType">If the operation returns successfully, the <see cref="DirectoryEntryType"/> of the file.</param>
|
||||
/// <param name="path">The full path to check.</param>
|
||||
/// <returns>The <see cref="DirectoryEntryType"/> of the file.</returns>
|
||||
/// <returns>The <see cref="Result"/> of the requested operation.</returns>
|
||||
/// <remarks>
|
||||
/// This function operates slightly differently than it does in Horizon OS.
|
||||
/// Instead of returning <see cref="ResultFs.PathNotFound"/> when an entry is missing,
|
||||
/// the function will return <see cref="DirectoryEntryType.NotFound"/>.
|
||||
/// </remarks>
|
||||
DirectoryEntryType GetEntryType(string path);
|
||||
Result GetEntryType(out DirectoryEntryType entryType, string path);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the amount of available free space on a drive, in bytes.
|
||||
/// </summary>
|
||||
/// <param name="freeSpace">If the operation returns successfully, the amount of free space available on the drive, in bytes.</param>
|
||||
/// <param name="path">The path of the drive to query. Unused in almost all cases.</param>
|
||||
/// <returns>The amount of free space available on the drive, in bytes.</returns>
|
||||
long GetFreeSpaceSize(string path);
|
||||
/// <returns>The <see cref="Result"/> of the requested operation.</returns>
|
||||
Result GetFreeSpaceSize(out long freeSpace, string path);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the total size of storage space on a drive, in bytes.
|
||||
/// </summary>
|
||||
/// <param name="totalSpace">If the operation returns successfully, the total size of the drive, in bytes.</param>
|
||||
/// <param name="path">The path of the drive to query. Unused in almost all cases.</param>
|
||||
/// <returns>The total size of the drive, in bytes.</returns>
|
||||
long GetTotalSpaceSize(string path);
|
||||
/// <returns>The <see cref="Result"/> of the requested operation.</returns>
|
||||
Result GetTotalSpaceSize(out long totalSpace, string path);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the creation, last accessed, and last modified timestamps of a file or directory.
|
||||
/// Opens an <see cref="IFile"/> instance for the specified path.
|
||||
/// </summary>
|
||||
/// <param name="path">The path of the file or directory.</param>
|
||||
/// <returns>The timestamps for the specified file or directory.
|
||||
/// This value is expressed as a Unix timestamp</returns>
|
||||
/// <param name="file">If the operation returns successfully,
|
||||
/// An <see cref="IFile"/> instance for the specified path.</param>
|
||||
/// <param name="path">The full path of the file to open.</param>
|
||||
/// <param name="mode">Specifies the access permissions of the created <see cref="IFile"/>.</param>
|
||||
/// <returns>The <see cref="Result"/> of the requested operation.</returns>
|
||||
/// <remarks>
|
||||
/// A <see cref="HorizonResultException"/> will be thrown with the given <see cref="Result"/> under the following conditions:
|
||||
/// The following <see cref="Result"/> codes may be returned under certain conditions:
|
||||
///
|
||||
/// The specified path does not exist: <see cref="ResultFs.PathNotFound"/>
|
||||
/// The specified path does not exist or is a directory: <see cref="ResultFs.PathNotFound"/>
|
||||
/// </remarks>
|
||||
FileTimeStampRaw GetFileTimeStampRaw(string path);
|
||||
Result OpenFile(out IFile file, string path, OpenMode mode);
|
||||
|
||||
/// <summary>
|
||||
/// Creates an <see cref="IDirectory"/> instance for enumerating the specified directory.
|
||||
/// </summary>
|
||||
/// <param name="directory">If the operation returns successfully,
|
||||
/// An <see cref="IDirectory"/> instance for the specified directory.</param>
|
||||
/// <param name="path">The directory's full path.</param>
|
||||
/// <param name="mode">Specifies which sub-entries should be enumerated.</param>
|
||||
/// <returns>The <see cref="Result"/> of the requested operation.</returns>
|
||||
/// <remarks>
|
||||
/// The following <see cref="Result"/> codes may be returned under certain conditions:
|
||||
///
|
||||
/// The specified path does not exist or is a file: <see cref="ResultFs.PathNotFound"/>
|
||||
/// </remarks>
|
||||
Result OpenDirectory(out IDirectory directory, string path, OpenDirectoryMode mode);
|
||||
|
||||
/// <summary>
|
||||
/// Commits any changes to a transactional file system.
|
||||
/// Does nothing if called on a non-transactional file system.
|
||||
/// </summary>
|
||||
void Commit();
|
||||
/// <returns>The <see cref="Result"/> of the requested operation.</returns>
|
||||
Result Commit();
|
||||
|
||||
/// <summary>
|
||||
/// Gets the creation, last accessed, and last modified timestamps of a file or directory.
|
||||
/// </summary>
|
||||
/// <param name="timeStamp">If the operation returns successfully, the timestamps for the specified file or directory.
|
||||
/// These value are expressed as Unix timestamps.</param>
|
||||
/// <param name="path">The path of the file or directory.</param>
|
||||
/// <returns>The <see cref="Result"/> of the requested operation.</returns>
|
||||
/// <remarks>
|
||||
/// The following <see cref="Result"/> codes may be returned under certain conditions:
|
||||
///
|
||||
/// The specified path does not exist: <see cref="ResultFs.PathNotFound"/>
|
||||
/// </remarks>
|
||||
Result GetFileTimeStampRaw(out FileTimeStampRaw timeStamp, string path);
|
||||
|
||||
/// <summary>
|
||||
/// Performs a query on the specified file.
|
||||
@ -193,9 +210,10 @@ namespace LibHac.Fs
|
||||
/// May be unused depending on the query type.</param>
|
||||
/// <param name="inBuffer">The buffer for sending data to the query operation.
|
||||
/// May be unused depending on the query type.</param>
|
||||
/// <param name="path">The full path of the file to query.</param>
|
||||
/// <param name="queryId">The type of query to perform.</param>
|
||||
void QueryEntry(Span<byte> outBuffer, ReadOnlySpan<byte> inBuffer, string path, QueryId queryId);
|
||||
/// <param name="path">The full path of the file to query.</param>
|
||||
/// <returns>The <see cref="Result"/> of the requested operation.</returns>
|
||||
Result QueryEntry(Span<byte> outBuffer, ReadOnlySpan<byte> inBuffer, QueryId queryId, string path);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -204,9 +222,10 @@ namespace LibHac.Fs
|
||||
[Flags]
|
||||
public enum OpenDirectoryMode
|
||||
{
|
||||
Directories = 1,
|
||||
Files = 2,
|
||||
All = Directories | Files
|
||||
Directory = 1 << 0,
|
||||
File = 1 << 1,
|
||||
NoFileSize = 1 << 31,
|
||||
All = Directory | File
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -14,7 +14,7 @@ namespace LibHac.Fs
|
||||
/// The number of bytes read will be equal to the length of the buffer.</param>
|
||||
/// <param name="offset">The offset in the <see cref="IStorage"/> at which to begin reading.</param>
|
||||
/// <exception cref="ArgumentException">Invalid offset or the IStorage contains fewer bytes than requested. </exception>
|
||||
void Read(Span<byte> destination, long offset);
|
||||
Result Read(long offset, Span<byte> destination);
|
||||
|
||||
/// <summary>
|
||||
/// Writes a sequence of bytes to the current <see cref="IStorage"/>.
|
||||
@ -23,24 +23,36 @@ namespace LibHac.Fs
|
||||
/// <param name="offset">The offset in the <see cref="IStorage"/> at which to begin writing.</param>
|
||||
/// <exception cref="ArgumentException">Invalid offset or <paramref name="source"/>
|
||||
/// is too large to be written to the IStorage. </exception>
|
||||
void Write(ReadOnlySpan<byte> source, long offset);
|
||||
Result Write(long offset, ReadOnlySpan<byte> source);
|
||||
|
||||
/// <summary>
|
||||
/// Causes any buffered data to be written to the underlying device.
|
||||
/// </summary>
|
||||
void Flush();
|
||||
Result Flush();
|
||||
|
||||
/// <summary>
|
||||
/// Sets the size of the current IStorage.
|
||||
/// </summary>
|
||||
/// <param name="size">The desired size of the current IStorage in bytes.</param>
|
||||
void SetSize(long size);
|
||||
Result SetSize(long size);
|
||||
|
||||
/// <summary>
|
||||
/// The size of the<see cref="IStorage"/>. -1 will be returned if
|
||||
/// the <see cref="IStorage"/> cannot be represented as a sequence of contiguous bytes.
|
||||
/// </summary>
|
||||
/// <returns>The size of the <see cref="IStorage"/> in bytes.</returns>
|
||||
long GetSize();
|
||||
Result GetSize(out long size);
|
||||
|
||||
/// <summary>
|
||||
/// Performs various operations on the file. Used to extend the functionality of the <see cref="IStorage"/> interface.
|
||||
/// </summary>
|
||||
/// <param name="outBuffer">A buffer that will contain the response from the operation.</param>
|
||||
/// <param name="operationId">The operation to be performed.</param>
|
||||
/// <param name="offset">The offset of the range to operate on.</param>
|
||||
/// <param name="size">The size of the range to operate on.</param>
|
||||
/// <param name="inBuffer">An input buffer. Size may vary depending on the operation performed.</param>
|
||||
/// <returns>The <see cref="Result"/> of the requested operation.</returns>
|
||||
Result OperateRange(Span<byte> outBuffer, OperationId operationId, long offset, long size,
|
||||
ReadOnlySpan<byte> inBuffer);
|
||||
}
|
||||
}
|
||||
|
@ -1,133 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace LibHac.Fs
|
||||
{
|
||||
public class LayeredFileSystem : IFileSystem
|
||||
{
|
||||
private List<IFileSystem> Sources { get; } = new List<IFileSystem>();
|
||||
|
||||
public LayeredFileSystem(IList<IFileSystem> sourceFileSystems)
|
||||
{
|
||||
Sources.AddRange(sourceFileSystems);
|
||||
}
|
||||
|
||||
public IDirectory OpenDirectory(string path, OpenDirectoryMode mode)
|
||||
{
|
||||
path = PathTools.Normalize(path);
|
||||
|
||||
var dirs = new List<IDirectory>();
|
||||
|
||||
foreach (IFileSystem fs in Sources)
|
||||
{
|
||||
DirectoryEntryType type = fs.GetEntryType(path);
|
||||
|
||||
if (type == DirectoryEntryType.File && dirs.Count == 0)
|
||||
{
|
||||
ThrowHelper.ThrowResult(ResultFs.PathNotFound);
|
||||
}
|
||||
|
||||
if (fs.GetEntryType(path) == DirectoryEntryType.Directory)
|
||||
{
|
||||
dirs.Add(fs.OpenDirectory(path, mode));
|
||||
}
|
||||
}
|
||||
|
||||
var dir = new LayeredFileSystemDirectory(this, dirs, path, mode);
|
||||
|
||||
return dir;
|
||||
}
|
||||
|
||||
public IFile OpenFile(string path, OpenMode mode)
|
||||
{
|
||||
path = PathTools.Normalize(path);
|
||||
|
||||
foreach (IFileSystem fs in Sources)
|
||||
{
|
||||
DirectoryEntryType type = fs.GetEntryType(path);
|
||||
|
||||
if (type == DirectoryEntryType.File)
|
||||
{
|
||||
return fs.OpenFile(path, mode);
|
||||
}
|
||||
|
||||
if (type == DirectoryEntryType.Directory)
|
||||
{
|
||||
ThrowHelper.ThrowResult(ResultFs.PathNotFound);
|
||||
}
|
||||
}
|
||||
|
||||
ThrowHelper.ThrowResult(ResultFs.PathNotFound);
|
||||
return default;
|
||||
}
|
||||
|
||||
public DirectoryEntryType GetEntryType(string path)
|
||||
{
|
||||
path = PathTools.Normalize(path);
|
||||
|
||||
foreach (IFileSystem fs in Sources)
|
||||
{
|
||||
DirectoryEntryType type = fs.GetEntryType(path);
|
||||
|
||||
if (type != DirectoryEntryType.NotFound) return type;
|
||||
}
|
||||
|
||||
return DirectoryEntryType.NotFound;
|
||||
}
|
||||
|
||||
public FileTimeStampRaw GetFileTimeStampRaw(string path)
|
||||
{
|
||||
path = PathTools.Normalize(path);
|
||||
|
||||
foreach (IFileSystem fs in Sources)
|
||||
{
|
||||
if (fs.GetEntryType(path) != DirectoryEntryType.NotFound)
|
||||
{
|
||||
return fs.GetFileTimeStampRaw(path);
|
||||
}
|
||||
}
|
||||
|
||||
ThrowHelper.ThrowResult(ResultFs.PathNotFound);
|
||||
return default;
|
||||
}
|
||||
|
||||
public void QueryEntry(Span<byte> outBuffer, ReadOnlySpan<byte> inBuffer, string path, QueryId queryId)
|
||||
{
|
||||
path = PathTools.Normalize(path);
|
||||
|
||||
foreach (IFileSystem fs in Sources)
|
||||
{
|
||||
if (fs.GetEntryType(path) != DirectoryEntryType.NotFound)
|
||||
{
|
||||
fs.QueryEntry(outBuffer, inBuffer, path, queryId);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
ThrowHelper.ThrowResult(ResultFs.PathNotFound);
|
||||
}
|
||||
|
||||
public void Commit() { }
|
||||
|
||||
public void CreateDirectory(string path) => ThrowHelper.ThrowResult(ResultFs.UnsupportedOperation);
|
||||
public void CreateFile(string path, long size, CreateFileOptions options) => ThrowHelper.ThrowResult(ResultFs.UnsupportedOperation);
|
||||
public void DeleteDirectory(string path) => ThrowHelper.ThrowResult(ResultFs.UnsupportedOperation);
|
||||
public void DeleteDirectoryRecursively(string path) => ThrowHelper.ThrowResult(ResultFs.UnsupportedOperation);
|
||||
public void CleanDirectoryRecursively(string path) => ThrowHelper.ThrowResult(ResultFs.UnsupportedOperation);
|
||||
public void DeleteFile(string path) => ThrowHelper.ThrowResult(ResultFs.UnsupportedOperation);
|
||||
public void RenameDirectory(string srcPath, string dstPath) => ThrowHelper.ThrowResult(ResultFs.UnsupportedOperation);
|
||||
public void RenameFile(string srcPath, string dstPath) => ThrowHelper.ThrowResult(ResultFs.UnsupportedOperation);
|
||||
|
||||
public long GetFreeSpaceSize(string path)
|
||||
{
|
||||
ThrowHelper.ThrowResult(ResultFs.UnsupportedOperation);
|
||||
return default;
|
||||
}
|
||||
|
||||
public long GetTotalSpaceSize(string path)
|
||||
{
|
||||
ThrowHelper.ThrowResult(ResultFs.UnsupportedOperation);
|
||||
return default;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,44 +0,0 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
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)
|
||||
{
|
||||
ParentFileSystem = fs;
|
||||
Sources = sources;
|
||||
FullPath = path;
|
||||
Mode = mode;
|
||||
}
|
||||
|
||||
public IEnumerable<DirectoryEntry> Read()
|
||||
{
|
||||
var returnedFiles = new HashSet<string>();
|
||||
|
||||
foreach (IDirectory source in Sources)
|
||||
{
|
||||
foreach (DirectoryEntry entry in source.Read())
|
||||
{
|
||||
if (returnedFiles.Contains(entry.FullPath)) continue;
|
||||
|
||||
returnedFiles.Add(entry.FullPath);
|
||||
yield return entry;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public int GetEntryCount()
|
||||
{
|
||||
return Read().Count();
|
||||
}
|
||||
}
|
||||
}
|
@ -1,80 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace LibHac.Fs
|
||||
{
|
||||
public class LocalDirectory : IDirectory
|
||||
{
|
||||
public IFileSystem ParentFileSystem { get; }
|
||||
public string FullPath { get; }
|
||||
|
||||
private string LocalPath { get; }
|
||||
public OpenDirectoryMode Mode { get; }
|
||||
private DirectoryInfo DirInfo { get; }
|
||||
|
||||
public LocalDirectory(LocalFileSystem fs, string path, OpenDirectoryMode mode)
|
||||
{
|
||||
ParentFileSystem = fs;
|
||||
FullPath = path;
|
||||
LocalPath = fs.ResolveLocalPath(path);
|
||||
Mode = mode;
|
||||
|
||||
try
|
||||
{
|
||||
DirInfo = new DirectoryInfo(LocalPath);
|
||||
}
|
||||
catch (Exception ex) when (ex is ArgumentNullException || ex is ArgumentException ||
|
||||
ex is PathTooLongException)
|
||||
{
|
||||
ThrowHelper.ThrowResult(ResultFs.PathNotFound, ex);
|
||||
throw;
|
||||
}
|
||||
|
||||
if (!DirInfo.Exists)
|
||||
{
|
||||
ThrowHelper.ThrowResult(ResultFs.PathNotFound);
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerable<DirectoryEntry> Read()
|
||||
{
|
||||
foreach (FileSystemInfo entry in DirInfo.EnumerateFileSystemInfos())
|
||||
{
|
||||
bool isDir = (entry.Attributes & FileAttributes.Directory) != 0;
|
||||
|
||||
if (!CanReturnEntry(isDir, Mode)) continue;
|
||||
|
||||
DirectoryEntryType type = isDir ? DirectoryEntryType.Directory : DirectoryEntryType.File;
|
||||
long length = isDir ? 0 : ((FileInfo)entry).Length;
|
||||
|
||||
yield return new DirectoryEntry(entry.Name, PathTools.Combine(FullPath, entry.Name), type, length)
|
||||
{
|
||||
Attributes = entry.Attributes.ToNxAttributes()
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public int GetEntryCount()
|
||||
{
|
||||
int count = 0;
|
||||
|
||||
foreach (FileSystemInfo entry in DirInfo.EnumerateFileSystemInfos())
|
||||
{
|
||||
bool isDir = (entry.Attributes & FileAttributes.Directory) != 0;
|
||||
|
||||
if (CanReturnEntry(isDir, Mode)) count++;
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static bool CanReturnEntry(bool isDir, OpenDirectoryMode mode)
|
||||
{
|
||||
return isDir && (mode & OpenDirectoryMode.Directories) != 0 ||
|
||||
!isDir && (mode & OpenDirectoryMode.Files) != 0;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,94 +0,0 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
|
||||
namespace LibHac.Fs
|
||||
{
|
||||
public class LocalFile : FileBase
|
||||
{
|
||||
private const int ErrorHandleDiskFull = unchecked((int)0x80070027);
|
||||
private const int ErrorDiskFull = unchecked((int)0x80070070);
|
||||
|
||||
private FileStream Stream { get; }
|
||||
private StreamFile File { get; }
|
||||
|
||||
public LocalFile(string path, OpenMode mode)
|
||||
{
|
||||
Mode = mode;
|
||||
Stream = OpenFile(path, mode);
|
||||
File = new StreamFile(Stream, mode);
|
||||
|
||||
ToDispose.Add(File);
|
||||
ToDispose.Add(Stream);
|
||||
}
|
||||
|
||||
public override int Read(Span<byte> destination, long offset, ReadOption options)
|
||||
{
|
||||
int toRead = ValidateReadParamsAndGetSize(destination, offset);
|
||||
|
||||
File.Read(destination.Slice(0, toRead), offset, options);
|
||||
|
||||
return toRead;
|
||||
}
|
||||
|
||||
public override void Write(ReadOnlySpan<byte> source, long offset, WriteOption options)
|
||||
{
|
||||
ValidateWriteParams(source, offset);
|
||||
|
||||
File.Write(source, offset, options);
|
||||
}
|
||||
|
||||
public override void Flush()
|
||||
{
|
||||
File.Flush();
|
||||
}
|
||||
|
||||
public override long GetSize()
|
||||
{
|
||||
return File.GetSize();
|
||||
}
|
||||
|
||||
public override void SetSize(long size)
|
||||
{
|
||||
try
|
||||
{
|
||||
File.SetSize(size);
|
||||
}
|
||||
catch (IOException ex) when (ex.HResult == ErrorDiskFull || ex.HResult == ErrorHandleDiskFull)
|
||||
{
|
||||
ThrowHelper.ThrowResult(ResultFs.InsufficientFreeSpace, ex);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
private static FileAccess GetFileAccess(OpenMode mode)
|
||||
{
|
||||
// FileAccess and OpenMode have the same flags
|
||||
return (FileAccess)(mode & OpenMode.ReadWrite);
|
||||
}
|
||||
|
||||
private static FileShare GetFileShare(OpenMode mode)
|
||||
{
|
||||
return mode.HasFlag(OpenMode.Write) ? FileShare.Read : FileShare.ReadWrite;
|
||||
}
|
||||
|
||||
private static FileStream OpenFile(string path, OpenMode mode)
|
||||
{
|
||||
try
|
||||
{
|
||||
return new FileStream(path, FileMode.Open, GetFileAccess(mode), GetFileShare(mode));
|
||||
}
|
||||
catch (Exception ex) when (ex is ArgumentNullException || ex is ArgumentException ||
|
||||
ex is PathTooLongException || ex is DirectoryNotFoundException ||
|
||||
ex is FileNotFoundException || ex is NotSupportedException)
|
||||
{
|
||||
ThrowHelper.ThrowResult(ResultFs.PathNotFound, ex);
|
||||
throw;
|
||||
}
|
||||
catch (Exception ex) when (ex is IOException || ex is UnauthorizedAccessException)
|
||||
{
|
||||
// todo: Should a HorizonResultException be thrown?
|
||||
throw;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,480 +0,0 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Security;
|
||||
|
||||
namespace LibHac.Fs
|
||||
{
|
||||
public class LocalFileSystem : IAttributeFileSystem
|
||||
{
|
||||
private const int ErrorHandleDiskFull = unchecked((int)0x80070027);
|
||||
private const int ErrorFileExists = unchecked((int)0x80070050);
|
||||
private const int ErrorDiskFull = unchecked((int)0x80070070);
|
||||
private const int ErrorDirNotEmpty = unchecked((int)0x80070091);
|
||||
|
||||
private string BasePath { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Opens a directory on local storage as an <see cref="IFileSystem"/>.
|
||||
/// The directory will be created if it does not exist.
|
||||
/// </summary>
|
||||
/// <param name="basePath">The path that will be the root of the <see cref="LocalFileSystem"/>.</param>
|
||||
public LocalFileSystem(string basePath)
|
||||
{
|
||||
BasePath = Path.GetFullPath(basePath);
|
||||
|
||||
if (!Directory.Exists(BasePath))
|
||||
{
|
||||
Directory.CreateDirectory(BasePath);
|
||||
}
|
||||
}
|
||||
|
||||
internal string ResolveLocalPath(string path)
|
||||
{
|
||||
return PathTools.Combine(BasePath, path);
|
||||
}
|
||||
|
||||
public NxFileAttributes GetFileAttributes(string path)
|
||||
{
|
||||
path = PathTools.Normalize(path);
|
||||
return File.GetAttributes(ResolveLocalPath(path)).ToNxAttributes();
|
||||
}
|
||||
|
||||
public void SetFileAttributes(string path, NxFileAttributes attributes)
|
||||
{
|
||||
path = PathTools.Normalize(path);
|
||||
string localPath = ResolveLocalPath(path);
|
||||
|
||||
FileAttributes attributesOld = File.GetAttributes(localPath);
|
||||
FileAttributes attributesNew = attributesOld.ApplyNxAttributes(attributes);
|
||||
|
||||
File.SetAttributes(localPath, attributesNew);
|
||||
}
|
||||
|
||||
public long GetFileSize(string path)
|
||||
{
|
||||
string localPath = ResolveLocalPath(PathTools.Normalize(path));
|
||||
|
||||
FileInfo info = GetFileInfo(localPath);
|
||||
return GetSizeInternal(info);
|
||||
}
|
||||
|
||||
public void CreateDirectory(string path)
|
||||
{
|
||||
string localPath = ResolveLocalPath(PathTools.Normalize(path));
|
||||
|
||||
DirectoryInfo dir = GetDirInfo(localPath);
|
||||
|
||||
if (dir.Exists)
|
||||
{
|
||||
ThrowHelper.ThrowResult(ResultFs.PathAlreadyExists);
|
||||
}
|
||||
|
||||
if (dir.Parent?.Exists != true)
|
||||
{
|
||||
ThrowHelper.ThrowResult(ResultFs.PathNotFound);
|
||||
}
|
||||
|
||||
CreateDirInternal(dir);
|
||||
}
|
||||
|
||||
public void CreateFile(string path, long size, CreateFileOptions options)
|
||||
{
|
||||
string localPath = ResolveLocalPath(PathTools.Normalize(path));
|
||||
|
||||
FileInfo file = GetFileInfo(localPath);
|
||||
|
||||
if (file.Exists)
|
||||
{
|
||||
ThrowHelper.ThrowResult(ResultFs.PathAlreadyExists);
|
||||
}
|
||||
|
||||
if (file.Directory?.Exists != true)
|
||||
{
|
||||
ThrowHelper.ThrowResult(ResultFs.PathNotFound);
|
||||
}
|
||||
|
||||
using (FileStream stream = CreateFileInternal(file))
|
||||
{
|
||||
SetStreamLengthInternal(stream, size);
|
||||
}
|
||||
}
|
||||
|
||||
public void DeleteDirectory(string path)
|
||||
{
|
||||
string localPath = ResolveLocalPath(PathTools.Normalize(path));
|
||||
|
||||
DirectoryInfo dir = GetDirInfo(localPath);
|
||||
|
||||
DeleteDirectoryInternal(dir, false);
|
||||
}
|
||||
|
||||
public void DeleteDirectoryRecursively(string path)
|
||||
{
|
||||
string localPath = ResolveLocalPath(PathTools.Normalize(path));
|
||||
|
||||
DirectoryInfo dir = GetDirInfo(localPath);
|
||||
|
||||
DeleteDirectoryInternal(dir, true);
|
||||
}
|
||||
|
||||
public void CleanDirectoryRecursively(string path)
|
||||
{
|
||||
string localPath = ResolveLocalPath(PathTools.Normalize(path));
|
||||
|
||||
foreach (string file in Directory.EnumerateFiles(localPath))
|
||||
{
|
||||
DeleteFileInternal(GetFileInfo(file));
|
||||
}
|
||||
|
||||
foreach (string dir in Directory.EnumerateDirectories(localPath))
|
||||
{
|
||||
DeleteDirectoryInternal(GetDirInfo(dir), true);
|
||||
}
|
||||
}
|
||||
|
||||
public void DeleteFile(string path)
|
||||
{
|
||||
string localPath = ResolveLocalPath(PathTools.Normalize(path));
|
||||
|
||||
FileInfo file = GetFileInfo(localPath);
|
||||
|
||||
DeleteFileInternal(file);
|
||||
}
|
||||
|
||||
public IDirectory OpenDirectory(string path, OpenDirectoryMode mode)
|
||||
{
|
||||
path = PathTools.Normalize(path);
|
||||
|
||||
if (GetEntryType(path) == DirectoryEntryType.File)
|
||||
{
|
||||
ThrowHelper.ThrowResult(ResultFs.PathNotFound);
|
||||
}
|
||||
|
||||
return new LocalDirectory(this, path, mode);
|
||||
}
|
||||
|
||||
public IFile OpenFile(string path, OpenMode mode)
|
||||
{
|
||||
string localPath = ResolveLocalPath(PathTools.Normalize(path));
|
||||
|
||||
if (GetEntryType(path) == DirectoryEntryType.Directory)
|
||||
{
|
||||
ThrowHelper.ThrowResult(ResultFs.PathNotFound);
|
||||
}
|
||||
|
||||
return new LocalFile(localPath, mode);
|
||||
}
|
||||
|
||||
public void RenameDirectory(string srcPath, string dstPath)
|
||||
{
|
||||
srcPath = PathTools.Normalize(srcPath);
|
||||
dstPath = PathTools.Normalize(dstPath);
|
||||
|
||||
// Official FS behavior is to do nothing in this case
|
||||
if (srcPath == dstPath) return;
|
||||
|
||||
// FS does the subpath check before verifying the path exists
|
||||
if (PathTools.IsSubPath(srcPath.AsSpan(), dstPath.AsSpan()))
|
||||
{
|
||||
ThrowHelper.ThrowResult(ResultFs.DestinationIsSubPathOfSource);
|
||||
}
|
||||
|
||||
DirectoryInfo srcDir = GetDirInfo(ResolveLocalPath(srcPath));
|
||||
DirectoryInfo dstDir = GetDirInfo(ResolveLocalPath(dstPath));
|
||||
|
||||
RenameDirInternal(srcDir, dstDir);
|
||||
}
|
||||
|
||||
public void RenameFile(string srcPath, string dstPath)
|
||||
{
|
||||
string srcLocalPath = ResolveLocalPath(PathTools.Normalize(srcPath));
|
||||
string dstLocalPath = ResolveLocalPath(PathTools.Normalize(dstPath));
|
||||
|
||||
// Official FS behavior is to do nothing in this case
|
||||
if (srcLocalPath == dstLocalPath) return;
|
||||
|
||||
FileInfo srcFile = GetFileInfo(srcLocalPath);
|
||||
FileInfo dstFile = GetFileInfo(dstLocalPath);
|
||||
|
||||
RenameFileInternal(srcFile, dstFile);
|
||||
}
|
||||
|
||||
public DirectoryEntryType GetEntryType(string path)
|
||||
{
|
||||
string localPath = ResolveLocalPath(PathTools.Normalize(path));
|
||||
|
||||
DirectoryInfo dir = GetDirInfo(localPath);
|
||||
|
||||
if (dir.Exists)
|
||||
{
|
||||
return DirectoryEntryType.Directory;
|
||||
}
|
||||
|
||||
FileInfo file = GetFileInfo(localPath);
|
||||
|
||||
if (file.Exists)
|
||||
{
|
||||
return DirectoryEntryType.File;
|
||||
}
|
||||
|
||||
return DirectoryEntryType.NotFound;
|
||||
}
|
||||
|
||||
public FileTimeStampRaw GetFileTimeStampRaw(string path)
|
||||
{
|
||||
string localPath = ResolveLocalPath(PathTools.Normalize(path));
|
||||
|
||||
if (!GetFileInfo(localPath).Exists) ThrowHelper.ThrowResult(ResultFs.PathNotFound);
|
||||
|
||||
FileTimeStampRaw timeStamp = default;
|
||||
|
||||
timeStamp.Created = new DateTimeOffset(File.GetCreationTime(localPath)).ToUnixTimeSeconds();
|
||||
timeStamp.Accessed = new DateTimeOffset(File.GetLastAccessTime(localPath)).ToUnixTimeSeconds();
|
||||
timeStamp.Modified = new DateTimeOffset(File.GetLastWriteTime(localPath)).ToUnixTimeSeconds();
|
||||
|
||||
return timeStamp;
|
||||
}
|
||||
|
||||
public long GetFreeSpaceSize(string path)
|
||||
{
|
||||
return new DriveInfo(BasePath).AvailableFreeSpace;
|
||||
}
|
||||
|
||||
public long GetTotalSpaceSize(string path)
|
||||
{
|
||||
return new DriveInfo(BasePath).TotalSize;
|
||||
}
|
||||
|
||||
public void Commit() { }
|
||||
|
||||
public void QueryEntry(Span<byte> outBuffer, ReadOnlySpan<byte> inBuffer, string path, QueryId queryId) =>
|
||||
ThrowHelper.ThrowResult(ResultFs.UnsupportedOperation);
|
||||
|
||||
private static long GetSizeInternal(FileInfo file)
|
||||
{
|
||||
try
|
||||
{
|
||||
return file.Length;
|
||||
}
|
||||
catch (FileNotFoundException ex)
|
||||
{
|
||||
ThrowHelper.ThrowResult(ResultFs.PathNotFound, ex);
|
||||
throw;
|
||||
}
|
||||
catch (Exception ex) when (ex is SecurityException || ex is UnauthorizedAccessException)
|
||||
{
|
||||
// todo: Should a HorizonResultException be thrown?
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
private static FileStream CreateFileInternal(FileInfo file)
|
||||
{
|
||||
try
|
||||
{
|
||||
return new FileStream(file.FullName, FileMode.CreateNew, FileAccess.ReadWrite);
|
||||
}
|
||||
catch (DirectoryNotFoundException ex)
|
||||
{
|
||||
ThrowHelper.ThrowResult(ResultFs.PathNotFound, ex);
|
||||
throw;
|
||||
}
|
||||
catch (IOException ex) when (ex.HResult == ErrorDiskFull || ex.HResult == ErrorHandleDiskFull)
|
||||
{
|
||||
ThrowHelper.ThrowResult(ResultFs.InsufficientFreeSpace, ex);
|
||||
throw;
|
||||
}
|
||||
catch (IOException ex) when (ex.HResult == ErrorFileExists)
|
||||
{
|
||||
ThrowHelper.ThrowResult(ResultFs.PathAlreadyExists, ex);
|
||||
throw;
|
||||
}
|
||||
catch (Exception ex) when (ex is SecurityException || ex is UnauthorizedAccessException)
|
||||
{
|
||||
// todo: Should a HorizonResultException be thrown?
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
private static void SetStreamLengthInternal(Stream stream, long size)
|
||||
{
|
||||
try
|
||||
{
|
||||
stream.SetLength(size);
|
||||
}
|
||||
catch (IOException ex) when (ex.HResult == ErrorDiskFull || ex.HResult == ErrorHandleDiskFull)
|
||||
{
|
||||
ThrowHelper.ThrowResult(ResultFs.InsufficientFreeSpace, ex);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
private static void DeleteDirectoryInternal(DirectoryInfo dir, bool recursive)
|
||||
{
|
||||
if (!dir.Exists) ThrowHelper.ThrowResult(ResultFs.PathNotFound);
|
||||
|
||||
try
|
||||
{
|
||||
dir.Delete(recursive);
|
||||
}
|
||||
catch (DirectoryNotFoundException ex)
|
||||
{
|
||||
ThrowHelper.ThrowResult(ResultFs.PathNotFound, ex);
|
||||
throw;
|
||||
}
|
||||
catch (IOException ex) when (ex.HResult == ErrorDirNotEmpty)
|
||||
{
|
||||
ThrowHelper.ThrowResult(ResultFs.DirectoryNotEmpty, ex);
|
||||
throw;
|
||||
}
|
||||
catch (Exception ex) when (ex is IOException || ex is UnauthorizedAccessException)
|
||||
{
|
||||
// todo: Should a HorizonResultException be thrown?
|
||||
throw;
|
||||
}
|
||||
|
||||
EnsureDeleted(dir);
|
||||
}
|
||||
|
||||
private static void DeleteFileInternal(FileInfo file)
|
||||
{
|
||||
if (!file.Exists) ThrowHelper.ThrowResult(ResultFs.PathNotFound);
|
||||
|
||||
try
|
||||
{
|
||||
file.Delete();
|
||||
}
|
||||
catch (DirectoryNotFoundException ex)
|
||||
{
|
||||
ThrowHelper.ThrowResult(ResultFs.PathNotFound, ex);
|
||||
throw;
|
||||
}
|
||||
catch (IOException ex) when (ex.HResult == ErrorDirNotEmpty)
|
||||
{
|
||||
ThrowHelper.ThrowResult(ResultFs.DirectoryNotEmpty, ex);
|
||||
throw;
|
||||
}
|
||||
catch (Exception ex) when (ex is IOException || ex is UnauthorizedAccessException)
|
||||
{
|
||||
// todo: Should a HorizonResultException be thrown?
|
||||
throw;
|
||||
}
|
||||
|
||||
EnsureDeleted(file);
|
||||
}
|
||||
|
||||
private static void CreateDirInternal(DirectoryInfo dir)
|
||||
{
|
||||
try
|
||||
{
|
||||
dir.Create();
|
||||
}
|
||||
catch (DirectoryNotFoundException ex)
|
||||
{
|
||||
ThrowHelper.ThrowResult(ResultFs.PathNotFound, ex);
|
||||
throw;
|
||||
}
|
||||
catch (IOException ex) when (ex.HResult == ErrorDiskFull || ex.HResult == ErrorHandleDiskFull)
|
||||
{
|
||||
ThrowHelper.ThrowResult(ResultFs.InsufficientFreeSpace, ex);
|
||||
throw;
|
||||
}
|
||||
catch (Exception ex) when (ex is SecurityException || ex is UnauthorizedAccessException)
|
||||
{
|
||||
// todo: Should a HorizonResultException be thrown?
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
private static void RenameDirInternal(DirectoryInfo source, DirectoryInfo dest)
|
||||
{
|
||||
if (!source.Exists) ThrowHelper.ThrowResult(ResultFs.PathNotFound);
|
||||
if (dest.Exists) ThrowHelper.ThrowResult(ResultFs.PathAlreadyExists);
|
||||
|
||||
try
|
||||
{
|
||||
source.MoveTo(dest.FullName);
|
||||
}
|
||||
catch (DirectoryNotFoundException ex)
|
||||
{
|
||||
ThrowHelper.ThrowResult(ResultFs.PathNotFound, ex);
|
||||
throw;
|
||||
}
|
||||
catch (Exception ex) when (ex is IOException || ex is UnauthorizedAccessException)
|
||||
{
|
||||
// todo: Should a HorizonResultException be thrown?
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
private static void RenameFileInternal(FileInfo source, FileInfo dest)
|
||||
{
|
||||
if (!source.Exists) ThrowHelper.ThrowResult(ResultFs.PathNotFound);
|
||||
if (dest.Exists) ThrowHelper.ThrowResult(ResultFs.PathAlreadyExists);
|
||||
|
||||
try
|
||||
{
|
||||
source.MoveTo(dest.FullName);
|
||||
}
|
||||
catch (DirectoryNotFoundException ex)
|
||||
{
|
||||
ThrowHelper.ThrowResult(ResultFs.PathNotFound, ex);
|
||||
throw;
|
||||
}
|
||||
catch (Exception ex) when (ex is IOException || ex is UnauthorizedAccessException)
|
||||
{
|
||||
// todo: Should a HorizonResultException be thrown?
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// GetFileInfo and GetDirInfo detect invalid paths
|
||||
private static FileInfo GetFileInfo(string path)
|
||||
{
|
||||
try
|
||||
{
|
||||
return new FileInfo(path);
|
||||
}
|
||||
catch (Exception ex) when (ex is ArgumentNullException || ex is ArgumentException ||
|
||||
ex is PathTooLongException)
|
||||
{
|
||||
ThrowHelper.ThrowResult(ResultFs.PathNotFound, ex);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
private static DirectoryInfo GetDirInfo(string path)
|
||||
{
|
||||
try
|
||||
{
|
||||
return new DirectoryInfo(path);
|
||||
}
|
||||
catch (Exception ex) when (ex is ArgumentNullException || ex is ArgumentException ||
|
||||
ex is PathTooLongException)
|
||||
{
|
||||
ThrowHelper.ThrowResult(ResultFs.PathNotFound, ex);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
// Delete operations on IFileSystem should be synchronous
|
||||
// DeleteFile and RemoveDirectory only mark the file for deletion, so we need
|
||||
// to poll the filesystem until it's actually gone
|
||||
private static void EnsureDeleted(FileSystemInfo entry)
|
||||
{
|
||||
int tries = 0;
|
||||
|
||||
do
|
||||
{
|
||||
entry.Refresh();
|
||||
tries++;
|
||||
|
||||
if (tries > 1000)
|
||||
{
|
||||
throw new IOException($"Unable to delete file {entry.FullName}");
|
||||
}
|
||||
} while (entry.Exists);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,41 +0,0 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
|
||||
namespace LibHac.Fs
|
||||
{
|
||||
public class LocalStorage : StorageBase
|
||||
{
|
||||
private string Path { get; }
|
||||
private FileStream Stream { get; }
|
||||
private StreamStorage Storage { get; }
|
||||
|
||||
public LocalStorage(string path, FileAccess access) : this(path, access, FileMode.Open) { }
|
||||
|
||||
public LocalStorage(string path, FileAccess access, FileMode mode)
|
||||
{
|
||||
Path = path;
|
||||
Stream = new FileStream(Path, mode, access);
|
||||
Storage = new StreamStorage(Stream, false);
|
||||
|
||||
ToDispose.Add(Storage);
|
||||
ToDispose.Add(Stream);
|
||||
}
|
||||
|
||||
protected override void ReadImpl(Span<byte> destination, long offset)
|
||||
{
|
||||
Storage.Read(destination, offset);
|
||||
}
|
||||
|
||||
protected override void WriteImpl(ReadOnlySpan<byte> source, long offset)
|
||||
{
|
||||
Storage.Write(source, offset);
|
||||
}
|
||||
|
||||
public override void Flush()
|
||||
{
|
||||
Storage.Flush();
|
||||
}
|
||||
|
||||
public override long GetSize() => Storage.GetSize();
|
||||
}
|
||||
}
|
33
src/LibHac/Fs/MountHelpers.cs
Normal file
33
src/LibHac/Fs/MountHelpers.cs
Normal file
@ -0,0 +1,33 @@
|
||||
using LibHac.Common;
|
||||
|
||||
namespace LibHac.Fs
|
||||
{
|
||||
internal static class MountHelpers
|
||||
{
|
||||
public static Result CheckMountName(U8Span name)
|
||||
{
|
||||
if (name.IsNull()) return ResultFs.NullArgument.Log();
|
||||
|
||||
if (name.Length > 0 && name[0] == '@') return ResultFs.InvalidMountName.Log();
|
||||
if (!CheckMountNameImpl(name)) return ResultFs.InvalidMountName.Log();
|
||||
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
public static Result CheckMountNameAcceptingReservedMountName(U8Span name)
|
||||
{
|
||||
if (name.IsNull()) return ResultFs.NullArgument.Log();
|
||||
|
||||
if (!CheckMountNameImpl(name)) return ResultFs.InvalidMountName.Log();
|
||||
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
// ReSharper disable once UnusedParameter.Local
|
||||
private static bool CheckMountNameImpl(U8Span name)
|
||||
{
|
||||
// Todo
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,38 +0,0 @@
|
||||
using System;
|
||||
|
||||
namespace LibHac.Fs
|
||||
{
|
||||
public class NullFile : FileBase
|
||||
{
|
||||
public NullFile()
|
||||
{
|
||||
Mode = OpenMode.ReadWrite;
|
||||
}
|
||||
|
||||
public NullFile(long length) : this() => Length = length;
|
||||
|
||||
private long Length { get; }
|
||||
|
||||
public override int Read(Span<byte> destination, long offset, ReadOption options)
|
||||
{
|
||||
int toRead = ValidateReadParamsAndGetSize(destination, offset);
|
||||
destination.Slice(0, toRead).Clear();
|
||||
return toRead;
|
||||
}
|
||||
|
||||
public override void Write(ReadOnlySpan<byte> source, long offset, WriteOption options)
|
||||
{
|
||||
}
|
||||
|
||||
public override void Flush()
|
||||
{
|
||||
}
|
||||
|
||||
public override long GetSize() => Length;
|
||||
|
||||
public override void SetSize(long size)
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
}
|
||||
}
|
@ -1,30 +0,0 @@
|
||||
using System;
|
||||
|
||||
namespace LibHac.Fs
|
||||
{
|
||||
/// <summary>
|
||||
/// An <see cref="IStorage"/> that returns all zeros when read, and does nothing on write.
|
||||
/// </summary>
|
||||
public class NullStorage : StorageBase
|
||||
{
|
||||
public NullStorage() { }
|
||||
public NullStorage(long length) => _length = length;
|
||||
|
||||
private long _length;
|
||||
|
||||
protected override void ReadImpl(Span<byte> destination, long offset)
|
||||
{
|
||||
destination.Clear();
|
||||
}
|
||||
|
||||
protected override void WriteImpl(ReadOnlySpan<byte> source, long offset)
|
||||
{
|
||||
}
|
||||
|
||||
public override void Flush()
|
||||
{
|
||||
}
|
||||
|
||||
public override long GetSize() => _length;
|
||||
}
|
||||
}
|
@ -1,49 +0,0 @@
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
|
||||
namespace LibHac.Fs
|
||||
{
|
||||
public class PartitionDirectory : IDirectory
|
||||
{
|
||||
IFileSystem IDirectory.ParentFileSystem => ParentFileSystem;
|
||||
public PartitionFileSystem ParentFileSystem { get; }
|
||||
public string FullPath { get; }
|
||||
|
||||
public OpenDirectoryMode Mode { get; }
|
||||
|
||||
public PartitionDirectory(PartitionFileSystem fs, string path, OpenDirectoryMode mode)
|
||||
{
|
||||
path = PathTools.Normalize(path);
|
||||
|
||||
if (path != "/") throw new DirectoryNotFoundException();
|
||||
|
||||
ParentFileSystem = fs;
|
||||
FullPath = path;
|
||||
Mode = mode;
|
||||
}
|
||||
|
||||
|
||||
public IEnumerable<DirectoryEntry> Read()
|
||||
{
|
||||
if (Mode.HasFlag(OpenDirectoryMode.Files))
|
||||
{
|
||||
foreach (PartitionFileEntry entry in ParentFileSystem.Files)
|
||||
{
|
||||
yield return new DirectoryEntry(entry.Name, '/' + entry.Name, DirectoryEntryType.File, entry.Size);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public int GetEntryCount()
|
||||
{
|
||||
int count = 0;
|
||||
|
||||
if (Mode.HasFlag(OpenDirectoryMode.Files))
|
||||
{
|
||||
count += ParentFileSystem.Files.Length;
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,59 +0,0 @@
|
||||
using System;
|
||||
|
||||
namespace LibHac.Fs
|
||||
{
|
||||
public class PartitionFile : FileBase
|
||||
{
|
||||
private IStorage BaseStorage { get; }
|
||||
private long Offset { get; }
|
||||
private long Size { get; }
|
||||
|
||||
public PartitionFile(IStorage baseStorage, long offset, long size, OpenMode mode)
|
||||
{
|
||||
Mode = mode;
|
||||
BaseStorage = baseStorage;
|
||||
Offset = offset;
|
||||
Size = size;
|
||||
}
|
||||
|
||||
public override int Read(Span<byte> destination, long offset, ReadOption options)
|
||||
{
|
||||
int toRead = ValidateReadParamsAndGetSize(destination, offset);
|
||||
|
||||
long storageOffset = Offset + offset;
|
||||
BaseStorage.Read(destination.Slice(0, toRead), storageOffset);
|
||||
|
||||
return toRead;
|
||||
}
|
||||
|
||||
public override void Write(ReadOnlySpan<byte> source, long offset, WriteOption options)
|
||||
{
|
||||
ValidateWriteParams(source, offset);
|
||||
|
||||
BaseStorage.Write(source, offset);
|
||||
|
||||
if ((options & WriteOption.Flush) != 0)
|
||||
{
|
||||
BaseStorage.Flush();
|
||||
}
|
||||
}
|
||||
|
||||
public override void Flush()
|
||||
{
|
||||
if ((Mode & OpenMode.Write) != 0)
|
||||
{
|
||||
BaseStorage.Flush();
|
||||
}
|
||||
}
|
||||
|
||||
public override long GetSize()
|
||||
{
|
||||
return Size;
|
||||
}
|
||||
|
||||
public override void SetSize(long size)
|
||||
{
|
||||
ThrowHelper.ThrowResult(ResultFs.UnsupportedOperationInPartitionFileSetSize);
|
||||
}
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
@ -1,32 +0,0 @@
|
||||
using System;
|
||||
|
||||
namespace LibHac.Fs
|
||||
{
|
||||
public class ReadOnlyFile : FileBase
|
||||
{
|
||||
private IFile BaseFile { get; }
|
||||
|
||||
public ReadOnlyFile(IFile baseFile)
|
||||
{
|
||||
BaseFile = baseFile;
|
||||
}
|
||||
|
||||
public override int Read(Span<byte> destination, long offset, ReadOption options)
|
||||
{
|
||||
return BaseFile.Read(destination, offset, options);
|
||||
}
|
||||
|
||||
public override long GetSize()
|
||||
{
|
||||
return BaseFile.GetSize();
|
||||
}
|
||||
|
||||
public override void Flush() { }
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
@ -1,81 +0,0 @@
|
||||
using System;
|
||||
|
||||
namespace LibHac.Fs
|
||||
{
|
||||
public class ReadOnlyFileSystem : IFileSystem
|
||||
{
|
||||
private IFileSystem BaseFs { get; }
|
||||
|
||||
public ReadOnlyFileSystem(IFileSystem baseFileSystem)
|
||||
{
|
||||
BaseFs = baseFileSystem;
|
||||
}
|
||||
|
||||
public IDirectory OpenDirectory(string path, OpenDirectoryMode mode)
|
||||
{
|
||||
IDirectory baseDir = BaseFs.OpenDirectory(path, mode);
|
||||
return new ReadOnlyDirectory(this, baseDir);
|
||||
}
|
||||
|
||||
public IFile OpenFile(string path, OpenMode mode)
|
||||
{
|
||||
IFile baseFile = BaseFs.OpenFile(path, mode);
|
||||
return new ReadOnlyFile(baseFile);
|
||||
}
|
||||
|
||||
public DirectoryEntryType GetEntryType(string path)
|
||||
{
|
||||
return BaseFs.GetEntryType(path);
|
||||
}
|
||||
|
||||
public long GetFreeSpaceSize(string path)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
public long GetTotalSpaceSize(string path)
|
||||
{
|
||||
return BaseFs.GetTotalSpaceSize(path);
|
||||
}
|
||||
|
||||
public FileTimeStampRaw GetFileTimeStampRaw(string path)
|
||||
{
|
||||
return BaseFs.GetFileTimeStampRaw(path);
|
||||
}
|
||||
|
||||
public void Commit()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public void QueryEntry(Span<byte> outBuffer, ReadOnlySpan<byte> inBuffer, string path, QueryId queryId)
|
||||
{
|
||||
BaseFs.QueryEntry(outBuffer, inBuffer, path, queryId);
|
||||
}
|
||||
|
||||
public void CreateDirectory(string path) =>
|
||||
ThrowHelper.ThrowResult(ResultFs.UnsupportedOperationModifyReadOnlyFileSystem);
|
||||
|
||||
public void CreateFile(string path, long size, CreateFileOptions options) =>
|
||||
ThrowHelper.ThrowResult(ResultFs.UnsupportedOperationModifyReadOnlyFileSystem);
|
||||
|
||||
public void DeleteDirectory(string path) =>
|
||||
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);
|
||||
|
||||
}
|
||||
}
|
@ -11,6 +11,19 @@
|
||||
public static Result InsufficientFreeSpace => new Result(ModuleFs, 30);
|
||||
public static Result MountNameAlreadyExists => new Result(ModuleFs, 60);
|
||||
|
||||
public static Result PartitionNotFound => new Result(ModuleFs, 1001);
|
||||
public static Result TargetNotFound => new Result(ModuleFs, 1002);
|
||||
public static Result ExternalKeyNotFound => new Result(ModuleFs, 1004);
|
||||
|
||||
public static Result InvalidBufferForGameCard => new Result(ModuleFs, 2503);
|
||||
public static Result GameCardNotInserted => new Result(ModuleFs, 2520);
|
||||
|
||||
public static Result GameCardNotInsertedOnGetHandle => new Result(ModuleFs, 2951);
|
||||
public static Result InvalidGameCardHandleOnRead => new Result(ModuleFs, 2952);
|
||||
public static Result InvalidGameCardHandleOnGetCardInfo => new Result(ModuleFs, 2954);
|
||||
public static Result InvalidGameCardHandleOnOpenNormalPartition => new Result(ModuleFs, 2960);
|
||||
public static Result InvalidGameCardHandleOnOpenSecurePartition => new Result(ModuleFs, 2961);
|
||||
|
||||
public static Result NotImplemented => new Result(ModuleFs, 3001);
|
||||
public static Result Result3002 => new Result(ModuleFs, 3002);
|
||||
public static Result SaveDataPathAlreadyExists => new Result(ModuleFs, 3003);
|
||||
@ -25,6 +38,7 @@
|
||||
public static Result InvalidIndirectStorageSource => new Result(ModuleFs, 4023);
|
||||
|
||||
public static Result Result4302 => new Result(ModuleFs, 4302);
|
||||
public static Result InvalidSaveDataEntryType => new Result(ModuleFs, 4303);
|
||||
public static Result InvalidSaveDataHeader => new Result(ModuleFs, 4315);
|
||||
public static Result Result4362 => new Result(ModuleFs, 4362);
|
||||
public static Result Result4363 => new Result(ModuleFs, 4363);
|
||||
@ -61,6 +75,10 @@
|
||||
|
||||
public static Result Result4812 => new Result(ModuleFs, 4812);
|
||||
|
||||
public static Result UnexpectedErrorInHostFileFlush => new Result(ModuleFs, 5307);
|
||||
public static Result UnexpectedErrorInHostFileGetSize => new Result(ModuleFs, 5308);
|
||||
public static Result UnknownHostFileSystemError => new Result(ModuleFs, 5309);
|
||||
|
||||
public static Result PreconditionViolation => new Result(ModuleFs, 6000);
|
||||
public static Result InvalidArgument => new Result(ModuleFs, 6001);
|
||||
public static Result InvalidPath => new Result(ModuleFs, 6002);
|
||||
@ -77,17 +95,24 @@
|
||||
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 ExtensionSizeTooLarge => new Result(ModuleFs, 6066);
|
||||
public static Result ExtensionSizeInvalid => new Result(ModuleFs, 6067);
|
||||
|
||||
public static Result InvalidOpenModeOperation => new Result(ModuleFs, 6200);
|
||||
public static Result AllowAppendRequiredForImplicitExtension => new Result(ModuleFs, 6201);
|
||||
public static Result FileExtensionWithoutOpenModeAllowAppend => new Result(ModuleFs, 6201);
|
||||
public static Result InvalidOpenModeForRead => new Result(ModuleFs, 6202);
|
||||
public static Result InvalidOpenModeForWrite => new Result(ModuleFs, 6203);
|
||||
|
||||
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 SubStorageNotResizable => new Result(ModuleFs, 6302);
|
||||
public static Result SubStorageNotResizableMiddleOfFile => new Result(ModuleFs, 6303);
|
||||
public static Result UnsupportedOperationInMemoryStorageSetSize => new Result(ModuleFs, 6304);
|
||||
public static Result UnsupportedOperationInAesCtrExStorageWrite => new Result(ModuleFs, 6310);
|
||||
public static Result UnsupportedOperationInHierarchicalIvfcStorageSetSize => new Result(ModuleFs, 6316);
|
||||
public static Result UnsupportedOperationInIndirectStorageWrite => new Result(ModuleFs, 6324);
|
||||
public static Result UnsupportedOperationInIndirectStorageSetSize => new Result(ModuleFs, 6325);
|
||||
public static Result UnsupportedOperationInRoGameCardStorageWrite => new Result(ModuleFs, 6350);
|
||||
public static Result UnsupportedOperationInRoGameCardStorageSetSize => new Result(ModuleFs, 6351);
|
||||
public static Result UnsupportedOperationInConcatFsQueryEntry => new Result(ModuleFs, 6359);
|
||||
public static Result UnsupportedOperationModifyRomFsFileSystem => new Result(ModuleFs, 6364);
|
||||
public static Result UnsupportedOperationRomFsFileSystemGetSpace => new Result(ModuleFs, 6366);
|
||||
@ -99,12 +124,18 @@
|
||||
public static Result UnsupportedOperationInPartitionFileSetSize => new Result(ModuleFs, 6376);
|
||||
|
||||
public static Result PermissionDenied => new Result(ModuleFs, 6400);
|
||||
public static Result ExternalKeyAlreadyRegistered => new Result(ModuleFs, 6452);
|
||||
public static Result WriteStateUnflushed => new Result(ModuleFs, 6454);
|
||||
public static Result WritableFileOpen => new Result(ModuleFs, 6457);
|
||||
|
||||
|
||||
public static Result MappingTableFull => new Result(ModuleFs, 6706);
|
||||
public static Result AllocationTableInsufficientFreeBlocks => new Result(ModuleFs, 6707);
|
||||
public static Result OpenCountLimit => new Result(ModuleFs, 6709);
|
||||
|
||||
public static Result RemapStorageMapFull => new Result(ModuleFs, 6811);
|
||||
|
||||
public static Result SubStorageNotInitialized => new Result(ModuleFs, 6902);
|
||||
public static Result MountNameNotFound => new Result(ModuleFs, 6905);
|
||||
}
|
||||
}
|
||||
|
62
src/LibHac/Fs/RightsId.cs
Normal file
62
src/LibHac/Fs/RightsId.cs
Normal file
@ -0,0 +1,62 @@
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Runtime.InteropServices;
|
||||
using LibHac.Common;
|
||||
|
||||
namespace LibHac.Fs
|
||||
{
|
||||
[DebuggerDisplay("{DebugDisplay(),nq}")]
|
||||
[StructLayout(LayoutKind.Sequential, Size = 0x10)]
|
||||
public struct RightsId : IEquatable<RightsId>, IComparable<RightsId>, IComparable
|
||||
{
|
||||
public readonly Id128 Id;
|
||||
|
||||
public RightsId(ulong high, ulong low)
|
||||
{
|
||||
Id = new Id128(high, low);
|
||||
}
|
||||
|
||||
public RightsId(ReadOnlySpan<byte> uid)
|
||||
{
|
||||
Id = new Id128(uid);
|
||||
}
|
||||
|
||||
public override string ToString() => Id.ToString();
|
||||
|
||||
public string DebugDisplay()
|
||||
{
|
||||
ReadOnlySpan<byte> highBytes = AsBytes().Slice(0, 8);
|
||||
ReadOnlySpan<byte> lowBytes = AsBytes().Slice(8, 8);
|
||||
|
||||
return $"{highBytes.ToHexString()} {lowBytes.ToHexString()}";
|
||||
}
|
||||
|
||||
public bool Equals(RightsId other) => Id == other.Id;
|
||||
public override bool Equals(object obj) => obj is RightsId other && Equals(other);
|
||||
|
||||
public override int GetHashCode() => Id.GetHashCode();
|
||||
|
||||
public int CompareTo(RightsId other) => Id.CompareTo(other.Id);
|
||||
|
||||
public int CompareTo(object obj)
|
||||
{
|
||||
if (obj is null) return 1;
|
||||
return obj is RightsId other ? CompareTo(other) : throw new ArgumentException($"Object must be of type {nameof(RightsId)}");
|
||||
}
|
||||
|
||||
public void ToBytes(Span<byte> output) => Id.ToBytes(output);
|
||||
|
||||
public ReadOnlySpan<byte> AsBytes()
|
||||
{
|
||||
return SpanHelpers.AsByteSpan(ref this);
|
||||
}
|
||||
|
||||
public static bool operator ==(RightsId left, RightsId right) => left.Equals(right);
|
||||
public static bool operator !=(RightsId left, RightsId right) => !left.Equals(right);
|
||||
|
||||
public static bool operator <(RightsId left, RightsId right) => left.CompareTo(right) < 0;
|
||||
public static bool operator >(RightsId left, RightsId right) => left.CompareTo(right) > 0;
|
||||
public static bool operator <=(RightsId left, RightsId right) => left.CompareTo(right) <= 0;
|
||||
public static bool operator >=(RightsId left, RightsId right) => left.CompareTo(right) >= 0;
|
||||
}
|
||||
}
|
@ -1,71 +0,0 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace LibHac.Fs.RomFs
|
||||
{
|
||||
public class RomFsDirectory : IDirectory
|
||||
{
|
||||
IFileSystem IDirectory.ParentFileSystem => ParentFileSystem;
|
||||
public RomFsFileSystem ParentFileSystem { get; }
|
||||
public string FullPath { get; }
|
||||
|
||||
public OpenDirectoryMode Mode { get; }
|
||||
|
||||
private FindPosition InitialPosition { get; }
|
||||
|
||||
public RomFsDirectory(RomFsFileSystem fs, string path, FindPosition position, OpenDirectoryMode mode)
|
||||
{
|
||||
ParentFileSystem = fs;
|
||||
InitialPosition = position;
|
||||
FullPath = path;
|
||||
Mode = mode;
|
||||
}
|
||||
|
||||
public IEnumerable<DirectoryEntry> Read()
|
||||
{
|
||||
FindPosition position = InitialPosition;
|
||||
HierarchicalRomFileTable<RomFileInfo> tab = ParentFileSystem.FileTable;
|
||||
|
||||
if (Mode.HasFlag(OpenDirectoryMode.Directories))
|
||||
{
|
||||
while (tab.FindNextDirectory(ref position, out string name))
|
||||
{
|
||||
yield return new DirectoryEntry(name, PathTools.Combine(FullPath, name), DirectoryEntryType.Directory, 0);
|
||||
}
|
||||
}
|
||||
|
||||
if (Mode.HasFlag(OpenDirectoryMode.Files))
|
||||
{
|
||||
while (tab.FindNextFile(ref position, out RomFileInfo info, out string name))
|
||||
{
|
||||
yield return new DirectoryEntry(name, PathTools.Combine(FullPath, name), DirectoryEntryType.File, info.Length);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public int GetEntryCount()
|
||||
{
|
||||
int count = 0;
|
||||
|
||||
FindPosition position = InitialPosition;
|
||||
HierarchicalRomFileTable<RomFileInfo> tab = ParentFileSystem.FileTable;
|
||||
|
||||
if (Mode.HasFlag(OpenDirectoryMode.Directories))
|
||||
{
|
||||
while (tab.FindNextDirectory(ref position, out string _))
|
||||
{
|
||||
count++;
|
||||
}
|
||||
}
|
||||
|
||||
if (Mode.HasFlag(OpenDirectoryMode.Files))
|
||||
{
|
||||
while (tab.FindNextFile(ref position, out RomFileInfo _, out string _))
|
||||
{
|
||||
count++;
|
||||
}
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,48 +0,0 @@
|
||||
using System;
|
||||
|
||||
namespace LibHac.Fs.RomFs
|
||||
{
|
||||
public class RomFsFile : FileBase
|
||||
{
|
||||
private IStorage BaseStorage { get; }
|
||||
private long Offset { get; }
|
||||
private long Size { get; }
|
||||
|
||||
public RomFsFile(IStorage baseStorage, long offset, long size)
|
||||
{
|
||||
Mode = OpenMode.Read;
|
||||
BaseStorage = baseStorage;
|
||||
Offset = offset;
|
||||
Size = size;
|
||||
}
|
||||
|
||||
public override int Read(Span<byte> destination, long offset, ReadOption options)
|
||||
{
|
||||
int toRead = ValidateReadParamsAndGetSize(destination, offset);
|
||||
|
||||
long storageOffset = Offset + offset;
|
||||
BaseStorage.Read(destination.Slice(0, toRead), storageOffset);
|
||||
|
||||
return toRead;
|
||||
}
|
||||
|
||||
public override void Write(ReadOnlySpan<byte> source, long offset, WriteOption options)
|
||||
{
|
||||
ThrowHelper.ThrowResult(ResultFs.UnsupportedOperationModifyRomFsFile);
|
||||
}
|
||||
|
||||
public override void Flush()
|
||||
{
|
||||
}
|
||||
|
||||
public override long GetSize()
|
||||
{
|
||||
return Size;
|
||||
}
|
||||
|
||||
public override void SetSize(long size)
|
||||
{
|
||||
ThrowHelper.ThrowResult(ResultFs.UnsupportedOperationModifyRomFsFile);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,71 +0,0 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace LibHac.Fs.Save
|
||||
{
|
||||
public class SaveDataDirectory : IDirectory
|
||||
{
|
||||
IFileSystem IDirectory.ParentFileSystem => ParentFileSystem;
|
||||
public SaveDataFileSystemCore ParentFileSystem { get; }
|
||||
public string FullPath { get; }
|
||||
|
||||
public OpenDirectoryMode Mode { get; }
|
||||
|
||||
private SaveFindPosition InitialPosition { get; }
|
||||
|
||||
public SaveDataDirectory(SaveDataFileSystemCore fs, string path, SaveFindPosition position, OpenDirectoryMode mode)
|
||||
{
|
||||
ParentFileSystem = fs;
|
||||
InitialPosition = position;
|
||||
FullPath = path;
|
||||
Mode = mode;
|
||||
}
|
||||
|
||||
public IEnumerable<DirectoryEntry> Read()
|
||||
{
|
||||
SaveFindPosition position = InitialPosition;
|
||||
HierarchicalSaveFileTable tab = ParentFileSystem.FileTable;
|
||||
|
||||
if (Mode.HasFlag(OpenDirectoryMode.Directories))
|
||||
{
|
||||
while (tab.FindNextDirectory(ref position, out string name))
|
||||
{
|
||||
yield return new DirectoryEntry(name, PathTools.Combine(FullPath, name), DirectoryEntryType.Directory, 0);
|
||||
}
|
||||
}
|
||||
|
||||
if (Mode.HasFlag(OpenDirectoryMode.Files))
|
||||
{
|
||||
while (tab.FindNextFile(ref position, out SaveFileInfo info, out string name))
|
||||
{
|
||||
yield return new DirectoryEntry(name, PathTools.Combine(FullPath, name), DirectoryEntryType.File, info.Length);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public int GetEntryCount()
|
||||
{
|
||||
int count = 0;
|
||||
|
||||
SaveFindPosition position = InitialPosition;
|
||||
HierarchicalSaveFileTable tab = ParentFileSystem.FileTable;
|
||||
|
||||
if (Mode.HasFlag(OpenDirectoryMode.Directories))
|
||||
{
|
||||
while (tab.FindNextDirectory(ref position, out string _))
|
||||
{
|
||||
count++;
|
||||
}
|
||||
}
|
||||
|
||||
if (Mode.HasFlag(OpenDirectoryMode.Files))
|
||||
{
|
||||
while (tab.FindNextFile(ref position, out SaveFileInfo _, out string _))
|
||||
{
|
||||
count++;
|
||||
}
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,73 +0,0 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
|
||||
namespace LibHac.Fs.Save
|
||||
{
|
||||
public class SaveDataFile : FileBase
|
||||
{
|
||||
private AllocationTableStorage BaseStorage { get; }
|
||||
private string Path { get; }
|
||||
private HierarchicalSaveFileTable FileTable { get; }
|
||||
private long Size { get; set; }
|
||||
|
||||
public SaveDataFile(AllocationTableStorage baseStorage, string path, HierarchicalSaveFileTable fileTable, long size, OpenMode mode)
|
||||
{
|
||||
Mode = mode;
|
||||
BaseStorage = baseStorage;
|
||||
Path = path;
|
||||
FileTable = fileTable;
|
||||
Size = size;
|
||||
}
|
||||
|
||||
public override int Read(Span<byte> destination, long offset, ReadOption options)
|
||||
{
|
||||
int toRead = ValidateReadParamsAndGetSize(destination, offset);
|
||||
|
||||
BaseStorage.Read(destination.Slice(0, toRead), offset);
|
||||
|
||||
return toRead;
|
||||
}
|
||||
|
||||
public override void Write(ReadOnlySpan<byte> source, long offset, WriteOption options)
|
||||
{
|
||||
ValidateWriteParams(source, offset);
|
||||
|
||||
BaseStorage.Write(source, offset);
|
||||
|
||||
if ((options & WriteOption.Flush) != 0)
|
||||
{
|
||||
Flush();
|
||||
}
|
||||
}
|
||||
|
||||
public override void Flush()
|
||||
{
|
||||
BaseStorage.Flush();
|
||||
}
|
||||
|
||||
public override long GetSize()
|
||||
{
|
||||
return Size;
|
||||
}
|
||||
|
||||
public override void SetSize(long size)
|
||||
{
|
||||
if (size < 0) throw new ArgumentOutOfRangeException(nameof(size));
|
||||
if (Size == size) return;
|
||||
|
||||
BaseStorage.SetSize(size);
|
||||
|
||||
if (!FileTable.TryOpenFile(Path, out SaveFileInfo fileInfo))
|
||||
{
|
||||
throw new FileNotFoundException();
|
||||
}
|
||||
|
||||
fileInfo.StartBlock = BaseStorage.InitialBlock;
|
||||
fileInfo.Length = size;
|
||||
|
||||
FileTable.AddFile(Path, ref fileInfo);
|
||||
|
||||
Size = size;
|
||||
}
|
||||
}
|
||||
}
|
108
src/LibHac/Fs/SaveData.cs
Normal file
108
src/LibHac/Fs/SaveData.cs
Normal file
@ -0,0 +1,108 @@
|
||||
using LibHac.Common;
|
||||
using LibHac.FsService;
|
||||
|
||||
namespace LibHac.Fs
|
||||
{
|
||||
public static class SaveData
|
||||
{
|
||||
public static Result MountSystemSaveData(this FileSystemClient fs, U8Span mountName, SaveDataSpaceId spaceId, ulong saveDataId)
|
||||
{
|
||||
return MountSystemSaveData(fs, mountName, spaceId, saveDataId, UserId.Zero);
|
||||
}
|
||||
|
||||
public static Result MountSystemSaveData(this FileSystemClient fs, U8Span mountName,
|
||||
SaveDataSpaceId spaceId, ulong saveDataId, UserId userId)
|
||||
{
|
||||
Result rc = MountHelpers.CheckMountName(mountName);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
IFileSystemProxy fsProxy = fs.GetFileSystemProxyServiceObject();
|
||||
|
||||
SaveDataAttribute attribute = default;
|
||||
attribute.UserId = userId;
|
||||
attribute.SaveDataId = saveDataId;
|
||||
|
||||
rc = fsProxy.OpenSaveDataFileSystemBySystemSaveDataId(out IFileSystem fileSystem, spaceId, ref attribute);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
return fs.Register(mountName, fileSystem);
|
||||
}
|
||||
|
||||
public static Result CreateSystemSaveData(this FileSystemClient fs, SaveDataSpaceId spaceId,
|
||||
ulong saveDataId, UserId userId, ulong ownerId, long size, long journalSize, uint flags)
|
||||
{
|
||||
return fs.RunOperationWithAccessLog(LocalAccessLogMode.System,
|
||||
() =>
|
||||
{
|
||||
IFileSystemProxy fsProxy = fs.GetFileSystemProxyServiceObject();
|
||||
|
||||
var attribute = new SaveDataAttribute
|
||||
{
|
||||
UserId = userId,
|
||||
SaveDataId = saveDataId
|
||||
};
|
||||
|
||||
var createInfo = new SaveDataCreateInfo
|
||||
{
|
||||
Size = size,
|
||||
JournalSize = journalSize,
|
||||
BlockSize = 0x4000,
|
||||
OwnerId = ownerId,
|
||||
Flags = flags,
|
||||
SpaceId = spaceId
|
||||
};
|
||||
|
||||
return fsProxy.CreateSaveDataFileSystemBySystemSaveDataId(ref attribute, ref createInfo);
|
||||
},
|
||||
() => $", savedataspaceid: {spaceId}, savedataid: 0x{saveDataId:X}, userid: 0x{userId.Id.High:X16}{userId.Id.Low:X16}, save_data_owner_id: 0x{ownerId:X}, save_data_size: {size}, save_data_journal_size: {journalSize}, save_data_flags: 0x{flags:X8}");
|
||||
}
|
||||
|
||||
public static Result CreateSystemSaveData(this FileSystemClient fs, ulong saveDataId, UserId userId,
|
||||
ulong ownerId, long size, long journalSize, uint flags)
|
||||
{
|
||||
return CreateSystemSaveData(fs, SaveDataSpaceId.System, saveDataId, userId, ownerId, size, journalSize, flags);
|
||||
}
|
||||
|
||||
public static Result CreateSystemSaveData(this FileSystemClient fs, ulong saveDataId, UserId userId, long size,
|
||||
long journalSize, uint flags)
|
||||
{
|
||||
return CreateSystemSaveData(fs, SaveDataSpaceId.System, saveDataId, userId, 0, size, journalSize, flags);
|
||||
}
|
||||
|
||||
public static Result CreateSystemSaveData(this FileSystemClient fs, ulong saveDataId, ulong ownerId, long size,
|
||||
long journalSize, uint flags)
|
||||
{
|
||||
return CreateSystemSaveData(fs, SaveDataSpaceId.System, saveDataId, UserId.Zero, ownerId, size, journalSize, flags);
|
||||
}
|
||||
|
||||
public static Result CreateSystemSaveData(this FileSystemClient fs, ulong saveDataId, long size,
|
||||
long journalSize, uint flags)
|
||||
{
|
||||
return CreateSystemSaveData(fs, SaveDataSpaceId.System, saveDataId, UserId.Zero, 0, size, journalSize, flags);
|
||||
}
|
||||
|
||||
public static Result CreateSystemSaveData(this FileSystemClient fs, SaveDataSpaceId spaceId, ulong saveDataId,
|
||||
ulong ownerId, long size, long journalSize, uint flags)
|
||||
{
|
||||
return CreateSystemSaveData(fs, spaceId, saveDataId, UserId.Zero, ownerId, size, journalSize, flags);
|
||||
}
|
||||
|
||||
public static Result DeleteSaveData(this FileSystemClient fs, ulong saveDataId)
|
||||
{
|
||||
return fs.RunOperationWithAccessLog(LocalAccessLogMode.System,
|
||||
() =>
|
||||
{
|
||||
IFileSystemProxy fsProxy = fs.GetFileSystemProxyServiceObject();
|
||||
return fsProxy.DeleteSaveDataFileSystem(saveDataId);
|
||||
},
|
||||
() => $", savedataid: 0x{saveDataId:X}");
|
||||
}
|
||||
|
||||
public static Result DisableAutoSaveDataCreation(this FileSystemClient fsClient)
|
||||
{
|
||||
IFileSystemProxy fsProxy = fsClient.GetFileSystemProxyServiceObject();
|
||||
|
||||
return fsProxy.DisableAutoSaveDataCreation();
|
||||
}
|
||||
}
|
||||
}
|
@ -1,11 +1,11 @@
|
||||
using System;
|
||||
using System.Buffers.Binary;
|
||||
using LibHac.Fs.Save;
|
||||
using LibHac.FsSystem.Save;
|
||||
using LibHac.Kvdb;
|
||||
|
||||
namespace LibHac.Fs
|
||||
{
|
||||
public class SaveDataStruct : IComparable<SaveDataStruct>, IComparable, IEquatable<SaveDataStruct>, IExportable
|
||||
public class SaveDataAttributeKvdb : IComparable<SaveDataAttributeKvdb>, IComparable, IEquatable<SaveDataAttributeKvdb>, IExportable
|
||||
{
|
||||
public ulong TitleId { get; private set; }
|
||||
public UserId UserId { get; private set; }
|
||||
@ -44,7 +44,7 @@ namespace LibHac.Fs
|
||||
|
||||
public void Freeze() => _isFrozen = true;
|
||||
|
||||
public bool Equals(SaveDataStruct other)
|
||||
public bool Equals(SaveDataAttributeKvdb other)
|
||||
{
|
||||
return other != null && TitleId == other.TitleId && UserId.Equals(other.UserId) && SaveId == other.SaveId &&
|
||||
Type == other.Type && Rank == other.Rank && Index == other.Index;
|
||||
@ -52,7 +52,7 @@ namespace LibHac.Fs
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
return obj is SaveDataStruct other && Equals(other);
|
||||
return obj is SaveDataAttributeKvdb other && Equals(other);
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
@ -71,7 +71,7 @@ namespace LibHac.Fs
|
||||
}
|
||||
}
|
||||
|
||||
public int CompareTo(SaveDataStruct other)
|
||||
public int CompareTo(SaveDataAttributeKvdb other)
|
||||
{
|
||||
int titleIdComparison = TitleId.CompareTo(other.TitleId);
|
||||
if (titleIdComparison != 0) return titleIdComparison;
|
||||
@ -89,7 +89,7 @@ namespace LibHac.Fs
|
||||
public int CompareTo(object obj)
|
||||
{
|
||||
if (obj is null) return 1;
|
||||
return obj is SaveDataStruct other ? CompareTo(other) : throw new ArgumentException($"Object must be of type {nameof(SaveDataStruct)}");
|
||||
return obj is SaveDataAttributeKvdb other ? CompareTo(other) : throw new ArgumentException($"Object must be of type {nameof(SaveDataAttributeKvdb)}");
|
||||
}
|
||||
}
|
||||
}
|
89
src/LibHac/Fs/SaveDataStructs.cs
Normal file
89
src/LibHac/Fs/SaveDataStructs.cs
Normal file
@ -0,0 +1,89 @@
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
using LibHac.Common;
|
||||
using LibHac.FsSystem.Save;
|
||||
using LibHac.Ncm;
|
||||
|
||||
namespace LibHac.Fs
|
||||
{
|
||||
[StructLayout(LayoutKind.Explicit, Size = 0x40)]
|
||||
public struct SaveDataAttribute
|
||||
{
|
||||
[FieldOffset(0x00)] public ulong TitleId;
|
||||
[FieldOffset(0x08)] public UserId UserId;
|
||||
[FieldOffset(0x18)] public ulong SaveDataId;
|
||||
[FieldOffset(0x20)] public SaveDataType Type;
|
||||
[FieldOffset(0x21)] public byte Rank;
|
||||
[FieldOffset(0x22)] public short Index;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Explicit, Size = 0x48)]
|
||||
public struct SaveDataFilter
|
||||
{
|
||||
[FieldOffset(0x00)] public bool FilterByTitleId;
|
||||
[FieldOffset(0x01)] public bool FilterBySaveDataType;
|
||||
[FieldOffset(0x02)] public bool FilterByUserId;
|
||||
[FieldOffset(0x03)] public bool FilterBySaveDataId;
|
||||
[FieldOffset(0x04)] public bool FilterByIndex;
|
||||
[FieldOffset(0x05)] public byte Rank;
|
||||
|
||||
[FieldOffset(0x08)] public TitleId TitleID;
|
||||
[FieldOffset(0x10)] public UserId UserId;
|
||||
[FieldOffset(0x20)] public ulong SaveDataId;
|
||||
[FieldOffset(0x28)] public SaveDataType SaveDataType;
|
||||
[FieldOffset(0x2A)] public short Index;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Explicit, Size = 0x50)]
|
||||
public struct SaveDataFilterInternal
|
||||
{
|
||||
[FieldOffset(0x00)] public bool FilterBySaveDataSpaceId;
|
||||
[FieldOffset(0x01)] public SaveDataSpaceId SpaceId;
|
||||
|
||||
[FieldOffset(0x08)] public bool FilterByTitleId;
|
||||
[FieldOffset(0x10)] public TitleId TitleID;
|
||||
|
||||
[FieldOffset(0x18)] public bool FilterBySaveDataType;
|
||||
[FieldOffset(0x19)] public SaveDataType SaveDataType;
|
||||
|
||||
[FieldOffset(0x20)] public bool FilterByUserId;
|
||||
[FieldOffset(0x28)] public UserId UserId;
|
||||
|
||||
[FieldOffset(0x38)] public bool FilterBySaveDataId;
|
||||
[FieldOffset(0x40)] public ulong SaveDataId;
|
||||
|
||||
[FieldOffset(0x48)] public bool FilterByIndex;
|
||||
[FieldOffset(0x4A)] public short Index;
|
||||
|
||||
[FieldOffset(0x4C)] public int Rank;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Explicit, Size = HashLength)]
|
||||
public struct HashSalt
|
||||
{
|
||||
private const int HashLength = 0x20;
|
||||
|
||||
[FieldOffset(0x00)] private byte _hashStart;
|
||||
|
||||
public Span<byte> Hash => SpanHelpers.CreateSpan(ref _hashStart, HashLength);
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Explicit, Size = 0x10)]
|
||||
public struct SaveMetaCreateInfo
|
||||
{
|
||||
[FieldOffset(0)] public int Size;
|
||||
[FieldOffset(4)] public SaveMetaType Type;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Explicit, Size = 0x40)]
|
||||
public struct SaveDataCreateInfo
|
||||
{
|
||||
[FieldOffset(0x00)] public long Size;
|
||||
[FieldOffset(0x08)] public long JournalSize;
|
||||
[FieldOffset(0x10)] public ulong BlockSize;
|
||||
[FieldOffset(0x18)] public ulong OwnerId;
|
||||
[FieldOffset(0x20)] public uint Flags;
|
||||
[FieldOffset(0x24)] public SaveDataSpaceId SpaceId;
|
||||
[FieldOffset(0x25)] public bool Field25;
|
||||
}
|
||||
}
|
16
src/LibHac/Fs/SdCardAccessLog.cs
Normal file
16
src/LibHac/Fs/SdCardAccessLog.cs
Normal file
@ -0,0 +1,16 @@
|
||||
using System;
|
||||
using LibHac.FsService;
|
||||
|
||||
namespace LibHac.Fs
|
||||
{
|
||||
/// <summary>
|
||||
/// The default access logger that will output to the SD card via <see cref="FileSystemProxy"/>.
|
||||
/// </summary>
|
||||
public class SdCardAccessLog : IAccessLog
|
||||
{
|
||||
public void Log(Result result, TimeSpan startTime, TimeSpan endTime, int handleId, string message, string caller = "")
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
}
|
@ -1,64 +0,0 @@
|
||||
using System;
|
||||
|
||||
namespace LibHac.Fs
|
||||
{
|
||||
public class SectorStorage : StorageBase
|
||||
{
|
||||
protected IStorage BaseStorage { get; }
|
||||
|
||||
public int SectorSize { get; }
|
||||
public int SectorCount { get; private set; }
|
||||
|
||||
private long _length;
|
||||
|
||||
public SectorStorage(IStorage baseStorage, int sectorSize, bool leaveOpen)
|
||||
{
|
||||
BaseStorage = baseStorage;
|
||||
SectorSize = sectorSize;
|
||||
SectorCount = (int)Util.DivideByRoundUp(BaseStorage.GetSize(), SectorSize);
|
||||
_length = BaseStorage.GetSize();
|
||||
|
||||
if (!leaveOpen) ToDispose.Add(BaseStorage);
|
||||
}
|
||||
|
||||
protected override void ReadImpl(Span<byte> destination, long offset)
|
||||
{
|
||||
ValidateSize(destination.Length, offset);
|
||||
BaseStorage.Read(destination, offset);
|
||||
}
|
||||
|
||||
protected override void WriteImpl(ReadOnlySpan<byte> source, long offset)
|
||||
{
|
||||
ValidateSize(source.Length, offset);
|
||||
BaseStorage.Write(source, offset);
|
||||
}
|
||||
|
||||
public override void Flush()
|
||||
{
|
||||
BaseStorage.Flush();
|
||||
}
|
||||
|
||||
public override long GetSize() => _length;
|
||||
|
||||
public override void SetSize(long size)
|
||||
{
|
||||
BaseStorage.SetSize(size);
|
||||
|
||||
SectorCount = (int)Util.DivideByRoundUp(BaseStorage.GetSize(), SectorSize);
|
||||
_length = BaseStorage.GetSize();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Validates that the size is a multiple of the sector size
|
||||
/// </summary>
|
||||
protected void ValidateSize(long size, long offset)
|
||||
{
|
||||
if (size < 0)
|
||||
throw new ArgumentException("Size must be non-negative");
|
||||
if (offset < 0)
|
||||
throw new ArgumentException("Offset must be non-negative");
|
||||
if (offset % SectorSize != 0)
|
||||
throw new ArgumentException($"Offset must be a multiple of {SectorSize}");
|
||||
}
|
||||
}
|
||||
}
|
@ -1,69 +1,85 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
|
||||
namespace LibHac.Fs
|
||||
{
|
||||
public abstract class StorageBase : IStorage
|
||||
{
|
||||
private bool _isDisposed;
|
||||
protected internal List<IDisposable> ToDispose { get; } = new List<IDisposable>();
|
||||
protected bool CanAutoExpand { get; set; }
|
||||
// 0 = not disposed; 1 = disposed
|
||||
private int _disposedState;
|
||||
private bool IsDisposed => _disposedState != 0;
|
||||
|
||||
protected abstract void ReadImpl(Span<byte> destination, long offset);
|
||||
protected abstract void WriteImpl(ReadOnlySpan<byte> source, long offset);
|
||||
public abstract void Flush();
|
||||
public abstract long GetSize();
|
||||
protected abstract Result ReadImpl(long offset, Span<byte> destination);
|
||||
protected abstract Result WriteImpl(long offset, ReadOnlySpan<byte> source);
|
||||
protected abstract Result FlushImpl();
|
||||
protected abstract Result GetSizeImpl(out long size);
|
||||
protected abstract Result SetSizeImpl(long size);
|
||||
|
||||
public void Read(Span<byte> destination, long offset)
|
||||
protected virtual Result OperateRangeImpl(Span<byte> outBuffer, OperationId operationId, long offset, long size,
|
||||
ReadOnlySpan<byte> inBuffer)
|
||||
{
|
||||
ValidateParameters(destination, offset);
|
||||
ReadImpl(destination, offset);
|
||||
return ResultFs.NotImplemented.Log();
|
||||
}
|
||||
|
||||
public void Write(ReadOnlySpan<byte> source, long offset)
|
||||
public Result Read(long offset, Span<byte> destination)
|
||||
{
|
||||
ValidateParameters(source, offset);
|
||||
WriteImpl(source, offset);
|
||||
if (IsDisposed) return ResultFs.PreconditionViolation.Log();
|
||||
|
||||
return ReadImpl(offset, destination);
|
||||
}
|
||||
|
||||
public virtual void SetSize(long size)
|
||||
public Result Write(long offset, ReadOnlySpan<byte> source)
|
||||
{
|
||||
ThrowHelper.ThrowResult(ResultFs.NotImplemented);
|
||||
if (IsDisposed) return ResultFs.PreconditionViolation.Log();
|
||||
|
||||
return WriteImpl(offset, source);
|
||||
}
|
||||
|
||||
protected virtual void Dispose(bool disposing)
|
||||
public Result Flush()
|
||||
{
|
||||
if (_isDisposed) return;
|
||||
if (IsDisposed) return ResultFs.PreconditionViolation.Log();
|
||||
|
||||
if (disposing)
|
||||
{
|
||||
Flush();
|
||||
foreach (IDisposable item in ToDispose)
|
||||
{
|
||||
item?.Dispose();
|
||||
}
|
||||
}
|
||||
return FlushImpl();
|
||||
}
|
||||
|
||||
_isDisposed = true;
|
||||
public Result SetSize(long size)
|
||||
{
|
||||
if (IsDisposed) return ResultFs.PreconditionViolation.Log();
|
||||
|
||||
return SetSizeImpl(size);
|
||||
}
|
||||
|
||||
public Result GetSize(out long size)
|
||||
{
|
||||
size = default;
|
||||
if (IsDisposed) return ResultFs.PreconditionViolation.Log();
|
||||
|
||||
return GetSizeImpl(out size);
|
||||
}
|
||||
|
||||
public Result OperateRange(Span<byte> outBuffer, OperationId operationId, long offset, long size,
|
||||
ReadOnlySpan<byte> inBuffer)
|
||||
{
|
||||
if (IsDisposed) return ResultFs.PreconditionViolation.Log();
|
||||
|
||||
return OperateRange(outBuffer, operationId, offset, size, inBuffer);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
// Make sure Dispose is only called once
|
||||
if (Interlocked.CompareExchange(ref _disposedState, 1, 0) == 0)
|
||||
{
|
||||
Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
}
|
||||
|
||||
protected void ValidateParameters(ReadOnlySpan<byte> span, long offset)
|
||||
protected virtual void Dispose(bool disposing) { }
|
||||
|
||||
public static bool IsRangeValid(long offset, long size, long totalSize)
|
||||
{
|
||||
if (_isDisposed) throw new ObjectDisposedException(null);
|
||||
if (offset < 0) throw new ArgumentOutOfRangeException(nameof(offset), "Argument must be non-negative.");
|
||||
|
||||
long length = GetSize();
|
||||
|
||||
if (length != -1 && !CanAutoExpand)
|
||||
{
|
||||
if (offset + span.Length > length) throw new ArgumentException("The given offset and count exceed the length of the Storage");
|
||||
}
|
||||
return offset >= 0 && size >= 0 && size <= totalSize && offset <= totalSize - size;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,51 +0,0 @@
|
||||
using System;
|
||||
|
||||
namespace LibHac.Fs
|
||||
{
|
||||
public class StorageFile : FileBase
|
||||
{
|
||||
private IStorage BaseStorage { get; }
|
||||
|
||||
public StorageFile(IStorage baseStorage, OpenMode mode)
|
||||
{
|
||||
BaseStorage = baseStorage;
|
||||
Mode = mode;
|
||||
}
|
||||
|
||||
public override int Read(Span<byte> destination, long offset, ReadOption options)
|
||||
{
|
||||
int toRead = ValidateReadParamsAndGetSize(destination, offset);
|
||||
|
||||
BaseStorage.Read(destination.Slice(0, toRead), offset);
|
||||
|
||||
return toRead;
|
||||
}
|
||||
|
||||
public override void Write(ReadOnlySpan<byte> source, long offset, WriteOption options)
|
||||
{
|
||||
ValidateWriteParams(source, offset);
|
||||
|
||||
BaseStorage.Write(source, offset);
|
||||
|
||||
if ((options & WriteOption.Flush) != 0)
|
||||
{
|
||||
Flush();
|
||||
}
|
||||
}
|
||||
|
||||
public override void Flush()
|
||||
{
|
||||
BaseStorage.Flush();
|
||||
}
|
||||
|
||||
public override long GetSize()
|
||||
{
|
||||
return BaseStorage.GetSize();
|
||||
}
|
||||
|
||||
public override void SetSize(long size)
|
||||
{
|
||||
BaseStorage.SetSize(size);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,76 +0,0 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
|
||||
namespace LibHac.Fs
|
||||
{
|
||||
public class SubStorage : StorageBase
|
||||
{
|
||||
private IStorage BaseStorage { get; }
|
||||
private long Offset { get; }
|
||||
private FileAccess Access { get; } = FileAccess.ReadWrite;
|
||||
private long _length;
|
||||
|
||||
public SubStorage(IStorage baseStorage, long offset, long length)
|
||||
{
|
||||
BaseStorage = baseStorage;
|
||||
Offset = offset;
|
||||
_length = length;
|
||||
}
|
||||
|
||||
public SubStorage(SubStorage baseStorage, long offset, long length)
|
||||
{
|
||||
BaseStorage = baseStorage.BaseStorage;
|
||||
Offset = baseStorage.Offset + offset;
|
||||
_length = length;
|
||||
}
|
||||
|
||||
public SubStorage(IStorage baseStorage, long offset, long length, bool leaveOpen)
|
||||
: this(baseStorage, offset, length)
|
||||
{
|
||||
if (!leaveOpen) ToDispose.Add(BaseStorage);
|
||||
}
|
||||
|
||||
public SubStorage(IStorage baseStorage, long offset, long length, bool leaveOpen, FileAccess access)
|
||||
: this(baseStorage, offset, length, leaveOpen)
|
||||
{
|
||||
Access = access;
|
||||
}
|
||||
|
||||
protected override void ReadImpl(Span<byte> destination, long offset)
|
||||
{
|
||||
if ((Access & FileAccess.Read) == 0) throw new InvalidOperationException("Storage is not readable");
|
||||
BaseStorage.Read(destination, offset + Offset);
|
||||
}
|
||||
|
||||
protected override void WriteImpl(ReadOnlySpan<byte> source, long offset)
|
||||
{
|
||||
if ((Access & FileAccess.Write) == 0) throw new InvalidOperationException("Storage is not writable");
|
||||
BaseStorage.Write(source, offset + Offset);
|
||||
}
|
||||
|
||||
public override void Flush()
|
||||
{
|
||||
BaseStorage.Flush();
|
||||
}
|
||||
|
||||
public override long GetSize() => _length;
|
||||
|
||||
public override void SetSize(long size)
|
||||
{
|
||||
//if (!IsResizable)
|
||||
// return 0x313802;
|
||||
|
||||
//if (Offset < 0 || size < 0)
|
||||
// return 0x2F5C02;
|
||||
|
||||
if (BaseStorage.GetSize() != Offset + _length)
|
||||
{
|
||||
throw new NotSupportedException("SubStorage cannot be resized unless it is located at the end of the base storage.");
|
||||
}
|
||||
|
||||
BaseStorage.SetSize(Offset + size);
|
||||
|
||||
_length = size;
|
||||
}
|
||||
}
|
||||
}
|
85
src/LibHac/Fs/SubStorage2.cs
Normal file
85
src/LibHac/Fs/SubStorage2.cs
Normal file
@ -0,0 +1,85 @@
|
||||
using System;
|
||||
|
||||
namespace LibHac.Fs
|
||||
{
|
||||
public class SubStorage2 : StorageBase
|
||||
{
|
||||
private IStorage BaseStorage { get; }
|
||||
private long Offset { get; }
|
||||
private long Size { get; set; }
|
||||
public bool IsResizable { get; set; }
|
||||
|
||||
public SubStorage2(IStorage baseStorage, long offset, long size)
|
||||
{
|
||||
BaseStorage = baseStorage;
|
||||
Offset = offset;
|
||||
Size = size;
|
||||
}
|
||||
|
||||
public SubStorage2(SubStorage2 baseStorage, long offset, long size)
|
||||
{
|
||||
BaseStorage = baseStorage.BaseStorage;
|
||||
Offset = baseStorage.Offset + offset;
|
||||
Size = size;
|
||||
}
|
||||
|
||||
protected override Result ReadImpl(long offset, Span<byte> destination)
|
||||
{
|
||||
if (BaseStorage == null) return ResultFs.SubStorageNotInitialized.Log();
|
||||
if (destination.Length == 0) return Result.Success;
|
||||
|
||||
if (!IsRangeValid(offset, destination.Length, Size)) return ResultFs.ValueOutOfRange.Log();
|
||||
|
||||
return BaseStorage.Read(Offset + offset, destination);
|
||||
}
|
||||
|
||||
protected override Result WriteImpl(long offset, ReadOnlySpan<byte> source)
|
||||
{
|
||||
if (BaseStorage == null) return ResultFs.SubStorageNotInitialized.Log();
|
||||
if (source.Length == 0) return Result.Success;
|
||||
|
||||
if (!IsRangeValid(offset, source.Length, Size)) return ResultFs.ValueOutOfRange.Log();
|
||||
|
||||
return BaseStorage.Write(Offset + offset, source);
|
||||
}
|
||||
|
||||
protected override Result FlushImpl()
|
||||
{
|
||||
if (BaseStorage == null) return ResultFs.SubStorageNotInitialized.Log();
|
||||
|
||||
return BaseStorage.Flush();
|
||||
}
|
||||
|
||||
protected override Result SetSizeImpl(long size)
|
||||
{
|
||||
if (BaseStorage == null) return ResultFs.SubStorageNotInitialized.Log();
|
||||
if (!IsResizable) return ResultFs.SubStorageNotResizable.Log();
|
||||
if (size < 0 || Offset < 0) return ResultFs.InvalidSize.Log();
|
||||
|
||||
Result rc = BaseStorage.GetSize(out long baseSize);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
if (baseSize != Offset + Size)
|
||||
{
|
||||
// SubStorage cannot be resized unless it is located at the end of the base storage.
|
||||
return ResultFs.SubStorageNotResizableMiddleOfFile.Log();
|
||||
}
|
||||
|
||||
rc = BaseStorage.SetSize(Offset + size);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
Size = size;
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
protected override Result GetSizeImpl(out long size)
|
||||
{
|
||||
size = default;
|
||||
|
||||
if (BaseStorage == null) return ResultFs.SubStorageNotInitialized.Log();
|
||||
|
||||
size = Size;
|
||||
return Result.Success;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,135 +0,0 @@
|
||||
using System;
|
||||
|
||||
namespace LibHac.Fs
|
||||
{
|
||||
public class SubdirectoryFileSystem : IFileSystem
|
||||
{
|
||||
private string RootPath { get; }
|
||||
private IFileSystem ParentFileSystem { get; }
|
||||
|
||||
private string ResolveFullPath(string path)
|
||||
{
|
||||
return PathTools.Combine(RootPath, path);
|
||||
}
|
||||
|
||||
public SubdirectoryFileSystem(IFileSystem fs, string rootPath)
|
||||
{
|
||||
ParentFileSystem = fs;
|
||||
RootPath = PathTools.Normalize(rootPath);
|
||||
}
|
||||
|
||||
public void CreateDirectory(string path)
|
||||
{
|
||||
path = PathTools.Normalize(path);
|
||||
|
||||
ParentFileSystem.CreateDirectory(ResolveFullPath(path));
|
||||
}
|
||||
|
||||
public void CreateFile(string path, long size, CreateFileOptions options)
|
||||
{
|
||||
path = PathTools.Normalize(path);
|
||||
|
||||
ParentFileSystem.CreateFile(ResolveFullPath(path), size, options);
|
||||
}
|
||||
|
||||
public void DeleteDirectory(string path)
|
||||
{
|
||||
path = PathTools.Normalize(path);
|
||||
|
||||
ParentFileSystem.DeleteDirectory(ResolveFullPath(path));
|
||||
}
|
||||
|
||||
public void DeleteDirectoryRecursively(string path)
|
||||
{
|
||||
path = PathTools.Normalize(path);
|
||||
|
||||
ParentFileSystem.DeleteDirectoryRecursively(ResolveFullPath(path));
|
||||
}
|
||||
|
||||
public void CleanDirectoryRecursively(string path)
|
||||
{
|
||||
path = PathTools.Normalize(path);
|
||||
|
||||
ParentFileSystem.CleanDirectoryRecursively(ResolveFullPath(path));
|
||||
}
|
||||
|
||||
public void DeleteFile(string path)
|
||||
{
|
||||
path = PathTools.Normalize(path);
|
||||
|
||||
ParentFileSystem.DeleteFile(ResolveFullPath(path));
|
||||
}
|
||||
|
||||
public IDirectory OpenDirectory(string path, OpenDirectoryMode mode)
|
||||
{
|
||||
path = PathTools.Normalize(path);
|
||||
|
||||
IDirectory baseDir = ParentFileSystem.OpenDirectory(ResolveFullPath(path), mode);
|
||||
|
||||
return new SubdirectoryFileSystemDirectory(this, baseDir, path, mode);
|
||||
}
|
||||
|
||||
public IFile OpenFile(string path, OpenMode mode)
|
||||
{
|
||||
path = PathTools.Normalize(path);
|
||||
|
||||
return ParentFileSystem.OpenFile(ResolveFullPath(path), mode);
|
||||
}
|
||||
|
||||
public void RenameDirectory(string srcPath, string dstPath)
|
||||
{
|
||||
srcPath = PathTools.Normalize(srcPath);
|
||||
dstPath = PathTools.Normalize(dstPath);
|
||||
|
||||
ParentFileSystem.RenameDirectory(ResolveFullPath(srcPath), ResolveFullPath(dstPath));
|
||||
}
|
||||
|
||||
public void RenameFile(string srcPath, string dstPath)
|
||||
{
|
||||
srcPath = PathTools.Normalize(srcPath);
|
||||
dstPath = PathTools.Normalize(dstPath);
|
||||
|
||||
ParentFileSystem.RenameFile(ResolveFullPath(srcPath), ResolveFullPath(dstPath));
|
||||
}
|
||||
|
||||
public DirectoryEntryType GetEntryType(string path)
|
||||
{
|
||||
path = PathTools.Normalize(path);
|
||||
|
||||
return ParentFileSystem.GetEntryType(ResolveFullPath(path));
|
||||
}
|
||||
|
||||
public void Commit()
|
||||
{
|
||||
ParentFileSystem.Commit();
|
||||
}
|
||||
|
||||
public long GetFreeSpaceSize(string path)
|
||||
{
|
||||
path = PathTools.Normalize(path);
|
||||
|
||||
return ParentFileSystem.GetFreeSpaceSize(ResolveFullPath(path));
|
||||
}
|
||||
|
||||
public long GetTotalSpaceSize(string path)
|
||||
{
|
||||
path = PathTools.Normalize(path);
|
||||
|
||||
return ParentFileSystem.GetTotalSpaceSize(ResolveFullPath(path));
|
||||
}
|
||||
|
||||
public FileTimeStampRaw GetFileTimeStampRaw(string path)
|
||||
{
|
||||
path = PathTools.Normalize(path);
|
||||
|
||||
return ParentFileSystem.GetFileTimeStampRaw(ResolveFullPath(path));
|
||||
}
|
||||
|
||||
public void QueryEntry(Span<byte> outBuffer, ReadOnlySpan<byte> inBuffer, string path, QueryId queryId)
|
||||
{
|
||||
path = PathTools.Normalize(path);
|
||||
|
||||
ParentFileSystem.QueryEntry(outBuffer, inBuffer, ResolveFullPath(path), queryId);
|
||||
}
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
@ -1,66 +1,59 @@
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Runtime.InteropServices;
|
||||
using LibHac.Common;
|
||||
|
||||
namespace LibHac.Fs
|
||||
{
|
||||
[DebuggerDisplay("{ToString(),nq}")]
|
||||
[StructLayout(LayoutKind.Sequential, Size = 0x10)]
|
||||
public struct UserId : IEquatable<UserId>, IComparable<UserId>, IComparable
|
||||
{
|
||||
public readonly ulong High;
|
||||
public readonly ulong Low;
|
||||
public static UserId Zero => default;
|
||||
|
||||
public readonly Id128 Id;
|
||||
|
||||
public UserId(ulong high, ulong low)
|
||||
{
|
||||
High = high;
|
||||
Low = low;
|
||||
Id = new Id128(high, low);
|
||||
}
|
||||
|
||||
public UserId(ReadOnlySpan<byte> uid)
|
||||
{
|
||||
ReadOnlySpan<ulong> longs = MemoryMarshal.Cast<byte, ulong>(uid);
|
||||
|
||||
High = longs[0];
|
||||
Low = longs[1];
|
||||
Id = new Id128(uid);
|
||||
}
|
||||
|
||||
public bool Equals(UserId other)
|
||||
public override string ToString()
|
||||
{
|
||||
return High == other.High && Low == other.Low;
|
||||
return $"0x{Id.High:x8}{Id.Low:x8}";
|
||||
}
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
return obj is UserId other && Equals(other);
|
||||
}
|
||||
public bool Equals(UserId other) => Id == other.Id;
|
||||
public override bool Equals(object obj) => obj is UserId other && Equals(other);
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
unchecked
|
||||
{
|
||||
return (High.GetHashCode() * 397) ^ Low.GetHashCode();
|
||||
}
|
||||
}
|
||||
public override int GetHashCode() => Id.GetHashCode();
|
||||
|
||||
public int CompareTo(UserId other)
|
||||
{
|
||||
// ReSharper disable ImpureMethodCallOnReadonlyValueField
|
||||
int highComparison = High.CompareTo(other.High);
|
||||
if (highComparison != 0) return highComparison;
|
||||
return Low.CompareTo(other.Low);
|
||||
// ReSharper restore ImpureMethodCallOnReadonlyValueField
|
||||
}
|
||||
public int CompareTo(UserId other) => Id.CompareTo(other.Id);
|
||||
|
||||
public int CompareTo(object obj)
|
||||
{
|
||||
if (ReferenceEquals(null, obj)) return 1;
|
||||
if (obj is null) return 1;
|
||||
return obj is UserId other ? CompareTo(other) : throw new ArgumentException($"Object must be of type {nameof(UserId)}");
|
||||
}
|
||||
|
||||
public void ToBytes(Span<byte> output)
|
||||
{
|
||||
Span<ulong> longs = MemoryMarshal.Cast<byte, ulong>(output);
|
||||
public void ToBytes(Span<byte> output) => Id.ToBytes(output);
|
||||
|
||||
longs[0] = High;
|
||||
longs[1] = Low;
|
||||
public ReadOnlySpan<byte> AsBytes()
|
||||
{
|
||||
return SpanHelpers.AsByteSpan(ref this);
|
||||
}
|
||||
|
||||
public static bool operator ==(UserId left, UserId right) => left.Equals(right);
|
||||
public static bool operator !=(UserId left, UserId right) => !left.Equals(right);
|
||||
|
||||
public static bool operator <(UserId left, UserId right) => left.CompareTo(right) < 0;
|
||||
public static bool operator >(UserId left, UserId right) => left.CompareTo(right) > 0;
|
||||
public static bool operator <=(UserId left, UserId right) => left.CompareTo(right) <= 0;
|
||||
public static bool operator >=(UserId left, UserId right) => left.CompareTo(right) >= 0;
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,99 @@
|
||||
using System.Diagnostics;
|
||||
using LibHac.Fs;
|
||||
|
||||
namespace LibHac.FsService.Creators
|
||||
{
|
||||
public class EmulatedBisFileSystemCreator : IBuiltInStorageFileSystemCreator
|
||||
{
|
||||
private EmulatedBisFileSystemCreatorConfig Config { get; }
|
||||
|
||||
public EmulatedBisFileSystemCreator(IFileSystem rootFileSystem)
|
||||
{
|
||||
Config = new EmulatedBisFileSystemCreatorConfig();
|
||||
Config.RootFileSystem = rootFileSystem;
|
||||
}
|
||||
|
||||
public EmulatedBisFileSystemCreator(EmulatedBisFileSystemCreatorConfig config)
|
||||
{
|
||||
Config = config;
|
||||
}
|
||||
|
||||
public Result Create(out IFileSystem fileSystem, string rootPath, BisPartitionId partitionId)
|
||||
{
|
||||
fileSystem = default;
|
||||
|
||||
if (!IsValidPartitionId(partitionId)) return ResultFs.InvalidArgument;
|
||||
if (rootPath == null) return ResultFs.NullArgument;
|
||||
|
||||
if (Config.TryGetFileSystem(out fileSystem, partitionId))
|
||||
{
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
if (Config.RootFileSystem == null)
|
||||
{
|
||||
return ResultFs.PreconditionViolation;
|
||||
}
|
||||
|
||||
string partitionPath = GetPartitionPath(partitionId);
|
||||
|
||||
Result rc =
|
||||
Util.CreateSubFileSystem(out IFileSystem subFileSystem, Config.RootFileSystem, partitionPath, true);
|
||||
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
if (rootPath == string.Empty)
|
||||
{
|
||||
fileSystem = subFileSystem;
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
return Util.CreateSubFileSystemImpl(out fileSystem, subFileSystem, rootPath);
|
||||
}
|
||||
|
||||
public Result CreateFatFileSystem(out IFileSystem fileSystem, BisPartitionId partitionId)
|
||||
{
|
||||
fileSystem = default;
|
||||
return ResultFs.NotImplemented;
|
||||
}
|
||||
|
||||
public Result SetBisRootForHost(BisPartitionId partitionId, string rootPath)
|
||||
{
|
||||
return Config.SetPath(rootPath, partitionId);
|
||||
}
|
||||
|
||||
private bool IsValidPartitionId(BisPartitionId id)
|
||||
{
|
||||
return id >= BisPartitionId.CalibrationFile && id <= BisPartitionId.System;
|
||||
}
|
||||
|
||||
private string GetPartitionPath(BisPartitionId id)
|
||||
{
|
||||
if (Config.TryGetPartitionPath(out string path, id))
|
||||
{
|
||||
return path;
|
||||
}
|
||||
|
||||
return GetDefaultPartitionPath(id);
|
||||
}
|
||||
|
||||
private string GetDefaultPartitionPath(BisPartitionId id)
|
||||
{
|
||||
Debug.Assert(IsValidPartitionId(id));
|
||||
|
||||
switch (id)
|
||||
{
|
||||
case BisPartitionId.CalibrationFile:
|
||||
return "/bis/cal";
|
||||
case BisPartitionId.SafeMode:
|
||||
return "/bis/safe";
|
||||
case BisPartitionId.User:
|
||||
return "/bis/user";
|
||||
case BisPartitionId.System:
|
||||
return "/bis/system";
|
||||
default:
|
||||
return string.Empty;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,73 @@
|
||||
using System.Diagnostics;
|
||||
using LibHac.Fs;
|
||||
|
||||
namespace LibHac.FsService.Creators
|
||||
{
|
||||
public class EmulatedBisFileSystemCreatorConfig
|
||||
{
|
||||
private const int ValidPartitionCount = 4;
|
||||
|
||||
public IFileSystem RootFileSystem { get; set; }
|
||||
|
||||
private IFileSystem[] PartitionFileSystems { get; } = new IFileSystem[ValidPartitionCount];
|
||||
private string[] PartitionPaths { get; } = new string[ValidPartitionCount];
|
||||
|
||||
public Result SetFileSystem(IFileSystem fileSystem, BisPartitionId partitionId)
|
||||
{
|
||||
if (fileSystem == null) return ResultFs.NullArgument;
|
||||
if (!IsValidPartitionId(partitionId)) return ResultFs.InvalidArgument;
|
||||
|
||||
PartitionFileSystems[GetArrayIndex(partitionId)] = fileSystem;
|
||||
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
public Result SetPath(string path, BisPartitionId partitionId)
|
||||
{
|
||||
if (path == null) return ResultFs.NullArgument;
|
||||
if (!IsValidPartitionId(partitionId)) return ResultFs.InvalidArgument;
|
||||
|
||||
PartitionPaths[GetArrayIndex(partitionId)] = path;
|
||||
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
public bool TryGetFileSystem(out IFileSystem fileSystem, BisPartitionId partitionId)
|
||||
{
|
||||
if (!IsValidPartitionId(partitionId))
|
||||
{
|
||||
fileSystem = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
fileSystem = PartitionFileSystems[GetArrayIndex(partitionId)];
|
||||
|
||||
return fileSystem != null;
|
||||
}
|
||||
|
||||
public bool TryGetPartitionPath(out string path, BisPartitionId partitionId)
|
||||
{
|
||||
if (!IsValidPartitionId(partitionId))
|
||||
{
|
||||
path = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
path = PartitionPaths[GetArrayIndex(partitionId)];
|
||||
|
||||
return path != null;
|
||||
}
|
||||
|
||||
private int GetArrayIndex(BisPartitionId id)
|
||||
{
|
||||
Debug.Assert(IsValidPartitionId(id));
|
||||
|
||||
return id - BisPartitionId.CalibrationFile;
|
||||
}
|
||||
|
||||
private bool IsValidPartitionId(BisPartitionId id)
|
||||
{
|
||||
return id >= BisPartitionId.CalibrationFile && id <= BisPartitionId.System;
|
||||
}
|
||||
}
|
||||
}
|
34
src/LibHac/FsService/Creators/EmulatedGameCardFsCreator.cs
Normal file
34
src/LibHac/FsService/Creators/EmulatedGameCardFsCreator.cs
Normal file
@ -0,0 +1,34 @@
|
||||
using LibHac.Fs;
|
||||
|
||||
namespace LibHac.FsService.Creators
|
||||
{
|
||||
public class EmulatedGameCardFsCreator : IGameCardFileSystemCreator
|
||||
{
|
||||
// ReSharper disable once UnusedAutoPropertyAccessor.Local
|
||||
private EmulatedGameCardStorageCreator StorageCreator { get; }
|
||||
private EmulatedGameCard GameCard { get; }
|
||||
|
||||
public EmulatedGameCardFsCreator(EmulatedGameCardStorageCreator storageCreator, EmulatedGameCard gameCard)
|
||||
{
|
||||
StorageCreator = storageCreator;
|
||||
GameCard = gameCard;
|
||||
}
|
||||
public Result Create(out IFileSystem fileSystem, GameCardHandle handle, GameCardPartition partitionType)
|
||||
{
|
||||
// Use the old xci code temporarily
|
||||
|
||||
fileSystem = default;
|
||||
|
||||
Result rc = GameCard.GetXci(out Xci xci, handle);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
if (!xci.HasPartition((XciPartitionType)partitionType))
|
||||
{
|
||||
return ResultFs.PartitionNotFound.Log();
|
||||
}
|
||||
|
||||
fileSystem = xci.OpenPartition((XciPartitionType)partitionType);
|
||||
return Result.Success;
|
||||
}
|
||||
}
|
||||
}
|
125
src/LibHac/FsService/Creators/EmulatedGameCardStorageCreator.cs
Normal file
125
src/LibHac/FsService/Creators/EmulatedGameCardStorageCreator.cs
Normal file
@ -0,0 +1,125 @@
|
||||
using System;
|
||||
using LibHac.Fs;
|
||||
|
||||
namespace LibHac.FsService.Creators
|
||||
{
|
||||
public class EmulatedGameCardStorageCreator : IGameCardStorageCreator
|
||||
{
|
||||
private EmulatedGameCard GameCard { get; }
|
||||
|
||||
public EmulatedGameCardStorageCreator(EmulatedGameCard gameCard)
|
||||
{
|
||||
GameCard = gameCard;
|
||||
}
|
||||
|
||||
public Result CreateNormal(GameCardHandle handle, out IStorage storage)
|
||||
{
|
||||
storage = default;
|
||||
|
||||
if (GameCard.IsGameCardHandleInvalid(handle))
|
||||
{
|
||||
return ResultFs.InvalidGameCardHandleOnOpenNormalPartition.Log();
|
||||
}
|
||||
|
||||
var baseStorage = new ReadOnlyGameCardStorage(GameCard, handle);
|
||||
|
||||
Result rc = GameCard.GetCardInfo(out GameCardInfo cardInfo, handle);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
storage = new SubStorage2(baseStorage, 0, cardInfo.SecureAreaOffset);
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
public Result CreateSecure(GameCardHandle handle, out IStorage storage)
|
||||
{
|
||||
storage = default;
|
||||
|
||||
if (GameCard.IsGameCardHandleInvalid(handle))
|
||||
{
|
||||
return ResultFs.InvalidGameCardHandleOnOpenSecurePartition.Log();
|
||||
}
|
||||
|
||||
Span<byte> deviceId = stackalloc byte[0x10];
|
||||
Span<byte> imageHash = stackalloc byte[0x20];
|
||||
|
||||
Result rc = GameCard.GetGameCardDeviceId(deviceId);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
rc = GameCard.GetGameCardImageHash(imageHash);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
var baseStorage = new ReadOnlyGameCardStorage(GameCard, handle, deviceId, imageHash);
|
||||
|
||||
rc = GameCard.GetCardInfo(out GameCardInfo cardInfo, handle);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
storage = new SubStorage2(baseStorage, cardInfo.SecureAreaOffset, cardInfo.SecureAreaSize);
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
public Result CreateWritable(GameCardHandle handle, out IStorage storage)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
private class ReadOnlyGameCardStorage : StorageBase
|
||||
{
|
||||
private EmulatedGameCard GameCard { get; }
|
||||
private GameCardHandle Handle { get; set; }
|
||||
|
||||
// ReSharper disable once UnusedAutoPropertyAccessor.Local
|
||||
private bool IsSecureMode { get; }
|
||||
private byte[] DeviceId { get; } = new byte[0x10];
|
||||
private byte[] ImageHash { get; } = new byte[0x20];
|
||||
|
||||
public ReadOnlyGameCardStorage(EmulatedGameCard gameCard, GameCardHandle handle)
|
||||
{
|
||||
GameCard = gameCard;
|
||||
Handle = handle;
|
||||
}
|
||||
|
||||
public ReadOnlyGameCardStorage(EmulatedGameCard gameCard, GameCardHandle handle, ReadOnlySpan<byte> deviceId, ReadOnlySpan<byte> imageHash)
|
||||
{
|
||||
GameCard = gameCard;
|
||||
Handle = handle;
|
||||
IsSecureMode = true;
|
||||
deviceId.CopyTo(DeviceId);
|
||||
imageHash.CopyTo(ImageHash);
|
||||
}
|
||||
|
||||
protected override Result ReadImpl(long offset, Span<byte> destination)
|
||||
{
|
||||
// In secure mode, if Handle is old and the card's device ID and
|
||||
// header hash are still the same, Handle is updated to the new handle
|
||||
|
||||
return GameCard.Read(Handle, offset, destination);
|
||||
}
|
||||
|
||||
protected override Result WriteImpl(long offset, ReadOnlySpan<byte> source)
|
||||
{
|
||||
return ResultFs.UnsupportedOperationInRoGameCardStorageWrite.Log();
|
||||
}
|
||||
|
||||
protected override Result FlushImpl()
|
||||
{
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
protected override Result SetSizeImpl(long size)
|
||||
{
|
||||
return ResultFs.UnsupportedOperationInRoGameCardStorageSetSize.Log();
|
||||
}
|
||||
|
||||
protected override Result GetSizeImpl(out long size)
|
||||
{
|
||||
size = 0;
|
||||
|
||||
Result rc = GameCard.GetCardInfo(out GameCardInfo info, Handle);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
size = info.Size;
|
||||
return Result.Success;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
48
src/LibHac/FsService/Creators/EmulatedSdFileSystemCreator.cs
Normal file
48
src/LibHac/FsService/Creators/EmulatedSdFileSystemCreator.cs
Normal file
@ -0,0 +1,48 @@
|
||||
using System;
|
||||
using LibHac.Fs;
|
||||
|
||||
namespace LibHac.FsService.Creators
|
||||
{
|
||||
class EmulatedSdFileSystemCreator : ISdFileSystemCreator
|
||||
{
|
||||
private const string DefaultPath = "/sdcard";
|
||||
|
||||
public IFileSystem RootFileSystem { get; set; }
|
||||
public string Path { get; set; }
|
||||
|
||||
public IFileSystem SdCardFileSystem { get; set; }
|
||||
|
||||
public EmulatedSdFileSystemCreator(IFileSystem rootFileSystem)
|
||||
{
|
||||
RootFileSystem = rootFileSystem;
|
||||
}
|
||||
|
||||
public Result Create(out IFileSystem fileSystem)
|
||||
{
|
||||
fileSystem = default;
|
||||
|
||||
if (SdCardFileSystem != null)
|
||||
{
|
||||
fileSystem = SdCardFileSystem;
|
||||
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
if (RootFileSystem == null)
|
||||
{
|
||||
return ResultFs.PreconditionViolation;
|
||||
}
|
||||
|
||||
string path = Path ?? DefaultPath;
|
||||
|
||||
// Todo: Add ProxyFileSystem?
|
||||
|
||||
return Util.CreateSubFileSystem(out fileSystem, RootFileSystem, path, true);
|
||||
}
|
||||
|
||||
public Result Format(bool closeOpenEntries)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
}
|
34
src/LibHac/FsService/Creators/EncryptedFileSystemCreator.cs
Normal file
34
src/LibHac/FsService/Creators/EncryptedFileSystemCreator.cs
Normal file
@ -0,0 +1,34 @@
|
||||
using System;
|
||||
using LibHac.Fs;
|
||||
using LibHac.FsSystem;
|
||||
|
||||
namespace LibHac.FsService.Creators
|
||||
{
|
||||
public class EncryptedFileSystemCreator : IEncryptedFileSystemCreator
|
||||
{
|
||||
private Keyset Keyset { get; }
|
||||
|
||||
public EncryptedFileSystemCreator(Keyset keyset)
|
||||
{
|
||||
Keyset = keyset;
|
||||
}
|
||||
|
||||
public Result Create(out IFileSystem encryptedFileSystem, IFileSystem baseFileSystem, EncryptedFsKeyId keyId,
|
||||
ReadOnlySpan<byte> encryptionSeed)
|
||||
{
|
||||
encryptedFileSystem = default;
|
||||
|
||||
if (keyId < EncryptedFsKeyId.Save || keyId > EncryptedFsKeyId.CustomStorage)
|
||||
{
|
||||
return ResultFs.InvalidArgument;
|
||||
}
|
||||
|
||||
// todo: "proper" key generation instead of a lazy hack
|
||||
Keyset.SetSdSeed(encryptionSeed.ToArray());
|
||||
|
||||
encryptedFileSystem = new AesXtsFileSystem(baseFileSystem, Keyset.SdCardKeys[(int)keyId], 0x4000);
|
||||
|
||||
return Result.Success;
|
||||
}
|
||||
}
|
||||
}
|
22
src/LibHac/FsService/Creators/FileSystemCreators.cs
Normal file
22
src/LibHac/FsService/Creators/FileSystemCreators.cs
Normal file
@ -0,0 +1,22 @@
|
||||
namespace LibHac.FsService.Creators
|
||||
{
|
||||
public class FileSystemCreators
|
||||
{
|
||||
public IRomFileSystemCreator RomFileSystemCreator { get; set; }
|
||||
public IPartitionFileSystemCreator PartitionFileSystemCreator { get; set; }
|
||||
public IStorageOnNcaCreator StorageOnNcaCreator { get; set; }
|
||||
public IFatFileSystemCreator FatFileSystemCreator { get; set; }
|
||||
public IHostFileSystemCreator HostFileSystemCreator { get; set; }
|
||||
public ITargetManagerFileSystemCreator TargetManagerFileSystemCreator { get; set; }
|
||||
public ISubDirectoryFileSystemCreator SubDirectoryFileSystemCreator { get; set; }
|
||||
public IBuiltInStorageCreator BuiltInStorageCreator { get; set; }
|
||||
public ISdStorageCreator SdStorageCreator { get; set; }
|
||||
public ISaveDataFileSystemCreator SaveDataFileSystemCreator { get; set; }
|
||||
public IGameCardStorageCreator GameCardStorageCreator { get; set; }
|
||||
public IGameCardFileSystemCreator GameCardFileSystemCreator { get; set; }
|
||||
public IEncryptedFileSystemCreator EncryptedFileSystemCreator { get; set; }
|
||||
public IMemoryStorageCreator MemoryStorageCreator { get; set; }
|
||||
public IBuiltInStorageFileSystemCreator BuiltInStorageFileSystemCreator { get; set; }
|
||||
public ISdFileSystemCreator SdFileSystemCreator { get; set; }
|
||||
}
|
||||
}
|
9
src/LibHac/FsService/Creators/IBuiltInStorageCreator.cs
Normal file
9
src/LibHac/FsService/Creators/IBuiltInStorageCreator.cs
Normal file
@ -0,0 +1,9 @@
|
||||
using LibHac.Fs;
|
||||
|
||||
namespace LibHac.FsService.Creators
|
||||
{
|
||||
public interface IBuiltInStorageCreator
|
||||
{
|
||||
Result Create(out IStorage storage, BisPartitionId partitionId);
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
using LibHac.Fs;
|
||||
|
||||
namespace LibHac.FsService.Creators
|
||||
{
|
||||
public interface IBuiltInStorageFileSystemCreator
|
||||
{
|
||||
Result Create(out IFileSystem fileSystem, string rootPath, BisPartitionId partitionId);
|
||||
Result CreateFatFileSystem(out IFileSystem fileSystem, BisPartitionId partitionId);
|
||||
Result SetBisRootForHost(BisPartitionId partitionId, string rootPath);
|
||||
}
|
||||
}
|
18
src/LibHac/FsService/Creators/IEncryptedFileSystemCreator.cs
Normal file
18
src/LibHac/FsService/Creators/IEncryptedFileSystemCreator.cs
Normal file
@ -0,0 +1,18 @@
|
||||
using System;
|
||||
using LibHac.Fs;
|
||||
|
||||
namespace LibHac.FsService.Creators
|
||||
{
|
||||
public interface IEncryptedFileSystemCreator
|
||||
{
|
||||
Result Create(out IFileSystem encryptedFileSystem, IFileSystem baseFileSystem, EncryptedFsKeyId keyId,
|
||||
ReadOnlySpan<byte> encryptionSeed);
|
||||
}
|
||||
|
||||
public enum EncryptedFsKeyId
|
||||
{
|
||||
Save = 0,
|
||||
Content = 1,
|
||||
CustomStorage = 2
|
||||
}
|
||||
}
|
9
src/LibHac/FsService/Creators/IFatFileSystemCreator.cs
Normal file
9
src/LibHac/FsService/Creators/IFatFileSystemCreator.cs
Normal file
@ -0,0 +1,9 @@
|
||||
using LibHac.Fs;
|
||||
|
||||
namespace LibHac.FsService.Creators
|
||||
{
|
||||
public interface IFatFileSystemCreator
|
||||
{
|
||||
Result Create(out IFileSystem fileSystem, IStorage baseStorage);
|
||||
}
|
||||
}
|
@ -0,0 +1,9 @@
|
||||
using LibHac.Fs;
|
||||
|
||||
namespace LibHac.FsService.Creators
|
||||
{
|
||||
public interface IGameCardFileSystemCreator
|
||||
{
|
||||
Result Create(out IFileSystem fileSystem, GameCardHandle handle, GameCardPartition partitionType);
|
||||
}
|
||||
}
|
11
src/LibHac/FsService/Creators/IGameCardStorageCreator.cs
Normal file
11
src/LibHac/FsService/Creators/IGameCardStorageCreator.cs
Normal file
@ -0,0 +1,11 @@
|
||||
using LibHac.Fs;
|
||||
|
||||
namespace LibHac.FsService.Creators
|
||||
{
|
||||
public interface IGameCardStorageCreator
|
||||
{
|
||||
Result CreateNormal(GameCardHandle handle, out IStorage storage);
|
||||
Result CreateSecure(GameCardHandle handle, out IStorage storage);
|
||||
Result CreateWritable(GameCardHandle handle, out IStorage storage);
|
||||
}
|
||||
}
|
10
src/LibHac/FsService/Creators/IHostFileSystemCreator.cs
Normal file
10
src/LibHac/FsService/Creators/IHostFileSystemCreator.cs
Normal file
@ -0,0 +1,10 @@
|
||||
using LibHac.Fs;
|
||||
|
||||
namespace LibHac.FsService.Creators
|
||||
{
|
||||
public interface IHostFileSystemCreator
|
||||
{
|
||||
Result Create(out IFileSystem fileSystem, bool someBool);
|
||||
Result Create(out IFileSystem fileSystem, string path, bool openCaseSensitive);
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user