Implement BaseFileSystemService and some related classes

This commit is contained in:
Alex Barney 2024-06-30 10:07:49 -07:00
parent 559b8c89f9
commit e757dff9b9
14 changed files with 719 additions and 94 deletions

View file

@ -0,0 +1,6 @@
namespace LibHac.Fat;
public class FatFileSystem
{
public static bool IsExFatSupported() => false;
}

View file

@ -0,0 +1,36 @@
namespace LibHac.Fat;
public readonly struct FatFormatAttribute
{
public readonly ulong MinimumSectorCount;
public readonly ulong MaximumSectorCount;
public readonly uint HiddenSectorCount;
public readonly short NumHeads;
public readonly short SectorsPerTrack;
public readonly short SectorsPerCluster;
public readonly int FatTableEntrySizeBits;
public readonly FatType FatType;
public readonly uint Reserved;
public FatFormatAttribute(ulong minimumSectorCount, ulong maximumSectorCount, uint hiddenSectorCount,
short numHeads, short sectorsPerTrack, short sectorsPerCluster, int fatTableEntrySizeBits, FatType fatType)
{
MinimumSectorCount = minimumSectorCount;
MaximumSectorCount = maximumSectorCount;
HiddenSectorCount = hiddenSectorCount;
NumHeads = numHeads;
SectorsPerTrack = sectorsPerTrack;
SectorsPerCluster = sectorsPerCluster;
FatTableEntrySizeBits = fatTableEntrySizeBits;
FatType = fatType;
Reserved = 0;
}
}
public enum FatType
{
Fat12 = 0,
Fat16 = 1,
Fat32 = 2,
FatEx = 3
}

View file

@ -0,0 +1,55 @@
using LibHac.Common;
using LibHac.Fs;
namespace LibHac.Fat.Impl;
public static class FatFileSystemStorageAdapter
{
private static readonly FatFormatAttribute[] FormatAttributes =
[
new FatFormatAttribute(minimumSectorCount: 0x1, maximumSectorCount: 0x1000, hiddenSectorCount: 0x10, numHeads: 0x2, sectorsPerTrack: 0x10, sectorsPerCluster: 0x10, fatTableEntrySizeBits: 0xC, FatType.Fat12),
new FatFormatAttribute(minimumSectorCount: 0x1000, maximumSectorCount: 0x4000, hiddenSectorCount: 0x10, numHeads: 0x2, sectorsPerTrack: 0x20, sectorsPerCluster: 0x10, fatTableEntrySizeBits: 0xC, FatType.Fat12),
new FatFormatAttribute(minimumSectorCount: 0x4000, maximumSectorCount: 0x8000, hiddenSectorCount: 0x20, numHeads: 0x2, sectorsPerTrack: 0x20, sectorsPerCluster: 0x20, fatTableEntrySizeBits: 0xC, FatType.Fat12),
new FatFormatAttribute(minimumSectorCount: 0x8000, maximumSectorCount: 0x10000, hiddenSectorCount: 0x20, numHeads: 0x4, sectorsPerTrack: 0x20, sectorsPerCluster: 0x20, fatTableEntrySizeBits: 0xC, FatType.Fat12),
new FatFormatAttribute(minimumSectorCount: 0x10000, maximumSectorCount: 0x20000, hiddenSectorCount: 0x20, numHeads: 0x8, sectorsPerTrack: 0x20, sectorsPerCluster: 0x20, fatTableEntrySizeBits: 0xC, FatType.Fat12),
new FatFormatAttribute(minimumSectorCount: 0x20000, maximumSectorCount: 0x40000, hiddenSectorCount: 0x40, numHeads: 0x8, sectorsPerTrack: 0x20, sectorsPerCluster: 0x20, fatTableEntrySizeBits: 0x10, FatType.Fat16),
new FatFormatAttribute(minimumSectorCount: 0x40000, maximumSectorCount: 0x80000, hiddenSectorCount: 0x40, numHeads: 0x10, sectorsPerTrack: 0x20, sectorsPerCluster: 0x20, fatTableEntrySizeBits: 0x10, FatType.Fat16),
new FatFormatAttribute(minimumSectorCount: 0x80000, maximumSectorCount: 0xFC000, hiddenSectorCount: 0x80, numHeads: 0x10, sectorsPerTrack: 0x3F, sectorsPerCluster: 0x20, fatTableEntrySizeBits: 0x10, FatType.Fat16),
new FatFormatAttribute(minimumSectorCount: 0xFC000, maximumSectorCount: 0x1F8000, hiddenSectorCount: 0x80, numHeads: 0x20, sectorsPerTrack: 0x3F, sectorsPerCluster: 0x20, fatTableEntrySizeBits: 0x10, FatType.Fat16),
new FatFormatAttribute(minimumSectorCount: 0x1F8000, maximumSectorCount: 0x200000, hiddenSectorCount: 0x80, numHeads: 0x40, sectorsPerTrack: 0x3F, sectorsPerCluster: 0x20, fatTableEntrySizeBits: 0x10, FatType.Fat16),
new FatFormatAttribute(minimumSectorCount: 0x200000, maximumSectorCount: 0x3F0000, hiddenSectorCount: 0x80, numHeads: 0x40, sectorsPerTrack: 0x3F, sectorsPerCluster: 0x40, fatTableEntrySizeBits: 0x10, FatType.Fat16),
new FatFormatAttribute(minimumSectorCount: 0x3F0000, maximumSectorCount: 0x400000, hiddenSectorCount: 0x80, numHeads: 0x80, sectorsPerTrack: 0x3F, sectorsPerCluster: 0x40, fatTableEntrySizeBits: 0x10, FatType.Fat16),
new FatFormatAttribute(minimumSectorCount: 0x414400, maximumSectorCount: 0x7E0000, hiddenSectorCount: 0x2000, numHeads: 0x80, sectorsPerTrack: 0x3F, sectorsPerCluster: 0x40, fatTableEntrySizeBits: 0x20, FatType.Fat32),
new FatFormatAttribute(minimumSectorCount: 0x7E0000, maximumSectorCount: 0x4000000, hiddenSectorCount: 0x2000, numHeads: 0xFF, sectorsPerTrack: 0x3F, sectorsPerCluster: 0x40, fatTableEntrySizeBits: 0x20, FatType.Fat32),
new FatFormatAttribute(minimumSectorCount: 0x4040000, maximumSectorCount: 0x10000000, hiddenSectorCount: 0x8000, numHeads: 0xFF, sectorsPerTrack: 0x3F, sectorsPerCluster: 0x100, fatTableEntrySizeBits: 0x20, FatType.FatEx),
new FatFormatAttribute(minimumSectorCount: 0x10000000, maximumSectorCount: 0x40000000, hiddenSectorCount: 0x10000, numHeads: 0xFF, sectorsPerTrack: 0x3F, sectorsPerCluster: 0x200, fatTableEntrySizeBits: 0x20, FatType.FatEx),
new FatFormatAttribute(minimumSectorCount: 0x40000000, maximumSectorCount: 0x100000000, hiddenSectorCount: 0x20000, numHeads: 0xFF, sectorsPerTrack: 0x3F, sectorsPerCluster: 0x400, fatTableEntrySizeBits: 0x20, FatType.FatEx)
];
public static Result FormatDryRun(uint userAreaSectorCount, uint protectedAreaSectorCount)
{
ulong totalSectorCount = (ulong)userAreaSectorCount + protectedAreaSectorCount;
Result res = GetFormatAttributes(out ReadOnlyRef<FatFormatAttribute> attributes, totalSectorCount);
if (res.IsFailure()) return res.Miss();
// Call nn::fat::detail::GetPfFormatParams
return Result.Success;
}
private static Result GetFormatAttributes(out ReadOnlyRef<FatFormatAttribute> outAttributes, ulong sectorCount)
{
for (int i = 0; i < FormatAttributes.Length; i++)
{
ref readonly FatFormatAttribute attribute = ref FormatAttributes[i];
if (attribute.MinimumSectorCount <= sectorCount && attribute.MaximumSectorCount >= sectorCount)
{
outAttributes = new(in attribute);
return Result.Success;
}
}
outAttributes = default;
return ResultFs.FatFsFormatUnsupportedSize.Log();
}
}

