aboutsummaryrefslogtreecommitdiff
path: root/src/Ryujinx.Horizon/Sdk/Sf/Cmif
diff options
context:
space:
mode:
Diffstat (limited to 'src/Ryujinx.Horizon/Sdk/Sf/Cmif')
-rw-r--r--src/Ryujinx.Horizon/Sdk/Sf/Cmif/CmifDomainInHeader.cs12
-rw-r--r--src/Ryujinx.Horizon/Sdk/Sf/Cmif/CmifDomainOutHeader.cs12
-rw-r--r--src/Ryujinx.Horizon/Sdk/Sf/Cmif/CmifDomainRequestType.cs9
-rw-r--r--src/Ryujinx.Horizon/Sdk/Sf/Cmif/CmifInHeader.cs10
-rw-r--r--src/Ryujinx.Horizon/Sdk/Sf/Cmif/CmifMessage.cs135
-rw-r--r--src/Ryujinx.Horizon/Sdk/Sf/Cmif/CmifOutHeader.cs14
-rw-r--r--src/Ryujinx.Horizon/Sdk/Sf/Cmif/CmifRequest.cs14
-rw-r--r--src/Ryujinx.Horizon/Sdk/Sf/Cmif/CmifRequestFormat.cs24
-rw-r--r--src/Ryujinx.Horizon/Sdk/Sf/Cmif/CmifResponse.cs12
-rw-r--r--src/Ryujinx.Horizon/Sdk/Sf/Cmif/CommandType.cs14
-rw-r--r--src/Ryujinx.Horizon/Sdk/Sf/Cmif/DomainServiceObject.cs7
-rw-r--r--src/Ryujinx.Horizon/Sdk/Sf/Cmif/DomainServiceObjectDispatchTable.cs75
-rw-r--r--src/Ryujinx.Horizon/Sdk/Sf/Cmif/DomainServiceObjectProcessor.cs140
-rw-r--r--src/Ryujinx.Horizon/Sdk/Sf/Cmif/HandlesToClose.cs52
-rw-r--r--src/Ryujinx.Horizon/Sdk/Sf/Cmif/InlineContext.cs11
-rw-r--r--src/Ryujinx.Horizon/Sdk/Sf/Cmif/PointerAndSize.cs17
-rw-r--r--src/Ryujinx.Horizon/Sdk/Sf/Cmif/ScopedInlineContextChange.cs19
-rw-r--r--src/Ryujinx.Horizon/Sdk/Sf/Cmif/ServerDomainBase.cs15
-rw-r--r--src/Ryujinx.Horizon/Sdk/Sf/Cmif/ServerDomainManager.cs246
-rw-r--r--src/Ryujinx.Horizon/Sdk/Sf/Cmif/ServerMessageProcessor.cs18
-rw-r--r--src/Ryujinx.Horizon/Sdk/Sf/Cmif/ServerMessageRuntimeMetadata.cs30
-rw-r--r--src/Ryujinx.Horizon/Sdk/Sf/Cmif/ServiceDispatchContext.cs18
-rw-r--r--src/Ryujinx.Horizon/Sdk/Sf/Cmif/ServiceDispatchMeta.cs12
-rw-r--r--src/Ryujinx.Horizon/Sdk/Sf/Cmif/ServiceDispatchTable.cs33
-rw-r--r--src/Ryujinx.Horizon/Sdk/Sf/Cmif/ServiceDispatchTableBase.cs94
-rw-r--r--src/Ryujinx.Horizon/Sdk/Sf/Cmif/ServiceObjectHolder.cs34
26 files changed, 1077 insertions, 0 deletions
diff --git a/src/Ryujinx.Horizon/Sdk/Sf/Cmif/CmifDomainInHeader.cs b/src/Ryujinx.Horizon/Sdk/Sf/Cmif/CmifDomainInHeader.cs
new file mode 100644
index 00000000..beaff613
--- /dev/null
+++ b/src/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/src/Ryujinx.Horizon/Sdk/Sf/Cmif/CmifDomainOutHeader.cs b/src/Ryujinx.Horizon/Sdk/Sf/Cmif/CmifDomainOutHeader.cs
new file mode 100644
index 00000000..2086d24c
--- /dev/null
+++ b/src/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/src/Ryujinx.Horizon/Sdk/Sf/Cmif/CmifDomainRequestType.cs b/src/Ryujinx.Horizon/Sdk/Sf/Cmif/CmifDomainRequestType.cs
new file mode 100644
index 00000000..1a02e082
--- /dev/null
+++ b/src/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/src/Ryujinx.Horizon/Sdk/Sf/Cmif/CmifInHeader.cs b/src/Ryujinx.Horizon/Sdk/Sf/Cmif/CmifInHeader.cs
new file mode 100644
index 00000000..55b859fc
--- /dev/null
+++ b/src/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/src/Ryujinx.Horizon/Sdk/Sf/Cmif/CmifMessage.cs b/src/Ryujinx.Horizon/Sdk/Sf/Cmif/CmifMessage.cs
new file mode 100644
index 00000000..0d23d33b
--- /dev/null
+++ b/src/Ryujinx.Horizon/Sdk/Sf/Cmif/CmifMessage.cs
@@ -0,0 +1,135 @@
+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()
+ {
+ 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[(Unsafe.SizeOf<CmifDomainInHeader>() / sizeof(uint))..];
+
+ request.Objects = data[((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)[Unsafe.SizeOf<CmifInHeader>()..];
+
+ int paddingSizeBefore = (rawDataSizeInWords - request.Hipc.DataWords.Length) * sizeof(uint);
+
+ Span<byte> outPointerTable = MemoryMarshal.Cast<uint, byte>(request.Hipc.DataWords)[(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(input);
+
+ Span<byte> data = MemoryMarshal.Cast<uint, byte>(responseMessage.Data.DataWords);
+ Span<uint> objects = Span<uint>.Empty;
+
+ if (isDomain)
+ {
+ data = data[Unsafe.SizeOf<CmifDomainOutHeader>()..];
+ objects = MemoryMarshal.Cast<byte, uint>(data[(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[Unsafe.SizeOf<CmifOutHeader>()..],
+ Objects = objects,
+ CopyHandles = responseMessage.Data.CopyHandles,
+ MoveHandles = responseMessage.Data.MoveHandles
+ };
+
+ return Result.Success;
+ }
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.Horizon/Sdk/Sf/Cmif/CmifOutHeader.cs b/src/Ryujinx.Horizon/Sdk/Sf/Cmif/CmifOutHeader.cs
new file mode 100644
index 00000000..00b9d2bd
--- /dev/null
+++ b/src/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/src/Ryujinx.Horizon/Sdk/Sf/Cmif/CmifRequest.cs b/src/Ryujinx.Horizon/Sdk/Sf/Cmif/CmifRequest.cs
new file mode 100644
index 00000000..e44a84ec
--- /dev/null
+++ b/src/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/src/Ryujinx.Horizon/Sdk/Sf/Cmif/CmifRequestFormat.cs b/src/Ryujinx.Horizon/Sdk/Sf/Cmif/CmifRequestFormat.cs
new file mode 100644
index 00000000..592f11f4
--- /dev/null
+++ b/src/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/src/Ryujinx.Horizon/Sdk/Sf/Cmif/CmifResponse.cs b/src/Ryujinx.Horizon/Sdk/Sf/Cmif/CmifResponse.cs
new file mode 100644
index 00000000..2ff31eb6
--- /dev/null
+++ b/src/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/src/Ryujinx.Horizon/Sdk/Sf/Cmif/CommandType.cs b/src/Ryujinx.Horizon/Sdk/Sf/Cmif/CommandType.cs
new file mode 100644
index 00000000..82c0648b
--- /dev/null
+++ b/src/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/src/Ryujinx.Horizon/Sdk/Sf/Cmif/DomainServiceObject.cs b/src/Ryujinx.Horizon/Sdk/Sf/Cmif/DomainServiceObject.cs
new file mode 100644
index 00000000..14839687
--- /dev/null
+++ b/src/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/src/Ryujinx.Horizon/Sdk/Sf/Cmif/DomainServiceObjectDispatchTable.cs b/src/Ryujinx.Horizon/Sdk/Sf/Cmif/DomainServiceObjectDispatchTable.cs
new file mode 100644
index 00000000..b0b4498d
--- /dev/null
+++ b/src/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[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[..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/src/Ryujinx.Horizon/Sdk/Sf/Cmif/DomainServiceObjectProcessor.cs b/src/Ryujinx.Horizon/Sdk/Sf/Cmif/DomainServiceObjectProcessor.cs
new file mode 100644
index 00000000..796b8a78
--- /dev/null
+++ b/src/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)[..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[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[outHeaderSize..];
+
+ _domain.UnreserveIds(new Span<int>(_reservedObjectIds)[..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)[_outObjectIdsOffset..]);
+
+ for (int i = 0; i < outObjectsCount; i++)
+ {
+ outObjectIds[i] = objectIds[i];
+ }
+ }
+ }
+}
diff --git a/src/Ryujinx.Horizon/Sdk/Sf/Cmif/HandlesToClose.cs b/src/Ryujinx.Horizon/Sdk/Sf/Cmif/HandlesToClose.cs
new file mode 100644
index 00000000..0f3b259a
--- /dev/null
+++ b/src/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/src/Ryujinx.Horizon/Sdk/Sf/Cmif/InlineContext.cs b/src/Ryujinx.Horizon/Sdk/Sf/Cmif/InlineContext.cs
new file mode 100644
index 00000000..ddb6943f
--- /dev/null
+++ b/src/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/src/Ryujinx.Horizon/Sdk/Sf/Cmif/PointerAndSize.cs b/src/Ryujinx.Horizon/Sdk/Sf/Cmif/PointerAndSize.cs
new file mode 100644
index 00000000..ad0e1824
--- /dev/null
+++ b/src/Ryujinx.Horizon/Sdk/Sf/Cmif/PointerAndSize.cs
@@ -0,0 +1,17 @@
+namespace Ryujinx.Horizon.Sdk.Sf.Cmif
+{
+ struct PointerAndSize
+ {
+ public static PointerAndSize Empty => new(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/src/Ryujinx.Horizon/Sdk/Sf/Cmif/ScopedInlineContextChange.cs b/src/Ryujinx.Horizon/Sdk/Sf/Cmif/ScopedInlineContextChange.cs
new file mode 100644
index 00000000..eabe544f
--- /dev/null
+++ b/src/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/src/Ryujinx.Horizon/Sdk/Sf/Cmif/ServerDomainBase.cs b/src/Ryujinx.Horizon/Sdk/Sf/Cmif/ServerDomainBase.cs
new file mode 100644
index 00000000..f38fa030
--- /dev/null
+++ b/src/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/src/Ryujinx.Horizon/Sdk/Sf/Cmif/ServerDomainManager.cs b/src/Ryujinx.Horizon/Sdk/Sf/Cmif/ServerDomainManager.cs
new file mode 100644
index 00000000..62ee2738
--- /dev/null
+++ b/src/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/src/Ryujinx.Horizon/Sdk/Sf/Cmif/ServerMessageProcessor.cs b/src/Ryujinx.Horizon/Sdk/Sf/Cmif/ServerMessageProcessor.cs
new file mode 100644
index 00000000..e7650238
--- /dev/null
+++ b/src/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/src/Ryujinx.Horizon/Sdk/Sf/Cmif/ServerMessageRuntimeMetadata.cs b/src/Ryujinx.Horizon/Sdk/Sf/Cmif/ServerMessageRuntimeMetadata.cs
new file mode 100644
index 00000000..6a92e8d5
--- /dev/null
+++ b/src/Ryujinx.Horizon/Sdk/Sf/Cmif/ServerMessageRuntimeMetadata.cs
@@ -0,0 +1,30 @@
+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;
+ }
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.Horizon/Sdk/Sf/Cmif/ServiceDispatchContext.cs b/src/Ryujinx.Horizon/Sdk/Sf/Cmif/ServiceDispatchContext.cs
new file mode 100644
index 00000000..31be810d
--- /dev/null
+++ b/src/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;
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.Horizon/Sdk/Sf/Cmif/ServiceDispatchMeta.cs b/src/Ryujinx.Horizon/Sdk/Sf/Cmif/ServiceDispatchMeta.cs
new file mode 100644
index 00000000..7fbd8eb8
--- /dev/null
+++ b/src/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/src/Ryujinx.Horizon/Sdk/Sf/Cmif/ServiceDispatchTable.cs b/src/Ryujinx.Horizon/Sdk/Sf/Cmif/ServiceDispatchTable.cs
new file mode 100644
index 00000000..21b342df
--- /dev/null
+++ b/src/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());
+ }
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.Horizon/Sdk/Sf/Cmif/ServiceDispatchTableBase.cs b/src/Ryujinx.Horizon/Sdk/Sf/Cmif/ServiceDispatchTableBase.cs
new file mode 100644
index 00000000..81600067
--- /dev/null
+++ b/src/Ryujinx.Horizon/Sdk/Sf/Cmif/ServiceDispatchTableBase.cs
@@ -0,0 +1,94 @@
+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))
+ {
+ if (HorizonStatic.Options.IgnoreMissingServices)
+ {
+ // If ignore missing services is enabled, just pretend that everything is fine.
+ PrepareForStubReply(ref context, out Span<byte> outRawData);
+ CommandHandler.GetCmifOutHeaderPointer(ref outHeader, ref outRawData);
+ outHeader[0] = new CmifOutHeader() { Magic = CmifMessage.CmifOutHeaderMagic, Result = Result.Success };
+
+ Logger.Warning?.Print(LogClass.Service, $"Missing service {objectName} (command ID: {commandId}) ignored");
+
+ return Result.Success;
+ }
+ else if (HorizonStatic.Options.ThrowOnInvalidCommandIds)
+ {
+ throw new NotImplementedException($"{objectName} command ID: {commandId} is not implemented");
+ }
+
+ 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 void 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);
+ }
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.Horizon/Sdk/Sf/Cmif/ServiceObjectHolder.cs b/src/Ryujinx.Horizon/Sdk/Sf/Cmif/ServiceObjectHolder.cs
new file mode 100644
index 00000000..6e87e340
--- /dev/null
+++ b/src/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);
+ }
+ }
+}