Implement or skeleton fsa file system code

This commit is contained in:
Alex Barney 2021-02-16 17:02:50 -07:00
parent 61654298d2
commit a11e84cc81
20 changed files with 1121 additions and 20 deletions

View File

@ -563,6 +563,7 @@ Module,DescriptionStart,DescriptionEnd,Flags,Namespace,Name,Summary
2,6071,,,,InvalidCommitNameCount,Up to 10 file systems can be committed at the same time.
2,6072,,,,InvalidOpenMode,
2,6074,,,,InvalidDirectoryOpenMode,
2,6075,,,,InvalidCommitOption,
2,6080,6099,,,InvalidEnumValue,
2,6081,,,,InvalidSaveDataState,

1 Module DescriptionStart DescriptionEnd Flags Namespace Name Summary
563 2 6352 6351 UnsupportedOperateRangeForReadOnlyGameCardStorage UnsupportedSetSizeForReadOnlyGameCardStorage
564 2 6353 6352 UnsupportedSetSizeForSdmmcStorage UnsupportedOperateRangeForReadOnlyGameCardStorage
565 2 6354 6353 UnsupportedOperateRangeForSdmmcStorage UnsupportedSetSizeForSdmmcStorage
566 2 6354 UnsupportedOperateRangeForSdmmcStorage
567 2 6355 UnsupportedOperateRangeForFatFile
568 2 6356 UnsupportedOperateRangeForStorageFile
569 2 6357 UnsupportedSetSizeForInternalStorageConcatenationFile

View File

@ -91,7 +91,7 @@ namespace LibHac.Common
/// </summary>
/// <returns><see langword="true"/> if the span has no buffer.
/// Otherwise, <see langword="false"/>.</returns>
public unsafe bool IsNull() => (IntPtr)Unsafe.AsPointer(ref MemoryMarshal.GetReference(_buffer)) == IntPtr.Zero;
public bool IsNull() => Unsafe.IsNullRef(ref MemoryMarshal.GetReference(_buffer));
/// <summary>
/// Checks if the <see cref="U8Span"/> has no buffer or begins with a null terminator.

View File

@ -87,7 +87,7 @@ namespace LibHac.Common
/// </summary>
/// <returns><see langword="true"/> if the span has no buffer.
/// Otherwise, <see langword="false"/>.</returns>
public unsafe bool IsNull() => (IntPtr)Unsafe.AsPointer(ref MemoryMarshal.GetReference(_buffer)) == IntPtr.Zero;
public bool IsNull() => Unsafe.IsNullRef(ref MemoryMarshal.GetReference(_buffer));
/// <summary>
/// Checks if the <see cref="U8StringMutable"/> has no buffer or begins with a null terminator.

View File

