diff --git a/build/CodeGen/results.csv b/build/CodeGen/results.csv
index fa74307a..e9b91cf2 100644
--- a/build/CodeGen/results.csv
+++ b/build/CodeGen/results.csv
@@ -205,6 +205,7 @@ Module,DescriptionStart,DescriptionEnd,Name,Summary
2,4812,,IncompleteBlockInZeroBitmapHashStorageFile,
2,5000,5999,Unexpected,
+2,5121,,UnexpectedFatFileSystemSectorCount,
2,5307,,UnexpectedErrorInHostFileFlush,
2,5308,,UnexpectedErrorInHostFileGetSize,
2,5309,,UnknownHostFileSystemError,
diff --git a/src/LibHac/Fs/ResultFs.cs b/src/LibHac/Fs/ResultFs.cs
index eaf025fb..d25479c8 100644
--- a/src/LibHac/Fs/ResultFs.cs
+++ b/src/LibHac/Fs/ResultFs.cs
@@ -305,6 +305,8 @@ namespace LibHac.Fs
/// Error code: 2002-5000; Range: 5000-5999; Inner value: 0x271002
public static Result.Base Unexpected { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new Result.Base(ModuleFs, 5000, 5999); }
+ /// Error code: 2002-5121; Inner value: 0x280202
+ public static Result.Base UnexpectedFatFileSystemSectorCount => new Result.Base(ModuleFs, 5121);
/// Error code: 2002-5307; Inner value: 0x297602
public static Result.Base UnexpectedErrorInHostFileFlush => new Result.Base(ModuleFs, 5307);
/// Error code: 2002-5308; Inner value: 0x297802
diff --git a/src/LibHac/FsSrv/BaseFileSystemService.cs b/src/LibHac/FsSrv/BaseFileSystemService.cs
new file mode 100644
index 00000000..41d083de
--- /dev/null
+++ b/src/LibHac/FsSrv/BaseFileSystemService.cs
@@ -0,0 +1,205 @@
+using LibHac.Fs;
+using LibHac.Fs.Fsa;
+using LibHac.FsSrv.Impl;
+using LibHac.FsSrv.Sf;
+using LibHac.Sf;
+
+namespace LibHac.FsSrv
+{
+ public readonly struct BaseFileSystemService
+ {
+ private readonly BaseFileSystemServiceImpl _serviceImpl;
+ private readonly ulong _processId;
+
+ public BaseFileSystemService(BaseFileSystemServiceImpl serviceImpl, ulong processId)
+ {
+ _serviceImpl = serviceImpl;
+ _processId = processId;
+ }
+
+ public Result OpenBisFileSystem(out IFileSystem fileSystem, in FspPath rootPath, BisPartitionId partitionId)
+ {
+ fileSystem = default;
+
+ Result rc = GetProgramInfo(out ProgramInfo programInfo);
+ if (rc.IsFailure()) return rc;
+
+ // 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.InvalidAlignment.Log();
+
+ // Verify the caller has the required permissions
+ Accessibility accessibility = programInfo.AccessControl.GetAccessibilityFor(requiredAccess);
+
+ if (!accessibility.CanRead || !accessibility.CanWrite)
+ return ResultFs.PermissionDenied.Log();
+
+ // Normalize the path
+ var normalizer = new PathNormalizer(rootPath, PathNormalizer.Option.AcceptEmpty);
+ if (normalizer.Result.IsFailure()) return normalizer.Result;
+
+ rc = _serviceImpl.OpenBisFileSystem(out IFileSystem bisFs, normalizer.Path, partitionId);
+ if (rc.IsFailure()) return rc;
+
+ fileSystem = bisFs;
+ return Result.Success;
+ }
+
+ public Result CreatePaddingFile(long size)
+ {
+ // File size must be non-negative
+ if (size < 0)
+ return ResultFs.InvalidSize.Log();
+
+ // Caller must have the FillBis permission
+ Result rc = GetProgramInfo(out ProgramInfo programInfo);
+ if (rc.IsFailure()) return rc;
+
+ if (!programInfo.AccessControl.CanCall(OperationType.FillBis))
+ return ResultFs.PermissionDenied.Log();
+
+ return _serviceImpl.CreatePaddingFile(size);
+ }
+
+ public Result DeleteAllPaddingFiles()
+ {
+ // Caller must have the FillBis permission
+ Result rc = GetProgramInfo(out ProgramInfo programInfo);
+ if (rc.IsFailure()) return rc;
+
+ if (!programInfo.AccessControl.CanCall(OperationType.FillBis))
+ return ResultFs.PermissionDenied.Log();
+
+ return _serviceImpl.DeleteAllPaddingFiles();
+ }
+
+ public Result OpenGameCardFileSystem(out IFileSystem fileSystem, GameCardHandle handle,
+ GameCardPartition partitionId)
+ {
+ fileSystem = default;
+
+ Result rc = GetProgramInfo(out ProgramInfo programInfo);
+ if (rc.IsFailure()) return rc;
+
+ if (!programInfo.AccessControl.GetAccessibilityFor(AccessibilityType.MountGameCard).CanRead)
+ return ResultFs.PermissionDenied.Log();
+
+ rc = _serviceImpl.OpenGameCardFileSystem(out IFileSystem gcFs, handle, partitionId);
+ if (rc.IsFailure()) return rc;
+
+ fileSystem = gcFs;
+ return Result.Success;
+ }
+
+ public Result OpenSdCardFileSystem(out IFileSystem fileSystem)
+ {
+ fileSystem = default;
+
+ Result rc = GetProgramInfo(out ProgramInfo programInfo);
+ if (rc.IsFailure()) return rc;
+
+ Accessibility accessibility = programInfo.AccessControl.GetAccessibilityFor(AccessibilityType.MountSdCard);
+
+ if (!accessibility.CanRead || !accessibility.CanWrite)
+ return ResultFs.PermissionDenied.Log();
+
+ rc = _serviceImpl.OpenSdCardProxyFileSystem(out IFileSystem sdCardFs);
+ if (rc.IsFailure()) return rc;
+
+ fileSystem = sdCardFs;
+ return Result.Success;
+ }
+
+ public Result FormatSdCardFileSystem()
+ {
+ // Caller must have the FormatSdCard permission
+ Result rc = GetProgramInfo(out ProgramInfo programInfo);
+ if (rc.IsFailure()) return rc;
+
+ if (!programInfo.AccessControl.CanCall(OperationType.FormatSdCard))
+ return ResultFs.PermissionDenied.Log();
+
+ return _serviceImpl.FormatSdCardProxyFileSystem();
+ }
+
+ public Result FormatSdCardDryRun()
+ {
+ // No permissions are needed to call this method
+
+ return _serviceImpl.FormatSdCardProxyFileSystem();
+ }
+
+ public Result IsExFatSupported(out bool isSupported)
+ {
+ // No permissions are needed to call this method
+
+ isSupported = _serviceImpl.IsExFatSupported();
+ return Result.Success;
+ }
+
+ public Result OpenImageDirectoryFileSystem(out IFileSystem fileSystem, ImageDirectoryId directoryId)
+ {
+ fileSystem = default;
+
+ // Caller must have the MountImageAndVideoStorage permission
+ Result rc = GetProgramInfo(out ProgramInfo programInfo);
+ if (rc.IsFailure()) return rc;
+
+ Accessibility accessibility =
+ programInfo.AccessControl.GetAccessibilityFor(AccessibilityType.MountImageAndVideoStorage);
+
+ if (!accessibility.CanRead || !accessibility.CanWrite)
+ return ResultFs.PermissionDenied.Log();
+
+ // Get the base file system ID
+ int id;
+ switch (directoryId)
+ {
+ case ImageDirectoryId.Nand: id = 0; break;
+ case ImageDirectoryId.SdCard: id = 1; break;
+ default:
+ return ResultFs.InvalidArgument.Log();
+ }
+
+ rc = _serviceImpl.OpenBaseFileSystem(out IFileSystem imageFs, id);
+ if (rc.IsFailure()) return rc;
+
+ fileSystem = imageFs;
+ return Result.Success;
+ }
+
+ public Result OpenBisWiper(out IWiper bisWiper, NativeHandle transferMemoryHandle, ulong transferMemorySize)
+ {
+ bisWiper = default;
+
+ // Caller must have the OpenBisWiper permission
+ Result rc = GetProgramInfo(out ProgramInfo programInfo);
+ if (rc.IsFailure()) return rc;
+
+ if (!programInfo.AccessControl.CanCall(OperationType.OpenBisWiper))
+ return ResultFs.PermissionDenied.Log();
+
+ rc = _serviceImpl.OpenBisWiper(out IWiper wiper, transferMemoryHandle, transferMemorySize);
+ if (rc.IsFailure()) return rc;
+
+ bisWiper = wiper;
+ return Result.Success;
+ }
+
+ private Result GetProgramInfo(out ProgramInfo programInfo)
+ {
+ return _serviceImpl.GetProgramInfo(out programInfo, _processId);
+ }
+ }
+}
diff --git a/src/LibHac/FsSrv/BaseFileSystemServiceImpl.cs b/src/LibHac/FsSrv/BaseFileSystemServiceImpl.cs
new file mode 100644
index 00000000..1fa1f998
--- /dev/null
+++ b/src/LibHac/FsSrv/BaseFileSystemServiceImpl.cs
@@ -0,0 +1,113 @@
+using System;
+using LibHac.Common;
+using LibHac.Fs;
+using LibHac.Fs.Fsa;
+using LibHac.FsSrv.Creators;
+using LibHac.FsSrv.Impl;
+using LibHac.FsSrv.Sf;
+using LibHac.Sf;
+
+namespace LibHac.FsSrv
+{
+ public class BaseFileSystemServiceImpl
+ {
+ private Configuration _config;
+
+ public delegate Result BisWiperCreator(out IWiper wiper, NativeHandle transferMemoryHandle,
+ ulong transferMemorySize);
+
+ public BaseFileSystemServiceImpl(in Configuration configuration)
+ {
+ _config = configuration;
+ }
+
+ public struct Configuration
+ {
+ public IBuiltInStorageFileSystemCreator BisFileSystemCreator;
+ public IGameCardFileSystemCreator GameCardFileSystemCreator;
+ public ISdCardProxyFileSystemCreator SdCardFileSystemCreator;
+ // CurrentTimeFunction
+ // FatFileSystemCacheManager
+ // AlbumDirectoryFileSystemManager
+ public BisWiperCreator BisWiperCreator;
+
+ // Note: The program registry service is global as of FS 10.0.0
+ public ProgramRegistryImpl ProgramRegistry;
+ }
+
+ public Result OpenBaseFileSystem(out IFileSystem fileSystem, int fileSystemId)
+ {
+ throw new NotImplementedException();
+ }
+
+ public Result OpenBisFileSystem(out IFileSystem fileSystem, U8Span rootPath, BisPartitionId partitionId)
+ {
+ return _config.BisFileSystemCreator.Create(out fileSystem, rootPath.ToString(), partitionId);
+ }
+
+ public Result CreatePaddingFile(long size)
+ {
+ throw new NotImplementedException();
+ }
+
+ public Result DeleteAllPaddingFiles()
+ {
+ throw new NotImplementedException();
+ }
+
+ public Result OpenGameCardFileSystem(out IFileSystem fileSystem, GameCardHandle handle,
+ GameCardPartition partitionId)
+ {
+ Result rc;
+ int tries = 0;
+
+ do
+ {
+ rc = _config.GameCardFileSystemCreator.Create(out fileSystem, handle, partitionId);
+
+ if (!ResultFs.DataCorrupted.Includes(rc))
+ break;
+
+ tries++;
+ } while (tries < 2);
+
+ return rc;
+ }
+
+ public Result OpenSdCardProxyFileSystem(out IFileSystem fileSystem)
+ {
+ return OpenSdCardProxyFileSystem(out fileSystem, false);
+ }
+
+ public Result OpenSdCardProxyFileSystem(out IFileSystem fileSystem, bool isCaseSensitive)
+ {
+ return _config.SdCardFileSystemCreator.Create(out fileSystem, isCaseSensitive);
+ }
+
+ public Result FormatSdCardProxyFileSystem()
+ {
+ return _config.SdCardFileSystemCreator.Format();
+ }
+
+ public Result FormatSdCardDryRun()
+ {
+ throw new NotImplementedException();
+ }
+
+ public bool IsExFatSupported()
+ {
+ // Returning false should probably be fine
+ return false;
+ }
+
+ public Result OpenBisWiper(out IWiper wiper, NativeHandle transferMemoryHandle, ulong transferMemorySize)
+ {
+ return _config.BisWiperCreator(out wiper, transferMemoryHandle, transferMemorySize);
+ }
+
+ internal Result GetProgramInfo(out ProgramInfo programInfo, ulong processId)
+ {
+ return _config.ProgramRegistry.GetProgramInfo(out programInfo, processId);
+ }
+ }
+}
diff --git a/src/LibHac/FsSrv/Creators/EmulatedSdFileSystemCreator.cs b/src/LibHac/FsSrv/Creators/EmulatedSdCardFileSystemCreator.cs
similarity index 73%
rename from src/LibHac/FsSrv/Creators/EmulatedSdFileSystemCreator.cs
rename to src/LibHac/FsSrv/Creators/EmulatedSdCardFileSystemCreator.cs
index a861c57f..022ce7ae 100644
--- a/src/LibHac/FsSrv/Creators/EmulatedSdFileSystemCreator.cs
+++ b/src/LibHac/FsSrv/Creators/EmulatedSdCardFileSystemCreator.cs
@@ -4,7 +4,7 @@ using LibHac.Fs.Fsa;
namespace LibHac.FsSrv.Creators
{
- public class EmulatedSdFileSystemCreator : ISdFileSystemCreator
+ public class EmulatedSdCardFileSystemCreator : ISdCardProxyFileSystemCreator
{
private const string DefaultPath = "/sdcard";
@@ -14,20 +14,20 @@ namespace LibHac.FsSrv.Creators
private IFileSystem SdCardFileSystem { get; set; }
- public EmulatedSdFileSystemCreator(EmulatedSdCard sdCard, IFileSystem rootFileSystem)
+ public EmulatedSdCardFileSystemCreator(EmulatedSdCard sdCard, IFileSystem rootFileSystem)
{
SdCard = sdCard;
RootFileSystem = rootFileSystem;
}
- public EmulatedSdFileSystemCreator(EmulatedSdCard sdCard, IFileSystem rootFileSystem, string path)
+ public EmulatedSdCardFileSystemCreator(EmulatedSdCard sdCard, IFileSystem rootFileSystem, string path)
{
SdCard = sdCard;
RootFileSystem = rootFileSystem;
Path = path;
}
- public Result Create(out IFileSystem fileSystem)
+ public Result Create(out IFileSystem fileSystem, bool isCaseSensitive)
{
fileSystem = default;
@@ -61,7 +61,12 @@ namespace LibHac.FsSrv.Creators
return Result.Success;
}
- public Result Format(bool closeOpenEntries)
+ public Result Format(bool removeFromFatFsCache)
+ {
+ throw new NotImplementedException();
+ }
+
+ public Result Format()
{
throw new NotImplementedException();
}
diff --git a/src/LibHac/FsSrv/Creators/FileSystemCreators.cs b/src/LibHac/FsSrv/Creators/FileSystemCreators.cs
index 1e15191f..22ece1f3 100644
--- a/src/LibHac/FsSrv/Creators/FileSystemCreators.cs
+++ b/src/LibHac/FsSrv/Creators/FileSystemCreators.cs
@@ -17,6 +17,6 @@
public IEncryptedFileSystemCreator EncryptedFileSystemCreator { get; set; }
public IMemoryStorageCreator MemoryStorageCreator { get; set; }
public IBuiltInStorageFileSystemCreator BuiltInStorageFileSystemCreator { get; set; }
- public ISdFileSystemCreator SdFileSystemCreator { get; set; }
+ public ISdCardProxyFileSystemCreator SdCardFileSystemCreator { get; set; }
}
}
diff --git a/src/LibHac/FsSrv/Creators/ISdCardProxyFileSystemCreator.cs b/src/LibHac/FsSrv/Creators/ISdCardProxyFileSystemCreator.cs
new file mode 100644
index 00000000..6a8c978b
--- /dev/null
+++ b/src/LibHac/FsSrv/Creators/ISdCardProxyFileSystemCreator.cs
@@ -0,0 +1,23 @@
+using LibHac.Fs.Fsa;
+
+namespace LibHac.FsSrv.Creators
+{
+ public interface ISdCardProxyFileSystemCreator
+ {
+ Result Create(out IFileSystem fileSystem, bool isCaseSensitive);
+
+ ///
+ /// Formats the SD card.
+ ///
+ /// Should the SD card file system be removed from the
+ /// FAT file system cache?
+ /// The of the operation.
+ Result Format(bool removeFromFatFsCache);
+
+ ///
+ /// Automatically closes all open proxy file system entries and formats the SD card.
+ ///
+ /// The of the operation.
+ Result Format();
+ }
+}
diff --git a/src/LibHac/FsSrv/Creators/ISdFileSystemCreator.cs b/src/LibHac/FsSrv/Creators/ISdFileSystemCreator.cs
deleted file mode 100644
index 08167cdc..00000000
--- a/src/LibHac/FsSrv/Creators/ISdFileSystemCreator.cs
+++ /dev/null
@@ -1,10 +0,0 @@
-using LibHac.Fs.Fsa;
-
-namespace LibHac.FsSrv.Creators
-{
- public interface ISdFileSystemCreator
- {
- Result Create(out IFileSystem fileSystem);
- Result Format(bool closeOpenEntries);
- }
-}
diff --git a/src/LibHac/FsSrv/DefaultFsServerObjects.cs b/src/LibHac/FsSrv/DefaultFsServerObjects.cs
index d9f0e7e0..d764f4f2 100644
--- a/src/LibHac/FsSrv/DefaultFsServerObjects.cs
+++ b/src/LibHac/FsSrv/DefaultFsServerObjects.cs
@@ -29,7 +29,7 @@ namespace LibHac.FsSrv
creators.GameCardFileSystemCreator = new EmulatedGameCardFsCreator(gcStorageCreator, gameCard);
creators.EncryptedFileSystemCreator = new EncryptedFileSystemCreator(keySet);
creators.BuiltInStorageFileSystemCreator = new EmulatedBisFileSystemCreator(rootFileSystem);
- creators.SdFileSystemCreator = new EmulatedSdFileSystemCreator(sdCard, rootFileSystem);
+ creators.SdCardFileSystemCreator = new EmulatedSdCardFileSystemCreator(sdCard, rootFileSystem);
var deviceOperator = new EmulatedDeviceOperator(gameCard, sdCard);
diff --git a/src/LibHac/FsSrv/FileSystemProxyCore.cs b/src/LibHac/FsSrv/FileSystemProxyCore.cs
index a216178b..6441ef3b 100644
--- a/src/LibHac/FsSrv/FileSystemProxyCore.cs
+++ b/src/LibHac/FsSrv/FileSystemProxyCore.cs
@@ -627,7 +627,7 @@ namespace LibHac.FsSrv
public Result OpenSdCardFileSystem(out IFileSystem fileSystem)
{
- return FsCreators.SdFileSystemCreator.Create(out fileSystem);
+ return FsCreators.SdCardFileSystemCreator.Create(out fileSystem, false);
}
public Result OpenGameCardStorage(out IStorage storage, GameCardHandle handle, GameCardPartitionRaw partitionId)
@@ -707,7 +707,7 @@ namespace LibHac.FsSrv
{
case CustomStorageId.SdCard:
{
- Result rc = FsCreators.SdFileSystemCreator.Create(out IFileSystem sdFs);
+ Result rc = FsCreators.SdCardFileSystemCreator.Create(out IFileSystem sdFs, false);
if (rc.IsFailure()) return rc;
string customStorageDir = CustomStorage.GetCustomStorageDirectoryName(CustomStorageId.SdCard);
diff --git a/src/LibHac/FsSrv/ProgramRegistryImpl.cs b/src/LibHac/FsSrv/ProgramRegistryImpl.cs
index 7946b915..febbdc7f 100644
--- a/src/LibHac/FsSrv/ProgramRegistryImpl.cs
+++ b/src/LibHac/FsSrv/ProgramRegistryImpl.cs
@@ -14,7 +14,7 @@ namespace LibHac.FsSrv
/// storage location and file system permissions. This allows FS to resolve the program ID and
/// verify the permissions of any process calling it.
///
Based on FS 10.0.0 (nnSdk 10.4.0)
- internal class ProgramRegistryImpl : IProgramRegistry
+ public class ProgramRegistryImpl : IProgramRegistry
{
private ulong _processId;
diff --git a/src/LibHac/FsSrv/Sf/IWiper.cs b/src/LibHac/FsSrv/Sf/IWiper.cs
new file mode 100644
index 00000000..08b550b4
--- /dev/null
+++ b/src/LibHac/FsSrv/Sf/IWiper.cs
@@ -0,0 +1,8 @@
+namespace LibHac.FsSrv.Sf
+{
+ public interface IWiper
+ {
+ public Result Startup(out long spaceToWipe);
+ public Result Process(out long remainingSpaceToWipe);
+ }
+}
diff --git a/src/LibHac/Sf/NativeHandle.cs b/src/LibHac/Sf/NativeHandle.cs
new file mode 100644
index 00000000..fd2ed0ac
--- /dev/null
+++ b/src/LibHac/Sf/NativeHandle.cs
@@ -0,0 +1,21 @@
+namespace LibHac.Sf
+{
+ // How should this be handled? Using a C# struct would be more accurate, but C#
+ // doesn't have copy constructors or any way to prevent a struct from being copied.
+ public class NativeHandle
+ {
+ public uint Handle { get; private set; }
+ public bool IsManaged { get; private set; }
+
+ public NativeHandle(uint handle)
+ {
+ Handle = handle;
+ }
+
+ public NativeHandle(uint handle, bool isManaged)
+ {
+ Handle = handle;
+ IsManaged = isManaged;
+ }
+ }
+}