View file

@ -1217,7 +1217,7 @@ public static class PathFunctions
/// <returns><see cref="Result.Success"/>: The operation was successful.<br/>
/// <see cref="ResultFs.InvalidArgument"/>: <paramref name="pathBuffer"/> was too small to contain the built path.</returns>
internal static Result SetUpFixedPathSingleEntry(scoped ref Path path, Span<byte> pathBuffer,
ReadOnlySpan<byte> entryName)
scoped ReadOnlySpan<byte> entryName)
{
var sb = new U8StringBuilder(pathBuffer);
sb.Append((byte)'/').Append(entryName);
@ -1225,7 +1225,7 @@ public static class PathFunctions
if (sb.Overflowed)
return ResultFs.InvalidArgument.Log();
return SetUpFixedPath(ref path, pathBuffer);
return SetUpFixedPath(ref path, pathBuffer).Ret();
}
// /%s/%s
@ -1248,7 +1248,7 @@ public static class PathFunctions
if (sb.Overflowed)
return ResultFs.InvalidArgument.Log();
return SetUpFixedPath(ref path, pathBuffer);
return SetUpFixedPath(ref path, pathBuffer).Ret();
}
// /%016llx
@ -1268,7 +1268,7 @@ public static class PathFunctions
if (sb.Overflowed)
return ResultFs.InvalidArgument.Log();
return SetUpFixedPath(ref path, pathBuffer);
return SetUpFixedPath(ref path, pathBuffer).Ret();
}
// /%08x.meta
@ -1290,7 +1290,7 @@ public static class PathFunctions
if (sb.Overflowed)
return ResultFs.InvalidArgument.Log();
return SetUpFixedPath(ref path, pathBuffer);
return SetUpFixedPath(ref path, pathBuffer).Ret();
}
// /saveMeta/%016llx
@ -1312,6 +1312,30 @@ public static class PathFunctions
if (sb.Overflowed)
return ResultFs.InvalidArgument.Log();
return SetUpFixedPath(ref path, pathBuffer);
return SetUpFixedPath(ref path, pathBuffer).Ret();
}
// %s/%08x
/// <summary>
/// Initializes a <see cref="Path"/> using the format string <c>%s/%08x</c>
/// </summary>
/// <param name="path">The <see cref="Path"/> to be initialized.</param>
/// <param name="pathBuffer">The buffer that will contain the built string.</param>
/// <param name="entryName">The first entry in the generated path.</param>
/// <param name="value">The value to insert into the path.</param>
/// <returns><see cref="Result.Success"/>: The operation was successful.<br/>
/// <see cref="ResultFs.InvalidArgument"/>: <paramref name="pathBuffer"/> was too small to contain the built path.<br/>
/// <see cref="ResultFs.InvalidCharacter"/>: The path contains an invalid character.<br/>
/// <see cref="ResultFs.InvalidPathFormat"/>: The path is in an invalid format or is not normalized.</returns>
internal static Result SetUpFixedPathEntryWithInt(scoped ref Path path, Span<byte> pathBuffer, scoped ReadOnlySpan<byte> entryName, int value)
{
var sb = new U8StringBuilder(pathBuffer);
sb.Append(entryName)
.Append((byte)'/').AppendFormat(value, 'x', 8);
if (sb.Overflowed)
return ResultFs.InvalidArgument.Log();
return SetUpFixedPath(ref path, pathBuffer).Ret();
}
}

View file

@ -9,4 +9,7 @@ internal static class CommonDirNames
/// <summary>"<c>Contents</c>"</summary>
public static ReadOnlySpan<byte> ContentStorageDirectoryName => "Contents"u8;
/// <summary>"<c>Album</c>"</summary>
public static ReadOnlySpan<byte> ImageDirectoryName => "Album"u8;
}

