Update FS shims for new Horizon and access log code

Makes all current FS shims more accurate, including using rewritten access log and system tick handling code.
This commit is contained in:
Alex Barney 2021-03-01 01:22:59 -07:00
parent 684fcd460f
commit 6641109d94
58 changed files with 2720 additions and 2002 deletions

View file

@ -175,6 +175,7 @@ Module,DescriptionStart,DescriptionEnd,Flags,Namespace,Name,Summary
2,3005,,,,OutOfRange,
2,3100,,,,SystemPartitionNotReady,
2,3101,,,,StorageDeviceNotReady,
2,3200,3499,,,AllocationMemoryFailed,
2,3211,,,,AllocationMemoryFailedInFileSystemAccessorA,

1 Module DescriptionStart DescriptionEnd Flags Namespace Name Summary
175 2 3227 3226 AllocationMemoryFailedInGameCardC AllocationMemoryFailedInGameCardB
176 2 3228 3227 AllocationMemoryFailedInGameCardD AllocationMemoryFailedInGameCardC
177 2 3232 3228 AllocationMemoryFailedInImageDirectoryA AllocationMemoryFailedInGameCardD
178 2 3232 AllocationMemoryFailedInImageDirectoryA
179 2 3244 AllocationMemoryFailedInSdCardA
180 2 3245 AllocationMemoryFailedInSdCardB
181 2 3246 AllocationMemoryFailedInSystemSaveDataA

View file

@ -16,6 +16,17 @@ namespace LibHac.Diag
throw new LibHacException($"Abort: {message}");
}
[DoesNotReturn]
public static void DoAbort(string message = null)
{
if (string.IsNullOrWhiteSpace(message))
{
throw new LibHacException("Abort.");
}
throw new LibHacException($"Abort: {message}");
}
public static void DoAbortUnless([DoesNotReturnIf(false)] bool condition, string message = null)
{
if (condition)

View file

@ -18,6 +18,11 @@ namespace LibHac.Fs
public bool IsAccessLogInitialized;
public SdkMutexType MutexForAccessLogInitialization;
public void Initialize(FileSystemClient _)
{
MutexForAccessLogInitialization.Initialize();
}
}
[StructLayout(LayoutKind.Sequential, Size = 0x20)]
@ -40,35 +45,18 @@ namespace LibHac.Fs
public static class AccessLog
{
private static bool HasFileSystemServer(FileSystemClient fs)
{
return fs.Hos is not null;
}
public static Result GetGlobalAccessLogMode(this FileSystemClient fs, out GlobalAccessLogMode mode)
{
if (HasFileSystemServer(fs))
{
using ReferenceCountedDisposable<IFileSystemProxy> fsProxy = fs.Impl.GetFileSystemProxyServiceObject();
using ReferenceCountedDisposable<IFileSystemProxy> fsProxy = fs.Impl.GetFileSystemProxyServiceObject();
return fsProxy.Target.GetGlobalAccessLogMode(out mode);
}
mode = fs.Globals.AccessLog.GlobalAccessLogMode;
return Result.Success;
return fsProxy.Target.GetGlobalAccessLogMode(out mode);
}
public static Result SetGlobalAccessLogMode(this FileSystemClient fs, GlobalAccessLogMode mode)
{
if (HasFileSystemServer(fs))
{
using ReferenceCountedDisposable<IFileSystemProxy> fsProxy = fs.Impl.GetFileSystemProxyServiceObject();
using ReferenceCountedDisposable<IFileSystemProxy> fsProxy = fs.Impl.GetFileSystemProxyServiceObject();
return fsProxy.Target.SetGlobalAccessLogMode(mode);
}
fs.Globals.AccessLog.GlobalAccessLogMode = mode;
return Result.Success;
return fsProxy.Target.SetGlobalAccessLogMode(mode);
}
public static void SetLocalAccessLog(this FileSystemClient fs, bool enabled)
@ -104,6 +92,27 @@ namespace LibHac.Fs
fs.Globals.AccessLog.LocalAccessLogTarget &= ~AccessLogTarget.Application;
}
}
public static Result RunOperationWithAccessLog(this FileSystemClient fs, AccessLogTarget logTarget,
Func<Result> operation, Func<string> textGenerator, [CallerMemberName] string caller = "")
{
Result rc;
if (fs.Impl.IsEnabledAccessLog(logTarget))
{
Tick start = fs.Hos.Os.GetSystemTick();
rc = operation();
Tick end = fs.Hos.Os.GetSystemTick();
fs.Impl.OutputAccessLog(rc, start, end, null, textGenerator().ToU8Span(), caller);
}
else
{
rc = operation();
}
return rc;
}
}
}
@ -257,10 +266,10 @@ namespace LibHac.Fs.Impl
public ReadOnlySpan<byte> ToString(MountHostOption value)
{
switch (value)
switch (value.Flags)
{
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);
case MountHostOptionFlag.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.Flags);
}
}
}
@ -438,16 +447,242 @@ namespace LibHac.Fs.Impl
public static ReadOnlySpan<byte> ConvertFromBoolToAccessLogBooleanValue(bool value)
{
return value ? LogTrue : LogFalse;
return value ? AccessLogStrings.LogTrue : AccessLogStrings.LogFalse;
}
}
private static ReadOnlySpan<byte> LogTrue => // "true"
internal static class AccessLogStrings
{
public static byte LogQuote => (byte)'"';
public static ReadOnlySpan<byte> LogTrue => // "true"
new[] { (byte)'t', (byte)'r', (byte)'u', (byte)'e' };
private static ReadOnlySpan<byte> LogFalse => // "false"
public static ReadOnlySpan<byte> LogFalse => // "false"
new[] { (byte)'f', (byte)'a', (byte)'l', (byte)'s', (byte)'e' };
public static ReadOnlySpan<byte> LogEntryBufferCount => // ", entry_buffer_count: "
new[]
{
(byte)'f', (byte)'a', (byte)'l', (byte)'s', (byte)'e'
(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)' '
};
public 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)' '
};
public static ReadOnlySpan<byte> LogOffset => // ", offset: "
new[]
{
(byte)',', (byte)' ', (byte)'o', (byte)'f', (byte)'f', (byte)'s', (byte)'e', (byte)'t',
(byte)':', (byte)' '
};
public static ReadOnlySpan<byte> LogSize => // ", size: "
new[]
{
(byte)',', (byte)' ', (byte)'s', (byte)'i', (byte)'z', (byte)'e', (byte)':', (byte)' '
};
public 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'
};
public 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'
};
public static ReadOnlySpan<byte> LogPath => // ", path: ""
new[]
{
(byte)',', (byte)' ', (byte)'p', (byte)'a', (byte)'t', (byte)'h', (byte)':', (byte)' ',
(byte)'"'
};
public static ReadOnlySpan<byte> LogNewPath => // "", new_path: ""
new[]
{
(byte)'"', (byte)',', (byte)' ', (byte)'n', (byte)'e', (byte)'w', (byte)'_', (byte)'p',
(byte)'a', (byte)'t', (byte)'h', (byte)':', (byte)' ', (byte)'"'
};
public static ReadOnlySpan<byte> LogEntryType => // "", entry_type: "
new[]
{
(byte)'"', (byte)',', (byte)' ', (byte)'e', (byte)'n', (byte)'t', (byte)'r', (byte)'y',
(byte)'_', (byte)'t', (byte)'y', (byte)'p', (byte)'e', (byte)':', (byte)' '
};
public static ReadOnlySpan<byte> LogName => // ", name: ""
new[]
{
(byte)',', (byte)' ', (byte)'n', (byte)'a', (byte)'m', (byte)'e', (byte)':', (byte)' ',
(byte)'"'
};
public static ReadOnlySpan<byte> LogCommitOption => // "", commit_option: 0x"
new[]
{
(byte)'"', (byte)',', (byte)' ', (byte)'c', (byte)'o', (byte)'m', (byte)'m', (byte)'i',
(byte)'t', (byte)'_', (byte)'o', (byte)'p', (byte)'t', (byte)'i', (byte)'o', (byte)'n',
(byte)':', (byte)' ', (byte)'0', (byte)'x'
};
public static ReadOnlySpan<byte> LogIsMounted => // "", is_mounted: ""
new[]
{
(byte)'"', (byte)',', (byte)' ', (byte)'i', (byte)'s', (byte)'_', (byte)'m', (byte)'o',
(byte)'u', (byte)'n', (byte)'t', (byte)'e', (byte)'d', (byte)':', (byte)' ', (byte)'"'
};
public static ReadOnlySpan<byte> LogApplicationId => // ", applicationid: 0x"
new[]
{
(byte)',', (byte)' ', (byte)'a', (byte)'p', (byte)'p', (byte)'l', (byte)'i', (byte)'c',
(byte)'a', (byte)'t', (byte)'i', (byte)'o', (byte)'n', (byte)'i', (byte)'d', (byte)':',
(byte)' ', (byte)'0', (byte)'x'
};
public static ReadOnlySpan<byte> LogProgramId => // ", programid: 0x"
new[]
{
(byte)',', (byte)' ', (byte)'p', (byte)'r', (byte)'o', (byte)'g', (byte)'r', (byte)'a',
(byte)'m', (byte)'i', (byte)'d', (byte)':', (byte)' ', (byte)'0', (byte)'x'
};
public static ReadOnlySpan<byte> LogDataId => // ", dataid: 0x"
new[]
{
(byte)',', (byte)' ', (byte)'d', (byte)'a', (byte)'t', (byte)'a', (byte)'i', (byte)'d',
(byte)':', (byte)' ', (byte)'0', (byte)'x'
};
public static ReadOnlySpan<byte> LogBisPartitionId => // ", bispartitionid: "
new[]
{
(byte)',', (byte)' ', (byte)'b', (byte)'i', (byte)'s', (byte)'p', (byte)'a', (byte)'r',
(byte)'t', (byte)'i', (byte)'t', (byte)'i', (byte)'o', (byte)'n', (byte)'i', (byte)'d',
(byte)':', (byte)' '
};
public static ReadOnlySpan<byte> LogContentType => // ", content_type: "
new[]
{
(byte)',', (byte)' ', (byte)'c', (byte)'o', (byte)'n', (byte)'t', (byte)'e', (byte)'n',
(byte)'t', (byte)'_', (byte)'t', (byte)'y', (byte)'p', (byte)'e', (byte)':', (byte)' '
};
public static ReadOnlySpan<byte> LogContentStorageId => // ", contentstorageid: "
new[]
{
(byte)',', (byte)' ', (byte)'c', (byte)'o', (byte)'n', (byte)'t', (byte)'e', (byte)'n',
(byte)'t', (byte)'s', (byte)'t', (byte)'o', (byte)'r', (byte)'a', (byte)'g', (byte)'e',
(byte)'i', (byte)'d', (byte)':', (byte)' '
};
public static ReadOnlySpan<byte> LogGameCardHandle => // ", gamecard_handle: 0x"
new[]
{
(byte)',', (byte)' ', (byte)'g', (byte)'a', (byte)'m', (byte)'e', (byte)'c', (byte)'a',
(byte)'r', (byte)'d', (byte)'_', (byte)'h', (byte)'a', (byte)'n', (byte)'d', (byte)'l',
(byte)'e', (byte)':', (byte)' ', (byte)'0', (byte)'x'
};
public static ReadOnlySpan<byte> LogGameCardPartition => // ", gamecard_partition: "
new[]
{
(byte)',', (byte)' ', (byte)'g', (byte)'a', (byte)'m', (byte)'e', (byte)'c', (byte)'a',
(byte)'r', (byte)'d', (byte)'_', (byte)'p', (byte)'a', (byte)'r', (byte)'t', (byte)'i',
(byte)'t', (byte)'i', (byte)'o', (byte)'n', (byte)':', (byte)' '
};
public static ReadOnlySpan<byte> LogMountHostOption => // ", mount_host_option: "
new[]
{
(byte)',', (byte)' ', (byte)'m', (byte)'o', (byte)'u', (byte)'n', (byte)'t', (byte)'_',
(byte)'h', (byte)'o', (byte)'s', (byte)'t', (byte)'_', (byte)'o', (byte)'p', (byte)'t',
(byte)'i', (byte)'o', (byte)'n', (byte)':', (byte)' '
};
public static ReadOnlySpan<byte> LogRootPath => // ", root_path: ""
new[]
{
(byte)',', (byte)' ', (byte)'r', (byte)'o', (byte)'o', (byte)'t', (byte)'_', (byte)'p',
(byte)'a', (byte)'t', (byte)'h', (byte)':', (byte)' ', (byte)'"'
};
public static ReadOnlySpan<byte> LogUserId => // ", userid: 0x"
new[]
{
(byte)',', (byte)' ', (byte)'u', (byte)'s', (byte)'e', (byte)'r', (byte)'i', (byte)'d',
(byte)':', (byte)' ', (byte)'0', (byte)'x'
};
public static ReadOnlySpan<byte> LogIndex => // ", index: "
new[]
{
(byte)',', (byte)' ', (byte)'i', (byte)'n', (byte)'d', (byte)'e', (byte)'x', (byte)':',
(byte)' '
};
public static ReadOnlySpan<byte> LogSaveDataOwnerId => // ", save_data_owner_id: 0x"
new[]
{
(byte)',', (byte)' ', (byte)'s', (byte)'a', (byte)'v', (byte)'e', (byte)'_', (byte)'d',
(byte)'a', (byte)'t', (byte)'a', (byte)'_', (byte)'o', (byte)'w', (byte)'n', (byte)'e',
(byte)'r', (byte)'_', (byte)'i', (byte)'d', (byte)':', (byte)' ', (byte)'0', (byte)'x'
};
public static ReadOnlySpan<byte> LogSaveDataSize => // ", save_data_size: "
new[]
{
(byte)',', (byte)' ', (byte)'s', (byte)'a', (byte)'v', (byte)'e', (byte)'_', (byte)'d',
(byte)'a', (byte)'t', (byte)'a', (byte)'_', (byte)'s', (byte)'i', (byte)'z', (byte)'e',
(byte)':', (byte)' '
};
public static ReadOnlySpan<byte> LogSaveDataJournalSize => // ", save_data_journal_size: "
new[]
{
(byte)',', (byte)' ', (byte)'s', (byte)'a', (byte)'v', (byte)'e', (byte)'_', (byte)'d',
(byte)'a', (byte)'t', (byte)'a', (byte)'_', (byte)'j', (byte)'o', (byte)'u', (byte)'r',
(byte)'n', (byte)'a', (byte)'l', (byte)'_', (byte)'s', (byte)'i', (byte)'z', (byte)'e',
(byte)':', (byte)' '
};
public static ReadOnlySpan<byte> LogSaveDataFlags => // ", save_data_flags: 0x"
new[]
{
(byte)',', (byte)' ', (byte)'s', (byte)'a', (byte)'v', (byte)'e', (byte)'_', (byte)'d',
(byte)'a', (byte)'t', (byte)'a', (byte)'_', (byte)'f', (byte)'l', (byte)'a', (byte)'g',
(byte)'s', (byte)':', (byte)' ', (byte)'0', (byte)'x'
};
public static ReadOnlySpan<byte> LogSaveDataId => // ", savedataid: 0x"
new[]
{
(byte)',', (byte)' ', (byte)'s', (byte)'a', (byte)'v', (byte)'e', (byte)'d', (byte)'a',
(byte)'t', (byte)'a', (byte)'i', (byte)'d', (byte)':', (byte)' ', (byte)'0', (byte)'x'
};
public static ReadOnlySpan<byte> LogSaveDataSpaceId => // ", savedataspaceid: "
new[]
{
(byte)',', (byte)' ', (byte)'s', (byte)'a', (byte)'v', (byte)'e', (byte)'d', (byte)'a',
(byte)'t', (byte)'a', (byte)'s', (byte)'p', (byte)'a', (byte)'c', (byte)'e', (byte)'i',
(byte)'d', (byte)':', (byte)' '
};
}
}

View file

@ -1,44 +0,0 @@
using System;
using LibHac.Fs.Fsa;
namespace LibHac.Fs.Accessors
{
public class DirectoryAccessor : IDisposable
{
private IDirectory Directory { get; set; }
public FileSystemAccessor Parent { get; }
public DirectoryAccessor(IDirectory baseDirectory, FileSystemAccessor parent)
{
Directory = baseDirectory;
Parent = parent;
}
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(out entryCount);
}
public void Dispose()
{
if (Directory == null) return;
Parent?.NotifyCloseDirectory(this);
Directory = null;
}
private void CheckIfDisposed()
{
if (Directory == null) throw new ObjectDisposedException(null, "Cannot access closed directory.");
}
}
}

View file

@ -1,113 +0,0 @@
using System;
using LibHac.Fs.Fsa;
namespace LibHac.Fs.Accessors
{
public class FileAccessor : IFile
{
private IFile File { get; set; }
public FileSystemAccessor Parent { get; }
public WriteState WriteState { get; private set; }
public OpenMode OpenMode { get; }
public FileAccessor(IFile baseFile, FileSystemAccessor parent, OpenMode mode)
{
File = baseFile;
Parent = parent;
OpenMode = mode;
}
protected override Result DoRead(out long bytesRead, long offset, Span<byte> destination,
in ReadOption option)
{
CheckIfDisposed();
return File.Read(out bytesRead, offset, destination, in option);
}
protected override Result DoWrite(long offset, ReadOnlySpan<byte> source, in WriteOption option)
{
CheckIfDisposed();
if (source.Length == 0)
{
WriteState = (WriteState)(~option.Flags & WriteOptionFlag.Flush);
return Result.Success;
}
Result rc = File.Write(offset, source, in option);
if (rc.IsSuccess())
{
WriteState = (WriteState)(~option.Flags & WriteOptionFlag.Flush);
}
return rc;
}
protected override Result DoFlush()
{
CheckIfDisposed();
Result rc = File.Flush();
if (rc.IsSuccess())
{
WriteState = WriteState.None;
}
return rc;
}
protected override Result DoGetSize(out long size)
{
CheckIfDisposed();
return File.GetSize(out size);
}
protected override Result DoOperateRange(Span<byte> outBuffer, OperationId operationId, long offset, long size, ReadOnlySpan<byte> inBuffer)
{
return ResultFs.NotImplemented.Log();
}
protected override Result DoSetSize(long size)
{
CheckIfDisposed();
return File.SetSize(size);
}
protected override void Dispose(bool disposing)
{
if (File == null) return;
if (WriteState == WriteState.Unflushed)
{
// Original FS code would return an error:
// ThrowHelper.ThrowResult(ResultsFs.ResultFsWriteStateUnflushed);
Flush();
}
File.Dispose();
Parent?.NotifyCloseFile(this);
File = null;
}
private void CheckIfDisposed()
{
if (File == null) throw new ObjectDisposedException(null, "Cannot access closed file.");
}
}
public enum WriteState
{
None,
Unflushed,
Error
}
}

View file

@ -1,184 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using LibHac.Common;
using LibHac.Fs.Fsa;
using IFileSystemSf = LibHac.FsSrv.Sf.IFileSystem;
namespace LibHac.Fs.Accessors
{
public class FileSystemAccessor
{
public string Name { get; }
private IFileSystem FileSystem { 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>();
private readonly object _locker = new object();
internal bool IsAccessLogEnabled { get; set; }
public IMultiCommitTarget MultiCommitTarget { get; }
public FileSystemAccessor(U8Span name, IMultiCommitTarget multiCommitTarget, IFileSystem baseFileSystem,
FileSystemClient fsClient, ICommonMountNameGenerator nameGenerator)
{
Name = name.ToString();
MultiCommitTarget = multiCommitTarget;
FileSystem = baseFileSystem;
FsClient = fsClient;
MountNameGenerator = nameGenerator;
}
public Result CreateDirectory(U8Span path)
{
return FileSystem.CreateDirectory(path);
}
public Result CreateFile(U8Span path, long size, CreateFileOptions options)
{
return FileSystem.CreateFile(path, size, options);
}
public Result DeleteDirectory(U8Span path)
{
return FileSystem.DeleteDirectory(path);
}
public Result DeleteDirectoryRecursively(U8Span path)
{
return FileSystem.DeleteDirectoryRecursively(path);
}
public Result CleanDirectoryRecursively(U8Span path)
{
return FileSystem.CleanDirectoryRecursively(path);
}
public Result DeleteFile(U8Span path)
{
return FileSystem.DeleteFile(path);
}
public Result OpenDirectory(out DirectoryAccessor directory, U8Span path, OpenDirectoryMode mode)
{
directory = default;
Result rc = FileSystem.OpenDirectory(out IDirectory rawDirectory, path, mode);
if (rc.IsFailure()) return rc;
var accessor = new DirectoryAccessor(rawDirectory, this);
lock (_locker)
{
OpenDirectories.Add(accessor);
}
directory = accessor;
return Result.Success;
}
public Result OpenFile(out FileAccessor file, U8Span path, OpenMode mode)
{
file = default;
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);
}
file = accessor;
return Result.Success;
}
public Result RenameDirectory(U8Span oldPath, U8Span newPath)
{
return FileSystem.RenameDirectory(oldPath, newPath);
}
public Result RenameFile(U8Span oldPath, U8Span newPath)
{
return FileSystem.RenameFile(oldPath, newPath);
}
public Result GetEntryType(out DirectoryEntryType type, U8Span path)
{
return FileSystem.GetEntryType(out type, path);
}
public Result GetFreeSpaceSize(out long freeSpace, U8Span path)
{
return FileSystem.GetFreeSpaceSize(out freeSpace, path);
}
public Result GetTotalSpaceSize(out long totalSpace, U8Span path)
{
return FileSystem.GetTotalSpaceSize(out totalSpace, path);
}
public Result GetFileTimeStampRaw(out FileTimeStampRaw timeStamp, U8Span path)
{
return FileSystem.GetFileTimeStampRaw(out timeStamp, path);
}
public Result Commit()
{
if (OpenFiles.Any(x => (x.OpenMode & OpenMode.Write) != 0))
{
return ResultFs.WriteModeFileNotClosed.Log();
}
return FileSystem.Commit();
}
public Result QueryEntry(Span<byte> outBuffer, ReadOnlySpan<byte> inBuffer, U8Span path, QueryId queryId)
{
return FileSystem.QueryEntry(outBuffer, inBuffer, queryId, path);
}
public Result GetCommonMountName(Span<byte> nameBuffer)
{
if (MountNameGenerator == null) return ResultFs.PreconditionViolation.Log();
return MountNameGenerator.GenerateCommonMountName(nameBuffer);
}
public ReferenceCountedDisposable<IFileSystemSf> GetMultiCommitTarget()
{
return MultiCommitTarget?.GetMultiCommitTarget();
}
internal void NotifyCloseFile(FileAccessor file)
{
lock (_locker)
{
OpenFiles.Remove(file);
}
}
internal void NotifyCloseDirectory(DirectoryAccessor directory)
{
lock (_locker)
{
OpenDirectories.Remove(directory);
}
}
internal void Close()
{
// Todo: Possibly check for open files and directories
// Nintendo checks for them in DumpUnclosedAccessorList in
// FileSystemAccessor's destructor
FileSystem?.Dispose();
}
}
}

