aboutsummaryrefslogtreecommitdiff
path: root/Ryujinx.Horizon/Sdk/Sf/Hipc
diff options
context:
space:
mode:
Diffstat (limited to 'Ryujinx.Horizon/Sdk/Sf/Hipc')
-rw-r--r--Ryujinx.Horizon/Sdk/Sf/Hipc/Api.cs89
-rw-r--r--Ryujinx.Horizon/Sdk/Sf/Hipc/Header.cs65
-rw-r--r--Ryujinx.Horizon/Sdk/Sf/Hipc/HipcBufferDescriptor.cs15
-rw-r--r--Ryujinx.Horizon/Sdk/Sf/Hipc/HipcBufferFlags.cs17
-rw-r--r--Ryujinx.Horizon/Sdk/Sf/Hipc/HipcBufferMode.cs10
-rw-r--r--Ryujinx.Horizon/Sdk/Sf/Hipc/HipcManager.cs115
-rw-r--r--Ryujinx.Horizon/Sdk/Sf/Hipc/HipcMessage.cs222
-rw-r--r--Ryujinx.Horizon/Sdk/Sf/Hipc/HipcMessageData.cs16
-rw-r--r--Ryujinx.Horizon/Sdk/Sf/Hipc/HipcMetadata.cs16
-rw-r--r--Ryujinx.Horizon/Sdk/Sf/Hipc/HipcReceiveListEntry.cs14
-rw-r--r--Ryujinx.Horizon/Sdk/Sf/Hipc/HipcResult.cs22
-rw-r--r--Ryujinx.Horizon/Sdk/Sf/Hipc/HipcStaticDescriptor.cs22
-rw-r--r--Ryujinx.Horizon/Sdk/Sf/Hipc/ManagerOptions.cs20
-rw-r--r--Ryujinx.Horizon/Sdk/Sf/Hipc/ReceiveResult.cs9
-rw-r--r--Ryujinx.Horizon/Sdk/Sf/Hipc/Server.cs36
-rw-r--r--Ryujinx.Horizon/Sdk/Sf/Hipc/ServerDomainSessionManager.cs23
-rw-r--r--Ryujinx.Horizon/Sdk/Sf/Hipc/ServerManager.cs198
-rw-r--r--Ryujinx.Horizon/Sdk/Sf/Hipc/ServerManagerBase.cs307
-rw-r--r--Ryujinx.Horizon/Sdk/Sf/Hipc/ServerSession.cs23
-rw-r--r--Ryujinx.Horizon/Sdk/Sf/Hipc/ServerSessionManager.cs335
-rw-r--r--Ryujinx.Horizon/Sdk/Sf/Hipc/SpecialHeader.cs27
21 files changed, 1601 insertions, 0 deletions
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);
+ }
+ }
+}