@ -3,6 +3,7 @@ using System.Buffers;
using System.Buffers.Text;
using System.Diagnostics;
using System.Runtime.CompilerServices;
using LibHac.Diag;
using LibHac.Util;
namespace LibHac.Common
@ -12,9 +13,12 @@ namespace LibHac.Common
{
private const int NullTerminatorLength = 1;
public Span<byte> Buffer { get; }
public Span<byte> Buffer { get; private set; }
public int Length { get; private set; }
public bool Overflowed { get; private set; }
public bool AutoExpand { get; }
private byte[] _rentedArray;
public readonly int Capacity
{
@ -22,17 +26,36 @@ namespace LibHac.Common
get => Buffer.Length - NullTerminatorLength;
}
public U8StringBuilder(Span<byte> buffer)
public U8StringBuilder(Span<byte> buffer, bool autoExpand = false)
{
Buffer = buffer;
Length = 0;
Overflowed = false;
AutoExpand = autoExpand;
_rentedArray = null;
if (autoExpand)
{
TryEnsureAdditionalCapacity(1);
}
else
{
ThrowIfBufferLengthIsZero();
}
AddNullTerminator();
}
public void Dispose()
{
byte[] toReturn = _rentedArray;
this = default;
if (toReturn is not null)
{
ArrayPool<byte>.Shared.Return(_rentedArray);
}
}
// These functions are internal so they can be called by the extension methods
// in U8StringBuilderExtensions. It's not an ideal setup, but it allows append
// calls to be chained without accidentally creating a copy of the U8StringBuilder.
@ -42,7 +65,7 @@ namespace LibHac.Common
int valueLength = StringUtils.GetLength(value);
if (!HasAdditionalCapacity(valueLength))
if (!TryEnsureAdditionalCapacity(valueLength))
{
Overflowed = true;
return;
@ -57,7 +80,7 @@ namespace LibHac.Common
{
if (Overflowed) return;
if (!HasAdditionalCapacity(1))
if (!TryEnsureAdditionalCapacity(1))
{
Overflowed = true;
return;
@ -114,10 +137,18 @@ namespace LibHac.Common
return requiredCapacity <= Capacity;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private readonly bool HasAdditionalCapacity(int requiredAdditionalCapacity)
private bool TryEnsureAdditionalCapacity(int requiredAdditionalCapacity)
{
return HasCapacity(Length + requiredAdditionalCapacity);
bool hasCapacity = HasCapacity(Length + requiredAdditionalCapacity);
if (!hasCapacity && AutoExpand)
{
Grow(requiredAdditionalCapacity);
Assert.True(HasCapacity(Length + requiredAdditionalCapacity));
hasCapacity = true;
}
return hasCapacity;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
@ -126,6 +157,21 @@ namespace LibHac.Common
Buffer[Length] = 0;
}
private void Grow(int requiredAdditionalCapacity)
{
byte[] poolArray =
ArrayPool<byte>.Shared.Rent(Math.Max(Length + requiredAdditionalCapacity, Capacity * 2));
Buffer.Slice(0, Length).CopyTo(poolArray);
byte[] toReturn = _rentedArray;
_rentedArray = poolArray;
if (toReturn is not null)
{
ArrayPool<byte>.Shared.Return(toReturn);
}
}
private readonly void ThrowIfBufferLengthIsZero()
{
if (Buffer.Length == 0) throw new ArgumentException("Buffer length must be greater than 0.");

View File

@ -1,5 +1,8 @@
using System;
using System.Buffers.Text;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using LibHac.Common;
using LibHac.Diag;
using LibHac.Fs.Impl;
using LibHac.Fs.Shim;
@ -115,8 +118,160 @@ namespace LibHac.Fs.Impl
All = Application | System
}
internal readonly struct IdentifyAccessLogHandle
{
public readonly object Handle;
private IdentifyAccessLogHandle(object handle) => Handle = handle;
public static IdentifyAccessLogHandle MakeHandle(object handle) => new IdentifyAccessLogHandle(handle);
}
internal struct IdString
{
private Buffer32 _buffer;
private ReadOnlySpan<byte> ToValueString(int value)
{
bool success = Utf8Formatter.TryFormat(value, _buffer.Bytes, out int length);
Assert.True(success && length < _buffer.Bytes.Length);
_buffer[length] = 0;
return _buffer.Bytes;
}
public ReadOnlySpan<byte> ToString(Priority value)
{
switch (value)
{
case Priority.Realtime: return new[] { (byte)'R', (byte)'e', (byte)'a', (byte)'l', (byte)'t', (byte)'i', (byte)'m', (byte)'e' };
case Priority.Normal: return new[] { (byte)'N', (byte)'o', (byte)'r', (byte)'m', (byte)'a', (byte)'l' };
case Priority.Low: return new[] { (byte)'L', (byte)'o', (byte)'w' };
default: return ToValueString((int)value);
}
}
public ReadOnlySpan<byte> ToString(PriorityRaw value)
{
switch (value)
{
case PriorityRaw.Realtime: return new[] { (byte)'R', (byte)'e', (byte)'a', (byte)'l', (byte)'t', (byte)'i', (byte)'m', (byte)'e' };
case PriorityRaw.Normal: return new[] { (byte)'N', (byte)'o', (byte)'r', (byte)'m', (byte)'a', (byte)'l' };
case PriorityRaw.Low: return new[] { (byte)'L', (byte)'o', (byte)'w' };
case PriorityRaw.Background: return new[] { (byte)'B', (byte)'a', (byte)'c', (byte)'k', (byte)'g', (byte)'r', (byte)'o', (byte)'u', (byte)'n', (byte)'d' };
default: return ToValueString((int)value);
}
}
public ReadOnlySpan<byte> ToString(ImageDirectoryId value)
{
switch (value)
{
case ImageDirectoryId.Nand: return new[] { (byte)'N', (byte)'a', (byte)'n', (byte)'d' };
case ImageDirectoryId.SdCard: return new[] { (byte)'S', (byte)'d', (byte)'C', (byte)'a', (byte)'r', (byte)'d' };
default: return ToValueString((int)value);
}
}
public ReadOnlySpan<byte> ToString(ContentStorageId value)
{
switch (value)
{
case ContentStorageId.System: return new[] { (byte)'S', (byte)'y', (byte)'s', (byte)'t', (byte)'e', (byte)'m' };
case ContentStorageId.User: return new[] { (byte)'U', (byte)'s', (byte)'e', (byte)'r' };
case ContentStorageId.SdCard: return new[] { (byte)'S', (byte)'d', (byte)'C', (byte)'a', (byte)'r', (byte)'d' };
default: return ToValueString((int)value);
}
}
public ReadOnlySpan<byte> ToString(GameCardPartition value)
{
switch (value)
{
case GameCardPartition.Update: return new[] { (byte)'U', (byte)'p', (byte)'d', (byte)'a', (byte)'t', (byte)'e' };
case GameCardPartition.Normal: return new[] { (byte)'N', (byte)'o', (byte)'r', (byte)'m', (byte)'a', (byte)'l' };
case GameCardPartition.Secure: return new[] { (byte)'S', (byte)'e', (byte)'c', (byte)'u', (byte)'r', (byte)'e' };
case GameCardPartition.Logo: return new[] { (byte)'L', (byte)'o', (byte)'g', (byte)'o' };
default: return ToValueString((int)value);
}
}
public ReadOnlySpan<byte> ToString(SaveDataSpaceId value)
{
switch (value)
{
case SaveDataSpaceId.System: return new[] { (byte)'S', (byte)'y', (byte)'s', (byte)'t', (byte)'e', (byte)'m' };
case SaveDataSpaceId.User: return new[] { (byte)'U', (byte)'s', (byte)'e', (byte)'r' };
case SaveDataSpaceId.SdSystem: return new[] { (byte)'S', (byte)'d', (byte)'S', (byte)'y', (byte)'s', (byte)'t', (byte)'e', (byte)'m' };
case SaveDataSpaceId.ProperSystem: return new[] { (byte)'P', (byte)'r', (byte)'o', (byte)'p', (byte)'e', (byte)'r', (byte)'S', (byte)'y', (byte)'s', (byte)'t', (byte)'e', (byte)'m' };
default: return ToValueString((int)value);
}
}
public ReadOnlySpan<byte> ToString(ContentType value)
{
switch (value)
{
case ContentType.Meta: return new[] { (byte)'M', (byte)'e', (byte)'t', (byte)'a' };
case ContentType.Control: return new[] { (byte)'C', (byte)'o', (byte)'n', (byte)'t', (byte)'r', (byte)'o', (byte)'l' };
case ContentType.Manual: return new[] { (byte)'M', (byte)'a', (byte)'n', (byte)'u', (byte)'a', (byte)'l' };
case ContentType.Logo: return new[] { (byte)'L', (byte)'o', (byte)'g', (byte)'o' };
case ContentType.Data: return new[] { (byte)'D', (byte)'a', (byte)'t', (byte)'a' };
default: return ToValueString((int)value);
}
}
public ReadOnlySpan<byte> ToString(BisPartitionId value)
{
switch (value)
{
case BisPartitionId.BootPartition1Root: return new[] { (byte)'B', (byte)'o', (byte)'o', (byte)'t', (byte)'P', (byte)'a', (byte)'r', (byte)'t', (byte)'i', (byte)'t', (byte)'i', (byte)'o', (byte)'n', (byte)'1', (byte)'R', (byte)'o', (byte)'o', (byte)'t' };
case BisPartitionId.BootPartition2Root: return new[] { (byte)'B', (byte)'o', (byte)'o', (byte)'t', (byte)'P', (byte)'a', (byte)'r', (byte)'t', (byte)'i', (byte)'t', (byte)'i', (byte)'o', (byte)'n', (byte)'2', (byte)'R', (byte)'o', (byte)'o', (byte)'t' };
case BisPartitionId.UserDataRoot: return new[] { (byte)'U', (byte)'s', (byte)'e', (byte)'r', (byte)'D', (byte)'a', (byte)'t', (byte)'a', (byte)'R', (byte)'o', (byte)'o', (byte)'t' };
case BisPartitionId.BootConfigAndPackage2Part1: return new[] { (byte)'B', (byte)'o', (byte)'o', (byte)'t', (byte)'C', (byte)'o', (byte)'n', (byte)'f', (byte)'i', (byte)'g', (byte)'A', (byte)'n', (byte)'d', (byte)'P', (byte)'a', (byte)'c', (byte)'k', (byte)'a', (byte)'g', (byte)'e', (byte)'2', (byte)'P', (byte)'a', (byte)'r', (byte)'t', (byte)'1' };
case BisPartitionId.BootConfigAndPackage2Part2: return new[] { (byte)'B', (byte)'o', (byte)'o', (byte)'t', (byte)'C', (byte)'o', (byte)'n', (byte)'f', (byte)'i', (byte)'g', (byte)'A', (byte)'n', (byte)'d', (byte)'P', (byte)'a', (byte)'c', (byte)'k', (byte)'a', (byte)'g', (byte)'e', (byte)'2', (byte)'P', (byte)'a', (byte)'r', (byte)'t', (byte)'2' };
case BisPartitionId.BootConfigAndPackage2Part3: return new[] { (byte)'B', (byte)'o', (byte)'o', (byte)'t', (byte)'C', (byte)'o', (byte)'n', (byte)'f', (byte)'i', (byte)'g', (byte)'A', (byte)'n', (byte)'d', (byte)'P', (byte)'a', (byte)'c', (byte)'k', (byte)'a', (byte)'g', (byte)'e', (byte)'2', (byte)'P', (byte)'a', (byte)'r', (byte)'t', (byte)'3' };
case BisPartitionId.BootConfigAndPackage2Part4: return new[] { (byte)'B', (byte)'o', (byte)'o', (byte)'t', (byte)'C', (byte)'o', (byte)'n', (byte)'f', (byte)'i', (byte)'g', (byte)'A', (byte)'n', (byte)'d', (byte)'P', (byte)'a', (byte)'c', (byte)'k', (byte)'a', (byte)'g', (byte)'e', (byte)'2', (byte)'P', (byte)'a', (byte)'r', (byte)'t', (byte)'4' };
case BisPartitionId.BootConfigAndPackage2Part5: return new[] { (byte)'B', (byte)'o', (byte)'o', (byte)'t', (byte)'C', (byte)'o', (byte)'n', (byte)'f', (byte)'i', (byte)'g', (byte)'A', (byte)'n', (byte)'d', (byte)'P', (byte)'a', (byte)'c', (byte)'k', (byte)'a', (byte)'g', (byte)'e', (byte)'2', (byte)'P', (byte)'a', (byte)'r', (byte)'t', (byte)'5' };
case BisPartitionId.BootConfigAndPackage2Part6: return new[] { (byte)'B', (byte)'o', (byte)'o', (byte)'t', (byte)'C', (byte)'o', (byte)'n', (byte)'f', (byte)'i', (byte)'g', (byte)'A', (byte)'n', (byte)'d', (byte)'P', (byte)'a', (byte)'c', (byte)'k', (byte)'a', (byte)'g', (byte)'e', (byte)'2', (byte)'P', (byte)'a', (byte)'r', (byte)'t', (byte)'6' };
case BisPartitionId.CalibrationBinary: return new[] { (byte)'C', (byte)'a', (byte)'l', (byte)'i', (byte)'b', (byte)'r', (byte)'a', (byte)'t', (byte)'i', (byte)'o', (byte)'n', (byte)'B', (byte)'i', (byte)'n', (byte)'a', (byte)'r', (byte)'y' };
case BisPartitionId.CalibrationFile: return new[] { (byte)'C', (byte)'a', (byte)'l', (byte)'i', (byte)'b', (byte)'r', (byte)'a', (byte)'t', (byte)'i', (byte)'o', (byte)'n', (byte)'F', (byte)'i', (byte)'l', (byte)'e' };
case BisPartitionId.SafeMode: return new[] { (byte)'S', (byte)'a', (byte)'f', (byte)'e', (byte)'M', (byte)'o', (byte)'d', (byte)'e' };
case BisPartitionId.User: return new[] { (byte)'U', (byte)'s', (byte)'e', (byte)'r' };
case BisPartitionId.System: return new[] { (byte)'S', (byte)'y', (byte)'s', (byte)'t', (byte)'e', (byte)'m' };
case BisPartitionId.SystemProperEncryption: return new[] { (byte)'S', (byte)'y', (byte)'s', (byte)'t', (byte)'e', (byte)'m', (byte)'P', (byte)'r', (byte)'o', (byte)'p', (byte)'e', (byte)'r', (byte)'E', (byte)'n', (byte)'c', (byte)'r', (byte)'y', (byte)'p', (byte)'t', (byte)'i', (byte)'o', (byte)'n' };
case BisPartitionId.SystemProperPartition: return new[] { (byte)'S', (byte)'y', (byte)'s', (byte)'t', (byte)'e', (byte)'m', (byte)'P', (byte)'r', (byte)'o', (byte)'p', (byte)'e', (byte)'r', (byte)'P', (byte)'a', (byte)'r', (byte)'t', (byte)'i', (byte)'t', (byte)'i', (byte)'o', (byte)'n' };
case (BisPartitionId)35: return new[] { (byte)'I', (byte)'n', (byte)'v', (byte)'a', (byte)'l', (byte)'i', (byte)'d' };
default: return ToValueString((int)value);
}
}
public ReadOnlySpan<byte> ToString(DirectoryEntryType value)
{
switch (value)
{
case DirectoryEntryType.Directory: return new[] { (byte)'D', (byte)'i', (byte)'r', (byte)'e', (byte)'c', (byte)'t', (byte)'o', (byte)'r', (byte)'y' };
case DirectoryEntryType.File: return new[] { (byte)'F', (byte)'i', (byte)'l', (byte)'e' };
default: return ToValueString((int)value);
}
}
public ReadOnlySpan<byte> ToString(MountHostOption value)
{
switch (value)
{
case MountHostOption.PseudoCaseSensitive: return new[] { (byte)'M', (byte)'o', (byte)'u', (byte)'n', (byte)'t', (byte)'H', (byte)'o', (byte)'s', (byte)'t', (byte)'O', (byte)'p', (byte)'t', (byte)'i', (byte)'o', (byte)'n', (byte)'F', (byte)'l', (byte)'a', (byte)'g', (byte)'_', (byte)'P', (byte)'s', (byte)'e', (byte)'u', (byte)'d', (byte)'o', (byte)'C', (byte)'a', (byte)'s', (byte)'e', (byte)'S', (byte)'e', (byte)'n', (byte)'s', (byte)'i', (byte)'t', (byte)'i', (byte)'v', (byte)'e' };
default: return ToValueString((int)value);
}
}
}
internal static class AccessLogImpl
{
internal static T DereferenceOutValue<T>(in T value, Result result) where T : unmanaged
{
return result.IsSuccess() ? value : default;
}
private static void GetProgramIndexForAccessLog(FileSystemClient fs, out int index, out int count)
{
throw new NotImplementedException();
@ -137,6 +292,53 @@ namespace LibHac.Fs.Impl
throw new NotImplementedException();
}
public static void OutputAccessLog(this FileSystemClientImpl fs, Result result, Tick start, Tick end,
FileHandle2 handle, U8Span message, [CallerMemberName] string functionName = "")
{
throw new NotImplementedException();
}
public static void OutputAccessLog(this FileSystemClientImpl fs, Result result, Tick start, Tick end,
DirectoryHandle2 handle, U8Span message, [CallerMemberName] string functionName = "")
{
throw new NotImplementedException();
}
public static void OutputAccessLog(this FileSystemClientImpl fs, Result result, Tick start, Tick end,
IdentifyAccessLogHandle handle, U8Span message, [CallerMemberName] string functionName = "")
{
throw new NotImplementedException();
}
public static void OutputAccessLog(this FileSystemClientImpl fs, Result result, Tick start, Tick end,
object handle, U8Span message, [CallerMemberName] string functionName = "")
{
throw new NotImplementedException();
}
public static void OutputAccessLogToOnlySdCard(this FileSystemClientImpl fs, U8Span message)
{
throw new NotImplementedException();
}
public static void OutputAccessLogUnlessResultSuccess(this FileSystemClientImpl fs, Result result, Tick start,
Tick end, FileHandle2 handle, U8Span message, [CallerMemberName] string functionName = "")
{
throw new NotImplementedException();
}
public static void OutputAccessLogUnlessResultSuccess(this FileSystemClientImpl fs, Result result, Tick start,
Tick end, DirectoryHandle2 handle, U8Span message, [CallerMemberName] string functionName = "")
{
throw new NotImplementedException();
}
public static void OutputAccessLogUnlessResultSuccess(this FileSystemClientImpl fs, Result result, Tick start,
Tick end, object handle, U8Span message, [CallerMemberName] string functionName = "")
{
throw new NotImplementedException();
}
internal static bool IsEnabledAccessLog(this FileSystemClientImpl fs, AccessLogTarget target)
{
ref AccessLogGlobals g = ref fs.Globals.AccessLog;
@ -180,5 +382,58 @@ namespace LibHac.Fs.Impl
{
return fs.IsEnabledAccessLog(AccessLogTarget.All);
}
public static bool IsEnabledHandleAccessLog(this FileSystemClientImpl _, FileHandle2 handle)
{
if (handle.File is null)
return true;
FileSystemAccessor fsAccessor = handle.File.GetParent();
return fsAccessor is not null && fsAccessor.IsEnabledAccessLog();
}
public static bool IsEnabledHandleAccessLog(this FileSystemClientImpl _, DirectoryHandle2 handle)
{
if (handle.Directory is null)
return true;
return handle.Directory.GetParent().IsEnabledAccessLog();
}
public static bool IsEnabledHandleAccessLog(this FileSystemClientImpl _, IdentifyAccessLogHandle handle)
{
return true;
}
public static bool IsEnabledHandleAccessLog(this FileSystemClientImpl _, object handle)
{
if (handle is null)
return true;
// We should never receive non-null here.
// ReSharper disable once ConditionIsAlwaysTrueOrFalse
Assert.True(handle is null);
return false;
}
public static bool IsEnabledFileSystemAccessorAccessLog(this FileSystemClientImpl fs, U8Span mountName)
{
throw new NotImplementedException();
}
public static void EnableFileSystemAccessorAccessLog(this FileSystemClientImpl fs, U8Span mountName)
{
throw new NotImplementedException();
}
public static void FlushAccessLog(this FileSystemClientImpl fs)
{
throw new NotImplementedException();
}
public static void FlushAccessLogOnSdCard(this FileSystemClientImpl fs)
{
throw new NotImplementedException();
}
}
}

View File

@ -19,4 +19,14 @@ namespace LibHac.Fs
Directory.Parent.FsClient.CloseDirectory(this);
}
}
public readonly struct DirectoryHandle2
{
internal readonly Impl.DirectoryAccessor Directory;
internal DirectoryHandle2(Impl.DirectoryAccessor directory)
{
Directory = directory;
}
}
}

View File

@ -19,4 +19,14 @@ namespace LibHac.Fs
File.Parent.FsClient.CloseFile(this);
}
}
public readonly struct FileHandle2
{
internal readonly Impl.FileAccessor File;
internal FileHandle2(Impl.FileAccessor file)
{
File = file;
}
}
}