View file

@ -1,57 +0,0 @@
using System.Collections.Generic;
namespace LibHac.Fs.Accessors
{
public class MountTable
{
private Dictionary<string, FileSystemAccessor> Table { get; } = new Dictionary<string, FileSystemAccessor>();
private readonly object _locker = new object();
public Result Mount(FileSystemAccessor fileSystem)
{
lock (_locker)
{
string mountName = fileSystem.Name;
if (Table.ContainsKey(mountName))
{
return ResultFs.MountNameAlreadyExists.Log();
}
Table.Add(mountName, fileSystem);
return Result.Success;
}
}
public Result Find(string name, out FileSystemAccessor fileSystem)
{
lock (_locker)
{
if (!Table.TryGetValue(name, out fileSystem))
{
return ResultFs.NotMounted.Log();
}
return Result.Success;
}
}
public Result Unmount(string name)
{
lock (_locker)
{
if (!Table.TryGetValue(name, out FileSystemAccessor fsAccessor))
{
return ResultFs.NotMounted.Log();
}
Table.Remove(name);
fsAccessor.Close();
return Result.Success;
}
}
}
}

View file

@ -3,6 +3,7 @@ using LibHac.Common;
namespace LibHac.Fs
{
// Todo: Migrate to LibHac.Fs.Impl.CommonMountNames and remove
internal static class CommonPaths
{
public const char ReservedMountNamePrefixCharacter = '@';

View file

@ -20,7 +20,7 @@ namespace LibHac.Fs
public WriteOption(int flags)
{
Flags = (WriteOptionFlag) flags;
Flags = (WriteOptionFlag)flags;
}
public WriteOption(WriteOptionFlag flags)

View file

@ -1,158 +0,0 @@
using System;
using System.Runtime.CompilerServices;
using LibHac.Common;
using LibHac.Fs.Impl;
using LibHac.Fs.Shim;
using LibHac.FsSrv.Sf;
using LibHac.Sf;
using FileSystemAccessor = LibHac.Fs.Accessors.FileSystemAccessor;
namespace LibHac.Fs
{
public partial class FileSystemClient
{
private GlobalAccessLogMode GlobalAccessLogMode { get; set; }
private AccessLogTarget AccessLogTarget { get; set; }
private bool AccessLogInitialized { get; set; }
private readonly object _accessLogInitLocker = new object();
public void SetAccessLogObject(IAccessLog accessLog)
{
AccessLog = accessLog;
}
internal bool IsEnabledAccessLog(AccessLogTarget target)
{
if ((AccessLogTarget & target) == 0)
{
return false;
}
if (AccessLogInitialized)
{
return GlobalAccessLogMode != GlobalAccessLogMode.None;
}
lock (_accessLogInitLocker)
{
if (!AccessLogInitialized)
{
if (HasFileSystemServer())
{
using ReferenceCountedDisposable<IFileSystemProxy> fsProxy =
Impl.GetFileSystemProxyServiceObject();
Result rc = fsProxy.Target.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 void EnableFileSystemAccessorAccessLog(U8Span mountName)
{
if (MountTable.Find(mountName.ToString(), out FileSystemAccessor accessor).IsFailure())
{
throw new LibHacException("abort");
}
accessor.IsAccessLogEnabled = true;
}
internal void OutputAccessLog(Result result, System.TimeSpan startTime, System.TimeSpan endTime, string message, [CallerMemberName] string caller = "")
{
OutputAccessLogImpl(result, startTime, endTime, 0, message, caller);
}
internal void OutputAccessLogUnlessResultSuccess(Result result, System.TimeSpan startTime, System.TimeSpan endTime, string message, [CallerMemberName] string caller = "")
{
if (result.IsFailure())
{
OutputAccessLogImpl(result, startTime, endTime, 0, message, caller);
}
}
internal void OutputAccessLogImpl(Result result, System.TimeSpan startTime, System.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);
using ReferenceCountedDisposable<IFileSystemProxy> fsProxy = Impl.GetFileSystemProxyServiceObject();
fsProxy.Target.OutputAccessLogToSdCard(new InBuffer(logString.ToU8Span())).IgnoreResult();
}
}
public Result RunOperationWithAccessLog(AccessLogTarget logTarget, Func<Result> operation,
Func<string> textGenerator, [CallerMemberName] string caller = "")
{
Result rc;
if (IsEnabledAccessLog(logTarget))
{
System.TimeSpan startTime = Time.GetCurrent();
rc = operation();
System.TimeSpan endTime = Time.GetCurrent();
OutputAccessLog(rc, startTime, endTime, textGenerator(), caller);
}
else
{
rc = operation();
}
return rc;
}
public Result RunOperationWithAccessLogOnFailure(AccessLogTarget logTarget, Func<Result> operation,
Func<string> textGenerator, [CallerMemberName] string caller = "")
{
Result rc;
if (IsEnabledAccessLog(logTarget))
{
System.TimeSpan startTime = Time.GetCurrent();
rc = operation();
System.TimeSpan endTime = Time.GetCurrent();
OutputAccessLogUnlessResultSuccess(rc, startTime, endTime, textGenerator(), caller);
}
else
{
rc = operation();
}
return rc;
}
}
}

View file

@ -1,22 +1,19 @@
using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
using LibHac.Common;
using LibHac.Diag;
using LibHac.Fs.Accessors;
using LibHac.Fs.Fsa;
using LibHac.Fs.Fsa;
using LibHac.Fs.Shim;
namespace LibHac.Fs
{
// Functions in the nn::fssrv::detail namespace use this struct.
public readonly struct FileSystemClientImpl
public class FileSystemClient
{
internal readonly FileSystemClient Fs;
internal HorizonClient Hos => Fs.Hos;
internal ref FileSystemClientGlobals Globals => ref Fs.Globals;
internal FileSystemClientGlobals Globals;
internal FileSystemClientImpl(FileSystemClient parentClient) => Fs = parentClient;
public FileSystemClientImpl Impl => new FileSystemClientImpl(this);
internal HorizonClient Hos => Globals.Hos;
public FileSystemClient(HorizonClient horizonClient)
{
Globals.Initialize(this, horizonClient);
}
}
internal struct FileSystemClientGlobals
@ -28,54 +25,24 @@ namespace LibHac.Fs
public FileSystemProxyServiceObjectGlobals FileSystemProxyServiceObject;
public FsContextHandlerGlobals FsContextHandler;
public ResultHandlingUtilityGlobals ResultHandlingUtility;
}
public partial class FileSystemClient
{
internal FileSystemClientGlobals Globals;
public FileSystemClientImpl Impl => new FileSystemClientImpl(this);
internal HorizonClient Hos => Globals.Hos;
internal ITimeSpanGenerator Time { get; }
private IAccessLog AccessLog { get; set; }
internal MountTable MountTable { get; } = new MountTable();
public FileSystemClient(ITimeSpanGenerator timer)
public void Initialize(FileSystemClient fsClient, HorizonClient horizonClient)
{
Time = timer ?? new StopWatchTimeSpanGenerator();
}
public FileSystemClient(HorizonClient horizonClient)
{
Time = horizonClient.Time;
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;
Hos = horizonClient;
InitMutex = new object();
AccessLog.Initialize(fsClient);
UserMountTable.Initialize(fsClient);
FsContextHandler.Initialize(fsClient);
}
}
[StructLayout(LayoutKind.Sequential, Size = 16)]
[DebuggerDisplay("{ToString()}")]
internal struct MountName
// Functions in the nn::fs::detail namespace use this struct.
public readonly struct FileSystemClientImpl
{
public Span<byte> Name => SpanHelpers.AsByteSpan(ref this);
internal readonly FileSystemClient Fs;
internal HorizonClient Hos => Fs.Hos;
internal ref FileSystemClientGlobals Globals => ref Fs.Globals;
public override string ToString() => new U8Span(Name).ToString();
internal FileSystemClientImpl(FileSystemClient parentClient) => Fs = parentClient;
}
}

View file

@ -2,7 +2,6 @@
using System.Buffers;
using System.Collections.Generic;
using LibHac.Common;
using LibHac.Fs.Accessors;
using LibHac.Fs.Fsa;
using LibHac.FsSystem;
@ -236,15 +235,5 @@ namespace LibHac.Fs
return fs.CreateFile(u8Path, 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;
}
}
}

View file

@ -199,13 +199,6 @@ namespace LibHac.Fs
SdCard = 2
}
[Flags]
public enum MountHostOption
{
None = 0,
PseudoCaseSensitive = 1
}
public enum SimulatingDeviceDetectionMode
{
NoSimulation = 0,

View file

@ -6,6 +6,7 @@ using LibHac.Fs.Impl;
using LibHac.FsSystem;
using LibHac.Os;
using LibHac.Util;
using static LibHac.Fs.Impl.AccessLogStrings;
namespace LibHac.Fs.Fsa
{
@ -119,7 +120,7 @@ namespace LibHac.Fs.Fsa
if (fs.IsUsedReservedMountName(name))
return ResultFs.InvalidMountName.Log();
if (fs.IsValidMountName(name))
if (!fs.IsValidMountName(name))
return ResultFs.InvalidMountName.Log();
return Result.Success;
@ -130,7 +131,7 @@ namespace LibHac.Fs.Fsa
if (name.IsNull())
return ResultFs.NullptrArgument.Log();
if (fs.IsValidMountName(name))
if (!fs.IsValidMountName(name))
return ResultFs.InvalidMountName.Log();
return Result.Success;
@ -183,9 +184,8 @@ namespace LibHac.Fs.Fsa
var sb = new U8StringBuilder(logBuffer, true);
sb.Append(LogName).Append(mountName).Append((byte)'"');
logBuffer = sb.Buffer;
fs.Impl.OutputAccessLog(rc, start, end, null, new U8Span(logBuffer));
fs.Impl.OutputAccessLog(rc, start, end, null, new U8Span(sb.Buffer));
}
else
{
@ -210,9 +210,8 @@ namespace LibHac.Fs.Fsa
var sb = new U8StringBuilder(logBuffer, true);
ReadOnlySpan<byte> boolString = AccessLogImpl.ConvertFromBoolToAccessLogBooleanValue(isMounted);
sb.Append(LogName).Append(mountName).Append(LogIsMounted).Append(boolString).Append((byte)'"');
logBuffer = sb.Buffer;
fs.Impl.OutputAccessLog(rc, start, end, null, new U8Span(logBuffer));
fs.Impl.OutputAccessLog(rc, start, end, null, new U8Span(sb.Buffer));
}
else
{
@ -254,19 +253,5 @@ namespace LibHac.Fs.Fsa
StringUtils.Copy(commonPathBuffer.Slice(commonPathLength), subPath);
return Result.Success;
}
private static ReadOnlySpan<byte> LogName => // ", name: ""
new[]
{
(byte)',', (byte)' ', (byte)'n', (byte)'a', (byte)'m', (byte)'e', (byte)':', (byte)' ',
(byte)'"'
};
private static ReadOnlySpan<byte> LogIsMounted => // "", is_mounted: ""
new[]
{
(byte)'"', (byte)',', (byte)' ', (byte)'i', (byte)'s', (byte)'_', (byte)'m', (byte)'o',
(byte)'u', (byte)'n', (byte)'t', (byte)'e', (byte)'d', (byte)':', (byte)' ', (byte)'"'
};
}
}

View file

@ -3,6 +3,7 @@ using System.Runtime.CompilerServices;
using LibHac.Common;
using LibHac.Fs.Impl;
using LibHac.Os;
using static LibHac.Fs.Impl.AccessLogStrings;
namespace LibHac.Fs.Fsa
{
@ -81,20 +82,5 @@ namespace LibHac.Fs.Fsa
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

@ -3,6 +3,7 @@ using System.Runtime.CompilerServices;
using LibHac.Common;
using LibHac.Fs.Impl;
using LibHac.Os;
using static LibHac.Fs.Impl.AccessLogStrings;
namespace LibHac.Fs.Fsa
{
@ -222,33 +223,5 @@ namespace LibHac.Fs.Fsa
fs.Impl.AbortIfNeeded(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

@ -5,6 +5,7 @@ using LibHac.Fs.Impl;
using LibHac.Fs.Shim;
using LibHac.FsSrv.Sf;
using LibHac.Os;
using static LibHac.Fs.Impl.AccessLogStrings;
using IFileSystemSf = LibHac.FsSrv.Sf.IFileSystem;
namespace LibHac.Fs.Fsa
@ -436,7 +437,7 @@ namespace LibHac.Fs.Fsa
Tick end = fs.Hos.Os.GetSystemTick();
var sb = new U8StringBuilder(logBuffer, true);
sb.Append(LogPath).Append(path).Append(LogSize)
sb.Append(LogPath).Append(path).Append((byte)'"').Append(LogSize)
.AppendFormat(AccessLogImpl.DereferenceOutValue(in freeSpace, rc));
logBuffer = sb.Buffer;
@ -465,7 +466,7 @@ namespace LibHac.Fs.Fsa
Tick end = fs.Hos.Os.GetSystemTick();
var sb = new U8StringBuilder(logBuffer, true);
sb.Append(LogPath).Append(path).Append(LogSize)
sb.Append(LogPath).Append(path).Append((byte)'"').Append(LogSize)
.AppendFormat(AccessLogImpl.DereferenceOutValue(in freeSpace, rc));
logBuffer = sb.Buffer;
@ -495,7 +496,7 @@ namespace LibHac.Fs.Fsa
Tick end = fs.Hos.Os.GetSystemTick();
var sb = new U8StringBuilder(logBuffer, true);
sb.Append(LogPath).Append(path).Append(LogOpenMode).AppendFormat((int)mode, 'X');
sb.Append(LogPath).Append(path).Append((byte)'"').Append(LogOpenMode).AppendFormat((int)mode, 'X');
logBuffer = sb.Buffer;
fs.Impl.OutputAccessLogUnlessResultSuccess(rc, start, end, null, new U8Span(logBuffer));
@ -552,7 +553,7 @@ namespace LibHac.Fs.Fsa
Tick end = fs.Hos.Os.GetSystemTick();
var sb = new U8StringBuilder(logBuffer, true);
sb.Append(LogPath).Append(path).Append(LogOpenMode).AppendFormat((int)mode, 'X');
sb.Append(LogPath).Append(path).Append((byte)'"').Append(LogOpenMode).AppendFormat((int)mode, 'X');
logBuffer = sb.Buffer;
fs.Impl.OutputAccessLogUnlessResultSuccess(rc, start, end, null, new U8Span(logBuffer));
@ -751,55 +752,5 @@ namespace LibHac.Fs.Fsa
{
return CommitImpl(fs, mountName);
}
private static ReadOnlySpan<byte> LogPath => // ", path: ""
new[]
{
(byte)',', (byte)' ', (byte)'p', (byte)'a', (byte)'t', (byte)'h', (byte)':', (byte)' ',
(byte)'"'
};
private static ReadOnlySpan<byte> LogNewPath => // "", new_path: ""
new[]
{
(byte)'"', (byte)',', (byte)' ', (byte)'n', (byte)'e', (byte)'w', (byte)'_', (byte)'p',
(byte)'a', (byte)'t', (byte)'h', (byte)':', (byte)' ', (byte)'"'
};
private static ReadOnlySpan<byte> LogEntryType => // "", entry_type: "
new[]
{
(byte)'"', (byte)',', (byte)' ', (byte)'e', (byte)'n', (byte)'t', (byte)'r', (byte)'y',
(byte)'_', (byte)'t', (byte)'y', (byte)'p', (byte)'e', (byte)':', (byte)' '
};
private static ReadOnlySpan<byte> LogSize => // "", size: "
new[]
{
(byte)'"', (byte)',', (byte)' ', (byte)'s', (byte)'i', (byte)'z', (byte)'e', (byte)':',
(byte)' '
};
private static ReadOnlySpan<byte> LogOpenMode => // "", open_mode: 0x"
new[]
{
(byte)'"', (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'
};
private static ReadOnlySpan<byte> LogName => // ", name: ""
new[]
{
(byte)',', (byte)' ', (byte)'n', (byte)'a', (byte)'m', (byte)'e', (byte)':', (byte)' ',
(byte)'"'
};
private static ReadOnlySpan<byte> LogCommitOption => // "", commit_option: 0x"
new[]
{
(byte)'"', (byte)',', (byte)' ', (byte)'c', (byte)'o', (byte)'m', (byte)'m', (byte)'i',
(byte)'t', (byte)'_', (byte)'o', (byte)'p', (byte)'t', (byte)'i', (byte)'o', (byte)'n',
(byte)':', (byte)' ', (byte)'0', (byte)'x'
};
}
}

View file

@ -3,6 +3,7 @@ using System.Runtime.CompilerServices;
using LibHac.Common;
using LibHac.Fs.Impl;
using LibHac.Os;
using static LibHac.Fs.Impl.AccessLogStrings;
namespace LibHac.Fs.Fsa
{
@ -41,7 +42,7 @@ namespace LibHac.Fs.Fsa
Tick end = fs.Hos.Os.GetSystemTick();
var sb = new U8StringBuilder(logBuffer, true);
sb.Append(LogPath).Append(path).Append(LogSize).AppendFormat(size);
sb.Append(LogPath).Append(path).Append((byte)'"').Append(LogSize).AppendFormat(size);
logBuffer = sb.Buffer;
fs.Impl.OutputAccessLog(rc, start, end, null, new U8Span(logBuffer));
@ -77,19 +78,5 @@ namespace LibHac.Fs.Fsa
fs.Impl.AbortIfNeeded(rc);
return rc;
}
private static ReadOnlySpan<byte> LogPath => // ", path: ""
new[]
{
(byte)',', (byte)' ', (byte)'p', (byte)'a', (byte)'t', (byte)'h', (byte)':', (byte)' ',
(byte)'"'
};
private static ReadOnlySpan<byte> LogSize => // "", size: "
new[]
{
(byte)'"', (byte)',', (byte)' ', (byte)'s', (byte)'i', (byte)'z', (byte)'e', (byte)':',
(byte)' '
};
}
}

31
src/LibHac/Fs/Host.cs Normal file
View file

@ -0,0 +1,31 @@
using System;
namespace LibHac.Fs
{
public readonly struct MountHostOption
{
public readonly MountHostOptionFlag Flags;
public MountHostOption(int flags)
{
Flags = (MountHostOptionFlag)flags;
}
public MountHostOption(MountHostOptionFlag flags)
{
Flags = flags;
}
public static MountHostOption None => new MountHostOption(MountHostOptionFlag.None);
public static MountHostOption PseudoCaseSensitive =>
new MountHostOption(MountHostOptionFlag.PseudoCaseSensitive);
}
[Flags]
public enum MountHostOptionFlag
{
None = 0,
PseudoCaseSensitive = 1
}
}

View file

@ -0,0 +1,81 @@
using System;
namespace LibHac.Fs.Impl
{
public static class CommonMountNames
{
public const char ReservedMountNamePrefixCharacter = '@';
// Filesystem names.
public static ReadOnlySpan<byte> HostRootFileSystemMountName => // "@Host"
new[] { (byte)'@', (byte)'H', (byte)'o', (byte)'s', (byte)'t' };
public static ReadOnlySpan<byte> SdCardFileSystemMountName => // "@Sdcard"
new[] { (byte)'@', (byte)'S', (byte)'d', (byte)'c', (byte)'a', (byte)'r', (byte)'d' };
public static ReadOnlySpan<byte> GameCardFileSystemMountName => // "@Gc"
new[] { (byte)'@', (byte)'G', (byte)'c' };
public static ReadOnlySpan<byte> GameCardFileSystemMountNameUpdateSuffix => // "U"
new[] { (byte)'U' };
public static ReadOnlySpan<byte> GameCardFileSystemMountNameNormalSuffix => // "N"
new[] { (byte)'N' };
public static ReadOnlySpan<byte> GameCardFileSystemMountNameSecureSuffix => // "S"
new[] { (byte)'S' };
// Built-in storage names.
public static ReadOnlySpan<byte> BisCalibrationFilePartitionMountName => // "@CalibFile"
new[]
{
(byte)'@', (byte)'C', (byte)'a', (byte)'l', (byte)'i', (byte)'b', (byte)'F', (byte)'i',
(byte)'l', (byte)'e'
};
public static ReadOnlySpan<byte> BisSafeModePartitionMountName => // "@Safe"
new[] { (byte)'@', (byte)'S', (byte)'a', (byte)'f', (byte)'e' };
public static ReadOnlySpan<byte> BisUserPartitionMountName => // "@User"
new[] { (byte)'@', (byte)'U', (byte)'s', (byte)'e', (byte)'r' };
public static ReadOnlySpan<byte> BisSystemPartitionMountName => // "@System"
new[] { (byte)'@', (byte)'S', (byte)'y', (byte)'s', (byte)'t', (byte)'e', (byte)'m' };
//Content storage names.
public static ReadOnlySpan<byte> ContentStorageSystemMountName => // "@SystemContent"
new[]
{
(byte)'@', (byte)'S', (byte)'y', (byte)'s', (byte)'t', (byte)'e', (byte)'m', (byte)'C',
(byte)'o', (byte)'n', (byte)'t', (byte)'e', (byte)'n', (byte)'t'
};
public static ReadOnlySpan<byte> ContentStorageUserMountName => // "@UserContent"
new[]
{
(byte)'@', (byte)'U', (byte)'s', (byte)'e', (byte)'r', (byte)'C', (byte)'o', (byte)'n',
(byte)'t', (byte)'e', (byte)'n', (byte)'t'
};
public static ReadOnlySpan<byte> ContentStorageSdCardMountName => // "@SdCardContent"
new[]
{
(byte)'@', (byte)'S', (byte)'d', (byte)'C', (byte)'a', (byte)'r', (byte)'d', (byte)'C',
(byte)'o', (byte)'n', (byte)'t', (byte)'e', (byte)'n', (byte)'t'
};
// Registered update partition
public static ReadOnlySpan<byte> RegisteredUpdatePartitionMountName => // "@RegUpdate"
new[]
{
(byte)'@', (byte)'R', (byte)'e', (byte)'g', (byte)'U', (byte)'p', (byte)'d', (byte)'a',
(byte)'t', (byte)'e'
};
public static ReadOnlySpan<byte> SdCardNintendoRootDirectoryName => // "Nintendo"
new[]
{
(byte)'N', (byte)'i', (byte)'n', (byte)'t', (byte)'e', (byte)'n', (byte)'d', (byte)'o'
};
}
}

