diff options
Diffstat (limited to 'src/Ryujinx.HLE/HOS/Services/ServerBase.cs')
-rw-r--r-- | src/Ryujinx.HLE/HOS/Services/ServerBase.cs | 423 |
1 files changed, 423 insertions, 0 deletions
diff --git a/src/Ryujinx.HLE/HOS/Services/ServerBase.cs b/src/Ryujinx.HLE/HOS/Services/ServerBase.cs new file mode 100644 index 00000000..b994679a --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/ServerBase.cs @@ -0,0 +1,423 @@ +using Ryujinx.Common; +using Ryujinx.Common.Memory; +using Ryujinx.HLE.HOS.Ipc; +using Ryujinx.HLE.HOS.Kernel; +using Ryujinx.HLE.HOS.Kernel.Ipc; +using Ryujinx.HLE.HOS.Kernel.Process; +using Ryujinx.HLE.HOS.Kernel.Threading; +using Ryujinx.Horizon.Common; +using System; +using System.Buffers; +using System.Buffers.Binary; +using System.Collections.Generic; +using System.IO; +using System.Threading; + +namespace Ryujinx.HLE.HOS.Services +{ + class ServerBase : IDisposable + { + // Must be the maximum value used by services (highest one know is the one used by nvservices = 0x8000). + // Having a size that is too low will cause failures as data copy will fail if the receiving buffer is + // not large enough. + private const int PointerBufferSize = 0x8000; + + private readonly static uint[] DefaultCapabilities = new uint[] + { + 0x030363F7, + 0x1FFFFFCF, + 0x207FFFEF, + 0x47E0060F, + 0x0048BFFF, + 0x01007FFF + }; + + private readonly object _handleLock = new(); + + private readonly KernelContext _context; + private KProcess _selfProcess; + + private readonly List<int> _sessionHandles = new List<int>(); + private readonly List<int> _portHandles = new List<int>(); + private readonly Dictionary<int, IpcService> _sessions = new Dictionary<int, IpcService>(); + private readonly Dictionary<int, Func<IpcService>> _ports = new Dictionary<int, Func<IpcService>>(); + + private readonly MemoryStream _requestDataStream; + private readonly BinaryReader _requestDataReader; + + private readonly MemoryStream _responseDataStream; + private readonly BinaryWriter _responseDataWriter; + + public ManualResetEvent InitDone { get; } + public string Name { get; } + public Func<IpcService> SmObjectFactory { get; } + + public ServerBase(KernelContext context, string name, Func<IpcService> smObjectFactory = null) + { + _context = context; + + _requestDataStream = MemoryStreamManager.Shared.GetStream(); + _requestDataReader = new BinaryReader(_requestDataStream); + + _responseDataStream = MemoryStreamManager.Shared.GetStream(); + _responseDataWriter = new BinaryWriter(_responseDataStream); + + InitDone = new ManualResetEvent(false); + Name = name; + SmObjectFactory = smObjectFactory; + + const ProcessCreationFlags flags = + ProcessCreationFlags.EnableAslr | + ProcessCreationFlags.AddressSpace64Bit | + ProcessCreationFlags.Is64Bit | + ProcessCreationFlags.PoolPartitionSystem; + + ProcessCreationInfo creationInfo = new ProcessCreationInfo("Service", 1, 0, 0x8000000, 1, flags, 0, 0); + + KernelStatic.StartInitialProcess(context, creationInfo, DefaultCapabilities, 44, Main); + } + + private void AddPort(int serverPortHandle, Func<IpcService> objectFactory) + { + lock (_handleLock) + { + _portHandles.Add(serverPortHandle); + } + _ports.Add(serverPortHandle, objectFactory); + } + + public void AddSessionObj(KServerSession serverSession, IpcService obj) + { + // Ensure that the sever loop is running. + InitDone.WaitOne(); + + _selfProcess.HandleTable.GenerateHandle(serverSession, out int serverSessionHandle); + AddSessionObj(serverSessionHandle, obj); + } + + public void AddSessionObj(int serverSessionHandle, IpcService obj) + { + lock (_handleLock) + { + _sessionHandles.Add(serverSessionHandle); + } + _sessions.Add(serverSessionHandle, obj); + } + + private void Main() + { + ServerLoop(); + } + + private void ServerLoop() + { + _selfProcess = KernelStatic.GetCurrentProcess(); + + if (SmObjectFactory != null) + { + _context.Syscall.ManageNamedPort(out int serverPortHandle, "sm:", 50); + + AddPort(serverPortHandle, SmObjectFactory); + } + + InitDone.Set(); + + KThread thread = KernelStatic.GetCurrentThread(); + ulong messagePtr = thread.TlsAddress; + _context.Syscall.SetHeapSize(out ulong heapAddr, 0x200000); + + _selfProcess.CpuMemory.Write(messagePtr + 0x0, 0); + _selfProcess.CpuMemory.Write(messagePtr + 0x4, 2 << 10); + _selfProcess.CpuMemory.Write(messagePtr + 0x8, heapAddr | ((ulong)PointerBufferSize << 48)); + + int replyTargetHandle = 0; + + while (true) + { + int handleCount; + int portHandleCount; + int[] handles; + + lock (_handleLock) + { + portHandleCount = _portHandles.Count; + handleCount = portHandleCount + _sessionHandles.Count; + + handles = ArrayPool<int>.Shared.Rent(handleCount); + + _portHandles.CopyTo(handles, 0); + _sessionHandles.CopyTo(handles, portHandleCount); + } + + // We still need a timeout here to allow the service to pick up and listen new sessions... + var rc = _context.Syscall.ReplyAndReceive(out int signaledIndex, handles.AsSpan(0, handleCount), replyTargetHandle, 1000000L); + + thread.HandlePostSyscall(); + + if (!thread.Context.Running) + { + break; + } + + replyTargetHandle = 0; + + if (rc == Result.Success && signaledIndex >= portHandleCount) + { + // We got a IPC request, process it, pass to the appropriate service if needed. + int signaledHandle = handles[signaledIndex]; + + if (Process(signaledHandle, heapAddr)) + { + replyTargetHandle = signaledHandle; + } + } + else + { + if (rc == Result.Success) + { + // We got a new connection, accept the session to allow servicing future requests. + if (_context.Syscall.AcceptSession(out int serverSessionHandle, handles[signaledIndex]) == Result.Success) + { + IpcService obj = _ports[handles[signaledIndex]].Invoke(); + + AddSessionObj(serverSessionHandle, obj); + } + } + + _selfProcess.CpuMemory.Write(messagePtr + 0x0, 0); + _selfProcess.CpuMemory.Write(messagePtr + 0x4, 2 << 10); + _selfProcess.CpuMemory.Write(messagePtr + 0x8, heapAddr | ((ulong)PointerBufferSize << 48)); + } + + ArrayPool<int>.Shared.Return(handles); + } + + Dispose(); + } + + private bool Process(int serverSessionHandle, ulong recvListAddr) + { + KProcess process = KernelStatic.GetCurrentProcess(); + KThread thread = KernelStatic.GetCurrentThread(); + ulong messagePtr = thread.TlsAddress; + + IpcMessage request = ReadRequest(process, messagePtr); + + IpcMessage response = new IpcMessage(); + + ulong tempAddr = recvListAddr; + int sizesOffset = request.RawData.Length - ((request.RecvListBuff.Count * 2 + 3) & ~3); + + bool noReceive = true; + + for (int i = 0; i < request.ReceiveBuff.Count; i++) + { + noReceive &= (request.ReceiveBuff[i].Position == 0); + } + + if (noReceive) + { + response.PtrBuff.EnsureCapacity(request.RecvListBuff.Count); + + for (int i = 0; i < request.RecvListBuff.Count; i++) + { + ulong size = (ulong)BinaryPrimitives.ReadInt16LittleEndian(request.RawData.AsSpan(sizesOffset + i * 2, 2)); + + response.PtrBuff.Add(new IpcPtrBuffDesc(tempAddr, (uint)i, size)); + + request.RecvListBuff[i] = new IpcRecvListBuffDesc(tempAddr, size); + + tempAddr += size; + } + } + + bool shouldReply = true; + bool isTipcCommunication = false; + + _requestDataStream.SetLength(0); + _requestDataStream.Write(request.RawData); + _requestDataStream.Position = 0; + + if (request.Type == IpcMessageType.CmifRequest || + request.Type == IpcMessageType.CmifRequestWithContext) + { + response.Type = IpcMessageType.CmifResponse; + + _responseDataStream.SetLength(0); + + ServiceCtx context = new ServiceCtx( + _context.Device, + process, + process.CpuMemory, + thread, + request, + response, + _requestDataReader, + _responseDataWriter); + + _sessions[serverSessionHandle].CallCmifMethod(context); + + response.RawData = _responseDataStream.ToArray(); + } + else if (request.Type == IpcMessageType.CmifControl || + request.Type == IpcMessageType.CmifControlWithContext) + { + uint magic = (uint)_requestDataReader.ReadUInt64(); + uint cmdId = (uint)_requestDataReader.ReadUInt64(); + + switch (cmdId) + { + case 0: + FillHipcResponse(response, 0, _sessions[serverSessionHandle].ConvertToDomain()); + break; + + case 3: + FillHipcResponse(response, 0, PointerBufferSize); + break; + + // TODO: Whats the difference between IpcDuplicateSession/Ex? + case 2: + case 4: + int unknown = _requestDataReader.ReadInt32(); + + _context.Syscall.CreateSession(out int dupServerSessionHandle, out int dupClientSessionHandle, false, 0); + + AddSessionObj(dupServerSessionHandle, _sessions[serverSessionHandle]); + + response.HandleDesc = IpcHandleDesc.MakeMove(dupClientSessionHandle); + + FillHipcResponse(response, 0); + + break; + + default: throw new NotImplementedException(cmdId.ToString()); + } + } + else if (request.Type == IpcMessageType.CmifCloseSession || request.Type == IpcMessageType.TipcCloseSession) + { + _context.Syscall.CloseHandle(serverSessionHandle); + lock (_handleLock) + { + _sessionHandles.Remove(serverSessionHandle); + } + IpcService service = _sessions[serverSessionHandle]; + (service as IDisposable)?.Dispose(); + _sessions.Remove(serverSessionHandle); + shouldReply = false; + } + // If the type is past 0xF, we are using TIPC + else if (request.Type > IpcMessageType.TipcCloseSession) + { + isTipcCommunication = true; + + // Response type is always the same as request on TIPC. + response.Type = request.Type; + + _responseDataStream.SetLength(0); + + ServiceCtx context = new ServiceCtx( + _context.Device, + process, + process.CpuMemory, + thread, + request, + response, + _requestDataReader, + _responseDataWriter); + + _sessions[serverSessionHandle].CallTipcMethod(context); + + response.RawData = _responseDataStream.ToArray(); + + using var responseStream = response.GetStreamTipc(); + process.CpuMemory.Write(messagePtr, responseStream.GetReadOnlySequence()); + } + else + { + throw new NotImplementedException(request.Type.ToString()); + } + + if (!isTipcCommunication) + { + using var responseStream = response.GetStream((long)messagePtr, recvListAddr | ((ulong)PointerBufferSize << 48)); + process.CpuMemory.Write(messagePtr, responseStream.GetReadOnlySequence()); + } + + return shouldReply; + } + + private static IpcMessage ReadRequest(KProcess process, ulong messagePtr) + { + const int messageSize = 0x100; + + byte[] reqData = ArrayPool<byte>.Shared.Rent(messageSize); + + Span<byte> reqDataSpan = reqData.AsSpan(0, messageSize); + reqDataSpan.Clear(); + + process.CpuMemory.Read(messagePtr, reqDataSpan); + + IpcMessage request = new IpcMessage(reqDataSpan, (long)messagePtr); + + ArrayPool<byte>.Shared.Return(reqData); + + return request; + } + + private void FillHipcResponse(IpcMessage response, long result) + { + FillHipcResponse(response, result, ReadOnlySpan<byte>.Empty); + } + + private void FillHipcResponse(IpcMessage response, long result, int value) + { + Span<byte> span = stackalloc byte[sizeof(int)]; + BinaryPrimitives.WriteInt32LittleEndian(span, value); + FillHipcResponse(response, result, span); + } + + private void FillHipcResponse(IpcMessage response, long result, ReadOnlySpan<byte> data) + { + response.Type = IpcMessageType.CmifResponse; + + _responseDataStream.SetLength(0); + + _responseDataStream.Write(IpcMagic.Sfco); + _responseDataStream.Write(result); + + _responseDataStream.Write(data); + + response.RawData = _responseDataStream.ToArray(); + } + + protected virtual void Dispose(bool disposing) + { + if (disposing) + { + foreach (IpcService service in _sessions.Values) + { + if (service is IDisposable disposableObj) + { + disposableObj.Dispose(); + } + + service.DestroyAtExit(); + } + + _sessions.Clear(); + + _requestDataReader.Dispose(); + _requestDataStream.Dispose(); + _responseDataWriter.Dispose(); + _responseDataStream.Dispose(); + + InitDone.Dispose(); + } + } + + public void Dispose() + { + Dispose(true); + } + } +} |