View File

@ -27,7 +27,10 @@ namespace LibHac.Fs
public HorizonClient Hos;
public object InitMutex;
public AccessLogGlobals AccessLog;
public UserMountTableGlobals UserMountTable;
public FileSystemProxyServiceObjectGlobals FileSystemProxyServiceObject;
public FsContextHandlerGlobals FsContextHandler;
public ResultHandlingUtilityGlobals ResultHandlingUtility;
}
public partial class FileSystemClient
@ -51,12 +54,19 @@ namespace LibHac.Fs
{
Time = horizonClient.Time;
Globals.Hos = horizonClient;
Globals.InitMutex = new object();
InitializeGlobals(horizonClient);
Assert.NotNull(Time);
}
private void InitializeGlobals(HorizonClient horizonClient)
{
Globals.Hos = horizonClient;
Globals.InitMutex = new object();
Globals.UserMountTable.Initialize(this);
Globals.FsContextHandler.Initialize(this);
}
public bool HasFileSystemServer()
{
return Hos != null;

130
src/LibHac/Fs/FsContext.cs Normal file
View File

@ -0,0 +1,130 @@
using System;
namespace LibHac.Fs
{
public enum AbortSpecifier
{
Default,
Abort,
Return
}
internal struct FsContextHandlerGlobals
{
public bool IsAutoAbortEnabled;
public FsContext DefaultContext;
public FsContext AllReturnContext;
public void Initialize(FileSystemClient fsClient)
{
DefaultContext = new FsContext(fsClient, FsContextHandler.DefaultResultHandler);
AllReturnContext = new FsContext(fsClient, FsContextHandler.AllReturnResultHandler);
}
}
public delegate AbortSpecifier ResultHandler(FileSystemClient fs, Result result);
public static class FsContextHandler
{
[ThreadStatic]
private static FsContext _currentThreadContext;
internal static AbortSpecifier DefaultResultHandler(FileSystemClient fs, Result result)
{
if (fs.Globals.FsContextHandler.IsAutoAbortEnabled)
return AbortSpecifier.Default;
else
return AbortSpecifier.Return;
}
internal static AbortSpecifier AllReturnResultHandler(FileSystemClient fs, Result result)
{
return AbortSpecifier.Return;
}
public static void SetEnabledAutoAbort(this FileSystemClient fs, bool isEnabled)
{
fs.Globals.FsContextHandler.IsAutoAbortEnabled = isEnabled;
}
public static void SetDefaultFsContextResultHandler(this FileSystemClient fs,
ResultHandler resultHandler)
{
fs.Globals.FsContextHandler.DefaultContext.SetHandler(resultHandler ?? DefaultResultHandler);
}
public static ref FsContext GetCurrentThreadFsContext(this FileSystemClient fs)
{
ref FsContext context = ref _currentThreadContext;
if (context.IsValid)
{
return ref context;
}
return ref fs.Globals.FsContextHandler.DefaultContext;
}
public static void SetCurrentThreadFsContext(this FileSystemClient fs, FsContext context)
{
_currentThreadContext = context;
}
public static bool IsResolubleAccessFailureResult(Result result)
{
return ResultFs.GameCardAccessFailed.Includes(result);
}
public static bool IsAutoAbortPolicyCustomized(this FileSystemClient fs)
{
ref FsContextHandlerGlobals g = ref fs.Globals.FsContextHandler;
return !g.IsAutoAbortEnabled || GetCurrentThreadFsContext(fs).GetHandler() != DefaultResultHandler;
}
}
public struct FsContext
{
private readonly FileSystemClient _fsClient;
private ResultHandler _resultHandler;
internal bool IsValid => _fsClient is not null;
public FsContext(FileSystemClient fsClient, ResultHandler resultHandler)
{
_fsClient = fsClient;
_resultHandler = resultHandler;
}
public AbortSpecifier HandleResult(Result result)
{
return _resultHandler(_fsClient, result);
}
public ResultHandler GetHandler()
{
return _resultHandler;
}
public void SetHandler(ResultHandler handler)
{
_resultHandler = handler;
}
}
public struct ScopedAutoAbortDisabler
{
private FileSystemClient _fsClient;
private FsContext _prevContext;
public ScopedAutoAbortDisabler(FileSystemClient fs)
{
_fsClient = fs;
_prevContext = fs.GetCurrentThreadFsContext();
fs.SetCurrentThreadFsContext(fs.Globals.FsContextHandler.AllReturnContext);
}
public void Dispose()
{
_fsClient.SetCurrentThreadFsContext(_prevContext);
}
}
}

View File

@ -177,6 +177,12 @@ namespace LibHac.Fs
Restore = 1 << 4
}
public enum CommitOptionFlag
{
None = 1,
SetRestoreFlag = 2
}
public enum SdmmcPort
{
Mmc = 0,

View File

@ -1,4 +1,5 @@
using System;
using LibHac.Common;
namespace LibHac.Fs.Fsa
{
@ -11,5 +12,36 @@ namespace LibHac.Fs.Fsa
{
Result GetSaveDataAttribute(out SaveDataAttribute attribute);
}
internal static class Registrar
{
public static Result Register(U8Span name, IFileSystem fileSystem)
{
throw new NotImplementedException();
}
public static Result Register(U8Span name, IFileSystem fileSystem, ICommonMountNameGenerator mountNameGenerator)
{
throw new NotImplementedException();
}
public static Result Register(U8Span name, IMultiCommitTarget multiCommitTarget, IFileSystem fileSystem,
ICommonMountNameGenerator mountNameGenerator, bool useDataCache, bool usePathCache)
{
throw new NotImplementedException();
}
public static Result Register(U8Span name, IMultiCommitTarget multiCommitTarget, IFileSystem fileSystem,
ICommonMountNameGenerator mountNameGenerator, ISaveDataAttributeGetter saveAttributeGetter,
bool useDataCache, bool usePathCache)
{
throw new NotImplementedException();
}
public static void Unregister(U8Span name)
{
}
}
}

View File

@ -0,0 +1,100 @@
using System;
using System.Runtime.CompilerServices;
using LibHac.Common;
using LibHac.Fs.Impl;
using LibHac.Os;
namespace LibHac.Fs.Fsa
{
[SkipLocalsInit]
public static class UserDirectory
{
private static DirectoryAccessor Get(DirectoryHandle2 handle)
{
return handle.Directory;
}
public static Result ReadDirectory(this FileSystemClient fs, out long entriesRead,
Span<DirectoryEntry> entryBuffer, DirectoryHandle2 handle)
{
Result rc;
if (fs.Impl.IsEnabledAccessLog() && fs.Impl.IsEnabledHandleAccessLog(handle))
{
Tick start = fs.Hos.Os.GetSystemTick();
rc = Get(handle).Read(out entriesRead, entryBuffer);
Tick end = fs.Hos.Os.GetSystemTick();
Span<byte> buffer = stackalloc byte[0x50];
var sb = new U8StringBuilder(buffer, true);
sb.Append(LogEntryBufferCount).AppendFormat(entryBuffer.Length)
.Append(LogEntryCount).AppendFormat(entriesRead);
fs.Impl.OutputAccessLog(rc, start, end, handle, new U8Span(sb.Buffer));
}
else
{
rc = Get(handle).Read(out entriesRead, entryBuffer);
}
fs.Impl.AbortIfNeeded(rc);
return rc;
}
public static Result GetDirectoryEntryCount(this FileSystemClient fs, out long count, DirectoryHandle2 handle)
{
Result rc;
if (fs.Impl.IsEnabledAccessLog() && fs.Impl.IsEnabledHandleAccessLog(handle))
{
Tick start = fs.Hos.Os.GetSystemTick();
rc = Get(handle).GetEntryCount(out count);
Tick end = fs.Hos.Os.GetSystemTick();
Span<byte> buffer = stackalloc byte[0x50];
var sb = new U8StringBuilder(buffer, true);
sb.Append(LogEntryCount).AppendFormat(count);
fs.Impl.OutputAccessLog(rc, start, end, handle, new U8Span(sb.Buffer));
}
else
{
rc = Get(handle).GetEntryCount(out count);
}
fs.Impl.AbortIfNeeded(rc);
return rc;
}
public static void CloseDirectory(this FileSystemClient fs, DirectoryHandle2 handle)
{
if (fs.Impl.IsEnabledAccessLog() && fs.Impl.IsEnabledHandleAccessLog(handle))
{
Tick start = fs.Hos.Os.GetSystemTick();
Get(handle).Dispose();
Tick end = fs.Hos.Os.GetSystemTick();
fs.Impl.OutputAccessLog(Result.Success, start, end, handle, U8Span.Empty);
}
else
{
Get(handle).Dispose();
}
}
private static ReadOnlySpan<byte> LogEntryBufferCount => // ", entry_buffer_count: "
new[]
{
(byte)',', (byte)' ', (byte)'e', (byte)'n', (byte)'t', (byte)'r', (byte)'y', (byte)'_',
(byte)'b', (byte)'u', (byte)'f', (byte)'f', (byte)'e', (byte)'r', (byte)'_', (byte)'c',
(byte)'o', (byte)'u', (byte)'n', (byte)'t', (byte)':', (byte)' '
};
private static ReadOnlySpan<byte> LogEntryCount => // ", entry_count: "
new[]
{
(byte)',', (byte)' ', (byte)'e', (byte)'n', (byte)'t', (byte)'r', (byte)'y', (byte)'_',
(byte)'c', (byte)'o', (byte)'u', (byte)'n', (byte)'t', (byte)':', (byte)' '
};
}
}

View File

@ -0,0 +1,254 @@
using System;
using System.Runtime.CompilerServices;
using LibHac.Common;
using LibHac.Fs.Impl;
using LibHac.Os;
namespace LibHac.Fs.Fsa
{
[SkipLocalsInit]
public static class UserFile
{
private static FileAccessor Get(FileHandle2 handle)
{
return handle.File;
}
private static Result ReadFileImpl(FileSystemClient fs, out long bytesRead, FileHandle2 handle, long offset,
Span<byte> destination, in ReadOption option)
{
return Get(handle).Read(out bytesRead, offset, destination, in option);
}
public static Result ReadFile(this FileSystemClient fs, FileHandle2 handle, long offset, Span<byte> destination,
in ReadOption option)
{
Result rc = ReadFileImpl(fs, out long bytesRead, handle, offset, destination, in option);
fs.Impl.AbortIfNeeded(rc);
if (rc.IsFailure()) return rc;
if (bytesRead == destination.Length)
return Result.Success;
rc = ResultFs.OutOfRange.Log();
fs.Impl.AbortIfNeeded(rc);
return rc;
}
public static Result ReadFile(this FileSystemClient fs, FileHandle2 handle, long offset, Span<byte> destination)
{
Result rc = ReadFileImpl(fs, out long bytesRead, handle, offset, destination, ReadOption.None);
fs.Impl.AbortIfNeeded(rc);
if (rc.IsFailure()) return rc;
if (bytesRead == destination.Length)
return Result.Success;
rc = ResultFs.OutOfRange.Log();
fs.Impl.AbortIfNeeded(rc);
return rc;
}
public static Result ReadFile(this FileSystemClient fs, out long bytesRead, FileHandle2 handle, long offset,
Span<byte> destination, in ReadOption option)
{
Result rc = ReadFileImpl(fs, out bytesRead, handle, offset, destination, in option);
fs.Impl.AbortIfNeeded(rc);
return rc;
}
public static Result ReadFile(this FileSystemClient fs, out long bytesRead, FileHandle2 handle, long offset,
Span<byte> destination)
{
Result rc = ReadFileImpl(fs, out bytesRead, handle, offset, destination, ReadOption.None);
fs.Impl.AbortIfNeeded(rc);
return rc;
}
public static Result WriteFile(this FileSystemClient fs, FileHandle2 handle, long offset,
ReadOnlySpan<byte> source, in WriteOption option)
{
Result rc;
if (fs.Impl.IsEnabledAccessLog() && fs.Impl.IsEnabledHandleAccessLog(handle))
{
Tick start = fs.Hos.Os.GetSystemTick();
rc = Get(handle).Write(offset, source, in option);
Tick end = fs.Hos.Os.GetSystemTick();
Span<byte> buffer = stackalloc byte[0x60];
var sb = new U8StringBuilder(buffer, true);
sb.Append(LogOffset).AppendFormat(offset).Append(LogSize).AppendFormat(source.Length);
if (option.HasFlushFlag())
sb.Append(LogWriteOptionFlush);
fs.Impl.OutputAccessLog(rc, start, end, handle, new U8Span(sb.Buffer));
sb.Dispose();
}
else
{
rc = Get(handle).Write(offset, source, in option);
}
fs.Impl.AbortIfNeeded(rc);
return rc;
}
public static Result FlushFile(this FileSystemClient fs, FileHandle2 handle)
{
Result rc;
if (fs.Impl.IsEnabledAccessLog() && fs.Impl.IsEnabledHandleAccessLog(handle))
{
Tick start = fs.Hos.Os.GetSystemTick();
rc = Get(handle).Flush();
Tick end = fs.Hos.Os.GetSystemTick();
fs.Impl.OutputAccessLog(rc, start, end, handle, U8Span.Empty);
}
else
{
rc = Get(handle).Flush();
}
fs.Impl.AbortIfNeeded(rc);
return rc;
}
public static Result SetFileSize(this FileSystemClient fs, FileHandle2 handle, long size)
{
Result rc;
if (fs.Impl.IsEnabledAccessLog() && fs.Impl.IsEnabledHandleAccessLog(handle))
{
Tick start = fs.Hos.Os.GetSystemTick();
rc = Get(handle).SetSize(size);
Tick end = fs.Hos.Os.GetSystemTick();
Span<byte> buffer = stackalloc byte[0x20];
var sb = new U8StringBuilder(buffer, true);
sb.Append(LogSize).AppendFormat(size);
fs.Impl.OutputAccessLog(rc, start, end, handle, new U8Span(sb.Buffer));
}
else
{
rc = Get(handle).SetSize(size);
}
fs.Impl.AbortIfNeeded(rc);
return rc;
}
public static Result GetFileSize(this FileSystemClient fs, out long size, FileHandle2 handle)
{
Result rc;
if (fs.Impl.IsEnabledAccessLog() && fs.Impl.IsEnabledHandleAccessLog(handle))
{
Tick start = fs.Hos.Os.GetSystemTick();
rc = Get(handle).GetSize(out size);
Tick end = fs.Hos.Os.GetSystemTick();
Span<byte> buffer = stackalloc byte[0x20];
var sb = new U8StringBuilder(buffer, true);
sb.Append(LogSize).AppendFormat(AccessLogImpl.DereferenceOutValue(in size, rc));
fs.Impl.OutputAccessLog(rc, start, end, handle, new U8Span(sb.Buffer));
}
else
{
rc = Get(handle).GetSize(out size);
}
fs.Impl.AbortIfNeeded(rc);
return rc;
}
public static OpenMode GetFileOpenMode(this FileSystemClient fs, FileHandle2 handle)
{
OpenMode mode = Get(handle).GetOpenMode();
if (fs.Impl.IsEnabledAccessLog() && fs.Impl.IsEnabledHandleAccessLog(handle))
{
Tick start = fs.Hos.Os.GetSystemTick();
Tick end = fs.Hos.Os.GetSystemTick();
Span<byte> buffer = stackalloc byte[0x20];
var sb = new U8StringBuilder(buffer, true);
sb.Append(LogOpenMode).AppendFormat((int)mode, 'X');
fs.Impl.OutputAccessLog(Result.Success, start, end, handle, new U8Span(sb.Buffer));
}
return mode;
}
public static void CloseFile(this FileSystemClient fs, FileHandle2 handle)
{
if (fs.Impl.IsEnabledAccessLog() && fs.Impl.IsEnabledHandleAccessLog(handle))
{
Tick start = fs.Hos.Os.GetSystemTick();
Get(handle).Dispose();
Tick end = fs.Hos.Os.GetSystemTick();
fs.Impl.OutputAccessLog(Result.Success, start, end, handle, U8Span.Empty);
}
else
{
Get(handle).Dispose();
}
}
public static Result QueryRange(this FileSystemClient fs, out QueryRangeInfo rangeInfo, FileHandle2 handle,
long offset, long size)
{
Unsafe.SkipInit(out rangeInfo);
Result rc = Get(handle).OperateRange(SpanHelpers.AsByteSpan(ref rangeInfo), OperationId.QueryRange, offset,
size, ReadOnlySpan<byte>.Empty);
fs.Impl.IsAbortNeeded(rc);
return rc;
}
public static Result InvalidateCache(this FileSystemClient fs, FileHandle2 handle, long offset, long size)
{
Result rc = Get(handle).OperateRange(Span<byte>.Empty, OperationId.InvalidateCache, offset, size,
ReadOnlySpan<byte>.Empty);
fs.Impl.IsAbortNeeded(rc);
return rc;
}
private static ReadOnlySpan<byte> LogOffset => // ", offset: "
new[]
{
(byte)',', (byte)' ', (byte)'o', (byte)'f', (byte)'f', (byte)'s', (byte)'e', (byte)'t',
(byte)':', (byte)' '
};
private static ReadOnlySpan<byte> LogSize => // ", size: "
new[]
{
(byte)',', (byte)' ', (byte)'s', (byte)'i', (byte)'z', (byte)'e', (byte)':', (byte)' '
};
private static ReadOnlySpan<byte> LogWriteOptionFlush => // ", write_option: Flush"
new[]
{
(byte)',', (byte)' ', (byte)'w', (byte)'r', (byte)'i', (byte)'t', (byte)'e', (byte)'_',
(byte)'o', (byte)'p', (byte)'t', (byte)'i', (byte)'o', (byte)'n', (byte)':', (byte)' ',
(byte)'F', (byte)'l', (byte)'u', (byte)'s', (byte)'h'
};
private static ReadOnlySpan<byte> LogOpenMode => // ", open_mode: 0x"
new[]
{
(byte)',', (byte)' ', (byte)'o', (byte)'p', (byte)'e', (byte)'n', (byte)'_', (byte)'m',
(byte)'o', (byte)'d', (byte)'e', (byte)':', (byte)' ', (byte)'0', (byte)'x'
};
}
}

View File

@ -0,0 +1,101 @@
using System;
using System.Runtime.CompilerServices;
using LibHac.Common;
namespace LibHac.Fs.Fsa
{
public static class UserFileSystem
{
public static Result CreateFile(this FileSystemClient fs, U8Span path, long size)
{
throw new NotImplementedException();
}
public static Result DeleteFile(this FileSystemClient fs, U8Span path)
{
throw new NotImplementedException();
}
public static Result CreateDirectory(this FileSystemClient fs, U8Span path)
{
throw new NotImplementedException();
}
public static Result DeleteDirectory(this FileSystemClient fs, U8Span path)
{
throw new NotImplementedException();
}
public static Result DeleteDirectoryRecursively(this FileSystemClient fs, U8Span path)
{
throw new NotImplementedException();
}
public static Result CleanDirectoryRecursively(this FileSystemClient fs, U8Span path)
{
throw new NotImplementedException();
}
public static Result RenameFile(this FileSystemClient fs, U8Span oldPath, U8Span newPath)
{
throw new NotImplementedException();
}
public static Result RenameDirectory(this FileSystemClient fs, U8Span oldPath, U8Span newPath)
{
throw new NotImplementedException();
}
public static Result GetEntryType(this FileSystemClient fs, out DirectoryEntryType type, U8Span path)
{
throw new NotImplementedException();
}
public static Result GetFreeSpaceSize(this FileSystemClient fs, out long freeSpace, U8Span path)
{
throw new NotImplementedException();
}
public static Result OpenFile(this FileSystemClient fs, out FileHandle2 handle, U8Span path, OpenMode mode)
{
throw new NotImplementedException();
}
public static Result OpenFile(this FileSystemClient fs, out FileHandle2 handle, IFile file, OpenMode mode)
{
throw new NotImplementedException();
}
public static Result OpenDirectory(this FileSystemClient fs, out DirectoryHandle2 handle, U8Span path,
OpenDirectoryMode mode)
{
throw new NotImplementedException();
}
private static Result CommitImpl(FileSystemClient fs, U8Span mountName,
[CallerMemberName] string functionName = "")
{
throw new NotImplementedException();
}
public static Result Commit(this FileSystemClient fs, ReadOnlySpan<U8String> mountNames)
{
throw new NotImplementedException();
}
public static Result Commit(this FileSystemClient fs, U8Span mountName, CommitOption option)
{
throw new NotImplementedException();
}
public static Result Commit(this FileSystemClient fs, U8Span mountName)
{
throw new NotImplementedException();
}
public static Result CommitSaveData(this FileSystemClient fs, U8Span mountName)
{
throw new NotImplementedException();
}
}
}

View File

@ -0,0 +1,34 @@
using System;
using LibHac.Common;
using LibHac.Fs.Impl;
namespace LibHac.Fs.Fsa
{
internal struct UserMountTableGlobals
{
public MountTable MountTable;
public void Initialize(FileSystemClient fsClient)
{
MountTable = new MountTable(fsClient);
}
}
internal static class UserMountTable
{
public static Result Register(this FileSystemClientImpl fs, FileSystemAccessor fileSystem)
{
throw new NotImplementedException();
}
public static Result Find(this FileSystemClientImpl fs, out FileSystemAccessor fileSystem, U8Span name)
{
throw new NotImplementedException();
}
public static void Unregister(this FileSystemClientImpl fs, U8Span name)
{
throw new NotImplementedException();
}
}
}

View File

@ -991,6 +991,8 @@ namespace LibHac.Fs
public static Result.Base InvalidOpenMode => new Result.Base(ModuleFs, 6072);
/// <summary>Error code: 2002-6074; Inner value: 0x2f7402</summary>
public static Result.Base InvalidDirectoryOpenMode => new Result.Base(ModuleFs, 6074);
/// <summary>Error code: 2002-6075; Inner value: 0x2f7602</summary>
public static Result.Base InvalidCommitOption => new Result.Base(ModuleFs, 6075);
/// <summary>Error code: 2002-6080; Range: 6080-6099; Inner value: 0x2f8002</summary>
public static Result.Base InvalidEnumValue { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new Result.Base(ModuleFs, 6080, 6099); }

View File

@ -0,0 +1,68 @@
using System.Runtime.CompilerServices;
using LibHac.Diag;
namespace LibHac.Fs
{
internal struct ResultHandlingUtilityGlobals
{
public bool IsResultHandledByApplication;
}
public static class ResultHandlingUtility
{
public static void SetResultHandledByApplication(this FileSystemClient fs, bool isHandledByApplication)
{
fs.Globals.ResultHandlingUtility.IsResultHandledByApplication = isHandledByApplication;
}
public static bool IsAbortNeeded(this FileSystemClientImpl fs, Result result)
{
if (result.IsSuccess())
return false;
switch (fs.Fs.GetCurrentThreadFsContext().HandleResult(result))
{
case AbortSpecifier.Default:
if (fs.Globals.ResultHandlingUtility.IsResultHandledByApplication)
{
return ResultFs.HandledByAllProcess.Includes(result);
}
else
{
return !(ResultFs.HandledByAllProcess.Includes(result) ||
ResultFs.HandledBySystemProcess.Includes(result));
}
case AbortSpecifier.Abort:
return true;
case AbortSpecifier.Return:
return false;
default:
Abort.UnexpectedDefault();
return default;
}
}
public static void LogErrorMessage(this FileSystemClientImpl fs, Result result,
[CallerMemberName] string functionName = "")
{
// Todo
}
public static void LogResultErrorMessage(this FileSystemClientImpl fs, Result result)
{
// Todo
}
internal static void AbortIfNeeded(this FileSystemClientImpl fs, Result result,
[CallerMemberName] string functionName = "")
{
if (!IsAbortNeeded(fs, result))
return;
fs.LogErrorMessage(result, functionName);
if (!result.IsSuccess())
Abort.DoAbort(result);
}
}
}

View File

@ -223,13 +223,6 @@ namespace LibHac.Fs
public ReadOnlySpan<byte> HashRo => SpanHelpers.CreateReadOnlySpan(in _hashStart, HashLength);
}
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct OptionalHashSalt
{
public bool IsSet;
public HashSalt HashSalt;
}
[StructLayout(LayoutKind.Explicit, Size = 0x10)]
public struct SaveDataMetaInfo
{
@ -276,6 +269,11 @@ namespace LibHac.Fs
[FieldOffset(0x68)] public long CommitId;
}
public struct CommitOption
{
public CommitOptionFlag Flags;
}
internal static class SaveDataTypesValidity
{
public static bool IsValid(in SaveDataAttribute attribute)

View File

@ -0,0 +1,45 @@
namespace LibHac.Fs
{
public enum Priority
{
Realtime = 0,
Normal = 1,
Low = 2
}
public enum PriorityRaw
{
Realtime = 0,
Normal = 1,
Low = 2,
Background = 3
}
}
namespace LibHac.Fs.Shim
{
public static class PriorityShim
{
public static void SetPriorityOnCurrentThread(this FileSystemClient fs, Priority priority)
{
// Todo
}
public static Priority GetPriorityOnCurrentThread(this FileSystemClient fs)
{
// Todo
return Priority.Normal;
}
public static void SetPriorityRawOnCurrentThread(this FileSystemClient fs, PriorityRaw priority)
{
// Todo
}
public static PriorityRaw GetPriorityRawOnCurrentThread(this FileSystemClient fs)
{
// Todo
return PriorityRaw.Normal;
}
}
}

View File

@ -4,7 +4,6 @@ using System.Text;
using LibHac;
using LibHac.Common.Keys;
using LibHac.Fs;
using LibHac.Fs.Impl;
using LibHac.Util;
namespace hactoolnet