View file

@ -0,0 +1,43 @@
using System.Runtime.CompilerServices;
namespace LibHac.Fs.Impl
{
internal readonly struct SaveDataMetaPolicy
{
private readonly SaveDataType _type;
private const int ThumbnailFileSize = 0x40060;
public SaveDataMetaPolicy(SaveDataType saveType)
{
_type = saveType;
}
public void GenerateMetaInfo(out SaveDataMetaInfo metaInfo)
{
Unsafe.SkipInit(out metaInfo);
if (_type == SaveDataType.Account || _type == SaveDataType.Device)
{
metaInfo.Type = SaveDataMetaType.Thumbnail;
metaInfo.Size = ThumbnailFileSize;
}
else
{
metaInfo.Type = SaveDataMetaType.None;
metaInfo.Size = 0;
}
}
public long GetSaveDataMetaSize()
{
GenerateMetaInfo(out SaveDataMetaInfo metaInfo);
return metaInfo.Size;
}
public SaveDataMetaType GetSaveDataMetaType()
{
GenerateMetaInfo(out SaveDataMetaInfo metaInfo);
return metaInfo.Type;
}
}
}

View file

@ -1,39 +0,0 @@
using LibHac.Common;
using static LibHac.Fs.CommonPaths;
namespace LibHac.Fs
{
internal static class MountHelpers
{
public static Result CheckMountName(U8Span name)
{
if (name.IsNull()) return ResultFs.NullptrArgument.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.NullptrArgument.Log();
if (!CheckMountNameImpl(name)) return ResultFs.InvalidMountName.Log();
return Result.Success;
}
public static bool IsReservedMountName(U8Span name)
{
return (uint)name.Length > 0 && name[0] == ReservedMountNamePrefixCharacter;
}
// ReSharper disable once UnusedParameter.Local
private static bool CheckMountNameImpl(U8Span name)
{
// Todo
return true;
}
}
}

View file

@ -0,0 +1,16 @@
using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
using LibHac.Common;
namespace LibHac.Fs
{
[StructLayout(LayoutKind.Sequential, Size = 16)]
[DebuggerDisplay("{ToString()}")]
internal struct MountName
{
public Span<byte> Name => SpanHelpers.AsByteSpan(ref this);
public override string ToString() => new U8Span(Name).ToString();
}
}

View file

@ -257,6 +257,8 @@ namespace LibHac.Fs
public static Result.Base OutOfRange => new Result.Base(ModuleFs, 3005);
/// <summary>Error code: 2002-3100; Inner value: 0x183802</summary>
public static Result.Base SystemPartitionNotReady => new Result.Base(ModuleFs, 3100);
/// <summary>Error code: 2002-3101; Inner value: 0x183a02</summary>
public static Result.Base StorageDeviceNotReady => new Result.Base(ModuleFs, 3101);
/// <summary>Error code: 2002-3200; Range: 3200-3499; Inner value: 0x190002</summary>
public static Result.Base AllocationMemoryFailed { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new Result.Base(ModuleFs, 3200, 3499); }

View file

@ -7,5 +7,7 @@ namespace LibHac.Fs
public const ulong SaveIndexerId = 0x8000000000000000;
public static ProgramId InvalidProgramId => ProgramId.InvalidId;
public static ProgramId AutoResolveCallerProgramId => new ProgramId(ulong.MaxValue - 1);
public static UserId InvalidUserId => UserId.InvalidId;
}
}

View file

