diff --git a/src/LibHac/FsSrv/FileSystemServer.cs b/src/LibHac/FsSrv/FileSystemServer.cs
index 1c6adecd..527ef384 100644
--- a/src/LibHac/FsSrv/FileSystemServer.cs
+++ b/src/LibHac/FsSrv/FileSystemServer.cs
@@ -29,6 +29,7 @@ internal struct FileSystemServerGlobals
public ProgramRegistryImplGlobals ProgramRegistryImpl;
public DeviceEventSimulatorGlobals DeviceEventSimulator;
public AccessControlGlobals AccessControl;
+ public ProgramInfoGlobals ProgramInfo;
public StorageDeviceManagerFactoryGlobals StorageDeviceManagerFactory;
public SaveDataSharedFileStorageGlobals SaveDataSharedFileStorage;
public MultiCommitManagerGlobals MultiCommitManager;
@@ -63,4 +64,4 @@ public readonly struct FileSystemServerImpl
internal ref FileSystemServerGlobals Globals => ref FsSrv.Globals;
internal FileSystemServerImpl(FileSystemServer parentServer) => FsSrv = parentServer;
-}
+}
\ No newline at end of file
diff --git a/src/LibHac/FsSrv/Impl/ProgramInfo.cs b/src/LibHac/FsSrv/Impl/ProgramInfo.cs
new file mode 100644
index 00000000..70ad6f6d
--- /dev/null
+++ b/src/LibHac/FsSrv/Impl/ProgramInfo.cs
@@ -0,0 +1,120 @@
+using System;
+using LibHac.Common;
+using LibHac.Diag;
+using LibHac.Ncm;
+using LibHac.Os;
+
+namespace LibHac.FsSrv.Impl;
+
+public static class ProgramInfoGlobalMethods
+{
+ public static bool IsInitialProgram(this FileSystemServer fsSrv, ulong processId)
+ {
+ ref ProgramInfoGlobals g = ref fsSrv.Globals.ProgramInfo;
+ using (var guard = new InitializationGuard(ref g.InitialProcessIdRangeInitGuard, fsSrv.Globals.InitMutex))
+ {
+ if (!guard.IsInitialized)
+ {
+ // Todo: We have no kernel to call into, so use hardcoded values for now
+ g.InitialProcessIdMin = OsState.InitialProcessCountMin;
+ g.InitialProcessIdMax = OsState.InitialProcessCountMax;
+
+ Abort.DoAbortUnless(0 < g.InitialProcessIdMin && g.InitialProcessIdMin <= g.InitialProcessIdMax,
+ "Invalid initial process ID range");
+ }
+ }
+
+ Abort.DoAbortUnless(g.InitialProcessIdMin != 0);
+
+ return g.InitialProcessIdMin <= processId && processId <= g.InitialProcessIdMax;
+ }
+
+ public static bool IsCurrentProcess(this FileSystemServer fsSrv, ulong processId)
+ {
+ ref ProgramInfoGlobals g = ref fsSrv.Globals.ProgramInfo;
+ using (var guard = new InitializationGuard(ref g.CurrentProcessIdInitGuard, fsSrv.Globals.InitMutex))
+ {
+ if (!guard.IsInitialized)
+ {
+ g.CurrentProcessId = fsSrv.Hos.Os.GetCurrentProcessId().Value;
+ }
+ }
+
+ return g.CurrentProcessId == processId;
+ }
+}
+
+internal struct ProgramInfoGlobals
+{
+ public nint CurrentProcessIdInitGuard;
+ public ulong CurrentProcessId;
+
+ public nint InitialProcessIdRangeInitGuard;
+ public ulong InitialProcessIdMin;
+ public ulong InitialProcessIdMax;
+
+ public nint ProgramInfoForInitialProcessInitGuard;
+ public ProgramInfo ProgramInfoForInitialProcess;
+}
+
+///
+/// Contains the program ID, storage location and FS permissions of a running process.
+///
+/// Based on FS 13.1.0 (nnSdk 13.4.0)
+public class ProgramInfo
+{
+ private readonly ulong _processId;
+ public ProgramId ProgramId { get; }
+ public StorageId StorageId { get; }
+ public AccessControl AccessControl { get; }
+
+ public ulong ProgramIdValue => ProgramId.Value;
+
+ private ProgramInfo(FileSystemServer fsServer, ReadOnlySpan accessControlData,
+ ReadOnlySpan accessControlDescriptor)
+ {
+ _processId = 0;
+ AccessControl = new AccessControl(fsServer, accessControlData, accessControlDescriptor, ulong.MaxValue);
+ ProgramId = default;
+ StorageId = 0;
+ }
+
+ public ProgramInfo(FileSystemServer fsServer, ulong processId, ProgramId programId, StorageId storageId,
+ ReadOnlySpan accessControlData, ReadOnlySpan accessControlDescriptor)
+ {
+ _processId = processId;
+ AccessControl = new AccessControl(fsServer, accessControlData, accessControlDescriptor);
+ ProgramId = programId;
+ StorageId = storageId;
+ }
+
+ public bool Contains(ulong processId) => _processId == processId;
+
+ public static ProgramInfo GetProgramInfoForInitialProcess(FileSystemServer fsSrv)
+ {
+ ref ProgramInfoGlobals g = ref fsSrv.Globals.ProgramInfo;
+ using (var guard = new InitializationGuard(ref g.ProgramInfoForInitialProcessInitGuard, fsSrv.Globals.InitMutex))
+ {
+ if (!guard.IsInitialized)
+ {
+ g.ProgramInfoForInitialProcess = new ProgramInfo(fsSrv, InitialProcessAccessControlDataHeader,
+ InitialProcessAccessControlDescriptor);
+ }
+ }
+
+ return g.ProgramInfoForInitialProcess;
+ }
+
+ private static ReadOnlySpan InitialProcessAccessControlDataHeader => new byte[]
+ {
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x1C, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x1C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
+ };
+
+ private static ReadOnlySpan InitialProcessAccessControlDescriptor => new byte[]
+ {
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF
+ };
+}
\ No newline at end of file
diff --git a/src/LibHac/FsSrv/Impl/ProgramRegistryManager.cs b/src/LibHac/FsSrv/Impl/ProgramRegistryManager.cs
index 6e722f71..be69cc92 100644
--- a/src/LibHac/FsSrv/Impl/ProgramRegistryManager.cs
+++ b/src/LibHac/FsSrv/Impl/ProgramRegistryManager.cs
@@ -3,43 +3,31 @@ using System.Collections.Generic;
using LibHac.Common;
using LibHac.Fs;
using LibHac.Ncm;
+using LibHac.Os;
namespace LibHac.FsSrv.Impl;
///
/// Handles adding, removing, and accessing from the .
///
-/// Based on FS 10.0.0 (nnSdk 10.4.0)
-internal class ProgramRegistryManager
+/// Based on FS 13.1.0 (nnSdk 13.4.0)
+internal class ProgramRegistryManager : IDisposable
{
// Note: FS keeps each ProgramInfo in a shared_ptr, but there aren't any non-memory resources
// that need to be freed, so we use plain ProgramInfos
- private LinkedList ProgramInfoList { get; }
- private FileSystemServer FsServer { get; }
+ private LinkedList _programInfoList;
+ private SdkMutexType _mutex;
- // Note: This variable is global in FS. It's moved to ProgramRegistryManager here because it
- // relies on some state kept in FileSystemServer, and it's only used by ProgramRegistryManager
- private ProgramInfo _programInfoForInitialProcess;
- private readonly object _programInfoForInitialProcessGuard = new object();
+ private FileSystemServer _fsServer;
public ProgramRegistryManager(FileSystemServer fsServer)
{
- ProgramInfoList = new LinkedList();
- FsServer = fsServer;
+ _programInfoList = new LinkedList();
+ _mutex = new SdkMutexType();
+ _fsServer = fsServer;
}
- private ProgramInfo GetProgramInfoForInitialProcess()
- {
- if (_programInfoForInitialProcess == null)
- {
- lock (_programInfoForInitialProcessGuard)
- {
- _programInfoForInitialProcess ??= ProgramInfo.CreateProgramInfoForInitialProcess(FsServer);
- }
- }
-
- return _programInfoForInitialProcess;
- }
+ public void Dispose() { }
///
/// Registers a program with information about that program in the program registry.
@@ -54,20 +42,19 @@ internal class ProgramRegistryManager
public Result RegisterProgram(ulong processId, ProgramId programId, StorageId storageId,
ReadOnlySpan accessControlData, ReadOnlySpan accessControlDescriptor)
{
- var programInfo = new ProgramInfo(FsServer, processId, programId, storageId, accessControlData,
+ var programInfo = new ProgramInfo(_fsServer, processId, programId, storageId, accessControlData,
accessControlDescriptor);
- lock (ProgramInfoList)
- {
- foreach (ProgramInfo info in ProgramInfoList)
- {
- if (info.Contains(processId))
- return ResultFs.InvalidArgument.Log();
- }
+ using ScopedLock scopedLock = ScopedLock.Lock(ref _mutex);
- ProgramInfoList.AddLast(programInfo);
- return Result.Success;
+ foreach (ProgramInfo info in _programInfoList)
+ {
+ if (info.Contains(processId))
+ return ResultFs.InvalidArgument.Log();
}
+
+ _programInfoList.AddLast(programInfo);
+ return Result.Success;
}
///
@@ -78,19 +65,18 @@ internal class ProgramRegistryManager
/// : The process ID is not registered.
public Result UnregisterProgram(ulong processId)
{
- lock (ProgramInfoList)
- {
- for (LinkedListNode node = ProgramInfoList.First; node != null; node = node.Next)
- {
- if (node.Value.Contains(processId))
- {
- ProgramInfoList.Remove(node);
- return Result.Success;
- }
- }
+ using ScopedLock scopedLock = ScopedLock.Lock(ref _mutex);
- return ResultFs.InvalidArgument.Log();
+ for (LinkedListNode node = _programInfoList.First; node != null; node = node.Next)
+ {
+ if (node.Value.Contains(processId))
+ {
+ _programInfoList.Remove(node);
+ return Result.Success;
+ }
}
+
+ return ResultFs.InvalidArgument.Log();
}
///
@@ -103,26 +89,25 @@ internal class ProgramRegistryManager
/// : The was not found.
public Result GetProgramInfo(out ProgramInfo programInfo, ulong processId)
{
- lock (ProgramInfoList)
+ using ScopedLock scopedLock = ScopedLock.Lock(ref _mutex);
+
+ if (_fsServer.IsInitialProgram(processId))
{
- if (ProgramInfo.IsInitialProgram(processId))
+ programInfo = ProgramInfo.GetProgramInfoForInitialProcess(_fsServer);
+ return Result.Success;
+ }
+
+ foreach (ProgramInfo info in _programInfoList)
+ {
+ if (info.Contains(processId))
{
- programInfo = GetProgramInfoForInitialProcess();
+ programInfo = info;
return Result.Success;
}
-
- foreach (ProgramInfo info in ProgramInfoList)
- {
- if (info.Contains(processId))
- {
- programInfo = info;
- return Result.Success;
- }
- }
-
- UnsafeHelpers.SkipParamInit(out programInfo);
- return ResultFs.ProgramInfoNotFound.Log();
}
+
+ UnsafeHelpers.SkipParamInit(out programInfo);
+ return ResultFs.ProgramInfoNotFound.Log();
}
///
@@ -135,77 +120,18 @@ internal class ProgramRegistryManager
/// : The was not found.
public Result GetProgramInfoByProgramId(out ProgramInfo programInfo, ulong programId)
{
- lock (ProgramInfoList)
+ using ScopedLock scopedLock = ScopedLock.Lock(ref _mutex);
+
+ foreach (ProgramInfo info in _programInfoList)
{
- foreach (ProgramInfo info in ProgramInfoList)
+ if (info.ProgramId.Value == programId)
{
- if (info.ProgramId.Value == programId)
- {
- programInfo = info;
- return Result.Success;
- }
+ programInfo = info;
+ return Result.Success;
}
-
- UnsafeHelpers.SkipParamInit(out programInfo);
- return ResultFs.ProgramInfoNotFound.Log();
}
+
+ UnsafeHelpers.SkipParamInit(out programInfo);
+ return ResultFs.ProgramInfoNotFound.Log();
}
-}
-
-public class ProgramInfo
-{
- private ulong ProcessId { get; }
- public ProgramId ProgramId { get; }
- public StorageId StorageId { get; }
- public AccessControl AccessControl { get; }
-
- public ulong ProgramIdValue => ProgramId.Value;
-
- public ProgramInfo(FileSystemServer fsServer, ulong processId, ProgramId programId, StorageId storageId,
- ReadOnlySpan accessControlData, ReadOnlySpan accessControlDescriptor)
- {
- ProcessId = processId;
- AccessControl = new AccessControl(fsServer, accessControlData, accessControlDescriptor);
- ProgramId = programId;
- StorageId = storageId;
- }
-
- private ProgramInfo(FileSystemServer fsServer, ReadOnlySpan accessControlData,
- ReadOnlySpan accessControlDescriptor)
- {
- ProcessId = 0;
- AccessControl = new AccessControl(fsServer, accessControlData, accessControlDescriptor, ulong.MaxValue);
- ProgramId = default;
- StorageId = 0;
- }
-
- public bool Contains(ulong processId) => ProcessId == processId;
-
- public static bool IsInitialProgram(ulong processId)
- {
- // Todo: We have no kernel to call into, so use hardcoded values for now
- const int initialProcessIdLowerBound = 1;
- const int initialProcessIdUpperBound = 0x50;
-
- return initialProcessIdLowerBound <= processId && processId <= initialProcessIdUpperBound;
- }
-
- internal static ProgramInfo CreateProgramInfoForInitialProcess(FileSystemServer fsServer)
- {
- return new ProgramInfo(fsServer, InitialProcessAccessControlDataHeader,
- InitialProcessAccessControlDescriptor);
- }
-
- private static ReadOnlySpan InitialProcessAccessControlDataHeader => new byte[]
- {
- 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x1C, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x1C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
- };
-
- private static ReadOnlySpan InitialProcessAccessControlDescriptor => new byte[]
- {
- 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF
- };
-}
+}
\ No newline at end of file
diff --git a/src/LibHac/FsSrv/ProgramRegistryImpl.cs b/src/LibHac/FsSrv/ProgramRegistryImpl.cs
index 973714ce..fc7c3633 100644
--- a/src/LibHac/FsSrv/ProgramRegistryImpl.cs
+++ b/src/LibHac/FsSrv/ProgramRegistryImpl.cs
@@ -50,7 +50,7 @@ public class ProgramRegistryImpl : IProgramRegistry
public Result RegisterProgram(ulong processId, ProgramId programId, StorageId storageId,
InBuffer accessControlData, InBuffer accessControlDescriptor)
{
- if (!ProgramInfo.IsInitialProgram(_processId))
+ if (!_fsServer.IsInitialProgram(_processId))
return ResultFs.PermissionDenied.Log();
return Globals.ServiceImpl.RegisterProgramInfo(processId, programId, storageId, accessControlData.Buffer,
@@ -63,7 +63,7 @@ public class ProgramRegistryImpl : IProgramRegistry
///
public Result UnregisterProgram(ulong processId)
{
- if (!ProgramInfo.IsInitialProgram(_processId))
+ if (!_fsServer.IsInitialProgram(_processId))
return ResultFs.PermissionDenied.Log();
return Globals.ServiceImpl.UnregisterProgramInfo(processId);
@@ -91,4 +91,4 @@ public class ProgramRegistryImpl : IProgramRegistry
_processId = processId;
return Result.Success;
}
-}
+}
\ No newline at end of file
diff --git a/src/LibHac/FsSrv/SaveDataFileSystemService.cs b/src/LibHac/FsSrv/SaveDataFileSystemService.cs
index bf148599..fb1287c6 100644
--- a/src/LibHac/FsSrv/SaveDataFileSystemService.cs
+++ b/src/LibHac/FsSrv/SaveDataFileSystemService.cs
@@ -517,7 +517,7 @@ internal class SaveDataFileSystemService : ISaveDataTransferCoreInterface, ISave
// Only the FS process may delete the save indexer's save data.
if (saveDataId == SaveData.SaveIndexerId)
{
- if (!IsCurrentProcess(_processId))
+ if (!_serviceImpl.FsServer.IsCurrentProcess(_processId))
return ResultFs.PermissionDenied.Log();
actualSpaceId = spaceId;
@@ -2157,13 +2157,6 @@ internal class SaveDataFileSystemService : ISaveDataTransferCoreInterface, ISave
return _serviceImpl.GetProgramInfo(out programInfo, _processId);
}
- private bool IsCurrentProcess(ulong processId)
- {
- ulong currentId = Hos.Os.GetCurrentProcessId().Value;
-
- return processId == currentId;
- }
-
private SaveDataSpaceId ConvertToRealSpaceId(SaveDataSpaceId spaceId)
{
return spaceId == SaveDataSpaceId.ProperSystem || spaceId == SaveDataSpaceId.SafeMode
diff --git a/src/LibHac/Horizon.cs b/src/LibHac/Horizon.cs
index e1756c0f..1d58acb1 100644
--- a/src/LibHac/Horizon.cs
+++ b/src/LibHac/Horizon.cs
@@ -12,8 +12,6 @@ namespace LibHac;
public class Horizon
{
- private const int InitialProcessCountMax = 0x50;
-
internal ITickGenerator TickGenerator { get; }
internal ServiceManager ServiceManager { get; }
private HorizonClient LoaderClient { get; }
@@ -23,7 +21,7 @@ public class Horizon
public Horizon(HorizonConfiguration config)
{
- _currentProcessId = InitialProcessCountMax;
+ _currentProcessId = OsState.InitialProcessCountMax + 1;
TickGenerator = config.TickGenerator ?? new DefaultTickGenerator();
ServiceManager = new ServiceManager();
@@ -35,7 +33,7 @@ public class Horizon
{
ulong processId = Interlocked.Increment(ref _currentInitialProcessId);
- Abort.DoAbortUnless(processId <= InitialProcessCountMax, "Created too many privileged clients.");
+ Abort.DoAbortUnless(processId <= OsState.InitialProcessCountMax, "Created too many privileged clients.");
// Todo: Register process with FS
@@ -82,4 +80,4 @@ public class Horizon
return client;
}
-}
+}
\ No newline at end of file
diff --git a/src/LibHac/Os/OsState.cs b/src/LibHac/Os/OsState.cs
index 3a1d27a5..1322c9db 100644
--- a/src/LibHac/Os/OsState.cs
+++ b/src/LibHac/Os/OsState.cs
@@ -5,6 +5,9 @@ namespace LibHac.Os;
public class OsState : IDisposable
{
+ internal const int InitialProcessCountMin = 1;
+ internal const int InitialProcessCountMax = 0x50;
+
public OsStateImpl Impl => new OsStateImpl(this);
internal HorizonClient Hos { get; }
internal OsResourceManager ResourceManager { get; }
@@ -34,4 +37,4 @@ public readonly struct OsStateImpl
internal HorizonClient Hos => Os.Hos;
internal OsStateImpl(OsState parent) => Os = parent;
-}
+}
\ No newline at end of file