mirror of
https://github.com/Ryujinx/Ryujinx.git
synced 2025-06-28 21:00:47 -07:00
Move solution and projects to src
This commit is contained in:
143
src/Ryujinx.Horizon/HeapAllocator.cs
Normal file
143
src/Ryujinx.Horizon/HeapAllocator.cs
Normal file
@ -0,0 +1,143 @@
|
||||
using Ryujinx.Common;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace Ryujinx.Horizon
|
||||
{
|
||||
class HeapAllocator
|
||||
{
|
||||
private const ulong InvalidAddress = ulong.MaxValue;
|
||||
|
||||
private struct Range : IComparable<Range>
|
||||
{
|
||||
public ulong Offset { get; }
|
||||
public ulong Size { get; }
|
||||
|
||||
public Range(ulong offset, ulong size)
|
||||
{
|
||||
Offset = offset;
|
||||
Size = size;
|
||||
}
|
||||
|
||||
public int CompareTo(Range other)
|
||||
{
|
||||
return Offset.CompareTo(other.Offset);
|
||||
}
|
||||
}
|
||||
|
||||
private readonly List<Range> _freeRanges;
|
||||
private ulong _currentHeapSize;
|
||||
|
||||
public HeapAllocator()
|
||||
{
|
||||
_freeRanges = new List<Range>();
|
||||
_currentHeapSize = 0;
|
||||
}
|
||||
|
||||
public ulong Allocate(ulong size, ulong alignment = 1UL)
|
||||
{
|
||||
ulong address = AllocateImpl(size, alignment);
|
||||
|
||||
if (address == InvalidAddress)
|
||||
{
|
||||
ExpandHeap(size + alignment - 1UL);
|
||||
|
||||
address = AllocateImpl(size, alignment);
|
||||
|
||||
Debug.Assert(address != InvalidAddress);
|
||||
}
|
||||
|
||||
return address;
|
||||
}
|
||||
|
||||
private void ExpandHeap(ulong expansionSize)
|
||||
{
|
||||
ulong oldHeapSize = _currentHeapSize;
|
||||
ulong newHeapSize = BitUtils.AlignUp(oldHeapSize + expansionSize, 0x200000UL);
|
||||
|
||||
_currentHeapSize = newHeapSize;
|
||||
|
||||
HorizonStatic.Syscall.SetHeapSize(out ulong heapAddress, newHeapSize).AbortOnFailure();
|
||||
|
||||
Free(heapAddress + oldHeapSize, newHeapSize - oldHeapSize);
|
||||
}
|
||||
|
||||
private ulong AllocateImpl(ulong size, ulong alignment)
|
||||
{
|
||||
for (int i = 0; i < _freeRanges.Count; i++)
|
||||
{
|
||||
var range = _freeRanges[i];
|
||||
|
||||
ulong alignedOffset = BitUtils.AlignUp(range.Offset, alignment);
|
||||
ulong sizeDelta = alignedOffset - range.Offset;
|
||||
ulong usableSize = range.Size - sizeDelta;
|
||||
|
||||
if (sizeDelta < range.Size && usableSize >= size)
|
||||
{
|
||||
_freeRanges.RemoveAt(i);
|
||||
|
||||
if (sizeDelta != 0)
|
||||
{
|
||||
InsertFreeRange(range.Offset, sizeDelta);
|
||||
}
|
||||
|
||||
ulong endOffset = range.Offset + range.Size;
|
||||
ulong remainingSize = endOffset - (alignedOffset + size);
|
||||
if (remainingSize != 0)
|
||||
{
|
||||
InsertFreeRange(endOffset - remainingSize, remainingSize);
|
||||
}
|
||||
|
||||
return alignedOffset;
|
||||
}
|
||||
}
|
||||
|
||||
return InvalidAddress;
|
||||
}
|
||||
|
||||
public void Free(ulong offset, ulong size)
|
||||
{
|
||||
InsertFreeRangeComingled(offset, size);
|
||||
}
|
||||
|
||||
private void InsertFreeRange(ulong offset, ulong size)
|
||||
{
|
||||
var range = new Range(offset, size);
|
||||
int index = _freeRanges.BinarySearch(range);
|
||||
if (index < 0)
|
||||
{
|
||||
index = ~index;
|
||||
}
|
||||
|
||||
_freeRanges.Insert(index, range);
|
||||
}
|
||||
|
||||
private void InsertFreeRangeComingled(ulong offset, ulong size)
|
||||
{
|
||||
ulong endOffset = offset + size;
|
||||
var range = new Range(offset, size);
|
||||
int index = _freeRanges.BinarySearch(range);
|
||||
if (index < 0)
|
||||
{
|
||||
index = ~index;
|
||||
}
|
||||
|
||||
if (index < _freeRanges.Count && _freeRanges[index].Offset == endOffset)
|
||||
{
|
||||
endOffset = _freeRanges[index].Offset + _freeRanges[index].Size;
|
||||
_freeRanges.RemoveAt(index);
|
||||
}
|
||||
|
||||
if (index > 0 && _freeRanges[index - 1].Offset + _freeRanges[index - 1].Size == offset)
|
||||
{
|
||||
offset = _freeRanges[index - 1].Offset;
|
||||
_freeRanges.RemoveAt(--index);
|
||||
}
|
||||
|
||||
range = new Range(offset, endOffset - offset);
|
||||
|
||||
_freeRanges.Insert(index, range);
|
||||
}
|
||||
}
|
||||
}
|
14
src/Ryujinx.Horizon/HorizonOptions.cs
Normal file
14
src/Ryujinx.Horizon/HorizonOptions.cs
Normal file
@ -0,0 +1,14 @@
|
||||
namespace Ryujinx.Horizon
|
||||
{
|
||||
public struct HorizonOptions
|
||||
{
|
||||
public bool IgnoreMissingServices { get; }
|
||||
public bool ThrowOnInvalidCommandIds { get; }
|
||||
|
||||
public HorizonOptions(bool ignoreMissingServices)
|
||||
{
|
||||
IgnoreMissingServices = ignoreMissingServices;
|
||||
ThrowOnInvalidCommandIds = true;
|
||||
}
|
||||
}
|
||||
}
|
44
src/Ryujinx.Horizon/HorizonStatic.cs
Normal file
44
src/Ryujinx.Horizon/HorizonStatic.cs
Normal file
@ -0,0 +1,44 @@
|
||||
using Ryujinx.Horizon.Common;
|
||||
using Ryujinx.Memory;
|
||||
using System;
|
||||
|
||||
namespace Ryujinx.Horizon
|
||||
{
|
||||
static class HorizonStatic
|
||||
{
|
||||
[ThreadStatic]
|
||||
private static HorizonOptions _options;
|
||||
|
||||
[ThreadStatic]
|
||||
private static ISyscallApi _syscall;
|
||||
|
||||
[ThreadStatic]
|
||||
private static IVirtualMemoryManager _addressSpace;
|
||||
|
||||
[ThreadStatic]
|
||||
private static IThreadContext _threadContext;
|
||||
|
||||
[ThreadStatic]
|
||||
private static int _threadHandle;
|
||||
|
||||
public static HorizonOptions Options => _options;
|
||||
public static ISyscallApi Syscall => _syscall;
|
||||
public static IVirtualMemoryManager AddressSpace => _addressSpace;
|
||||
public static IThreadContext ThreadContext => _threadContext;
|
||||
public static int CurrentThreadHandle => _threadHandle;
|
||||
|
||||
public static void Register(
|
||||
HorizonOptions options,
|
||||
ISyscallApi syscallApi,
|
||||
IVirtualMemoryManager addressSpace,
|
||||
IThreadContext threadContext,
|
||||
int threadHandle)
|
||||
{
|
||||
_options = options;
|
||||
_syscall = syscallApi;
|
||||
_addressSpace = addressSpace;
|
||||
_threadContext = threadContext;
|
||||
_threadHandle = threadHandle;
|
||||
}
|
||||
}
|
||||
}
|
7
src/Ryujinx.Horizon/IService.cs
Normal file
7
src/Ryujinx.Horizon/IService.cs
Normal file
@ -0,0 +1,7 @@
|
||||
namespace Ryujinx.Horizon
|
||||
{
|
||||
interface IService
|
||||
{
|
||||
abstract static void Main(ServiceTable serviceTable);
|
||||
}
|
||||
}
|
176
src/Ryujinx.Horizon/LogManager/Ipc/LmLogger.cs
Normal file
176
src/Ryujinx.Horizon/LogManager/Ipc/LmLogger.cs
Normal file
@ -0,0 +1,176 @@
|
||||
using Ryujinx.Common.Logging;
|
||||
using Ryujinx.Common.Memory;
|
||||
using Ryujinx.Horizon.Common;
|
||||
using Ryujinx.Horizon.LogManager.Types;
|
||||
using Ryujinx.Horizon.Sdk.Lm;
|
||||
using Ryujinx.Horizon.Sdk.Sf;
|
||||
using Ryujinx.Horizon.Sdk.Sf.Hipc;
|
||||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
|
||||
namespace Ryujinx.Horizon.LogManager.Ipc
|
||||
{
|
||||
partial class LmLogger : ILmLogger
|
||||
{
|
||||
private const int MessageLengthLimit = 5000;
|
||||
|
||||
private readonly LogService _log;
|
||||
private readonly ulong _pid;
|
||||
|
||||
private LogPacket _logPacket;
|
||||
|
||||
public LmLogger(LogService log, ulong pid)
|
||||
{
|
||||
_log = log;
|
||||
_pid = pid;
|
||||
|
||||
_logPacket = new LogPacket();
|
||||
}
|
||||
|
||||
[CmifCommand(0)]
|
||||
public Result Log([Buffer(HipcBufferFlags.In | HipcBufferFlags.AutoSelect)] Span<byte> message)
|
||||
{
|
||||
if (!SetProcessId(message, _pid))
|
||||
{
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
if (LogImpl(message))
|
||||
{
|
||||
Logger.Guest?.Print(LogClass.ServiceLm, _logPacket.ToString());
|
||||
|
||||
_logPacket = new LogPacket();
|
||||
}
|
||||
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
[CmifCommand(1)] // 3.0.0+
|
||||
public Result SetDestination(LogDestination destination)
|
||||
{
|
||||
_log.LogDestination = destination;
|
||||
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
private static bool SetProcessId(Span<byte> message, ulong processId)
|
||||
{
|
||||
ref LogPacketHeader header = ref MemoryMarshal.Cast<byte, LogPacketHeader>(message)[0];
|
||||
|
||||
uint expectedMessageSize = (uint)Unsafe.SizeOf<LogPacketHeader>() + header.PayloadSize;
|
||||
if (expectedMessageSize != (uint)message.Length)
|
||||
{
|
||||
Logger.Warning?.Print(LogClass.ServiceLm, $"Invalid message size (expected 0x{expectedMessageSize:X} but got 0x{message.Length:X}).");
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
header.ProcessId = processId;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private bool LogImpl(ReadOnlySpan<byte> message)
|
||||
{
|
||||
SpanReader reader = new(message);
|
||||
LogPacketHeader header = reader.Read<LogPacketHeader>();
|
||||
|
||||
bool isHeadPacket = (header.Flags & LogPacketFlags.IsHead) != 0;
|
||||
bool isTailPacket = (header.Flags & LogPacketFlags.IsTail) != 0;
|
||||
|
||||
_logPacket.Severity = header.Severity;
|
||||
|
||||
while (reader.Length > 0)
|
||||
{
|
||||
int type = ReadUleb128(ref reader);
|
||||
int size = ReadUleb128(ref reader);
|
||||
|
||||
LogDataChunkKey key = (LogDataChunkKey)type;
|
||||
|
||||
if (key == LogDataChunkKey.Start)
|
||||
{
|
||||
reader.Skip(size);
|
||||
|
||||
continue;
|
||||
}
|
||||
else if (key == LogDataChunkKey.Stop)
|
||||
{
|
||||
break;
|
||||
}
|
||||
else if (key == LogDataChunkKey.Line)
|
||||
{
|
||||
_logPacket.Line = reader.Read<int>();
|
||||
}
|
||||
else if (key == LogDataChunkKey.DropCount)
|
||||
{
|
||||
_logPacket.DropCount = reader.Read<long>();
|
||||
}
|
||||
else if (key == LogDataChunkKey.Time)
|
||||
{
|
||||
_logPacket.Time = reader.Read<long>();
|
||||
}
|
||||
else if (key == LogDataChunkKey.Message)
|
||||
{
|
||||
string text = Encoding.UTF8.GetString(reader.GetSpanSafe(size)).TrimEnd();
|
||||
|
||||
if (isHeadPacket && isTailPacket)
|
||||
{
|
||||
_logPacket.Message = text;
|
||||
}
|
||||
else
|
||||
{
|
||||
_logPacket.Message += text;
|
||||
|
||||
if (_logPacket.Message.Length >= MessageLengthLimit)
|
||||
{
|
||||
isTailPacket = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (key == LogDataChunkKey.Filename)
|
||||
{
|
||||
_logPacket.Filename = Encoding.UTF8.GetString(reader.GetSpanSafe(size)).TrimEnd();
|
||||
}
|
||||
else if (key == LogDataChunkKey.Function)
|
||||
{
|
||||
_logPacket.Function = Encoding.UTF8.GetString(reader.GetSpanSafe(size)).TrimEnd();
|
||||
}
|
||||
else if (key == LogDataChunkKey.Module)
|
||||
{
|
||||
_logPacket.Module = Encoding.UTF8.GetString(reader.GetSpanSafe(size)).TrimEnd();
|
||||
}
|
||||
else if (key == LogDataChunkKey.Thread)
|
||||
{
|
||||
_logPacket.Thread = Encoding.UTF8.GetString(reader.GetSpanSafe(size)).TrimEnd();
|
||||
}
|
||||
else if (key == LogDataChunkKey.ProgramName)
|
||||
{
|
||||
_logPacket.ProgramName = Encoding.UTF8.GetString(reader.GetSpanSafe(size)).TrimEnd();
|
||||
}
|
||||
}
|
||||
|
||||
return isTailPacket;
|
||||
}
|
||||
|
||||
private static int ReadUleb128(ref SpanReader reader)
|
||||
{
|
||||
int result = 0;
|
||||
int count = 0;
|
||||
|
||||
byte encoded;
|
||||
|
||||
do
|
||||
{
|
||||
encoded = reader.Read<byte>();
|
||||
|
||||
result += (encoded & 0x7F) << (7 * count);
|
||||
|
||||
count++;
|
||||
} while ((encoded & 0x80) != 0);
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
20
src/Ryujinx.Horizon/LogManager/Ipc/LogService.cs
Normal file
20
src/Ryujinx.Horizon/LogManager/Ipc/LogService.cs
Normal file
@ -0,0 +1,20 @@
|
||||
using Ryujinx.Horizon.Common;
|
||||
using Ryujinx.Horizon.Sdk.Lm;
|
||||
using Ryujinx.Horizon.Sdk.Sf;
|
||||
|
||||
namespace Ryujinx.Horizon.LogManager.Ipc
|
||||
{
|
||||
partial class LogService : ILogService
|
||||
{
|
||||
public LogDestination LogDestination { get; set; } = LogDestination.TargetManager;
|
||||
|
||||
[CmifCommand(0)]
|
||||
public Result OpenLogger(out LmLogger logger, [ClientProcessId] ulong pid)
|
||||
{
|
||||
// NOTE: Internal name is Logger, but we rename it LmLogger to avoid name clash with Ryujinx.Common.Logging logger.
|
||||
logger = new LmLogger(this, pid);
|
||||
|
||||
return Result.Success;
|
||||
}
|
||||
}
|
||||
}
|
43
src/Ryujinx.Horizon/LogManager/LmIpcServer.cs
Normal file
43
src/Ryujinx.Horizon/LogManager/LmIpcServer.cs
Normal file
@ -0,0 +1,43 @@
|
||||
using Ryujinx.Horizon.LogManager.Ipc;
|
||||
using Ryujinx.Horizon.Sdk.Sf.Hipc;
|
||||
using Ryujinx.Horizon.Sdk.Sm;
|
||||
|
||||
namespace Ryujinx.Horizon.LogManager
|
||||
{
|
||||
class LmIpcServer
|
||||
{
|
||||
private const int LogMaxSessionsCount = 42;
|
||||
|
||||
private const int PointerBufferSize = 0x400;
|
||||
private const int MaxDomains = 31;
|
||||
private const int MaxDomainObjects = 61;
|
||||
private const int MaxPortsCount = 1;
|
||||
|
||||
private static readonly ManagerOptions _logManagerOptions = new(PointerBufferSize, MaxDomains, MaxDomainObjects, false);
|
||||
|
||||
private SmApi _sm;
|
||||
private ServerManager _serverManager;
|
||||
|
||||
public void Initialize()
|
||||
{
|
||||
HeapAllocator allocator = new();
|
||||
|
||||
_sm = new SmApi();
|
||||
_sm.Initialize().AbortOnFailure();
|
||||
|
||||
_serverManager = new ServerManager(allocator, _sm, MaxPortsCount, _logManagerOptions, LogMaxSessionsCount);
|
||||
|
||||
_serverManager.RegisterObjectForServer(new LogService(), ServiceName.Encode("lm"), LogMaxSessionsCount);
|
||||
}
|
||||
|
||||
public void ServiceRequests()
|
||||
{
|
||||
_serverManager.ServiceRequests();
|
||||
}
|
||||
|
||||
public void Shutdown()
|
||||
{
|
||||
_serverManager.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
17
src/Ryujinx.Horizon/LogManager/LmMain.cs
Normal file
17
src/Ryujinx.Horizon/LogManager/LmMain.cs
Normal file
@ -0,0 +1,17 @@
|
||||
namespace Ryujinx.Horizon.LogManager
|
||||
{
|
||||
class LmMain : IService
|
||||
{
|
||||
public static void Main(ServiceTable serviceTable)
|
||||
{
|
||||
LmIpcServer ipcServer = new();
|
||||
|
||||
ipcServer.Initialize();
|
||||
|
||||
serviceTable.SignalServiceReady();
|
||||
|
||||
ipcServer.ServiceRequests();
|
||||
ipcServer.Shutdown();
|
||||
}
|
||||
}
|
||||
}
|
72
src/Ryujinx.Horizon/LogManager/Types/LogPacket.cs
Normal file
72
src/Ryujinx.Horizon/LogManager/Types/LogPacket.cs
Normal file
@ -0,0 +1,72 @@
|
||||
using Ryujinx.Horizon.Sdk.Diag;
|
||||
using System.Text;
|
||||
|
||||
namespace Ryujinx.Horizon.LogManager.Types
|
||||
{
|
||||
struct LogPacket
|
||||
{
|
||||
public string Message;
|
||||
public int Line;
|
||||
public string Filename;
|
||||
public string Function;
|
||||
public string Module;
|
||||
public string Thread;
|
||||
public long DropCount;
|
||||
public long Time;
|
||||
public string ProgramName;
|
||||
public LogSeverity Severity;
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
StringBuilder builder = new();
|
||||
builder.AppendLine($"Guest Log:\n Log level: {Severity}");
|
||||
|
||||
if (Time > 0)
|
||||
{
|
||||
builder.AppendLine($" Time: {Time}s");
|
||||
}
|
||||
|
||||
if (DropCount > 0)
|
||||
{
|
||||
builder.AppendLine($" DropCount: {DropCount}");
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(ProgramName))
|
||||
{
|
||||
builder.AppendLine($" ProgramName: {ProgramName}");
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(Module))
|
||||
{
|
||||
builder.AppendLine($" Module: {Module}");
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(Thread))
|
||||
{
|
||||
builder.AppendLine($" Thread: {Thread}");
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(Filename))
|
||||
{
|
||||
builder.AppendLine($" Filename: {Filename}");
|
||||
}
|
||||
|
||||
if (Line > 0)
|
||||
{
|
||||
builder.AppendLine($" Line: {Line}");
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(Function))
|
||||
{
|
||||
builder.AppendLine($" Function: {Function}");
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(Message))
|
||||
{
|
||||
builder.AppendLine($" Message: {Message}");
|
||||
}
|
||||
|
||||
return builder.ToString();
|
||||
}
|
||||
}
|
||||
}
|
225
src/Ryujinx.Horizon/Prepo/Ipc/PrepoService.cs
Normal file
225
src/Ryujinx.Horizon/Prepo/Ipc/PrepoService.cs
Normal file
@ -0,0 +1,225 @@
|
||||
using MsgPack;
|
||||
using MsgPack.Serialization;
|
||||
using Ryujinx.Common.Logging;
|
||||
using Ryujinx.Common.Utilities;
|
||||
using Ryujinx.Horizon.Common;
|
||||
using Ryujinx.Horizon.Prepo.Types;
|
||||
using Ryujinx.Horizon.Sdk.Account;
|
||||
using Ryujinx.Horizon.Sdk.Prepo;
|
||||
using Ryujinx.Horizon.Sdk.Sf;
|
||||
using Ryujinx.Horizon.Sdk.Sf.Hipc;
|
||||
using System;
|
||||
using System.Text;
|
||||
|
||||
namespace Ryujinx.Horizon.Prepo.Ipc
|
||||
{
|
||||
partial class PrepoService : IPrepoService
|
||||
{
|
||||
enum PlayReportKind
|
||||
{
|
||||
Normal,
|
||||
System
|
||||
}
|
||||
|
||||
private readonly PrepoServicePermissionLevel _permissionLevel;
|
||||
private ulong _systemSessionId;
|
||||
|
||||
private bool _immediateTransmissionEnabled = false;
|
||||
private bool _userAgreementCheckEnabled = true;
|
||||
|
||||
public PrepoService(PrepoServicePermissionLevel permissionLevel)
|
||||
{
|
||||
_permissionLevel = permissionLevel;
|
||||
}
|
||||
|
||||
[CmifCommand(10100)] // 1.0.0-5.1.0
|
||||
[CmifCommand(10102)] // 6.0.0-9.2.0
|
||||
[CmifCommand(10104)] // 10.0.0+
|
||||
public Result SaveReport([Buffer(HipcBufferFlags.In | HipcBufferFlags.Pointer)] ReadOnlySpan<byte> gameRoomBuffer, [Buffer(HipcBufferFlags.In | HipcBufferFlags.MapAlias)] ReadOnlySpan<byte> reportBuffer, [ClientProcessId] ulong pid)
|
||||
{
|
||||
if ((_permissionLevel & PrepoServicePermissionLevel.User) == 0)
|
||||
{
|
||||
return PrepoResult.PermissionDenied;
|
||||
}
|
||||
|
||||
ProcessPlayReport(PlayReportKind.Normal, gameRoomBuffer, reportBuffer, pid, Uid.Null);
|
||||
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
[CmifCommand(10101)] // 1.0.0-5.1.0
|
||||
[CmifCommand(10103)] // 6.0.0-9.2.0
|
||||
[CmifCommand(10105)] // 10.0.0+
|
||||
public Result SaveReportWithUser(Uid userId, [Buffer(HipcBufferFlags.In | HipcBufferFlags.Pointer)] ReadOnlySpan<byte> gameRoomBuffer, [Buffer(HipcBufferFlags.In | HipcBufferFlags.MapAlias)] ReadOnlySpan<byte> reportBuffer, [ClientProcessId] ulong pid)
|
||||
{
|
||||
if ((_permissionLevel & PrepoServicePermissionLevel.User) == 0)
|
||||
{
|
||||
return PrepoResult.PermissionDenied;
|
||||
}
|
||||
|
||||
ProcessPlayReport(PlayReportKind.Normal, gameRoomBuffer, reportBuffer, pid, userId, true);
|
||||
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
[CmifCommand(10200)]
|
||||
public Result RequestImmediateTransmission()
|
||||
{
|
||||
_immediateTransmissionEnabled = true;
|
||||
|
||||
// It signals an event of nn::prepo::detail::service::core::TransmissionStatusManager that requests the transmission of the report.
|
||||
// Since we don't use reports, it's fine to do nothing.
|
||||
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
[CmifCommand(10300)]
|
||||
public Result GetTransmissionStatus(out int status)
|
||||
{
|
||||
status = 0;
|
||||
|
||||
if (_immediateTransmissionEnabled && _userAgreementCheckEnabled)
|
||||
{
|
||||
status = 1;
|
||||
}
|
||||
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
[CmifCommand(10400)] // 9.0.0+
|
||||
public Result GetSystemSessionId(out ulong systemSessionId)
|
||||
{
|
||||
systemSessionId = default;
|
||||
|
||||
if ((_permissionLevel & PrepoServicePermissionLevel.User) == 0)
|
||||
{
|
||||
return PrepoResult.PermissionDenied;
|
||||
}
|
||||
|
||||
if (_systemSessionId == 0)
|
||||
{
|
||||
_systemSessionId = (ulong)Random.Shared.NextInt64();
|
||||
}
|
||||
|
||||
systemSessionId = _systemSessionId;
|
||||
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
[CmifCommand(20100)]
|
||||
public Result SaveSystemReport([Buffer(HipcBufferFlags.In | HipcBufferFlags.Pointer)] ReadOnlySpan<byte> gameRoomBuffer, Sdk.Ncm.ApplicationId applicationId, [Buffer(HipcBufferFlags.In | HipcBufferFlags.MapAlias)] ReadOnlySpan<byte> reportBuffer)
|
||||
{
|
||||
if ((_permissionLevel & PrepoServicePermissionLevel.System) != 0)
|
||||
{
|
||||
return PrepoResult.PermissionDenied;
|
||||
}
|
||||
|
||||
return ProcessPlayReport(PlayReportKind.System, gameRoomBuffer, reportBuffer, 0, Uid.Null, false, applicationId);
|
||||
}
|
||||
|
||||
[CmifCommand(20101)]
|
||||
public Result SaveSystemReportWithUser(Uid userId, [Buffer(HipcBufferFlags.In | HipcBufferFlags.Pointer)] ReadOnlySpan<byte> gameRoomBuffer, Sdk.Ncm.ApplicationId applicationId, [Buffer(HipcBufferFlags.In | HipcBufferFlags.MapAlias)] ReadOnlySpan<byte> reportBuffer)
|
||||
{
|
||||
if ((_permissionLevel & PrepoServicePermissionLevel.System) != 0)
|
||||
{
|
||||
return PrepoResult.PermissionDenied;
|
||||
}
|
||||
|
||||
return ProcessPlayReport(PlayReportKind.System, gameRoomBuffer, reportBuffer, 0, userId, true, applicationId);
|
||||
}
|
||||
|
||||
[CmifCommand(40100)] // 2.0.0+
|
||||
public Result IsUserAgreementCheckEnabled(out bool enabled)
|
||||
{
|
||||
enabled = false;
|
||||
|
||||
if (_permissionLevel == PrepoServicePermissionLevel.User || _permissionLevel == PrepoServicePermissionLevel.System)
|
||||
{
|
||||
enabled = _userAgreementCheckEnabled;
|
||||
|
||||
// If "enabled" is false, it sets some internal fields to 0.
|
||||
// Then, it mounts "prepo-sys:/is_user_agreement_check_enabled.bin" and returns the contained bool.
|
||||
// We can return the private bool instead, we don't care about the agreement since we don't send reports.
|
||||
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
return PrepoResult.PermissionDenied;
|
||||
}
|
||||
|
||||
[CmifCommand(40101)] // 2.0.0+
|
||||
public Result SetUserAgreementCheckEnabled(bool enabled)
|
||||
{
|
||||
if (_permissionLevel == PrepoServicePermissionLevel.User || _permissionLevel == PrepoServicePermissionLevel.System)
|
||||
{
|
||||
_userAgreementCheckEnabled = enabled;
|
||||
|
||||
// If "enabled" is false, it sets some internal fields to 0.
|
||||
// Then, it mounts "prepo-sys:/is_user_agreement_check_enabled.bin" and stores the "enabled" value.
|
||||
// We can store in the private bool instead, we don't care about the agreement since we don't send reports.
|
||||
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
return PrepoResult.PermissionDenied;
|
||||
}
|
||||
|
||||
private static Result ProcessPlayReport(PlayReportKind playReportKind, ReadOnlySpan<byte> gameRoomBuffer, ReadOnlySpan<byte> reportBuffer, ulong pid, Uid userId, bool withUserId = false, Sdk.Ncm.ApplicationId applicationId = default)
|
||||
{
|
||||
if (withUserId)
|
||||
{
|
||||
if (userId.IsNull)
|
||||
{
|
||||
return PrepoResult.InvalidArgument;
|
||||
}
|
||||
}
|
||||
|
||||
if (gameRoomBuffer.Length > 31)
|
||||
{
|
||||
return PrepoResult.InvalidArgument;
|
||||
}
|
||||
|
||||
string gameRoom = Encoding.UTF8.GetString(gameRoomBuffer).TrimEnd();
|
||||
|
||||
if (gameRoom == string.Empty)
|
||||
{
|
||||
return PrepoResult.InvalidState;
|
||||
}
|
||||
|
||||
if (reportBuffer.Length == 0)
|
||||
{
|
||||
return PrepoResult.InvalidBufferSize;
|
||||
}
|
||||
|
||||
StringBuilder builder = new();
|
||||
MessagePackObject deserializedReport = MessagePackSerializer.UnpackMessagePackObject(reportBuffer.ToArray());
|
||||
|
||||
builder.AppendLine();
|
||||
builder.AppendLine("PlayReport log:");
|
||||
builder.AppendLine($" Kind: {playReportKind}");
|
||||
|
||||
// NOTE: The service calls arp:r using the pid to get the application id, if it fails PrepoResult.InvalidPid is returned.
|
||||
// Reports are stored internally and an event is signaled to transmit them.
|
||||
if (pid != 0)
|
||||
{
|
||||
builder.AppendLine($" Pid: {pid}");
|
||||
}
|
||||
else
|
||||
{
|
||||
builder.AppendLine($" ApplicationId: {applicationId}");
|
||||
}
|
||||
|
||||
if (!userId.IsNull)
|
||||
{
|
||||
builder.AppendLine($" UserId: {userId}");
|
||||
}
|
||||
|
||||
builder.AppendLine($" Room: {gameRoom}");
|
||||
builder.AppendLine($" Report: {MessagePackObjectFormatter.Format(deserializedReport)}");
|
||||
|
||||
Logger.Info?.Print(LogClass.ServicePrepo, builder.ToString());
|
||||
|
||||
return Result.Success;
|
||||
}
|
||||
}
|
||||
}
|
49
src/Ryujinx.Horizon/Prepo/PrepoIpcServer.cs
Normal file
49
src/Ryujinx.Horizon/Prepo/PrepoIpcServer.cs
Normal file
@ -0,0 +1,49 @@
|
||||
using Ryujinx.Horizon.Prepo.Types;
|
||||
using Ryujinx.Horizon.Sdk.Sf.Hipc;
|
||||
using Ryujinx.Horizon.Sdk.Sm;
|
||||
|
||||
namespace Ryujinx.Horizon.Prepo
|
||||
{
|
||||
class PrepoIpcServer
|
||||
{
|
||||
private const int PrepoMaxSessionsCount = 12;
|
||||
private const int PrepoTotalMaxSessionsCount = PrepoMaxSessionsCount * 6;
|
||||
|
||||
private const int PointerBufferSize = 0x3800;
|
||||
private const int MaxDomains = 64;
|
||||
private const int MaxDomainObjects = 16;
|
||||
private const int MaxPortsCount = 6;
|
||||
|
||||
private static readonly ManagerOptions _logManagerOptions = new(PointerBufferSize, MaxDomains, MaxDomainObjects, false);
|
||||
|
||||
private SmApi _sm;
|
||||
private PrepoServerManager _serverManager;
|
||||
|
||||
public void Initialize()
|
||||
{
|
||||
HeapAllocator allocator = new();
|
||||
|
||||
_sm = new SmApi();
|
||||
_sm.Initialize().AbortOnFailure();
|
||||
|
||||
_serverManager = new PrepoServerManager(allocator, _sm, MaxPortsCount, _logManagerOptions, PrepoTotalMaxSessionsCount);
|
||||
|
||||
_serverManager.RegisterServer((int)PrepoPortIndex.Admin, ServiceName.Encode("prepo:a"), PrepoMaxSessionsCount); // 1.0.0-5.1.0
|
||||
_serverManager.RegisterServer((int)PrepoPortIndex.Admin2, ServiceName.Encode("prepo:a2"), PrepoMaxSessionsCount); // 6.0.0+
|
||||
_serverManager.RegisterServer((int)PrepoPortIndex.Manager, ServiceName.Encode("prepo:m"), PrepoMaxSessionsCount);
|
||||
_serverManager.RegisterServer((int)PrepoPortIndex.User, ServiceName.Encode("prepo:u"), PrepoMaxSessionsCount);
|
||||
_serverManager.RegisterServer((int)PrepoPortIndex.System, ServiceName.Encode("prepo:s"), PrepoMaxSessionsCount);
|
||||
_serverManager.RegisterServer((int)PrepoPortIndex.Debug, ServiceName.Encode("prepo:d"), PrepoMaxSessionsCount); // 1.0.0
|
||||
}
|
||||
|
||||
public void ServiceRequests()
|
||||
{
|
||||
_serverManager.ServiceRequests();
|
||||
}
|
||||
|
||||
public void Shutdown()
|
||||
{
|
||||
_serverManager.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
17
src/Ryujinx.Horizon/Prepo/PrepoMain.cs
Normal file
17
src/Ryujinx.Horizon/Prepo/PrepoMain.cs
Normal file
@ -0,0 +1,17 @@
|
||||
namespace Ryujinx.Horizon.Prepo
|
||||
{
|
||||
class PrepoMain : IService
|
||||
{
|
||||
public static void Main(ServiceTable serviceTable)
|
||||
{
|
||||
PrepoIpcServer ipcServer = new();
|
||||
|
||||
ipcServer.Initialize();
|
||||
|
||||
serviceTable.SignalServiceReady();
|
||||
|
||||
ipcServer.ServiceRequests();
|
||||
ipcServer.Shutdown();
|
||||
}
|
||||
}
|
||||
}
|
15
src/Ryujinx.Horizon/Prepo/PrepoResult.cs
Normal file
15
src/Ryujinx.Horizon/Prepo/PrepoResult.cs
Normal file
@ -0,0 +1,15 @@
|
||||
using Ryujinx.Horizon.Common;
|
||||
|
||||
namespace Ryujinx.Horizon.Prepo
|
||||
{
|
||||
static class PrepoResult
|
||||
{
|
||||
private const int ModuleId = 129;
|
||||
|
||||
public static Result InvalidArgument => new(ModuleId, 1);
|
||||
public static Result InvalidState => new(ModuleId, 5);
|
||||
public static Result InvalidBufferSize => new(ModuleId, 9);
|
||||
public static Result PermissionDenied => new(ModuleId, 90);
|
||||
public static Result InvalidPid => new(ModuleId, 101);
|
||||
}
|
||||
}
|
30
src/Ryujinx.Horizon/Prepo/PrepoServerManager.cs
Normal file
30
src/Ryujinx.Horizon/Prepo/PrepoServerManager.cs
Normal file
@ -0,0 +1,30 @@
|
||||
using Ryujinx.Horizon.Common;
|
||||
using Ryujinx.Horizon.Prepo.Ipc;
|
||||
using Ryujinx.Horizon.Prepo.Types;
|
||||
using Ryujinx.Horizon.Sdk.Sf.Hipc;
|
||||
using Ryujinx.Horizon.Sdk.Sm;
|
||||
using System;
|
||||
|
||||
namespace Ryujinx.Horizon.Prepo
|
||||
{
|
||||
class PrepoServerManager : ServerManager
|
||||
{
|
||||
public PrepoServerManager(HeapAllocator allocator, SmApi sm, int maxPorts, ManagerOptions options, int maxSessions) : base(allocator, sm, maxPorts, options, maxSessions)
|
||||
{
|
||||
}
|
||||
|
||||
protected override Result OnNeedsToAccept(int portIndex, Server server)
|
||||
{
|
||||
return (PrepoPortIndex)portIndex switch
|
||||
{
|
||||
PrepoPortIndex.Admin => AcceptImpl(server, new PrepoService(PrepoServicePermissionLevel.Admin)),
|
||||
PrepoPortIndex.Admin2 => AcceptImpl(server, new PrepoService(PrepoServicePermissionLevel.Admin)),
|
||||
PrepoPortIndex.Manager => AcceptImpl(server, new PrepoService(PrepoServicePermissionLevel.Manager)),
|
||||
PrepoPortIndex.User => AcceptImpl(server, new PrepoService(PrepoServicePermissionLevel.User)),
|
||||
PrepoPortIndex.System => AcceptImpl(server, new PrepoService(PrepoServicePermissionLevel.System)),
|
||||
PrepoPortIndex.Debug => AcceptImpl(server, new PrepoService(PrepoServicePermissionLevel.Debug)),
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(portIndex)),
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
12
src/Ryujinx.Horizon/Prepo/Types/PrepoPortIndex.cs
Normal file
12
src/Ryujinx.Horizon/Prepo/Types/PrepoPortIndex.cs
Normal file
@ -0,0 +1,12 @@
|
||||
namespace Ryujinx.Horizon.Prepo.Types
|
||||
{
|
||||
enum PrepoPortIndex
|
||||
{
|
||||
Admin,
|
||||
Admin2,
|
||||
Manager,
|
||||
User,
|
||||
System,
|
||||
Debug
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
namespace Ryujinx.Horizon.Prepo.Types
|
||||
{
|
||||
enum PrepoServicePermissionLevel
|
||||
{
|
||||
Admin = -1,
|
||||
User = 1,
|
||||
System = 2,
|
||||
Manager = 6,
|
||||
Debug = unchecked((int)0x80000006)
|
||||
}
|
||||
}
|
18
src/Ryujinx.Horizon/Ryujinx.Horizon.csproj
Normal file
18
src/Ryujinx.Horizon/Ryujinx.Horizon.csproj
Normal file
@ -0,0 +1,18 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net7.0</TargetFramework>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Ryujinx.Common\Ryujinx.Common.csproj" />
|
||||
<ProjectReference Include="..\Ryujinx.Horizon.Common\Ryujinx.Horizon.Common.csproj" />
|
||||
<ProjectReference Include="..\Ryujinx.Horizon.Generators\Ryujinx.Horizon.Generators.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false" />
|
||||
<ProjectReference Include="..\Ryujinx.Memory\Ryujinx.Memory.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="LibHac" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
62
src/Ryujinx.Horizon/Sdk/Account/Uid.cs
Normal file
62
src/Ryujinx.Horizon/Sdk/Account/Uid.cs
Normal file
@ -0,0 +1,62 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Ryujinx.Horizon.Sdk.Account
|
||||
{
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
readonly record struct Uid
|
||||
{
|
||||
public readonly long High;
|
||||
public readonly long Low;
|
||||
|
||||
public bool IsNull => (Low | High) == 0;
|
||||
|
||||
public static Uid Null => new(0, 0);
|
||||
|
||||
public Uid(long low, long high)
|
||||
{
|
||||
Low = low;
|
||||
High = high;
|
||||
}
|
||||
|
||||
public Uid(byte[] bytes)
|
||||
{
|
||||
High = BitConverter.ToInt64(bytes, 0);
|
||||
Low = BitConverter.ToInt64(bytes, 8);
|
||||
}
|
||||
|
||||
public Uid(string hex)
|
||||
{
|
||||
if (hex == null || hex.Length != 32 || !hex.All("0123456789abcdefABCDEF".Contains))
|
||||
{
|
||||
throw new ArgumentException("Invalid Hex value!", nameof(hex));
|
||||
}
|
||||
|
||||
Low = Convert.ToInt64(hex[16..], 16);
|
||||
High = Convert.ToInt64(hex[..16], 16);
|
||||
}
|
||||
|
||||
public void Write(BinaryWriter binaryWriter)
|
||||
{
|
||||
binaryWriter.Write(High);
|
||||
binaryWriter.Write(Low);
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return High.ToString("x16") + Low.ToString("x16");
|
||||
}
|
||||
|
||||
public LibHac.Account.Uid ToLibHacUid()
|
||||
{
|
||||
return new LibHac.Account.Uid((ulong)High, (ulong)Low);
|
||||
}
|
||||
|
||||
public UInt128 ToUInt128()
|
||||
{
|
||||
return new UInt128((ulong)High, (ulong)Low);
|
||||
}
|
||||
}
|
||||
}
|
12
src/Ryujinx.Horizon/Sdk/DebugUtil.cs
Normal file
12
src/Ryujinx.Horizon/Sdk/DebugUtil.cs
Normal file
@ -0,0 +1,12 @@
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace Ryujinx.Horizon.Sdk
|
||||
{
|
||||
static class DebugUtil
|
||||
{
|
||||
public static void Assert(bool condition)
|
||||
{
|
||||
Debug.Assert(condition);
|
||||
}
|
||||
}
|
||||
}
|
11
src/Ryujinx.Horizon/Sdk/Diag/LogSeverity.cs
Normal file
11
src/Ryujinx.Horizon/Sdk/Diag/LogSeverity.cs
Normal file
@ -0,0 +1,11 @@
|
||||
namespace Ryujinx.Horizon.Sdk.Diag
|
||||
{
|
||||
enum LogSeverity : byte
|
||||
{
|
||||
Trace = 0,
|
||||
Info = 1,
|
||||
Warn = 2,
|
||||
Error = 3,
|
||||
Fatal = 4
|
||||
}
|
||||
}
|
12
src/Ryujinx.Horizon/Sdk/Lm/ILmLogger.cs
Normal file
12
src/Ryujinx.Horizon/Sdk/Lm/ILmLogger.cs
Normal file
@ -0,0 +1,12 @@
|
||||
using Ryujinx.Horizon.Common;
|
||||
using Ryujinx.Horizon.Sdk.Sf;
|
||||
using System;
|
||||
|
||||
namespace Ryujinx.Horizon.Sdk.Lm
|
||||
{
|
||||
interface ILmLogger : IServiceObject
|
||||
{
|
||||
Result Log(Span<byte> message);
|
||||
Result SetDestination(LogDestination destination);
|
||||
}
|
||||
}
|
11
src/Ryujinx.Horizon/Sdk/Lm/ILogService.cs
Normal file
11
src/Ryujinx.Horizon/Sdk/Lm/ILogService.cs
Normal file
@ -0,0 +1,11 @@
|
||||
using Ryujinx.Horizon.Common;
|
||||
using Ryujinx.Horizon.LogManager.Ipc;
|
||||
using Ryujinx.Horizon.Sdk.Sf;
|
||||
|
||||
namespace Ryujinx.Horizon.Sdk.Lm
|
||||
{
|
||||
interface ILogService : IServiceObject
|
||||
{
|
||||
Result OpenLogger(out LmLogger logger, ulong pid);
|
||||
}
|
||||
}
|
19
src/Ryujinx.Horizon/Sdk/Lm/LogDataChunkKey.cs
Normal file
19
src/Ryujinx.Horizon/Sdk/Lm/LogDataChunkKey.cs
Normal file
@ -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
|
||||
}
|
||||
}
|
14
src/Ryujinx.Horizon/Sdk/Lm/LogDestination.cs
Normal file
14
src/Ryujinx.Horizon/Sdk/Lm/LogDestination.cs
Normal file
@ -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
|
||||
}
|
||||
}
|
12
src/Ryujinx.Horizon/Sdk/Lm/LogPacketFlags.cs
Normal file
12
src/Ryujinx.Horizon/Sdk/Lm/LogPacketFlags.cs
Normal file
@ -0,0 +1,12 @@
|
||||
using System;
|
||||
|
||||
namespace Ryujinx.Horizon.Sdk.Lm
|
||||
{
|
||||
[Flags]
|
||||
enum LogPacketFlags : byte
|
||||
{
|
||||
IsHead = 1 << 0,
|
||||
IsTail = 1 << 1,
|
||||
IsLittleEndian = 1 << 2
|
||||
}
|
||||
}
|
15
src/Ryujinx.Horizon/Sdk/Lm/LogPacketHeader.cs
Normal file
15
src/Ryujinx.Horizon/Sdk/Lm/LogPacketHeader.cs
Normal file
@ -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;
|
||||
}
|
||||
}
|
52
src/Ryujinx.Horizon/Sdk/Ncm/ApplicationId.cs
Normal file
52
src/Ryujinx.Horizon/Sdk/Ncm/ApplicationId.cs
Normal file
@ -0,0 +1,52 @@
|
||||
namespace Ryujinx.Horizon.Sdk.Ncm
|
||||
{
|
||||
readonly struct ApplicationId
|
||||
{
|
||||
public readonly ulong Id;
|
||||
|
||||
public static int Length => sizeof(ulong);
|
||||
|
||||
public static ApplicationId First => new(0x0100000000010000);
|
||||
|
||||
public static ApplicationId Last => new(0x01FFFFFFFFFFFFFF);
|
||||
|
||||
public static ApplicationId Invalid => new(0);
|
||||
|
||||
public bool IsValid => Id >= First.Id && Id <= Last.Id;
|
||||
|
||||
public ApplicationId(ulong id)
|
||||
{
|
||||
Id = id;
|
||||
}
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
return obj is ApplicationId applicationId && applicationId.Equals(this);
|
||||
}
|
||||
|
||||
public bool Equals(ApplicationId other)
|
||||
{
|
||||
return other.Id == Id;
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return Id.GetHashCode();
|
||||
}
|
||||
|
||||
public static bool operator ==(ApplicationId lhs, ApplicationId rhs)
|
||||
{
|
||||
return lhs.Equals(rhs);
|
||||
}
|
||||
|
||||
public static bool operator !=(ApplicationId lhs, ApplicationId rhs)
|
||||
{
|
||||
return !lhs.Equals(rhs);
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return $"0x{Id:x}";
|
||||
}
|
||||
}
|
||||
}
|
61
src/Ryujinx.Horizon/Sdk/OsTypes/Event.cs
Normal file
61
src/Ryujinx.Horizon/Sdk/OsTypes/Event.cs
Normal file
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
8
src/Ryujinx.Horizon/Sdk/OsTypes/EventClearMode.cs
Normal file
8
src/Ryujinx.Horizon/Sdk/OsTypes/EventClearMode.cs
Normal file
@ -0,0 +1,8 @@
|
||||
namespace Ryujinx.Horizon.Sdk.OsTypes
|
||||
{
|
||||
enum EventClearMode
|
||||
{
|
||||
ManualClear,
|
||||
AutoClear
|
||||
}
|
||||
}
|
15
src/Ryujinx.Horizon/Sdk/OsTypes/EventType.cs
Normal file
15
src/Ryujinx.Horizon/Sdk/OsTypes/EventType.cs
Normal file
@ -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;
|
||||
}
|
||||
}
|
89
src/Ryujinx.Horizon/Sdk/OsTypes/Impl/InterProcessEvent.cs
Normal file
89
src/Ryujinx.Horizon/Sdk/OsTypes/Impl/InterProcessEvent.cs
Normal file
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
136
src/Ryujinx.Horizon/Sdk/OsTypes/Impl/InterProcessEventImpl.cs
Normal file
136
src/Ryujinx.Horizon/Sdk/OsTypes/Impl/InterProcessEventImpl.cs
Normal file
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
250
src/Ryujinx.Horizon/Sdk/OsTypes/Impl/MultiWaitImpl.cs
Normal file
250
src/Ryujinx.Horizon/Sdk/OsTypes/Impl/MultiWaitImpl.cs
Normal file
@ -0,0 +1,250 @@
|
||||
using Ryujinx.Common;
|
||||
using Ryujinx.Horizon.Common;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
8
src/Ryujinx.Horizon/Sdk/OsTypes/InitializationState.cs
Normal file
8
src/Ryujinx.Horizon/Sdk/OsTypes/InitializationState.cs
Normal file
@ -0,0 +1,8 @@
|
||||
namespace Ryujinx.Horizon.Sdk.OsTypes
|
||||
{
|
||||
enum InitializationState : byte
|
||||
{
|
||||
NotInitialized,
|
||||
Initialized
|
||||
}
|
||||
}
|
27
src/Ryujinx.Horizon/Sdk/OsTypes/InterProcessEventType.cs
Normal file
27
src/Ryujinx.Horizon/Sdk/OsTypes/InterProcessEventType.cs
Normal file
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
43
src/Ryujinx.Horizon/Sdk/OsTypes/MultiWait.cs
Normal file
43
src/Ryujinx.Horizon/Sdk/OsTypes/MultiWait.cs
Normal file
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
16
src/Ryujinx.Horizon/Sdk/OsTypes/MultiWaitHolder.cs
Normal file
16
src/Ryujinx.Horizon/Sdk/OsTypes/MultiWaitHolder.cs
Normal file
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
39
src/Ryujinx.Horizon/Sdk/OsTypes/MultiWaitHolderBase.cs
Normal file
39
src/Ryujinx.Horizon/Sdk/OsTypes/MultiWaitHolderBase.cs
Normal file
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
45
src/Ryujinx.Horizon/Sdk/OsTypes/MultiWaitHolderOfEvent.cs
Normal file
45
src/Ryujinx.Horizon/Sdk/OsTypes/MultiWaitHolderOfEvent.cs
Normal file
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
14
src/Ryujinx.Horizon/Sdk/OsTypes/MultiWaitHolderOfHandle.cs
Normal file
14
src/Ryujinx.Horizon/Sdk/OsTypes/MultiWaitHolderOfHandle.cs
Normal file
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
130
src/Ryujinx.Horizon/Sdk/OsTypes/OsEvent.cs
Normal file
130
src/Ryujinx.Horizon/Sdk/OsTypes/OsEvent.cs
Normal file
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
10
src/Ryujinx.Horizon/Sdk/OsTypes/OsMultiWait.cs
Normal file
10
src/Ryujinx.Horizon/Sdk/OsTypes/OsMultiWait.cs
Normal file
@ -0,0 +1,10 @@
|
||||
namespace Ryujinx.Horizon.Sdk.OsTypes
|
||||
{
|
||||
static partial class Os
|
||||
{
|
||||
public static void FinalizeMultiWaitHolder(MultiWaitHolderBase holder)
|
||||
{
|
||||
DebugUtil.Assert(!holder.IsLinked);
|
||||
}
|
||||
}
|
||||
}
|
33
src/Ryujinx.Horizon/Sdk/OsTypes/OsProcessHandle.cs
Normal file
33
src/Ryujinx.Horizon/Sdk/OsTypes/OsProcessHandle.cs
Normal file
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
11
src/Ryujinx.Horizon/Sdk/OsTypes/OsResult.cs
Normal file
11
src/Ryujinx.Horizon/Sdk/OsTypes/OsResult.cs
Normal file
@ -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);
|
||||
}
|
||||
}
|
85
src/Ryujinx.Horizon/Sdk/OsTypes/OsSystemEvent.cs
Normal file
85
src/Ryujinx.Horizon/Sdk/OsTypes/OsSystemEvent.cs
Normal file
@ -0,0 +1,85 @@
|
||||
using Ryujinx.Horizon.Common;
|
||||
using Ryujinx.Horizon.Sdk.OsTypes.Impl;
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
10
src/Ryujinx.Horizon/Sdk/OsTypes/OsThreadManager.cs
Normal file
10
src/Ryujinx.Horizon/Sdk/OsTypes/OsThreadManager.cs
Normal file
@ -0,0 +1,10 @@
|
||||
namespace Ryujinx.Horizon.Sdk.OsTypes
|
||||
{
|
||||
static partial class Os
|
||||
{
|
||||
public static int GetCurrentThreadHandle()
|
||||
{
|
||||
return HorizonStatic.CurrentThreadHandle;
|
||||
}
|
||||
}
|
||||
}
|
17
src/Ryujinx.Horizon/Sdk/OsTypes/SystemEventType.cs
Normal file
17
src/Ryujinx.Horizon/Sdk/OsTypes/SystemEventType.cs
Normal file
@ -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;
|
||||
}
|
||||
}
|
9
src/Ryujinx.Horizon/Sdk/OsTypes/TriBool.cs
Normal file
9
src/Ryujinx.Horizon/Sdk/OsTypes/TriBool.cs
Normal file
@ -0,0 +1,9 @@
|
||||
namespace Ryujinx.Horizon.Sdk.OsTypes
|
||||
{
|
||||
enum TriBool
|
||||
{
|
||||
False,
|
||||
True,
|
||||
Undefined
|
||||
}
|
||||
}
|
20
src/Ryujinx.Horizon/Sdk/Prepo/IPrepoService.cs
Normal file
20
src/Ryujinx.Horizon/Sdk/Prepo/IPrepoService.cs
Normal file
@ -0,0 +1,20 @@
|
||||
using Ryujinx.Horizon.Common;
|
||||
using Ryujinx.Horizon.Sdk.Account;
|
||||
using Ryujinx.Horizon.Sdk.Sf;
|
||||
using System;
|
||||
|
||||
namespace Ryujinx.Horizon.Sdk.Prepo
|
||||
{
|
||||
interface IPrepoService : IServiceObject
|
||||
{
|
||||
Result SaveReport(ReadOnlySpan<byte> gameRoomBuffer, ReadOnlySpan<byte> reportBuffer, ulong pid);
|
||||
Result SaveReportWithUser(Uid userId, ReadOnlySpan<byte> gameRoomBuffer, ReadOnlySpan<byte> reportBuffer, ulong pid);
|
||||
Result RequestImmediateTransmission();
|
||||
Result GetTransmissionStatus(out int status);
|
||||
Result GetSystemSessionId(out ulong systemSessionId);
|
||||
Result SaveSystemReport(ReadOnlySpan<byte> gameRoomBuffer, Ncm.ApplicationId applicationId, ReadOnlySpan<byte> reportBuffer);
|
||||
Result SaveSystemReportWithUser(Uid userId, ReadOnlySpan<byte> gameRoomBuffer, Ncm.ApplicationId applicationId, ReadOnlySpan<byte> reportBuffer);
|
||||
Result IsUserAgreementCheckEnabled(out bool enabled);
|
||||
Result SetUserAgreementCheckEnabled(bool enabled);
|
||||
}
|
||||
}
|
39
src/Ryujinx.Horizon/Sdk/ServiceUtil.cs
Normal file
39
src/Ryujinx.Horizon/Sdk/ServiceUtil.cs
Normal file
@ -0,0 +1,39 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
12
src/Ryujinx.Horizon/Sdk/Sf/Cmif/CmifDomainInHeader.cs
Normal file
12
src/Ryujinx.Horizon/Sdk/Sf/Cmif/CmifDomainInHeader.cs
Normal file
@ -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;
|
||||
}
|
||||
}
|
12
src/Ryujinx.Horizon/Sdk/Sf/Cmif/CmifDomainOutHeader.cs
Normal file
12
src/Ryujinx.Horizon/Sdk/Sf/Cmif/CmifDomainOutHeader.cs
Normal file
@ -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
|
||||
}
|
||||
}
|
9
src/Ryujinx.Horizon/Sdk/Sf/Cmif/CmifDomainRequestType.cs
Normal file
9
src/Ryujinx.Horizon/Sdk/Sf/Cmif/CmifDomainRequestType.cs
Normal file
@ -0,0 +1,9 @@
|
||||
namespace Ryujinx.Horizon.Sdk.Sf.Cmif
|
||||
{
|
||||
enum CmifDomainRequestType : byte
|
||||
{
|
||||
Invalid = 0,
|
||||
SendMessage = 1,
|
||||
Close = 2
|
||||
}
|
||||
}
|
10
src/Ryujinx.Horizon/Sdk/Sf/Cmif/CmifInHeader.cs
Normal file
10
src/Ryujinx.Horizon/Sdk/Sf/Cmif/CmifInHeader.cs
Normal file
@ -0,0 +1,10 @@
|
||||
namespace Ryujinx.Horizon.Sdk.Sf.Cmif
|
||||
{
|
||||
struct CmifInHeader
|
||||
{
|
||||
public uint Magic;
|
||||
public uint Version;
|
||||
public uint CommandId;
|
||||
public uint Token;
|
||||
}
|
||||
}
|
135
src/Ryujinx.Horizon/Sdk/Sf/Cmif/CmifMessage.cs
Normal file
135
src/Ryujinx.Horizon/Sdk/Sf/Cmif/CmifMessage.cs
Normal file
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
14
src/Ryujinx.Horizon/Sdk/Sf/Cmif/CmifOutHeader.cs
Normal file
14
src/Ryujinx.Horizon/Sdk/Sf/Cmif/CmifOutHeader.cs
Normal file
@ -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
|
||||
}
|
||||
}
|
14
src/Ryujinx.Horizon/Sdk/Sf/Cmif/CmifRequest.cs
Normal file
14
src/Ryujinx.Horizon/Sdk/Sf/Cmif/CmifRequest.cs
Normal file
@ -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;
|
||||
}
|
||||
}
|
24
src/Ryujinx.Horizon/Sdk/Sf/Cmif/CmifRequestFormat.cs
Normal file
24
src/Ryujinx.Horizon/Sdk/Sf/Cmif/CmifRequestFormat.cs
Normal file
@ -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
|
||||
}
|
||||
}
|
12
src/Ryujinx.Horizon/Sdk/Sf/Cmif/CmifResponse.cs
Normal file
12
src/Ryujinx.Horizon/Sdk/Sf/Cmif/CmifResponse.cs
Normal file
@ -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;
|
||||
}
|
||||
}
|
14
src/Ryujinx.Horizon/Sdk/Sf/Cmif/CommandType.cs
Normal file
14
src/Ryujinx.Horizon/Sdk/Sf/Cmif/CommandType.cs
Normal file
@ -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
|
||||
}
|
||||
}
|
7
src/Ryujinx.Horizon/Sdk/Sf/Cmif/DomainServiceObject.cs
Normal file
7
src/Ryujinx.Horizon/Sdk/Sf/Cmif/DomainServiceObject.cs
Normal file
@ -0,0 +1,7 @@
|
||||
namespace Ryujinx.Horizon.Sdk.Sf.Cmif
|
||||
{
|
||||
abstract partial class DomainServiceObject : ServerDomainBase, IServiceObject
|
||||
{
|
||||
public abstract ServerDomainBase GetServerDomain();
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
140
src/Ryujinx.Horizon/Sdk/Sf/Cmif/DomainServiceObjectProcessor.cs
Normal file
140
src/Ryujinx.Horizon/Sdk/Sf/Cmif/DomainServiceObjectProcessor.cs
Normal file
@ -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];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
52
src/Ryujinx.Horizon/Sdk/Sf/Cmif/HandlesToClose.cs
Normal file
52
src/Ryujinx.Horizon/Sdk/Sf/Cmif/HandlesToClose.cs
Normal file
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
11
src/Ryujinx.Horizon/Sdk/Sf/Cmif/InlineContext.cs
Normal file
11
src/Ryujinx.Horizon/Sdk/Sf/Cmif/InlineContext.cs
Normal file
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
17
src/Ryujinx.Horizon/Sdk/Sf/Cmif/PointerAndSize.cs
Normal file
17
src/Ryujinx.Horizon/Sdk/Sf/Cmif/PointerAndSize.cs
Normal file
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
19
src/Ryujinx.Horizon/Sdk/Sf/Cmif/ScopedInlineContextChange.cs
Normal file
19
src/Ryujinx.Horizon/Sdk/Sf/Cmif/ScopedInlineContextChange.cs
Normal file
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
15
src/Ryujinx.Horizon/Sdk/Sf/Cmif/ServerDomainBase.cs
Normal file
15
src/Ryujinx.Horizon/Sdk/Sf/Cmif/ServerDomainBase.cs
Normal file
@ -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);
|
||||
}
|
||||
}
|
246
src/Ryujinx.Horizon/Sdk/Sf/Cmif/ServerDomainManager.cs
Normal file
246
src/Ryujinx.Horizon/Sdk/Sf/Cmif/ServerDomainManager.cs
Normal file
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
18
src/Ryujinx.Horizon/Sdk/Sf/Cmif/ServerMessageProcessor.cs
Normal file
18
src/Ryujinx.Horizon/Sdk/Sf/Cmif/ServerMessageProcessor.cs
Normal file
@ -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);
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
18
src/Ryujinx.Horizon/Sdk/Sf/Cmif/ServiceDispatchContext.cs
Normal file
18
src/Ryujinx.Horizon/Sdk/Sf/Cmif/ServiceDispatchContext.cs
Normal file
@ -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;
|
||||
}
|
||||
}
|
12
src/Ryujinx.Horizon/Sdk/Sf/Cmif/ServiceDispatchMeta.cs
Normal file
12
src/Ryujinx.Horizon/Sdk/Sf/Cmif/ServiceDispatchMeta.cs
Normal file
@ -0,0 +1,12 @@
|
||||
namespace Ryujinx.Horizon.Sdk.Sf.Cmif
|
||||
{
|
||||
struct ServiceDispatchMeta
|
||||
{
|
||||
public ServiceDispatchTableBase DispatchTable { get; }
|
||||
|
||||
public ServiceDispatchMeta(ServiceDispatchTableBase dispatchTable)
|
||||
{
|
||||
DispatchTable = dispatchTable;
|
||||
}
|
||||
}
|
||||
}
|
33
src/Ryujinx.Horizon/Sdk/Sf/Cmif/ServiceDispatchTable.cs
Normal file
33
src/Ryujinx.Horizon/Sdk/Sf/Cmif/ServiceDispatchTable.cs
Normal file
@ -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());
|
||||
}
|
||||
}
|
||||
}
|
94
src/Ryujinx.Horizon/Sdk/Sf/Cmif/ServiceDispatchTableBase.cs
Normal file
94
src/Ryujinx.Horizon/Sdk/Sf/Cmif/ServiceDispatchTableBase.cs
Normal file
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
34
src/Ryujinx.Horizon/Sdk/Sf/Cmif/ServiceObjectHolder.cs
Normal file
34
src/Ryujinx.Horizon/Sdk/Sf/Cmif/ServiceObjectHolder.cs
Normal file
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
15
src/Ryujinx.Horizon/Sdk/Sf/CmifCommandAttribute.cs
Normal file
15
src/Ryujinx.Horizon/Sdk/Sf/CmifCommandAttribute.cs
Normal file
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
56
src/Ryujinx.Horizon/Sdk/Sf/CommandArg.cs
Normal file
56
src/Ryujinx.Horizon/Sdk/Sf/CommandArg.cs
Normal file
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
38
src/Ryujinx.Horizon/Sdk/Sf/CommandArgAttributes.cs
Normal file
38
src/Ryujinx.Horizon/Sdk/Sf/CommandArgAttributes.cs
Normal file
@ -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
|
||||
{
|
||||
}
|
||||
}
|
52
src/Ryujinx.Horizon/Sdk/Sf/CommandHandler.cs
Normal file
52
src/Ryujinx.Horizon/Sdk/Sf/CommandHandler.cs
Normal file
@ -0,0 +1,52 @@
|
||||
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);
|
||||
|
||||
return result.IsFailure ? result : _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)[..1];
|
||||
outRawData = outRawData[Unsafe.SizeOf<CmifOutHeader>()..];
|
||||
}
|
||||
}
|
||||
}
|
69
src/Ryujinx.Horizon/Sdk/Sf/CommandSerialization.cs
Normal file
69
src/Ryujinx.Horizon/Sdk/Sf/CommandSerialization.cs
Normal file
@ -0,0 +1,69 @@
|
||||
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] = 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;
|
||||
}
|
||||
}
|
||||
}
|
85
src/Ryujinx.Horizon/Sdk/Sf/Hipc/Api.cs
Normal file
85
src/Ryujinx.Horizon/Sdk/Sf/Hipc/Api.cs
Normal file
@ -0,0 +1,85 @@
|
||||
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)
|
||||
{
|
||||
var tlsSpan = HorizonStatic.AddressSpace.GetSpan(HorizonStatic.ThreadContext.TlsAddress, TlsMessageBufferSize);
|
||||
|
||||
if (messageBuffer == tlsSpan)
|
||||
{
|
||||
return HorizonStatic.Syscall.ReplyAndReceive(out _, ReadOnlySpan<int>.Empty, 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;
|
||||
}
|
||||
}
|
||||
}
|
65
src/Ryujinx.Horizon/Sdk/Sf/Hipc/Header.cs
Normal file
65
src/Ryujinx.Horizon/Sdk/Sf/Hipc/Header.cs
Normal file
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
15
src/Ryujinx.Horizon/Sdk/Sf/Hipc/HipcBufferDescriptor.cs
Normal file
15
src/Ryujinx.Horizon/Sdk/Sf/Hipc/HipcBufferDescriptor.cs
Normal file
@ -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);
|
||||
}
|
||||
}
|
17
src/Ryujinx.Horizon/Sdk/Sf/Hipc/HipcBufferFlags.cs
Normal file
17
src/Ryujinx.Horizon/Sdk/Sf/Hipc/HipcBufferFlags.cs
Normal file
@ -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
|
||||
}
|
||||
}
|
10
src/Ryujinx.Horizon/Sdk/Sf/Hipc/HipcBufferMode.cs
Normal file
10
src/Ryujinx.Horizon/Sdk/Sf/Hipc/HipcBufferMode.cs
Normal file
@ -0,0 +1,10 @@
|
||||
namespace Ryujinx.Horizon.Sdk.Sf.Hipc
|
||||
{
|
||||
enum HipcBufferMode
|
||||
{
|
||||
Normal = 0,
|
||||
NonSecure = 1,
|
||||
Invalid = 2,
|
||||
NonDevice = 3
|
||||
}
|
||||
}
|
115
src/Ryujinx.Horizon/Sdk/Sf/Hipc/HipcManager.cs
Normal file
115
src/Ryujinx.Horizon/Sdk/Sf/Hipc/HipcManager.cs
Normal file
@ -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 not 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;
|
||||
}
|
||||
}
|
||||
}
|
218
src/Ryujinx.Horizon/Sdk/Sf/Hipc/HipcMessage.cs
Normal file
218
src/Ryujinx.Horizon/Sdk/Sf/Hipc/HipcMessage.cs
Normal file
@ -0,0 +1,218 @@
|
||||
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[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[Unsafe.SizeOf<SpecialHeader>()..];
|
||||
|
||||
if (specialHeader.SendPid)
|
||||
{
|
||||
pid = MemoryMarshal.Cast<byte, ulong>(data)[0];
|
||||
data = data[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[Unsafe.SizeOf<Header>()..];
|
||||
|
||||
if (hasSpecialHeader)
|
||||
{
|
||||
MemoryMarshal.Cast<byte, SpecialHeader>(destination)[0] = new SpecialHeader()
|
||||
{
|
||||
SendPid = meta.SendPid,
|
||||
CopyHandlesCount = meta.CopyHandlesCount,
|
||||
MoveHandlesCount = meta.MoveHandlesCount
|
||||
};
|
||||
|
||||
destination = destination[Unsafe.SizeOf<SpecialHeader>()..];
|
||||
|
||||
if (meta.SendPid)
|
||||
{
|
||||
destination = destination[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)[..meta.CopyHandlesCount];
|
||||
|
||||
data = data[(meta.CopyHandlesCount * sizeof(int))..];
|
||||
}
|
||||
|
||||
Span<int> moveHandles = Span<int>.Empty;
|
||||
|
||||
if (meta.MoveHandlesCount != 0)
|
||||
{
|
||||
moveHandles = MemoryMarshal.Cast<byte, int>(data)[..meta.MoveHandlesCount];
|
||||
|
||||
data = data[(meta.MoveHandlesCount * sizeof(int))..];
|
||||
}
|
||||
|
||||
Span<HipcStaticDescriptor> sendStatics = Span<HipcStaticDescriptor>.Empty;
|
||||
|
||||
if (meta.SendStaticsCount != 0)
|
||||
{
|
||||
sendStatics = MemoryMarshal.Cast<byte, HipcStaticDescriptor>(data)[..meta.SendStaticsCount];
|
||||
|
||||
data = data[(meta.SendStaticsCount * Unsafe.SizeOf<HipcStaticDescriptor>())..];
|
||||
}
|
||||
|
||||
Span<HipcBufferDescriptor> sendBuffers = Span<HipcBufferDescriptor>.Empty;
|
||||
|
||||
if (meta.SendBuffersCount != 0)
|
||||
{
|
||||
sendBuffers = MemoryMarshal.Cast<byte, HipcBufferDescriptor>(data)[..meta.SendBuffersCount];
|
||||
|
||||
data = data[(meta.SendBuffersCount * Unsafe.SizeOf<HipcBufferDescriptor>())..];
|
||||
}
|
||||
|
||||
Span<HipcBufferDescriptor> receiveBuffers = Span<HipcBufferDescriptor>.Empty;
|
||||
|
||||
if (meta.ReceiveBuffersCount != 0)
|
||||
{
|
||||
receiveBuffers = MemoryMarshal.Cast<byte, HipcBufferDescriptor>(data)[..meta.ReceiveBuffersCount];
|
||||
|
||||
data = data[(meta.ReceiveBuffersCount * Unsafe.SizeOf<HipcBufferDescriptor>())..];
|
||||
}
|
||||
|
||||
Span<HipcBufferDescriptor> exchangeBuffers = Span<HipcBufferDescriptor>.Empty;
|
||||
|
||||
if (meta.ExchangeBuffersCount != 0)
|
||||
{
|
||||
exchangeBuffers = MemoryMarshal.Cast<byte, HipcBufferDescriptor>(data)[..meta.ExchangeBuffersCount];
|
||||
|
||||
data = data[(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)[padding..meta.DataWordsCount];
|
||||
|
||||
data = data[(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)[..receiveListSize];
|
||||
}
|
||||
|
||||
return new HipcMessageData()
|
||||
{
|
||||
SendStatics = sendStatics,
|
||||
SendBuffers = sendBuffers,
|
||||
ReceiveBuffers = receiveBuffers,
|
||||
ExchangeBuffers = exchangeBuffers,
|
||||
DataWords = dataWords,
|
||||
ReceiveList = receiveList,
|
||||
CopyHandles = copyHandles,
|
||||
MoveHandles = moveHandles
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
16
src/Ryujinx.Horizon/Sdk/Sf/Hipc/HipcMessageData.cs
Normal file
16
src/Ryujinx.Horizon/Sdk/Sf/Hipc/HipcMessageData.cs
Normal file
@ -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;
|
||||
}
|
||||
}
|
16
src/Ryujinx.Horizon/Sdk/Sf/Hipc/HipcMetadata.cs
Normal file
16
src/Ryujinx.Horizon/Sdk/Sf/Hipc/HipcMetadata.cs
Normal file
@ -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;
|
||||
}
|
||||
}
|
14
src/Ryujinx.Horizon/Sdk/Sf/Hipc/HipcReceiveListEntry.cs
Normal file
14
src/Ryujinx.Horizon/Sdk/Sf/Hipc/HipcReceiveListEntry.cs
Normal file
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
19
src/Ryujinx.Horizon/Sdk/Sf/Hipc/HipcResult.cs
Normal file
19
src/Ryujinx.Horizon/Sdk/Sf/Hipc/HipcResult.cs
Normal file
@ -0,0 +1,19 @@
|
||||
using Ryujinx.Horizon.Common;
|
||||
|
||||
namespace Ryujinx.Horizon.Sdk.Sf.Hipc
|
||||
{
|
||||
static class HipcResult
|
||||
{
|
||||
public const int ModuleId = 11;
|
||||
|
||||
public static Result OutOfSessionMemory => new(ModuleId, 102);
|
||||
public static Result OutOfSessions => new(ModuleId, 131);
|
||||
public static Result PointerBufferTooSmall => new(ModuleId, 141);
|
||||
public static Result OutOfDomains => new(ModuleId, 200);
|
||||
public static Result InvalidRequestSize => new(ModuleId, 402);
|
||||
public static Result UnknownCommandType => new(ModuleId, 403);
|
||||
public static Result InvalidCmifRequest => new(ModuleId, 420);
|
||||
public static Result TargetNotDomain => new(ModuleId, 491);
|
||||
public static Result DomainObjectNotFound => new(ModuleId, 492);
|
||||
}
|
||||
}
|
22
src/Ryujinx.Horizon/Sdk/Sf/Hipc/HipcStaticDescriptor.cs
Normal file
22
src/Ryujinx.Horizon/Sdk/Sf/Hipc/HipcStaticDescriptor.cs
Normal file
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
20
src/Ryujinx.Horizon/Sdk/Sf/Hipc/ManagerOptions.cs
Normal file
20
src/Ryujinx.Horizon/Sdk/Sf/Hipc/ManagerOptions.cs
Normal file
@ -0,0 +1,20 @@
|
||||
namespace Ryujinx.Horizon.Sdk.Sf.Hipc
|
||||
{
|
||||
struct ManagerOptions
|
||||
{
|
||||
public static ManagerOptions Default => new(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;
|
||||
}
|
||||
}
|
||||
}
|
9
src/Ryujinx.Horizon/Sdk/Sf/Hipc/ReceiveResult.cs
Normal file
9
src/Ryujinx.Horizon/Sdk/Sf/Hipc/ReceiveResult.cs
Normal file
@ -0,0 +1,9 @@
|
||||
namespace Ryujinx.Horizon.Sdk.Sf.Hipc
|
||||
{
|
||||
enum ReceiveResult
|
||||
{
|
||||
Success,
|
||||
Closed,
|
||||
NeedsRetry
|
||||
}
|
||||
}
|
36
src/Ryujinx.Horizon/Sdk/Sf/Hipc/Server.cs
Normal file
36
src/Ryujinx.Horizon/Sdk/Sf/Hipc/Server.cs
Normal file
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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(this, session);
|
||||
|
||||
return DispatchRequest(new ServiceObjectHolder(hipcManager), session, inMessage, outMessage);
|
||||
}
|
||||
}
|
||||
}
|
198
src/Ryujinx.Horizon/Sdk/Sf/Hipc/ServerManager.cs
Normal file
198
src/Ryujinx.Horizon/Sdk/Sf/Hipc/ServerManager.cs
Normal file
@ -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(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(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);
|
||||
}
|
||||
}
|
||||
}
|
314
src/Ryujinx.Horizon/Sdk/Sf/Hipc/ServerManagerBase.cs
Normal file
314
src/Ryujinx.Horizon/Sdk/Sf/Hipc/ServerManagerBase.cs
Normal file
@ -0,0 +1,314 @@
|
||||
using Ryujinx.Horizon.Common;
|
||||
using Ryujinx.Horizon.Sdk.OsTypes;
|
||||
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();
|
||||
}
|
||||
|
||||
protected Result AcceptImpl(Server server, IServiceObject obj)
|
||||
{
|
||||
return AcceptSession(server.PortHandle, new ServiceObjectHolder(obj));
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
return (UserDataTag)holder.UserData switch
|
||||
{
|
||||
UserDataTag.Server => ProcessForServer(holder),
|
||||
UserDataTag.Session => ProcessForSession(holder),
|
||||
_ => 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;
|
||||
}
|
||||
}
|
||||
}
|
23
src/Ryujinx.Horizon/Sdk/Sf/Hipc/ServerSession.cs
Normal file
23
src/Ryujinx.Horizon/Sdk/Sf/Hipc/ServerSession.cs
Normal file
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user