@ -2,6 +2,7 @@
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using LibHac.Common;
using LibHac.FsSrv.Impl;
using LibHac.Ncm;
using LibHac.Util;
@ -109,6 +110,39 @@ namespace LibHac.Fs
}
}
[StructLayout(LayoutKind.Explicit, Size = 0x40)]
public struct SaveDataCreationInfo
{
[FieldOffset(0x00)] public long Size;
[FieldOffset(0x08)] public long JournalSize;
[FieldOffset(0x10)] public long BlockSize;
[FieldOffset(0x18)] public ulong OwnerId;
[FieldOffset(0x20)] public SaveDataFlags Flags;
[FieldOffset(0x24)] public SaveDataSpaceId SpaceId;
[FieldOffset(0x25)] public bool Field25;
public static Result Make(out SaveDataCreationInfo creationInfo, long size, long journalSize, ulong ownerId,
SaveDataFlags flags, SaveDataSpaceId spaceId)
{
Unsafe.SkipInit(out creationInfo);
SaveDataCreationInfo tempCreationInfo = default;
tempCreationInfo.Size = size;
tempCreationInfo.JournalSize = journalSize;
tempCreationInfo.BlockSize = SaveDataProperties.DefaultSaveDataBlockSize;
tempCreationInfo.OwnerId = ownerId;
tempCreationInfo.Flags = flags;
tempCreationInfo.SpaceId = spaceId;
tempCreationInfo.Field25 = false;
if (!SaveDataTypesValidity.IsValid(in tempCreationInfo))
return ResultFs.InvalidArgument.Log();
creationInfo = tempCreationInfo;
return Result.Success;
}
}
[StructLayout(LayoutKind.Explicit, Size = 0x48)]
public struct SaveDataFilter
{
@ -230,18 +264,6 @@ namespace LibHac.Fs
[FieldOffset(4)] public SaveDataMetaType Type;
}
[StructLayout(LayoutKind.Explicit, Size = 0x40)]
public struct SaveDataCreationInfo
{
[FieldOffset(0x00)] public long Size;
[FieldOffset(0x08)] public long JournalSize;
[FieldOffset(0x10)] public long BlockSize;
[FieldOffset(0x18)] public ulong OwnerId;
[FieldOffset(0x20)] public SaveDataFlags Flags;
[FieldOffset(0x24)] public SaveDataSpaceId SpaceId;
[FieldOffset(0x25)] public bool Field25;
}
[StructLayout(LayoutKind.Explicit, Size = 0x60)]
public struct SaveDataInfo
{

View file

@ -1,59 +1,72 @@
using LibHac.Common;
using System;
using System.Runtime.CompilerServices;
using LibHac.Common;
using LibHac.Fs.Fsa;
using LibHac.Fs.Impl;
using LibHac.FsSrv.Sf;
using LibHac.Os;
using static LibHac.Fs.Impl.AccessLogStrings;
using IFileSystemSf = LibHac.FsSrv.Sf.IFileSystem;
namespace LibHac.Fs.Shim
{
[SkipLocalsInit]
public static class Application
{
public static Result MountApplicationPackage(this FileSystemClient fs, U8Span mountName, U8Span path)
{
Result rc;
if (fs.IsEnabledAccessLog(AccessLogTarget.System))
if (fs.Impl.IsEnabledAccessLog(AccessLogTarget.System))
{
System.TimeSpan startTime = fs.Time.GetCurrent();
rc = Run(fs, mountName, path);
System.TimeSpan endTime = fs.Time.GetCurrent();
Tick start = fs.Hos.Os.GetSystemTick();
rc = Mount(fs, mountName, path);
Tick end = fs.Hos.Os.GetSystemTick();
fs.OutputAccessLog(rc, startTime, endTime, "");
Span<byte> logBuffer = stackalloc byte[0x300];
var sb = new U8StringBuilder(logBuffer, true);
sb.Append(LogName).Append(mountName).Append(LogQuote)
.Append(LogPath).Append(path).Append(LogQuote);
fs.Impl.OutputAccessLog(rc, start, end, null, new U8Span(sb.Buffer));
}
else
{
rc = Run(fs, mountName, path);
rc = Mount(fs, mountName, path);
}
fs.Impl.AbortIfNeeded(rc);
if (rc.IsFailure()) return rc;
if (fs.IsEnabledAccessLog(AccessLogTarget.System))
{
fs.EnableFileSystemAccessorAccessLog(mountName);
}
if (fs.Impl.IsEnabledAccessLog(AccessLogTarget.System))
fs.Impl.EnableFileSystemAccessorAccessLog(mountName);
return Result.Success;
static Result Run(FileSystemClient fs, U8Span mountName, U8Span path)
static Result Mount(FileSystemClient fs, U8Span mountName, U8Span path)
{
// ReSharper disable once VariableHidesOuterVariable
Result rc = MountHelpers.CheckMountName(mountName);
Result rc = fs.Impl.CheckMountName(mountName);
if (rc.IsFailure()) return rc;
FspPath.FromSpan(out FspPath sfPath, path);
rc = FspPath.FromSpan(out FspPath sfPath, path);
if (rc.IsFailure()) return rc;
using ReferenceCountedDisposable<IFileSystemProxy> fsProxy = fs.Impl.GetFileSystemProxyServiceObject();
rc = fsProxy.Target.OpenFileSystemWithId(out ReferenceCountedDisposable<IFileSystemSf> fileSystem,
in sfPath, default, FileSystemProxyType.Package);
if (rc.IsFailure()) return rc;
using (fileSystem)
ReferenceCountedDisposable<IFileSystemSf> fileSystem = null;
try
{
var fileSystemAdapter = new FileSystemServiceObjectAdapter(fileSystem);
rc = fsProxy.Target.OpenFileSystemWithId(out fileSystem, in sfPath, Ncm.ProgramId.InvalidId.Value,
FileSystemProxyType.Package);
if (rc.IsFailure()) return rc;
var fileSystemAdapter = new FileSystemServiceObjectAdapter(fileSystem);
return fs.Register(mountName, fileSystemAdapter);
}
finally
{
fileSystem?.Dispose();
}
}
}
}

View file

@ -1,65 +1,73 @@
using LibHac.Common;
using System;
using System.Runtime.CompilerServices;
using LibHac.Common;
using LibHac.Fs.Fsa;
using LibHac.Fs.Impl;
using LibHac.FsSrv.Sf;
using LibHac.Os;
using static LibHac.Fs.Impl.AccessLogStrings;
using IFileSystemSf = LibHac.FsSrv.Sf.IFileSystem;
namespace LibHac.Fs.Shim
{
[SkipLocalsInit]
public static class BcatSaveData
{
public static Result MountBcatSaveData(this FileSystemClient fs, U8Span mountName, Ncm.ApplicationId applicationId)
public static Result MountBcatSaveData(this FileSystemClient fs, U8Span mountName,
Ncm.ApplicationId applicationId)
{
Result rc;
if (fs.IsEnabledAccessLog(AccessLogTarget.System))
if (fs.Impl.IsEnabledAccessLog(AccessLogTarget.System))
{
System.TimeSpan startTime = fs.Time.GetCurrent();
rc = MountBcatSaveDataImpl(fs, mountName, applicationId);
System.TimeSpan endTime = fs.Time.GetCurrent();
Tick start = fs.Hos.Os.GetSystemTick();
rc = Mount(fs, mountName, applicationId);
Tick end = fs.Hos.Os.GetSystemTick();
string logMessage = $", name: \"{mountName.ToString()}\", applicationid: 0x{applicationId}\"";
Span<byte> logBuffer = stackalloc byte[0x50];
var sb = new U8StringBuilder(logBuffer, true);
fs.OutputAccessLog(rc, startTime, endTime, logMessage);
sb.Append(LogName).Append(mountName).Append(LogQuote)
.Append(LogApplicationId).AppendFormat(applicationId.Value, 'X');
fs.Impl.OutputAccessLog(rc, start, end, null, new U8Span(sb.Buffer));
}
else
{
rc = MountBcatSaveDataImpl(fs, mountName, applicationId);
rc = Mount(fs, mountName, applicationId);
}
fs.Impl.AbortIfNeeded(rc);
if (rc.IsFailure()) return rc;
if (fs.IsEnabledAccessLog(AccessLogTarget.System))
{
fs.EnableFileSystemAccessorAccessLog(mountName);
}
if (fs.Impl.IsEnabledAccessLog(AccessLogTarget.System))
fs.Impl.EnableFileSystemAccessorAccessLog(mountName);
return Result.Success;
}
private static Result MountBcatSaveDataImpl(FileSystemClient fs, U8Span mountName, Ncm.ApplicationId applicationId)
{
Result rc = MountHelpers.CheckMountName(mountName);
if (rc.IsFailure()) return rc;
using ReferenceCountedDisposable<IFileSystemProxy> fsProxy = fs.Impl.GetFileSystemProxyServiceObject();
var attribute = new SaveDataAttribute(applicationId, SaveDataType.Bcat, UserId.InvalidId, 0);
ReferenceCountedDisposable<IFileSystemSf> saveFs = null;
try
static Result Mount(FileSystemClient fs, U8Span mountName, Ncm.ApplicationId applicationId)
{
rc = fsProxy.Target.OpenSaveDataFileSystem(out saveFs, SaveDataSpaceId.User, in attribute);
Result rc = fs.Impl.CheckMountName(mountName);
if (rc.IsFailure()) return rc;
var fileSystemAdapter = new FileSystemServiceObjectAdapter(saveFs);
using ReferenceCountedDisposable<IFileSystemProxy> fsProxy = fs.Impl.GetFileSystemProxyServiceObject();
return fs.Register(mountName, fileSystemAdapter);
}
finally
{
saveFs?.Dispose();
rc = SaveDataAttribute.Make(out SaveDataAttribute attribute, applicationId, SaveDataType.Bcat,
Fs.SaveData.InvalidUserId, 0);
if (rc.IsFailure()) return rc;
ReferenceCountedDisposable<IFileSystemSf> fileSystem = null;
try
{
rc = fsProxy.Target.OpenSaveDataFileSystem(out fileSystem, SaveDataSpaceId.User, in attribute);
if (rc.IsFailure()) return rc;
var fileSystemAdapter = new FileSystemServiceObjectAdapter(fileSystem);
return fs.Register(mountName, fileSystemAdapter);
}
finally
{
fileSystem?.Dispose();
}
}
}
}

View file

@ -2,17 +2,21 @@
using System.Diagnostics;
using System.Runtime.CompilerServices;
using LibHac.Common;
using LibHac.Diag;
using LibHac.Fs.Fsa;
using LibHac.Fs.Impl;
using LibHac.FsSrv.Sf;
using LibHac.FsSystem;
using LibHac.Os;
using LibHac.Util;
using static LibHac.Fs.CommonPaths;
using static LibHac.Fs.Impl.CommonMountNames;
using static LibHac.Fs.Impl.AccessLogStrings;
using IFileSystemSf = LibHac.FsSrv.Sf.IFileSystem;
using IStorageSf = LibHac.FsSrv.Sf.IStorage;
namespace LibHac.Fs.Shim
{
[SkipLocalsInit]
public static class Bis
{
private class BisCommonMountNameGenerator : ICommonMountNameGenerator
@ -28,7 +32,7 @@ namespace LibHac.Fs.Shim
public Result GenerateCommonMountName(Span<byte> nameBuffer)
{
U8Span mountName = GetBisMountName(PartitionId);
ReadOnlySpan<byte> mountName = GetBisMountName(PartitionId);
// Add 2 for the mount name separator and null terminator
// ReSharper disable once RedundantAssignment
@ -45,72 +49,77 @@ namespace LibHac.Fs.Shim
}
}
public static Result MountBis(this FileSystemClient fs, U8Span mountName, BisPartitionId partitionId)
{
return MountBis(fs, mountName, partitionId, default);
}
public static Result MountBis(this FileSystemClient fs, BisPartitionId partitionId, U8Span rootPath)
{
return MountBis(fs, GetBisMountName(partitionId), partitionId, rootPath);
}
// nn::fs::detail::MountBis
private static Result MountBis(FileSystemClient fs, U8Span mountName, BisPartitionId partitionId, U8Span rootPath)
private static Result MountBis(this FileSystemClientImpl fs, U8Span mountName, BisPartitionId partitionId, U8Span rootPath)
{
Result rc;
if (fs.IsEnabledAccessLog(AccessLogTarget.System))
{
System.TimeSpan startTime = fs.Time.GetCurrent();
rc = MountBisImpl(fs, mountName, partitionId, rootPath);
System.TimeSpan endTime = fs.Time.GetCurrent();
Tick start = fs.Hos.Os.GetSystemTick();
rc = Mount(fs, mountName, partitionId);
Tick end = fs.Hos.Os.GetSystemTick();
string logMessage = $", name: \"{mountName.ToString()}\", bispartitionid: {partitionId}, path: \"{rootPath.ToString()}\"";
Span<byte> logBuffer = stackalloc byte[0x300];
var idString = new IdString();
var sb = new U8StringBuilder(logBuffer, true);
fs.OutputAccessLog(rc, startTime, endTime, logMessage);
sb.Append(LogName).Append(mountName).Append(LogQuote)
.Append(LogBisPartitionId).Append(idString.ToString(partitionId))
.Append(LogPath).Append(rootPath).Append(LogQuote);
fs.OutputAccessLog(rc, start, end, null, new U8Span(sb.Buffer));
}
else
{
rc = MountBisImpl(fs, mountName, partitionId, rootPath);
rc = Mount(fs, mountName, partitionId);
}
fs.AbortIfNeeded(rc);
if (rc.IsFailure()) return rc;
if (fs.IsEnabledAccessLog(AccessLogTarget.System))
{
fs.EnableFileSystemAccessorAccessLog(mountName);
}
return Result.Success;
}
// ReSharper disable once UnusedParameter.Local
private static Result MountBisImpl(FileSystemClient fs, U8Span mountName, BisPartitionId partitionId,
U8Span rootPath)
{
Result rc = MountHelpers.CheckMountNameAcceptingReservedMountName(mountName);
if (rc.IsFailure()) return rc;
using ReferenceCountedDisposable<IFileSystemProxy> fsProxy = fs.Impl.GetFileSystemProxyServiceObject();
// Nintendo doesn't use the provided rootPath
FspPath.CreateEmpty(out FspPath sfPath);
rc = fsProxy.Target.OpenBisFileSystem(out ReferenceCountedDisposable<IFileSystemSf> fileSystem, in sfPath,
partitionId);
if (rc.IsFailure()) return rc;
using (fileSystem)
static Result Mount(FileSystemClientImpl fs, U8Span mountName, BisPartitionId partitionId)
{
var nameGenerator = new BisCommonMountNameGenerator(partitionId);
var fileSystemAdapter = new FileSystemServiceObjectAdapter(fileSystem);
Result rc = fs.CheckMountNameAcceptingReservedMountName(mountName);
if (rc.IsFailure()) return rc;
return fs.Register(mountName, fileSystemAdapter, nameGenerator);
using ReferenceCountedDisposable<IFileSystemProxy> fsProxy = fs.GetFileSystemProxyServiceObject();
// Nintendo doesn't use the provided rootPath
FspPath.CreateEmpty(out FspPath sfPath);
ReferenceCountedDisposable<IFileSystemSf> fileSystem = null;
try
{
rc = fsProxy.Target.OpenBisFileSystem(out fileSystem, in sfPath, partitionId);
if (rc.IsFailure()) return rc;
var nameGenerator = new BisCommonMountNameGenerator(partitionId);
var fileSystemAdapter = new FileSystemServiceObjectAdapter(fileSystem);
return fs.Fs.Register(mountName, fileSystemAdapter, nameGenerator);
}
finally
{
fileSystem?.Dispose();
}
}
}
public static U8Span GetBisMountName(BisPartitionId partitionId)
public static Result MountBis(this FileSystemClient fs, U8Span mountName, BisPartitionId partitionId)
{
return MountBis(fs.Impl, mountName, partitionId, default);
}
public static Result MountBis(this FileSystemClient fs, BisPartitionId partitionId, U8Span rootPath)
{
return MountBis(fs.Impl, new U8Span(GetBisMountName(partitionId)), partitionId, rootPath);
}
public static ReadOnlySpan<byte> GetBisMountName(BisPartitionId partitionId)
{
switch (partitionId)
{
@ -124,7 +133,8 @@ namespace LibHac.Fs.Shim
case BisPartitionId.BootConfigAndPackage2Part5:
case BisPartitionId.BootConfigAndPackage2Part6:
case BisPartitionId.CalibrationBinary:
throw new HorizonResultException(default, "The partition specified is not mountable.");
Abort.DoAbort("The partition specified is not mountable.");
break;
case BisPartitionId.CalibrationFile:
return BisCalibrationFilePartitionMountName;
@ -136,18 +146,24 @@ namespace LibHac.Fs.Shim
return BisSystemPartitionMountName;
default:
throw new ArgumentOutOfRangeException(nameof(partitionId), partitionId, null);
Abort.UnexpectedDefault();
break;
}
return ReadOnlySpan<byte>.Empty;
}
// todo: Decide how to handle SetBisRootForHost since it allows mounting any directory on the user's computer
public static Result SetBisRootForHost(this FileSystemClient fs, BisPartitionId partitionId, U8Span rootPath)
{
Unsafe.SkipInit(out FsPath path);
Result rc;
int pathLen = StringUtils.GetLength(rootPath, PathTools.MaxPathLength + 1);
if (pathLen > PathTools.MaxPathLength)
{
fs.Impl.LogErrorMessage(ResultFs.TooLongPath.Value);
return ResultFs.TooLongPath.Log();
}
if (pathLen > 0)
{
@ -156,7 +172,7 @@ namespace LibHac.Fs.Shim
: StringTraits.DirectorySeparator;
var sb = new U8StringBuilder(path.Str);
Result rc = sb.Append(rootPath).Append(endingSeparator).ToSfPath();
rc = sb.Append(rootPath).Append(endingSeparator).ToSfPath();
if (rc.IsFailure()) return rc;
}
else
@ -168,7 +184,9 @@ namespace LibHac.Fs.Shim
using ReferenceCountedDisposable<IFileSystemProxy> fsProxy = fs.Impl.GetFileSystemProxyServiceObject();
return fsProxy.Target.SetBisRootForHost(partitionId, in sfPath);
rc = fsProxy.Target.SetBisRootForHost(partitionId, in sfPath);
fs.Impl.LogErrorMessage(rc);
return rc;
}
public static Result OpenBisPartition(this FileSystemClient fs, out IStorage partitionStorage,
@ -176,23 +194,31 @@ namespace LibHac.Fs.Shim
{
partitionStorage = default;
using ReferenceCountedDisposable<IFileSystemProxy> fsProxy = fs.Impl.GetFileSystemProxyServiceObject();
Result rc = fsProxy.Target.OpenBisStorage(out ReferenceCountedDisposable<IStorageSf> storage, partitionId);
if (rc.IsFailure()) return rc;
using (storage)
ReferenceCountedDisposable<IStorageSf> storage = null;
try
{
using ReferenceCountedDisposable<IFileSystemProxy> fsProxy = fs.Impl.GetFileSystemProxyServiceObject();
Result rc = fsProxy.Target.OpenBisStorage(out storage, partitionId);
fs.Impl.AbortIfNeeded(rc);
if (rc.IsFailure()) return rc;
var storageAdapter = new StorageServiceObjectAdapter(storage);
partitionStorage = storageAdapter;
return Result.Success;
}
finally
{
storage?.Dispose();
}
}
public static Result InvalidateBisCache(this FileSystemClient fs)
{
using ReferenceCountedDisposable<IFileSystemProxy> fsProxy = fs.Impl.GetFileSystemProxyServiceObject();
return fsProxy.Target.InvalidateBisCache();
Result rc = fsProxy.Target.InvalidateBisCache();
fs.Impl.AbortIfNeeded(rc);
return rc;
}
}
}

View file

@ -1,65 +1,80 @@
using System.Runtime.CompilerServices;
using System;
using System.Runtime.CompilerServices;
using LibHac.Common;
using LibHac.Fs.Fsa;
using LibHac.Fs.Impl;
using LibHac.FsSrv.Sf;
using LibHac.Ncm;
using LibHac.Os;
using static LibHac.Fs.Impl.AccessLogStrings;
using IFileSystemSf = LibHac.FsSrv.Sf.IFileSystem;
namespace LibHac.Fs.Shim
{
[SkipLocalsInit]
public static class Code
{
public static Result MountCode(this FileSystemClient fs, out CodeVerificationData verificationData,
U8Span mountName, U8Span path, ProgramId programId)
{
Result rc;
if (fs.IsEnabledAccessLog(AccessLogTarget.System))
if (fs.Impl.IsEnabledAccessLog(AccessLogTarget.System))
{
System.TimeSpan startTime = fs.Time.GetCurrent();
rc = MountCodeImpl(fs, out verificationData, mountName, path, programId);
System.TimeSpan endTime = fs.Time.GetCurrent();
Tick start = fs.Hos.Os.GetSystemTick();
rc = Mount(fs, out verificationData, mountName, path, programId);
Tick end = fs.Hos.Os.GetSystemTick();
fs.OutputAccessLog(rc, startTime, endTime,
$", name: \"{mountName.ToString()}\", name: \"{path.ToString()}\", programid: 0x{programId}");
Span<byte> logBuffer = stackalloc byte[0x300];
var sb = new U8StringBuilder(logBuffer, true);
sb.Append(LogName).Append(mountName).Append(LogQuote)
.Append(LogPath).Append(path).Append(LogQuote)
.Append(LogProgramId).AppendFormat(programId.Value, 'X');
fs.Impl.OutputAccessLog(rc, start, end, null, new U8Span(sb.Buffer));
}
else
{
rc = MountCodeImpl(fs, out verificationData, mountName, path, programId);
rc = Mount(fs, out verificationData, mountName, path, programId);
}
fs.Impl.AbortIfNeeded(rc);
if (rc.IsFailure()) return rc;
if (rc.IsSuccess() && fs.IsEnabledAccessLog(AccessLogTarget.System))
if (fs.Impl.IsEnabledAccessLog(AccessLogTarget.System))
fs.Impl.EnableFileSystemAccessorAccessLog(mountName);
return Result.Success;
static Result Mount(FileSystemClient fs, out CodeVerificationData verificationData,
U8Span mountName, U8Span path, ProgramId programId)
{
fs.EnableFileSystemAccessorAccessLog(mountName);
}
Unsafe.SkipInit(out verificationData);
return rc;
}
Result rc = fs.Impl.CheckMountName(mountName);
if (rc.IsFailure()) return rc;
private static Result MountCodeImpl(this FileSystemClient fs, out CodeVerificationData verificationData,
U8Span mountName, U8Span path, ProgramId programId)
{
Unsafe.SkipInit(out verificationData);
if (path.IsNull())
return ResultFs.NullptrArgument.Log();
Result rc = MountHelpers.CheckMountName(mountName);
if (rc.IsFailure()) return rc;
rc = FspPath.FromSpan(out FspPath fsPath, path);
if (rc.IsFailure()) return rc;
rc = FspPath.FromSpan(out FspPath fsPath, path);
if (rc.IsFailure()) return rc;
using ReferenceCountedDisposable<IFileSystemProxyForLoader> fsProxy =
fs.Impl.GetFileSystemProxyForLoaderServiceObject();
using ReferenceCountedDisposable<IFileSystemProxyForLoader> fsProxy =
fs.Impl.GetFileSystemProxyForLoaderServiceObject();
ReferenceCountedDisposable<IFileSystemSf> fileSystem = null;
try
{
rc = fsProxy.Target.OpenCodeFileSystem(out fileSystem, out verificationData, in fsPath, programId);
if (rc.IsFailure()) return rc;
rc = fsProxy.Target.OpenCodeFileSystem(out ReferenceCountedDisposable<IFileSystemSf> codeFs,
out verificationData, in fsPath, programId);
if (rc.IsFailure()) return rc;
using (codeFs)
{
var fileSystemAdapter = new FileSystemServiceObjectAdapter(codeFs);
return fs.Register(mountName, fileSystemAdapter);
var fileSystemAdapter = new FileSystemServiceObjectAdapter(fileSystem);
return fs.Register(mountName, fileSystemAdapter);
}
finally
{
fileSystem?.Dispose();
}
}
}
}

View file

@ -1,90 +1,260 @@
using System;
using System.Runtime.CompilerServices;
using LibHac.Common;
using LibHac.Diag;
using LibHac.Fs.Fsa;
using LibHac.Fs.Impl;
using LibHac.FsSrv.Sf;
using LibHac.Ncm;
using LibHac.Os;
using static LibHac.Fs.Impl.AccessLogStrings;
using IFileSystemSf = LibHac.FsSrv.Sf.IFileSystem;
namespace LibHac.Fs.Shim
{
[SkipLocalsInit]
public static class Content
{
// todo: add logging
public static Result MountContent(this FileSystemClient fs, U8Span mountName, U8Span path, ContentType type)
private static FileSystemProxyType ConvertToFileSystemProxyType(ContentType type)
{
if (type == ContentType.Meta)
return ResultFs.InvalidArgument.Log();
return MountContent(fs, mountName, path, ProgramId.InvalidId, type);
}
public static Result MountContent(this FileSystemClient fs, U8Span mountName, ProgramId programId, ContentType type)
{
Result rc = MountHelpers.CheckMountNameAcceptingReservedMountName(mountName);
if (rc.IsFailure()) return rc;
FileSystemProxyType fspType = ConvertToFileSystemProxyType(type);
using ReferenceCountedDisposable<IFileSystemProxy> fsProxy = fs.Impl.GetFileSystemProxyServiceObject();
rc = fsProxy.Target.OpenFileSystemWithPatch(out ReferenceCountedDisposable<IFileSystemSf> fileSystem,
programId, fspType);
if (rc.IsFailure()) return rc;
using (fileSystem)
switch (type)
{
var fileSystemAdapter = new FileSystemServiceObjectAdapter(fileSystem);
return fs.Register(mountName, fileSystemAdapter);
case ContentType.Meta: return FileSystemProxyType.Meta;
case ContentType.Control: return FileSystemProxyType.Control;
case ContentType.Manual: return FileSystemProxyType.Manual;
case ContentType.Data: return FileSystemProxyType.Data;
default:
Abort.UnexpectedDefault();
return default;
}
}
public static Result MountContent(this FileSystemClient fs, U8Span mountName, U8Span path, ProgramId programId, ContentType type)
private static Result MountContentImpl(FileSystemClient fs, U8Span mountName, U8Span path, ulong id,
ContentType contentType)
{
Result rc = MountHelpers.CheckMountNameAcceptingReservedMountName(mountName);
Result rc = fs.Impl.CheckMountNameAcceptingReservedMountName(mountName);
if (rc.IsFailure()) return rc;
FileSystemProxyType fspType = ConvertToFileSystemProxyType(type);
FileSystemProxyType fsType = ConvertToFileSystemProxyType(contentType);
return MountContentImpl(fs, mountName, path, programId.Value, fspType);
}
if (path.IsNull())
return ResultFs.NullptrArgument.Log();
public static Result MountContent(this FileSystemClient fs, U8Span mountName, U8Span path, DataId dataId, ContentType type)
{
Result rc = MountHelpers.CheckMountNameAcceptingReservedMountName(mountName);
rc = FspPath.FromSpan(out FspPath fsPath, path);
if (rc.IsFailure()) return rc;
FileSystemProxyType fspType = ConvertToFileSystemProxyType(type);
return MountContentImpl(fs, mountName, path, dataId.Value, fspType);
}
private static Result MountContentImpl(FileSystemClient fs, U8Span mountName, U8Span path, ulong id, FileSystemProxyType type)
{
FspPath.FromSpan(out FspPath fsPath, path);
using ReferenceCountedDisposable<IFileSystemProxy> fsProxy = fs.Impl.GetFileSystemProxyServiceObject();
Result rc = fsProxy.Target.OpenFileSystemWithId(out ReferenceCountedDisposable<IFileSystemSf> fileSystem,
in fsPath, id, type);
if (rc.IsFailure()) return rc;
using (fileSystem)
ReferenceCountedDisposable<IFileSystemSf> fileSystem = null;
try
{
var fileSystemAdapter = new FileSystemServiceObjectAdapter(fileSystem);
rc = fsProxy.Target.OpenFileSystemWithId(out fileSystem, in fsPath, id, fsType);
if (rc.IsFailure()) return rc;
var fileSystemAdapter = new FileSystemServiceObjectAdapter(fileSystem);
return fs.Register(mountName, fileSystemAdapter);
}
finally
{
fileSystem?.Dispose();
}
}
private static FileSystemProxyType ConvertToFileSystemProxyType(ContentType type) => type switch
public static Result MountContent(this FileSystemClient fs, U8Span mountName, U8Span path,
ContentType contentType)
{
ContentType.Meta => FileSystemProxyType.Meta,
ContentType.Control => FileSystemProxyType.Control,
ContentType.Manual => FileSystemProxyType.Manual,
ContentType.Data => FileSystemProxyType.Data,
_ => throw new ArgumentOutOfRangeException(nameof(type), type, null)
};
Result rc;
Span<byte> logBuffer = stackalloc byte[0x300];
if (fs.Impl.IsEnabledAccessLog(AccessLogTarget.System))
{
Tick start = fs.Hos.Os.GetSystemTick();
rc = PreMount(contentType);
Tick end = fs.Hos.Os.GetSystemTick();
var idString = new IdString();
var sb = new U8StringBuilder(logBuffer, true);
sb.Append(LogName).Append(mountName).Append(LogQuote)
.Append(LogPath).Append(path).Append(LogQuote)
.Append(LogContentType).Append(idString.ToString(contentType));
fs.Impl.OutputAccessLogUnlessResultSuccess(rc, start, end, null, new U8Span(sb.Buffer));
}
else
{
rc = PreMount(contentType);
}
fs.Impl.AbortIfNeeded(rc);
if (rc.IsFailure()) return rc;
const ulong programId = 0;
if (fs.Impl.IsEnabledAccessLog(AccessLogTarget.System))
{
Tick start = fs.Hos.Os.GetSystemTick();
rc = MountContentImpl(fs, mountName, path, programId, contentType);
Tick end = fs.Hos.Os.GetSystemTick();
var idString = new IdString();
var sb = new U8StringBuilder(logBuffer, true);
sb.Append(LogName).Append(mountName).Append(LogQuote)
.Append(LogPath).Append(path).Append(LogQuote)
.Append(LogProgramId).AppendFormat(programId, 'X')
.Append(LogContentType).Append(idString.ToString(contentType));
fs.Impl.OutputAccessLog(rc, start, end, null, new U8Span(sb.Buffer));
}
else
{
rc = MountContentImpl(fs, mountName, path, 0, contentType);
}
fs.Impl.AbortIfNeeded(rc);
if (rc.IsFailure()) return rc;
if (fs.Impl.IsEnabledAccessLog(AccessLogTarget.System))
fs.Impl.EnableFileSystemAccessorAccessLog(mountName);
return Result.Success;
static Result PreMount(ContentType contentType)
{
if (contentType == ContentType.Meta)
return ResultFs.InvalidArgument.Log();
return Result.Success;
}
}
public static Result MountContent(this FileSystemClient fs, U8Span mountName, U8Span path, ProgramId programId,
ContentType contentType)
{
Result rc;
Span<byte> logBuffer = stackalloc byte[0x300];
if (fs.Impl.IsEnabledAccessLog(AccessLogTarget.System))
{
Tick start = fs.Hos.Os.GetSystemTick();
rc = MountContentImpl(fs, mountName, path, programId.Value, contentType);
Tick end = fs.Hos.Os.GetSystemTick();
var idString = new IdString();
var sb = new U8StringBuilder(logBuffer, true);
sb.Append(LogName).Append(mountName).Append(LogQuote)
.Append(LogPath).Append(path).Append(LogQuote)
.Append(LogProgramId).AppendFormat(programId.Value, 'X')
.Append(LogContentType).Append(idString.ToString(contentType));
logBuffer = sb.Buffer;
fs.Impl.OutputAccessLog(rc, start, end, null, new U8Span(logBuffer));
}
else
{
rc = MountContentImpl(fs, mountName, path, programId.Value, contentType);
}
fs.Impl.AbortIfNeeded(rc);
if (rc.IsFailure()) return rc;
if (fs.Impl.IsEnabledAccessLog(AccessLogTarget.System))
fs.Impl.EnableFileSystemAccessorAccessLog(mountName);
return Result.Success;
}
public static Result MountContent(this FileSystemClient fs, U8Span mountName, U8Span path, DataId dataId,
ContentType contentType)
{
Result rc;
Span<byte> logBuffer = stackalloc byte[0x300];
if (fs.Impl.IsEnabledAccessLog(AccessLogTarget.System))
{
Tick start = fs.Hos.Os.GetSystemTick();
rc = MountContentImpl(fs, mountName, path, dataId.Value, contentType);
Tick end = fs.Hos.Os.GetSystemTick();
var idString = new IdString();
var sb = new U8StringBuilder(logBuffer, true);
sb.Append(LogName).Append(mountName).Append(LogQuote)
.Append(LogPath).Append(path).Append(LogQuote)
.Append(LogDataId).AppendFormat(dataId.Value, 'X')
.Append(LogContentType).Append(idString.ToString(contentType));
fs.Impl.OutputAccessLog(rc, start, end, null, new U8Span(sb.Buffer));
}
else
{
rc = MountContentImpl(fs, mountName, path, dataId.Value, contentType);
}
fs.Impl.AbortIfNeeded(rc);
if (rc.IsFailure()) return rc;
if (fs.Impl.IsEnabledAccessLog(AccessLogTarget.System))
fs.Impl.EnableFileSystemAccessorAccessLog(mountName);
return Result.Success;
}
public static Result MountContent(this FileSystemClient fs, U8Span mountName, ProgramId programId,
ContentType contentType)
{
Result rc;
Span<byte> logBuffer = stackalloc byte[0x300];
if (fs.Impl.IsEnabledAccessLog(AccessLogTarget.System))
{
Tick start = fs.Hos.Os.GetSystemTick();
rc = Mount(fs, mountName, programId, contentType);
Tick end = fs.Hos.Os.GetSystemTick();
var idString = new IdString();
var sb = new U8StringBuilder(logBuffer, true);
sb.Append(LogName).Append(mountName).Append(LogQuote)
.Append(LogProgramId).AppendFormat(programId.Value, 'X')
.Append(LogContentType).Append(idString.ToString(contentType));
fs.Impl.OutputAccessLog(rc, start, end, null, new U8Span(sb.Buffer));
}
else
{
rc = Mount(fs, mountName, programId, contentType);
}
fs.Impl.AbortIfNeeded(rc);
if (rc.IsFailure()) return rc;
if (fs.Impl.IsEnabledAccessLog(AccessLogTarget.System))
fs.Impl.EnableFileSystemAccessorAccessLog(mountName);
return Result.Success;
static Result Mount(FileSystemClient fs, U8Span mountName, ProgramId programId, ContentType contentType)
{
Result rc = fs.Impl.CheckMountNameAcceptingReservedMountName(mountName);
if (rc.IsFailure()) return rc;
FileSystemProxyType fsType = ConvertToFileSystemProxyType(contentType);
using ReferenceCountedDisposable<IFileSystemProxy> fsProxy = fs.Impl.GetFileSystemProxyServiceObject();
ReferenceCountedDisposable<IFileSystemSf> fileSystem = null;
try
{
rc = fsProxy.Target.OpenFileSystemWithPatch(out fileSystem, programId, fsType);
if (rc.IsFailure()) return rc;
var fileSystemAdapter = new FileSystemServiceObjectAdapter(fileSystem);
return fs.Register(mountName, fileSystemAdapter);
}
finally
{
fileSystem?.Dispose();
}
}
}
}
}

View file

@ -1,56 +1,20 @@
using System;
using System.Runtime.CompilerServices;
using LibHac.Common;
using LibHac.Diag;
using LibHac.Fs.Fsa;
using LibHac.Fs.Impl;
using LibHac.FsSrv.Sf;
using LibHac.Os;
using LibHac.Util;
using static LibHac.Fs.Impl.AccessLogStrings;
using IFileSystemSf = LibHac.FsSrv.Sf.IFileSystem;
namespace LibHac.Fs.Shim
{
[SkipLocalsInit]
public static class ContentStorage
{
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;
using ReferenceCountedDisposable<IFileSystemProxy> fsProxy = fs.Impl.GetFileSystemProxyServiceObject();
rc = fsProxy.Target.OpenContentStorageFileSystem(out ReferenceCountedDisposable<IFileSystemSf> contentFs,
storageId);
if (rc.IsFailure()) return rc;
using (contentFs)
{
var mountNameGenerator = new ContentStorageCommonMountNameGenerator(storageId);
var fileSystemAdapter = new FileSystemServiceObjectAdapter(contentFs);
return fs.Register(mountName, fileSystemAdapter, mountNameGenerator);
}
}
public static U8String GetContentStorageMountName(ContentStorageId storageId)
{
switch (storageId)
{
case ContentStorageId.System:
return CommonPaths.ContentStorageSystemMountName;
case ContentStorageId.User:
return CommonPaths.ContentStorageUserMountName;
case ContentStorageId.SdCard:
return CommonPaths.ContentStorageSdCardMountName;
default:
throw new ArgumentOutOfRangeException(nameof(storageId), storageId, null);
}
}
private class ContentStorageCommonMountNameGenerator : ICommonMountNameGenerator
{
private ContentStorageId StorageId { get; }
@ -64,14 +28,115 @@ namespace LibHac.Fs.Shim
public Result GenerateCommonMountName(Span<byte> nameBuffer)
{
U8String mountName = GetContentStorageMountName(StorageId);
// Determine how much space we need.
int neededSize =
StringUtils.GetLength(GetContentStorageMountName(StorageId), PathTool.MountNameLengthMax) + 2;
int length = StringUtils.Copy(nameBuffer, mountName);
nameBuffer[length] = (byte)':';
nameBuffer[length + 1] = 0;
Assert.True(nameBuffer.Length >= neededSize);
// Generate the name.
var sb = new U8StringBuilder(nameBuffer);
sb.Append(GetContentStorageMountName(StorageId))
.Append(StringTraits.DriveSeparator);
Assert.True(sb.Length == neededSize - 1);
return Result.Success;
}
}
public static Result MountContentStorage(this FileSystemClient fs, ContentStorageId storageId)
{
return MountContentStorage(fs, new U8Span(GetContentStorageMountName(storageId)), storageId);
}
public static Result MountContentStorage(this FileSystemClient fs, U8Span mountName, ContentStorageId storageId)
{
Result rc;
Span<byte> logBuffer = stackalloc byte[0x40];
if (fs.Impl.IsEnabledAccessLog(AccessLogTarget.System))
{
Tick start = fs.Hos.Os.GetSystemTick();
rc = Mount(fs, mountName, storageId);
Tick end = fs.Hos.Os.GetSystemTick();
var idString = new IdString();
var sb = new U8StringBuilder(logBuffer, true);
sb.Append(LogName).Append(mountName).Append(LogQuote)
.Append(LogContentStorageId).Append(idString.ToString(storageId));
fs.Impl.OutputAccessLog(rc, start, end, null, new U8Span(sb.Buffer));
}
else
{
rc = Mount(fs, mountName, storageId);
}
fs.Impl.AbortIfNeeded(rc);
if (rc.IsFailure()) return rc;
if (fs.Impl.IsEnabledAccessLog(AccessLogTarget.System))
fs.Impl.EnableFileSystemAccessorAccessLog(mountName);
return Result.Success;
static Result Mount(FileSystemClient fs, U8Span mountName, ContentStorageId storageId)
{
// It can take some time for the system partition to be ready (if it's on the SD card).
// Thus, we will retry up to 10 times, waiting one second each time.
const int maxRetries = 10;
const int retryInterval = 1000;
Result rc = fs.Impl.CheckMountNameAcceptingReservedMountName(mountName);
if (rc.IsFailure()) return rc;
using ReferenceCountedDisposable<IFileSystemProxy> fsProxy = fs.Impl.GetFileSystemProxyServiceObject();
ReferenceCountedDisposable<IFileSystemSf> fileSystem = null;
try
{
for (int i = 0; i < maxRetries; i++)
{
rc = fsProxy.Target.OpenContentStorageFileSystem(out fileSystem, storageId);
if (rc.IsSuccess())
break;
if (!ResultFs.SystemPartitionNotReady.Includes(rc))
return rc;
if (i == maxRetries - 1)
return rc;
fs.Hos.Os.SleepThread(TimeSpan.FromMilliSeconds(retryInterval));
}
var fileSystemAdapter = new FileSystemServiceObjectAdapter(fileSystem);
var mountNameGenerator = new ContentStorageCommonMountNameGenerator(storageId);
return fs.Register(mountName, fileSystemAdapter, mountNameGenerator);
}
finally
{
fileSystem?.Dispose();
}
}
}
public static ReadOnlySpan<byte> GetContentStorageMountName(ContentStorageId storageId)
{
switch (storageId)
{
case ContentStorageId.System:
return CommonMountNames.ContentStorageSystemMountName;
case ContentStorageId.User:
return CommonMountNames.ContentStorageUserMountName;
case ContentStorageId.SdCard:
return CommonMountNames.ContentStorageSdCardMountName;
default:
Abort.UnexpectedDefault();
return default;
}
}
}
}

View file

@ -1,4 +1,5 @@
using System;
using System.Runtime.CompilerServices;
using LibHac.Common;
using LibHac.Diag;
using LibHac.Fs.Fsa;
@ -8,31 +9,9 @@ using IFileSystemSf = LibHac.FsSrv.Sf.IFileSystem;
namespace LibHac.Fs.Shim
{
[SkipLocalsInit]
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;
ReferenceCountedDisposable<IFileSystemSf> customFs = null;
try
{
using ReferenceCountedDisposable<IFileSystemProxy> fsProxy = fs.Impl.GetFileSystemProxyServiceObject();
rc = fsProxy.Target.OpenCustomStorageFileSystem(out customFs, storageId);
if (rc.IsFailure()) return rc;
var adapter = new FileSystemServiceObjectAdapter(customFs);
return fs.Register(mountName, adapter);
}
finally
{
customFs?.Dispose();
}
}
public static U8Span GetCustomStorageDirectoryName(CustomStorageId storageId)
{
switch (storageId)
@ -46,11 +25,33 @@ namespace LibHac.Fs.Shim
}
}
private static ReadOnlySpan<byte> CustomStorageDirectoryName => // CustomStorage0
public static Result MountCustomStorage(this FileSystemClient fs, U8Span mountName, CustomStorageId storageId)
{
Result rc = fs.Impl.CheckMountName(mountName);
if (rc.IsFailure()) return rc;
ReferenceCountedDisposable<IFileSystemSf> fileSystem = null;
try
{
using ReferenceCountedDisposable<IFileSystemProxy> fsProxy = fs.Impl.GetFileSystemProxyServiceObject();
rc = fsProxy.Target.OpenCustomStorageFileSystem(out fileSystem, storageId);
if (rc.IsFailure()) return rc;
var fileSystemAdapter = new FileSystemServiceObjectAdapter(fileSystem);
return fs.Register(mountName, fileSystemAdapter);
}
finally
{
fileSystem?.Dispose();
}
}
private static ReadOnlySpan<byte> CustomStorageDirectoryName => // "CustomStorage0"
new[]
{
(byte) 'C', (byte) 'u', (byte) 's', (byte) 't', (byte) 'o', (byte) 'm', (byte) 'S', (byte) 't',
(byte) 'o', (byte) 'r', (byte) 'a', (byte) 'g', (byte) 'e', (byte) '0'
(byte)'C', (byte)'u', (byte)'s', (byte)'t', (byte)'o', (byte)'m', (byte)'S', (byte)'t',
(byte)'o', (byte)'r', (byte)'a', (byte)'g', (byte)'e', (byte)'0'
};
}
}

