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/> /// <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> /// <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, internal static Result SetUpFixedPathSingleEntry(scoped ref Path path, Span<byte> pathBuffer,
ReadOnlySpan<byte> entryName) scoped ReadOnlySpan<byte> entryName)
{ {
var sb = new U8StringBuilder(pathBuffer); var sb = new U8StringBuilder(pathBuffer);
sb.Append((byte)'/').Append(entryName); sb.Append((byte)'/').Append(entryName);
@ -1225,7 +1225,7 @@ public static class PathFunctions
if (sb.Overflowed) if (sb.Overflowed)
return ResultFs.InvalidArgument.Log(); return ResultFs.InvalidArgument.Log();
return SetUpFixedPath(ref path, pathBuffer); return SetUpFixedPath(ref path, pathBuffer).Ret();
} }
// /%s/%s // /%s/%s
@ -1248,7 +1248,7 @@ public static class PathFunctions
if (sb.Overflowed) if (sb.Overflowed)
return ResultFs.InvalidArgument.Log(); return ResultFs.InvalidArgument.Log();
return SetUpFixedPath(ref path, pathBuffer); return SetUpFixedPath(ref path, pathBuffer).Ret();
} }
// /%016llx // /%016llx
@ -1268,7 +1268,7 @@ public static class PathFunctions
if (sb.Overflowed) if (sb.Overflowed)
return ResultFs.InvalidArgument.Log(); return ResultFs.InvalidArgument.Log();
return SetUpFixedPath(ref path, pathBuffer); return SetUpFixedPath(ref path, pathBuffer).Ret();
} }
// /%08x.meta // /%08x.meta
@ -1290,7 +1290,7 @@ public static class PathFunctions
if (sb.Overflowed) if (sb.Overflowed)
return ResultFs.InvalidArgument.Log(); return ResultFs.InvalidArgument.Log();
return SetUpFixedPath(ref path, pathBuffer); return SetUpFixedPath(ref path, pathBuffer).Ret();
} }
// /saveMeta/%016llx // /saveMeta/%016llx
@ -1312,6 +1312,30 @@ public static class PathFunctions
if (sb.Overflowed) if (sb.Overflowed)
return ResultFs.InvalidArgument.Log(); 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> /// <summary>"<c>Contents</c>"</summary>
public static ReadOnlySpan<byte> ContentStorageDirectoryName => "Contents"u8; 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.FsSrv.Sf;
using LibHac.FsSystem; using LibHac.FsSystem;
using LibHac.Sf; using LibHac.Sf;
using static LibHac.FsSrv.Anonymous;
using IFileSystem = LibHac.Fs.Fsa.IFileSystem; using IFileSystem = LibHac.Fs.Fsa.IFileSystem;
using IFileSystemSf = LibHac.FsSrv.Sf.IFileSystem; using IFileSystemSf = LibHac.FsSrv.Sf.IFileSystem;
using Path = LibHac.Fs.Path; using Path = LibHac.Fs.Path;
@ -11,44 +12,31 @@ using Utility = LibHac.FsSrv.Impl.Utility;
namespace LibHac.FsSrv; namespace LibHac.FsSrv;
public readonly struct BaseFileSystemService file static class Anonymous
{ {
private readonly BaseFileSystemServiceImpl _serviceImpl; public static Result GetProgramInfo(FileSystemServer fsServer, out ProgramInfo programInfo, ulong processId)
private readonly ulong _processId;
public BaseFileSystemService(BaseFileSystemServiceImpl serviceImpl, ulong processId)
{ {
_serviceImpl = serviceImpl; var programRegistry = new ProgramRegistryImpl(fsServer);
_processId = processId; 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); Result res = GetProgramInfo(fsServer, out ProgramInfo 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);
if (res.IsFailure()) return res.Miss(); if (res.IsFailure()) return res.Miss();
AccessControl accessControl = programInfo.AccessControl;
if (id == BaseFileSystemId.TemporaryDirectory) if (id == BaseFileSystemId.TemporaryDirectory)
{ {
Accessibility accessibility = Accessibility accessibility = accessControl.GetAccessibilityFor(AccessibilityType.MountTemporaryDirectory);
programInfo.AccessControl.GetAccessibilityFor(AccessibilityType.MountTemporaryDirectory);
if (!accessibility.CanRead || !accessibility.CanWrite) if (!accessibility.CanRead || !accessibility.CanWrite)
return ResultFs.PermissionDenied.Log(); return ResultFs.PermissionDenied.Log();
} }
else else
{ {
Accessibility accessibility = Accessibility accessibility = accessControl.GetAccessibilityFor(AccessibilityType.MountAllBaseFileSystem);
programInfo.AccessControl.GetAccessibilityFor(AccessibilityType.MountAllBaseFileSystem);
if (!accessibility.CanRead || !accessibility.CanWrite) if (!accessibility.CanRead || !accessibility.CanWrite)
return ResultFs.PermissionDenied.Log(); return ResultFs.PermissionDenied.Log();
@ -56,10 +44,28 @@ public readonly struct BaseFileSystemService
return Result.Success; 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) 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(); if (res.IsFailure()) return res.Miss();
// Open the file system // Open the file system
@ -68,8 +74,7 @@ public readonly struct BaseFileSystemService
if (res.IsFailure()) return res.Miss(); if (res.IsFailure()) return res.Miss();
// Create an SF adapter for the file system // 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); outFileSystem.SetByMove(ref fileSystemAdapter.Ref);
return Result.Success; return Result.Success;
@ -77,38 +82,72 @@ public readonly struct BaseFileSystemService
public Result FormatBaseFileSystem(BaseFileSystemId fileSystemId) public Result FormatBaseFileSystem(BaseFileSystemId fileSystemId)
{ {
Result res = CheckCapabilityById(fileSystemId, _processId); Result res = CheckCapabilityById(FsServer, fileSystemId, _processId);
if (res.IsFailure()) return res.Miss(); 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, public Result OpenBisFileSystem(ref SharedRef<IFileSystemSf> outFileSystem, ref readonly FspPath rootPath,
BisPartitionId partitionId) BisPartitionId partitionId)
{ {
Result res = GetProgramInfo(out ProgramInfo programInfo); Result res = GetProgramInfo(FsServer, out ProgramInfo programInfo, _processId);
if (res.IsFailure()) return res.Miss(); 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 // 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) break;
return ResultFs.PermissionDenied.Log(); }
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; const StorageLayoutType storageFlag = StorageLayoutType.Bis;
using var scopedContext = new ScopedStorageLayoutTypeSetter(storageFlag); using var scopedContext = new ScopedStorageLayoutTypeSetter(storageFlag);
@ -125,7 +164,7 @@ public readonly struct BaseFileSystemService
// Open the file system // Open the file system
using var fileSystem = new SharedRef<IFileSystem>(); 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(); if (res.IsFailure()) return res.Miss();
using var subDirFileSystem = new SharedRef<IFileSystem>(); using var subDirFileSystem = new SharedRef<IFileSystem>();
@ -149,46 +188,46 @@ public readonly struct BaseFileSystemService
return ResultFs.InvalidSize.Log(); return ResultFs.InvalidSize.Log();
// Caller must have the FillBis permission // 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 (res.IsFailure()) return res.Miss();
if (!programInfo.AccessControl.CanCall(OperationType.FillBis)) if (!programInfo.AccessControl.CanCall(OperationType.FillBis))
return ResultFs.PermissionDenied.Log(); return ResultFs.PermissionDenied.Log();
return _serviceImpl.CreatePaddingFile(size); using var scopedContext = new ScopedStorageLayoutTypeSetter(StorageLayoutType.Bis);
return _serviceImpl.CreatePaddingFile(size).Ret();
} }
public Result DeleteAllPaddingFiles() public Result DeleteAllPaddingFiles()
{ {
// Caller must have the FillBis permission // 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 (res.IsFailure()) return res.Miss();
if (!programInfo.AccessControl.CanCall(OperationType.FillBis)) if (!programInfo.AccessControl.CanCall(OperationType.FillBis))
return ResultFs.PermissionDenied.Log(); 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, public Result OpenGameCardFileSystem(ref SharedRef<IFileSystemSf> outFileSystem, GameCardHandle handle,
GameCardPartition partitionId) GameCardPartition partitionId)
{ {
Result res = GetProgramInfo(out ProgramInfo programInfo); Result res = GetProgramInfo(FsServer, out ProgramInfo programInfo, _processId);
if (res.IsFailure()) return res.Miss(); if (res.IsFailure()) return res.Miss();
if (!programInfo.AccessControl.GetAccessibilityFor(AccessibilityType.MountGameCard).CanRead) if (!programInfo.AccessControl.GetAccessibilityFor(AccessibilityType.MountGameCard).CanRead)
return ResultFs.PermissionDenied.Log(); return ResultFs.PermissionDenied.Log();
using var fileSystem = new SharedRef<IFileSystem>(); using var fileSystem = new SharedRef<IFileSystem>();
res = _serviceImpl.OpenGameCardFileSystem(ref fileSystem.Ref, handle, partitionId); res = _serviceImpl.OpenGameCardFileSystem(ref fileSystem.Ref, handle, partitionId);
if (res.IsFailure()) return res.Miss(); if (res.IsFailure()) return res.Miss();
using var asyncFileSystem = using var asyncFileSystem = new SharedRef<IFileSystem>(new AsynchronousAccessFileSystem(in fileSystem));
new SharedRef<IFileSystem>(new AsynchronousAccessFileSystem(in fileSystem)); using SharedRef<IFileSystemSf> fileSystemAdapter = FileSystemInterfaceAdapter.CreateShared(in asyncFileSystem, allowAllOperations: false);
using SharedRef<IFileSystemSf> fileSystemAdapter =
FileSystemInterfaceAdapter.CreateShared(in asyncFileSystem, false);
outFileSystem.SetByMove(ref fileSystemAdapter.Ref); outFileSystem.SetByMove(ref fileSystemAdapter.Ref);
@ -197,7 +236,7 @@ public readonly struct BaseFileSystemService
public Result OpenSdCardFileSystem(ref SharedRef<IFileSystemSf> outFileSystem) 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(); if (res.IsFailure()) return res.Miss();
Accessibility accessibility = programInfo.AccessControl.GetAccessibilityFor(AccessibilityType.MountSdCard); Accessibility accessibility = programInfo.AccessControl.GetAccessibilityFor(AccessibilityType.MountSdCard);
@ -205,7 +244,7 @@ public readonly struct BaseFileSystemService
if (!accessibility.CanRead || !accessibility.CanWrite) if (!accessibility.CanRead || !accessibility.CanWrite)
return ResultFs.PermissionDenied.Log(); return ResultFs.PermissionDenied.Log();
const StorageLayoutType storageFlag = StorageLayoutType.Bis; const StorageLayoutType storageFlag = StorageLayoutType.SdCard;
using var scopedContext = new ScopedStorageLayoutTypeSetter(storageFlag); using var scopedContext = new ScopedStorageLayoutTypeSetter(storageFlag);
using var fileSystem = new SharedRef<IFileSystem>(); using var fileSystem = new SharedRef<IFileSystem>();
@ -225,20 +264,24 @@ public readonly struct BaseFileSystemService
public Result FormatSdCardFileSystem() public Result FormatSdCardFileSystem()
{ {
// Caller must have the FormatSdCard permission // 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 (res.IsFailure()) return res.Miss();
if (!programInfo.AccessControl.CanCall(OperationType.FormatSdCard)) if (!programInfo.AccessControl.CanCall(OperationType.FormatSdCard))
return ResultFs.PermissionDenied.Log(); return ResultFs.PermissionDenied.Log();
return _serviceImpl.FormatSdCardProxyFileSystem(); using var scopedContext = new ScopedStorageLayoutTypeSetter(StorageLayoutType.SdCard);
return _serviceImpl.FormatSdCardProxyFileSystem().Ret();
} }
public Result FormatSdCardDryRun() public Result FormatSdCardDryRun()
{ {
// No permissions are needed to call this method // 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) public Result IsExFatSupported(out bool isSupported)
@ -251,8 +294,11 @@ public readonly struct BaseFileSystemService
public Result OpenImageDirectoryFileSystem(ref SharedRef<IFileSystemSf> outFileSystem, ImageDirectoryId directoryId) 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 // 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(); if (res.IsFailure()) return res.Miss();
Accessibility accessibility = Accessibility accessibility =
@ -280,7 +326,7 @@ public readonly struct BaseFileSystemService
if (res.IsFailure()) return res.Miss(); if (res.IsFailure()) return res.Miss();
using SharedRef<IFileSystemSf> fileSystemAdapter = using SharedRef<IFileSystemSf> fileSystemAdapter =
FileSystemInterfaceAdapter.CreateShared(in baseFileSystem, false); FileSystemInterfaceAdapter.CreateShared(in baseFileSystem, allowAllOperations: false);
outFileSystem.SetByMove(ref fileSystemAdapter.Ref); outFileSystem.SetByMove(ref fileSystemAdapter.Ref);
@ -290,8 +336,11 @@ public readonly struct BaseFileSystemService
public Result OpenBisWiper(ref SharedRef<IWiper> outBisWiper, NativeHandle transferMemoryHandle, public Result OpenBisWiper(ref SharedRef<IWiper> outBisWiper, NativeHandle transferMemoryHandle,
ulong transferMemorySize) ulong transferMemorySize)
{ {
const StorageLayoutType storageFlag = StorageLayoutType.Bis;
using var scopedContext = new ScopedStorageLayoutTypeSetter(storageFlag);
// Caller must have the OpenBisWiper permission // 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 (res.IsFailure()) return res.Miss();
if (!programInfo.AccessControl.CanCall(OperationType.OpenBisWiper)) if (!programInfo.AccessControl.CanCall(OperationType.OpenBisWiper))

View file

@ -1,11 +1,17 @@
using System; using System;
using LibHac.Common; using LibHac.Common;
using LibHac.Fat;
using LibHac.Fs; using LibHac.Fs;
using LibHac.Fs.Fsa;
using LibHac.FsSrv.FsCreator; using LibHac.FsSrv.FsCreator;
using LibHac.FsSrv.Impl; using LibHac.FsSrv.Impl;
using LibHac.FsSrv.Sf; using LibHac.FsSrv.Sf;
using LibHac.FsSrv.Storage;
using LibHac.Os;
using LibHac.Sf; using LibHac.Sf;
using IFileSystem = LibHac.Fs.Fsa.IFileSystem; using IFileSystem = LibHac.Fs.Fsa.IFileSystem;
using Path = LibHac.Fs.Path;
using Utility = LibHac.FsSystem.Utility;
namespace LibHac.FsSrv; namespace LibHac.FsSrv;
@ -13,12 +19,18 @@ public class BaseFileSystemServiceImpl
{ {
private Configuration _config; 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, public delegate Result BisWiperCreator(ref UniqueRef<IWiper> outWiper, NativeHandle transferMemoryHandle,
ulong transferMemorySize); ulong transferMemorySize);
public BaseFileSystemServiceImpl(in Configuration configuration) public BaseFileSystemServiceImpl(in Configuration configuration)
{ {
_config = configuration; _config = configuration;
// nn::fat::SetCurrentTimeStampCallback(configuration.CurrentTimeFunction);
} }
public struct Configuration public struct Configuration
@ -27,8 +39,8 @@ public class BaseFileSystemServiceImpl
public IGameCardFileSystemCreator GameCardFileSystemCreator; public IGameCardFileSystemCreator GameCardFileSystemCreator;
public ISdCardProxyFileSystemCreator SdCardFileSystemCreator; public ISdCardProxyFileSystemCreator SdCardFileSystemCreator;
// CurrentTimeFunction // CurrentTimeFunction
// FatFileSystemCacheManager public FatFileSystemCacheManager FatFileSystemCacheManager;
// BaseFileSystemCreatorHolder public BaseFileSystemCreatorHolder BaseFileSystemCreatorHolder;
public BisWiperCreator BisWiperCreator; public BisWiperCreator BisWiperCreator;
// LibHac additions // LibHac additions
@ -37,36 +49,97 @@ public class BaseFileSystemServiceImpl
public Result OpenBaseFileSystem(ref SharedRef<IFileSystem> outFileSystem, BaseFileSystemId fileSystemId) 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) 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) 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, public Result OpenBisFileSystem(ref SharedRef<IFileSystem> outFileSystem, BisPartitionId partitionId,
bool caseSensitive) bool caseSensitive)
{ {
Result res = _config.BisFileSystemCreator.Create(ref outFileSystem, partitionId, caseSensitive); return _config.BisFileSystemCreator.Create(ref outFileSystem, partitionId, caseSensitive).Ret();
if (res.IsFailure()) return res.Miss();
return Result.Success;
} }
public Result CreatePaddingFile(long size) 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() 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, public Result OpenGameCardFileSystem(ref SharedRef<IFileSystem> outFileSystem, GameCardHandle handle,
@ -83,44 +156,69 @@ public class BaseFileSystemServiceImpl
break; break;
} }
return res; if (res.IsFailure()) return res.Miss();
return Result.Success;
} }
public Result OpenSdCardProxyFileSystem(ref SharedRef<IFileSystem> outFileSystem) 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) 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() public Result FormatSdCardProxyFileSystem()
{ {
return _config.SdCardFileSystemCreator.Format(); return _config.SdCardFileSystemCreator.Format().Ret();
} }
public Result FormatSdCardDryRun() 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() public bool IsExFatSupported()
{ {
// Returning false should probably be fine return FatFileSystem.IsExFatSupported();
return false; }
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, public Result OpenBisWiper(ref UniqueRef<IWiper> outBisWiper, NativeHandle transferMemoryHandle,
ulong transferMemorySize) 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) internal Result GetProgramInfo(out ProgramInfo programInfo, ulong processId)
{ {
var registry = new ProgramRegistryImpl(_config.FsServer); 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 public interface IBuiltInStorageFileSystemCreator
{ {
Result Create(ref SharedRef<IFileSystem> outFileSystem, BisPartitionId partitionId, bool caseSensitive); 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> /// </summary>
/// <returns>The <see cref="Result"/> of the operation.</returns> /// <returns>The <see cref="Result"/> of the operation.</returns>
Result Format(); 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; private SdkRecursiveMutexType _impl;
@ -95,6 +95,11 @@ public class SdkRecursiveMutex : IBasicLockable
_impl.Lock(); _impl.Lock();
} }
public bool TryLock()
{
return _impl.TryLock();
}
public void Unlock() public void Unlock()
{ {
_impl.Unlock(); _impl.Unlock();