From 08831eecf77cedd3c4192ebab5a9c485fb15d51e Mon Sep 17 00:00:00 2001
From: gdkchan <gab.dark.100@gmail.com>
Date: Wed, 4 Jan 2023 19:15:45 -0300
Subject: IPC refactor part 3+4: New server HIPC message processor (#4188)

* IPC refactor part 3 + 4: New server HIPC message processor with source generator based serialization

* Make types match on calls to AlignUp/AlignDown

* Formatting

* Address some PR feedback

* Move BitfieldExtensions to Ryujinx.Common.Utilities and consolidate implementations

* Rename Reader/Writer to SpanReader/SpanWriter and move to Ryujinx.Common.Memory

* Implement EventType

* Address more PR feedback

* Log request processing errors since they are not normal

* Rename waitable to multiwait and add missing lock

* PR feedback

* Ac_K PR feedback
---
 Ryujinx.Horizon/Sdk/DebugUtil.cs                   |  12 +
 Ryujinx.Horizon/Sdk/Diag/LogSeverity.cs            |  11 +
 Ryujinx.Horizon/Sdk/Lm/LogDataChunkKey.cs          |  19 +
 Ryujinx.Horizon/Sdk/Lm/LogDestination.cs           |  14 +
 Ryujinx.Horizon/Sdk/Lm/LogPacketFlags.cs           |  12 +
 Ryujinx.Horizon/Sdk/Lm/LogPacketHeader.cs          |  15 +
 Ryujinx.Horizon/Sdk/OsTypes/Event.cs               |  61 +++
 Ryujinx.Horizon/Sdk/OsTypes/EventClearMode.cs      |   8 +
 Ryujinx.Horizon/Sdk/OsTypes/EventType.cs           |  15 +
 .../Sdk/OsTypes/Impl/InterProcessEvent.cs          |  89 +++++
 .../Sdk/OsTypes/Impl/InterProcessEventImpl.cs      | 136 +++++++
 Ryujinx.Horizon/Sdk/OsTypes/Impl/MultiWaitImpl.cs  | 250 ++++++++++++
 Ryujinx.Horizon/Sdk/OsTypes/InitializationState.cs |   8 +
 .../Sdk/OsTypes/InterProcessEventType.cs           |  27 ++
 Ryujinx.Horizon/Sdk/OsTypes/MultiWait.cs           |  43 +++
 Ryujinx.Horizon/Sdk/OsTypes/MultiWaitHolder.cs     |  16 +
 Ryujinx.Horizon/Sdk/OsTypes/MultiWaitHolderBase.cs |  39 ++
 .../Sdk/OsTypes/MultiWaitHolderOfEvent.cs          |  45 +++
 .../Sdk/OsTypes/MultiWaitHolderOfHandle.cs         |  14 +
 Ryujinx.Horizon/Sdk/OsTypes/OsEvent.cs             | 130 +++++++
 Ryujinx.Horizon/Sdk/OsTypes/OsMultiWait.cs         |  10 +
 Ryujinx.Horizon/Sdk/OsTypes/OsProcessHandle.cs     |  33 ++
 Ryujinx.Horizon/Sdk/OsTypes/OsResult.cs            |  11 +
 Ryujinx.Horizon/Sdk/OsTypes/OsSystemEvent.cs       |  85 +++++
 Ryujinx.Horizon/Sdk/OsTypes/OsThreadManager.cs     |  10 +
 Ryujinx.Horizon/Sdk/OsTypes/SystemEventType.cs     |  17 +
 Ryujinx.Horizon/Sdk/OsTypes/TriBool.cs             |   9 +
 Ryujinx.Horizon/Sdk/ServiceUtil.cs                 |  38 ++
 Ryujinx.Horizon/Sdk/Sf/Cmif/CmifDomainInHeader.cs  |  12 +
 Ryujinx.Horizon/Sdk/Sf/Cmif/CmifDomainOutHeader.cs |  12 +
 .../Sdk/Sf/Cmif/CmifDomainRequestType.cs           |   9 +
 Ryujinx.Horizon/Sdk/Sf/Cmif/CmifInHeader.cs        |  10 +
 Ryujinx.Horizon/Sdk/Sf/Cmif/CmifMessage.cs         | 128 +++++++
 Ryujinx.Horizon/Sdk/Sf/Cmif/CmifOutHeader.cs       |  14 +
 Ryujinx.Horizon/Sdk/Sf/Cmif/CmifRequest.cs         |  14 +
 Ryujinx.Horizon/Sdk/Sf/Cmif/CmifRequestFormat.cs   |  24 ++
 Ryujinx.Horizon/Sdk/Sf/Cmif/CmifResponse.cs        |  12 +
 Ryujinx.Horizon/Sdk/Sf/Cmif/CommandType.cs         |  14 +
 Ryujinx.Horizon/Sdk/Sf/Cmif/DomainServiceObject.cs |   7 +
 .../Sf/Cmif/DomainServiceObjectDispatchTable.cs    |  75 ++++
 .../Sdk/Sf/Cmif/DomainServiceObjectProcessor.cs    | 140 +++++++
 Ryujinx.Horizon/Sdk/Sf/Cmif/HandlesToClose.cs      |  52 +++
 Ryujinx.Horizon/Sdk/Sf/Cmif/InlineContext.cs       |  11 +
 Ryujinx.Horizon/Sdk/Sf/Cmif/PointerAndSize.cs      |  17 +
 .../Sdk/Sf/Cmif/ScopedInlineContextChange.cs       |  19 +
 Ryujinx.Horizon/Sdk/Sf/Cmif/ServerDomainBase.cs    |  15 +
 Ryujinx.Horizon/Sdk/Sf/Cmif/ServerDomainManager.cs | 246 ++++++++++++
 .../Sdk/Sf/Cmif/ServerMessageProcessor.cs          |  18 +
 .../Sdk/Sf/Cmif/ServerMessageRuntimeMetadata.cs    |  29 ++
 .../Sdk/Sf/Cmif/ServiceDispatchContext.cs          |  18 +
 Ryujinx.Horizon/Sdk/Sf/Cmif/ServiceDispatchMeta.cs |  12 +
 .../Sdk/Sf/Cmif/ServiceDispatchTable.cs            |  33 ++
 .../Sdk/Sf/Cmif/ServiceDispatchTableBase.cs        |  90 +++++
 Ryujinx.Horizon/Sdk/Sf/Cmif/ServiceObjectHolder.cs |  34 ++
 Ryujinx.Horizon/Sdk/Sf/CmifCommandAttribute.cs     |  15 +
 Ryujinx.Horizon/Sdk/Sf/CommandArg.cs               |  56 +++
 Ryujinx.Horizon/Sdk/Sf/CommandArgAttributes.cs     |  38 ++
 Ryujinx.Horizon/Sdk/Sf/CommandHandler.cs           |  57 +++
 Ryujinx.Horizon/Sdk/Sf/CommandSerialization.cs     |  68 ++++
 Ryujinx.Horizon/Sdk/Sf/Hipc/Api.cs                 |  89 +++++
 Ryujinx.Horizon/Sdk/Sf/Hipc/Header.cs              |  65 ++++
 .../Sdk/Sf/Hipc/HipcBufferDescriptor.cs            |  15 +
 Ryujinx.Horizon/Sdk/Sf/Hipc/HipcBufferFlags.cs     |  17 +
 Ryujinx.Horizon/Sdk/Sf/Hipc/HipcBufferMode.cs      |  10 +
 Ryujinx.Horizon/Sdk/Sf/Hipc/HipcManager.cs         | 115 ++++++
 Ryujinx.Horizon/Sdk/Sf/Hipc/HipcMessage.cs         | 222 +++++++++++
 Ryujinx.Horizon/Sdk/Sf/Hipc/HipcMessageData.cs     |  16 +
 Ryujinx.Horizon/Sdk/Sf/Hipc/HipcMetadata.cs        |  16 +
 .../Sdk/Sf/Hipc/HipcReceiveListEntry.cs            |  14 +
 Ryujinx.Horizon/Sdk/Sf/Hipc/HipcResult.cs          |  22 ++
 .../Sdk/Sf/Hipc/HipcStaticDescriptor.cs            |  22 ++
 Ryujinx.Horizon/Sdk/Sf/Hipc/ManagerOptions.cs      |  20 +
 Ryujinx.Horizon/Sdk/Sf/Hipc/ReceiveResult.cs       |   9 +
 Ryujinx.Horizon/Sdk/Sf/Hipc/Server.cs              |  36 ++
 .../Sdk/Sf/Hipc/ServerDomainSessionManager.cs      |  23 ++
 Ryujinx.Horizon/Sdk/Sf/Hipc/ServerManager.cs       | 198 ++++++++++
 Ryujinx.Horizon/Sdk/Sf/Hipc/ServerManagerBase.cs   | 307 +++++++++++++++
 Ryujinx.Horizon/Sdk/Sf/Hipc/ServerSession.cs       |  23 ++
 .../Sdk/Sf/Hipc/ServerSessionManager.cs            | 335 ++++++++++++++++
 Ryujinx.Horizon/Sdk/Sf/Hipc/SpecialHeader.cs       |  27 ++
 Ryujinx.Horizon/Sdk/Sf/HipcCommandProcessor.cs     | 421 +++++++++++++++++++++
 Ryujinx.Horizon/Sdk/Sf/IServiceObject.cs           |   9 +
 Ryujinx.Horizon/Sdk/Sf/RawDataOffsetCalculator.cs  |  51 +++
 Ryujinx.Horizon/Sdk/Sf/SfResult.cs                 |  31 ++
 Ryujinx.Horizon/Sdk/Sm/ServiceName.cs              |  98 +++++
 Ryujinx.Horizon/Sdk/Sm/SmApi.cs                    | 107 ++++++
 86 files changed, 4794 insertions(+)
 create mode 100644 Ryujinx.Horizon/Sdk/DebugUtil.cs
 create mode 100644 Ryujinx.Horizon/Sdk/Diag/LogSeverity.cs
 create mode 100644 Ryujinx.Horizon/Sdk/Lm/LogDataChunkKey.cs
 create mode 100644 Ryujinx.Horizon/Sdk/Lm/LogDestination.cs
 create mode 100644 Ryujinx.Horizon/Sdk/Lm/LogPacketFlags.cs
 create mode 100644 Ryujinx.Horizon/Sdk/Lm/LogPacketHeader.cs
 create mode 100644 Ryujinx.Horizon/Sdk/OsTypes/Event.cs
 create mode 100644 Ryujinx.Horizon/Sdk/OsTypes/EventClearMode.cs
 create mode 100644 Ryujinx.Horizon/Sdk/OsTypes/EventType.cs
 create mode 100644 Ryujinx.Horizon/Sdk/OsTypes/Impl/InterProcessEvent.cs
 create mode 100644 Ryujinx.Horizon/Sdk/OsTypes/Impl/InterProcessEventImpl.cs
 create mode 100644 Ryujinx.Horizon/Sdk/OsTypes/Impl/MultiWaitImpl.cs
 create mode 100644 Ryujinx.Horizon/Sdk/OsTypes/InitializationState.cs
 create mode 100644 Ryujinx.Horizon/Sdk/OsTypes/InterProcessEventType.cs
 create mode 100644 Ryujinx.Horizon/Sdk/OsTypes/MultiWait.cs
 create mode 100644 Ryujinx.Horizon/Sdk/OsTypes/MultiWaitHolder.cs
 create mode 100644 Ryujinx.Horizon/Sdk/OsTypes/MultiWaitHolderBase.cs
 create mode 100644 Ryujinx.Horizon/Sdk/OsTypes/MultiWaitHolderOfEvent.cs
 create mode 100644 Ryujinx.Horizon/Sdk/OsTypes/MultiWaitHolderOfHandle.cs
 create mode 100644 Ryujinx.Horizon/Sdk/OsTypes/OsEvent.cs
 create mode 100644 Ryujinx.Horizon/Sdk/OsTypes/OsMultiWait.cs
 create mode 100644 Ryujinx.Horizon/Sdk/OsTypes/OsProcessHandle.cs
 create mode 100644 Ryujinx.Horizon/Sdk/OsTypes/OsResult.cs
 create mode 100644 Ryujinx.Horizon/Sdk/OsTypes/OsSystemEvent.cs
 create mode 100644 Ryujinx.Horizon/Sdk/OsTypes/OsThreadManager.cs
 create mode 100644 Ryujinx.Horizon/Sdk/OsTypes/SystemEventType.cs
 create mode 100644 Ryujinx.Horizon/Sdk/OsTypes/TriBool.cs
 create mode 100644 Ryujinx.Horizon/Sdk/ServiceUtil.cs
 create mode 100644 Ryujinx.Horizon/Sdk/Sf/Cmif/CmifDomainInHeader.cs
 create mode 100644 Ryujinx.Horizon/Sdk/Sf/Cmif/CmifDomainOutHeader.cs
 create mode 100644 Ryujinx.Horizon/Sdk/Sf/Cmif/CmifDomainRequestType.cs
 create mode 100644 Ryujinx.Horizon/Sdk/Sf/Cmif/CmifInHeader.cs
 create mode 100644 Ryujinx.Horizon/Sdk/Sf/Cmif/CmifMessage.cs
 create mode 100644 Ryujinx.Horizon/Sdk/Sf/Cmif/CmifOutHeader.cs
 create mode 100644 Ryujinx.Horizon/Sdk/Sf/Cmif/CmifRequest.cs
 create mode 100644 Ryujinx.Horizon/Sdk/Sf/Cmif/CmifRequestFormat.cs
 create mode 100644 Ryujinx.Horizon/Sdk/Sf/Cmif/CmifResponse.cs
 create mode 100644 Ryujinx.Horizon/Sdk/Sf/Cmif/CommandType.cs
 create mode 100644 Ryujinx.Horizon/Sdk/Sf/Cmif/DomainServiceObject.cs
 create mode 100644 Ryujinx.Horizon/Sdk/Sf/Cmif/DomainServiceObjectDispatchTable.cs
 create mode 100644 Ryujinx.Horizon/Sdk/Sf/Cmif/DomainServiceObjectProcessor.cs
 create mode 100644 Ryujinx.Horizon/Sdk/Sf/Cmif/HandlesToClose.cs
 create mode 100644 Ryujinx.Horizon/Sdk/Sf/Cmif/InlineContext.cs
 create mode 100644 Ryujinx.Horizon/Sdk/Sf/Cmif/PointerAndSize.cs
 create mode 100644 Ryujinx.Horizon/Sdk/Sf/Cmif/ScopedInlineContextChange.cs
 create mode 100644 Ryujinx.Horizon/Sdk/Sf/Cmif/ServerDomainBase.cs
 create mode 100644 Ryujinx.Horizon/Sdk/Sf/Cmif/ServerDomainManager.cs
 create mode 100644 Ryujinx.Horizon/Sdk/Sf/Cmif/ServerMessageProcessor.cs
 create mode 100644 Ryujinx.Horizon/Sdk/Sf/Cmif/ServerMessageRuntimeMetadata.cs
 create mode 100644 Ryujinx.Horizon/Sdk/Sf/Cmif/ServiceDispatchContext.cs
 create mode 100644 Ryujinx.Horizon/Sdk/Sf/Cmif/ServiceDispatchMeta.cs
 create mode 100644 Ryujinx.Horizon/Sdk/Sf/Cmif/ServiceDispatchTable.cs
 create mode 100644 Ryujinx.Horizon/Sdk/Sf/Cmif/ServiceDispatchTableBase.cs
 create mode 100644 Ryujinx.Horizon/Sdk/Sf/Cmif/ServiceObjectHolder.cs
 create mode 100644 Ryujinx.Horizon/Sdk/Sf/CmifCommandAttribute.cs
 create mode 100644 Ryujinx.Horizon/Sdk/Sf/CommandArg.cs
 create mode 100644 Ryujinx.Horizon/Sdk/Sf/CommandArgAttributes.cs
 create mode 100644 Ryujinx.Horizon/Sdk/Sf/CommandHandler.cs
 create mode 100644 Ryujinx.Horizon/Sdk/Sf/CommandSerialization.cs
 create mode 100644 Ryujinx.Horizon/Sdk/Sf/Hipc/Api.cs
 create mode 100644 Ryujinx.Horizon/Sdk/Sf/Hipc/Header.cs
 create mode 100644 Ryujinx.Horizon/Sdk/Sf/Hipc/HipcBufferDescriptor.cs
 create mode 100644 Ryujinx.Horizon/Sdk/Sf/Hipc/HipcBufferFlags.cs
 create mode 100644 Ryujinx.Horizon/Sdk/Sf/Hipc/HipcBufferMode.cs
 create mode 100644 Ryujinx.Horizon/Sdk/Sf/Hipc/HipcManager.cs
 create mode 100644 Ryujinx.Horizon/Sdk/Sf/Hipc/HipcMessage.cs
 create mode 100644 Ryujinx.Horizon/Sdk/Sf/Hipc/HipcMessageData.cs
 create mode 100644 Ryujinx.Horizon/Sdk/Sf/Hipc/HipcMetadata.cs
 create mode 100644 Ryujinx.Horizon/Sdk/Sf/Hipc/HipcReceiveListEntry.cs
 create mode 100644 Ryujinx.Horizon/Sdk/Sf/Hipc/HipcResult.cs
 create mode 100644 Ryujinx.Horizon/Sdk/Sf/Hipc/HipcStaticDescriptor.cs
 create mode 100644 Ryujinx.Horizon/Sdk/Sf/Hipc/ManagerOptions.cs
 create mode 100644 Ryujinx.Horizon/Sdk/Sf/Hipc/ReceiveResult.cs
 create mode 100644 Ryujinx.Horizon/Sdk/Sf/Hipc/Server.cs
 create mode 100644 Ryujinx.Horizon/Sdk/Sf/Hipc/ServerDomainSessionManager.cs
 create mode 100644 Ryujinx.Horizon/Sdk/Sf/Hipc/ServerManager.cs
 create mode 100644 Ryujinx.Horizon/Sdk/Sf/Hipc/ServerManagerBase.cs
 create mode 100644 Ryujinx.Horizon/Sdk/Sf/Hipc/ServerSession.cs
 create mode 100644 Ryujinx.Horizon/Sdk/Sf/Hipc/ServerSessionManager.cs
 create mode 100644 Ryujinx.Horizon/Sdk/Sf/Hipc/SpecialHeader.cs
 create mode 100644 Ryujinx.Horizon/Sdk/Sf/HipcCommandProcessor.cs
 create mode 100644 Ryujinx.Horizon/Sdk/Sf/IServiceObject.cs
 create mode 100644 Ryujinx.Horizon/Sdk/Sf/RawDataOffsetCalculator.cs
 create mode 100644 Ryujinx.Horizon/Sdk/Sf/SfResult.cs
 create mode 100644 Ryujinx.Horizon/Sdk/Sm/ServiceName.cs
 create mode 100644 Ryujinx.Horizon/Sdk/Sm/SmApi.cs

(limited to 'Ryujinx.Horizon/Sdk')

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);
+        }
+    }
+}
-- 
cgit v1.2.3-70-g09d2