View file

@ -1,5 +1,4 @@
using System;
using LibHac.Common;
using LibHac.Common;
using LibHac.FsSrv.Sf;
namespace LibHac.Fs.Shim
@ -20,11 +19,6 @@ namespace LibHac.Fs.Shim
public static class FileSystemProxyServiceObject
{
private static bool HasFileSystemServer(FileSystemClientImpl fs)
{
return fs.Hos is not null;
}
public static ReferenceCountedDisposable<IFileSystemProxy> GetFileSystemProxyServiceObject(
this FileSystemClientImpl fs)
{
@ -49,11 +43,6 @@ namespace LibHac.Fs.Shim
if (dfcServiceObject is not null)
return dfcServiceObject.AddReference();
if (!HasFileSystemServer(fs))
{
throw new InvalidOperationException("Client was not initialized with a server object.");
}
Result rc = fs.Hos.Sm.GetService(out ReferenceCountedDisposable<IFileSystemProxy> fsProxy, "fsp-srv");
if (rc.IsFailure())
@ -83,11 +72,6 @@ namespace LibHac.Fs.Shim
private static ReferenceCountedDisposable<IFileSystemProxyForLoader>
GetFileSystemProxyForLoaderServiceObjectImpl(FileSystemClientImpl fs)
{
if (!HasFileSystemServer(fs))
{
throw new InvalidOperationException("Client was not initialized with a server object.");
}
Result rc = fs.Hos.Sm.GetService(out ReferenceCountedDisposable<IFileSystemProxyForLoader> fsProxy,
"fsp-ldr");
@ -118,11 +102,6 @@ namespace LibHac.Fs.Shim
private static ReferenceCountedDisposable<IProgramRegistry> GetProgramRegistryServiceObjectImpl(
FileSystemClientImpl fs)
{
if (!HasFileSystemServer(fs))
{
throw new InvalidOperationException("Client was not initialized with a server object.");
}
Result rc = fs.Hos.Sm.GetService(out ReferenceCountedDisposable<IProgramRegistry> registry, "fsp-pr");
if (rc.IsFailure())

View file

@ -1,97 +1,32 @@
using System;
using System.Runtime.CompilerServices;
using LibHac.Common;
using LibHac.Diag;
using LibHac.Fs.Fsa;
using LibHac.Fs.Impl;
using LibHac.FsSrv;
using LibHac.FsSrv.Sf;
using LibHac.Os;
using LibHac.Util;
using static LibHac.Fs.Impl.AccessLogStrings;
using IFileSystemSf = LibHac.FsSrv.Sf.IFileSystem;
using IStorageSf = LibHac.FsSrv.Sf.IStorage;
namespace LibHac.Fs.Shim
{
[SkipLocalsInit]
public static class GameCard
{
public static Result GetGameCardHandle(this FileSystemClient fs, out GameCardHandle handle)
private static ReadOnlySpan<byte> GetGameCardMountNameSuffix(GameCardPartition partition)
{
handle = default;
ReferenceCountedDisposable<IDeviceOperator> deviceOperator = null;
try
switch (partition)
{
using ReferenceCountedDisposable<IFileSystemProxy> fsProxy = fs.Impl.GetFileSystemProxyServiceObject();
Result rc = fsProxy.Target.OpenDeviceOperator(out deviceOperator);
if (rc.IsFailure()) return rc;
return deviceOperator.Target.GetGameCardHandle(out handle);
}
finally
{
deviceOperator?.Dispose();
}
}
public static bool IsGameCardInserted(this FileSystemClient fs)
{
ReferenceCountedDisposable<IDeviceOperator> deviceOperator = null;
try
{
using ReferenceCountedDisposable<IFileSystemProxy> fsProxy = fs.Impl.GetFileSystemProxyServiceObject();
Result rc = fsProxy.Target.OpenDeviceOperator(out deviceOperator);
if (rc.IsFailure()) throw new LibHacException("Abort");
rc = deviceOperator.Target.IsGameCardInserted(out bool isInserted);
if (rc.IsFailure()) throw new LibHacException("Abort");
return isInserted;
}
finally
{
deviceOperator?.Dispose();
}
}
public static Result OpenGameCardPartition(this FileSystemClient fs, out IStorage storage,
GameCardHandle handle, GameCardPartitionRaw partitionType)
{
storage = default;
ReferenceCountedDisposable<IStorageSf> sfStorage = null;
try
{
using ReferenceCountedDisposable<IFileSystemProxy> fsProxy = fs.Impl.GetFileSystemProxyServiceObject();
Result rc = fsProxy.Target.OpenGameCardStorage(out sfStorage, handle, partitionType);
if (rc.IsFailure()) return rc;
storage = new StorageServiceObjectAdapter(sfStorage);
return Result.Success;
}
finally
{
sfStorage?.Dispose();
}
}
public static Result MountGameCardPartition(this FileSystemClient fs, U8Span mountName, GameCardHandle handle,
GameCardPartition partitionId)
{
Result rc = MountHelpers.CheckMountNameAcceptingReservedMountName(mountName);
if (rc.IsFailure()) return rc;
using ReferenceCountedDisposable<IFileSystemProxy> fsProxy = fs.Impl.GetFileSystemProxyServiceObject();
rc = fsProxy.Target.OpenGameCardFileSystem(out ReferenceCountedDisposable<IFileSystemSf> cardFs, handle,
partitionId);
if (rc.IsFailure()) return rc;
using (cardFs)
{
var mountNameGenerator = new GameCardCommonMountNameGenerator(handle, partitionId);
var fileSystemAdapter = new FileSystemServiceObjectAdapter(cardFs);
return fs.Register(mountName, fileSystemAdapter, mountNameGenerator);
case GameCardPartition.Update: return CommonMountNames.GameCardFileSystemMountNameUpdateSuffix;
case GameCardPartition.Normal: return CommonMountNames.GameCardFileSystemMountNameNormalSuffix;
case GameCardPartition.Secure: return CommonMountNames.GameCardFileSystemMountNameSecureSuffix;
default:
Abort.UnexpectedDefault();
return default;
}
}
@ -110,24 +45,153 @@ namespace LibHac.Fs.Shim
public Result GenerateCommonMountName(Span<byte> nameBuffer)
{
char letter = GetGameCardMountNameSuffix(PartitionId);
int handleDigitCount = Unsafe.SizeOf<GameCardHandle>() * 2;
string mountName = $"{CommonPaths.GameCardFileSystemMountName}{letter}{Handle.Value:x8}";
new U8Span(mountName).Value.CopyTo(nameBuffer);
// Determine how much space we need.
int neededSize =
StringUtils.GetLength(CommonMountNames.GameCardFileSystemMountName, PathTool.MountNameLengthMax) +
StringUtils.GetLength(GetGameCardMountNameSuffix(PartitionId), PathTool.MountNameLengthMax) +
handleDigitCount + 2;
Assert.True(nameBuffer.Length >= neededSize);
// Generate the name.
var sb = new U8StringBuilder(nameBuffer);
sb.Append(CommonMountNames.GameCardFileSystemMountName)
.Append(GetGameCardMountNameSuffix(PartitionId))
.AppendFormat(Handle.Value, 'x', (byte)handleDigitCount)
.Append(StringTraits.DriveSeparator);
Assert.True(sb.Length == neededSize - 1);
return Result.Success;
}
}
private static char GetGameCardMountNameSuffix(GameCardPartition partition)
public static Result GetGameCardHandle(this FileSystemClient fs, out GameCardHandle handle)
{
Unsafe.SkipInit(out handle);
using ReferenceCountedDisposable<IFileSystemProxy> fsProxy = fs.Impl.GetFileSystemProxyServiceObject();
ReferenceCountedDisposable<IDeviceOperator> deviceOperator = null;
try
{
switch (partition)
Result rc = fsProxy.Target.OpenDeviceOperator(out deviceOperator);
fs.Impl.AbortIfNeeded(rc);
if (rc.IsFailure()) return rc;
rc = deviceOperator.Target.GetGameCardHandle(out handle);
fs.Impl.AbortIfNeeded(rc);
return rc;
}
finally
{
deviceOperator?.Dispose();
}
}
public static Result MountGameCardPartition(this FileSystemClient fs, U8Span mountName, GameCardHandle handle,
GameCardPartition partitionId)
{
Result rc;
Span<byte> logBuffer = stackalloc byte[0x60];
if (fs.Impl.IsEnabledAccessLog(AccessLogTarget.System))
{
Tick start = fs.Hos.Os.GetSystemTick();
rc = Mount(fs, mountName, handle, partitionId);
Tick end = fs.Hos.Os.GetSystemTick();
var idString = new IdString();
var sb = new U8StringBuilder(logBuffer, true);
sb.Append(LogName).Append(mountName).Append(LogQuote)
.Append(LogGameCardHandle).AppendFormat(handle.Value)
.Append(LogGameCardPartition).Append(idString.ToString(partitionId));
fs.Impl.OutputAccessLog(rc, start, end, null, new U8Span(sb.Buffer));
}
else
{
rc = Mount(fs, mountName, handle, partitionId);
}
fs.Impl.AbortIfNeeded(rc);
if (rc.IsFailure()) return rc;
if (fs.Impl.IsEnabledAccessLog(AccessLogTarget.System))
fs.Impl.EnableFileSystemAccessorAccessLog(mountName);
return Result.Success;
static Result Mount(FileSystemClient fs, U8Span mountName, GameCardHandle handle,
GameCardPartition partitionId)
{
Result rc = fs.Impl.CheckMountNameAcceptingReservedMountName(mountName);
if (rc.IsFailure()) return rc;
using ReferenceCountedDisposable<IFileSystemProxy> fsProxy = fs.Impl.GetFileSystemProxyServiceObject();
ReferenceCountedDisposable<IFileSystemSf> fileSystem = null;
try
{
case GameCardPartition.Update: return 'U';
case GameCardPartition.Normal: return 'N';
case GameCardPartition.Secure: return 'S';
default:
throw new ArgumentOutOfRangeException(nameof(partition), partition, null);
rc = fsProxy.Target.OpenGameCardFileSystem(out fileSystem, handle, partitionId);
if (rc.IsFailure()) return rc;
var fileSystemAdapter = new FileSystemServiceObjectAdapter(fileSystem);
var mountNameGenerator = new GameCardCommonMountNameGenerator(handle, partitionId);
return fs.Register(mountName, fileSystemAdapter, mountNameGenerator);
}
finally
{
fileSystem?.Dispose();
}
}
}
public static bool IsGameCardInserted(this FileSystemClient fs)
{
using ReferenceCountedDisposable<IFileSystemProxy> fsProxy = fs.Impl.GetFileSystemProxyServiceObject();
ReferenceCountedDisposable<IDeviceOperator> deviceOperator = null;
try
{
Result rc = fsProxy.Target.OpenDeviceOperator(out deviceOperator);
fs.Impl.LogErrorMessage(rc);
Abort.DoAbortUnless(rc.IsSuccess());
rc = deviceOperator.Target.IsGameCardInserted(out bool isInserted);
fs.Impl.LogErrorMessage(rc);
Abort.DoAbortUnless(rc.IsSuccess());
return isInserted;
}
finally
{
deviceOperator?.Dispose();
}
}
public static Result OpenGameCardPartition(this FileSystemClient fs, out IStorage storage,
GameCardHandle handle, GameCardPartitionRaw partitionType)
{
storage = default;
using ReferenceCountedDisposable<IFileSystemProxy> fsProxy = fs.Impl.GetFileSystemProxyServiceObject();
ReferenceCountedDisposable<IStorageSf> sfStorage = null;
try
{
Result rc = fsProxy.Target.OpenGameCardStorage(out sfStorage, handle, partitionType);
fs.Impl.AbortIfNeeded(rc);
if (rc.IsFailure()) return rc;
storage = new StorageServiceObjectAdapter(sfStorage);
return Result.Success;
}
finally
{
sfStorage?.Dispose();
}
}
}

View file

@ -1,13 +1,15 @@
using System;
using System.Diagnostics;
using System.Runtime.CompilerServices;
using LibHac.Common;
using LibHac.Diag;
using LibHac.Fs.Fsa;
using LibHac.Fs.Impl;
using LibHac.FsSrv.Sf;
using LibHac.FsSystem;
using LibHac.Os;
using LibHac.Util;
using static LibHac.Fs.CommonPaths;
using static LibHac.Fs.Impl.AccessLogStrings;
using static LibHac.Fs.Impl.CommonMountNames;
using IFileSystem = LibHac.Fs.Fsa.IFileSystem;
using IFileSystemSf = LibHac.FsSrv.Sf.IFileSystem;
@ -16,14 +18,53 @@ namespace LibHac.Fs.Shim
/// <summary>
/// Contains functions for mounting file systems from a host computer.
/// </summary>
/// <remarks>Based on nnSdk 9.3.0</remarks>
/// <remarks>Based on nnSdk 11.4.0</remarks>
[SkipLocalsInit]
public static class Host
{
private static ReadOnlySpan<byte> HostRootFileSystemPath => new[]
{(byte) '@', (byte) 'H', (byte) 'o', (byte) 's', (byte) 't', (byte) ':', (byte) '/'};
private static ReadOnlySpan<byte> HostRootFileSystemPath => // "@Host:/"
new[] { (byte)'@', (byte)'H', (byte)'o', (byte)'s', (byte)'t', (byte)':', (byte)'/' };
private const int HostRootFileSystemPathLength = 8;
/// <summary>
/// Opens a host file system via <see cref="IFileSystemProxy"/>.
/// </summary>
/// <param name="fs">The <see cref="FileSystemClient"/> to use.</param>
/// <param name="fileSystem">If successful, the opened host file system.</param>
/// <param name="path">The path on the host computer to open. e.g. /C:\Windows\System32/</param>
/// <param name="option">Options for opening the host file system.</param>
/// <returns>The <see cref="Result"/> of the operation.</returns>
private static Result OpenHostFileSystemImpl(FileSystemClient fs, out IFileSystem fileSystem, in FspPath path,
MountHostOption option)
{
fileSystem = default;
using ReferenceCountedDisposable<IFileSystemProxy> fsProxy = fs.Impl.GetFileSystemProxyServiceObject();
ReferenceCountedDisposable<IFileSystemSf> hostFs = null;
try
{
if (option.Flags != MountHostOptionFlag.None)
{
Result rc = fsProxy.Target.OpenHostFileSystemWithOption(out hostFs, in path, option);
if (rc.IsFailure()) return rc;
}
else
{
Result rc = fsProxy.Target.OpenHostFileSystem(out hostFs, in path);
if (rc.IsFailure()) return rc;
}
fileSystem = new FileSystemServiceObjectAdapter(hostFs);
return Result.Success;
}
finally
{
hostFs?.Dispose();
}
}
private class HostCommonMountNameGenerator : ICommonMountNameGenerator
{
private FsPath _path;
@ -43,7 +84,8 @@ namespace LibHac.Fs.Shim
public Result GenerateCommonMountName(Span<byte> nameBuffer)
{
int requiredNameBufferSize = StringUtils.GetLength(_path.Str, FsPath.MaxLength) + HostRootFileSystemPathLength;
int requiredNameBufferSize =
StringUtils.GetLength(_path.Str, FsPath.MaxLength) + HostRootFileSystemPathLength;
if (nameBuffer.Length < requiredNameBufferSize)
return ResultFs.TooLongPath.Log();
@ -51,7 +93,7 @@ namespace LibHac.Fs.Shim
var sb = new U8StringBuilder(nameBuffer);
sb.Append(HostRootFileSystemPath).Append(_path.Str);
Debug.Assert(sb.Length == requiredNameBufferSize - 1);
Assert.True(sb.Length == requiredNameBufferSize - 1);
return Result.Success;
}
@ -65,226 +107,17 @@ namespace LibHac.Fs.Shim
{
const int requiredNameBufferSize = HostRootFileSystemPathLength;
Debug.Assert(nameBuffer.Length >= requiredNameBufferSize);
Assert.True(nameBuffer.Length >= requiredNameBufferSize);
// ReSharper disable once RedundantAssignment
int size = StringUtils.Copy(nameBuffer, HostRootFileSystemPath);
Debug.Assert(size == requiredNameBufferSize - 1);
var sb = new U8StringBuilder(nameBuffer);
sb.Append(HostRootFileSystemPath);
Assert.True(sb.Length == requiredNameBufferSize - 1);
return Result.Success;
}
}
/// <summary>
/// Mounts the C:\ drive of a host Windows computer at @Host:/
/// </summary>
/// <param name="fs">The <see cref="FileSystemClient"/> to use.</param>
/// <returns>The <see cref="Result"/> of the operation.</returns>
public static Result MountHostRoot(this FileSystemClient fs)
{
IFileSystem hostFileSystem = default;
FspPath.CreateEmpty(out FspPath path);
static string LogMessageGenerator() => $", name: \"{HostRootFileSystemMountName.ToString()}\"";
Result OpenHostFs() => OpenHostFileSystemImpl(fs, out hostFileSystem, in path, MountHostOption.None);
Result MountHostFs() => fs.Register(HostRootFileSystemMountName, hostFileSystem,
new HostRootCommonMountNameGenerator());
// Open the host file system
Result result =
fs.RunOperationWithAccessLogOnFailure(AccessLogTarget.Application, OpenHostFs, LogMessageGenerator);
if (result.IsFailure()) return result;
// Mount the host file system
result = fs.RunOperationWithAccessLog(AccessLogTarget.Application, MountHostFs, LogMessageGenerator);
if (result.IsFailure()) return result;
if (fs.IsEnabledAccessLog(AccessLogTarget.Application))
fs.EnableFileSystemAccessorAccessLog(HostRootFileSystemMountName);
return Result.Success;
}
/// <summary>
/// Mounts the C:\ drive of a host Windows computer at @Host:/
/// </summary>
/// <param name="fs">The <see cref="FileSystemClient"/> to use.</param>
/// <param name="option">Options for mounting the host file system.</param>
/// <returns>The <see cref="Result"/> of the operation.</returns>
public static Result MountHostRoot(this FileSystemClient fs, MountHostOption option)
{
IFileSystem hostFileSystem = default;
FspPath.CreateEmpty(out FspPath path);
string LogMessageGenerator() =>
$", name: \"{HostRootFileSystemMountName.ToString()}, mount_host_option: {option}\"";
Result OpenHostFs() => OpenHostFileSystemImpl(fs, out hostFileSystem, in path, option);
Result MountHostFs() => fs.Register(HostRootFileSystemMountName, hostFileSystem,
new HostRootCommonMountNameGenerator());
// Open the host file system
Result result =
fs.RunOperationWithAccessLogOnFailure(AccessLogTarget.Application, OpenHostFs, LogMessageGenerator);
if (result.IsFailure()) return result;
// Mount the host file system
result = fs.RunOperationWithAccessLog(AccessLogTarget.Application, MountHostFs, LogMessageGenerator);
if (result.IsFailure()) return result;
if (fs.IsEnabledAccessLog(AccessLogTarget.Application))
fs.EnableFileSystemAccessorAccessLog(HostRootFileSystemMountName);
return Result.Success;
}
/// <summary>
/// Unmounts the file system at @Host:/
/// </summary>
/// <param name="fs">The <see cref="FileSystemClient"/> to use.</param>
public static void UnmountHostRoot(this FileSystemClient fs)
{
fs.Unmount(HostRootFileSystemMountName);
}
/// <summary>
/// Mounts a directory on a host Windows computer at the specified mount point.
/// </summary>
/// <param name="fs">The <see cref="FileSystemClient"/> to use.</param>
/// <param name="mountName">The mount name at which the file system will be mounted.</param>
/// <param name="path">The path on the host computer to mount. e.g. C:\Windows\System32</param>
/// <returns>The <see cref="Result"/> of the operation.</returns>
public static Result MountHost(this FileSystemClient fs, U8Span mountName, U8Span path)
{
return MountHostImpl(fs, mountName, path, null);
}
/// <summary>
/// Mounts a directory on a host Windows computer at the specified mount point.
/// </summary>
/// <param name="fs">The <see cref="FileSystemClient"/> to use.</param>
/// <param name="mountName">The mount name at which the file system will be mounted.</param>
/// <param name="path">The path on the host computer to mount. e.g. C:\Windows\System32</param>
/// <param name="option">Options for mounting the host file system.</param>
/// <returns>The <see cref="Result"/> of the operation.</returns>
public static Result MountHost(this FileSystemClient fs, U8Span mountName, U8Span path, MountHostOption option)
{
return MountHostImpl(fs, mountName, path, option);
}
/// <summary>
/// Mounts a directory on a host Windows computer at the specified mount point.
/// </summary>
/// <param name="fs">The <see cref="FileSystemClient"/> to use.</param>
/// <param name="mountName">The mount name at which the file system will be mounted.</param>
/// <param name="path">The path on the host computer to mount. e.g. C:\Windows\System32</param>
/// <param name="optionalOption">Options for mounting the host file system. Specifying this parameter is optional.</param>
/// <param name="caller">The caller of this function.</param>
/// <returns>The <see cref="Result"/> of the operation.</returns>
private static Result MountHostImpl(this FileSystemClient fs, U8Span mountName, U8Span path,
MountHostOption? optionalOption, [CallerMemberName] string caller = "")
{
Result rc;
ICommonMountNameGenerator nameGenerator;
string logMessage = null;
var option = MountHostOption.None;
// Set the mount option if it was specified
if (optionalOption.HasValue)
{
option = optionalOption.Value;
}
if (fs.IsEnabledAccessLog(AccessLogTarget.Application))
{
if (optionalOption.HasValue)
{
logMessage = $", name: \"{mountName.ToString()}\", mount_host_option: {option}";
}
else
{
logMessage = $", name: \"{mountName.ToString()}\"";
}
System.TimeSpan startTime = fs.Time.GetCurrent();
rc = PreMountHost(out nameGenerator, mountName, path);
System.TimeSpan endTime = fs.Time.GetCurrent();
fs.OutputAccessLogUnlessResultSuccess(rc, startTime, endTime, logMessage, caller);
}
else
{
rc = PreMountHost(out nameGenerator, mountName, path);
}
if (rc.IsFailure()) return rc;
IFileSystem hostFileSystem;
if (fs.IsEnabledAccessLog(AccessLogTarget.Application))
{
System.TimeSpan startTime = fs.Time.GetCurrent();
rc = OpenHostFileSystem(fs, out hostFileSystem, mountName, path, option);
System.TimeSpan endTime = fs.Time.GetCurrent();
fs.OutputAccessLogUnlessResultSuccess(rc, startTime, endTime, logMessage, caller);
}
else
{
rc = OpenHostFileSystem(fs, out hostFileSystem, mountName, path, option);
}
if (rc.IsFailure()) return rc;
if (fs.IsEnabledAccessLog(AccessLogTarget.Application))
{
System.TimeSpan startTime = fs.Time.GetCurrent();
rc = fs.Register(mountName, hostFileSystem, nameGenerator);
System.TimeSpan endTime = fs.Time.GetCurrent();
fs.OutputAccessLog(rc, startTime, endTime, logMessage, caller);
}
else
{
rc = fs.Register(mountName, hostFileSystem, nameGenerator);
}
if (rc.IsFailure()) return rc;
if (fs.IsEnabledAccessLog(AccessLogTarget.Application))
{
fs.EnableFileSystemAccessorAccessLog(mountName);
}
return Result.Success;
}
/// <summary>
/// Creates an <see cref="ICommonMountNameGenerator"/> based on the <paramref name="mountName"/> and
/// <paramref name="path"/>, and verifies the <paramref name="mountName"/>.
/// </summary>
/// <param name="nameGenerator">If successful, the created <see cref="ICommonMountNameGenerator"/>.</param>
/// <param name="mountName">The mount name at which the file system will be mounted.</param>
/// <param name="path">The path that will be opened on the host computer. e.g. C:\Windows\System32</param>
/// <returns>The <see cref="Result"/> of the operation.</returns>
private static Result PreMountHost(out ICommonMountNameGenerator nameGenerator, U8Span mountName, U8Span path)
{
nameGenerator = default;
Result rc = MountHelpers.CheckMountName(mountName);
if (rc.IsFailure()) return rc;
if (path.IsNull())
return ResultFs.NullptrArgument.Log();
nameGenerator = new HostCommonMountNameGenerator(path);
return Result.Success;
}
/// <summary>
/// Verifies parameters and opens a host file system.
/// </summary>
@ -308,7 +141,7 @@ namespace LibHac.Fs.Shim
if (PathUtility.IsWindowsDrive(mountName))
return ResultFs.InvalidMountName.Log();
if (MountHelpers.IsReservedMountName(mountName))
if (fs.Impl.IsUsedReservedMountName(mountName))
return ResultFs.InvalidMountName.Log();
bool needsTrailingSeparator = false;
@ -345,47 +178,337 @@ namespace LibHac.Fs.Shim
}
}
FspPath.FromSpan(out FspPath sfPath, fullPath.Str);
Result rc = FspPath.FromSpan(out FspPath sfPath, fullPath.Str);
if (rc.IsFailure()) return rc;
return OpenHostFileSystemImpl(fs, out fileSystem, in sfPath, option);
}
/// <summary>
/// Opens a host file system via <see cref="IFileSystemProxy"/>.
/// Creates a <see cref="HostCommonMountNameGenerator"/> based on the <paramref name="mountName"/> and
/// <paramref name="path"/>, and verifies the <paramref name="mountName"/>.
/// </summary>
/// <param name="fs">The <see cref="FileSystemClient"/> to use.</param>
/// <param name="fileSystem">If successful, the opened host file system.</param>
/// <param name="path">The path on the host computer to open. e.g. /C:\Windows\System32/</param>
/// <param name="option">Options for opening the host file system.</param>
/// <param name="nameGenerator">If successful, the created <see cref="ICommonMountNameGenerator"/>.</param>
/// <param name="mountName">The mount name at which the file system will be mounted.</param>
/// <param name="path">The path that will be opened on the host computer. e.g. C:\Windows\System32</param>
/// <returns>The <see cref="Result"/> of the operation.</returns>
private static Result OpenHostFileSystemImpl(FileSystemClient fs, out IFileSystem fileSystem, in FspPath path,
MountHostOption option)
private static Result PreMountHost(FileSystemClient fs, out HostCommonMountNameGenerator nameGenerator,
U8Span mountName, U8Span path)
{
fileSystem = default;
nameGenerator = default;
using ReferenceCountedDisposable<IFileSystemProxy> fsProxy = fs.Impl.GetFileSystemProxyServiceObject();
ReferenceCountedDisposable<IFileSystemSf> hostFs = null;
Result rc = fs.Impl.CheckMountName(mountName);
if (rc.IsFailure()) return rc;
try
if (path.IsNull())
return ResultFs.NullptrArgument.Log();
nameGenerator = new HostCommonMountNameGenerator(path);
return Result.Success;
}
/// <summary>
/// Mounts a directory on a host Windows computer at the specified mount point.
/// </summary>
/// <param name="fs">The <see cref="FileSystemClient"/> to use.</param>
/// <param name="mountName">The mount name at which the file system will be mounted.</param>
/// <param name="path">The path on the host computer to mount. e.g. C:\Windows\System32</param>
/// <returns>The <see cref="Result"/> of the operation.</returns>
public static Result MountHost(this FileSystemClient fs, U8Span mountName, U8Span path)
{
Result rc;
Span<byte> logBuffer = stackalloc byte[0x300];
HostCommonMountNameGenerator mountNameGenerator;
if (fs.Impl.IsEnabledAccessLog(AccessLogTarget.Application))
{
if (option == MountHostOption.None)
{
Result rc = fsProxy.Target.OpenHostFileSystem(out hostFs, in path);
if (rc.IsFailure()) return rc;
}
else
{
Result rc = fsProxy.Target.OpenHostFileSystemWithOption(out hostFs, in path, option);
if (rc.IsFailure()) return rc;
}
Tick start = fs.Hos.Os.GetSystemTick();
rc = PreMountHost(fs, out mountNameGenerator, mountName, path);
Tick end = fs.Hos.Os.GetSystemTick();
fileSystem = new FileSystemServiceObjectAdapter(hostFs);
return Result.Success;
var sb = new U8StringBuilder(logBuffer, true);
sb.Append(LogName).Append(mountName).Append(LogQuote)
.Append(LogRootPath).Append(path).Append(LogQuote);
logBuffer = sb.Buffer;
fs.Impl.OutputAccessLogUnlessResultSuccess(rc, start, end, null, new U8Span(logBuffer));
}
finally
else
{
hostFs?.Dispose();
rc = PreMountHost(fs, out mountNameGenerator, mountName, path);
}
fs.Impl.AbortIfNeeded(rc);
if (rc.IsFailure()) return rc;
IFileSystem fileSystem;
if (fs.Impl.IsEnabledAccessLog(AccessLogTarget.Application))
{
Tick start = fs.Hos.Os.GetSystemTick();
rc = OpenHostFileSystem(fs, out fileSystem, mountName, path, MountHostOption.None);
Tick end = fs.Hos.Os.GetSystemTick();
fs.Impl.OutputAccessLogUnlessResultSuccess(rc, start, end, null, new U8Span(logBuffer));
}
else
{
rc = OpenHostFileSystem(fs, out fileSystem, mountName, path, MountHostOption.None);
}
// No AbortIfNeeded here
if (rc.IsFailure()) return rc;
if (fs.Impl.IsEnabledAccessLog(AccessLogTarget.Application))
{
Tick start = fs.Hos.Os.GetSystemTick();
rc = fs.Register(mountName, fileSystem, mountNameGenerator);
Tick end = fs.Hos.Os.GetSystemTick();
fs.Impl.OutputAccessLog(rc, start, end, null, new U8Span(logBuffer));
}
else
{
rc = fs.Register(mountName, fileSystem, mountNameGenerator);
}
fs.Impl.AbortIfNeeded(rc);
if (rc.IsFailure()) return rc;
if (fs.Impl.IsEnabledAccessLog(AccessLogTarget.Application))
fs.Impl.EnableFileSystemAccessorAccessLog(mountName);
return Result.Success;
}
/// <summary>
/// Mounts a directory on a host Windows computer at the specified mount point.
/// </summary>
/// <param name="fs">The <see cref="FileSystemClient"/> to use.</param>
/// <param name="mountName">The mount name at which the file system will be mounted.</param>
/// <param name="path">The path on the host computer to mount. e.g. C:\Windows\System32</param>
/// <param name="option">Options for mounting the host file system.</param>
/// <returns>The <see cref="Result"/> of the operation.</returns>
public static Result MountHost(this FileSystemClient fs, U8Span mountName, U8Span path, MountHostOption option)
{
Result rc;
Span<byte> logBuffer = stackalloc byte[0x300];
HostCommonMountNameGenerator mountNameGenerator;
if (fs.Impl.IsEnabledAccessLog(AccessLogTarget.Application))
{
Tick start = fs.Hos.Os.GetSystemTick();
rc = PreMountHost(fs, out mountNameGenerator, mountName, path);
Tick end = fs.Hos.Os.GetSystemTick();
var idString = new IdString();
var sb = new U8StringBuilder(logBuffer, true);
sb.Append(LogName).Append(mountName).Append(LogQuote)
.Append(LogRootPath).Append(path).Append(LogQuote)
.Append(LogMountHostOption).Append(idString.ToString(option));
logBuffer = sb.Buffer;
fs.Impl.OutputAccessLogUnlessResultSuccess(rc, start, end, null, new U8Span(logBuffer));
}
else
{
rc = PreMountHost(fs, out mountNameGenerator, mountName, path);
}
fs.Impl.AbortIfNeeded(rc);
if (rc.IsFailure()) return rc;
IFileSystem fileSystem;
if (fs.Impl.IsEnabledAccessLog(AccessLogTarget.Application))
{
Tick start = fs.Hos.Os.GetSystemTick();
rc = OpenHostFileSystem(fs, out fileSystem, mountName, path, option);
Tick end = fs.Hos.Os.GetSystemTick();
fs.Impl.OutputAccessLogUnlessResultSuccess(rc, start, end, null, new U8Span(logBuffer));
}
else
{
rc = OpenHostFileSystem(fs, out fileSystem, mountName, path, MountHostOption.None);
}
// No AbortIfNeeded here
if (rc.IsFailure()) return rc;
if (fs.Impl.IsEnabledAccessLog(AccessLogTarget.Application))
{
Tick start = fs.Hos.Os.GetSystemTick();
rc = fs.Register(mountName, fileSystem, mountNameGenerator);
Tick end = fs.Hos.Os.GetSystemTick();
fs.Impl.OutputAccessLog(rc, start, end, null, new U8Span(logBuffer));
}
else
{
rc = fs.Register(mountName, fileSystem, mountNameGenerator);
}
fs.Impl.AbortIfNeeded(rc);
if (rc.IsFailure()) return rc;
if (fs.Impl.IsEnabledAccessLog(AccessLogTarget.Application))
fs.Impl.EnableFileSystemAccessorAccessLog(mountName);
return Result.Success;
}
/// <summary>
/// Mounts the C:\ drive of a host Windows computer at @Host:/
/// </summary>
/// <param name="fs">The <see cref="FileSystemClient"/> to use.</param>
/// <returns>The <see cref="Result"/> of the operation.</returns>
public static Result MountHostRoot(this FileSystemClient fs)
{
Result rc;
Span<byte> logBuffer = stackalloc byte[0x30];
IFileSystem fileSystem;
FspPath.CreateEmpty(out FspPath sfPath);
if (fs.Impl.IsEnabledAccessLog(AccessLogTarget.Application))
{
Tick start = fs.Hos.Os.GetSystemTick();
rc = OpenHostFileSystemImpl(fs, out fileSystem, in sfPath, MountHostOption.None);
Tick end = fs.Hos.Os.GetSystemTick();
var sb = new U8StringBuilder(logBuffer, true);
sb.Append(LogName).Append(HostRootFileSystemMountName).Append(LogQuote);
logBuffer = sb.Buffer;
fs.Impl.OutputAccessLogUnlessResultSuccess(rc, start, end, null, new U8Span(logBuffer));
}
else
{
rc = OpenHostFileSystemImpl(fs, out fileSystem, in sfPath, MountHostOption.None);
}
// No AbortIfNeeded here
if (rc.IsFailure()) return rc;
if (fs.Impl.IsEnabledAccessLog(AccessLogTarget.Application))
{
Tick start = fs.Hos.Os.GetSystemTick();
rc = MountHostFs(fs, fileSystem);
Tick end = fs.Hos.Os.GetSystemTick();
fs.Impl.OutputAccessLog(rc, start, end, null, new U8Span(logBuffer));
}
else
{
rc = MountHostFs(fs, fileSystem);
}
fs.Impl.AbortIfNeeded(rc);
if (rc.IsFailure()) return rc;
if (fs.Impl.IsEnabledAccessLog(AccessLogTarget.Application))
fs.Impl.EnableFileSystemAccessorAccessLog(new U8Span(HostRootFileSystemMountName));
return Result.Success;
static Result MountHostFs(FileSystemClient fs, IFileSystem fileSystem)
{
return fs.Register(new U8Span(HostRootFileSystemMountName), fileSystem,
new HostRootCommonMountNameGenerator());
}
}
/// <summary>
/// Mounts the C:\ drive of a host Windows computer at @Host:/
/// </summary>
/// <param name="fs">The <see cref="FileSystemClient"/> to use.</param>
/// <param name="option">Options for mounting the host file system.</param>
/// <returns>The <see cref="Result"/> of the operation.</returns>
public static Result MountHostRoot(this FileSystemClient fs, MountHostOption option)
{
Result rc;
Span<byte> logBuffer = stackalloc byte[0x60];
IFileSystem fileSystem;
FspPath.CreateEmpty(out FspPath sfPath);
if (fs.Impl.IsEnabledAccessLog(AccessLogTarget.Application))
{
Tick start = fs.Hos.Os.GetSystemTick();
rc = OpenHostFileSystemImpl(fs, out fileSystem, in sfPath, option);
Tick end = fs.Hos.Os.GetSystemTick();
var idString = new IdString();
var sb = new U8StringBuilder(logBuffer, true);
sb.Append(LogName).Append(HostRootFileSystemMountName).Append(LogQuote)
.Append(LogMountHostOption).Append(idString.ToString(option));
logBuffer = sb.Buffer;
fs.Impl.OutputAccessLogUnlessResultSuccess(rc, start, end, null, new U8Span(logBuffer));
}
else
{
rc = OpenHostFileSystemImpl(fs, out fileSystem, in sfPath, option);
}
// No AbortIfNeeded here
if (rc.IsFailure()) return rc;
if (fs.Impl.IsEnabledAccessLog(AccessLogTarget.Application))
{
Tick start = fs.Hos.Os.GetSystemTick();
rc = MountHostFs(fs, fileSystem);
Tick end = fs.Hos.Os.GetSystemTick();
fs.Impl.OutputAccessLog(rc, start, end, null, new U8Span(logBuffer));
}
else
{
rc = MountHostFs(fs, fileSystem);
}
fs.Impl.AbortIfNeeded(rc);
if (rc.IsFailure()) return rc;
if (fs.Impl.IsEnabledAccessLog(AccessLogTarget.Application))
fs.Impl.EnableFileSystemAccessorAccessLog(new U8Span(HostRootFileSystemMountName));
return Result.Success;
static Result MountHostFs(FileSystemClient fs, IFileSystem fileSystem)
{
return fs.Register(new U8Span(HostRootFileSystemMountName), fileSystem,
new HostRootCommonMountNameGenerator());
}
}
/// <summary>
/// Unmounts the file system at @Host:/
/// </summary>
/// <param name="fs">The <see cref="FileSystemClient"/> to use.</param>
public static void UnmountHostRoot(this FileSystemClient fs)
{
Result rc;
Span<byte> logBuffer = stackalloc byte[0x30];
var mountName = new U8Span(HostRootFileSystemMountName);
if (fs.Impl.IsEnabledAccessLog() && fs.Impl.IsEnabledFileSystemAccessorAccessLog(mountName))
{
Tick start = fs.Hos.Os.GetSystemTick();
rc = fs.Impl.Unmount(mountName);
Tick end = fs.Hos.Os.GetSystemTick();
var sb = new U8StringBuilder(logBuffer, true);
sb.Append(LogName).Append(mountName).Append((byte)'"');
fs.Impl.OutputAccessLog(rc, start, end, null, new U8Span(sb.Buffer));
}
else
{
rc = fs.Impl.Unmount(mountName);
}
fs.Impl.LogErrorMessage(rc);
Abort.DoAbortUnless(rc.IsSuccess());
}
}
}