View file

@ -4,6 +4,7 @@ using LibHac.FsSrv.Impl;
using LibHac.FsSrv.Sf;
using LibHac.FsSystem;
using LibHac.Sf;
using static LibHac.FsSrv.Anonymous;
using IFileSystem = LibHac.Fs.Fsa.IFileSystem;
using IFileSystemSf = LibHac.FsSrv.Sf.IFileSystem;
using Path = LibHac.Fs.Path;
@ -11,44 +12,31 @@ using Utility = LibHac.FsSrv.Impl.Utility;
namespace LibHac.FsSrv;
public readonly struct BaseFileSystemService
file static class Anonymous
{
private readonly BaseFileSystemServiceImpl _serviceImpl;
private readonly ulong _processId;
public BaseFileSystemService(BaseFileSystemServiceImpl serviceImpl, ulong processId)
public static Result GetProgramInfo(FileSystemServer fsServer, out ProgramInfo programInfo, ulong processId)
{
_serviceImpl = serviceImpl;
_processId = processId;
var programRegistry = new ProgramRegistryImpl(fsServer);
return programRegistry.GetProgramInfo(out programInfo, processId).Ret();
}
private Result GetProgramInfo(out ProgramInfo programInfo)
public static Result CheckCapabilityById(FileSystemServer fsServer, BaseFileSystemId id, ulong processId)
{
return GetProgramInfo(out programInfo, _processId);
}
private Result GetProgramInfo(out ProgramInfo programInfo, ulong processId)
{
return _serviceImpl.GetProgramInfo(out programInfo, processId);
}
private Result CheckCapabilityById(BaseFileSystemId id, ulong processId)
{
Result res = GetProgramInfo(out ProgramInfo programInfo, processId);
Result res = GetProgramInfo(fsServer, out ProgramInfo programInfo, processId);
if (res.IsFailure()) return res.Miss();
AccessControl accessControl = programInfo.AccessControl;
if (id == BaseFileSystemId.TemporaryDirectory)
{
Accessibility accessibility =
programInfo.AccessControl.GetAccessibilityFor(AccessibilityType.MountTemporaryDirectory);
Accessibility accessibility = accessControl.GetAccessibilityFor(AccessibilityType.MountTemporaryDirectory);
if (!accessibility.CanRead || !accessibility.CanWrite)
return ResultFs.PermissionDenied.Log();
}
else
{
Accessibility accessibility =
programInfo.AccessControl.GetAccessibilityFor(AccessibilityType.MountAllBaseFileSystem);
Accessibility accessibility = accessControl.GetAccessibilityFor(AccessibilityType.MountAllBaseFileSystem);
if (!accessibility.CanRead || !accessibility.CanWrite)
return ResultFs.PermissionDenied.Log();
@ -56,10 +44,28 @@ public readonly struct BaseFileSystemService
return Result.Success;
}
}
/// <summary>
/// Handles managing and opening file systems that aren't NCAs or save data.
/// </summary>
/// <remarks>Based on nnSdk 18.3.0 (FS 18.0.0)</remarks>
public readonly struct BaseFileSystemService
{
private readonly BaseFileSystemServiceImpl _serviceImpl;
private readonly ulong _processId;
private FileSystemServer FsServer => _serviceImpl.FsServer;
public BaseFileSystemService(BaseFileSystemServiceImpl serviceImpl, ulong processId)
{
_serviceImpl = serviceImpl;
_processId = processId;
}
public Result OpenBaseFileSystem(ref SharedRef<IFileSystemSf> outFileSystem, BaseFileSystemId fileSystemId)
{
Result res = CheckCapabilityById(fileSystemId, _processId);
Result res = CheckCapabilityById(FsServer, fileSystemId, _processId);
if (res.IsFailure()) return res.Miss();
// Open the file system
@ -68,8 +74,7 @@ public readonly struct BaseFileSystemService
if (res.IsFailure()) return res.Miss();
// Create an SF adapter for the file system
using SharedRef<IFileSystemSf> fileSystemAdapter = FileSystemInterfaceAdapter.CreateShared(in fileSystem, false);
using SharedRef<IFileSystemSf> fileSystemAdapter = FileSystemInterfaceAdapter.CreateShared(in fileSystem, allowAllOperations: false);
outFileSystem.SetByMove(ref fileSystemAdapter.Ref);
return Result.Success;
@ -77,38 +82,72 @@ public readonly struct BaseFileSystemService
public Result FormatBaseFileSystem(BaseFileSystemId fileSystemId)
{
Result res = CheckCapabilityById(fileSystemId, _processId);
Result res = CheckCapabilityById(FsServer, fileSystemId, _processId);
if (res.IsFailure()) return res.Miss();
return _serviceImpl.FormatBaseFileSystem(fileSystemId);
return _serviceImpl.FormatBaseFileSystem(fileSystemId).Ret();
}
public Result OpenBisFileSystem(ref SharedRef<IFileSystemSf> outFileSystem, ref readonly FspPath rootPath,
BisPartitionId partitionId)
{
Result res = GetProgramInfo(out ProgramInfo programInfo);
Result res = GetProgramInfo(FsServer, out ProgramInfo programInfo, _processId);
if (res.IsFailure()) return res.Miss();
// Get the permissions the caller needs
AccessibilityType requiredAccess = partitionId switch
{
BisPartitionId.CalibrationFile => AccessibilityType.MountBisCalibrationFile,
BisPartitionId.SafeMode => AccessibilityType.MountBisSafeMode,
BisPartitionId.User => AccessibilityType.MountBisUser,
BisPartitionId.System => AccessibilityType.MountBisSystem,
BisPartitionId.SystemProperPartition => AccessibilityType.MountBisSystemProperPartition,
_ => AccessibilityType.NotMount
};
// Reject opening invalid partitions
if (requiredAccess == AccessibilityType.NotMount)
return ResultFs.InvalidArgument.Log();
// Verify the caller has the required permissions
Accessibility accessibility = programInfo.AccessControl.GetAccessibilityFor(requiredAccess);
switch (partitionId)
{
case BisPartitionId.CalibrationFile:
{
Accessibility accessibility = programInfo.AccessControl.GetAccessibilityFor(AccessibilityType.MountBisCalibrationFile);
if (!accessibility.CanRead || !accessibility.CanWrite)
return ResultFs.PermissionDenied.Log();
if (!accessibility.CanRead || !accessibility.CanWrite)
return ResultFs.PermissionDenied.Log();
break;
}
case BisPartitionId.SafeMode:
{
Accessibility accessibility = programInfo.AccessControl.GetAccessibilityFor(AccessibilityType.MountBisSafeMode);
if (!accessibility.CanRead || !accessibility.CanWrite)
return ResultFs.PermissionDenied.Log();
break;
}
case BisPartitionId.System:
{
Accessibility accessibility = programInfo.AccessControl.GetAccessibilityFor(AccessibilityType.MountBisSystem);
if (!accessibility.CanRead || !accessibility.CanWrite)
return ResultFs.PermissionDenied.Log();
break;
}
case BisPartitionId.System0:
{
Accessibility accessibility = programInfo.AccessControl.GetAccessibilityFor(AccessibilityType.MountBisSystem);
if (!accessibility.CanRead || !accessibility.CanWrite)
return ResultFs.PermissionDenied.Log();
break;
}
case BisPartitionId.User:
{
Accessibility accessibility = programInfo.AccessControl.GetAccessibilityFor(AccessibilityType.MountBisUser);
if (!accessibility.CanRead || !accessibility.CanWrite)
return ResultFs.PermissionDenied.Log();
break;
}
case BisPartitionId.SystemProperPartition:
{
Accessibility accessibility = programInfo.AccessControl.GetAccessibilityFor(AccessibilityType.MountBisSystemProperPartition);
if (!accessibility.CanRead || !accessibility.CanWrite)
return ResultFs.PermissionDenied.Log();
break;
}
default:
return ResultFs.InvalidArgument.Log();
}
const StorageLayoutType storageFlag = StorageLayoutType.Bis;
using var scopedContext = new ScopedStorageLayoutTypeSetter(storageFlag);
@ -125,7 +164,7 @@ public readonly struct BaseFileSystemService
// Open the file system
using var fileSystem = new SharedRef<IFileSystem>();
res = _serviceImpl.OpenBisFileSystem(ref fileSystem.Ref, partitionId, false);
res = _serviceImpl.OpenBisFileSystem(ref fileSystem.Ref, partitionId, caseSensitive: false);
if (res.IsFailure()) return res.Miss();
using var subDirFileSystem = new SharedRef<IFileSystem>();
@ -149,46 +188,46 @@ public readonly struct BaseFileSystemService
return ResultFs.InvalidSize.Log();
// Caller must have the FillBis permission
Result res = GetProgramInfo(out ProgramInfo programInfo);
Result res = GetProgramInfo(FsServer, out ProgramInfo programInfo, _processId);
if (res.IsFailure()) return res.Miss();
if (!programInfo.AccessControl.CanCall(OperationType.FillBis))
return ResultFs.PermissionDenied.Log();
return _serviceImpl.CreatePaddingFile(size);
using var scopedContext = new ScopedStorageLayoutTypeSetter(StorageLayoutType.Bis);
return _serviceImpl.CreatePaddingFile(size).Ret();
}
public Result DeleteAllPaddingFiles()
{
// Caller must have the FillBis permission
Result res = GetProgramInfo(out ProgramInfo programInfo);
Result res = GetProgramInfo(FsServer, out ProgramInfo programInfo, _processId);
if (res.IsFailure()) return res.Miss();
if (!programInfo.AccessControl.CanCall(OperationType.FillBis))
return ResultFs.PermissionDenied.Log();
return _serviceImpl.DeleteAllPaddingFiles();
using var scopedContext = new ScopedStorageLayoutTypeSetter(StorageLayoutType.Bis);
return _serviceImpl.DeleteAllPaddingFiles().Ret();
}
public Result OpenGameCardFileSystem(ref SharedRef<IFileSystemSf> outFileSystem, GameCardHandle handle,
GameCardPartition partitionId)
{
Result res = GetProgramInfo(out ProgramInfo programInfo);
Result res = GetProgramInfo(FsServer, out ProgramInfo programInfo, _processId);
if (res.IsFailure()) return res.Miss();
if (!programInfo.AccessControl.GetAccessibilityFor(AccessibilityType.MountGameCard).CanRead)
return ResultFs.PermissionDenied.Log();
using var fileSystem = new SharedRef<IFileSystem>();
res = _serviceImpl.OpenGameCardFileSystem(ref fileSystem.Ref, handle, partitionId);
if (res.IsFailure()) return res.Miss();
using var asyncFileSystem =
new SharedRef<IFileSystem>(new AsynchronousAccessFileSystem(in fileSystem));
using SharedRef<IFileSystemSf> fileSystemAdapter =
FileSystemInterfaceAdapter.CreateShared(in asyncFileSystem, false);
using var asyncFileSystem = new SharedRef<IFileSystem>(new AsynchronousAccessFileSystem(in fileSystem));
using SharedRef<IFileSystemSf> fileSystemAdapter = FileSystemInterfaceAdapter.CreateShared(in asyncFileSystem, allowAllOperations: false);
outFileSystem.SetByMove(ref fileSystemAdapter.Ref);
@ -197,7 +236,7 @@ public readonly struct BaseFileSystemService
public Result OpenSdCardFileSystem(ref SharedRef<IFileSystemSf> outFileSystem)
{
Result res = GetProgramInfo(out ProgramInfo programInfo);
Result res = GetProgramInfo(FsServer, out ProgramInfo programInfo, _processId);
if (res.IsFailure()) return res.Miss();
Accessibility accessibility = programInfo.AccessControl.GetAccessibilityFor(AccessibilityType.MountSdCard);
@ -205,7 +244,7 @@ public readonly struct BaseFileSystemService
if (!accessibility.CanRead || !accessibility.CanWrite)
return ResultFs.PermissionDenied.Log();
const StorageLayoutType storageFlag = StorageLayoutType.Bis;
const StorageLayoutType storageFlag = StorageLayoutType.SdCard;
using var scopedContext = new ScopedStorageLayoutTypeSetter(storageFlag);
using var fileSystem = new SharedRef<IFileSystem>();
@ -225,20 +264,24 @@ public readonly struct BaseFileSystemService
public Result FormatSdCardFileSystem()
{
// Caller must have the FormatSdCard permission
Result res = GetProgramInfo(out ProgramInfo programInfo);
Result res = GetProgramInfo(FsServer, out ProgramInfo programInfo, _processId);
if (res.IsFailure()) return res.Miss();
if (!programInfo.AccessControl.CanCall(OperationType.FormatSdCard))
return ResultFs.PermissionDenied.Log();
return _serviceImpl.FormatSdCardProxyFileSystem();
using var scopedContext = new ScopedStorageLayoutTypeSetter(StorageLayoutType.SdCard);
return _serviceImpl.FormatSdCardProxyFileSystem().Ret();
}
public Result FormatSdCardDryRun()
{
// No permissions are needed to call this method
return _serviceImpl.FormatSdCardProxyFileSystem();
using var scopedContext = new ScopedStorageLayoutTypeSetter(StorageLayoutType.SdCard);
return _serviceImpl.FormatSdCardDryRun().Ret();
}
public Result IsExFatSupported(out bool isSupported)
@ -251,8 +294,11 @@ public readonly struct BaseFileSystemService
public Result OpenImageDirectoryFileSystem(ref SharedRef<IFileSystemSf> outFileSystem, ImageDirectoryId directoryId)
{
const StorageLayoutType storageFlag = StorageLayoutType.NonGameCard;
using var scopedContext = new ScopedStorageLayoutTypeSetter(storageFlag);
// Caller must have the MountImageAndVideoStorage permission
Result res = GetProgramInfo(out ProgramInfo programInfo);
Result res = GetProgramInfo(FsServer, out ProgramInfo programInfo, _processId);
if (res.IsFailure()) return res.Miss();
Accessibility accessibility =
@ -280,7 +326,7 @@ public readonly struct BaseFileSystemService
if (res.IsFailure()) return res.Miss();
using SharedRef<IFileSystemSf> fileSystemAdapter =
FileSystemInterfaceAdapter.CreateShared(in baseFileSystem, false);
FileSystemInterfaceAdapter.CreateShared(in baseFileSystem, allowAllOperations: false);
outFileSystem.SetByMove(ref fileSystemAdapter.Ref);
@ -290,8 +336,11 @@ public readonly struct BaseFileSystemService
public Result OpenBisWiper(ref SharedRef<IWiper> outBisWiper, NativeHandle transferMemoryHandle,
ulong transferMemorySize)
{
const StorageLayoutType storageFlag = StorageLayoutType.Bis;
using var scopedContext = new ScopedStorageLayoutTypeSetter(storageFlag);
// Caller must have the OpenBisWiper permission
Result res = GetProgramInfo(out ProgramInfo programInfo);
Result res = GetProgramInfo(FsServer, out ProgramInfo programInfo, _processId);
if (res.IsFailure()) return res.Miss();
if (!programInfo.AccessControl.CanCall(OperationType.OpenBisWiper))

View file

@ -1,11 +1,17 @@
using System;
using LibHac.Common;
using LibHac.Fat;
using LibHac.Fs;
using LibHac.Fs.Fsa;
using LibHac.FsSrv.FsCreator;
using LibHac.FsSrv.Impl;
using LibHac.FsSrv.Sf;
using LibHac.FsSrv.Storage;
using LibHac.Os;
using LibHac.Sf;
using IFileSystem = LibHac.Fs.Fsa.IFileSystem;
using Path = LibHac.Fs.Path;
using Utility = LibHac.FsSystem.Utility;
namespace LibHac.FsSrv;
@ -13,12 +19,18 @@ public class BaseFileSystemServiceImpl
{
private Configuration _config;
public FileSystemServer FsServer => _config.FsServer;
private static ReadOnlySpan<byte> PaddingDirectoryName => "/Padding"u8;
private const int PaddingFileCountMax = 0x10000000;
public delegate Result BisWiperCreator(ref UniqueRef<IWiper> outWiper, NativeHandle transferMemoryHandle,
ulong transferMemorySize);
public BaseFileSystemServiceImpl(in Configuration configuration)
{
_config = configuration;
// nn::fat::SetCurrentTimeStampCallback(configuration.CurrentTimeFunction);
}
public struct Configuration
@ -27,8 +39,8 @@ public class BaseFileSystemServiceImpl
public IGameCardFileSystemCreator GameCardFileSystemCreator;
public ISdCardProxyFileSystemCreator SdCardFileSystemCreator;
// CurrentTimeFunction
// FatFileSystemCacheManager
// BaseFileSystemCreatorHolder
public FatFileSystemCacheManager FatFileSystemCacheManager;
public BaseFileSystemCreatorHolder BaseFileSystemCreatorHolder;
public BisWiperCreator BisWiperCreator;
// LibHac additions
@ -37,36 +49,97 @@ public class BaseFileSystemServiceImpl
public Result OpenBaseFileSystem(ref SharedRef<IFileSystem> outFileSystem, BaseFileSystemId fileSystemId)
{
throw new NotImplementedException();
Result res = _config.BaseFileSystemCreatorHolder.Get(out IBaseFileSystemCreator baseFsCreator, fileSystemId);
if (res.IsFailure()) return res.Miss();
return baseFsCreator.Create(ref outFileSystem, fileSystemId).Ret();
}
public Result FormatBaseFileSystem(BaseFileSystemId fileSystemId)
{
throw new NotImplementedException();
Result res = _config.BaseFileSystemCreatorHolder.Get(out IBaseFileSystemCreator baseFsCreator, fileSystemId);
if (res.IsFailure()) return res.Miss();
return baseFsCreator.Format(fileSystemId).Ret();
}
public Result OpenBisFileSystem(ref SharedRef<IFileSystem> outFileSystem, BisPartitionId partitionId)
{
return OpenBisFileSystem(ref outFileSystem, partitionId, false);
return OpenBisFileSystem(ref outFileSystem, partitionId, caseSensitive: false).Ret();
}
public Result OpenBisFileSystem(ref SharedRef<IFileSystem> outFileSystem, BisPartitionId partitionId,
bool caseSensitive)
{
Result res = _config.BisFileSystemCreator.Create(ref outFileSystem, partitionId, caseSensitive);
if (res.IsFailure()) return res.Miss();
return Result.Success;
return _config.BisFileSystemCreator.Create(ref outFileSystem, partitionId, caseSensitive).Ret();
}
public Result CreatePaddingFile(long size)
{
throw new NotImplementedException();
using var fileSystem = new SharedRef<IFileSystem>();
Result res = OpenBisFileSystem(ref fileSystem.Ref, BisPartitionId.User);
if (res.IsFailure()) return res.Miss();
using var pathPaddingDirectory = new Path();
res = PathFunctions.SetUpFixedPath(ref pathPaddingDirectory.Ref(), PaddingDirectoryName);
if (res.IsFailure()) return res.Miss();
res = Utility.EnsureDirectory(fileSystem.Get, in pathPaddingDirectory);
if (res.IsFailure()) return res.Miss();
Span<byte> pathPaddingFileBuffer = stackalloc byte[0x80];
for (int i = 0; i < PaddingFileCountMax; i++)
{
using scoped var pathPaddingFile = new Path();
res = PathFunctions.SetUpFixedPathEntryWithInt(ref pathPaddingFile.Ref(), pathPaddingFileBuffer, PaddingDirectoryName, i);
if (res.IsFailure()) return res.Miss();
res = fileSystem.Get.CreateFile(in pathPaddingFile, size, CreateFileOptions.CreateConcatenationFile);
if (!res.IsSuccess())
{
if (ResultFs.PathAlreadyExists.Includes(res))
{
res.Catch().Handle();
}
else
{
return res.Miss();
}
}
else
{
return Result.Success;
}
}
return ResultFs.PathAlreadyExists.Log();
}
public Result DeleteAllPaddingFiles()
{
throw new NotImplementedException();
using var fileSystem = new SharedRef<IFileSystem>();
Result res = OpenBisFileSystem(ref fileSystem.Ref, BisPartitionId.User);
if (res.IsFailure()) return res.Miss();
using var pathPaddingDirectory = new Path();
res = PathFunctions.SetUpFixedPath(ref pathPaddingDirectory.Ref(), PaddingDirectoryName);
if (res.IsFailure()) return res.Miss();
res = fileSystem.Get.DeleteDirectoryRecursively(in pathPaddingDirectory);
if (!res.IsSuccess())
{
if (ResultFs.PathNotFound.Includes(res))
{
res.Catch().Handle();
}
else
{
return res.Miss();
}
}
return Result.Success;
}
public Result OpenGameCardFileSystem(ref SharedRef<IFileSystem> outFileSystem, GameCardHandle handle,
@ -83,44 +156,69 @@ public class BaseFileSystemServiceImpl
break;
}
return res;
if (res.IsFailure()) return res.Miss();
return Result.Success;
}
public Result OpenSdCardProxyFileSystem(ref SharedRef<IFileSystem> outFileSystem)
{
return OpenSdCardProxyFileSystem(ref outFileSystem, false);
return OpenSdCardProxyFileSystem(ref outFileSystem, openCaseSensitive: false).Ret();
}
public Result OpenSdCardProxyFileSystem(ref SharedRef<IFileSystem> outFileSystem, bool openCaseSensitive)
{
return _config.SdCardFileSystemCreator.Create(ref outFileSystem, openCaseSensitive);
return _config.SdCardFileSystemCreator.Create(ref outFileSystem, openCaseSensitive).Ret();
}
public Result FormatSdCardProxyFileSystem()
{
return _config.SdCardFileSystemCreator.Format();
return _config.SdCardFileSystemCreator.Format().Ret();
}
public Result FormatSdCardDryRun()
{
throw new NotImplementedException();
Result res = FsServer.Storage.GetSdCardUserAreaNumSectors(out uint userAreaSectors);
if (res.IsFailure()) return res.Miss();
res = FsServer.Storage.GetSdCardProtectedAreaNumSectors(out uint protectedAreaSectors);
if (res.IsFailure()) return res.Miss();
return Fat.Impl.FatFileSystemStorageAdapter.FormatDryRun(userAreaSectors, protectedAreaSectors).Ret();
}
public bool IsExFatSupported()
{
// Returning false should probably be fine
return false;
return FatFileSystem.IsExFatSupported();
}
public void FlushFatCache()
{
using UniqueLock<SdkRecursiveMutex> scopedLock = _config.FatFileSystemCacheManager.GetScopedLock();
FatFileSystemCacheManager.Iterator iter = _config.FatFileSystemCacheManager.GetIterator();
while (!iter.IsEnd())
{
using SharedRef<IFileSystem> fileSystem = iter.Get();
if (fileSystem.HasValue)
{
fileSystem.Get.Flush().IgnoreResult();
}
iter.Next();
}
}
public Result OpenBisWiper(ref UniqueRef<IWiper> outBisWiper, NativeHandle transferMemoryHandle,
ulong transferMemorySize)
{
return _config.BisWiperCreator(ref outBisWiper, transferMemoryHandle, transferMemorySize);
return _config.BisWiperCreator(ref outBisWiper, transferMemoryHandle, transferMemorySize).Ret();
}
internal Result GetProgramInfo(out ProgramInfo programInfo, ulong processId)
{
var registry = new ProgramRegistryImpl(_config.FsServer);
return registry.GetProgramInfo(out programInfo, processId);
return registry.GetProgramInfo(out programInfo, processId).Ret();
}
}

View file

@ -0,0 +1,43 @@
using System;
using System.Collections.Generic;
using LibHac.Common;
using LibHac.Fs;
using LibHac.Fs.Fsa;
namespace LibHac.FsSrv.FsCreator;
public interface IBaseFileSystemCreator
{
Result Create(ref SharedRef<IFileSystem> outFileSystem, BaseFileSystemId id);
Result Format(BaseFileSystemId id);
}
public class BaseFileSystemCreatorHolder : IDisposable
{
private Dictionary<BaseFileSystemId, IBaseFileSystemCreator> _creators;
public BaseFileSystemCreatorHolder()
{
_creators = new Dictionary<BaseFileSystemId, IBaseFileSystemCreator>();
}
public void Dispose()
{
throw new NotImplementedException();
}
public Result Get(out IBaseFileSystemCreator outCreator, BaseFileSystemId id)
{
if (!_creators.TryGetValue(id, out outCreator))
{
return ResultFs.StorageDeviceInvalidOperation.Log();
}
return Result.Success;
}
public void Register(IBaseFileSystemCreator creator, BaseFileSystemId id)
{
_creators.TryAdd(id, creator);
}
}

View file

@ -0,0 +1,59 @@
using LibHac.Common;
using LibHac.Fs;
using LibHac.Fs.Fsa;
using LibHac.FsSrv.Impl;
using LibHac.FsSystem;
using Utility = LibHac.FsSystem.Utility;
namespace LibHac.FsSrv.FsCreator;
public class BisFsSubDirectoryFileSystemCreator : IBaseFileSystemCreator
{
private IBuiltInStorageFileSystemCreator _bisFileSystemCreator;
private BisPartitionId _partitionId;
private ISubDirectoryFileSystemCreator _subDirectoryFileSystemCreator;
private U8String _path;
public BisFsSubDirectoryFileSystemCreator(IBuiltInStorageFileSystemCreator bisFileSystemCreator,
BisPartitionId partitionId, ISubDirectoryFileSystemCreator subDirectoryFileSystemCreator, U8Span path)
{
_bisFileSystemCreator = bisFileSystemCreator;
_partitionId = partitionId;
_subDirectoryFileSystemCreator = subDirectoryFileSystemCreator;
_path = path.ToU8String();
}
public Result Create(ref SharedRef<IFileSystem> outFileSystem, BaseFileSystemId id)
{
using var fileSystem = new SharedRef<IFileSystem>();
using var subDirPath = new Path();
Result res = PathFunctions.SetUpFixedPath(ref subDirPath.Ref(), _path);
if (res.IsFailure()) return res.Miss();
// Open the base file system
res = _bisFileSystemCreator.Create(ref fileSystem.Ref, _partitionId);
if (res.IsFailure()) return res.Miss();
// Ensure the subdirectory exists and get an IFileSystem over it
res = Utility.EnsureDirectory(fileSystem.Get, in subDirPath);
if (res.IsFailure()) return res.Miss();
using var subDirFs = new SharedRef<IFileSystem>();
res = _subDirectoryFileSystemCreator.Create(ref subDirFs.Ref, in fileSystem, in subDirPath);
if (res.IsFailure()) return res.Miss();
// Add all the file system wrappers
using var typeSetFileSystem = new SharedRef<IFileSystem>(new StorageLayoutTypeSetFileSystem(in fileSystem, StorageLayoutType.Bis));
using var asyncFileSystem = new SharedRef<IFileSystem>(new AsynchronousAccessFileSystem(in typeSetFileSystem));
outFileSystem.SetByMove(ref asyncFileSystem.Ref);
return Result.Success;
}
public Result Format(BaseFileSystemId id)
{
return ResultFs.NotImplemented.Log();
}
}

View file

@ -7,4 +7,9 @@ namespace LibHac.FsSrv.FsCreator;
public interface IBuiltInStorageFileSystemCreator
{
Result Create(ref SharedRef<IFileSystem> outFileSystem, BisPartitionId partitionId, bool caseSensitive);
public Result Create(ref SharedRef<IFileSystem> outFileSystem, BisPartitionId partitionId)
{
return Create(ref outFileSystem, partitionId, caseSensitive: false).Ret();
}
}

View file

@ -20,4 +20,9 @@ public interface ISdCardProxyFileSystemCreator
/// </summary>
/// <returns>The <see cref="Result"/> of the operation.</returns>
Result Format();
public Result Create(ref SharedRef<IFileSystem> outFileSystem)
{
return Create(ref outFileSystem, openCaseSensitive: false).Ret();
}
}

View file

@ -0,0 +1,85 @@
using System;
using LibHac.Common;
using LibHac.Fs;
using LibHac.Fs.Fsa;
using LibHac.FsSrv.Impl;
using LibHac.FsSystem;
using Utility = LibHac.FsSystem.Utility;
namespace LibHac.FsSrv.FsCreator;
public class ImageDirectoryFileSystemCreator : IBaseFileSystemCreator
{
private Configuration _config;
public struct Configuration
{
public IBuiltInStorageFileSystemCreator BisFileSystemCreator;
public ISdCardProxyFileSystemCreator SdCardFileSystemCreator;
public ILocalFileSystemCreator LocalFileSystemCreator;
public ISubDirectoryFileSystemCreator SubDirectoryFileSystemCreator;
}
public ImageDirectoryFileSystemCreator(in Configuration config)
{
_config = config;
}
public Result Create(ref SharedRef<IFileSystem> outFileSystem, BaseFileSystemId id)
{
Result res;
using var fileSystem = new SharedRef<IFileSystem>();
using scoped var imageDirectoryPath = new Path();
Span<byte> imageDirectoryPathBuffer = stackalloc byte[0x40];
// Open the base file system containing the image directory, and get the path of the image directory
switch (id)
{
case BaseFileSystemId.ImageDirectoryNand:
{
res = _config.BisFileSystemCreator.Create(ref fileSystem.Ref, BisPartitionId.User);
if (res.IsFailure()) return res.Miss();
res = PathFunctions.SetUpFixedPathSingleEntry(ref imageDirectoryPath.Ref(), imageDirectoryPathBuffer,
CommonDirNames.ImageDirectoryName);
if (res.IsFailure()) return res.Miss();
break;
}
case BaseFileSystemId.ImageDirectorySdCard:
{
res = _config.SdCardFileSystemCreator.Create(ref fileSystem.Ref);
if (res.IsFailure()) return res.Miss();
res = PathFunctions.SetUpFixedPathDoubleEntry(ref imageDirectoryPath.Ref(), imageDirectoryPathBuffer,
CommonDirNames.SdCardNintendoRootDirectoryName, CommonDirNames.ImageDirectoryName);
if (res.IsFailure()) return res.Miss();
break;
}
default:
return ResultFs.InvalidArgument.Log();
}
res = Utility.EnsureDirectory(fileSystem.Get, in imageDirectoryPath);
if (res.IsFailure()) return res.Miss();
using var subDirFs = new SharedRef<IFileSystem>();
res = _config.SubDirectoryFileSystemCreator.Create(ref subDirFs.Ref, in fileSystem, in imageDirectoryPath);
if (res.IsFailure()) return res.Miss();
const StorageLayoutType storageFlag = StorageLayoutType.NonGameCard;
using var typeSetFileSystem = new SharedRef<IFileSystem>(new StorageLayoutTypeSetFileSystem(in subDirFs, storageFlag));
using var asyncFileSystem = new SharedRef<IFileSystem>(new AsynchronousAccessFileSystem(in typeSetFileSystem));
outFileSystem.SetByMove(ref asyncFileSystem.Ref);
return Result.Success;
}
public Result Format(BaseFileSystemId id)
{
return ResultFs.NotImplemented.Log();
}
}

View file

@ -0,0 +1,152 @@
using System;
using System.Collections.Generic;
using LibHac.Common;
using LibHac.Diag;
using LibHac.Fs.Fsa;
using LibHac.Os;
namespace LibHac.FsSrv.Impl;
/// <summary>
/// Caches opened FatFileSystems.
/// </summary>
/// <remarks>Based on nnSdk 18.3.0 (FS 18.0.0)</remarks>
public class FatFileSystemCacheManager : IDisposable
{
public readonly struct CacheId
{
internal readonly int Value;
internal CacheId(int value) => Value = value;
}
private struct CacheNode : IDisposable
{
public int Id;
public SharedRef<IFileSystem> FileSystem;
public CacheNode(int id, ref readonly SharedRef<IFileSystem> fileSystem)
{
Id = id;
FileSystem = SharedRef<IFileSystem>.CreateCopy(in fileSystem);
}
public void Dispose()
{
FileSystem.Destroy();
}
}
public struct Iterator
{
private LinkedListNode<CacheNode> _current;
internal Iterator(FatFileSystemCacheManager manager)
{
_current = manager._cache.First;
}
public readonly bool IsEnd() => _current is not null;
public void Next() => _current = _current.Next;
public readonly SharedRef<IFileSystem> Get() => SharedRef<IFileSystem>.CreateCopy(in _current.ValueRef.FileSystem);
}
private SdkRecursiveMutex _mutex;
private int _nextCacheId;
private LinkedList<CacheNode> _cache;
public FatFileSystemCacheManager()
{
_mutex = new SdkRecursiveMutex();
_nextCacheId = 0;
_cache = new LinkedList<CacheNode>();
}
public void Dispose()
{
while (_cache.First is not null)
{
LinkedListNode<CacheNode> currentNode = _cache.First;
_cache.Remove(currentNode);
currentNode.ValueRef.Dispose();
}
}
public UniqueLock<SdkRecursiveMutex> GetScopedLock()
{
return new UniqueLock<SdkRecursiveMutex>(_mutex);
}
private SharedRef<IFileSystem> GetCache(int cacheId)
{
LinkedListNode<CacheNode> currentNode = _cache.First;
while (currentNode is not null)
{
if (currentNode.ValueRef.Id == cacheId)
{
return SharedRef<IFileSystem>.CreateCopy(in currentNode.ValueRef.FileSystem);
}
currentNode = currentNode.Next;
}
return new SharedRef<IFileSystem>();
}
public SharedRef<IFileSystem> GetCache(CacheId cacheId)
{
return GetCache(cacheId.Value);
}
public Result SetCache(out CacheId outCacheId, ref readonly SharedRef<IFileSystem> fileSystem)
{
int originalId = _nextCacheId;
while (true)
{
bool cacheIdInUse;
using (SharedRef<IFileSystem> fs = GetCache(_nextCacheId))
{
cacheIdInUse = fs.HasValue;
}
if (!cacheIdInUse)
break;
_nextCacheId++;
if (_nextCacheId == originalId)
Abort.DoAbort();
}
int id = _nextCacheId;
var node = new LinkedListNode<CacheNode>(new CacheNode(_nextCacheId, in fileSystem));
_cache.AddLast(node);
_nextCacheId++;
outCacheId = new CacheId(id);
return Result.Success;
}
public void UnsetCache(CacheId cacheId)
{
LinkedListNode<CacheNode> currentNode = _cache.First;
while (currentNode is not null)
{
if (currentNode.ValueRef.Id == cacheId.Value)
{
_cache.Remove(currentNode);
currentNode.ValueRef.Dispose();
return;
}
currentNode = currentNode.Next;
}
}
public Iterator GetIterator()
{
return new Iterator(this);
}
}

View file

@ -81,7 +81,7 @@ public struct SdkMutexType : ILockable
}
}
public class SdkRecursiveMutex : IBasicLockable
public class SdkRecursiveMutex : ILockable
{
private SdkRecursiveMutexType _impl;
@ -95,6 +95,11 @@ public class SdkRecursiveMutex : IBasicLockable
_impl.Lock();
}
public bool TryLock()
{
return _impl.TryLock();
}
public void Unlock()
{
_impl.Unlock();