aboutsummaryrefslogtreecommitdiff
path: root/Ryujinx.Horizon/Sdk
diff options
context:
space:
mode:
Diffstat (limited to 'Ryujinx.Horizon/Sdk')
-rw-r--r--Ryujinx.Horizon/Sdk/DebugUtil.cs12
-rw-r--r--Ryujinx.Horizon/Sdk/Diag/LogSeverity.cs11
-rw-r--r--Ryujinx.Horizon/Sdk/Lm/LogDataChunkKey.cs19
-rw-r--r--Ryujinx.Horizon/Sdk/Lm/LogDestination.cs14
-rw-r--r--Ryujinx.Horizon/Sdk/Lm/LogPacketFlags.cs12
-rw-r--r--Ryujinx.Horizon/Sdk/Lm/LogPacketHeader.cs15
-rw-r--r--Ryujinx.Horizon/Sdk/OsTypes/Event.cs61
-rw-r--r--Ryujinx.Horizon/Sdk/OsTypes/EventClearMode.cs8
-rw-r--r--Ryujinx.Horizon/Sdk/OsTypes/EventType.cs15
-rw-r--r--Ryujinx.Horizon/Sdk/OsTypes/Impl/InterProcessEvent.cs89
-rw-r--r--Ryujinx.Horizon/Sdk/OsTypes/Impl/InterProcessEventImpl.cs136
-rw-r--r--Ryujinx.Horizon/Sdk/OsTypes/Impl/MultiWaitImpl.cs250
-rw-r--r--Ryujinx.Horizon/Sdk/OsTypes/InitializationState.cs8
-rw-r--r--Ryujinx.Horizon/Sdk/OsTypes/InterProcessEventType.cs27
-rw-r--r--Ryujinx.Horizon/Sdk/OsTypes/MultiWait.cs43
-rw-r--r--Ryujinx.Horizon/Sdk/OsTypes/MultiWaitHolder.cs16
-rw-r--r--Ryujinx.Horizon/Sdk/OsTypes/MultiWaitHolderBase.cs39
-rw-r--r--Ryujinx.Horizon/Sdk/OsTypes/MultiWaitHolderOfEvent.cs45
-rw-r--r--Ryujinx.Horizon/Sdk/OsTypes/MultiWaitHolderOfHandle.cs14
-rw-r--r--Ryujinx.Horizon/Sdk/OsTypes/OsEvent.cs130
-rw-r--r--Ryujinx.Horizon/Sdk/OsTypes/OsMultiWait.cs10
-rw-r--r--Ryujinx.Horizon/Sdk/OsTypes/OsProcessHandle.cs33
-rw-r--r--Ryujinx.Horizon/Sdk/OsTypes/OsResult.cs11
-rw-r--r--Ryujinx.Horizon/Sdk/OsTypes/OsSystemEvent.cs85
-rw-r--r--Ryujinx.Horizon/Sdk/OsTypes/OsThreadManager.cs10
-rw-r--r--Ryujinx.Horizon/Sdk/OsTypes/SystemEventType.cs17
-rw-r--r--Ryujinx.Horizon/Sdk/OsTypes/TriBool.cs9
-rw-r--r--Ryujinx.Horizon/Sdk/ServiceUtil.cs38
-rw-r--r--Ryujinx.Horizon/Sdk/Sf/Cmif/CmifDomainInHeader.cs12
-rw-r--r--Ryujinx.Horizon/Sdk/Sf/Cmif/CmifDomainOutHeader.cs12
-rw-r--r--Ryujinx.Horizon/Sdk/Sf/Cmif/CmifDomainRequestType.cs9
-rw-r--r--Ryujinx.Horizon/Sdk/Sf/Cmif/CmifInHeader.cs10
-rw-r--r--Ryujinx.Horizon/Sdk/Sf/Cmif/CmifMessage.cs128
-rw-r--r--Ryujinx.Horizon/Sdk/Sf/Cmif/CmifOutHeader.cs14
-rw-r--r--Ryujinx.Horizon/Sdk/Sf/Cmif/CmifRequest.cs14
-rw-r--r--Ryujinx.Horizon/Sdk/Sf/Cmif/CmifRequestFormat.cs24
-rw-r--r--Ryujinx.Horizon/Sdk/Sf/Cmif/CmifResponse.cs12
-rw-r--r--Ryujinx.Horizon/Sdk/Sf/Cmif/CommandType.cs14
-rw-r--r--Ryujinx.Horizon/Sdk/Sf/Cmif/DomainServiceObject.cs7
-rw-r--r--Ryujinx.Horizon/Sdk/Sf/Cmif/DomainServiceObjectDispatchTable.cs75
-rw-r--r--Ryujinx.Horizon/Sdk/Sf/Cmif/DomainServiceObjectProcessor.cs140
-rw-r--r--Ryujinx.Horizon/Sdk/Sf/Cmif/HandlesToClose.cs52
-rw-r--r--Ryujinx.Horizon/Sdk/Sf/Cmif/InlineContext.cs11
-rw-r--r--Ryujinx.Horizon/Sdk/Sf/Cmif/PointerAndSize.cs17
-rw-r--r--Ryujinx.Horizon/Sdk/Sf/Cmif/ScopedInlineContextChange.cs19
-rw-r--r--Ryujinx.Horizon/Sdk/Sf/Cmif/ServerDomainBase.cs15
-rw-r--r--Ryujinx.Horizon/Sdk/Sf/Cmif/ServerDomainManager.cs246
-rw-r--r--Ryujinx.Horizon/Sdk/Sf/Cmif/ServerMessageProcessor.cs18
-rw-r--r--Ryujinx.Horizon/Sdk/Sf/Cmif/ServerMessageRuntimeMetadata.cs29
-rw-r--r--Ryujinx.Horizon/Sdk/Sf/Cmif/ServiceDispatchContext.cs18
-rw-r--r--Ryujinx.Horizon/Sdk/Sf/Cmif/ServiceDispatchMeta.cs12
-rw-r--r--Ryujinx.Horizon/Sdk/Sf/Cmif/ServiceDispatchTable.cs33
-rw-r--r--Ryujinx.Horizon/Sdk/Sf/Cmif/ServiceDispatchTableBase.cs90
-rw-r--r--Ryujinx.Horizon/Sdk/Sf/Cmif/ServiceObjectHolder.cs34
-rw-r--r--Ryujinx.Horizon/Sdk/Sf/CmifCommandAttribute.cs15
-rw-r--r--Ryujinx.Horizon/Sdk/Sf/CommandArg.cs56
-rw-r--r--Ryujinx.Horizon/Sdk/Sf/CommandArgAttributes.cs38
-rw-r--r--Ryujinx.Horizon/Sdk/Sf/CommandHandler.cs57
-rw-r--r--Ryujinx.Horizon/Sdk/Sf/CommandSerialization.cs68
-rw-r--r--Ryujinx.Horizon/Sdk/Sf/Hipc/Api.cs89
-rw-r--r--Ryujinx.Horizon/Sdk/Sf/Hipc/Header.cs65
-rw-r--r--Ryujinx.Horizon/Sdk/Sf/Hipc/HipcBufferDescriptor.cs15
-rw-r--r--Ryujinx.Horizon/Sdk/Sf/Hipc/HipcBufferFlags.cs17
-rw-r--r--Ryujinx.Horizon/Sdk/Sf/Hipc/HipcBufferMode.cs10
-rw-r--r--Ryujinx.Horizon/Sdk/Sf/Hipc/HipcManager.cs115
-rw-r--r--Ryujinx.Horizon/Sdk/Sf/Hipc/HipcMessage.cs222
-rw-r--r--Ryujinx.Horizon/Sdk/Sf/Hipc/HipcMessageData.cs16
-rw-r--r--Ryujinx.Horizon/Sdk/Sf/Hipc/HipcMetadata.cs16
-rw-r--r--Ryujinx.Horizon/Sdk/Sf/Hipc/HipcReceiveListEntry.cs14
-rw-r--r--Ryujinx.Horizon/Sdk/Sf/Hipc/HipcResult.cs22
-rw-r--r--Ryujinx.Horizon/Sdk/Sf/Hipc/HipcStaticDescriptor.cs22
-rw-r--r--Ryujinx.Horizon/Sdk/Sf/Hipc/ManagerOptions.cs20
-rw-r--r--Ryujinx.Horizon/Sdk/Sf/Hipc/ReceiveResult.cs9
-rw-r--r--Ryujinx.Horizon/Sdk/Sf/Hipc/Server.cs36
-rw-r--r--Ryujinx.Horizon/Sdk/Sf/Hipc/ServerDomainSessionManager.cs23
-rw-r--r--Ryujinx.Horizon/Sdk/Sf/Hipc/ServerManager.cs198
-rw-r--r--Ryujinx.Horizon/Sdk/Sf/Hipc/ServerManagerBase.cs307
-rw-r--r--Ryujinx.Horizon/Sdk/Sf/Hipc/ServerSession.cs23
-rw-r--r--Ryujinx.Horizon/Sdk/Sf/Hipc/ServerSessionManager.cs335
-rw-r--r--Ryujinx.Horizon/Sdk/Sf/Hipc/SpecialHeader.cs27
-rw-r--r--Ryujinx.Horizon/Sdk/Sf/HipcCommandProcessor.cs421
-rw-r--r--Ryujinx.Horizon/Sdk/Sf/IServiceObject.cs9
-rw-r--r--Ryujinx.Horizon/Sdk/Sf/RawDataOffsetCalculator.cs51
-rw-r--r--Ryujinx.Horizon/Sdk/Sf/SfResult.cs31
-rw-r--r--Ryujinx.Horizon/Sdk/Sm/ServiceName.cs98
-rw-r--r--Ryujinx.Horizon/Sdk/Sm/SmApi.cs107
86 files changed, 4794 insertions, 0 deletions
diff --git a/Ryujinx.Horizon/Sdk/DebugUtil.cs b/Ryujinx.Horizon/Sdk/DebugUtil.cs
new file mode 100644
index 00000000..f56a50ec
--- /dev/null
+++ b/Ryujinx.Horizon/Sdk/DebugUtil.cs
@@ -0,0 +1,12 @@
+using System.Diagnostics;
+
+namespace Ryujinx.Horizon.Sdk
+{
+ static class DebugUtil
+ {
+ public static void Assert(bool condition)
+ {
+ Debug.Assert(condition);
+ }
+ }
+}
diff --git a/Ryujinx.Horizon/Sdk/Diag/LogSeverity.cs b/Ryujinx.Horizon/Sdk/Diag/LogSeverity.cs
new file mode 100644
index 00000000..72acf789
--- /dev/null
+++ b/Ryujinx.Horizon/Sdk/Diag/LogSeverity.cs
@@ -0,0 +1,11 @@
+namespace Ryujinx.Horizon.Sdk.Diag
+{
+ enum LogSeverity : byte
+ {
+ Trace = 0,
+ Info = 1,
+ Warn = 2,
+ Error = 3,
+ Fatal = 4
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Horizon/Sdk/Lm/LogDataChunkKey.cs b/Ryujinx.Horizon/Sdk/Lm/LogDataChunkKey.cs
new file mode 100644
index 00000000..90756ece
--- /dev/null
+++ b/Ryujinx.Horizon/Sdk/Lm/LogDataChunkKey.cs
@@ -0,0 +1,19 @@
+namespace Ryujinx.Horizon.Sdk.Lm
+{
+ enum LogDataChunkKey
+ {
+ Start = 0,
+ Stop = 1,
+ Message = 2,
+ Line = 3,
+ Filename = 4,
+ Function = 5,
+ Module = 6,
+ Thread = 7,
+ DropCount = 8,
+ Time = 9,
+ ProgramName = 10,
+
+ Count
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Horizon/Sdk/Lm/LogDestination.cs b/Ryujinx.Horizon/Sdk/Lm/LogDestination.cs
new file mode 100644
index 00000000..8b08548d
--- /dev/null
+++ b/Ryujinx.Horizon/Sdk/Lm/LogDestination.cs
@@ -0,0 +1,14 @@
+using System;
+
+namespace Ryujinx.Horizon.Sdk.Lm
+{
+ [Flags]
+ enum LogDestination
+ {
+ TargetManager = 1 << 0,
+ Uart = 1 << 1,
+ UartIfSleep = 1 << 2,
+
+ All = 0xffff
+ }
+}
diff --git a/Ryujinx.Horizon/Sdk/Lm/LogPacketFlags.cs b/Ryujinx.Horizon/Sdk/Lm/LogPacketFlags.cs
new file mode 100644
index 00000000..75d9f40b
--- /dev/null
+++ b/Ryujinx.Horizon/Sdk/Lm/LogPacketFlags.cs
@@ -0,0 +1,12 @@
+using System;
+
+namespace Ryujinx.Horizon.Sdk.Lm
+{
+ [Flags]
+ enum LogPacketFlags : byte
+ {
+ IsHead = 1 << 0,
+ IsTail = 1 << 1,
+ IsLittleEndian = 1 << 2
+ }
+}
diff --git a/Ryujinx.Horizon/Sdk/Lm/LogPacketHeader.cs b/Ryujinx.Horizon/Sdk/Lm/LogPacketHeader.cs
new file mode 100644
index 00000000..022ba8da
--- /dev/null
+++ b/Ryujinx.Horizon/Sdk/Lm/LogPacketHeader.cs
@@ -0,0 +1,15 @@
+using Ryujinx.Horizon.Sdk.Diag;
+
+namespace Ryujinx.Horizon.Sdk.Lm
+{
+ struct LogPacketHeader
+ {
+ public ulong ProcessId;
+ public ulong ThreadId;
+ public LogPacketFlags Flags;
+ public byte Padding;
+ public LogSeverity Severity;
+ public byte Verbosity;
+ public uint PayloadSize;
+ }
+}
diff --git a/Ryujinx.Horizon/Sdk/OsTypes/Event.cs b/Ryujinx.Horizon/Sdk/OsTypes/Event.cs
new file mode 100644
index 00000000..79d7408e
--- /dev/null
+++ b/Ryujinx.Horizon/Sdk/OsTypes/Event.cs
@@ -0,0 +1,61 @@
+using System;
+using System.Collections.Generic;
+
+namespace Ryujinx.Horizon.Sdk.OsTypes
+{
+ class Event : IDisposable
+ {
+ private EventType _event;
+
+ public object EventLock => _event.Lock;
+ public LinkedList<MultiWaitHolderBase> MultiWaitHolders => _event.MultiWaitHolders;
+
+ public Event(EventClearMode clearMode)
+ {
+ Os.InitializeEvent(out _event, signaled: false, clearMode);
+ }
+
+ public TriBool IsSignaledThreadUnsafe()
+ {
+ return _event.Signaled ? TriBool.True : TriBool.False;
+ }
+
+ public void Wait()
+ {
+ Os.WaitEvent(ref _event);
+ }
+
+ public bool TryWait()
+ {
+ return Os.TryWaitEvent(ref _event);
+ }
+
+ public bool TimedWait(TimeSpan timeout)
+ {
+ return Os.TimedWaitEvent(ref _event, timeout);
+ }
+
+ public void Signal()
+ {
+ Os.SignalEvent(ref _event);
+ }
+
+ public void Clear()
+ {
+ Os.ClearEvent(ref _event);
+ }
+
+ protected virtual void Dispose(bool disposing)
+ {
+ if (disposing)
+ {
+ Os.FinalizeEvent(ref _event);
+ }
+ }
+
+ public void Dispose()
+ {
+ Dispose(true);
+ }
+ }
+}
diff --git a/Ryujinx.Horizon/Sdk/OsTypes/EventClearMode.cs b/Ryujinx.Horizon/Sdk/OsTypes/EventClearMode.cs
new file mode 100644
index 00000000..b500e6b3
--- /dev/null
+++ b/Ryujinx.Horizon/Sdk/OsTypes/EventClearMode.cs
@@ -0,0 +1,8 @@
+namespace Ryujinx.Horizon.Sdk.OsTypes
+{
+ enum EventClearMode
+ {
+ ManualClear,
+ AutoClear
+ }
+}
diff --git a/Ryujinx.Horizon/Sdk/OsTypes/EventType.cs b/Ryujinx.Horizon/Sdk/OsTypes/EventType.cs
new file mode 100644
index 00000000..b4b1a275
--- /dev/null
+++ b/Ryujinx.Horizon/Sdk/OsTypes/EventType.cs
@@ -0,0 +1,15 @@
+using System.Collections.Generic;
+
+namespace Ryujinx.Horizon.Sdk.OsTypes
+{
+ struct EventType
+ {
+ public LinkedList<MultiWaitHolderBase> MultiWaitHolders;
+ public bool Signaled;
+ public bool InitiallySignaled;
+ public EventClearMode ClearMode;
+ public InitializationState State;
+ public ulong BroadcastCounter;
+ public object Lock;
+ }
+}
diff --git a/Ryujinx.Horizon/Sdk/OsTypes/Impl/InterProcessEvent.cs b/Ryujinx.Horizon/Sdk/OsTypes/Impl/InterProcessEvent.cs
new file mode 100644
index 00000000..62b5bf06
--- /dev/null
+++ b/Ryujinx.Horizon/Sdk/OsTypes/Impl/InterProcessEvent.cs
@@ -0,0 +1,89 @@
+using Ryujinx.Horizon.Common;
+
+namespace Ryujinx.Horizon.Sdk.OsTypes.Impl
+{
+ static class InterProcessEvent
+ {
+ public static Result Create(ref InterProcessEventType ipEvent, EventClearMode clearMode)
+ {
+ Result result = InterProcessEventImpl.Create(out int writableHandle, out int readableHandle);
+
+ if (result != Result.Success)
+ {
+ return result;
+ }
+
+ ipEvent = new InterProcessEventType(
+ clearMode == EventClearMode.AutoClear,
+ true,
+ true,
+ readableHandle,
+ writableHandle);
+
+ return Result.Success;
+ }
+
+ public static void Destroy(ref InterProcessEventType ipEvent)
+ {
+ ipEvent.State = InitializationState.NotInitialized;
+
+ if (ipEvent.ReadableHandleManaged)
+ {
+ if (ipEvent.ReadableHandle != 0)
+ {
+ InterProcessEventImpl.Close(ipEvent.ReadableHandle);
+ }
+ ipEvent.ReadableHandleManaged = false;
+ }
+
+ if (ipEvent.WritableHandleManaged)
+ {
+ if (ipEvent.WritableHandle != 0)
+ {
+ InterProcessEventImpl.Close(ipEvent.WritableHandle);
+ }
+ ipEvent.WritableHandleManaged = false;
+ }
+ }
+
+ public static int DetachReadableHandle(ref InterProcessEventType ipEvent)
+ {
+ int handle = ipEvent.ReadableHandle;
+
+ ipEvent.ReadableHandle = 0;
+ ipEvent.ReadableHandleManaged = false;
+
+ return handle;
+ }
+
+ public static int DetachWritableHandle(ref InterProcessEventType ipEvent)
+ {
+ int handle = ipEvent.WritableHandle;
+
+ ipEvent.WritableHandle = 0;
+ ipEvent.WritableHandleManaged = false;
+
+ return handle;
+ }
+
+ public static int GetReadableHandle(ref InterProcessEventType ipEvent)
+ {
+ return ipEvent.ReadableHandle;
+ }
+
+ public static int GetWritableHandle(ref InterProcessEventType ipEvent)
+ {
+ return ipEvent.WritableHandle;
+ }
+
+ public static void Signal(ref InterProcessEventType ipEvent)
+ {
+ InterProcessEventImpl.Signal(ipEvent.WritableHandle);
+ }
+
+ public static void Clear(ref InterProcessEventType ipEvent)
+ {
+ InterProcessEventImpl.Clear(ipEvent.ReadableHandle == 0 ? ipEvent.WritableHandle : ipEvent.ReadableHandle);
+ }
+ }
+}
diff --git a/Ryujinx.Horizon/Sdk/OsTypes/Impl/InterProcessEventImpl.cs b/Ryujinx.Horizon/Sdk/OsTypes/Impl/InterProcessEventImpl.cs
new file mode 100644
index 00000000..a8aeacc9
--- /dev/null
+++ b/Ryujinx.Horizon/Sdk/OsTypes/Impl/InterProcessEventImpl.cs
@@ -0,0 +1,136 @@
+using Ryujinx.Horizon.Common;
+using System;
+
+namespace Ryujinx.Horizon.Sdk.OsTypes.Impl
+{
+ static class InterProcessEventImpl
+ {
+ public static Result Create(out int writableHandle, out int readableHandle)
+ {
+ Result result = HorizonStatic.Syscall.CreateEvent(out writableHandle, out readableHandle);
+
+ if (result == KernelResult.OutOfResource)
+ {
+ return OsResult.OutOfResource;
+ }
+
+ result.AbortOnFailure();
+
+ return Result.Success;
+ }
+
+ public static void Close(int handle)
+ {
+ if (handle != 0)
+ {
+ HorizonStatic.Syscall.CloseHandle(handle).AbortOnFailure();
+ }
+ }
+
+ public static void Signal(int handle)
+ {
+ HorizonStatic.Syscall.SignalEvent(handle).AbortOnFailure();
+ }
+
+ public static void Clear(int handle)
+ {
+ HorizonStatic.Syscall.ClearEvent(handle).AbortOnFailure();
+ }
+
+ public static void Wait(int handle, bool autoClear)
+ {
+ Span<int> handles = stackalloc int[1];
+
+ handles[0] = handle;
+
+ while (true)
+ {
+ Result result = HorizonStatic.Syscall.WaitSynchronization(out _, handles, -1L);
+
+ if (result == Result.Success)
+ {
+ if (autoClear)
+ {
+ result = HorizonStatic.Syscall.ResetSignal(handle);
+
+ if (result == KernelResult.InvalidState)
+ {
+ continue;
+ }
+
+ result.AbortOnFailure();
+ }
+
+ return;
+ }
+
+ result.AbortUnless(KernelResult.Cancelled);
+ }
+ }
+
+ public static bool TryWait(int handle, bool autoClear)
+ {
+ if (autoClear)
+ {
+ return HorizonStatic.Syscall.ResetSignal(handle) == Result.Success;
+ }
+
+ Span<int> handles = stackalloc int[1];
+
+ handles[0] = handle;
+
+ while (true)
+ {
+ Result result = HorizonStatic.Syscall.WaitSynchronization(out _, handles, 0);
+
+ if (result == Result.Success)
+ {
+ return true;
+ }
+ else if (result == KernelResult.TimedOut)
+ {
+ return false;
+ }
+
+ result.AbortUnless(KernelResult.Cancelled);
+ }
+ }
+
+ public static bool TimedWait(int handle, bool autoClear, TimeSpan timeout)
+ {
+ Span<int> handles = stackalloc int[1];
+
+ handles[0] = handle;
+
+ long timeoutNs = timeout.Milliseconds * 1000000L;
+
+ while (true)
+ {
+ Result result = HorizonStatic.Syscall.WaitSynchronization(out _, handles, timeoutNs);
+
+ if (result == Result.Success)
+ {
+ if (autoClear)
+ {
+ result = HorizonStatic.Syscall.ResetSignal(handle);
+
+ if (result == KernelResult.InvalidState)
+ {
+ continue;
+ }
+
+ result.AbortOnFailure();
+ }
+
+ return true;
+ }
+ else if (result == KernelResult.TimedOut)
+ {
+ return false;
+ }
+
+ result.AbortUnless(KernelResult.Cancelled);
+ }
+ }
+ }
+}
diff --git a/Ryujinx.Horizon/Sdk/OsTypes/Impl/MultiWaitImpl.cs b/Ryujinx.Horizon/Sdk/OsTypes/Impl/MultiWaitImpl.cs
new file mode 100644
index 00000000..fd45792d
--- /dev/null
+++ b/Ryujinx.Horizon/Sdk/OsTypes/Impl/MultiWaitImpl.cs
@@ -0,0 +1,250 @@
+using Ryujinx.Common;
+using Ryujinx.Horizon.Common;
+using System.Collections.Generic;
+using System;
+
+namespace Ryujinx.Horizon.Sdk.OsTypes.Impl
+{
+ class MultiWaitImpl
+ {
+ private const int WaitTimedOut = -1;
+ private const int WaitCancelled = -2;
+ private const int WaitInvalid = -3;
+
+ private readonly List<MultiWaitHolderBase> _multiWaits;
+
+ private object _lock;
+
+ private int _waitingThreadHandle;
+
+ private MultiWaitHolderBase _signaledHolder;
+
+ public long CurrentTime { get; private set; }
+
+ public MultiWaitImpl()
+ {
+ _multiWaits = new List<MultiWaitHolderBase>();
+
+ _lock = new object();
+ }
+
+ public void LinkMultiWaitHolder(MultiWaitHolderBase multiWaitHolder)
+ {
+ _multiWaits.Add(multiWaitHolder);
+ }
+
+ public void UnlinkMultiWaitHolder(MultiWaitHolderBase multiWaitHolder)
+ {
+ _multiWaits.Remove(multiWaitHolder);
+ }
+
+ public void MoveAllFrom(MultiWaitImpl other)
+ {
+ foreach (MultiWaitHolderBase multiWait in other._multiWaits)
+ {
+ multiWait.SetMultiWait(this);
+ }
+
+ _multiWaits.AddRange(other._multiWaits);
+
+ other._multiWaits.Clear();
+ }
+
+ public MultiWaitHolderBase WaitAnyImpl(bool infinite, long timeout)
+ {
+ _signaledHolder = null;
+ _waitingThreadHandle = Os.GetCurrentThreadHandle();
+
+ MultiWaitHolderBase result = LinkHoldersToObjectList();
+
+ lock (_lock)
+ {
+ if (_signaledHolder != null)
+ {
+ result = _signaledHolder;
+ }
+ }
+
+ if (result == null)
+ {
+ result = WaitAnyHandleImpl(infinite, timeout);
+ }
+
+ UnlinkHoldersFromObjectsList();
+ _waitingThreadHandle = 0;
+
+ return result;
+ }
+
+ private MultiWaitHolderBase WaitAnyHandleImpl(bool infinite, long timeout)
+ {
+ Span<int> objectHandles = new int[64];
+
+ Span<MultiWaitHolderBase> objects = new MultiWaitHolderBase[64];
+
+ int count = FillObjectsArray(objectHandles, objects);
+
+ long endTime = infinite ? long.MaxValue : PerformanceCounter.ElapsedMilliseconds * 1000000;
+
+ while (true)
+ {
+ CurrentTime = PerformanceCounter.ElapsedMilliseconds * 1000000;
+
+ MultiWaitHolderBase minTimeoutObject = RecalcMultiWaitTimeout(endTime, out long minTimeout);
+
+ int index;
+
+ if (count == 0 && minTimeout == 0)
+ {
+ index = WaitTimedOut;
+ }
+ else
+ {
+ index = WaitSynchronization(objectHandles.Slice(0, count), minTimeout);
+
+ DebugUtil.Assert(index != WaitInvalid);
+ }
+
+ switch (index)
+ {
+ case WaitTimedOut:
+ if (minTimeoutObject != null)
+ {
+ CurrentTime = PerformanceCounter.ElapsedMilliseconds * 1000000;
+
+ if (minTimeoutObject.Signaled == TriBool.True)
+ {
+ lock (_lock)
+ {
+ _signaledHolder = minTimeoutObject;
+
+ return _signaledHolder;
+ }
+ }
+ }
+ else
+ {
+ return null;
+ }
+ break;
+ case WaitCancelled:
+ lock (_lock)
+ {
+ if (_signaledHolder != null)
+ {
+ return _signaledHolder;
+ }
+ }
+ break;
+ default:
+ lock (_lock)
+ {
+ _signaledHolder = objects[index];
+
+ return _signaledHolder;
+ }
+ }
+ }
+ }
+
+ private int FillObjectsArray(Span<int> handles, Span<MultiWaitHolderBase> objects)
+ {
+ int count = 0;
+
+ foreach (MultiWaitHolderBase holder in _multiWaits)
+ {
+ int handle = holder.Handle;
+
+ if (handle != 0)
+ {
+ handles[count] = handle;
+ objects[count] = holder;
+
+ count++;
+ }
+ }
+
+ return count;
+ }
+
+ private MultiWaitHolderBase RecalcMultiWaitTimeout(long endTime, out long minTimeout)
+ {
+ MultiWaitHolderBase minTimeHolder = null;
+
+ long minTime = endTime;
+
+ foreach (MultiWaitHolder holder in _multiWaits)
+ {
+ long currentTime = holder.GetAbsoluteTimeToWakeup();
+
+ if ((ulong)currentTime < (ulong)minTime)
+ {
+ minTimeHolder = holder;
+
+ minTime = currentTime;
+ }
+ }
+
+ minTimeout = (ulong)minTime < (ulong)CurrentTime ? 0 : minTime - CurrentTime;
+
+ return minTimeHolder;
+ }
+
+ private static int WaitSynchronization(ReadOnlySpan<int> handles, long timeout)
+ {
+ Result result = HorizonStatic.Syscall.WaitSynchronization(out int index, handles, timeout);
+
+ if (result == KernelResult.TimedOut)
+ {
+ return WaitTimedOut;
+ }
+ else if (result == KernelResult.Cancelled)
+ {
+ return WaitCancelled;
+ }
+ else
+ {
+ result.AbortOnFailure();
+ }
+
+ return index;
+ }
+
+ public void NotifyAndWakeUpThread(MultiWaitHolderBase holder)
+ {
+ lock (_lock)
+ {
+ if (_signaledHolder == null)
+ {
+ _signaledHolder = holder;
+ HorizonStatic.Syscall.CancelSynchronization(_waitingThreadHandle).AbortOnFailure();
+ }
+ }
+ }
+
+ private MultiWaitHolderBase LinkHoldersToObjectList()
+ {
+ MultiWaitHolderBase signaledHolder = null;
+
+ foreach (MultiWaitHolderBase holder in _multiWaits)
+ {
+ TriBool isSignaled = holder.LinkToObjectList();
+
+ if (signaledHolder == null && isSignaled == TriBool.True)
+ {
+ signaledHolder = holder;
+ }
+ }
+
+ return signaledHolder;
+ }
+
+ private void UnlinkHoldersFromObjectsList()
+ {
+ foreach (MultiWaitHolderBase holder in _multiWaits)
+ {
+ holder.UnlinkFromObjectList();
+ }
+ }
+ }
+}
diff --git a/Ryujinx.Horizon/Sdk/OsTypes/InitializationState.cs b/Ryujinx.Horizon/Sdk/OsTypes/InitializationState.cs
new file mode 100644
index 00000000..45ffd258
--- /dev/null
+++ b/Ryujinx.Horizon/Sdk/OsTypes/InitializationState.cs
@@ -0,0 +1,8 @@
+namespace Ryujinx.Horizon.Sdk.OsTypes
+{
+ enum InitializationState : byte
+ {
+ NotInitialized,
+ Initialized
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Horizon/Sdk/OsTypes/InterProcessEventType.cs b/Ryujinx.Horizon/Sdk/OsTypes/InterProcessEventType.cs
new file mode 100644
index 00000000..5f6824fe
--- /dev/null
+++ b/Ryujinx.Horizon/Sdk/OsTypes/InterProcessEventType.cs
@@ -0,0 +1,27 @@
+namespace Ryujinx.Horizon.Sdk.OsTypes
+{
+ struct InterProcessEventType
+ {
+ public readonly bool AutoClear;
+ public InitializationState State;
+ public bool ReadableHandleManaged;
+ public bool WritableHandleManaged;
+ public int ReadableHandle;
+ public int WritableHandle;
+
+ public InterProcessEventType(
+ bool autoClear,
+ bool readableHandleManaged,
+ bool writableHandleManaged,
+ int readableHandle,
+ int writableHandle)
+ {
+ AutoClear = autoClear;
+ State = InitializationState.Initialized;
+ ReadableHandleManaged = readableHandleManaged;
+ WritableHandleManaged = writableHandleManaged;
+ ReadableHandle = readableHandle;
+ WritableHandle = writableHandle;
+ }
+ }
+}
diff --git a/Ryujinx.Horizon/Sdk/OsTypes/MultiWait.cs b/Ryujinx.Horizon/Sdk/OsTypes/MultiWait.cs
new file mode 100644
index 00000000..5a91f6c3
--- /dev/null
+++ b/Ryujinx.Horizon/Sdk/OsTypes/MultiWait.cs
@@ -0,0 +1,43 @@
+using Ryujinx.Horizon.Sdk.OsTypes.Impl;
+
+namespace Ryujinx.Horizon.Sdk.OsTypes
+{
+ class MultiWait
+ {
+ private readonly MultiWaitImpl _impl;
+
+ public MultiWait()
+ {
+ _impl = new MultiWaitImpl();
+ }
+
+ public void LinkMultiWaitHolder(MultiWaitHolderBase multiWaitHolder)
+ {
+ DebugUtil.Assert(!multiWaitHolder.IsLinked);
+
+ _impl.LinkMultiWaitHolder(multiWaitHolder);
+
+ multiWaitHolder.SetMultiWait(_impl);
+ }
+
+ public void MoveAllFrom(MultiWait other)
+ {
+ _impl.MoveAllFrom(other._impl);
+ }
+
+ public MultiWaitHolder WaitAny()
+ {
+ return (MultiWaitHolder)_impl.WaitAnyImpl(true, -1L);
+ }
+
+ public MultiWaitHolder TryWaitAny()
+ {
+ return (MultiWaitHolder)_impl.WaitAnyImpl(false, 0);
+ }
+
+ public MultiWaitHolder TimedWaitAny(long timeout)
+ {
+ return (MultiWaitHolder)_impl.WaitAnyImpl(false, timeout);
+ }
+ }
+}
diff --git a/Ryujinx.Horizon/Sdk/OsTypes/MultiWaitHolder.cs b/Ryujinx.Horizon/Sdk/OsTypes/MultiWaitHolder.cs
new file mode 100644
index 00000000..a24b1906
--- /dev/null
+++ b/Ryujinx.Horizon/Sdk/OsTypes/MultiWaitHolder.cs
@@ -0,0 +1,16 @@
+namespace Ryujinx.Horizon.Sdk.OsTypes
+{
+ class MultiWaitHolder : MultiWaitHolderBase
+ {
+ public object UserData { get; set; }
+
+ public void UnlinkFromMultiWaitHolder()
+ {
+ DebugUtil.Assert(IsLinked);
+
+ MultiWait.UnlinkMultiWaitHolder(this);
+
+ SetMultiWait(null);
+ }
+ }
+}
diff --git a/Ryujinx.Horizon/Sdk/OsTypes/MultiWaitHolderBase.cs b/Ryujinx.Horizon/Sdk/OsTypes/MultiWaitHolderBase.cs
new file mode 100644
index 00000000..018305ba
--- /dev/null
+++ b/Ryujinx.Horizon/Sdk/OsTypes/MultiWaitHolderBase.cs
@@ -0,0 +1,39 @@
+using Ryujinx.Horizon.Sdk.OsTypes.Impl;
+
+namespace Ryujinx.Horizon.Sdk.OsTypes
+{
+ class MultiWaitHolderBase
+ {
+ protected MultiWaitImpl MultiWait;
+
+ public bool IsLinked => MultiWait != null;
+
+ public virtual TriBool Signaled => TriBool.False;
+
+ public virtual int Handle => 0;
+
+ public void SetMultiWait(MultiWaitImpl multiWait)
+ {
+ MultiWait = multiWait;
+ }
+
+ public MultiWaitImpl GetMultiWait()
+ {
+ return MultiWait;
+ }
+
+ public virtual TriBool LinkToObjectList()
+ {
+ return TriBool.Undefined;
+ }
+
+ public virtual void UnlinkFromObjectList()
+ {
+ }
+
+ public virtual long GetAbsoluteTimeToWakeup()
+ {
+ return long.MaxValue;
+ }
+ }
+}
diff --git a/Ryujinx.Horizon/Sdk/OsTypes/MultiWaitHolderOfEvent.cs b/Ryujinx.Horizon/Sdk/OsTypes/MultiWaitHolderOfEvent.cs
new file mode 100644
index 00000000..37ac22f0
--- /dev/null
+++ b/Ryujinx.Horizon/Sdk/OsTypes/MultiWaitHolderOfEvent.cs
@@ -0,0 +1,45 @@
+using System.Collections.Generic;
+
+namespace Ryujinx.Horizon.Sdk.OsTypes
+{
+ class MultiWaitHolderOfEvent : MultiWaitHolder
+ {
+ private Event _event;
+ private LinkedListNode<MultiWaitHolderBase> _node;
+
+ public override TriBool Signaled
+ {
+ get
+ {
+ lock (_event.EventLock)
+ {
+ return _event.IsSignaledThreadUnsafe();
+ }
+ }
+ }
+
+ public MultiWaitHolderOfEvent(Event evnt)
+ {
+ _event = evnt;
+ }
+
+ public override TriBool LinkToObjectList()
+ {
+ lock (_event.EventLock)
+ {
+ _node = _event.MultiWaitHolders.AddLast(this);
+
+ return _event.IsSignaledThreadUnsafe();
+ }
+ }
+
+ public override void UnlinkFromObjectList()
+ {
+ lock (_event.EventLock)
+ {
+ _event.MultiWaitHolders.Remove(_node);
+ _node = null;
+ }
+ }
+ }
+}
diff --git a/Ryujinx.Horizon/Sdk/OsTypes/MultiWaitHolderOfHandle.cs b/Ryujinx.Horizon/Sdk/OsTypes/MultiWaitHolderOfHandle.cs
new file mode 100644
index 00000000..6fc5c75b
--- /dev/null
+++ b/Ryujinx.Horizon/Sdk/OsTypes/MultiWaitHolderOfHandle.cs
@@ -0,0 +1,14 @@
+namespace Ryujinx.Horizon.Sdk.OsTypes
+{
+ class MultiWaitHolderOfHandle : MultiWaitHolder
+ {
+ private int _handle;
+
+ public override int Handle => _handle;
+
+ public MultiWaitHolderOfHandle(int handle)
+ {
+ _handle = handle;
+ }
+ }
+}
diff --git a/Ryujinx.Horizon/Sdk/OsTypes/OsEvent.cs b/Ryujinx.Horizon/Sdk/OsTypes/OsEvent.cs
new file mode 100644
index 00000000..cc7e8483
--- /dev/null
+++ b/Ryujinx.Horizon/Sdk/OsTypes/OsEvent.cs
@@ -0,0 +1,130 @@
+using System;
+using System.Collections.Generic;
+using System.Threading;
+
+namespace Ryujinx.Horizon.Sdk.OsTypes
+{
+ static partial class Os
+ {
+ public static void InitializeEvent(out EventType evnt, bool signaled, EventClearMode clearMode)
+ {
+ evnt = new EventType
+ {
+ MultiWaitHolders = new LinkedList<MultiWaitHolderBase>(),
+ Signaled = signaled,
+ InitiallySignaled = signaled,
+ ClearMode = clearMode,
+ State = InitializationState.Initialized,
+ Lock = new object()
+ };
+ }
+
+ public static void FinalizeEvent(ref EventType evnt)
+ {
+ evnt.State = InitializationState.NotInitialized;
+ }
+
+ public static void WaitEvent(ref EventType evnt)
+ {
+ lock (evnt.Lock)
+ {
+ ulong currentCounter = evnt.BroadcastCounter;
+
+ while (!evnt.Signaled)
+ {
+ if (currentCounter != evnt.BroadcastCounter)
+ {
+ break;
+ }
+
+ Monitor.Wait(evnt.Lock);
+ }
+
+ if (evnt.ClearMode == EventClearMode.AutoClear)
+ {
+ evnt.Signaled = false;
+ }
+ }
+ }
+
+ public static bool TryWaitEvent(ref EventType evnt)
+ {
+ lock (evnt.Lock)
+ {
+ bool signaled = evnt.Signaled;
+
+ if (evnt.ClearMode == EventClearMode.AutoClear)
+ {
+ evnt.Signaled = false;
+ }
+
+ return signaled;
+ }
+ }
+
+ public static bool TimedWaitEvent(ref EventType evnt, TimeSpan timeout)
+ {
+ lock (evnt.Lock)
+ {
+ ulong currentCounter = evnt.BroadcastCounter;
+
+ while (!evnt.Signaled)
+ {
+ if (currentCounter != evnt.BroadcastCounter)
+ {
+ break;
+ }
+
+ bool wasSignaledInTime = Monitor.Wait(evnt.Lock, timeout);
+ if (!wasSignaledInTime)
+ {
+ return false;
+ }
+ }
+
+ if (evnt.ClearMode == EventClearMode.AutoClear)
+ {
+ evnt.Signaled = false;
+ }
+ }
+
+ return true;
+ }
+
+ public static void SignalEvent(ref EventType evnt)
+ {
+ lock (evnt.Lock)
+ {
+ if (evnt.Signaled)
+ {
+ return;
+ }
+
+ evnt.Signaled = true;
+
+ if (evnt.ClearMode == EventClearMode.ManualClear)
+ {
+ evnt.BroadcastCounter++;
+ Monitor.PulseAll(evnt.Lock);
+ }
+ else
+ {
+ Monitor.Pulse(evnt.Lock);
+ }
+
+ foreach (MultiWaitHolderBase holder in evnt.MultiWaitHolders)
+ {
+ holder.GetMultiWait().NotifyAndWakeUpThread(holder);
+ }
+ }
+ }
+
+ public static void ClearEvent(ref EventType evnt)
+ {
+ lock (evnt.Lock)
+ {
+ evnt.Signaled = false;
+ }
+ }
+ }
+}
diff --git a/Ryujinx.Horizon/Sdk/OsTypes/OsMultiWait.cs b/Ryujinx.Horizon/Sdk/OsTypes/OsMultiWait.cs
new file mode 100644
index 00000000..827de231
--- /dev/null
+++ b/Ryujinx.Horizon/Sdk/OsTypes/OsMultiWait.cs
@@ -0,0 +1,10 @@
+namespace Ryujinx.Horizon.Sdk.OsTypes
+{
+ static partial class Os
+ {
+ public static void FinalizeMultiWaitHolder(MultiWaitHolderBase holder)
+ {
+ DebugUtil.Assert(!holder.IsLinked);
+ }
+ }
+}
diff --git a/Ryujinx.Horizon/Sdk/OsTypes/OsProcessHandle.cs b/Ryujinx.Horizon/Sdk/OsTypes/OsProcessHandle.cs
new file mode 100644
index 00000000..6a6d9bf2
--- /dev/null
+++ b/Ryujinx.Horizon/Sdk/OsTypes/OsProcessHandle.cs
@@ -0,0 +1,33 @@
+using Ryujinx.Horizon.Common;
+
+namespace Ryujinx.Horizon.Sdk.OsTypes
+{
+ static partial class Os
+ {
+ private const int SelfProcessHandle = (0x1ffff << 15) | 1;
+
+ public static int GetCurrentProcessHandle()
+ {
+ return SelfProcessHandle;
+ }
+
+ public static ulong GetCurrentProcessId()
+ {
+ return GetProcessId(GetCurrentProcessHandle());
+ }
+
+ private static ulong GetProcessId(int handle)
+ {
+ Result result = TryGetProcessId(handle, out ulong pid);
+
+ result.AbortOnFailure();
+
+ return pid;
+ }
+
+ private static Result TryGetProcessId(int handle, out ulong pid)
+ {
+ return HorizonStatic.Syscall.GetProcessId(out pid, handle);
+ }
+ }
+}
diff --git a/Ryujinx.Horizon/Sdk/OsTypes/OsResult.cs b/Ryujinx.Horizon/Sdk/OsTypes/OsResult.cs
new file mode 100644
index 00000000..86dcd1fa
--- /dev/null
+++ b/Ryujinx.Horizon/Sdk/OsTypes/OsResult.cs
@@ -0,0 +1,11 @@
+using Ryujinx.Horizon.Common;
+
+namespace Ryujinx.Horizon.Sdk.OsTypes
+{
+ static class OsResult
+ {
+ private const int ModuleId = 3;
+
+ public static Result OutOfResource => new Result(ModuleId, 9);
+ }
+}
diff --git a/Ryujinx.Horizon/Sdk/OsTypes/OsSystemEvent.cs b/Ryujinx.Horizon/Sdk/OsTypes/OsSystemEvent.cs
new file mode 100644
index 00000000..061d7a3c
--- /dev/null
+++ b/Ryujinx.Horizon/Sdk/OsTypes/OsSystemEvent.cs
@@ -0,0 +1,85 @@
+using Ryujinx.Horizon.Sdk.OsTypes.Impl;
+using Ryujinx.Horizon.Common;
+using System;
+
+namespace Ryujinx.Horizon.Sdk.OsTypes
+{
+ static partial class Os
+ {
+ public static Result CreateSystemEvent(out SystemEventType sysEvent, EventClearMode clearMode, bool interProcess)
+ {
+ sysEvent = new SystemEventType();
+
+ if (interProcess)
+ {
+ Result result = InterProcessEvent.Create(ref sysEvent.InterProcessEvent, clearMode);
+
+ if (result != Result.Success)
+ {
+ return result;
+ }
+
+ sysEvent.State = SystemEventType.InitializationState.InitializedAsInterProcess;
+ }
+ else
+ {
+ throw new NotImplementedException();
+ }
+
+ return Result.Success;
+ }
+
+ public static void DestroySystemEvent(ref SystemEventType sysEvent)
+ {
+ var oldState = sysEvent.State;
+ sysEvent.State = SystemEventType.InitializationState.NotInitialized;
+
+ switch (oldState)
+ {
+ case SystemEventType.InitializationState.InitializedAsInterProcess:
+ InterProcessEvent.Destroy(ref sysEvent.InterProcessEvent);
+ break;
+ }
+ }
+
+ public static int DetachReadableHandleOfSystemEvent(ref SystemEventType sysEvent)
+ {
+ return InterProcessEvent.DetachReadableHandle(ref sysEvent.InterProcessEvent);
+ }
+
+ public static int DetachWritableHandleOfSystemEvent(ref SystemEventType sysEvent)
+ {
+ return InterProcessEvent.DetachWritableHandle(ref sysEvent.InterProcessEvent);
+ }
+
+ public static int GetReadableHandleOfSystemEvent(ref SystemEventType sysEvent)
+ {
+ return InterProcessEvent.GetReadableHandle(ref sysEvent.InterProcessEvent);
+ }
+
+ public static int GetWritableHandleOfSystemEvent(ref SystemEventType sysEvent)
+ {
+ return InterProcessEvent.GetWritableHandle(ref sysEvent.InterProcessEvent);
+ }
+
+ public static void SignalSystemEvent(ref SystemEventType sysEvent)
+ {
+ switch (sysEvent.State)
+ {
+ case SystemEventType.InitializationState.InitializedAsInterProcess:
+ InterProcessEvent.Signal(ref sysEvent.InterProcessEvent);
+ break;
+ }
+ }
+
+ public static void ClearSystemEvent(ref SystemEventType sysEvent)
+ {
+ switch (sysEvent.State)
+ {
+ case SystemEventType.InitializationState.InitializedAsInterProcess:
+ InterProcessEvent.Clear(ref sysEvent.InterProcessEvent);
+ break;
+ }
+ }
+ }
+}
diff --git a/Ryujinx.Horizon/Sdk/OsTypes/OsThreadManager.cs b/Ryujinx.Horizon/Sdk/OsTypes/OsThreadManager.cs
new file mode 100644
index 00000000..2037cd7f
--- /dev/null
+++ b/Ryujinx.Horizon/Sdk/OsTypes/OsThreadManager.cs
@@ -0,0 +1,10 @@
+namespace Ryujinx.Horizon.Sdk.OsTypes
+{
+ static partial class Os
+ {
+ public static int GetCurrentThreadHandle()
+ {
+ return HorizonStatic.CurrentThreadHandle;
+ }
+ }
+}
diff --git a/Ryujinx.Horizon/Sdk/OsTypes/SystemEventType.cs b/Ryujinx.Horizon/Sdk/OsTypes/SystemEventType.cs
new file mode 100644
index 00000000..338493d2
--- /dev/null
+++ b/Ryujinx.Horizon/Sdk/OsTypes/SystemEventType.cs
@@ -0,0 +1,17 @@
+namespace Ryujinx.Horizon.Sdk.OsTypes
+{
+ struct SystemEventType
+ {
+ public enum InitializationState : byte
+ {
+ NotInitialized,
+ InitializedAsEvent,
+ InitializedAsInterProcess
+ }
+
+ public InterProcessEventType InterProcessEvent;
+ public InitializationState State;
+
+ public bool NotInitialized => State == InitializationState.NotInitialized;
+ }
+}
diff --git a/Ryujinx.Horizon/Sdk/OsTypes/TriBool.cs b/Ryujinx.Horizon/Sdk/OsTypes/TriBool.cs
new file mode 100644
index 00000000..7debd9e2
--- /dev/null
+++ b/Ryujinx.Horizon/Sdk/OsTypes/TriBool.cs
@@ -0,0 +1,9 @@
+namespace Ryujinx.Horizon.Sdk.OsTypes
+{
+ enum TriBool
+ {
+ False,
+ True,
+ Undefined
+ }
+}
diff --git a/Ryujinx.Horizon/Sdk/ServiceUtil.cs b/Ryujinx.Horizon/Sdk/ServiceUtil.cs
new file mode 100644
index 00000000..413ac1f6
--- /dev/null
+++ b/Ryujinx.Horizon/Sdk/ServiceUtil.cs
@@ -0,0 +1,38 @@
+using Ryujinx.Horizon.Common;
+using Ryujinx.Horizon.Sdk.Sf.Cmif;
+using Ryujinx.Horizon.Sdk.Sf.Hipc;
+using System;
+
+namespace Ryujinx.Horizon.Sdk
+{
+ static class ServiceUtil
+ {
+ public static Result SendRequest(out CmifResponse response, int sessionHandle, uint requestId, bool sendPid, scoped ReadOnlySpan<byte> data)
+ {
+ ulong tlsAddress = HorizonStatic.ThreadContext.TlsAddress;
+ int tlsSize = Api.TlsMessageBufferSize;
+
+ using (var tlsRegion = HorizonStatic.AddressSpace.GetWritableRegion(tlsAddress, tlsSize))
+ {
+ CmifRequest request = CmifMessage.CreateRequest(tlsRegion.Memory.Span, new CmifRequestFormat()
+ {
+ DataSize = data.Length,
+ RequestId = requestId,
+ SendPid = sendPid
+ });
+
+ data.CopyTo(request.Data);
+ }
+
+ Result result = HorizonStatic.Syscall.SendSyncRequest(sessionHandle);
+
+ if (result.IsFailure)
+ {
+ response = default;
+ return result;
+ }
+
+ return CmifMessage.ParseResponse(out response, HorizonStatic.AddressSpace.GetWritableRegion(tlsAddress, tlsSize).Memory.Span, false, 0);
+ }
+ }
+}
diff --git a/Ryujinx.Horizon/Sdk/Sf/Cmif/CmifDomainInHeader.cs b/Ryujinx.Horizon/Sdk/Sf/Cmif/CmifDomainInHeader.cs
new file mode 100644
index 00000000..88211501
--- /dev/null
+++ b/Ryujinx.Horizon/Sdk/Sf/Cmif/CmifDomainInHeader.cs
@@ -0,0 +1,12 @@
+namespace Ryujinx.Horizon.Sdk.Sf.Cmif
+{
+ struct CmifDomainInHeader
+ {
+ public CmifDomainRequestType Type;
+ public byte ObjectsCount;
+ public ushort DataSize;
+ public int ObjectId;
+ public uint Padding;
+ public uint Token;
+ }
+}
diff --git a/Ryujinx.Horizon/Sdk/Sf/Cmif/CmifDomainOutHeader.cs b/Ryujinx.Horizon/Sdk/Sf/Cmif/CmifDomainOutHeader.cs
new file mode 100644
index 00000000..2086d24c
--- /dev/null
+++ b/Ryujinx.Horizon/Sdk/Sf/Cmif/CmifDomainOutHeader.cs
@@ -0,0 +1,12 @@
+namespace Ryujinx.Horizon.Sdk.Sf.Cmif
+{
+ struct CmifDomainOutHeader
+ {
+#pragma warning disable CS0649
+ public uint ObjectsCount;
+ public uint Padding;
+ public uint Padding2;
+ public uint Padding3;
+#pragma warning restore CS0649
+ }
+}
diff --git a/Ryujinx.Horizon/Sdk/Sf/Cmif/CmifDomainRequestType.cs b/Ryujinx.Horizon/Sdk/Sf/Cmif/CmifDomainRequestType.cs
new file mode 100644
index 00000000..b913db94
--- /dev/null
+++ b/Ryujinx.Horizon/Sdk/Sf/Cmif/CmifDomainRequestType.cs
@@ -0,0 +1,9 @@
+namespace Ryujinx.Horizon.Sdk.Sf.Cmif
+{
+ enum CmifDomainRequestType : byte
+ {
+ Invalid = 0,
+ SendMessage = 1,
+ Close = 2
+ }
+}
diff --git a/Ryujinx.Horizon/Sdk/Sf/Cmif/CmifInHeader.cs b/Ryujinx.Horizon/Sdk/Sf/Cmif/CmifInHeader.cs
new file mode 100644
index 00000000..55b859fc
--- /dev/null
+++ b/Ryujinx.Horizon/Sdk/Sf/Cmif/CmifInHeader.cs
@@ -0,0 +1,10 @@
+namespace Ryujinx.Horizon.Sdk.Sf.Cmif
+{
+ struct CmifInHeader
+ {
+ public uint Magic;
+ public uint Version;
+ public uint CommandId;
+ public uint Token;
+ }
+}
diff --git a/Ryujinx.Horizon/Sdk/Sf/Cmif/CmifMessage.cs b/Ryujinx.Horizon/Sdk/Sf/Cmif/CmifMessage.cs
new file mode 100644
index 00000000..781452e3
--- /dev/null
+++ b/Ryujinx.Horizon/Sdk/Sf/Cmif/CmifMessage.cs
@@ -0,0 +1,128 @@
+using Ryujinx.Horizon.Common;
+using Ryujinx.Horizon.Sdk.Sf.Hipc;
+using System;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.Horizon.Sdk.Sf.Cmif
+{
+ static class CmifMessage
+ {
+ public const uint CmifInHeaderMagic = 0x49434653; // SFCI
+ public const uint CmifOutHeaderMagic = 0x4f434653; // SFCO
+
+ public static CmifRequest CreateRequest(Span<byte> output, CmifRequestFormat format)
+ {
+ int totalSize = 16;
+
+ if (format.ObjectId != 0)
+ {
+ totalSize += Unsafe.SizeOf<CmifDomainInHeader>() + format.ObjectsCount * sizeof(int);
+ }
+
+ totalSize += Unsafe.SizeOf<CmifInHeader>() + format.DataSize;
+ totalSize = (totalSize + 1) & ~1;
+ int outPointerSizeTableOffset = totalSize;
+ int outPointerSizeTableSize = format.OutAutoBuffersCount + format.OutPointersCount;
+ totalSize += sizeof(ushort) * outPointerSizeTableSize;
+ int rawDataSizeInWords = (totalSize + sizeof(uint) - 1) / sizeof(uint);
+
+ CmifRequest request = new CmifRequest();
+
+ request.Hipc = HipcMessage.WriteMessage(output, new HipcMetadata()
+ {
+ Type = format.Context != 0 ? (int)CommandType.RequestWithContext : (int)CommandType.Request,
+ SendStaticsCount = format.InAutoBuffersCount + format.InPointersCount,
+ SendBuffersCount = format.InAutoBuffersCount + format.InBuffersCount,
+ ReceiveBuffersCount = format.OutAutoBuffersCount + format.OutBuffersCount,
+ ExchangeBuffersCount = format.InOutBuffersCount,
+ DataWordsCount = rawDataSizeInWords,
+ ReceiveStaticsCount = outPointerSizeTableSize + format.OutFixedPointersCount,
+ SendPid = format.SendPid,
+ CopyHandlesCount = format.HandlesCount,
+ MoveHandlesCount = 0
+ });
+
+ Span<uint> data = request.Hipc.DataWords;
+
+ if (format.ObjectId != 0)
+ {
+ ref CmifDomainInHeader domainHeader = ref MemoryMarshal.Cast<uint, CmifDomainInHeader>(data)[0];
+
+ int payloadSize = Unsafe.SizeOf<CmifInHeader>() + format.DataSize;
+
+ domainHeader = new CmifDomainInHeader()
+ {
+ Type = CmifDomainRequestType.SendMessage,
+ ObjectsCount = (byte)format.ObjectsCount,
+ DataSize = (ushort)payloadSize,
+ ObjectId = format.ObjectId,
+ Padding = 0,
+ Token = format.Context
+ };
+
+ data = data.Slice(Unsafe.SizeOf<CmifDomainInHeader>() / sizeof(uint));
+
+ request.Objects = data.Slice((payloadSize + sizeof(uint) - 1) / sizeof(uint));
+ }
+
+ ref CmifInHeader header = ref MemoryMarshal.Cast<uint, CmifInHeader>(data)[0];
+
+ header = new CmifInHeader()
+ {
+ Magic = CmifInHeaderMagic,
+ Version = format.Context != 0 ? 1u : 0u,
+ CommandId = format.RequestId,
+ Token = format.ObjectId != 0 ? 0u : format.Context
+ };
+
+ request.Data = MemoryMarshal.Cast<uint, byte>(data).Slice(Unsafe.SizeOf<CmifInHeader>());
+
+ int paddingSizeBefore = (rawDataSizeInWords - request.Hipc.DataWords.Length) * sizeof(uint);
+
+ Span<byte> outPointerTable = MemoryMarshal.Cast<uint, byte>(request.Hipc.DataWords).Slice(outPointerSizeTableOffset - paddingSizeBefore);
+ request.OutPointerSizes = MemoryMarshal.Cast<byte, ushort>(outPointerTable);
+ request.ServerPointerSize = format.ServerPointerSize;
+
+ return request;
+ }
+
+ public static Result ParseResponse(out CmifResponse response, Span<byte> input, bool isDomain, int size)
+ {
+ HipcMessage responseMessage = new HipcMessage(input);
+
+ Span<byte> data = MemoryMarshal.Cast<uint, byte>(responseMessage.Data.DataWords);
+ Span<uint> objects = Span<uint>.Empty;
+
+ if (isDomain)
+ {
+ data = data.Slice(Unsafe.SizeOf<CmifDomainOutHeader>());
+ objects = MemoryMarshal.Cast<byte, uint>(data.Slice(Unsafe.SizeOf<CmifOutHeader>() + size));
+ }
+
+ CmifOutHeader header = MemoryMarshal.Cast<byte, CmifOutHeader>(data)[0];
+
+ if (header.Magic != CmifOutHeaderMagic)
+ {
+ response = default;
+ return SfResult.InvalidOutHeader;
+ }
+
+ if (header.Result.IsFailure)
+ {
+ response = default;
+ return header.Result;
+ }
+
+ response = new CmifResponse()
+ {
+ Data = data.Slice(Unsafe.SizeOf<CmifOutHeader>()),
+ Objects = objects,
+ CopyHandles = responseMessage.Data.CopyHandles,
+ MoveHandles = responseMessage.Data.MoveHandles
+ };
+
+ return Result.Success;
+ }
+ }
+}
diff --git a/Ryujinx.Horizon/Sdk/Sf/Cmif/CmifOutHeader.cs b/Ryujinx.Horizon/Sdk/Sf/Cmif/CmifOutHeader.cs
new file mode 100644
index 00000000..2828cde5
--- /dev/null
+++ b/Ryujinx.Horizon/Sdk/Sf/Cmif/CmifOutHeader.cs
@@ -0,0 +1,14 @@
+using Ryujinx.Horizon.Common;
+
+namespace Ryujinx.Horizon.Sdk.Sf.Cmif
+{
+ struct CmifOutHeader
+ {
+#pragma warning disable CS0649
+ public uint Magic;
+ public uint Version;
+ public Result Result;
+ public uint Token;
+#pragma warning restore CS0649
+ }
+}
diff --git a/Ryujinx.Horizon/Sdk/Sf/Cmif/CmifRequest.cs b/Ryujinx.Horizon/Sdk/Sf/Cmif/CmifRequest.cs
new file mode 100644
index 00000000..80772ad3
--- /dev/null
+++ b/Ryujinx.Horizon/Sdk/Sf/Cmif/CmifRequest.cs
@@ -0,0 +1,14 @@
+using Ryujinx.Horizon.Sdk.Sf.Hipc;
+using System;
+
+namespace Ryujinx.Horizon.Sdk.Sf.Cmif
+{
+ ref struct CmifRequest
+ {
+ public HipcMessageData Hipc;
+ public Span<byte> Data;
+ public Span<ushort> OutPointerSizes;
+ public Span<uint> Objects;
+ public int ServerPointerSize;
+ }
+}
diff --git a/Ryujinx.Horizon/Sdk/Sf/Cmif/CmifRequestFormat.cs b/Ryujinx.Horizon/Sdk/Sf/Cmif/CmifRequestFormat.cs
new file mode 100644
index 00000000..d1154578
--- /dev/null
+++ b/Ryujinx.Horizon/Sdk/Sf/Cmif/CmifRequestFormat.cs
@@ -0,0 +1,24 @@
+namespace Ryujinx.Horizon.Sdk.Sf.Cmif
+{
+ struct CmifRequestFormat
+ {
+#pragma warning disable CS0649
+ public int ObjectId;
+ public uint RequestId;
+ public uint Context;
+ public int DataSize;
+ public int ServerPointerSize;
+ public int InAutoBuffersCount;
+ public int OutAutoBuffersCount;
+ public int InBuffersCount;
+ public int OutBuffersCount;
+ public int InOutBuffersCount;
+ public int InPointersCount;
+ public int OutPointersCount;
+ public int OutFixedPointersCount;
+ public int ObjectsCount;
+ public int HandlesCount;
+ public bool SendPid;
+#pragma warning restore CS0649
+ }
+}
diff --git a/Ryujinx.Horizon/Sdk/Sf/Cmif/CmifResponse.cs b/Ryujinx.Horizon/Sdk/Sf/Cmif/CmifResponse.cs
new file mode 100644
index 00000000..d1d8dc9c
--- /dev/null
+++ b/Ryujinx.Horizon/Sdk/Sf/Cmif/CmifResponse.cs
@@ -0,0 +1,12 @@
+using System;
+
+namespace Ryujinx.Horizon.Sdk.Sf.Cmif
+{
+ ref struct CmifResponse
+ {
+ public ReadOnlySpan<byte> Data;
+ public ReadOnlySpan<uint> Objects;
+ public ReadOnlySpan<int> CopyHandles;
+ public ReadOnlySpan<int> MoveHandles;
+ }
+}
diff --git a/Ryujinx.Horizon/Sdk/Sf/Cmif/CommandType.cs b/Ryujinx.Horizon/Sdk/Sf/Cmif/CommandType.cs
new file mode 100644
index 00000000..b3b05864
--- /dev/null
+++ b/Ryujinx.Horizon/Sdk/Sf/Cmif/CommandType.cs
@@ -0,0 +1,14 @@
+namespace Ryujinx.Horizon.Sdk.Sf.Cmif
+{
+ enum CommandType
+ {
+ Invalid = 0,
+ LegacyRequest = 1,
+ Close = 2,
+ LegacyControl = 3,
+ Request = 4,
+ Control = 5,
+ RequestWithContext = 6,
+ ControlWithContext = 7
+ }
+}
diff --git a/Ryujinx.Horizon/Sdk/Sf/Cmif/DomainServiceObject.cs b/Ryujinx.Horizon/Sdk/Sf/Cmif/DomainServiceObject.cs
new file mode 100644
index 00000000..14839687
--- /dev/null
+++ b/Ryujinx.Horizon/Sdk/Sf/Cmif/DomainServiceObject.cs
@@ -0,0 +1,7 @@
+namespace Ryujinx.Horizon.Sdk.Sf.Cmif
+{
+ abstract partial class DomainServiceObject : ServerDomainBase, IServiceObject
+ {
+ public abstract ServerDomainBase GetServerDomain();
+ }
+}
diff --git a/Ryujinx.Horizon/Sdk/Sf/Cmif/DomainServiceObjectDispatchTable.cs b/Ryujinx.Horizon/Sdk/Sf/Cmif/DomainServiceObjectDispatchTable.cs
new file mode 100644
index 00000000..bcf311b2
--- /dev/null
+++ b/Ryujinx.Horizon/Sdk/Sf/Cmif/DomainServiceObjectDispatchTable.cs
@@ -0,0 +1,75 @@
+using Ryujinx.Horizon.Common;
+using System;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.Horizon.Sdk.Sf.Cmif
+{
+ class DomainServiceObjectDispatchTable : ServiceDispatchTableBase
+ {
+ public override Result ProcessMessage(ref ServiceDispatchContext context, ReadOnlySpan<byte> inRawData)
+ {
+ return ProcessMessageImpl(ref context, ((DomainServiceObject)context.ServiceObject).GetServerDomain(), inRawData);
+ }
+
+ private Result ProcessMessageImpl(ref ServiceDispatchContext context, ServerDomainBase domain, ReadOnlySpan<byte> inRawData)
+ {
+ if (inRawData.Length < Unsafe.SizeOf<CmifDomainInHeader>())
+ {
+ return SfResult.InvalidHeaderSize;
+ }
+
+ var inHeader = MemoryMarshal.Cast<byte, CmifDomainInHeader>(inRawData)[0];
+
+ ReadOnlySpan<byte> inDomainRawData = inRawData.Slice(Unsafe.SizeOf<CmifDomainInHeader>());
+
+ int targetObjectId = inHeader.ObjectId;
+
+ switch (inHeader.Type)
+ {
+ case CmifDomainRequestType.SendMessage:
+ var targetObject = domain.GetObject(targetObjectId);
+ if (targetObject == null)
+ {
+ return SfResult.TargetNotFound;
+ }
+
+ if (inHeader.DataSize + inHeader.ObjectsCount * sizeof(int) > inDomainRawData.Length)
+ {
+ return SfResult.InvalidHeaderSize;
+ }
+
+ ReadOnlySpan<byte> inMessageRawData = inDomainRawData.Slice(0, inHeader.DataSize);
+
+ if (inHeader.ObjectsCount > DomainServiceObjectProcessor.MaximumObjects)
+ {
+ return SfResult.InvalidInObjectsCount;
+ }
+
+ int[] inObjectIds = new int[inHeader.ObjectsCount];
+
+ var domainProcessor = new DomainServiceObjectProcessor(domain, inObjectIds);
+
+ if (context.Processor == null)
+ {
+ context.Processor = domainProcessor;
+ }
+ else
+ {
+ context.Processor.SetImplementationProcessor(domainProcessor);
+ }
+
+ context.ServiceObject = targetObject.ServiceObject;
+
+ return targetObject.ProcessMessage(ref context, inMessageRawData);
+
+ case CmifDomainRequestType.Close:
+ domain.UnregisterObject(targetObjectId);
+ return Result.Success;
+
+ default:
+ return SfResult.InvalidInHeader;
+ }
+ }
+ }
+}
diff --git a/Ryujinx.Horizon/Sdk/Sf/Cmif/DomainServiceObjectProcessor.cs b/Ryujinx.Horizon/Sdk/Sf/Cmif/DomainServiceObjectProcessor.cs
new file mode 100644
index 00000000..92d86196
--- /dev/null
+++ b/Ryujinx.Horizon/Sdk/Sf/Cmif/DomainServiceObjectProcessor.cs
@@ -0,0 +1,140 @@
+using Ryujinx.Horizon.Common;
+using Ryujinx.Horizon.Sdk.Sf.Hipc;
+using System;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.Horizon.Sdk.Sf.Cmif
+{
+ class DomainServiceObjectProcessor : ServerMessageProcessor
+ {
+ public const int MaximumObjects = 8;
+
+ private ServerMessageProcessor _implProcessor;
+ private readonly ServerDomainBase _domain;
+ private int _outObjectIdsOffset;
+ private readonly int[] _inObjectIds;
+ private readonly int[] _reservedObjectIds;
+ private ServerMessageRuntimeMetadata _implMetadata;
+
+ private int InObjectsCount => _inObjectIds.Length;
+ private int OutObjectsCount => _implMetadata.OutObjectsCount;
+ private int ImplOutHeadersSize => _implMetadata.OutHeadersSize;
+ private int ImplOutDataTotalSize => _implMetadata.OutDataSize + _implMetadata.OutHeadersSize;
+
+ public DomainServiceObjectProcessor(ServerDomainBase domain, int[] inObjectIds)
+ {
+ _domain = domain;
+ _inObjectIds = inObjectIds;
+ _reservedObjectIds = new int[MaximumObjects];
+ }
+
+ public override void SetImplementationProcessor(ServerMessageProcessor impl)
+ {
+ if (_implProcessor == null)
+ {
+ _implProcessor = impl;
+ }
+ else
+ {
+ _implProcessor.SetImplementationProcessor(impl);
+ }
+
+ _implMetadata = _implProcessor.GetRuntimeMetadata();
+ }
+
+ public override ServerMessageRuntimeMetadata GetRuntimeMetadata()
+ {
+ var runtimeMetadata = _implProcessor.GetRuntimeMetadata();
+
+ return new ServerMessageRuntimeMetadata(
+ (ushort)(runtimeMetadata.InDataSize + runtimeMetadata.InObjectsCount * sizeof(int)),
+ (ushort)(runtimeMetadata.OutDataSize + runtimeMetadata.OutObjectsCount * sizeof(int)),
+ (byte)(runtimeMetadata.InHeadersSize + Unsafe.SizeOf<CmifDomainInHeader>()),
+ (byte)(runtimeMetadata.OutHeadersSize + Unsafe.SizeOf<CmifDomainOutHeader>()),
+ 0,
+ 0);
+ }
+
+ public override Result PrepareForProcess(ref ServiceDispatchContext context, ServerMessageRuntimeMetadata runtimeMetadata)
+ {
+ if (_implMetadata.InObjectsCount != InObjectsCount)
+ {
+ return SfResult.InvalidInObjectsCount;
+ }
+
+ Result result = _domain.ReserveIds(new Span<int>(_reservedObjectIds).Slice(0, OutObjectsCount));
+
+ if (result.IsFailure)
+ {
+ return result;
+ }
+
+ return _implProcessor.PrepareForProcess(ref context, runtimeMetadata);
+ }
+
+ public override Result GetInObjects(Span<ServiceObjectHolder> inObjects)
+ {
+ for (int i = 0; i < InObjectsCount; i++)
+ {
+ inObjects[i] = _domain.GetObject(_inObjectIds[i]);
+ }
+
+ return Result.Success;
+ }
+
+ public override HipcMessageData PrepareForReply(scoped ref ServiceDispatchContext context, out Span<byte> outRawData, ServerMessageRuntimeMetadata runtimeMetadata)
+ {
+ var response = _implProcessor.PrepareForReply(ref context, out outRawData, runtimeMetadata);
+
+ int outHeaderSize = Unsafe.SizeOf<CmifDomainOutHeader>();
+ int implOutDataTotalSize = ImplOutDataTotalSize;
+
+ DebugUtil.Assert(outHeaderSize + implOutDataTotalSize + OutObjectsCount * sizeof(int) <= outRawData.Length);
+
+ outRawData = outRawData.Slice(outHeaderSize);
+ _outObjectIdsOffset = (response.DataWords.Length * sizeof(uint) - outRawData.Length) + implOutDataTotalSize;
+
+ return response;
+ }
+
+ public override void PrepareForErrorReply(scoped ref ServiceDispatchContext context, out Span<byte> outRawData, ServerMessageRuntimeMetadata runtimeMetadata)
+ {
+ _implProcessor.PrepareForErrorReply(ref context, out outRawData, runtimeMetadata);
+
+ int outHeaderSize = Unsafe.SizeOf<CmifDomainOutHeader>();
+ int implOutDataTotalSize = ImplOutDataTotalSize;
+
+ DebugUtil.Assert(outHeaderSize + implOutDataTotalSize <= outRawData.Length);
+
+ outRawData = outRawData.Slice(outHeaderSize);
+
+ _domain.UnreserveIds(new Span<int>(_reservedObjectIds).Slice(0, OutObjectsCount));
+ }
+
+ public override void SetOutObjects(scoped ref ServiceDispatchContext context, HipcMessageData response, Span<ServiceObjectHolder> outObjects)
+ {
+ int outObjectsCount = OutObjectsCount;
+ Span<int> objectIds = _reservedObjectIds;
+
+ for (int i = 0; i < outObjectsCount; i++)
+ {
+ if (outObjects[i] == null)
+ {
+ _domain.UnreserveIds(objectIds.Slice(i, 1));
+ objectIds[i] = 0;
+ continue;
+ }
+
+ _domain.RegisterObject(objectIds[i], outObjects[i]);
+ }
+
+ Span<int> outObjectIds = MemoryMarshal.Cast<byte, int>(MemoryMarshal.Cast<uint, byte>(response.DataWords).Slice(_outObjectIdsOffset));
+
+ for (int i = 0; i < outObjectsCount; i++)
+ {
+ outObjectIds[i] = objectIds[i];
+ }
+ }
+ }
+}
diff --git a/Ryujinx.Horizon/Sdk/Sf/Cmif/HandlesToClose.cs b/Ryujinx.Horizon/Sdk/Sf/Cmif/HandlesToClose.cs
new file mode 100644
index 00000000..0f3b259a
--- /dev/null
+++ b/Ryujinx.Horizon/Sdk/Sf/Cmif/HandlesToClose.cs
@@ -0,0 +1,52 @@
+using System;
+
+namespace Ryujinx.Horizon.Sdk.Sf.Cmif
+{
+ struct HandlesToClose
+ {
+ private int _handle0;
+ private int _handle1;
+ private int _handle2;
+ private int _handle3;
+ private int _handle4;
+ private int _handle5;
+ private int _handle6;
+ private int _handle7;
+
+ public int Count;
+
+ public int this[int index]
+ {
+ get
+ {
+ return index switch
+ {
+ 0 => _handle0,
+ 1 => _handle1,
+ 2 => _handle2,
+ 3 => _handle3,
+ 4 => _handle4,
+ 5 => _handle5,
+ 6 => _handle6,
+ 7 => _handle7,
+ _ => throw new IndexOutOfRangeException()
+ };
+ }
+ set
+ {
+ switch (index)
+ {
+ case 0: _handle0 = value; break;
+ case 1: _handle1 = value; break;
+ case 2: _handle2 = value; break;
+ case 3: _handle3 = value; break;
+ case 4: _handle4 = value; break;
+ case 5: _handle5 = value; break;
+ case 6: _handle6 = value; break;
+ case 7: _handle7 = value; break;
+ default: throw new IndexOutOfRangeException();
+ }
+ }
+ }
+ }
+}
diff --git a/Ryujinx.Horizon/Sdk/Sf/Cmif/InlineContext.cs b/Ryujinx.Horizon/Sdk/Sf/Cmif/InlineContext.cs
new file mode 100644
index 00000000..ddb6943f
--- /dev/null
+++ b/Ryujinx.Horizon/Sdk/Sf/Cmif/InlineContext.cs
@@ -0,0 +1,11 @@
+namespace Ryujinx.Horizon.Sdk.Sf.Cmif
+{
+ class InlineContext
+ {
+ public static int Set(int newContext)
+ {
+ // TODO: Implement (will require FS changes???)
+ return newContext;
+ }
+ }
+}
diff --git a/Ryujinx.Horizon/Sdk/Sf/Cmif/PointerAndSize.cs b/Ryujinx.Horizon/Sdk/Sf/Cmif/PointerAndSize.cs
new file mode 100644
index 00000000..5af00077
--- /dev/null
+++ b/Ryujinx.Horizon/Sdk/Sf/Cmif/PointerAndSize.cs
@@ -0,0 +1,17 @@
+namespace Ryujinx.Horizon.Sdk.Sf.Cmif
+{
+ struct PointerAndSize
+ {
+ public static PointerAndSize Empty => new PointerAndSize(0UL, 0UL);
+
+ public ulong Address { get; }
+ public ulong Size { get; }
+ public bool IsEmpty => Size == 0UL;
+
+ public PointerAndSize(ulong address, ulong size)
+ {
+ Address = address;
+ Size = size;
+ }
+ }
+}
diff --git a/Ryujinx.Horizon/Sdk/Sf/Cmif/ScopedInlineContextChange.cs b/Ryujinx.Horizon/Sdk/Sf/Cmif/ScopedInlineContextChange.cs
new file mode 100644
index 00000000..eabe544f
--- /dev/null
+++ b/Ryujinx.Horizon/Sdk/Sf/Cmif/ScopedInlineContextChange.cs
@@ -0,0 +1,19 @@
+using System;
+
+namespace Ryujinx.Horizon.Sdk.Sf.Cmif
+{
+ struct ScopedInlineContextChange : IDisposable
+ {
+ private readonly int _previousContext;
+
+ public ScopedInlineContextChange(int newContext)
+ {
+ _previousContext = InlineContext.Set(newContext);
+ }
+
+ public void Dispose()
+ {
+ InlineContext.Set(_previousContext);
+ }
+ }
+}
diff --git a/Ryujinx.Horizon/Sdk/Sf/Cmif/ServerDomainBase.cs b/Ryujinx.Horizon/Sdk/Sf/Cmif/ServerDomainBase.cs
new file mode 100644
index 00000000..f38fa030
--- /dev/null
+++ b/Ryujinx.Horizon/Sdk/Sf/Cmif/ServerDomainBase.cs
@@ -0,0 +1,15 @@
+using Ryujinx.Horizon.Common;
+using System;
+
+namespace Ryujinx.Horizon.Sdk.Sf.Cmif
+{
+ abstract class ServerDomainBase
+ {
+ public abstract Result ReserveIds(Span<int> outIds);
+ public abstract void UnreserveIds(ReadOnlySpan<int> ids);
+ public abstract void RegisterObject(int id, ServiceObjectHolder obj);
+
+ public abstract ServiceObjectHolder UnregisterObject(int id);
+ public abstract ServiceObjectHolder GetObject(int id);
+ }
+}
diff --git a/Ryujinx.Horizon/Sdk/Sf/Cmif/ServerDomainManager.cs b/Ryujinx.Horizon/Sdk/Sf/Cmif/ServerDomainManager.cs
new file mode 100644
index 00000000..62ee2738
--- /dev/null
+++ b/Ryujinx.Horizon/Sdk/Sf/Cmif/ServerDomainManager.cs
@@ -0,0 +1,246 @@
+using Ryujinx.Horizon.Common;
+using System;
+using System.Collections.Generic;
+
+namespace Ryujinx.Horizon.Sdk.Sf.Cmif
+{
+ class ServerDomainManager
+ {
+ private class EntryManager
+ {
+ public class Entry
+ {
+ public int Id { get; }
+ public Domain Owner { get; set; }
+ public ServiceObjectHolder Obj { get; set; }
+ public LinkedListNode<Entry> Node { get; set; }
+
+ public Entry(int id)
+ {
+ Id = id;
+ }
+ }
+
+ private readonly LinkedList<Entry> _freeList;
+ private readonly Entry[] _entries;
+
+ public EntryManager(int count)
+ {
+ _freeList = new LinkedList<Entry>();
+ _entries = new Entry[count];
+
+ for (int i = 0; i < count; i++)
+ {
+ _freeList.AddLast(_entries[i] = new Entry(i + 1));
+ }
+ }
+
+ public Entry AllocateEntry()
+ {
+ lock (_freeList)
+ {
+ if (_freeList.Count == 0)
+ {
+ return null;
+ }
+
+ var entry = _freeList.First.Value;
+ _freeList.RemoveFirst();
+ return entry;
+ }
+ }
+
+ public void FreeEntry(Entry entry)
+ {
+ lock (_freeList)
+ {
+ DebugUtil.Assert(entry.Owner == null);
+ DebugUtil.Assert(entry.Obj == null);
+ _freeList.AddFirst(entry);
+ }
+ }
+
+ public Entry GetEntry(int id)
+ {
+ if (id == 0)
+ {
+ return null;
+ }
+
+ int index = id - 1;
+
+ if ((uint)index >= (uint)_entries.Length)
+ {
+ return null;
+ }
+
+ return _entries[index];
+ }
+ }
+
+ private class Domain : DomainServiceObject, IDisposable
+ {
+ private readonly ServerDomainManager _manager;
+ private readonly LinkedList<EntryManager.Entry> _entries;
+
+ public Domain(ServerDomainManager manager)
+ {
+ _manager = manager;
+ _entries = new LinkedList<EntryManager.Entry>();
+ }
+
+ public override ServiceObjectHolder GetObject(int id)
+ {
+ var entry = _manager._entryManager.GetEntry(id);
+ if (entry == null)
+ {
+ return null;
+ }
+
+ lock (_manager._entryOwnerLock)
+ {
+ if (entry.Owner != this)
+ {
+ return null;
+ }
+ }
+
+ return entry.Obj.Clone();
+ }
+
+ public override ServerDomainBase GetServerDomain()
+ {
+ return this;
+ }
+
+ public override void RegisterObject(int id, ServiceObjectHolder obj)
+ {
+ var entry = _manager._entryManager.GetEntry(id);
+ DebugUtil.Assert(entry != null);
+
+ lock (_manager._entryOwnerLock)
+ {
+ DebugUtil.Assert(entry.Owner == null);
+ entry.Owner = this;
+ entry.Node = _entries.AddLast(entry);
+ }
+
+ entry.Obj = obj;
+ }
+
+ public override Result ReserveIds(Span<int> outIds)
+ {
+ for (int i = 0; i < outIds.Length; i++)
+ {
+ var entry = _manager._entryManager.AllocateEntry();
+ if (entry == null)
+ {
+ return SfResult.OutOfDomainEntries;
+ }
+
+ DebugUtil.Assert(entry.Owner == null);
+
+ outIds[i] = entry.Id;
+ }
+
+ return Result.Success;
+ }
+
+ public override ServiceObjectHolder UnregisterObject(int id)
+ {
+ var entry = _manager._entryManager.GetEntry(id);
+ if (entry == null)
+ {
+ return null;
+ }
+
+ ServiceObjectHolder obj;
+
+ lock (_manager._entryOwnerLock)
+ {
+ if (entry.Owner != this)
+ {
+ return null;
+ }
+
+ entry.Owner = null;
+ obj = entry.Obj;
+ entry.Obj = null;
+ _entries.Remove(entry.Node);
+ entry.Node = null;
+ }
+
+ _manager._entryManager.FreeEntry(entry);
+
+ return obj;
+ }
+
+ public override void UnreserveIds(ReadOnlySpan<int> ids)
+ {
+ for (int i = 0; i < ids.Length; i++)
+ {
+ var entry = _manager._entryManager.GetEntry(ids[i]);
+
+ DebugUtil.Assert(entry != null);
+ DebugUtil.Assert(entry.Owner == null);
+
+ _manager._entryManager.FreeEntry(entry);
+ }
+ }
+
+ public void Dispose()
+ {
+ foreach (var entry in _entries)
+ {
+ if (entry.Obj.ServiceObject is IDisposable disposableObj)
+ {
+ disposableObj.Dispose();
+ }
+ }
+
+ _manager.FreeDomain(this);
+ }
+ }
+
+ private readonly EntryManager _entryManager;
+ private readonly object _entryOwnerLock;
+ private readonly HashSet<Domain> _domains;
+ private int _maxDomains;
+
+ public ServerDomainManager(int entryCount, int maxDomains)
+ {
+ _entryManager = new EntryManager(entryCount);
+ _entryOwnerLock = new object();
+ _domains = new HashSet<Domain>();
+ _maxDomains = maxDomains;
+ }
+
+ public DomainServiceObject AllocateDomainServiceObject()
+ {
+ lock (_domains)
+ {
+ if (_domains.Count == _maxDomains)
+ {
+ return null;
+ }
+
+ var domain = new Domain(this);
+ _domains.Add(domain);
+ return domain;
+ }
+ }
+
+ public static void DestroyDomainServiceObject(DomainServiceObject obj)
+ {
+ ((Domain)obj).Dispose();
+ }
+
+ private void FreeDomain(Domain domain)
+ {
+ lock (_domains)
+ {
+ _domains.Remove(domain);
+ }
+ }
+ }
+}
diff --git a/Ryujinx.Horizon/Sdk/Sf/Cmif/ServerMessageProcessor.cs b/Ryujinx.Horizon/Sdk/Sf/Cmif/ServerMessageProcessor.cs
new file mode 100644
index 00000000..e7650238
--- /dev/null
+++ b/Ryujinx.Horizon/Sdk/Sf/Cmif/ServerMessageProcessor.cs
@@ -0,0 +1,18 @@
+using Ryujinx.Horizon.Common;
+using Ryujinx.Horizon.Sdk.Sf.Hipc;
+using System;
+
+namespace Ryujinx.Horizon.Sdk.Sf.Cmif
+{
+ abstract class ServerMessageProcessor
+ {
+ public abstract void SetImplementationProcessor(ServerMessageProcessor impl);
+ public abstract ServerMessageRuntimeMetadata GetRuntimeMetadata();
+
+ public abstract Result PrepareForProcess(scoped ref ServiceDispatchContext context, ServerMessageRuntimeMetadata runtimeMetadata);
+ public abstract Result GetInObjects(Span<ServiceObjectHolder> inObjects);
+ public abstract HipcMessageData PrepareForReply(scoped ref ServiceDispatchContext context, out Span<byte> outRawData, ServerMessageRuntimeMetadata runtimeMetadata);
+ public abstract void PrepareForErrorReply(scoped ref ServiceDispatchContext context, out Span<byte> outRawData, ServerMessageRuntimeMetadata runtimeMetadata);
+ public abstract void SetOutObjects(scoped ref ServiceDispatchContext context, HipcMessageData response, Span<ServiceObjectHolder> outObjects);
+ }
+}
diff --git a/Ryujinx.Horizon/Sdk/Sf/Cmif/ServerMessageRuntimeMetadata.cs b/Ryujinx.Horizon/Sdk/Sf/Cmif/ServerMessageRuntimeMetadata.cs
new file mode 100644
index 00000000..18a40430
--- /dev/null
+++ b/Ryujinx.Horizon/Sdk/Sf/Cmif/ServerMessageRuntimeMetadata.cs
@@ -0,0 +1,29 @@
+namespace Ryujinx.Horizon.Sdk.Sf.Cmif
+{
+ struct ServerMessageRuntimeMetadata
+ {
+ public ushort InDataSize { get; }
+ public ushort OutDataSize { get; }
+ public byte InHeadersSize { get; }
+ public byte OutHeadersSize { get; }
+ public byte InObjectsCount { get; }
+ public byte OutObjectsCount { get; }
+ public int UnfixedOutPointerSizeOffset => InDataSize + InHeadersSize + 0x10;
+
+ public ServerMessageRuntimeMetadata(
+ ushort inDataSize,
+ ushort outDataSize,
+ byte inHeadersSize,
+ byte outHeadersSize,
+ byte inObjectsCount,
+ byte outObjectsCount)
+ {
+ InDataSize = inDataSize;
+ OutDataSize = outDataSize;
+ InHeadersSize = inHeadersSize;
+ OutHeadersSize = outHeadersSize;
+ InObjectsCount = inObjectsCount;
+ OutObjectsCount = outObjectsCount;
+ }
+ }
+}
diff --git a/Ryujinx.Horizon/Sdk/Sf/Cmif/ServiceDispatchContext.cs b/Ryujinx.Horizon/Sdk/Sf/Cmif/ServiceDispatchContext.cs
new file mode 100644
index 00000000..3339a1a6
--- /dev/null
+++ b/Ryujinx.Horizon/Sdk/Sf/Cmif/ServiceDispatchContext.cs
@@ -0,0 +1,18 @@
+using Ryujinx.Horizon.Sdk.Sf.Hipc;
+using System;
+
+namespace Ryujinx.Horizon.Sdk.Sf.Cmif
+{
+ ref struct ServiceDispatchContext
+ {
+ public IServiceObject ServiceObject;
+ public ServerSessionManager Manager;
+ public ServerSession Session;
+ public ServerMessageProcessor Processor;
+ public HandlesToClose HandlesToClose;
+ public PointerAndSize PointerBuffer;
+ public ReadOnlySpan<byte> InMessageBuffer;
+ public Span<byte> OutMessageBuffer;
+ public HipcMessage Request;
+ }
+}
diff --git a/Ryujinx.Horizon/Sdk/Sf/Cmif/ServiceDispatchMeta.cs b/Ryujinx.Horizon/Sdk/Sf/Cmif/ServiceDispatchMeta.cs
new file mode 100644
index 00000000..7fbd8eb8
--- /dev/null
+++ b/Ryujinx.Horizon/Sdk/Sf/Cmif/ServiceDispatchMeta.cs
@@ -0,0 +1,12 @@
+namespace Ryujinx.Horizon.Sdk.Sf.Cmif
+{
+ struct ServiceDispatchMeta
+ {
+ public ServiceDispatchTableBase DispatchTable { get; }
+
+ public ServiceDispatchMeta(ServiceDispatchTableBase dispatchTable)
+ {
+ DispatchTable = dispatchTable;
+ }
+ }
+}
diff --git a/Ryujinx.Horizon/Sdk/Sf/Cmif/ServiceDispatchTable.cs b/Ryujinx.Horizon/Sdk/Sf/Cmif/ServiceDispatchTable.cs
new file mode 100644
index 00000000..145c1783
--- /dev/null
+++ b/Ryujinx.Horizon/Sdk/Sf/Cmif/ServiceDispatchTable.cs
@@ -0,0 +1,33 @@
+using Ryujinx.Horizon.Common;
+using System;
+using System.Collections.Generic;
+
+namespace Ryujinx.Horizon.Sdk.Sf.Cmif
+{
+ class ServiceDispatchTable : ServiceDispatchTableBase
+ {
+ private readonly string _objectName;
+ private readonly IReadOnlyDictionary<int, CommandHandler> _entries;
+
+ public ServiceDispatchTable(string objectName, IReadOnlyDictionary<int, CommandHandler> entries)
+ {
+ _objectName = objectName;
+ _entries = entries;
+ }
+
+ public override Result ProcessMessage(ref ServiceDispatchContext context, ReadOnlySpan<byte> inRawData)
+ {
+ return ProcessMessageImpl(ref context, inRawData, _entries, _objectName);
+ }
+
+ public static ServiceDispatchTableBase Create(IServiceObject instance)
+ {
+ if (instance is DomainServiceObject)
+ {
+ return new DomainServiceObjectDispatchTable();
+ }
+
+ return new ServiceDispatchTable(instance.GetType().Name, instance.GetCommandHandlers());
+ }
+ }
+}
diff --git a/Ryujinx.Horizon/Sdk/Sf/Cmif/ServiceDispatchTableBase.cs b/Ryujinx.Horizon/Sdk/Sf/Cmif/ServiceDispatchTableBase.cs
new file mode 100644
index 00000000..a0e28ca8
--- /dev/null
+++ b/Ryujinx.Horizon/Sdk/Sf/Cmif/ServiceDispatchTableBase.cs
@@ -0,0 +1,90 @@
+using Ryujinx.Common.Logging;
+using Ryujinx.Horizon.Common;
+using Ryujinx.Horizon.Sdk.Sf.Hipc;
+using System;
+using System.Collections.Generic;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.Horizon.Sdk.Sf.Cmif
+{
+ abstract class ServiceDispatchTableBase
+ {
+ private const uint MaxCmifVersion = 1;
+
+ public abstract Result ProcessMessage(ref ServiceDispatchContext context, ReadOnlySpan<byte> inRawData);
+
+ protected Result ProcessMessageImpl(ref ServiceDispatchContext context, ReadOnlySpan<byte> inRawData, IReadOnlyDictionary<int, CommandHandler> entries, string objectName)
+ {
+ if (inRawData.Length < Unsafe.SizeOf<CmifInHeader>())
+ {
+ Logger.Warning?.Print(LogClass.KernelIpc, $"Request message size 0x{inRawData.Length:X} is invalid");
+
+ return SfResult.InvalidHeaderSize;
+ }
+
+ CmifInHeader inHeader = MemoryMarshal.Cast<byte, CmifInHeader>(inRawData)[0];
+
+ if (inHeader.Magic != CmifMessage.CmifInHeaderMagic || inHeader.Version > MaxCmifVersion)
+ {
+ Logger.Warning?.Print(LogClass.KernelIpc, $"Request message header magic value 0x{inHeader.Magic:X} is invalid");
+
+ return SfResult.InvalidInHeader;
+ }
+
+ ReadOnlySpan<byte> inMessageRawData = inRawData[Unsafe.SizeOf<CmifInHeader>()..];
+ uint commandId = inHeader.CommandId;
+
+ var outHeader = Span<CmifOutHeader>.Empty;
+
+ if (!entries.TryGetValue((int)commandId, out var commandHandler))
+ {
+ Logger.Warning?.Print(LogClass.KernelIpc, $"{objectName} command ID 0x{commandId:X} is not implemented");
+
+ if (HorizonStatic.Options.IgnoreMissingServices)
+ {
+ // If ignore missing services is enabled, just pretend that everything is fine.
+ var response = PrepareForStubReply(ref context, out Span<byte> outRawData);
+ CommandHandler.GetCmifOutHeaderPointer(ref outHeader, ref outRawData);
+ outHeader[0] = new CmifOutHeader() { Magic = CmifMessage.CmifOutHeaderMagic, Result = Result.Success };
+
+ return Result.Success;
+ }
+
+ return SfResult.UnknownCommandId;
+ }
+
+ Logger.Trace?.Print(LogClass.KernelIpc, $"{objectName}.{commandHandler.MethodName} called");
+
+ Result commandResult = commandHandler.Invoke(ref outHeader, ref context, inMessageRawData);
+
+ if (commandResult.Module == SfResult.ModuleId ||
+ commandResult.Module == HipcResult.ModuleId)
+ {
+ Logger.Warning?.Print(LogClass.KernelIpc, $"{commandHandler.MethodName} returned error {commandResult}");
+ }
+
+ if (SfResult.RequestContextChanged(commandResult))
+ {
+ return commandResult;
+ }
+
+ if (outHeader.IsEmpty)
+ {
+ commandResult.AbortOnSuccess();
+ return commandResult;
+ }
+
+ outHeader[0] = new CmifOutHeader() { Magic = CmifMessage.CmifOutHeaderMagic, Result = commandResult };
+
+ return Result.Success;
+ }
+
+ private static HipcMessageData PrepareForStubReply(scoped ref ServiceDispatchContext context, out Span<byte> outRawData)
+ {
+ var response = HipcMessage.WriteResponse(context.OutMessageBuffer, 0, 0x20 / sizeof(uint), 0, 0);
+ outRawData = MemoryMarshal.Cast<uint, byte>(response.DataWords);
+ return response;
+ }
+ }
+}
diff --git a/Ryujinx.Horizon/Sdk/Sf/Cmif/ServiceObjectHolder.cs b/Ryujinx.Horizon/Sdk/Sf/Cmif/ServiceObjectHolder.cs
new file mode 100644
index 00000000..6e87e340
--- /dev/null
+++ b/Ryujinx.Horizon/Sdk/Sf/Cmif/ServiceObjectHolder.cs
@@ -0,0 +1,34 @@
+using Ryujinx.Horizon.Common;
+using System;
+
+namespace Ryujinx.Horizon.Sdk.Sf.Cmif
+{
+ class ServiceObjectHolder
+ {
+ public IServiceObject ServiceObject { get; }
+
+ private readonly ServiceDispatchMeta _dispatchMeta;
+
+ public ServiceObjectHolder(ServiceObjectHolder objectHolder)
+ {
+ ServiceObject = objectHolder.ServiceObject;
+ _dispatchMeta = objectHolder._dispatchMeta;
+ }
+
+ public ServiceObjectHolder(IServiceObject serviceImpl)
+ {
+ ServiceObject = serviceImpl;
+ _dispatchMeta = new ServiceDispatchMeta(ServiceDispatchTable.Create(serviceImpl));
+ }
+
+ public ServiceObjectHolder Clone()
+ {
+ return new ServiceObjectHolder(this);
+ }
+
+ public Result ProcessMessage(ref ServiceDispatchContext context, ReadOnlySpan<byte> inRawData)
+ {
+ return _dispatchMeta.DispatchTable.ProcessMessage(ref context, inRawData);
+ }
+ }
+}
diff --git a/Ryujinx.Horizon/Sdk/Sf/CmifCommandAttribute.cs b/Ryujinx.Horizon/Sdk/Sf/CmifCommandAttribute.cs
new file mode 100644
index 00000000..51a7b597
--- /dev/null
+++ b/Ryujinx.Horizon/Sdk/Sf/CmifCommandAttribute.cs
@@ -0,0 +1,15 @@
+using System;
+
+namespace Ryujinx.Horizon.Sdk.Sf
+{
+ [AttributeUsage(AttributeTargets.Method, AllowMultiple = true)]
+ class CmifCommandAttribute : Attribute
+ {
+ public uint CommandId { get; }
+
+ public CmifCommandAttribute(uint commandId)
+ {
+ CommandId = commandId;
+ }
+ }
+}
diff --git a/Ryujinx.Horizon/Sdk/Sf/CommandArg.cs b/Ryujinx.Horizon/Sdk/Sf/CommandArg.cs
new file mode 100644
index 00000000..8f367b4e
--- /dev/null
+++ b/Ryujinx.Horizon/Sdk/Sf/CommandArg.cs
@@ -0,0 +1,56 @@
+using Ryujinx.Horizon.Sdk.Sf.Hipc;
+
+namespace Ryujinx.Horizon.Sdk.Sf
+{
+ enum CommandArgType : byte
+ {
+ Invalid,
+
+ Buffer,
+ InArgument,
+ InCopyHandle,
+ InMoveHandle,
+ InObject,
+ OutArgument,
+ OutCopyHandle,
+ OutMoveHandle,
+ OutObject,
+ ProcessId
+ }
+
+ struct CommandArg
+ {
+ public CommandArgType Type { get; }
+ public HipcBufferFlags BufferFlags { get; }
+ public ushort BufferFixedSize { get; }
+ public int ArgSize { get; }
+ public int ArgAlignment { get; }
+
+ public CommandArg(CommandArgType type)
+ {
+ Type = type;
+ BufferFlags = default;
+ BufferFixedSize = 0;
+ ArgSize = 0;
+ ArgAlignment = 0;
+ }
+
+ public CommandArg(CommandArgType type, int argSize, int argAlignment)
+ {
+ Type = type;
+ BufferFlags = default;
+ BufferFixedSize = 0;
+ ArgSize = argSize;
+ ArgAlignment = argAlignment;
+ }
+
+ public CommandArg(HipcBufferFlags flags, ushort fixedSize = 0)
+ {
+ Type = CommandArgType.Buffer;
+ BufferFlags = flags;
+ BufferFixedSize = fixedSize;
+ ArgSize = 0;
+ ArgAlignment = 0;
+ }
+ }
+}
diff --git a/Ryujinx.Horizon/Sdk/Sf/CommandArgAttributes.cs b/Ryujinx.Horizon/Sdk/Sf/CommandArgAttributes.cs
new file mode 100644
index 00000000..5b7c302f
--- /dev/null
+++ b/Ryujinx.Horizon/Sdk/Sf/CommandArgAttributes.cs
@@ -0,0 +1,38 @@
+using Ryujinx.Horizon.Sdk.Sf.Hipc;
+using System;
+
+namespace Ryujinx.Horizon.Sdk.Sf
+{
+ [AttributeUsage(AttributeTargets.Parameter)]
+ class BufferAttribute : Attribute
+ {
+ public HipcBufferFlags Flags { get; }
+ public ushort FixedSize { get; }
+
+ public BufferAttribute(HipcBufferFlags flags)
+ {
+ Flags = flags;
+ }
+
+ public BufferAttribute(HipcBufferFlags flags, ushort fixedSize)
+ {
+ Flags = flags | HipcBufferFlags.FixedSize;
+ FixedSize = fixedSize;
+ }
+ }
+
+ [AttributeUsage(AttributeTargets.Parameter)]
+ class ClientProcessIdAttribute : Attribute
+ {
+ }
+
+ [AttributeUsage(AttributeTargets.Parameter)]
+ class CopyHandleAttribute : Attribute
+ {
+ }
+
+ [AttributeUsage(AttributeTargets.Parameter)]
+ class MoveHandleAttribute : Attribute
+ {
+ }
+}
diff --git a/Ryujinx.Horizon/Sdk/Sf/CommandHandler.cs b/Ryujinx.Horizon/Sdk/Sf/CommandHandler.cs
new file mode 100644
index 00000000..ae42a8ef
--- /dev/null
+++ b/Ryujinx.Horizon/Sdk/Sf/CommandHandler.cs
@@ -0,0 +1,57 @@
+using Ryujinx.Horizon.Common;
+using Ryujinx.Horizon.Sdk.Sf.Cmif;
+using System;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.Horizon.Sdk.Sf
+{
+ class CommandHandler
+ {
+ public delegate Result MethodInvoke(
+ ref ServiceDispatchContext context,
+ HipcCommandProcessor processor,
+ ServerMessageRuntimeMetadata runtimeMetadata,
+ ReadOnlySpan<byte> inRawData,
+ ref Span<CmifOutHeader> outHeader);
+
+ private readonly MethodInvoke _invoke;
+ private readonly HipcCommandProcessor _processor;
+
+ public string MethodName => _invoke.Method.Name;
+
+ public CommandHandler(MethodInvoke invoke, params CommandArg[] args)
+ {
+ _invoke = invoke;
+ _processor = new HipcCommandProcessor(args);
+ }
+
+ public Result Invoke(ref Span<CmifOutHeader> outHeader, ref ServiceDispatchContext context, ReadOnlySpan<byte> inRawData)
+ {
+ if (context.Processor == null)
+ {
+ context.Processor = _processor;
+ }
+ else
+ {
+ context.Processor.SetImplementationProcessor(_processor);
+ }
+
+ var runtimeMetadata = context.Processor.GetRuntimeMetadata();
+ Result result = context.Processor.PrepareForProcess(ref context, runtimeMetadata);
+
+ if (result.IsFailure)
+ {
+ return result;
+ }
+
+ return _invoke(ref context, _processor, runtimeMetadata, inRawData, ref outHeader);
+ }
+
+ public static void GetCmifOutHeaderPointer(ref Span<CmifOutHeader> outHeader, ref Span<byte> outRawData)
+ {
+ outHeader = MemoryMarshal.Cast<byte, CmifOutHeader>(outRawData).Slice(0, 1);
+ outRawData = outRawData.Slice(Unsafe.SizeOf<CmifOutHeader>());
+ }
+ }
+}
diff --git a/Ryujinx.Horizon/Sdk/Sf/CommandSerialization.cs b/Ryujinx.Horizon/Sdk/Sf/CommandSerialization.cs
new file mode 100644
index 00000000..9a3a511a
--- /dev/null
+++ b/Ryujinx.Horizon/Sdk/Sf/CommandSerialization.cs
@@ -0,0 +1,68 @@
+using Ryujinx.Horizon.Sdk.Sf.Cmif;
+using Ryujinx.Horizon.Sdk.Sf.Hipc;
+using Ryujinx.Memory;
+using System;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.Horizon.Sdk.Sf
+{
+ static class CommandSerialization
+ {
+ public static ReadOnlySpan<byte> GetReadOnlySpan(PointerAndSize bufferRange)
+ {
+ return HorizonStatic.AddressSpace.GetSpan(bufferRange.Address, checked((int)bufferRange.Size));
+ }
+
+ public static WritableRegion GetWritableRegion(PointerAndSize bufferRange)
+ {
+ return HorizonStatic.AddressSpace.GetWritableRegion(bufferRange.Address, checked((int)bufferRange.Size));
+ }
+
+ public static ref T GetRef<T>(PointerAndSize bufferRange) where T : unmanaged
+ {
+ var writableRegion = GetWritableRegion(bufferRange);
+ return ref MemoryMarshal.Cast<byte, T>(writableRegion.Memory.Span)[0];
+ }
+
+ public static object DeserializeArg<T>(ref ServiceDispatchContext context, ReadOnlySpan<byte> inRawData, int offset) where T : unmanaged
+ {
+ return MemoryMarshal.Cast<byte, T>(inRawData.Slice(offset, Unsafe.SizeOf<T>()))[0];
+ }
+
+ public static T DeserializeArg<T>(ReadOnlySpan<byte> inRawData, int offset) where T : unmanaged
+ {
+ return MemoryMarshal.Cast<byte, T>(inRawData.Slice(offset, Unsafe.SizeOf<T>()))[0];
+ }
+
+ public static ulong DeserializeClientProcessId(ref ServiceDispatchContext context)
+ {
+ return context.Request.Pid;
+ }
+
+ public static int DeserializeCopyHandle(ref ServiceDispatchContext context, int index)
+ {
+ return context.Request.Data.CopyHandles[index];
+ }
+
+ public static int DeserializeMoveHandle(ref ServiceDispatchContext context, int index)
+ {
+ return context.Request.Data.MoveHandles[index];
+ }
+
+ public static void SerializeArg<T>(Span<byte> outRawData, int offset, T value) where T : unmanaged
+ {
+ MemoryMarshal.Cast<byte, T>(outRawData.Slice(offset, Unsafe.SizeOf<T>()))[0] = (T)value;
+ }
+
+ public static void SerializeCopyHandle(HipcMessageData response, int index, int value)
+ {
+ response.CopyHandles[index] = value;
+ }
+
+ public static void SerializeMoveHandle(HipcMessageData response, int index, int value)
+ {
+ response.MoveHandles[index] = value;
+ }
+ }
+}
diff --git a/Ryujinx.Horizon/Sdk/Sf/Hipc/Api.cs b/Ryujinx.Horizon/Sdk/Sf/Hipc/Api.cs
new file mode 100644
index 00000000..deac524c
--- /dev/null
+++ b/Ryujinx.Horizon/Sdk/Sf/Hipc/Api.cs
@@ -0,0 +1,89 @@
+using Ryujinx.Horizon.Common;
+using System;
+
+namespace Ryujinx.Horizon.Sdk.Sf.Hipc
+{
+ static class Api
+ {
+ public const int TlsMessageBufferSize = 0x100;
+
+ public static Result Receive(out ReceiveResult recvResult, int sessionHandle, Span<byte> messageBuffer)
+ {
+ Result result = ReceiveImpl(sessionHandle, messageBuffer);
+
+ if (result == KernelResult.PortRemoteClosed)
+ {
+ recvResult = ReceiveResult.Closed;
+
+ return Result.Success;
+ }
+ else if (result == KernelResult.ReceiveListBroken)
+ {
+ recvResult = ReceiveResult.NeedsRetry;
+
+ return Result.Success;
+ }
+
+ recvResult = ReceiveResult.Success;
+
+ return result;
+ }
+
+ private static Result ReceiveImpl(int sessionHandle, Span<byte> messageBuffer)
+ {
+ Span<int> handles = stackalloc int[1];
+
+ handles[0] = sessionHandle;
+
+ var tlsSpan = HorizonStatic.AddressSpace.GetSpan(HorizonStatic.ThreadContext.TlsAddress, TlsMessageBufferSize);
+
+ if (messageBuffer == tlsSpan)
+ {
+ return HorizonStatic.Syscall.ReplyAndReceive(out _, handles, 0, -1L);
+ }
+ else
+ {
+ throw new NotImplementedException();
+ }
+ }
+
+ public static Result Reply(int sessionHandle, ReadOnlySpan<byte> messageBuffer)
+ {
+ Result result = ReplyImpl(sessionHandle, messageBuffer);
+
+ result.AbortUnless(KernelResult.TimedOut, KernelResult.PortRemoteClosed);
+
+ return Result.Success;
+ }
+
+ private static Result ReplyImpl(int sessionHandle, ReadOnlySpan<byte> messageBuffer)
+ {
+ Span<int> handles = stackalloc int[1];
+
+ handles[0] = sessionHandle;
+
+ var tlsSpan = HorizonStatic.AddressSpace.GetSpan(HorizonStatic.ThreadContext.TlsAddress, TlsMessageBufferSize);
+
+ if (messageBuffer == tlsSpan)
+ {
+ return HorizonStatic.Syscall.ReplyAndReceive(out _, handles, sessionHandle, 0);
+ }
+ else
+ {
+ throw new NotImplementedException();
+ }
+ }
+
+ public static Result CreateSession(out int serverHandle, out int clientHandle)
+ {
+ Result result = HorizonStatic.Syscall.CreateSession(out serverHandle, out clientHandle, false, null);
+
+ if (result == KernelResult.OutOfResource)
+ {
+ return HipcResult.OutOfSessions;
+ }
+
+ return result;
+ }
+ }
+}
diff --git a/Ryujinx.Horizon/Sdk/Sf/Hipc/Header.cs b/Ryujinx.Horizon/Sdk/Sf/Hipc/Header.cs
new file mode 100644
index 00000000..cdb50b57
--- /dev/null
+++ b/Ryujinx.Horizon/Sdk/Sf/Hipc/Header.cs
@@ -0,0 +1,65 @@
+using Ryujinx.Common.Utilities;
+using Ryujinx.Horizon.Sdk.Sf.Cmif;
+
+namespace Ryujinx.Horizon.Sdk.Sf.Hipc
+{
+ struct Header
+ {
+ private uint _word0;
+ private uint _word1;
+
+ public CommandType Type
+ {
+ get => (CommandType)_word0.Extract(0, 16);
+ set => _word0 = _word0.Insert(0, 16, (uint)value);
+ }
+
+ public int SendStaticsCount
+ {
+ get => (int)_word0.Extract(16, 4);
+ set => _word0 = _word0.Insert(16, 4, (uint)value);
+ }
+
+ public int SendBuffersCount
+ {
+ get => (int)_word0.Extract(20, 4);
+ set => _word0 = _word0.Insert(20, 4, (uint)value);
+ }
+
+ public int ReceiveBuffersCount
+ {
+ get => (int)_word0.Extract(24, 4);
+ set => _word0 = _word0.Insert(24, 4, (uint)value);
+ }
+
+ public int ExchangeBuffersCount
+ {
+ get => (int)_word0.Extract(28, 4);
+ set => _word0 = _word0.Insert(28, 4, (uint)value);
+ }
+
+ public int DataWordsCount
+ {
+ get => (int)_word1.Extract(0, 10);
+ set => _word1 = _word1.Insert(0, 10, (uint)value);
+ }
+
+ public int ReceiveStaticMode
+ {
+ get => (int)_word1.Extract(10, 4);
+ set => _word1 = _word1.Insert(10, 4, (uint)value);
+ }
+
+ public int ReceiveListOffset
+ {
+ get => (int)_word1.Extract(20, 11);
+ set => _word1 = _word1.Insert(20, 11, (uint)value);
+ }
+
+ public bool HasSpecialHeader
+ {
+ get => _word1.Extract(31);
+ set => _word1 = _word1.Insert(31, value);
+ }
+ }
+}
diff --git a/Ryujinx.Horizon/Sdk/Sf/Hipc/HipcBufferDescriptor.cs b/Ryujinx.Horizon/Sdk/Sf/Hipc/HipcBufferDescriptor.cs
new file mode 100644
index 00000000..7778d5bc
--- /dev/null
+++ b/Ryujinx.Horizon/Sdk/Sf/Hipc/HipcBufferDescriptor.cs
@@ -0,0 +1,15 @@
+namespace Ryujinx.Horizon.Sdk.Sf.Hipc
+{
+ struct HipcBufferDescriptor
+ {
+#pragma warning disable CS0649
+ private uint _sizeLow;
+ private uint _addressLow;
+ private uint _word2;
+#pragma warning restore CS0649
+
+ public ulong Address => _addressLow | (((ulong)_word2 << 4) & 0xf00000000UL) | (((ulong)_word2 << 34) & 0x7000000000UL);
+ public ulong Size => _sizeLow | ((ulong)_word2 << 8) & 0xf00000000UL;
+ public HipcBufferMode Mode => (HipcBufferMode)(_word2 & 3);
+ }
+}
diff --git a/Ryujinx.Horizon/Sdk/Sf/Hipc/HipcBufferFlags.cs b/Ryujinx.Horizon/Sdk/Sf/Hipc/HipcBufferFlags.cs
new file mode 100644
index 00000000..594af2c8
--- /dev/null
+++ b/Ryujinx.Horizon/Sdk/Sf/Hipc/HipcBufferFlags.cs
@@ -0,0 +1,17 @@
+using System;
+
+namespace Ryujinx.Horizon.Sdk.Sf.Hipc
+{
+ [Flags]
+ enum HipcBufferFlags : byte
+ {
+ In = 1 << 0,
+ Out = 1 << 1,
+ MapAlias = 1 << 2,
+ Pointer = 1 << 3,
+ FixedSize = 1 << 4,
+ AutoSelect = 1 << 5,
+ MapTransferAllowsNonSecure = 1 << 6,
+ MapTransferAllowsNonDevice = 1 << 7
+ }
+}
diff --git a/Ryujinx.Horizon/Sdk/Sf/Hipc/HipcBufferMode.cs b/Ryujinx.Horizon/Sdk/Sf/Hipc/HipcBufferMode.cs
new file mode 100644
index 00000000..4ef6374b
--- /dev/null
+++ b/Ryujinx.Horizon/Sdk/Sf/Hipc/HipcBufferMode.cs
@@ -0,0 +1,10 @@
+namespace Ryujinx.Horizon.Sdk.Sf.Hipc
+{
+ enum HipcBufferMode
+ {
+ Normal = 0,
+ NonSecure = 1,
+ Invalid = 2,
+ NonDevice = 3
+ }
+}
diff --git a/Ryujinx.Horizon/Sdk/Sf/Hipc/HipcManager.cs b/Ryujinx.Horizon/Sdk/Sf/Hipc/HipcManager.cs
new file mode 100644
index 00000000..ea2ec650
--- /dev/null
+++ b/Ryujinx.Horizon/Sdk/Sf/Hipc/HipcManager.cs
@@ -0,0 +1,115 @@
+using Ryujinx.Horizon.Common;
+using Ryujinx.Horizon.Sdk.Sf.Cmif;
+using System;
+
+namespace Ryujinx.Horizon.Sdk.Sf.Hipc
+{
+ partial class HipcManager : IServiceObject
+ {
+ private readonly ServerDomainSessionManager _manager;
+ private readonly ServerSession _session;
+
+ public HipcManager(ServerDomainSessionManager manager, ServerSession session)
+ {
+ _manager = manager;
+ _session = session;
+ }
+
+ [CmifCommand(0)]
+ public Result ConvertCurrentObjectToDomain(out int objectId)
+ {
+ objectId = 0;
+
+ var domain = _manager.Domain.AllocateDomainServiceObject();
+ if (domain == null)
+ {
+ return HipcResult.OutOfDomains;
+ }
+
+ bool succeeded = false;
+
+ try
+ {
+ Span<int> objectIds = stackalloc int[1];
+
+ Result result = domain.ReserveIds(objectIds);
+
+ if (result.IsFailure)
+ {
+ return result;
+ }
+
+ objectId = objectIds[0];
+ succeeded = true;
+ }
+ finally
+ {
+ if (!succeeded)
+ {
+ ServerDomainManager.DestroyDomainServiceObject(domain);
+ }
+ }
+
+ domain.RegisterObject(objectId, _session.ServiceObjectHolder);
+ _session.ServiceObjectHolder = new ServiceObjectHolder(domain);
+
+ return Result.Success;
+ }
+
+ [CmifCommand(1)]
+ public Result CopyFromCurrentDomain([MoveHandle] out int clientHandle, int objectId)
+ {
+ clientHandle = 0;
+
+ if (!(_session.ServiceObjectHolder.ServiceObject is DomainServiceObject domain))
+ {
+ return HipcResult.TargetNotDomain;
+ }
+
+ var obj = domain.GetObject(objectId);
+ if (obj == null)
+ {
+ return HipcResult.DomainObjectNotFound;
+ }
+
+ Api.CreateSession(out int serverHandle, out clientHandle).AbortOnFailure();
+ _manager.RegisterSession(serverHandle, obj).AbortOnFailure();
+
+ return Result.Success;
+ }
+
+ [CmifCommand(2)]
+ public Result CloneCurrentObject([MoveHandle] out int clientHandle)
+ {
+ return CloneCurrentObjectImpl(out clientHandle, _manager);
+ }
+
+ [CmifCommand(3)]
+ public void QueryPointerBufferSize(out ushort size)
+ {
+ size = (ushort)_session.PointerBuffer.Size;
+ }
+
+ [CmifCommand(4)]
+ public Result CloneCurrentObjectEx([MoveHandle] out int clientHandle, uint tag)
+ {
+ return CloneCurrentObjectImpl(out clientHandle, _manager.GetSessionManagerByTag(tag));
+ }
+
+ private Result CloneCurrentObjectImpl(out int clientHandle, ServerSessionManager manager)
+ {
+ clientHandle = 0;
+
+ var clone = _session.ServiceObjectHolder.Clone();
+ if (clone == null)
+ {
+ return HipcResult.DomainObjectNotFound;
+ }
+
+ Api.CreateSession(out int serverHandle, out clientHandle).AbortOnFailure();
+ manager.RegisterSession(serverHandle, clone).AbortOnFailure();
+
+ return Result.Success;
+ }
+ }
+}
diff --git a/Ryujinx.Horizon/Sdk/Sf/Hipc/HipcMessage.cs b/Ryujinx.Horizon/Sdk/Sf/Hipc/HipcMessage.cs
new file mode 100644
index 00000000..3017f404
--- /dev/null
+++ b/Ryujinx.Horizon/Sdk/Sf/Hipc/HipcMessage.cs
@@ -0,0 +1,222 @@
+using Ryujinx.Common;
+using Ryujinx.Horizon.Sdk.Sf.Cmif;
+using System;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.Horizon.Sdk.Sf.Hipc
+{
+ ref struct HipcMessage
+ {
+ public const int AutoReceiveStatic = byte.MaxValue;
+
+ public HipcMetadata Meta;
+ public HipcMessageData Data;
+ public ulong Pid;
+
+ public HipcMessage(Span<byte> data)
+ {
+ int initialLength = data.Length;
+
+ Header header = MemoryMarshal.Cast<byte, Header>(data)[0];
+
+ data = data.Slice(Unsafe.SizeOf<Header>());
+
+ int receiveStaticsCount = 0;
+ ulong pid = 0;
+
+ if (header.ReceiveStaticMode != 0)
+ {
+ if (header.ReceiveStaticMode == 2)
+ {
+ receiveStaticsCount = AutoReceiveStatic;
+ }
+ else if (header.ReceiveStaticMode > 2)
+ {
+ receiveStaticsCount = header.ReceiveStaticMode - 2;
+ }
+ }
+
+ SpecialHeader specialHeader = default;
+
+ if (header.HasSpecialHeader)
+ {
+ specialHeader = MemoryMarshal.Cast<byte, SpecialHeader>(data)[0];
+
+ data = data.Slice(Unsafe.SizeOf<SpecialHeader>());
+
+ if (specialHeader.SendPid)
+ {
+ pid = MemoryMarshal.Cast<byte, ulong>(data)[0];
+
+ data = data.Slice(sizeof(ulong));
+ }
+ }
+
+ Meta = new HipcMetadata()
+ {
+ Type = (int)header.Type,
+ SendStaticsCount = header.SendStaticsCount,
+ SendBuffersCount = header.SendBuffersCount,
+ ReceiveBuffersCount = header.ReceiveBuffersCount,
+ ExchangeBuffersCount = header.ExchangeBuffersCount,
+ DataWordsCount = header.DataWordsCount,
+ ReceiveStaticsCount = receiveStaticsCount,
+ SendPid = specialHeader.SendPid,
+ CopyHandlesCount = specialHeader.CopyHandlesCount,
+ MoveHandlesCount = specialHeader.MoveHandlesCount
+ };
+
+ Data = CreateMessageData(Meta, data, initialLength);
+ Pid = pid;
+ }
+
+ public static HipcMessageData WriteResponse(
+ Span<byte> destination,
+ int sendStaticCount,
+ int dataWordsCount,
+ int copyHandlesCount,
+ int moveHandlesCount)
+ {
+ return WriteMessage(destination, new HipcMetadata()
+ {
+ SendStaticsCount = sendStaticCount,
+ DataWordsCount = dataWordsCount,
+ CopyHandlesCount = copyHandlesCount,
+ MoveHandlesCount = moveHandlesCount
+ });
+ }
+
+ public static HipcMessageData WriteMessage(Span<byte> destination, HipcMetadata meta)
+ {
+ int initialLength = destination.Length;
+
+ bool hasSpecialHeader = meta.SendPid || meta.CopyHandlesCount != 0 || meta.MoveHandlesCount != 0;
+
+ MemoryMarshal.Cast<byte, Header>(destination)[0] = new Header()
+ {
+ Type = (CommandType)meta.Type,
+ SendStaticsCount = meta.SendStaticsCount,
+ SendBuffersCount = meta.SendBuffersCount,
+ ReceiveBuffersCount = meta.ReceiveBuffersCount,
+ ExchangeBuffersCount = meta.ExchangeBuffersCount,
+ DataWordsCount = meta.DataWordsCount,
+ ReceiveStaticMode = meta.ReceiveStaticsCount != 0 ? (meta.ReceiveStaticsCount != AutoReceiveStatic ? meta.ReceiveStaticsCount + 2 : 2) : 0,
+ HasSpecialHeader = hasSpecialHeader
+ };
+
+ destination = destination.Slice(Unsafe.SizeOf<Header>());
+
+ if (hasSpecialHeader)
+ {
+ MemoryMarshal.Cast<byte, SpecialHeader>(destination)[0] = new SpecialHeader()
+ {
+ SendPid = meta.SendPid,
+ CopyHandlesCount = meta.CopyHandlesCount,
+ MoveHandlesCount = meta.MoveHandlesCount
+ };
+
+ destination = destination.Slice(Unsafe.SizeOf<SpecialHeader>());
+
+ if (meta.SendPid)
+ {
+ destination = destination.Slice(sizeof(ulong));
+ }
+ }
+
+ return CreateMessageData(meta, destination, initialLength);
+ }
+
+ private static HipcMessageData CreateMessageData(HipcMetadata meta, Span<byte> data, int initialLength)
+ {
+ Span<int> copyHandles = Span<int>.Empty;
+
+ if (meta.CopyHandlesCount != 0)
+ {
+ copyHandles = MemoryMarshal.Cast<byte, int>(data).Slice(0, meta.CopyHandlesCount);
+
+ data = data.Slice(meta.CopyHandlesCount * sizeof(int));
+ }
+
+ Span<int> moveHandles = Span<int>.Empty;
+
+ if (meta.MoveHandlesCount != 0)
+ {
+ moveHandles = MemoryMarshal.Cast<byte, int>(data).Slice(0, meta.MoveHandlesCount);
+
+ data = data.Slice(meta.MoveHandlesCount * sizeof(int));
+ }
+
+ Span<HipcStaticDescriptor> sendStatics = Span<HipcStaticDescriptor>.Empty;
+
+ if (meta.SendStaticsCount != 0)
+ {
+ sendStatics = MemoryMarshal.Cast<byte, HipcStaticDescriptor>(data).Slice(0, meta.SendStaticsCount);
+
+ data = data.Slice(meta.SendStaticsCount * Unsafe.SizeOf<HipcStaticDescriptor>());
+ }
+
+ Span<HipcBufferDescriptor> sendBuffers = Span<HipcBufferDescriptor>.Empty;
+
+ if (meta.SendBuffersCount != 0)
+ {
+ sendBuffers = MemoryMarshal.Cast<byte, HipcBufferDescriptor>(data).Slice(0, meta.SendBuffersCount);
+
+ data = data.Slice(meta.SendBuffersCount * Unsafe.SizeOf<HipcBufferDescriptor>());
+ }
+
+ Span<HipcBufferDescriptor> receiveBuffers = Span<HipcBufferDescriptor>.Empty;
+
+ if (meta.ReceiveBuffersCount != 0)
+ {
+ receiveBuffers = MemoryMarshal.Cast<byte, HipcBufferDescriptor>(data).Slice(0, meta.ReceiveBuffersCount);
+
+ data = data.Slice(meta.ReceiveBuffersCount * Unsafe.SizeOf<HipcBufferDescriptor>());
+ }
+
+ Span<HipcBufferDescriptor> exchangeBuffers = Span<HipcBufferDescriptor>.Empty;
+
+ if (meta.ExchangeBuffersCount != 0)
+ {
+ exchangeBuffers = MemoryMarshal.Cast<byte, HipcBufferDescriptor>(data).Slice(0, meta.ExchangeBuffersCount);
+
+ data = data.Slice(meta.ExchangeBuffersCount * Unsafe.SizeOf<HipcBufferDescriptor>());
+ }
+
+ Span<uint> dataWords = Span<uint>.Empty;
+
+ if (meta.DataWordsCount != 0)
+ {
+ int dataOffset = initialLength - data.Length;
+ int dataOffsetAligned = BitUtils.AlignUp(dataOffset, 0x10);
+
+ int padding = (dataOffsetAligned - dataOffset) / sizeof(uint);
+
+ dataWords = MemoryMarshal.Cast<byte, uint>(data).Slice(padding, meta.DataWordsCount - padding);
+
+ data = data.Slice(meta.DataWordsCount * sizeof(uint));
+ }
+
+ Span<HipcReceiveListEntry> receiveList = Span<HipcReceiveListEntry>.Empty;
+
+ if (meta.ReceiveStaticsCount != 0)
+ {
+ int receiveListSize = meta.ReceiveStaticsCount == AutoReceiveStatic ? 1 : meta.ReceiveStaticsCount;
+
+ receiveList = MemoryMarshal.Cast<byte, HipcReceiveListEntry>(data).Slice(0, receiveListSize);
+ }
+
+ return new HipcMessageData()
+ {
+ SendStatics = sendStatics,
+ SendBuffers = sendBuffers,
+ ReceiveBuffers = receiveBuffers,
+ ExchangeBuffers = exchangeBuffers,
+ DataWords = dataWords,
+ ReceiveList = receiveList,
+ CopyHandles = copyHandles,
+ MoveHandles = moveHandles
+ };
+ }
+ }
+}
diff --git a/Ryujinx.Horizon/Sdk/Sf/Hipc/HipcMessageData.cs b/Ryujinx.Horizon/Sdk/Sf/Hipc/HipcMessageData.cs
new file mode 100644
index 00000000..c83c422c
--- /dev/null
+++ b/Ryujinx.Horizon/Sdk/Sf/Hipc/HipcMessageData.cs
@@ -0,0 +1,16 @@
+using System;
+
+namespace Ryujinx.Horizon.Sdk.Sf.Hipc
+{
+ ref struct HipcMessageData
+ {
+ public Span<HipcStaticDescriptor> SendStatics;
+ public Span<HipcBufferDescriptor> SendBuffers;
+ public Span<HipcBufferDescriptor> ReceiveBuffers;
+ public Span<HipcBufferDescriptor> ExchangeBuffers;
+ public Span<uint> DataWords;
+ public Span<HipcReceiveListEntry> ReceiveList;
+ public Span<int> CopyHandles;
+ public Span<int> MoveHandles;
+ }
+}
diff --git a/Ryujinx.Horizon/Sdk/Sf/Hipc/HipcMetadata.cs b/Ryujinx.Horizon/Sdk/Sf/Hipc/HipcMetadata.cs
new file mode 100644
index 00000000..fe13137a
--- /dev/null
+++ b/Ryujinx.Horizon/Sdk/Sf/Hipc/HipcMetadata.cs
@@ -0,0 +1,16 @@
+namespace Ryujinx.Horizon.Sdk.Sf.Hipc
+{
+ struct HipcMetadata
+ {
+ public int Type;
+ public int SendStaticsCount;
+ public int SendBuffersCount;
+ public int ReceiveBuffersCount;
+ public int ExchangeBuffersCount;
+ public int DataWordsCount;
+ public int ReceiveStaticsCount;
+ public bool SendPid;
+ public int CopyHandlesCount;
+ public int MoveHandlesCount;
+ }
+}
diff --git a/Ryujinx.Horizon/Sdk/Sf/Hipc/HipcReceiveListEntry.cs b/Ryujinx.Horizon/Sdk/Sf/Hipc/HipcReceiveListEntry.cs
new file mode 100644
index 00000000..94bf0968
--- /dev/null
+++ b/Ryujinx.Horizon/Sdk/Sf/Hipc/HipcReceiveListEntry.cs
@@ -0,0 +1,14 @@
+namespace Ryujinx.Horizon.Sdk.Sf.Hipc
+{
+ struct HipcReceiveListEntry
+ {
+ private uint _addressLow;
+ private uint _word1;
+
+ public HipcReceiveListEntry(ulong address, ulong size)
+ {
+ _addressLow = (uint)address;
+ _word1 = (ushort)(address >> 32) | (uint)(size << 16);
+ }
+ }
+}
diff --git a/Ryujinx.Horizon/Sdk/Sf/Hipc/HipcResult.cs b/Ryujinx.Horizon/Sdk/Sf/Hipc/HipcResult.cs
new file mode 100644
index 00000000..ef989a98
--- /dev/null
+++ b/Ryujinx.Horizon/Sdk/Sf/Hipc/HipcResult.cs
@@ -0,0 +1,22 @@
+using Ryujinx.Horizon.Common;
+
+namespace Ryujinx.Horizon.Sdk.Sf.Hipc
+{
+ static class HipcResult
+ {
+ public const int ModuleId = 11;
+
+ public static Result OutOfSessionMemory => new Result(ModuleId, 102);
+ public static Result OutOfSessions => new Result(ModuleId, 131);
+ public static Result PointerBufferTooSmall => new Result(ModuleId, 141);
+ public static Result OutOfDomains => new Result(ModuleId, 200);
+
+ public static Result InvalidRequestSize => new Result(ModuleId, 402);
+ public static Result UnknownCommandType => new Result(ModuleId, 403);
+
+ public static Result InvalidCmifRequest => new Result(ModuleId, 420);
+
+ public static Result TargetNotDomain => new Result(ModuleId, 491);
+ public static Result DomainObjectNotFound => new Result(ModuleId, 492);
+ }
+}
diff --git a/Ryujinx.Horizon/Sdk/Sf/Hipc/HipcStaticDescriptor.cs b/Ryujinx.Horizon/Sdk/Sf/Hipc/HipcStaticDescriptor.cs
new file mode 100644
index 00000000..5cebf47c
--- /dev/null
+++ b/Ryujinx.Horizon/Sdk/Sf/Hipc/HipcStaticDescriptor.cs
@@ -0,0 +1,22 @@
+namespace Ryujinx.Horizon.Sdk.Sf.Hipc
+{
+ struct HipcStaticDescriptor
+ {
+ private readonly ulong _data;
+
+ public ulong Address => ((((_data >> 2) & 0x70) | ((_data >> 12) & 0xf)) << 32) | (_data >> 32);
+ public ushort Size => (ushort)(_data >> 16);
+ public int ReceiveIndex => (int)(_data & 0xf);
+
+ public HipcStaticDescriptor(ulong address, ushort size, int receiveIndex)
+ {
+ ulong data = (uint)(receiveIndex & 0xf) | ((uint)size << 16);
+
+ data |= address << 32;
+ data |= (address >> 20) & 0xf000;
+ data |= (address >> 30) & 0xffc0;
+
+ _data = data;
+ }
+ }
+}
diff --git a/Ryujinx.Horizon/Sdk/Sf/Hipc/ManagerOptions.cs b/Ryujinx.Horizon/Sdk/Sf/Hipc/ManagerOptions.cs
new file mode 100644
index 00000000..e087cb22
--- /dev/null
+++ b/Ryujinx.Horizon/Sdk/Sf/Hipc/ManagerOptions.cs
@@ -0,0 +1,20 @@
+namespace Ryujinx.Horizon.Sdk.Sf.Hipc
+{
+ struct ManagerOptions
+ {
+ public static ManagerOptions Default => new ManagerOptions(0, 0, 0, false);
+
+ public int PointerBufferSize { get; }
+ public int MaxDomains { get; }
+ public int MaxDomainObjects { get; }
+ public bool CanDeferInvokeRequest { get; }
+
+ public ManagerOptions(int pointerBufferSize, int maxDomains, int maxDomainObjects, bool canDeferInvokeRequest)
+ {
+ PointerBufferSize = pointerBufferSize;
+ MaxDomains = maxDomains;
+ MaxDomainObjects = maxDomainObjects;
+ CanDeferInvokeRequest = canDeferInvokeRequest;
+ }
+ }
+}
diff --git a/Ryujinx.Horizon/Sdk/Sf/Hipc/ReceiveResult.cs b/Ryujinx.Horizon/Sdk/Sf/Hipc/ReceiveResult.cs
new file mode 100644
index 00000000..7c380a01
--- /dev/null
+++ b/Ryujinx.Horizon/Sdk/Sf/Hipc/ReceiveResult.cs
@@ -0,0 +1,9 @@
+namespace Ryujinx.Horizon.Sdk.Sf.Hipc
+{
+ enum ReceiveResult
+ {
+ Success,
+ Closed,
+ NeedsRetry
+ }
+}
diff --git a/Ryujinx.Horizon/Sdk/Sf/Hipc/Server.cs b/Ryujinx.Horizon/Sdk/Sf/Hipc/Server.cs
new file mode 100644
index 00000000..923f2d52
--- /dev/null
+++ b/Ryujinx.Horizon/Sdk/Sf/Hipc/Server.cs
@@ -0,0 +1,36 @@
+using Ryujinx.Horizon.Sdk.OsTypes;
+using Ryujinx.Horizon.Sdk.Sf.Cmif;
+using Ryujinx.Horizon.Sdk.Sm;
+
+namespace Ryujinx.Horizon.Sdk.Sf.Hipc
+{
+ class Server : MultiWaitHolderOfHandle
+ {
+ public int PortIndex { get; }
+ public int PortHandle { get; }
+ public ServiceName Name { get; }
+ public bool Managed { get; }
+ public ServiceObjectHolder StaticObject { get; }
+
+ public Server(
+ int portIndex,
+ int portHandle,
+ ServiceName name,
+ bool managed,
+ ServiceObjectHolder staticHoder) : base(portHandle)
+ {
+ PortHandle = portHandle;
+ Name = name;
+ Managed = managed;
+
+ if (staticHoder != null)
+ {
+ StaticObject = staticHoder;
+ }
+ else
+ {
+ PortIndex = portIndex;
+ }
+ }
+ }
+}
diff --git a/Ryujinx.Horizon/Sdk/Sf/Hipc/ServerDomainSessionManager.cs b/Ryujinx.Horizon/Sdk/Sf/Hipc/ServerDomainSessionManager.cs
new file mode 100644
index 00000000..d920a659
--- /dev/null
+++ b/Ryujinx.Horizon/Sdk/Sf/Hipc/ServerDomainSessionManager.cs
@@ -0,0 +1,23 @@
+using Ryujinx.Horizon.Common;
+using Ryujinx.Horizon.Sdk.Sf.Cmif;
+using System;
+
+namespace Ryujinx.Horizon.Sdk.Sf.Hipc
+{
+ class ServerDomainSessionManager : ServerSessionManager
+ {
+ public ServerDomainManager Domain { get; }
+
+ public ServerDomainSessionManager(int entryCount, int maxDomains)
+ {
+ Domain = new ServerDomainManager(entryCount, maxDomains);
+ }
+
+ protected override Result DispatchManagerRequest(ServerSession session, Span<byte> inMessage, Span<byte> outMessage)
+ {
+ HipcManager hipcManager = new HipcManager(this, session);
+
+ return DispatchRequest(new ServiceObjectHolder(hipcManager), session, inMessage, outMessage);
+ }
+ }
+}
diff --git a/Ryujinx.Horizon/Sdk/Sf/Hipc/ServerManager.cs b/Ryujinx.Horizon/Sdk/Sf/Hipc/ServerManager.cs
new file mode 100644
index 00000000..5bb2de25
--- /dev/null
+++ b/Ryujinx.Horizon/Sdk/Sf/Hipc/ServerManager.cs
@@ -0,0 +1,198 @@
+using Ryujinx.Horizon.Sdk.OsTypes;
+using Ryujinx.Horizon.Sdk.Sf.Cmif;
+using Ryujinx.Horizon.Sdk.Sm;
+using System;
+using System.Collections.Generic;
+using System.Numerics;
+
+namespace Ryujinx.Horizon.Sdk.Sf.Hipc
+{
+ class ServerManager : ServerManagerBase, IDisposable
+ {
+ private readonly SmApi _sm;
+ private readonly int _pointerBufferSize;
+ private readonly bool _canDeferInvokeRequest;
+ private readonly int _maxSessions;
+
+ private ulong _pointerBuffersBaseAddress;
+ private ulong _savedMessagesBaseAddress;
+
+ private readonly object _resourceLock;
+ private readonly ulong[] _sessionAllocationBitmap;
+ private readonly HashSet<ServerSession> _sessions;
+ private readonly HashSet<Server> _servers;
+
+ public ServerManager(HeapAllocator allocator, SmApi sm, int maxPorts, ManagerOptions options, int maxSessions) : base(sm, options)
+ {
+ _sm = sm;
+ _pointerBufferSize = options.PointerBufferSize;
+ _canDeferInvokeRequest = options.CanDeferInvokeRequest;
+ _maxSessions = maxSessions;
+
+ if (allocator != null)
+ {
+ _pointerBuffersBaseAddress = allocator.Allocate((ulong)maxSessions * (ulong)options.PointerBufferSize);
+
+ if (options.CanDeferInvokeRequest)
+ {
+ _savedMessagesBaseAddress = allocator.Allocate((ulong)maxSessions * (ulong)Api.TlsMessageBufferSize);
+ }
+ }
+
+ _resourceLock = new object();
+ _sessionAllocationBitmap = new ulong[(maxSessions + 63) / 64];
+ _sessions = new HashSet<ServerSession>();
+ _servers = new HashSet<Server>();
+ }
+
+ private PointerAndSize GetObjectBySessionIndex(ServerSession session, ulong baseAddress, ulong size)
+ {
+ return new PointerAndSize(baseAddress + (ulong)session.SessionIndex * size, size);
+ }
+
+ protected override ServerSession AllocateSession(int sessionHandle, ServiceObjectHolder obj)
+ {
+ int sessionIndex = -1;
+
+ lock (_resourceLock)
+ {
+ if (_sessions.Count >= _maxSessions)
+ {
+ return null;
+ }
+
+ for (int i = 0; i <_sessionAllocationBitmap.Length; i++)
+ {
+ ref ulong mask = ref _sessionAllocationBitmap[i];
+
+ if (mask != ulong.MaxValue)
+ {
+ int bit = BitOperations.TrailingZeroCount(~mask);
+ sessionIndex = i * 64 + bit;
+ mask |= 1UL << bit;
+
+ break;
+ }
+ }
+
+ if (sessionIndex == -1)
+ {
+ return null;
+ }
+
+ ServerSession session = new ServerSession(sessionIndex, sessionHandle, obj);
+
+ _sessions.Add(session);
+
+ return session;
+ }
+ }
+
+ protected override void FreeSession(ServerSession session)
+ {
+ if (session.ServiceObjectHolder.ServiceObject is IDisposable disposableObj)
+ {
+ disposableObj.Dispose();
+ }
+
+ lock (_resourceLock)
+ {
+ _sessionAllocationBitmap[session.SessionIndex / 64] &= ~(1UL << (session.SessionIndex & 63));
+ _sessions.Remove(session);
+ }
+ }
+
+ protected override Server AllocateServer(
+ int portIndex,
+ int portHandle,
+ ServiceName name,
+ bool managed,
+ ServiceObjectHolder staticHoder)
+ {
+ lock (_resourceLock)
+ {
+ Server server = new Server(portIndex, portHandle, name, managed, staticHoder);
+
+ _servers.Add(server);
+
+ return server;
+ }
+ }
+
+ protected override void DestroyServer(Server server)
+ {
+ lock (_resourceLock)
+ {
+ server.UnlinkFromMultiWaitHolder();
+ Os.FinalizeMultiWaitHolder(server);
+
+ if (server.Managed)
+ {
+ // We should AbortOnFailure, but sometimes SM is already gone when this is called,
+ // so let's just ignore potential errors.
+ _sm.UnregisterService(server.Name);
+
+ HorizonStatic.Syscall.CloseHandle(server.PortHandle);
+ }
+
+ _servers.Remove(server);
+ }
+ }
+
+ protected override PointerAndSize GetSessionPointerBuffer(ServerSession session)
+ {
+ if (_pointerBufferSize > 0)
+ {
+ return GetObjectBySessionIndex(session, _pointerBuffersBaseAddress, (ulong)_pointerBufferSize);
+ }
+ else
+ {
+ return PointerAndSize.Empty;
+ }
+ }
+
+ protected override PointerAndSize GetSessionSavedMessageBuffer(ServerSession session)
+ {
+ if (_canDeferInvokeRequest)
+ {
+ return GetObjectBySessionIndex(session, _savedMessagesBaseAddress, Api.TlsMessageBufferSize);
+ }
+ else
+ {
+ return PointerAndSize.Empty;
+ }
+ }
+
+ protected virtual void Dispose(bool disposing)
+ {
+ if (disposing)
+ {
+ lock (_resourceLock)
+ {
+ ServerSession[] sessionsToClose = new ServerSession[_sessions.Count];
+
+ _sessions.CopyTo(sessionsToClose);
+
+ foreach (ServerSession session in sessionsToClose)
+ {
+ CloseSessionImpl(session);
+ }
+
+ Server[] serversToClose = new Server[_servers.Count];
+
+ _servers.CopyTo(serversToClose);
+
+ foreach (Server server in serversToClose)
+ {
+ DestroyServer(server);
+ }
+ }
+ }
+ }
+
+ public void Dispose()
+ {
+ Dispose(true);
+ }
+ }
+}
diff --git a/Ryujinx.Horizon/Sdk/Sf/Hipc/ServerManagerBase.cs b/Ryujinx.Horizon/Sdk/Sf/Hipc/ServerManagerBase.cs
new file mode 100644
index 00000000..68cae6bc
--- /dev/null
+++ b/Ryujinx.Horizon/Sdk/Sf/Hipc/ServerManagerBase.cs
@@ -0,0 +1,307 @@
+using Ryujinx.Horizon.Sdk.OsTypes;
+using Ryujinx.Horizon.Common;
+using Ryujinx.Horizon.Sdk.Sf.Cmif;
+using Ryujinx.Horizon.Sdk.Sm;
+using System;
+
+namespace Ryujinx.Horizon.Sdk.Sf.Hipc
+{
+ class ServerManagerBase : ServerDomainSessionManager
+ {
+ private readonly SmApi _sm;
+
+ private bool _canDeferInvokeRequest;
+
+ private readonly MultiWait _multiWait;
+ private readonly MultiWait _waitList;
+
+ private readonly object _multiWaitSelectionLock;
+ private readonly object _waitListLock;
+
+ private readonly Event _requestStopEvent;
+ private readonly Event _notifyEvent;
+
+ private readonly MultiWaitHolderBase _requestStopEventHolder;
+ private readonly MultiWaitHolderBase _notifyEventHolder;
+
+ private enum UserDataTag
+ {
+ Server = 1,
+ Session = 2
+ }
+
+ public ServerManagerBase(SmApi sm, ManagerOptions options) : base(options.MaxDomainObjects, options.MaxDomains)
+ {
+ _sm = sm;
+ _canDeferInvokeRequest = options.CanDeferInvokeRequest;
+
+ _multiWait = new MultiWait();
+ _waitList = new MultiWait();
+
+ _multiWaitSelectionLock = new object();
+ _waitListLock = new object();
+
+ _requestStopEvent = new Event(EventClearMode.ManualClear);
+ _notifyEvent = new Event(EventClearMode.ManualClear);
+
+ _requestStopEventHolder = new MultiWaitHolderOfEvent(_requestStopEvent);
+ _multiWait.LinkMultiWaitHolder(_requestStopEventHolder);
+ _notifyEventHolder = new MultiWaitHolderOfEvent(_notifyEvent);
+ _multiWait.LinkMultiWaitHolder(_notifyEventHolder);
+ }
+
+ public void RegisterObjectForServer(IServiceObject staticObject, int portHandle)
+ {
+ RegisterServerImpl(0, new ServiceObjectHolder(staticObject), portHandle);
+ }
+
+ public Result RegisterObjectForServer(IServiceObject staticObject, ServiceName name, int maxSessions)
+ {
+ return RegisterServerImpl(0, new ServiceObjectHolder(staticObject), name, maxSessions);
+ }
+
+ public void RegisterServer(int portIndex, int portHandle)
+ {
+ RegisterServerImpl(portIndex, null, portHandle);
+ }
+
+ public Result RegisterServer(int portIndex, ServiceName name, int maxSessions)
+ {
+ return RegisterServerImpl(portIndex, null, name, maxSessions);
+ }
+
+ private void RegisterServerImpl(int portIndex, ServiceObjectHolder staticHolder, int portHandle)
+ {
+ Server server = AllocateServer(portIndex, portHandle, ServiceName.Invalid, managed: false, staticHolder);
+ RegisterServerImpl(server);
+ }
+
+ private Result RegisterServerImpl(int portIndex, ServiceObjectHolder staticHolder, ServiceName name, int maxSessions)
+ {
+ Result result = _sm.RegisterService(out int portHandle, name, maxSessions, isLight: false);
+
+ if (result.IsFailure)
+ {
+ return result;
+ }
+
+ Server server = AllocateServer(portIndex, portHandle, name, managed: true, staticHolder);
+ RegisterServerImpl(server);
+
+ return Result.Success;
+ }
+
+ private void RegisterServerImpl(Server server)
+ {
+ server.UserData = UserDataTag.Server;
+
+ _multiWait.LinkMultiWaitHolder(server);
+ }
+
+ protected virtual Result OnNeedsToAccept(int portIndex, Server server)
+ {
+ throw new NotSupportedException();
+ }
+
+ public void ServiceRequests()
+ {
+ while (WaitAndProcessRequestsImpl());
+ }
+
+ public void WaitAndProcessRequests()
+ {
+ WaitAndProcessRequestsImpl();
+ }
+
+ private bool WaitAndProcessRequestsImpl()
+ {
+ try
+ {
+ MultiWaitHolder multiWait = WaitSignaled();
+
+ if (multiWait == null)
+ {
+ return false;
+ }
+
+ DebugUtil.Assert(Process(multiWait).IsSuccess);
+
+ return HorizonStatic.ThreadContext.Running;
+ }
+ catch (ThreadTerminatedException)
+ {
+ return false;
+ }
+ }
+
+ private MultiWaitHolder WaitSignaled()
+ {
+ lock (_multiWaitSelectionLock)
+ {
+ while (true)
+ {
+ ProcessWaitList();
+
+ MultiWaitHolder selected = _multiWait.WaitAny();
+
+ if (selected == _requestStopEventHolder)
+ {
+ return null;
+ }
+ else if (selected == _notifyEventHolder)
+ {
+ _notifyEvent.Clear();
+ }
+ else
+ {
+ selected.UnlinkFromMultiWaitHolder();
+
+ return selected;
+ }
+ }
+ }
+ }
+
+ public void ResumeProcessing()
+ {
+ _requestStopEvent.Clear();
+ }
+
+ public void RequestStopProcessing()
+ {
+ _requestStopEvent.Signal();
+ }
+
+ protected override void RegisterSessionToWaitList(ServerSession session)
+ {
+ session.HasReceived = false;
+ session.UserData = UserDataTag.Session;
+ RegisterToWaitList(session);
+ }
+
+ private void RegisterToWaitList(MultiWaitHolder holder)
+ {
+ lock (_waitListLock)
+ {
+ _waitList.LinkMultiWaitHolder(holder);
+ _notifyEvent.Signal();
+ }
+ }
+
+ private void ProcessWaitList()
+ {
+ lock (_waitListLock)
+ {
+ _multiWait.MoveAllFrom(_waitList);
+ }
+ }
+
+ private Result Process(MultiWaitHolder holder)
+ {
+ switch ((UserDataTag)holder.UserData)
+ {
+ case UserDataTag.Server:
+ return ProcessForServer(holder);
+ case UserDataTag.Session:
+ return ProcessForSession(holder);
+ default:
+ throw new NotImplementedException(((UserDataTag)holder.UserData).ToString());
+ }
+ }
+
+ private Result ProcessForServer(MultiWaitHolder holder)
+ {
+ DebugUtil.Assert((UserDataTag)holder.UserData == UserDataTag.Server);
+
+ Server server = (Server)holder;
+
+ try
+ {
+ if (server.StaticObject != null)
+ {
+ return AcceptSession(server.PortHandle, server.StaticObject.Clone());
+ }
+ else
+ {
+ return OnNeedsToAccept(server.PortIndex, server);
+ }
+ }
+ finally
+ {
+ RegisterToWaitList(server);
+ }
+ }
+
+ private Result ProcessForSession(MultiWaitHolder holder)
+ {
+ DebugUtil.Assert((UserDataTag)holder.UserData == UserDataTag.Session);
+
+ ServerSession session = (ServerSession)holder;
+
+ using var tlsMessage = HorizonStatic.AddressSpace.GetWritableRegion(HorizonStatic.ThreadContext.TlsAddress, Api.TlsMessageBufferSize);
+
+ Result result;
+
+ if (_canDeferInvokeRequest)
+ {
+ // If the request is deferred, we save the message on a temporary buffer to process it later.
+ using var savedMessage = HorizonStatic.AddressSpace.GetWritableRegion(session.SavedMessage.Address, (int)session.SavedMessage.Size);
+
+ DebugUtil.Assert(tlsMessage.Memory.Length == savedMessage.Memory.Length);
+
+ if (!session.HasReceived)
+ {
+ result = ReceiveRequest(session, tlsMessage.Memory.Span);
+
+ if (result.IsFailure)
+ {
+ return result;
+ }
+
+ session.HasReceived = true;
+ tlsMessage.Memory.Span.CopyTo(savedMessage.Memory.Span);
+ }
+ else
+ {
+ savedMessage.Memory.Span.CopyTo(tlsMessage.Memory.Span);
+ }
+
+ result = ProcessRequest(session, tlsMessage.Memory.Span);
+
+ if (result.IsFailure && !SfResult.Invalidated(result))
+ {
+ return result;
+ }
+ }
+ else
+ {
+ if (!session.HasReceived)
+ {
+ result = ReceiveRequest(session, tlsMessage.Memory.Span);
+
+ if (result.IsFailure)
+ {
+ return result;
+ }
+
+ session.HasReceived = true;
+ }
+
+ result = ProcessRequest(session, tlsMessage.Memory.Span);
+
+ if (result.IsFailure)
+ {
+ // Those results are not valid because the service does not support deferral.
+ if (SfResult.RequestDeferred(result) || SfResult.Invalidated(result))
+ {
+ result.AbortOnFailure();
+ }
+
+ return result;
+ }
+ }
+
+ return Result.Success;
+ }
+ }
+}
diff --git a/Ryujinx.Horizon/Sdk/Sf/Hipc/ServerSession.cs b/Ryujinx.Horizon/Sdk/Sf/Hipc/ServerSession.cs
new file mode 100644
index 00000000..eb98fefd
--- /dev/null
+++ b/Ryujinx.Horizon/Sdk/Sf/Hipc/ServerSession.cs
@@ -0,0 +1,23 @@
+using Ryujinx.Horizon.Sdk.OsTypes;
+using Ryujinx.Horizon.Sdk.Sf.Cmif;
+
+namespace Ryujinx.Horizon.Sdk.Sf.Hipc
+{
+ class ServerSession : MultiWaitHolderOfHandle
+ {
+ public ServiceObjectHolder ServiceObjectHolder { get; set; }
+ public PointerAndSize PointerBuffer { get; set; }
+ public PointerAndSize SavedMessage { get; set; }
+ public int SessionIndex { get; }
+ public int SessionHandle { get; }
+ public bool IsClosed { get; set; }
+ public bool HasReceived { get; set; }
+
+ public ServerSession(int index, int handle, ServiceObjectHolder obj) : base(handle)
+ {
+ ServiceObjectHolder = obj;
+ SessionIndex = index;
+ SessionHandle = handle;
+ }
+ }
+}
diff --git a/Ryujinx.Horizon/Sdk/Sf/Hipc/ServerSessionManager.cs b/Ryujinx.Horizon/Sdk/Sf/Hipc/ServerSessionManager.cs
new file mode 100644
index 00000000..e85892f2
--- /dev/null
+++ b/Ryujinx.Horizon/Sdk/Sf/Hipc/ServerSessionManager.cs
@@ -0,0 +1,335 @@
+using Ryujinx.Common.Logging;
+using Ryujinx.Horizon.Common;
+using Ryujinx.Horizon.Sdk.OsTypes;
+using Ryujinx.Horizon.Sdk.Sf.Cmif;
+using Ryujinx.Horizon.Sdk.Sm;
+using System;
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.Horizon.Sdk.Sf.Hipc
+{
+ class ServerSessionManager
+ {
+ public Result AcceptSession(int portHandle, ServiceObjectHolder obj)
+ {
+ return AcceptSession(out _, portHandle, obj);
+ }
+
+ private Result AcceptSession(out ServerSession session, int portHandle, ServiceObjectHolder obj)
+ {
+ return AcceptSessionImpl(out session, portHandle, obj);
+ }
+
+ private Result AcceptSessionImpl(out ServerSession session, int portHandle, ServiceObjectHolder obj)
+ {
+ session = null;
+
+ Result result = HorizonStatic.Syscall.AcceptSession(out int sessionHandle, portHandle);
+
+ if (result.IsFailure)
+ {
+ return result;
+ }
+
+ bool succeeded = false;
+
+ try
+ {
+ result = RegisterSessionImpl(out session, sessionHandle, obj);
+
+ if (result.IsFailure)
+ {
+ return result;
+ }
+
+ succeeded = true;
+ }
+ finally
+ {
+ if (!succeeded)
+ {
+ HorizonStatic.Syscall.CloseHandle(sessionHandle);
+ }
+ }
+
+ return Result.Success;
+ }
+
+ public Result RegisterSession(int sessionHandle, ServiceObjectHolder obj)
+ {
+ return RegisterSession(out _, sessionHandle, obj);
+ }
+
+ public Result RegisterSession(out ServerSession session, int sessionHandle, ServiceObjectHolder obj)
+ {
+ return RegisterSessionImpl(out session, sessionHandle, obj);
+ }
+
+ private Result RegisterSessionImpl(out ServerSession session, int sessionHandle, ServiceObjectHolder obj)
+ {
+ Result result = CreateSessionImpl(out session, sessionHandle, obj);
+
+ if (result.IsFailure)
+ {
+ return result;
+ }
+
+ session.PointerBuffer = GetSessionPointerBuffer(session);
+ session.SavedMessage = GetSessionSavedMessageBuffer(session);
+
+ RegisterSessionToWaitList(session);
+ return Result.Success;
+ }
+
+ protected virtual void RegisterSessionToWaitList(ServerSession session)
+ {
+ throw new NotSupportedException();
+ }
+
+ private Result CreateSessionImpl(out ServerSession session, int sessionHandle, ServiceObjectHolder obj)
+ {
+ session = AllocateSession(sessionHandle, obj);
+
+ if (session == null)
+ {
+ return HipcResult.OutOfSessionMemory;
+ }
+
+ return Result.Success;
+ }
+
+ protected virtual ServerSession AllocateSession(int sessionHandle, ServiceObjectHolder obj)
+ {
+ throw new NotSupportedException();
+ }
+
+ protected virtual void FreeSession(ServerSession session)
+ {
+ throw new NotSupportedException();
+ }
+
+ protected virtual Server AllocateServer(
+ int portIndex,
+ int portHandle,
+ ServiceName name,
+ bool managed,
+ ServiceObjectHolder staticHoder)
+ {
+ throw new NotSupportedException();
+ }
+
+ protected virtual void DestroyServer(Server server)
+ {
+ throw new NotSupportedException();
+ }
+
+ protected virtual PointerAndSize GetSessionPointerBuffer(ServerSession session)
+ {
+ throw new NotSupportedException();
+ }
+
+ protected virtual PointerAndSize GetSessionSavedMessageBuffer(ServerSession session)
+ {
+ throw new NotSupportedException();
+ }
+
+ private void DestroySession(ServerSession session)
+ {
+ FreeSession(session);
+ }
+
+ protected void CloseSessionImpl(ServerSession session)
+ {
+ int sessionHandle = session.Handle;
+ Os.FinalizeMultiWaitHolder(session);
+ DestroySession(session);
+ HorizonStatic.Syscall.CloseHandle(sessionHandle).AbortOnFailure();
+ }
+
+ private static CommandType GetCmifCommandType(ReadOnlySpan<byte> message)
+ {
+ return MemoryMarshal.Cast<byte, Header>(message)[0].Type;
+ }
+
+ public Result ProcessRequest(ServerSession session, Span<byte> message)
+ {
+ if (session.IsClosed || GetCmifCommandType(message) == CommandType.Close)
+ {
+ CloseSessionImpl(session);
+ return Result.Success;
+ }
+ else
+ {
+ Result result = ProcessRequestImpl(session, message, message);
+
+ if (result.IsSuccess)
+ {
+ RegisterSessionToWaitList(session);
+ return Result.Success;
+ }
+ else if (SfResult.RequestContextChanged(result))
+ {
+ return result;
+ }
+ else
+ {
+ Logger.Warning?.Print(LogClass.KernelIpc, $"Request processing returned error {result}");
+
+ CloseSessionImpl(session);
+ return Result.Success;
+ }
+ }
+ }
+
+ private Result ProcessRequestImpl(ServerSession session, Span<byte> inMessage, Span<byte> outMessage)
+ {
+ CommandType commandType = GetCmifCommandType(inMessage);
+
+ using var _ = new ScopedInlineContextChange(GetInlineContext(commandType, inMessage));
+
+ switch (commandType)
+ {
+ case CommandType.Request:
+ case CommandType.RequestWithContext:
+ return DispatchRequest(session.ServiceObjectHolder, session, inMessage, outMessage);
+ case CommandType.Control:
+ case CommandType.ControlWithContext:
+ return DispatchManagerRequest(session, inMessage, outMessage);
+ default:
+ return HipcResult.UnknownCommandType;
+ }
+ }
+
+ private static int GetInlineContext(CommandType commandType, ReadOnlySpan<byte> inMessage)
+ {
+ switch (commandType)
+ {
+ case CommandType.RequestWithContext:
+ case CommandType.ControlWithContext:
+ if (inMessage.Length >= 0x10)
+ {
+ return MemoryMarshal.Cast<byte, int>(inMessage)[3];
+ }
+ break;
+ }
+
+ return 0;
+ }
+
+ protected Result ReceiveRequest(ServerSession session, Span<byte> message)
+ {
+ return ReceiveRequestImpl(session, message);
+ }
+
+ private Result ReceiveRequestImpl(ServerSession session, Span<byte> message)
+ {
+ PointerAndSize pointerBuffer = session.PointerBuffer;
+
+ while (true)
+ {
+ if (pointerBuffer.Address != 0)
+ {
+ HipcMessageData messageData = HipcMessage.WriteMessage(message, new HipcMetadata()
+ {
+ Type = (int)CommandType.Invalid,
+ ReceiveStaticsCount = HipcMessage.AutoReceiveStatic
+ });
+
+ messageData.ReceiveList[0] = new HipcReceiveListEntry(pointerBuffer.Address, pointerBuffer.Size);
+ }
+ else
+ {
+ MemoryMarshal.Cast<byte, Header>(message)[0] = new Header()
+ {
+ Type = CommandType.Invalid
+ };
+ }
+
+ Result result = Api.Receive(out ReceiveResult recvResult, session.Handle, message);
+
+ if (result.IsFailure)
+ {
+ return result;
+ }
+
+ switch (recvResult)
+ {
+ case ReceiveResult.Success:
+ session.IsClosed = false;
+ return Result.Success;
+ case ReceiveResult.Closed:
+ session.IsClosed = true;
+ return Result.Success;
+ }
+ }
+ }
+
+ protected virtual Result DispatchManagerRequest(ServerSession session, Span<byte> inMessage, Span<byte> outMessage)
+ {
+ return SfResult.NotSupported;
+ }
+
+ protected virtual Result DispatchRequest(
+ ServiceObjectHolder objectHolder,
+ ServerSession session,
+ Span<byte> inMessage,
+ Span<byte> outMessage)
+ {
+ HipcMessage request;
+
+ try
+ {
+ request = new HipcMessage(inMessage);
+ }
+ catch (ArgumentOutOfRangeException)
+ {
+ return HipcResult.InvalidRequestSize;
+ }
+
+ var dispatchCtx = new ServiceDispatchContext()
+ {
+ ServiceObject = objectHolder.ServiceObject,
+ Manager = this,
+ Session = session,
+ HandlesToClose = new HandlesToClose(),
+ PointerBuffer = session.PointerBuffer,
+ InMessageBuffer = inMessage,
+ OutMessageBuffer = outMessage,
+ Request = request
+ };
+
+ ReadOnlySpan<byte> inRawData = MemoryMarshal.Cast<uint, byte>(dispatchCtx.Request.Data.DataWords);
+
+ int inRawSize = dispatchCtx.Request.Meta.DataWordsCount * sizeof(uint);
+
+ if (inRawSize < 0x10)
+ {
+ return HipcResult.InvalidRequestSize;
+ }
+
+ Result result = objectHolder.ProcessMessage(ref dispatchCtx, inRawData);
+
+ if (result.IsFailure)
+ {
+ return result;
+ }
+
+ result = Api.Reply(session.SessionHandle, outMessage);
+
+ ref var handlesToClose = ref dispatchCtx.HandlesToClose;
+
+ for (int i = 0; i < handlesToClose.Count; i++)
+ {
+ HorizonStatic.Syscall.CloseHandle(handlesToClose[i]).AbortOnFailure();
+ }
+
+ return result;
+ }
+
+ public ServerSessionManager GetSessionManagerByTag(uint tag)
+ {
+ // Official FW does not do anything with the tag currently.
+ return this;
+ }
+ }
+}
diff --git a/Ryujinx.Horizon/Sdk/Sf/Hipc/SpecialHeader.cs b/Ryujinx.Horizon/Sdk/Sf/Hipc/SpecialHeader.cs
new file mode 100644
index 00000000..8b747626
--- /dev/null
+++ b/Ryujinx.Horizon/Sdk/Sf/Hipc/SpecialHeader.cs
@@ -0,0 +1,27 @@
+using Ryujinx.Common.Utilities;
+
+namespace Ryujinx.Horizon.Sdk.Sf.Hipc
+{
+ struct SpecialHeader
+ {
+ private uint _word;
+
+ public bool SendPid
+ {
+ get => _word.Extract(0);
+ set => _word = _word.Insert(0, value);
+ }
+
+ public int CopyHandlesCount
+ {
+ get => (int)_word.Extract(1, 4);
+ set => _word = _word.Insert(1, 4, (uint)value);
+ }
+
+ public int MoveHandlesCount
+ {
+ get => (int)_word.Extract(5, 4);
+ set => _word = _word.Insert(5, 4, (uint)value);
+ }
+ }
+}
diff --git a/Ryujinx.Horizon/Sdk/Sf/HipcCommandProcessor.cs b/Ryujinx.Horizon/Sdk/Sf/HipcCommandProcessor.cs
new file mode 100644
index 00000000..53202ede
--- /dev/null
+++ b/Ryujinx.Horizon/Sdk/Sf/HipcCommandProcessor.cs
@@ -0,0 +1,421 @@
+using Ryujinx.Common;
+using Ryujinx.Horizon.Common;
+using Ryujinx.Horizon.Sdk.Sf.Cmif;
+using Ryujinx.Horizon.Sdk.Sf.Hipc;
+using System;
+using System.Linq;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.Horizon.Sdk.Sf
+{
+ class HipcCommandProcessor : ServerMessageProcessor
+ {
+ private readonly CommandArg[] _args;
+
+ private readonly int[] _inOffsets;
+ private readonly int[] _outOffsets;
+ private readonly PointerAndSize[] _bufferRanges;
+
+ private readonly bool _hasInProcessIdHolder;
+ private readonly int _inObjectsCount;
+ private readonly int _outObjectsCount;
+ private readonly int _inMapAliasBuffersCount;
+ private readonly int _outMapAliasBuffersCount;
+ private readonly int _inPointerBuffersCount;
+ private readonly int _outPointerBuffersCount;
+ private readonly int _outFixedSizePointerBuffersCount;
+ private readonly int _inMoveHandlesCount;
+ private readonly int _inCopyHandlesCount;
+ private readonly int _outMoveHandlesCount;
+ private readonly int _outCopyHandlesCount;
+
+ public int FunctionArgumentsCount => _args.Length;
+
+ public int InRawDataSize => BitUtils.AlignUp(_inOffsets[^1], sizeof(ushort));
+ public int OutRawDataSize => BitUtils.AlignUp(_outOffsets[^1], sizeof(uint));
+
+ private int OutUnfixedSizePointerBuffersCount => _outPointerBuffersCount - _outFixedSizePointerBuffersCount;
+
+ public HipcCommandProcessor(CommandArg[] args)
+ {
+ _args = args;
+
+ for (int i = 0; i < args.Length; i++)
+ {
+ var argInfo = args[i];
+
+ switch (argInfo.Type)
+ {
+ case CommandArgType.Buffer:
+ var flags = argInfo.BufferFlags;
+
+ if (flags.HasFlag(HipcBufferFlags.In))
+ {
+ if (flags.HasFlag(HipcBufferFlags.AutoSelect))
+ {
+ _inMapAliasBuffersCount++;
+ _inPointerBuffersCount++;
+ }
+ else if (flags.HasFlag(HipcBufferFlags.MapAlias))
+ {
+ _inMapAliasBuffersCount++;
+ }
+ else if (flags.HasFlag(HipcBufferFlags.Pointer))
+ {
+ _inPointerBuffersCount++;
+ }
+ }
+ else
+ {
+ bool autoSelect = flags.HasFlag(HipcBufferFlags.AutoSelect);
+ if (autoSelect || flags.HasFlag(HipcBufferFlags.Pointer))
+ {
+ _outPointerBuffersCount++;
+
+ if (flags.HasFlag(HipcBufferFlags.FixedSize))
+ {
+ _outFixedSizePointerBuffersCount++;
+ }
+ }
+
+ if (autoSelect || flags.HasFlag(HipcBufferFlags.MapAlias))
+ {
+ _outMapAliasBuffersCount++;
+ }
+ }
+ break;
+ case CommandArgType.InCopyHandle:
+ _inCopyHandlesCount++;
+ break;
+ case CommandArgType.InMoveHandle:
+ _inMoveHandlesCount++;
+ break;
+ case CommandArgType.InObject:
+ _inObjectsCount++;
+ break;
+ case CommandArgType.ProcessId:
+ _hasInProcessIdHolder = true;
+ break;
+ case CommandArgType.OutCopyHandle:
+ _outCopyHandlesCount++;
+ break;
+ case CommandArgType.OutMoveHandle:
+ _outMoveHandlesCount++;
+ break;
+ case CommandArgType.OutObject:
+ _outObjectsCount++;
+ break;
+ }
+ }
+
+ _inOffsets = RawDataOffsetCalculator.Calculate(args.Where(x => x.Type == CommandArgType.InArgument).ToArray());
+ _outOffsets = RawDataOffsetCalculator.Calculate(args.Where(x => x.Type == CommandArgType.OutArgument).ToArray());
+ _bufferRanges = new PointerAndSize[args.Length];
+ }
+
+ public int GetInArgOffset(int argIndex)
+ {
+ return _inOffsets[argIndex];
+ }
+
+ public int GetOutArgOffset(int argIndex)
+ {
+ return _outOffsets[argIndex];
+ }
+
+ public PointerAndSize GetBufferRange(int argIndex)
+ {
+ return _bufferRanges[argIndex];
+ }
+
+ public Result ProcessBuffers(ref ServiceDispatchContext context, bool[] isBufferMapAlias, ServerMessageRuntimeMetadata runtimeMetadata)
+ {
+ bool mapAliasBuffersValid = true;
+
+ ulong pointerBufferTail = context.PointerBuffer.Address;
+ ulong pointerBufferHead = pointerBufferTail + context.PointerBuffer.Size;
+
+ int sendMapAliasIndex = 0;
+ int recvMapAliasIndex = 0;
+ int sendPointerIndex = 0;
+ int unfixedRecvPointerIndex = 0;
+
+ for (int i = 0; i < _args.Length; i++)
+ {
+ if (_args[i].Type != CommandArgType.Buffer)
+ {
+ continue;
+ }
+
+ var flags = _args[i].BufferFlags;
+ bool isMapAlias;
+
+ if (flags.HasFlag(HipcBufferFlags.MapAlias))
+ {
+ isMapAlias = true;
+ }
+ else if (flags.HasFlag(HipcBufferFlags.Pointer))
+ {
+ isMapAlias = false;
+ }
+ else /* if (flags.HasFlag(HipcBufferFlags.HipcAutoSelect)) */
+ {
+ var descriptor = flags.HasFlag(HipcBufferFlags.In)
+ ? context.Request.Data.SendBuffers[sendMapAliasIndex]
+ : context.Request.Data.ReceiveBuffers[recvMapAliasIndex];
+
+ isMapAlias = descriptor.Address != 0UL;
+ }
+
+ isBufferMapAlias[i] = isMapAlias;
+
+ if (isMapAlias)
+ {
+ var descriptor = flags.HasFlag(HipcBufferFlags.In)
+ ? context.Request.Data.SendBuffers[sendMapAliasIndex++]
+ : context.Request.Data.ReceiveBuffers[recvMapAliasIndex++];
+
+ _bufferRanges[i] = new PointerAndSize(descriptor.Address, descriptor.Size);
+
+ if (!IsMapTransferModeValid(flags, descriptor.Mode))
+ {
+ mapAliasBuffersValid = false;
+ }
+ }
+ else
+ {
+ if (flags.HasFlag(HipcBufferFlags.In))
+ {
+ var descriptor = context.Request.Data.SendStatics[sendPointerIndex++];
+ ulong address = descriptor.Address;
+ ulong size = descriptor.Size;
+ _bufferRanges[i] = new PointerAndSize(address, size);
+
+ if (size != 0)
+ {
+ pointerBufferTail = Math.Max(pointerBufferTail, address + size);
+ }
+ }
+ else /* if (flags.HasFlag(HipcBufferFlags.Out)) */
+ {
+ ulong size;
+
+ if (flags.HasFlag(HipcBufferFlags.FixedSize))
+ {
+ size = _args[i].BufferFixedSize;
+ }
+ else
+ {
+ var data = MemoryMarshal.Cast<uint, byte>(context.Request.Data.DataWords);
+ var recvPointerSizes = MemoryMarshal.Cast<byte, ushort>(data.Slice(runtimeMetadata.UnfixedOutPointerSizeOffset));
+ size = recvPointerSizes[unfixedRecvPointerIndex++];
+ }
+
+ pointerBufferHead = BitUtils.AlignDown(pointerBufferHead - size, 0x10UL);
+ _bufferRanges[i] = new PointerAndSize(pointerBufferHead, size);
+ }
+ }
+ }
+
+ if (!mapAliasBuffersValid)
+ {
+ return HipcResult.InvalidCmifRequest;
+ }
+
+ if (_outPointerBuffersCount != 0 && pointerBufferTail > pointerBufferHead)
+ {
+ return HipcResult.PointerBufferTooSmall;
+ }
+
+ return Result.Success;
+ }
+
+ private static bool IsMapTransferModeValid(HipcBufferFlags flags, HipcBufferMode mode)
+ {
+ if (flags.HasFlag(HipcBufferFlags.MapTransferAllowsNonSecure))
+ {
+ return mode == HipcBufferMode.NonSecure;
+ }
+ else if (flags.HasFlag(HipcBufferFlags.MapTransferAllowsNonDevice))
+ {
+ return mode == HipcBufferMode.NonDevice;
+ }
+ else
+ {
+ return mode == HipcBufferMode.Normal;
+ }
+ }
+
+ public void SetOutBuffers(HipcMessageData response, bool[] isBufferMapAlias)
+ {
+ int recvPointerIndex = 0;
+
+ for (int i = 0; i < _args.Length; i++)
+ {
+ if (_args[i].Type != CommandArgType.Buffer)
+ {
+ continue;
+ }
+
+ var flags = _args[i].BufferFlags;
+ if (flags.HasFlag(HipcBufferFlags.Out))
+ {
+ var buffer = _bufferRanges[i];
+
+ if (flags.HasFlag(HipcBufferFlags.Pointer))
+ {
+ response.SendStatics[recvPointerIndex] = new HipcStaticDescriptor(buffer.Address, (ushort)buffer.Size, recvPointerIndex);
+ }
+ else if (flags.HasFlag(HipcBufferFlags.AutoSelect))
+ {
+ if (!isBufferMapAlias[i])
+ {
+ response.SendStatics[recvPointerIndex] = new HipcStaticDescriptor(buffer.Address, (ushort)buffer.Size, recvPointerIndex);
+ }
+ else
+ {
+ response.SendStatics[recvPointerIndex] = new HipcStaticDescriptor(0UL, 0, recvPointerIndex);
+ }
+ }
+
+ recvPointerIndex++;
+ }
+ }
+ }
+
+ public override void SetImplementationProcessor(ServerMessageProcessor impl)
+ {
+ // We don't need to do anything here as this should be always the last processor to be called.
+ }
+
+ public override ServerMessageRuntimeMetadata GetRuntimeMetadata()
+ {
+ return new ServerMessageRuntimeMetadata(
+ (ushort)InRawDataSize,
+ (ushort)OutRawDataSize,
+ (byte)Unsafe.SizeOf<CmifInHeader>(),
+ (byte)Unsafe.SizeOf<CmifOutHeader>(),
+ (byte)_inObjectsCount,
+ (byte)_outObjectsCount);
+ }
+
+ public override Result PrepareForProcess(ref ServiceDispatchContext context, ServerMessageRuntimeMetadata runtimeMetadata)
+ {
+ ref var meta = ref context.Request.Meta;
+ bool requestValid = true;
+ requestValid &= meta.SendPid == _hasInProcessIdHolder;
+ requestValid &= meta.SendStaticsCount == _inPointerBuffersCount;
+ requestValid &= meta.SendBuffersCount == _inMapAliasBuffersCount;
+ requestValid &= meta.ReceiveBuffersCount == _outMapAliasBuffersCount;
+ requestValid &= meta.ExchangeBuffersCount == 0;
+ requestValid &= meta.CopyHandlesCount == _inCopyHandlesCount;
+ requestValid &= meta.MoveHandlesCount == _inMoveHandlesCount;
+
+ int rawSizeInBytes = meta.DataWordsCount * sizeof(uint);
+ int commandRawSize = BitUtils.AlignUp(runtimeMetadata.UnfixedOutPointerSizeOffset + (OutUnfixedSizePointerBuffersCount * sizeof(ushort)), sizeof(uint));
+ requestValid &= rawSizeInBytes >= commandRawSize;
+
+ return requestValid ? Result.Success : HipcResult.InvalidCmifRequest;
+ }
+
+ public Result GetInObjects(ServerMessageProcessor processor, Span<IServiceObject> objects)
+ {
+ if (objects.Length == 0)
+ {
+ return Result.Success;
+ }
+
+ ServiceObjectHolder[] inObjects = new ServiceObjectHolder[objects.Length];
+ Result result = processor.GetInObjects(inObjects);
+
+ if (result.IsFailure)
+ {
+ return result;
+ }
+
+ int inObjectIndex = 0;
+
+ for (int i = 0; i < _args.Length; i++)
+ {
+ if (_args[i].Type == CommandArgType.InObject)
+ {
+ int index = inObjectIndex++;
+ var inObject = inObjects[index];
+
+ objects[index] = inObject?.ServiceObject;
+ }
+ }
+
+ return Result.Success;
+ }
+
+ public override Result GetInObjects(Span<ServiceObjectHolder> inObjects)
+ {
+ return SfResult.NotSupported;
+ }
+
+ public override HipcMessageData PrepareForReply(scoped ref ServiceDispatchContext context, out Span<byte> outRawData, ServerMessageRuntimeMetadata runtimeMetadata)
+ {
+ int rawDataSize = OutRawDataSize + runtimeMetadata.OutHeadersSize;
+ var response = HipcMessage.WriteResponse(
+ context.OutMessageBuffer,
+ _outPointerBuffersCount,
+ (BitUtils.AlignUp(rawDataSize, 4) + 0x10) / sizeof(uint),
+ _outCopyHandlesCount,
+ _outMoveHandlesCount + runtimeMetadata.OutObjectsCount);
+ outRawData = MemoryMarshal.Cast<uint, byte>(response.DataWords);
+ return response;
+ }
+
+ public override void PrepareForErrorReply(scoped ref ServiceDispatchContext context, out Span<byte> outRawData, ServerMessageRuntimeMetadata runtimeMetadata)
+ {
+ int rawDataSize = runtimeMetadata.OutHeadersSize;
+ var response = HipcMessage.WriteResponse(
+ context.OutMessageBuffer,
+ 0,
+ (BitUtils.AlignUp(rawDataSize, 4) + 0x10) / sizeof(uint),
+ 0,
+ 0);
+ outRawData = MemoryMarshal.Cast<uint, byte>(response.DataWords);
+ }
+
+ public void SetOutObjects(ref ServiceDispatchContext context, HipcMessageData response, Span<IServiceObject> objects)
+ {
+ if (objects.Length == 0)
+ {
+ return;
+ }
+
+ ServiceObjectHolder[] outObjects = new ServiceObjectHolder[objects.Length];
+
+ for (int i = 0; i < objects.Length; i++)
+ {
+ outObjects[i] = objects[i] != null ? new ServiceObjectHolder(objects[i]) : null;
+ }
+
+ context.Processor.SetOutObjects(ref context, response, outObjects);
+ }
+
+ public override void SetOutObjects(scoped ref ServiceDispatchContext context, HipcMessageData response, Span<ServiceObjectHolder> outObjects)
+ {
+ for (int index = 0; index < _outObjectsCount; index++)
+ {
+ SetOutObjectImpl(index, response, context.Manager, outObjects[index]);
+ }
+ }
+
+ private void SetOutObjectImpl(int index, HipcMessageData response, ServerSessionManager manager, ServiceObjectHolder obj)
+ {
+ if (obj == null)
+ {
+ response.MoveHandles[index] = 0;
+ return;
+ }
+
+ Api.CreateSession(out int serverHandle, out int clientHandle).AbortOnFailure();
+ manager.RegisterSession(serverHandle, obj).AbortOnFailure();
+ response.MoveHandles[index] = clientHandle;
+ }
+ }
+}
diff --git a/Ryujinx.Horizon/Sdk/Sf/IServiceObject.cs b/Ryujinx.Horizon/Sdk/Sf/IServiceObject.cs
new file mode 100644
index 00000000..afa57fef
--- /dev/null
+++ b/Ryujinx.Horizon/Sdk/Sf/IServiceObject.cs
@@ -0,0 +1,9 @@
+using System.Collections.Generic;
+
+namespace Ryujinx.Horizon.Sdk.Sf
+{
+ interface IServiceObject
+ {
+ IReadOnlyDictionary<int, CommandHandler> GetCommandHandlers();
+ }
+}
diff --git a/Ryujinx.Horizon/Sdk/Sf/RawDataOffsetCalculator.cs b/Ryujinx.Horizon/Sdk/Sf/RawDataOffsetCalculator.cs
new file mode 100644
index 00000000..982f454f
--- /dev/null
+++ b/Ryujinx.Horizon/Sdk/Sf/RawDataOffsetCalculator.cs
@@ -0,0 +1,51 @@
+using Ryujinx.Common;
+
+namespace Ryujinx.Horizon.Sdk.Sf
+{
+ static class RawDataOffsetCalculator
+ {
+ public static int[] Calculate(CommandArg[] args)
+ {
+ int[] offsets = new int[args.Length + 1];
+
+ if (args.Length != 0)
+ {
+ int argsCount = args.Length;
+
+ int[] sizes = new int[argsCount];
+ int[] aligns = new int[argsCount];
+ int[] map = new int[argsCount];
+
+ for (int i = 0; i < argsCount; i++)
+ {
+ sizes[i] = args[i].ArgSize;
+ aligns[i] = args[i].ArgAlignment;
+ map[i] = i;
+ }
+
+ for (int i = 1; i < argsCount; i++)
+ {
+ for (int j = i; j > 0 && aligns[map[j - 1]] > aligns[map[j]]; j--)
+ {
+ var temp = map[j - 1];
+ map[j - 1] = map[j];
+ map[j] = temp;
+ }
+ }
+
+ int offset = 0;
+
+ foreach (int i in map)
+ {
+ offset = BitUtils.AlignUp(offset, aligns[i]);
+ offsets[i] = offset;
+ offset += sizes[i];
+ }
+
+ offsets[argsCount] = offset;
+ }
+
+ return offsets;
+ }
+ }
+}
diff --git a/Ryujinx.Horizon/Sdk/Sf/SfResult.cs b/Ryujinx.Horizon/Sdk/Sf/SfResult.cs
new file mode 100644
index 00000000..6aa11ba5
--- /dev/null
+++ b/Ryujinx.Horizon/Sdk/Sf/SfResult.cs
@@ -0,0 +1,31 @@
+using Ryujinx.Horizon.Common;
+
+namespace Ryujinx.Horizon.Sdk.Sf
+{
+ static class SfResult
+ {
+ public const int ModuleId = 10;
+
+ public static Result NotSupported => new Result(ModuleId, 1);
+ public static Result InvalidHeaderSize => new Result(ModuleId, 202);
+ public static Result InvalidInHeader => new Result(ModuleId, 211);
+ public static Result InvalidOutHeader => new Result(ModuleId, 212);
+ public static Result UnknownCommandId => new Result(ModuleId, 221);
+ public static Result InvalidOutRawSize => new Result(ModuleId, 232);
+ public static Result InvalidInObjectsCount => new Result(ModuleId, 235);
+ public static Result InvalidOutObjectsCount => new Result(ModuleId, 236);
+ public static Result InvalidInObject => new Result(ModuleId, 239);
+
+ public static Result TargetNotFound => new Result(ModuleId, 261);
+
+ public static Result OutOfDomainEntries => new Result(ModuleId, 301);
+
+ public static Result InvalidatedByUser => new Result(ModuleId, 802);
+ public static Result RequestDeferredByUser => new Result(ModuleId, 812);
+
+ public static bool RequestContextChanged(Result result) => result.InRange(800, 899);
+ public static bool Invalidated(Result result) => result.InRange(801, 809);
+
+ public static bool RequestDeferred(Result result) => result.InRange(811, 819);
+ }
+}
diff --git a/Ryujinx.Horizon/Sdk/Sm/ServiceName.cs b/Ryujinx.Horizon/Sdk/Sm/ServiceName.cs
new file mode 100644
index 00000000..dbb30078
--- /dev/null
+++ b/Ryujinx.Horizon/Sdk/Sm/ServiceName.cs
@@ -0,0 +1,98 @@
+using System;
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.Horizon.Sdk.Sm
+{
+ [StructLayout(LayoutKind.Sequential, Pack = 1)]
+ struct ServiceName
+ {
+ public static ServiceName Invalid { get; } = new ServiceName(0);
+
+ public bool IsInvalid => Packed == 0;
+
+ public int Length => sizeof(ulong);
+
+ public ulong Packed { get; }
+
+ public byte this[int index]
+ {
+ get
+ {
+ if ((uint)index >= sizeof(ulong))
+ {
+ throw new IndexOutOfRangeException();
+ }
+
+ return (byte)(Packed >> (index * 8));
+ }
+ }
+
+ private ServiceName(ulong packed)
+ {
+ Packed = packed;
+ }
+
+ public static ServiceName Encode(string name)
+ {
+ ulong packed = 0;
+
+ for (int index = 0; index < sizeof(ulong); index++)
+ {
+ if (index < name.Length)
+ {
+ packed |= (ulong)(byte)name[index] << (index * 8);
+ }
+ else
+ {
+ break;
+ }
+ }
+
+ return new ServiceName(packed);
+ }
+
+ public override bool Equals(object obj)
+ {
+ return obj is ServiceName serviceName && serviceName.Equals(this);
+ }
+
+ public bool Equals(ServiceName other)
+ {
+ return other.Packed == Packed;
+ }
+
+ public override int GetHashCode()
+ {
+ return Packed.GetHashCode();
+ }
+
+ public static bool operator ==(ServiceName lhs, ServiceName rhs)
+ {
+ return lhs.Equals(rhs);
+ }
+
+ public static bool operator !=(ServiceName lhs, ServiceName rhs)
+ {
+ return !lhs.Equals(rhs);
+ }
+
+ public override string ToString()
+ {
+ string name = string.Empty;
+
+ for (int index = 0; index < sizeof(ulong); index++)
+ {
+ byte character = (byte)(Packed >> (index * 8));
+
+ if (character == 0)
+ {
+ break;
+ }
+
+ name += (char)character;
+ }
+
+ return name;
+ }
+ }
+}
diff --git a/Ryujinx.Horizon/Sdk/Sm/SmApi.cs b/Ryujinx.Horizon/Sdk/Sm/SmApi.cs
new file mode 100644
index 00000000..e4b0eea1
--- /dev/null
+++ b/Ryujinx.Horizon/Sdk/Sm/SmApi.cs
@@ -0,0 +1,107 @@
+using Ryujinx.Common.Memory;
+using Ryujinx.Horizon.Common;
+using Ryujinx.Horizon.Sdk.Sf.Cmif;
+using System;
+
+namespace Ryujinx.Horizon.Sdk.Sm
+{
+ class SmApi
+ {
+ private int _portHandle;
+
+ public Result Initialize()
+ {
+ Result result = HorizonStatic.Syscall.ConnectToNamedPort(out int portHandle, "sm:");
+
+ while (result == KernelResult.NotFound)
+ {
+ HorizonStatic.Syscall.SleepThread(50000000L);
+ result = HorizonStatic.Syscall.ConnectToNamedPort(out portHandle, "sm:");
+ }
+
+ if (result.IsFailure)
+ {
+ return result;
+ }
+
+ _portHandle = portHandle;
+
+ return RegisterClient();
+ }
+
+ private Result RegisterClient()
+ {
+ Span<byte> data = stackalloc byte[8];
+
+ SpanWriter writer = new SpanWriter(data);
+
+ writer.Write(0UL);
+
+ return ServiceUtil.SendRequest(out _, _portHandle, 0, sendPid: true, data);
+ }
+
+ public Result GetServiceHandle(out int handle, ServiceName name)
+ {
+ Span<byte> data = stackalloc byte[8];
+
+ SpanWriter writer = new SpanWriter(data);
+
+ writer.Write(name);
+
+ Result result = ServiceUtil.SendRequest(out CmifResponse response, _portHandle, 1, sendPid: false, data);
+
+ if (result.IsFailure)
+ {
+ handle = 0;
+ return result;
+ }
+
+ handle = response.MoveHandles[0];
+ return Result.Success;
+ }
+
+ public Result RegisterService(out int handle, ServiceName name, int maxSessions, bool isLight)
+ {
+ Span<byte> data = stackalloc byte[16];
+
+ SpanWriter writer = new SpanWriter(data);
+
+ writer.Write(name);
+ writer.Write(isLight ? 1 : 0);
+ writer.Write(maxSessions);
+
+ Result result = ServiceUtil.SendRequest(out CmifResponse response, _portHandle, 2, sendPid: false, data);
+
+ if (result.IsFailure)
+ {
+ handle = 0;
+ return result;
+ }
+
+ handle = response.MoveHandles[0];
+ return Result.Success;
+ }
+
+ public Result UnregisterService(ServiceName name)
+ {
+ Span<byte> data = stackalloc byte[8];
+
+ SpanWriter writer = new SpanWriter(data);
+
+ writer.Write(name);
+
+ return ServiceUtil.SendRequest(out _, _portHandle, 3, sendPid: false, data);
+ }
+
+ public Result DetachClient()
+ {
+ Span<byte> data = stackalloc byte[8];
+
+ SpanWriter writer = new SpanWriter(data);
+
+ writer.Write(0UL);
+
+ return ServiceUtil.SendRequest(out _, _portHandle, 4, sendPid: true, data);
+ }
+ }
+}