View file

@ -10,7 +10,9 @@ namespace LibHac.Fs.Shim
using ReferenceCountedDisposable<IFileSystemProxyForLoader> fsProxy =
fs.Impl.GetFileSystemProxyForLoaderServiceObject();
return fsProxy.Target.IsArchivedProgram(out isArchived, processId.Value);
Result rc = fsProxy.Target.IsArchivedProgram(out isArchived, processId.Value);
fs.Impl.AbortIfNeeded(rc);
return rc;
}
}
}

View file

@ -24,7 +24,9 @@ namespace LibHac.Fs.Shim
var mapInfoBuffer = new InBuffer(MemoryMarshal.Cast<ProgramIndexMapInfo, byte>(mapInfo));
return fsProxy.Target.RegisterProgramIndexMapInfo(mapInfoBuffer, mapInfo.Length);
Result rc = fsProxy.Target.RegisterProgramIndexMapInfo(mapInfoBuffer, mapInfo.Length);
fs.Impl.AbortIfNeeded(rc);
return rc;
}
}
}

View file

@ -14,11 +14,15 @@ namespace LibHac.Fs.Shim
{
using ReferenceCountedDisposable<IProgramRegistry> registry = fs.Impl.GetProgramRegistryServiceObject();
Result rc = registry.Target.SetCurrentProcess(fs.Hos.ProcessId.Value);
Result rc = registry.Target.SetCurrentProcess(fs.Hos.Os.GetCurrentProcessId().Value);
fs.Impl.AbortIfNeeded(rc);
if (rc.IsFailure()) return rc;
return registry.Target.RegisterProgram(processId, programId, storageId, new InBuffer(accessControlData),
rc = registry.Target.RegisterProgram(processId, programId, storageId, new InBuffer(accessControlData),
new InBuffer(accessControlDescriptor));
fs.Impl.AbortIfNeeded(rc);
return rc;
}
/// <inheritdoc cref="ProgramRegistryImpl.UnregisterProgram"/>
@ -26,7 +30,7 @@ namespace LibHac.Fs.Shim
{
using ReferenceCountedDisposable<IProgramRegistry> registry = fs.Impl.GetProgramRegistryServiceObject();
Result rc = registry.Target.SetCurrentProcess(fs.Hos.ProcessId.Value);
Result rc = registry.Target.SetCurrentProcess(fs.Hos.Os.GetCurrentProcessId().Value);
if (rc.IsFailure()) return rc;
return registry.Target.UnregisterProgram(processId);

View file

@ -1,60 +1,120 @@
using LibHac.Common;
using System;
using System.Runtime.CompilerServices;
using LibHac.Common;
using LibHac.Fs.Fsa;
using LibHac.Fs.Impl;
using LibHac.FsSrv.Sf;
using LibHac.Ncm;
using LibHac.Os;
using static LibHac.Fs.Impl.AccessLogStrings;
using IFileSystemSf = LibHac.FsSrv.Sf.IFileSystem;
namespace LibHac.Fs.Shim
{
[SkipLocalsInit]
public static class SaveData
{
public static Result MountSaveData(this FileSystemClient fs, U8Span mountName, Ncm.ApplicationId applicationId, UserId userId)
private static Result MountSaveDataImpl(this FileSystemClientImpl fs, U8Span mountName, SaveDataSpaceId spaceId,
ProgramId programId, UserId userId, SaveDataType type, bool openReadOnly, ushort index)
{
Result rc = fs.CheckMountName(mountName);
if (rc.IsFailure()) return rc;
using ReferenceCountedDisposable<IFileSystemProxy> fsProxy = fs.GetFileSystemProxyServiceObject();
rc = SaveDataAttribute.Make(out SaveDataAttribute attribute, programId, type, userId, 0, index);
if (rc.IsFailure()) return rc;
ReferenceCountedDisposable<IFileSystemSf> fileSystem = null;
try
{
if (openReadOnly)
{
rc = fsProxy.Target.OpenReadOnlySaveDataFileSystem(out fileSystem, spaceId, in attribute);
if (rc.IsFailure()) return rc;
}
else
{
rc = fsProxy.Target.OpenSaveDataFileSystem(out fileSystem, spaceId, in attribute);
if (rc.IsFailure()) return rc;
}
var fileSystemAdapter = new FileSystemServiceObjectAdapter(fileSystem);
return fs.Fs.Register(mountName, fileSystemAdapter, fileSystemAdapter, null, false, true);
}
finally
{
fileSystem?.Dispose();
}
}
public static Result MountSaveData(this FileSystemClient fs, U8Span mountName, Ncm.ApplicationId applicationId,
UserId userId)
{
Result rc;
Span<byte> logBuffer = stackalloc byte[0x90];
if (fs.IsEnabledAccessLog(AccessLogTarget.Application))
if (fs.Impl.IsEnabledAccessLog(AccessLogTarget.Application))
{
System.TimeSpan startTime = fs.Time.GetCurrent();
rc = MountSaveDataImpl(fs, mountName, SaveDataSpaceId.User, applicationId, userId, SaveDataType.Account, false, 0);
System.TimeSpan endTime = fs.Time.GetCurrent();
Tick start = fs.Hos.Os.GetSystemTick();
rc = MountSaveDataImpl(fs.Impl, mountName, SaveDataSpaceId.User, applicationId, userId,
SaveDataType.Account, openReadOnly: false, index: 0);
Tick end = fs.Hos.Os.GetSystemTick();
fs.OutputAccessLog(rc, startTime, endTime, $", name: \"{mountName.ToString()}\", applicationid: 0x{applicationId}, userid: 0x{userId}");
var sb = new U8StringBuilder(logBuffer, true);
sb.Append(LogName).Append(mountName).Append(LogQuote)
.Append(LogApplicationId).AppendFormat(applicationId.Value, 'X')
.Append(LogUserId).AppendFormat(userId.Id.High, 'X', 16).AppendFormat(userId.Id.Low, 'X', 16);
fs.Impl.OutputAccessLog(rc, start, end, null, new U8Span(sb.Buffer));
}
else
{
rc = MountSaveDataImpl(fs, mountName, SaveDataSpaceId.User, applicationId, userId, SaveDataType.Account, false, 0);
rc = MountSaveDataImpl(fs.Impl, mountName, SaveDataSpaceId.User, applicationId, userId,
SaveDataType.Account, openReadOnly: false, index: 0);
}
if (rc.IsSuccess() && fs.IsEnabledAccessLog(AccessLogTarget.Application))
{
fs.EnableFileSystemAccessorAccessLog(mountName);
}
fs.Impl.AbortIfNeeded(rc);
if (rc.IsFailure()) return rc;
if (fs.Impl.IsEnabledAccessLog(AccessLogTarget.Application))
fs.Impl.EnableFileSystemAccessorAccessLog(mountName);
return rc;
}
public static Result MountSaveDataReadOnly(this FileSystemClient fs, U8Span mountName, Ncm.ApplicationId applicationId, UserId userId)
public static Result MountSaveDataReadOnly(this FileSystemClient fs, U8Span mountName,
Ncm.ApplicationId applicationId, UserId userId)
{
Result rc;
Span<byte> logBuffer = stackalloc byte[0x90];
if (fs.IsEnabledAccessLog(AccessLogTarget.Application))
if (fs.Impl.IsEnabledAccessLog(AccessLogTarget.Application))
{
System.TimeSpan startTime = fs.Time.GetCurrent();
rc = MountSaveDataImpl(fs, mountName, SaveDataSpaceId.User, applicationId, userId, SaveDataType.Account, true, 0);
System.TimeSpan endTime = fs.Time.GetCurrent();
Tick start = fs.Hos.Os.GetSystemTick();
rc = MountSaveDataImpl(fs.Impl, mountName, SaveDataSpaceId.User, applicationId, userId,
SaveDataType.Account, openReadOnly: true, index: 0);
Tick end = fs.Hos.Os.GetSystemTick();
fs.OutputAccessLog(rc, startTime, endTime, $", name: \"{mountName.ToString()}\", applicationid: 0x{applicationId}, userid: 0x{userId}");
var sb = new U8StringBuilder(logBuffer, true);
sb.Append(LogName).Append(mountName).Append(LogQuote)
.Append(LogApplicationId).AppendFormat(applicationId.Value, 'X')
.Append(LogUserId).AppendFormat(userId.Id.High, 'X', 16).AppendFormat(userId.Id.Low, 'X', 16);
fs.Impl.OutputAccessLog(rc, start, end, null, new U8Span(sb.Buffer));
}
else
{
rc = MountSaveDataImpl(fs, mountName, SaveDataSpaceId.User, applicationId, userId, SaveDataType.Account, false, 0);
rc = MountSaveDataImpl(fs.Impl, mountName, SaveDataSpaceId.User, applicationId, userId,
SaveDataType.Account, openReadOnly: true, index: 0);
}
if (rc.IsSuccess() && fs.IsEnabledAccessLog(AccessLogTarget.Application))
{
fs.EnableFileSystemAccessorAccessLog(mountName);
}
fs.Impl.AbortIfNeeded(rc);
if (rc.IsFailure()) return rc;
if (fs.Impl.IsEnabledAccessLog(AccessLogTarget.Application))
fs.Impl.EnableFileSystemAccessorAccessLog(mountName);
return rc;
}
@ -62,24 +122,31 @@ namespace LibHac.Fs.Shim
public static Result MountTemporaryStorage(this FileSystemClient fs, U8Span mountName)
{
Result rc;
Span<byte> logBuffer = stackalloc byte[0x30];
if (fs.IsEnabledAccessLog(AccessLogTarget.Application))
if (fs.Impl.IsEnabledAccessLog(AccessLogTarget.Application))
{
System.TimeSpan startTime = fs.Time.GetCurrent();
rc = MountSaveDataImpl(fs, mountName, SaveDataSpaceId.Temporary, default, default, SaveDataType.Temporary, false, 0);
System.TimeSpan endTime = fs.Time.GetCurrent();
Tick start = fs.Hos.Os.GetSystemTick();
rc = MountSaveDataImpl(fs.Impl, mountName, SaveDataSpaceId.Temporary, Fs.SaveData.InvalidProgramId,
Fs.SaveData.InvalidUserId, SaveDataType.Temporary, openReadOnly: false, index: 0);
Tick end = fs.Hos.Os.GetSystemTick();
fs.OutputAccessLog(rc, startTime, endTime, $", name: \"{mountName.ToString()}\"");
var sb = new U8StringBuilder(logBuffer, true);
sb.Append(LogName).Append(mountName).Append(LogQuote);
fs.Impl.OutputAccessLog(rc, start, end, null, new U8Span(sb.Buffer));
}
else
{
rc = MountSaveDataImpl(fs, mountName, SaveDataSpaceId.Temporary, default, default, SaveDataType.Temporary, false, 0);
rc = MountSaveDataImpl(fs.Impl, mountName, SaveDataSpaceId.Temporary, Fs.SaveData.InvalidProgramId,
Fs.SaveData.InvalidUserId, SaveDataType.Temporary, openReadOnly: false, index: 0);
}
if (rc.IsSuccess() && fs.IsEnabledAccessLog(AccessLogTarget.Application))
{
fs.EnableFileSystemAccessorAccessLog(mountName);
}
fs.Impl.AbortIfNeeded(rc);
if (rc.IsFailure()) return rc;
if (fs.Impl.IsEnabledAccessLog(AccessLogTarget.Application))
fs.Impl.EnableFileSystemAccessorAccessLog(mountName);
return rc;
}
@ -87,24 +154,31 @@ namespace LibHac.Fs.Shim
public static Result MountCacheStorage(this FileSystemClient fs, U8Span mountName)
{
Result rc;
Span<byte> logBuffer = stackalloc byte[0x30];
if (fs.IsEnabledAccessLog(AccessLogTarget.Application))
if (fs.Impl.IsEnabledAccessLog(AccessLogTarget.Application))
{
System.TimeSpan startTime = fs.Time.GetCurrent();
rc = MountSaveDataImpl(fs, mountName, SaveDataSpaceId.User, default, default, SaveDataType.Cache, false, 0);
System.TimeSpan endTime = fs.Time.GetCurrent();
Tick start = fs.Hos.Os.GetSystemTick();
rc = MountSaveDataImpl(fs.Impl, mountName, SaveDataSpaceId.User, Fs.SaveData.InvalidProgramId,
Fs.SaveData.InvalidUserId, SaveDataType.Cache, openReadOnly: false, index: 0);
Tick end = fs.Hos.Os.GetSystemTick();
fs.OutputAccessLog(rc, startTime, endTime, $", name: \"{mountName.ToString()}\"");
var sb = new U8StringBuilder(logBuffer, true);
sb.Append(LogName).Append(mountName).Append(LogQuote);
fs.Impl.OutputAccessLog(rc, start, end, null, new U8Span(sb.Buffer));
}
else
{
rc = MountSaveDataImpl(fs, mountName, SaveDataSpaceId.User, default, default, SaveDataType.Cache, false, 0);
rc = MountSaveDataImpl(fs.Impl, mountName, SaveDataSpaceId.User, Fs.SaveData.InvalidProgramId,
Fs.SaveData.InvalidUserId, SaveDataType.Cache, openReadOnly: false, index: 0);
}
if (rc.IsSuccess() && fs.IsEnabledAccessLog(AccessLogTarget.Application))
{
fs.EnableFileSystemAccessorAccessLog(mountName);
}
fs.Impl.AbortIfNeeded(rc);
if (rc.IsFailure()) return rc;
if (fs.Impl.IsEnabledAccessLog(AccessLogTarget.Application))
fs.Impl.EnableFileSystemAccessorAccessLog(mountName);
return rc;
}
@ -112,111 +186,103 @@ namespace LibHac.Fs.Shim
public static Result MountCacheStorage(this FileSystemClient fs, U8Span mountName, int index)
{
Result rc;
Span<byte> logBuffer = stackalloc byte[0x40];
if (fs.IsEnabledAccessLog(AccessLogTarget.Application))
if (fs.Impl.IsEnabledAccessLog(AccessLogTarget.Application))
{
System.TimeSpan startTime = fs.Time.GetCurrent();
rc = MountSaveDataImpl(fs, mountName, SaveDataSpaceId.User, default, default, SaveDataType.Cache, false, (ushort)index);
System.TimeSpan endTime = fs.Time.GetCurrent();
Tick start = fs.Hos.Os.GetSystemTick();
rc = MountSaveDataImpl(fs.Impl, mountName, SaveDataSpaceId.User, Fs.SaveData.InvalidProgramId,
Fs.SaveData.InvalidUserId, SaveDataType.Cache, openReadOnly: false, (ushort)index);
Tick end = fs.Hos.Os.GetSystemTick();
fs.OutputAccessLog(rc, startTime, endTime, $", name: \"{mountName.ToString()}\", index: {index}");
var sb = new U8StringBuilder(logBuffer, true);
sb.Append(LogName).Append(mountName).Append(LogQuote)
.Append(LogIndex).AppendFormat(index);
fs.Impl.OutputAccessLog(rc, start, end, null, new U8Span(sb.Buffer));
}
else
{
rc = MountSaveDataImpl(fs, mountName, SaveDataSpaceId.User, default, default, SaveDataType.Cache, false, (ushort)index);
rc = MountSaveDataImpl(fs.Impl, mountName, SaveDataSpaceId.User, Fs.SaveData.InvalidProgramId,
Fs.SaveData.InvalidUserId, SaveDataType.Cache, openReadOnly: false, (ushort)index);
}
if (rc.IsSuccess() && fs.IsEnabledAccessLog(AccessLogTarget.Application))
{
fs.EnableFileSystemAccessorAccessLog(mountName);
}
return rc;
}
public static Result MountCacheStorage(this FileSystemClient fs, U8Span mountName, Ncm.ApplicationId applicationId)
{
Result rc;
if (fs.IsEnabledAccessLog(AccessLogTarget.System))
{
System.TimeSpan startTime = fs.Time.GetCurrent();
rc = MountSaveDataImpl(fs, mountName, SaveDataSpaceId.User, applicationId, default, SaveDataType.Cache, false, 0);
System.TimeSpan endTime = fs.Time.GetCurrent();
fs.OutputAccessLog(rc, startTime, endTime, $", name: \"{mountName.ToString()}\", applicationid: 0x{applicationId}");
}
else
{
rc = MountSaveDataImpl(fs, mountName, SaveDataSpaceId.User, applicationId, default, SaveDataType.Cache, false, 0);
}
if (rc.IsSuccess() && fs.IsEnabledAccessLog(AccessLogTarget.System))
{
fs.EnableFileSystemAccessorAccessLog(mountName);
}
return rc;
}
public static Result MountCacheStorage(this FileSystemClient fs, U8Span mountName, Ncm.ApplicationId applicationId, int index)
{
Result rc;
if (fs.IsEnabledAccessLog(AccessLogTarget.System))
{
System.TimeSpan startTime = fs.Time.GetCurrent();
rc = MountSaveDataImpl(fs, mountName, SaveDataSpaceId.User, applicationId, default, SaveDataType.Cache, false, (ushort)index);
System.TimeSpan endTime = fs.Time.GetCurrent();
fs.OutputAccessLog(rc, startTime, endTime, $", name: \"{mountName.ToString()}\", applicationid: 0x{applicationId}, index: {index}");
}
else
{
rc = MountSaveDataImpl(fs, mountName, SaveDataSpaceId.User, applicationId, default, SaveDataType.Cache, false, (ushort)index);
}
if (rc.IsSuccess() && fs.IsEnabledAccessLog(AccessLogTarget.System))
{
fs.EnableFileSystemAccessorAccessLog(mountName);
}
return rc;
}
private static Result MountSaveDataImpl(this FileSystemClient fs, U8Span mountName, SaveDataSpaceId spaceId,
ProgramId programId, UserId userId, SaveDataType type, bool openReadOnly, ushort index)
{
Result rc = MountHelpers.CheckMountName(mountName);
fs.Impl.AbortIfNeeded(rc);
if (rc.IsFailure()) return rc;
using ReferenceCountedDisposable<IFileSystemProxy> fsProxy = fs.Impl.GetFileSystemProxyServiceObject();
if (fs.Impl.IsEnabledAccessLog(AccessLogTarget.Application))
fs.Impl.EnableFileSystemAccessorAccessLog(mountName);
var attribute = new SaveDataAttribute(programId, type, userId, 0, index);
return rc;
}
ReferenceCountedDisposable<IFileSystemSf> saveFs = null;
public static Result MountCacheStorage(this FileSystemClient fs, U8Span mountName,
Ncm.ApplicationId applicationId)
{
Result rc;
Span<byte> logBuffer = stackalloc byte[0x50];
try
if (fs.Impl.IsEnabledAccessLog(AccessLogTarget.System))
{
if (openReadOnly)
{
rc = fsProxy.Target.OpenReadOnlySaveDataFileSystem(out saveFs, spaceId, in attribute);
}
else
{
rc = fsProxy.Target.OpenSaveDataFileSystem(out saveFs, spaceId, in attribute);
}
Tick start = fs.Hos.Os.GetSystemTick();
rc = MountSaveDataImpl(fs.Impl, mountName, SaveDataSpaceId.User, applicationId,
Fs.SaveData.InvalidUserId, SaveDataType.Cache, openReadOnly: false, index: 0);
Tick end = fs.Hos.Os.GetSystemTick();
if (rc.IsFailure()) return rc;
var sb = new U8StringBuilder(logBuffer, true);
sb.Append(LogName).Append(mountName).Append(LogQuote)
.Append(LogApplicationId).AppendFormat(applicationId.Value, 'X');
var fileSystemAdapter = new FileSystemServiceObjectAdapter(saveFs);
return fs.Register(mountName, fileSystemAdapter, fileSystemAdapter, null, false, false);
fs.Impl.OutputAccessLog(rc, start, end, null, new U8Span(sb.Buffer));
}
finally
else
{
saveFs?.Dispose();
rc = MountSaveDataImpl(fs.Impl, mountName, SaveDataSpaceId.User, applicationId,
Fs.SaveData.InvalidUserId, SaveDataType.Cache, openReadOnly: false, index: 0);
}
fs.Impl.AbortIfNeeded(rc);
if (rc.IsFailure()) return rc;
if (fs.Impl.IsEnabledAccessLog(AccessLogTarget.System))
fs.Impl.EnableFileSystemAccessorAccessLog(mountName);
return rc;
}
public static Result MountCacheStorage(this FileSystemClient fs, U8Span mountName,
Ncm.ApplicationId applicationId, int index)
{
Result rc;
Span<byte> logBuffer = stackalloc byte[0x60];
if (fs.Impl.IsEnabledAccessLog(AccessLogTarget.System))
{
Tick start = fs.Hos.Os.GetSystemTick();
rc = MountSaveDataImpl(fs.Impl, mountName, SaveDataSpaceId.User, applicationId,
Fs.SaveData.InvalidUserId, SaveDataType.Cache, openReadOnly: false, (ushort)index);
Tick end = fs.Hos.Os.GetSystemTick();
var sb = new U8StringBuilder(logBuffer, true);
sb.Append(LogName).Append(mountName).Append(LogQuote)
.Append(LogApplicationId).AppendFormat(applicationId.Value, 'X')
.Append(LogIndex).AppendFormat(index);
fs.Impl.OutputAccessLog(rc, start, end, null, new U8Span(sb.Buffer));
}
else
{
rc = MountSaveDataImpl(fs.Impl, mountName, SaveDataSpaceId.User, applicationId,
Fs.SaveData.InvalidUserId, SaveDataType.Cache, openReadOnly: false, (ushort)index);
}
fs.Impl.AbortIfNeeded(rc);
if (rc.IsFailure()) return rc;
if (fs.Impl.IsEnabledAccessLog(AccessLogTarget.System))
fs.Impl.EnableFileSystemAccessorAccessLog(mountName);
return rc;
}
}
}

File diff suppressed because it is too large Load diff

View file

@ -1,71 +1,147 @@
using LibHac.Common;
using System;
using System.Runtime.CompilerServices;
using LibHac.Common;
using LibHac.Diag;
using LibHac.Fs.Fsa;
using LibHac.Fs.Impl;
using LibHac.FsSrv.Sf;
using LibHac.Os;
using static LibHac.Fs.Impl.AccessLogStrings;
using IFileSystemSf = LibHac.FsSrv.Sf.IFileSystem;
namespace LibHac.Fs.Shim
{
[SkipLocalsInit]
public static class SdCard
{
private static Result OpenSdCardFileSystem(FileSystemClient fs,
out ReferenceCountedDisposable<IFileSystemSf> fileSystem)
{
using ReferenceCountedDisposable<IFileSystemProxy> fsProxy = fs.Impl.GetFileSystemProxyServiceObject();
return OpenFileSystem(fs, fsProxy, out fileSystem);
static Result OpenFileSystem(FileSystemClient fs, ReferenceCountedDisposable<IFileSystemProxy> fsProxy,
out ReferenceCountedDisposable<IFileSystemSf> fileSystem)
{
fileSystem = default;
// Retry a few times if the storage device isn't ready yet
const int maxRetries = 10;
const int retryInterval = 1000;
for (int i = 0; i < maxRetries; i++)
{
Result rc = fsProxy.Target.OpenSdCardFileSystem(out fileSystem);
if (rc.IsSuccess())
break;
if (!ResultFs.StorageDeviceNotReady.Includes(rc))
return rc;
if (i == maxRetries - 1)
return rc;
fs.Hos.Os.SleepThread(TimeSpan.FromMilliSeconds(retryInterval));
}
return Result.Success;
}
}
private static Result RegisterFileSystem(FileSystemClient fs, U8Span mountName,
ReferenceCountedDisposable<IFileSystemSf> fileSystem)
{
var fileSystemAdapter = new FileSystemServiceObjectAdapter(fileSystem);
return fs.Register(mountName, fileSystemAdapter);
}
public static Result MountSdCard(this FileSystemClient fs, U8Span mountName)
{
Result rc;
Span<byte> logBuffer = stackalloc byte[0x30];
if (fs.IsEnabledAccessLog(AccessLogTarget.Application))
// Check if the mount name is valid
if (fs.Impl.IsEnabledAccessLog(AccessLogTarget.System))
{
System.TimeSpan startTime = fs.Time.GetCurrent();
rc = Run(fs, mountName);
System.TimeSpan endTime = fs.Time.GetCurrent();
Tick start = fs.Hos.Os.GetSystemTick();
rc = fs.Impl.CheckMountName(mountName);
Tick end = fs.Hos.Os.GetSystemTick();
fs.OutputAccessLog(rc, startTime, endTime, "");
var sb = new U8StringBuilder(logBuffer, true);
sb.Append(LogName).Append(mountName);
logBuffer = sb.Buffer;
fs.Impl.OutputAccessLogUnlessResultSuccess(rc, start, end, null, new U8Span(logBuffer));
}
else
{
rc = Run(fs, mountName);
rc = fs.Impl.CheckMountName(mountName);
}
fs.Impl.AbortIfNeeded(rc);
if (rc.IsFailure()) return rc;
if (fs.IsEnabledAccessLog(AccessLogTarget.Application))
// Open the SD card file system
ReferenceCountedDisposable<IFileSystemSf> fileSystem = null;
try
{
fs.EnableFileSystemAccessorAccessLog(mountName);
}
return Result.Success;
static Result Run(FileSystemClient fs, U8Span mountName)
{
// ReSharper disable once VariableHidesOuterVariable
Result rc = MountHelpers.CheckMountName(mountName);
if (rc.IsFailure()) return rc;
using ReferenceCountedDisposable<IFileSystemProxy> fsProxy = fs.Impl.GetFileSystemProxyServiceObject();
rc = fsProxy.Target.OpenSdCardFileSystem(out ReferenceCountedDisposable<IFileSystemSf> fileSystem);
if (rc.IsFailure()) return rc;
using (fileSystem)
if (fs.Impl.IsEnabledAccessLog(AccessLogTarget.System))
{
var fileSystemAdapter = new FileSystemServiceObjectAdapter(fileSystem);
Tick start = fs.Hos.Os.GetSystemTick();
rc = OpenSdCardFileSystem(fs, out fileSystem);
Tick end = fs.Hos.Os.GetSystemTick();
return fs.Register(mountName, fileSystemAdapter);
fs.Impl.OutputAccessLogUnlessResultSuccess(rc, start, end, null, new U8Span(logBuffer));
}
else
{
rc = OpenSdCardFileSystem(fs, out fileSystem);
}
fs.Impl.AbortIfNeeded(rc);
if (rc.IsFailure()) return rc;
// Mount the file system
if (fs.Impl.IsEnabledAccessLog(AccessLogTarget.System))
{
Tick start = fs.Hos.Os.GetSystemTick();
rc = RegisterFileSystem(fs, mountName, fileSystem);
Tick end = fs.Hos.Os.GetSystemTick();
fs.Impl.OutputAccessLog(rc, start, end, null, new U8Span(logBuffer));
}
else
{
rc = RegisterFileSystem(fs, mountName, fileSystem);
}
fs.Impl.AbortIfNeeded(rc);
if (rc.IsFailure()) return rc;
if (fs.Impl.IsEnabledAccessLog(AccessLogTarget.System))
fs.Impl.EnableFileSystemAccessorAccessLog(mountName);
return Result.Success;
}
finally
{
fileSystem?.Dispose();
}
}
public static bool IsSdCardInserted(this FileSystemClient fs)
{
using ReferenceCountedDisposable<IFileSystemProxy> fsProxy = fs.Impl.GetFileSystemProxyServiceObject();
ReferenceCountedDisposable<IDeviceOperator> deviceOperator = null;
try
{
using ReferenceCountedDisposable<IFileSystemProxy> fsProxy = fs.Impl.GetFileSystemProxyServiceObject();
Result rc = fsProxy.Target.OpenDeviceOperator(out deviceOperator);
if (rc.IsFailure()) throw new HorizonResultException(rc, "Abort");
fs.Impl.LogErrorMessage(rc);
Abort.DoAbortUnless(rc.IsSuccess());
rc = deviceOperator.Target.IsSdCardInserted(out bool isInserted);
if (rc.IsFailure()) throw new HorizonResultException(rc, "Abort");
rc = CheckIfInserted(fs, deviceOperator, out bool isInserted);
fs.Impl.LogErrorMessage(rc);
Abort.DoAbortUnless(rc.IsSuccess());
return isInserted;
}
@ -73,6 +149,34 @@ namespace LibHac.Fs.Shim
{
deviceOperator?.Dispose();
}
static Result CheckIfInserted(FileSystemClient fs,
ReferenceCountedDisposable<IDeviceOperator> deviceOperator, out bool isInserted)
{
Unsafe.SkipInit(out isInserted);
// Retry a few times if the storage device isn't ready yet
const int maxRetries = 10;
const int retryInterval = 1000;
for (int i = 0; i < maxRetries; i++)
{
Result rc = deviceOperator.Target.IsSdCardInserted(out isInserted);
if (rc.IsSuccess())
break;
if (!ResultFs.StorageDeviceNotReady.Includes(rc))
return rc;
if (i == maxRetries - 1)
return rc;
fs.Hos.Os.SleepThread(TimeSpan.FromMilliSeconds(retryInterval));
}
return Result.Success;
}
}
public static Result SetSdCardEncryptionSeed(this FileSystemClient fs, in EncryptionSeed seed)
@ -80,17 +184,15 @@ namespace LibHac.Fs.Shim
using ReferenceCountedDisposable<IFileSystemProxy> fsProxy = fs.Impl.GetFileSystemProxyServiceObject();
Result rc = fsProxy.Target.SetSdCardEncryptionSeed(in seed);
if (rc.IsFailure()) throw new HorizonResultException(rc, "Abort");
return Result.Success;
fs.Impl.AbortIfNeeded(rc);
return rc;
}
public static void SetSdCardAccessibility(this FileSystemClient fs, bool isAccessible)
{
using ReferenceCountedDisposable<IFileSystemProxy> fsProxy = fs.Impl.GetFileSystemProxyServiceObject();
Result rc = fsProxy.Target.SetSdCardAccessibility(isAccessible);
if (rc.IsFailure()) throw new HorizonResultException(rc, "Abort");
Result rc = fs.Impl.SetSdCardAccessibility(isAccessible);
fs.Impl.LogErrorMessage(rc);
Abort.DoAbortUnless(rc.IsSuccess());
}
public static bool IsSdCardAccessible(this FileSystemClient fs)
@ -98,9 +200,19 @@ namespace LibHac.Fs.Shim
using ReferenceCountedDisposable<IFileSystemProxy> fsProxy = fs.Impl.GetFileSystemProxyServiceObject();
Result rc = fsProxy.Target.IsSdCardAccessible(out bool isAccessible);
if (rc.IsFailure()) throw new HorizonResultException(rc, "Abort");
fs.Impl.LogErrorMessage(rc);
Abort.DoAbortUnless(rc.IsSuccess());
return isAccessible;
}
public static Result SetSdCardAccessibility(this FileSystemClientImpl fs, bool isAccessible)
{
using ReferenceCountedDisposable<IFileSystemProxy> fsProxy = fs.GetFileSystemProxyServiceObject();
Result rc = fsProxy.Target.SetSdCardAccessibility(isAccessible);
fs.AbortIfNeeded(rc);
return rc;
}
}
}

