diff --git a/src/LibHac/Fs/Shim/Debug.cs b/src/LibHac/Fs/Shim/Debug.cs new file mode 100644 index 00000000..be7307a7 --- /dev/null +++ b/src/LibHac/Fs/Shim/Debug.cs @@ -0,0 +1,74 @@ +using System; +using LibHac.Common; +using LibHac.FsSrv.Sf; +using LibHac.Sf; + +namespace LibHac.Fs.Shim; + +public enum DebugOptionKey : uint +{ + SaveDataEncryption = 0x20454453 // "SDE " +} + +/// +/// Contains debug-related functions for the FS service. +/// +/// Based on nnSdk 13.4.0 +public static class DebugShim +{ + public static Result CreatePaddingFile(this FileSystemClient fs, long size) + { + using SharedRef fileSystemProxy = fs.Impl.GetFileSystemProxyServiceObject(); + + Result rc = fileSystemProxy.Get.CreatePaddingFile(size); + fs.Impl.AbortIfNeeded(rc); + if (rc.IsFailure()) return rc.Miss(); + + return Result.Success; + } + + public static Result DeleteAllPaddingFiles(this FileSystemClient fs) + { + using SharedRef fileSystemProxy = fs.Impl.GetFileSystemProxyServiceObject(); + + Result rc = fileSystemProxy.Get.DeleteAllPaddingFiles(); + fs.Impl.AbortIfNeeded(rc); + if (rc.IsFailure()) return rc.Miss(); + + return Result.Success; + } + + public static Result OverrideSaveDataTransferTokenSignVerificationKey(this FileSystemClient fs, + ReadOnlySpan keyBuffer) + { + using SharedRef fileSystemProxy = fs.Impl.GetFileSystemProxyServiceObject(); + + Result rc = fileSystemProxy.Get.OverrideSaveDataTransferTokenSignVerificationKey(new InBuffer(keyBuffer)); + fs.Impl.AbortIfNeeded(rc); + if (rc.IsFailure()) return rc.Miss(); + + return Result.Success; + } + + public static Result SetDebugOption(this FileSystemClient fs, DebugOptionKey key, long value) + { + using SharedRef fileSystemProxy = fs.Impl.GetFileSystemProxyServiceObject(); + + Result rc = fileSystemProxy.Get.RegisterDebugConfiguration((uint)key, value); + fs.Impl.AbortIfNeeded(rc); + if (rc.IsFailure()) return rc.Miss(); + + return Result.Success; + } + + public static Result UnsetDebugOption(this FileSystemClient fs, DebugOptionKey key) + { + using SharedRef fileSystemProxy = fs.Impl.GetFileSystemProxyServiceObject(); + + Result rc = fileSystemProxy.Get.UnregisterDebugConfiguration((uint)key); + fs.Impl.AbortIfNeeded(rc); + if (rc.IsFailure()) return rc.Miss(); + + return Result.Success; + } +} \ No newline at end of file diff --git a/src/LibHac/FsSrv/DebugConfigurationService.cs b/src/LibHac/FsSrv/DebugConfigurationService.cs new file mode 100644 index 00000000..cd3593aa --- /dev/null +++ b/src/LibHac/FsSrv/DebugConfigurationService.cs @@ -0,0 +1,163 @@ +using System; +using LibHac.Common.FixedArrays; +using LibHac.Diag; +using LibHac.Fs; +using LibHac.Fs.Shim; +using LibHac.FsSrv.Impl; +using LibHac.Os; + +namespace LibHac.FsSrv; + +/// +/// Handles debug configuration calls for . +/// +/// Based on FS 13.1.0 (nnSdk 13.4.0) +public struct DebugConfigurationService +{ + private DebugConfigurationServiceImpl _serviceImpl; + private ulong _processId; + + // LibHac addition + private readonly FileSystemServer _fsServer; + + public DebugConfigurationService(FileSystemServer fsServer, DebugConfigurationServiceImpl serviceImpl, + ulong processId) + { + _serviceImpl = serviceImpl; + _processId = processId; + _fsServer = fsServer; + } + + private Result GetProgramInfo(out ProgramInfo programInfo, ulong processId) + { + using var programRegistry = new ProgramRegistryImpl(_fsServer); + + return programRegistry.GetProgramInfo(out programInfo, processId); + } + + public Result Register(uint key, long value) + { + Result rc = GetProgramInfo(out ProgramInfo programInfo, _processId); + if (rc.IsFailure()) return rc.Miss(); + + if (!programInfo.AccessControl.CanCall(OperationType.SetDebugConfiguration)) + return ResultFs.PermissionDenied.Log(); + + _serviceImpl.Register(key, value); + return Result.Success; + } + + public Result Unregister(uint key) + { + Result rc = GetProgramInfo(out ProgramInfo programInfo, _processId); + if (rc.IsFailure()) return rc.Miss(); + + if (!programInfo.AccessControl.CanCall(OperationType.SetDebugConfiguration)) + return ResultFs.PermissionDenied.Log(); + + _serviceImpl.Unregister(key); + return Result.Success; + } +} + +/// +/// Manages a key-value list of debug settings. +/// +/// Based on FS 13.1.0 (nnSdk 13.4.0) +public class DebugConfigurationServiceImpl : IDisposable +{ + private Configuration _config; + private Array4 _entries; + private SdkMutexType _mutex; + + public struct Configuration + { + public bool IsDisabled; + } + + private struct Entry + { + public uint Key; + public long Value; + } + + public DebugConfigurationServiceImpl(in Configuration config) + { + _config = config; + _mutex = new SdkMutexType(); + } + + public void Dispose() { } + + public void Register(uint key, long value) + { + Abort.DoAbortUnless(key != 0); + + if (_config.IsDisabled) + return; + + using ScopedLock scopedLock = ScopedLock.Lock(ref _mutex); + + for (int i = 0; i < _entries.ItemsRo.Length; i++) + { + // Update the existing value if the key is already registered + if (_entries[i].Key == key) + { + _entries[i].Value = value; + return; + } + } + + for (int i = 0; i < _entries.ItemsRo.Length; i++) + { + if (_entries[i].Key == 0) + { + _entries[i].Key = key; + _entries[i].Value = value; + return; + } + } + + Abort.DoAbort(); + } + + public void Unregister(uint key) + { + Abort.DoAbortUnless(key != 0); + + if (_config.IsDisabled) + return; + + using ScopedLock scopedLock = ScopedLock.Lock(ref _mutex); + + for (int i = 0; i < _entries.ItemsRo.Length; i++) + { + if (_entries[i].Key == key) + { + _entries[i].Key = 0; + _entries[i].Value = 0; + break; + } + } + } + + public long Get(DebugOptionKey key, long defaultValue) + { + Abort.DoAbortUnless(key != 0); + + if (_config.IsDisabled) + return defaultValue; + + using ScopedLock scopedLock = ScopedLock.Lock(ref _mutex); + + for (int i = 0; i < _entries.ItemsRo.Length; i++) + { + if (_entries[i].Key == (uint)key) + { + return _entries[i].Value; + } + } + + return defaultValue; + } +} \ No newline at end of file diff --git a/src/LibHac/FsSrv/FileSystemProxyConfiguration.cs b/src/LibHac/FsSrv/FileSystemProxyConfiguration.cs index 16332bcf..90a97cd9 100644 --- a/src/LibHac/FsSrv/FileSystemProxyConfiguration.cs +++ b/src/LibHac/FsSrv/FileSystemProxyConfiguration.cs @@ -14,4 +14,5 @@ public class FileSystemProxyConfiguration public StatusReportServiceImpl StatusReportService { get; set; } public ProgramRegistryServiceImpl ProgramRegistryService { get; set; } public AccessLogServiceImpl AccessLogService { get; set; } -} + public DebugConfigurationServiceImpl DebugConfigurationService { get; set; } +} \ No newline at end of file diff --git a/src/LibHac/FsSrv/FileSystemProxyImpl.cs b/src/LibHac/FsSrv/FileSystemProxyImpl.cs index 615e1dbc..0299f61d 100644 --- a/src/LibHac/FsSrv/FileSystemProxyImpl.cs +++ b/src/LibHac/FsSrv/FileSystemProxyImpl.cs @@ -35,6 +35,7 @@ public static class FileSystemProxyImplGlobalMethods g.StatusReportServiceImpl = configuration.StatusReportService; g.ProgramRegistryServiceImpl = configuration.ProgramRegistryService; g.AccessLogServiceImpl = configuration.AccessLogService; + g.DebugConfigurationServiceImpl = configuration.DebugConfigurationService; } } @@ -49,6 +50,7 @@ internal struct FileSystemProxyImplGlobals public StatusReportServiceImpl StatusReportServiceImpl; public ProgramRegistryServiceImpl ProgramRegistryServiceImpl; public AccessLogServiceImpl AccessLogServiceImpl; + public DebugConfigurationServiceImpl DebugConfigurationServiceImpl; public Optional FileSystemProxyCoreImpl; } @@ -146,6 +148,11 @@ public class FileSystemProxyImpl : IFileSystemProxy, IFileSystemProxyForLoader return new AccessLogService(Globals.AccessLogServiceImpl, _currentProcess); } + private DebugConfigurationService GetDebugConfigurationService() + { + return new DebugConfigurationService(_fsServer, Globals.DebugConfigurationServiceImpl, _currentProcess); + } + public Result OpenFileSystemWithId(ref SharedRef outFileSystem, in FspPath path, ulong id, FileSystemProxyType fsType) { @@ -1151,4 +1158,14 @@ public class FileSystemProxyImpl : IFileSystemProxy, IFileSystemProxyForLoader { return GetBaseFileSystemService().OpenBisWiper(ref outBisWiper, transferMemoryHandle, transferMemorySize); } + + public Result RegisterDebugConfiguration(uint key, long value) + { + return GetDebugConfigurationService().Register(key, value); + } + + public Result UnregisterDebugConfiguration(uint key) + { + return GetDebugConfigurationService().Unregister(key); + } } \ No newline at end of file diff --git a/src/LibHac/FsSrv/FileSystemServerInitializer.cs b/src/LibHac/FsSrv/FileSystemServerInitializer.cs index 8210b936..f36d95c6 100644 --- a/src/LibHac/FsSrv/FileSystemServerInitializer.cs +++ b/src/LibHac/FsSrv/FileSystemServerInitializer.cs @@ -74,6 +74,11 @@ public static class FileSystemServerInitializer Memory heapBuffer = GC.AllocateArray(BufferManagerHeapSize, true); bufferManager.Initialize(BufferManagerCacheSize, heapBuffer, BufferManagerBlockSize); + // Todo: Assign based on the value of "IsDevelopment" + var debugConfigurationServiceConfig = new DebugConfigurationServiceImpl.Configuration(); + debugConfigurationServiceConfig.IsDisabled = false; + var debugConfigurationService = new DebugConfigurationServiceImpl(in debugConfigurationServiceConfig); + var saveDataIndexerManager = new SaveDataIndexerManager(server.Hos.Fs, Fs.SaveData.SaveIndexerId, new ArrayPoolMemoryResource(), new SdHandleManager(), false); @@ -175,7 +180,8 @@ public static class FileSystemServerInitializer TimeService = timeService, StatusReportService = statusReportService, ProgramRegistryService = programRegistryService, - AccessLogService = accessLogService + AccessLogService = accessLogService, + DebugConfigurationService = debugConfigurationService }; server.InitializeFileSystemProxy(fspConfig); diff --git a/src/LibHac/FsSrv/Sf/IFileSystemProxy.cs b/src/LibHac/FsSrv/Sf/IFileSystemProxy.cs index d6145bb0..8a179974 100644 --- a/src/LibHac/FsSrv/Sf/IFileSystemProxy.cs +++ b/src/LibHac/FsSrv/Sf/IFileSystemProxy.cs @@ -124,8 +124,10 @@ public interface IFileSystemProxy : IDisposable Result FlushAccessLogOnSdCard(); Result OutputApplicationInfoAccessLog(in ApplicationInfo applicationInfo); Result OutputMultiProgramTagAccessLog(); + Result RegisterDebugConfiguration(uint key, long value); + Result UnregisterDebugConfiguration(uint key); Result OverrideSaveDataTransferTokenSignVerificationKey(InBuffer key); Result CorruptSaveDataFileSystemByOffset(SaveDataSpaceId spaceId, ulong saveDataId, long offset); Result OpenMultiCommitManager(ref SharedRef outCommitManager); Result OpenBisWiper(ref SharedRef outBisWiper, NativeHandle transferMemoryHandle, ulong transferMemorySize); -} +} \ No newline at end of file diff --git a/tests/LibHac.Tests/Fs/FileSystemClientTests/ShimTests/Debug.cs b/tests/LibHac.Tests/Fs/FileSystemClientTests/ShimTests/Debug.cs new file mode 100644 index 00000000..72254ad9 --- /dev/null +++ b/tests/LibHac.Tests/Fs/FileSystemClientTests/ShimTests/Debug.cs @@ -0,0 +1,93 @@ +using LibHac.Common; +using LibHac.Fs; +using LibHac.Fs.Shim; +using LibHac.Ncm; +using Xunit; + +namespace LibHac.Tests.Fs.FileSystemClientTests.ShimTests; + +public class Debug +{ + [Fact] + public void SetDebugOption_KeyIsZero_Aborts() + { + FileSystemClient fs = FileSystemServerFactory.CreateClient(true); + + Assert.Throws(() => fs.SetDebugOption(0, 1)); + } + + [Fact] + public void SetDebugOption_NoPermissions_ReturnsPermissionDenied() + { + Horizon hos = HorizonFactory.CreateBasicHorizon(); + + HorizonClient client = + hos.CreateHorizonClient(new ProgramLocation(new ProgramId(1), StorageId.BuiltInSystem), 0); + + Assert.Result(ResultFs.PermissionDenied, client.Fs.SetDebugOption((DebugOptionKey)1, 0)); + } + + [Fact] + public void SetDebugOption_DebugConfigIsFull_Aborts() + { + FileSystemClient fs = FileSystemServerFactory.CreateClient(true); + + Assert.Success(fs.SetDebugOption((DebugOptionKey)1, 0)); + Assert.Success(fs.SetDebugOption((DebugOptionKey)2, 0)); + Assert.Success(fs.SetDebugOption((DebugOptionKey)3, 0)); + Assert.Success(fs.SetDebugOption((DebugOptionKey)4, 0)); + + Assert.Throws(() => fs.SetDebugOption((DebugOptionKey)5, 0)); + } + + [Fact] + public void SetDebugOption_ReplaceExistingValueWhenFull_ReturnsSuccess() + { + FileSystemClient fs = FileSystemServerFactory.CreateClient(true); + + Assert.Success(fs.SetDebugOption((DebugOptionKey)1, 0)); + Assert.Success(fs.SetDebugOption((DebugOptionKey)2, 0)); + Assert.Success(fs.SetDebugOption((DebugOptionKey)3, 0)); + Assert.Success(fs.SetDebugOption((DebugOptionKey)4, 0)); + + Assert.Success(fs.SetDebugOption((DebugOptionKey)1, 10)); + } + + [Fact] + public void SetDebugOption_AfterRemovingKeyWhenFull_ReturnsSuccess() + { + FileSystemClient fs = FileSystemServerFactory.CreateClient(true); + + Assert.Success(fs.SetDebugOption((DebugOptionKey)1, 0)); + Assert.Success(fs.SetDebugOption((DebugOptionKey)2, 0)); + Assert.Success(fs.SetDebugOption((DebugOptionKey)3, 0)); + Assert.Success(fs.SetDebugOption((DebugOptionKey)4, 0)); + + Assert.Success(fs.UnsetDebugOption((DebugOptionKey)2)); + + Assert.Success(fs.SetDebugOption((DebugOptionKey)2, 4)); + } + + [Fact] + public void UnsetDebugOption_UnsetExistingKey_ReturnsSuccess() + { + const DebugOptionKey key = DebugOptionKey.SaveDataEncryption; + const long value = 0; + + FileSystemClient fs = FileSystemServerFactory.CreateClient(true); + + Assert.Success(fs.SetDebugOption(key, value)); + Assert.Success(fs.UnsetDebugOption(key)); + } + + [Fact] + public void UnsetDebugOption_NoPermissions_ReturnsPermissionDenied() + { + Horizon hos = HorizonFactory.CreateBasicHorizon(); + + HorizonClient client = + hos.CreateHorizonClient(new ProgramLocation(new ProgramId(1), StorageId.BuiltInSystem), 0); + + Assert.Result(ResultFs.PermissionDenied, client.Fs.UnsetDebugOption((DebugOptionKey)1)); + } +} \ No newline at end of file