View file

@ -1,43 +1,97 @@
using LibHac.Common;
using System;
using System.Runtime.CompilerServices;
using LibHac.Common;
using LibHac.Fs.Fsa;
using LibHac.Fs.Impl;
using LibHac.FsSrv.Sf;
using LibHac.Ncm;
using LibHac.Os;
using static LibHac.Fs.Impl.AccessLogStrings;
using IFileSystemSf = LibHac.FsSrv.Sf.IFileSystem;
namespace LibHac.Fs.Shim
{
[SkipLocalsInit]
public static class SystemSaveData
{
public static Result MountSystemSaveData(this FileSystemClient fs, U8Span mountName, ulong saveDataId)
{
return fs.MountSystemSaveData(mountName, saveDataId, Fs.SaveData.InvalidUserId);
}
public static Result MountSystemSaveData(this FileSystemClient fs, U8Span mountName, SaveDataSpaceId spaceId,
ulong saveDataId)
{
return MountSystemSaveData(fs, mountName, spaceId, saveDataId, UserId.InvalidId);
return fs.MountSystemSaveData(mountName, spaceId, saveDataId, Fs.SaveData.InvalidUserId);
}
public static Result MountSystemSaveData(this FileSystemClient fs, U8Span mountName, ulong saveDataId,
UserId userId)
{
return fs.MountSystemSaveData(mountName, SaveDataSpaceId.System, saveDataId, userId);
}
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;
Result rc;
Span<byte> logBuffer = stackalloc byte[0x90];
using ReferenceCountedDisposable<IFileSystemProxy> fsProxy = fs.Impl.GetFileSystemProxyServiceObject();
var attribute = new SaveDataAttribute(ProgramId.InvalidId, SaveDataType.System, userId, saveDataId);
ReferenceCountedDisposable<IFileSystemSf> saveFs = null;
try
if (fs.Impl.IsEnabledAccessLog(AccessLogTarget.System))
{
rc = fsProxy.Target.OpenSaveDataFileSystemBySystemSaveDataId(out saveFs, spaceId, in attribute);
Tick start = fs.Hos.Os.GetSystemTick();
rc = Mount(fs, mountName, spaceId, saveDataId, userId);
Tick end = fs.Hos.Os.GetSystemTick();
var idString = new IdString();
var sb = new U8StringBuilder(logBuffer, true);
sb.Append(LogName).Append(mountName)
.Append(LogSaveDataSpaceId).Append(idString.ToString(spaceId))
.Append(LogSaveDataId).AppendFormat(saveDataId, 'X')
.Append(LogUserId).AppendFormat(userId.Id.High, 'X', 16).AppendFormat(userId.Id.Low, 'X', 16);
fs.Impl.OutputAccessLog(rc, start, end, null, new U8Span(sb.Buffer));
}
else
{
rc = Mount(fs, mountName, spaceId, saveDataId, userId);
}
fs.Impl.AbortIfNeeded(rc);
return rc;
static Result Mount(FileSystemClient fs, U8Span mountName, SaveDataSpaceId spaceId, ulong saveDataId,
UserId userId)
{
Result rc = fs.Impl.CheckMountName(mountName);
if (rc.IsFailure()) return rc;
var fileSystemAdapter = new FileSystemServiceObjectAdapter(saveFs);
return fs.Register(mountName, fileSystemAdapter);
}
finally
{
saveFs?.Dispose();
using ReferenceCountedDisposable<IFileSystemProxy> fsProxy = fs.Impl.GetFileSystemProxyServiceObject();
rc = SaveDataAttribute.Make(out SaveDataAttribute attribute, Fs.SaveData.InvalidProgramId,
SaveDataType.System, userId, saveDataId);
if (rc.IsFailure()) return rc;
ReferenceCountedDisposable<IFileSystemSf> fileSystem = null;
try
{
rc = fsProxy.Target.OpenSaveDataFileSystemBySystemSaveDataId(out fileSystem, spaceId, in attribute);
if (rc.IsFailure()) return rc;
var fileSystemAdapter = new FileSystemServiceObjectAdapter(fileSystem);
if (spaceId == SaveDataSpaceId.System)
{
return fs.Register(mountName, fileSystemAdapter, fileSystemAdapter, null, false, false);
}
else
{
return fs.Register(mountName, fileSystemAdapter);
}
}
finally
{
fileSystem?.Dispose();
}
}
}
}

View file

@ -530,7 +530,7 @@ namespace LibHac.FsSrv
try
{
rc = FsProxyCore.OpenHostFileSystem(out hostFs, new U8Span(path.Str),
option.HasFlag(MountHostOption.PseudoCaseSensitive));
option.Flags.HasFlag(MountHostOptionFlag.PseudoCaseSensitive));
if (rc.IsFailure()) return rc;
bool isRootPath = path.Str[0] == 0;

View file

@ -17,8 +17,7 @@ namespace LibHac.FsSrv
/// <param name="horizonClient">The <see cref="HorizonClient"/> that will be used by this server.</param>
public FileSystemServer(HorizonClient horizonClient)
{
Globals.Hos = horizonClient;
Globals.InitMutex = new object();
Globals.Initialize(horizonClient);
}
}
@ -31,6 +30,12 @@ namespace LibHac.FsSrv
public DeviceEventSimulatorGlobals DeviceEventSimulator;
public AccessControlGlobals AccessControl;
public StorageDeviceManagerFactoryGlobals StorageDeviceManagerFactory;
public void Initialize(HorizonClient horizonClient)
{
Hos = horizonClient;
InitMutex = new object();
}
}
// Functions in the nn::fssrv::storage namespace use this struct.

View file

@ -2,7 +2,7 @@
namespace LibHac.FsSrv
{
public struct GameCardHandle : IEquatable<GameCardHandle>
public readonly struct GameCardHandle : IEquatable<GameCardHandle>
{
public readonly int Value;

View file

@ -5,6 +5,9 @@ namespace LibHac.FsSrv.Impl
{
public static class SaveDataProperties
{
public const long DefaultSaveDataBlockSize = 0x4000;
public const long BcatSaveDataJournalSize = 0x200000;
public static bool IsJournalingSupported(SaveDataType type)
{
switch (type)

View file

@ -1,5 +1,4 @@
using System.Diagnostics;
using System.Threading;
using System.Threading;
using LibHac.Common;
using LibHac.Diag;
using LibHac.Fs.Shim;
@ -13,21 +12,19 @@ namespace LibHac
public class Horizon
{
private const int InitialProcessCountMax = 0x50;
internal long StartTick { get; }
internal ITimeSpanGenerator Time { get; }
internal ITickGenerator TickGenerator { get; }
internal ServiceManager ServiceManager { get; }
private HorizonClient LoaderClient { get; }
private ulong _currentInitialProcessId;
private ulong _currentProcessId;
// Todo: Initialize with a configuration object
public Horizon(ITimeSpanGenerator timer)
public Horizon(HorizonConfiguration config)
{
_currentProcessId = InitialProcessCountMax;
Time = timer ?? new StopWatchTimeSpanGenerator();
StartTick = Stopwatch.GetTimestamp();
TickGenerator = config.TickGenerator;
ServiceManager = new ServiceManager();
LoaderClient = CreatePrivilegedHorizonClient();

View file

@ -21,16 +21,14 @@ namespace LibHac
public LrClient Lr { get; }
public ArpClient Arp => ArpLazy.Value;
public ITimeSpanGenerator Time => Horizon.Time;
internal HorizonClient(Horizon horizon, ProcessId processId)
{
Horizon = horizon;
ProcessId = processId;
Fs = new FileSystemClient(this);
Sm = new ServiceManagerClient(horizon.ServiceManager);
Os = new OsState(this, horizon.StartTick);
Sm = new ServiceManagerClient(Horizon.ServiceManager);
Os = new OsState(this, horizon.TickGenerator);
Lr = new LrClient(this);
ArpLazy = new Lazy<ArpClient>(InitArpClient, true);

View file

@ -0,0 +1,22 @@
#nullable enable
using LibHac.Os;
namespace LibHac
{
/// <summary>
/// Contains configuration options for instantiating a <see cref="Horizon"/> object.
/// </summary>
public class HorizonConfiguration
{
/// <summary>
/// Used when getting the current system <see cref="Tick"/>.
/// If <see langword="null"/>, a default <see cref="ITickGenerator"/> is used.
/// </summary>
public ITickGenerator? TickGenerator { get; set; }
}
public interface ITickGenerator
{
Tick GetCurrentTick();
}
}

View file

@ -1,13 +1,15 @@
using LibHac.Bcat;
using LibHac.Common.Keys;
using LibHac.Fs.Fsa;
using LibHac.FsSrv;
namespace LibHac
{
public static class HorizonFactory
{
public static Horizon CreateWithFsConfig(ITimeSpanGenerator timer, FileSystemServerConfig fsServerConfig)
public static Horizon CreateWithFsConfig(HorizonConfiguration config, FileSystemServerConfig fsServerConfig)
{
var horizon = new Horizon(timer);
var horizon = new Horizon(config);
HorizonClient fsServerClient = horizon.CreatePrivilegedHorizonClient();
var fsServer = new FileSystemServer(fsServerClient);
@ -19,5 +21,29 @@ namespace LibHac
return horizon;
}
public static Horizon CreateWithDefaultFsConfig(HorizonConfiguration config, IFileSystem rootFileSystem, KeySet keySet)
{
var horizon = new Horizon(config);
var defaultObjects = DefaultFsServerObjects.GetDefaultEmulatedCreators(rootFileSystem, keySet);
HorizonClient fsServerClient = horizon.CreatePrivilegedHorizonClient();
var fsServer = new FileSystemServer(fsServerClient);
var fsServerConfig = new FileSystemServerConfig
{
DeviceOperator = defaultObjects.DeviceOperator,
ExternalKeySet = keySet.ExternalKeySet,
FsCreators = defaultObjects.FsCreators
};
FileSystemServerInitializer.InitializeWithConfig(fsServerClient, fsServer, fsServerConfig);
HorizonClient bcatServerClient = horizon.CreateHorizonClient();
_ = new BcatServer(bcatServerClient);
return horizon;
}
}
}

View file

@ -7,9 +7,9 @@ namespace LibHac.Os.Impl
public TickManager TickManager { get; }
// Todo: Use configuration object if/when more options are added
public OsResourceManager(long startTick)
public OsResourceManager(ITickGenerator tickGenerator)
{
TickManager = new TickManager(startTick);
TickManager = new TickManager(tickGenerator);
}
public void Dispose()

View file

@ -7,11 +7,11 @@ namespace LibHac.Os.Impl
internal struct TickManagerImpl : IDisposable
{
private long _tickFrequency;
private long _startTick;
private ITickGenerator _tickGenerator;
private TimeSpan _maxTimeSpan;
private long _maxTick;
public TickManagerImpl(long startTick)
public TickManagerImpl(ITickGenerator tickGenerator)
{
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
@ -19,7 +19,7 @@ namespace LibHac.Os.Impl
}
_tickFrequency = Stopwatch.Frequency;
_startTick = startTick;
_tickGenerator = tickGenerator;
long nanoSecondsPerSecond = TimeSpan.FromSeconds(1).GetNanoSeconds();
@ -43,8 +43,8 @@ namespace LibHac.Os.Impl
}
}
public Tick GetTick() => new Tick(Stopwatch.GetTimestamp() - _startTick);
public Tick GetSystemTickOrdered() => new Tick(Stopwatch.GetTimestamp() - _startTick);
public Tick GetTick() => _tickGenerator.GetCurrentTick();
public Tick GetSystemTickOrdered() => _tickGenerator.GetCurrentTick();
public long GetTickFrequency() => _tickFrequency;
public long GetMaxTick() => _maxTick;
public long GetMaxTimeSpanNs() => _maxTimeSpan.GetNanoSeconds();

View file

@ -16,7 +16,7 @@ namespace LibHac.Os.Impl
private TickManagerImpl _impl;
public TickManager(long startTick) => _impl = new TickManagerImpl(startTick);
public TickManager(ITickGenerator tickGenerator) => _impl = new TickManagerImpl(tickGenerator);
~TickManager()
{

View file

@ -9,10 +9,10 @@ namespace LibHac.Os
internal OsResourceManager ResourceManager { get; }
// Todo: Use configuration object if/when more options are added
internal OsState(HorizonClient horizonClient, long startTick)
internal OsState(HorizonClient horizonClient, ITickGenerator tickGenerator)
{
Hos = horizonClient;
ResourceManager = new OsResourceManager(startTick);
ResourceManager = new OsResourceManager(tickGenerator);
}
public ProcessId GetCurrentProcessId()

View file

@ -67,17 +67,18 @@ namespace hactoolnet
using (var logger = new ProgressBar())
{
ctx.Logger = logger;
ctx.FsClient = new FileSystemClient(new StopWatchTimeSpanGenerator());
OpenKeySet(ctx);
Horizon horizon = HorizonFactory.CreateWithDefaultFsConfig(new HorizonConfiguration(),
new InMemoryFileSystem(), ctx.KeySet);
ctx.FsClient = horizon.CreatePrivilegedHorizonClient().Fs;
if (ctx.Options.AccessLog != null)
{
logWriter = new StreamWriter(ctx.Options.AccessLog);
var accessLog = new TextWriterAccessLog(logWriter);
ctx.FsClient.SetLocalSystemAccessLogForDebug(true);
ctx.FsClient.SetGlobalAccessLogMode(GlobalAccessLogMode.Log);
ctx.FsClient.SetAccessLogObject(accessLog);
}
if (ctx.Options.ResultLog != null)
@ -88,8 +89,6 @@ namespace hactoolnet
Result.SetLogger(resultLogger);
}
OpenKeySet(ctx);
if (ctx.Options.RunCustom)
{
CustomTask(ctx);

View file

@ -20,7 +20,7 @@ namespace LibHac.Tests.Fs.FileSystemClientTests
config.DeviceOperator = defaultObjects.DeviceOperator;
config.ExternalKeySet = new ExternalKeySet();
Horizon horizon = LibHac.HorizonFactory.CreateWithFsConfig(new StopWatchTimeSpanGenerator(), config);
Horizon horizon = LibHac.HorizonFactory.CreateWithFsConfig(new HorizonConfiguration(), config);
HorizonClient horizonClient = horizon.CreatePrivilegedHorizonClient();

View file

@ -18,7 +18,7 @@ namespace LibHac.Tests
config.DeviceOperator = defaultObjects.DeviceOperator;
config.ExternalKeySet = new ExternalKeySet();
Horizon horizon = LibHac.HorizonFactory.CreateWithFsConfig(new StopWatchTimeSpanGenerator(), config);
Horizon horizon = LibHac.HorizonFactory.CreateWithFsConfig(new HorizonConfiguration(), config);
return horizon;
}