Przeglądaj źródła

Improve kernel IPC implementation (#550)

* Implement some IPC related kernel SVCs properly

* Fix BLZ decompression when the segment also has a uncompressed chunck

* Set default cpu core on process start from ProgramLoader, remove debug message

* Load process capabilities properly on KIPs

* Fix a copy/paste error in UnmapPhysicalMemory64

* Implement smarter switching between old and new IPC system to support the old HLE services implementation without the manual switch

* Implement RegisterService on sm and AcceptSession (partial)

* Misc fixes and improvements on new IPC methods

* Move IPC related SVCs into a separate file, and logging on RegisterService (sm)

* Some small fixes related to receive list buffers and error cases

* Load NSOs using the correct pool partition

* Fix corner case on GetMaskFromMinMax where range is 64, doesn't happen in pratice however

* Fix send static buffer copy

* Session release, implement closing requests on client disconnect

* Implement ConnectToPort SVC

* KLightSession init
gdkchan 7 lat temu
rodzic
commit
22bacc6188
51 zmienionych plików z 4181 dodań i 783 usunięć
  1. 57 0
      Ryujinx.HLE/DeviceMemory.cs
  2. 13 2
      Ryujinx.HLE/HOS/Horizon.cs
  3. 6 6
      Ryujinx.HLE/HOS/Ipc/IpcHandler.cs
  4. 21 0
      Ryujinx.HLE/HOS/Kernel/Common/KAutoObject.cs
  5. 5 9
      Ryujinx.HLE/HOS/Kernel/Common/KResourceLimit.cs
  6. 1 1
      Ryujinx.HLE/HOS/Kernel/Common/KSynchronizationObject.cs
  7. 31 26
      Ryujinx.HLE/HOS/Kernel/Common/KernelResult.cs
  8. 20 0
      Ryujinx.HLE/HOS/Kernel/Common/KernelTransfer.cs
  9. 10 0
      Ryujinx.HLE/HOS/Kernel/Ipc/ChannelState.cs
  10. 20 0
      Ryujinx.HLE/HOS/Kernel/Ipc/KBufferDescriptor.cs
  11. 216 0
      Ryujinx.HLE/HOS/Kernel/Ipc/KBufferDescriptorTable.cs
  12. 108 2
      Ryujinx.HLE/HOS/Kernel/Ipc/KClientPort.cs
  13. 60 0
      Ryujinx.HLE/HOS/Kernel/Ipc/KClientSession.cs
  14. 14 0
      Ryujinx.HLE/HOS/Kernel/Ipc/KLightClientSession.cs
  15. 14 0
      Ryujinx.HLE/HOS/Kernel/Ipc/KLightServerSession.cs
  16. 20 0
      Ryujinx.HLE/HOS/Kernel/Ipc/KLightSession.cs
  17. 54 11
      Ryujinx.HLE/HOS/Kernel/Ipc/KPort.cs
  18. 73 2
      Ryujinx.HLE/HOS/Kernel/Ipc/KServerPort.cs
  19. 1262 0
      Ryujinx.HLE/HOS/Kernel/Ipc/KServerSession.cs
  20. 42 8
      Ryujinx.HLE/HOS/Kernel/Ipc/KSession.cs
  21. 31 0
      Ryujinx.HLE/HOS/Kernel/Ipc/KSessionRequest.cs
  22. 93 13
      Ryujinx.HLE/HOS/Kernel/Memory/KMemoryBlock.cs
  23. 17 14
      Ryujinx.HLE/HOS/Kernel/Memory/KMemoryInfo.cs
  24. 901 247
      Ryujinx.HLE/HOS/Kernel/Memory/KMemoryManager.cs
  25. 172 113
      Ryujinx.HLE/HOS/Kernel/Memory/KMemoryRegionManager.cs
  26. 3 2
      Ryujinx.HLE/HOS/Kernel/Memory/KSharedMemory.cs
  27. 4 2
      Ryujinx.HLE/HOS/Kernel/Memory/KTransferMemory.cs
  28. 4 2
      Ryujinx.HLE/HOS/Kernel/Process/KHandleEntry.cs
  29. 82 7
      Ryujinx.HLE/HOS/Kernel/Process/KHandleTable.cs
  30. 14 6
      Ryujinx.HLE/HOS/Kernel/Process/KProcess.cs
  31. 5 0
      Ryujinx.HLE/HOS/Kernel/Process/KProcessCapabilities.cs
  32. 0 23
      Ryujinx.HLE/HOS/Kernel/SupervisorCall/SvcHandler.cs
  33. 532 0
      Ryujinx.HLE/HOS/Kernel/SupervisorCall/SvcIpc.cs
  34. 2 2
      Ryujinx.HLE/HOS/Kernel/SupervisorCall/SvcMemory.cs
  35. 1 180
      Ryujinx.HLE/HOS/Kernel/SupervisorCall/SvcSystem.cs
  36. 5 1
      Ryujinx.HLE/HOS/Kernel/SupervisorCall/SvcTable.cs
  37. 18 12
      Ryujinx.HLE/HOS/Kernel/SupervisorCall/SvcThread.cs
  38. 1 1
      Ryujinx.HLE/HOS/Kernel/Threading/KEvent.cs
  39. 20 0
      Ryujinx.HLE/HOS/Kernel/Threading/KScheduler.cs
  40. 1 1
      Ryujinx.HLE/HOS/Kernel/Threading/KSynchronization.cs
  41. 50 12
      Ryujinx.HLE/HOS/Kernel/Threading/KThread.cs
  42. 2 2
      Ryujinx.HLE/HOS/Kernel/Threading/KWritableEvent.cs
  43. 17 4
      Ryujinx.HLE/HOS/ProgramLoader.cs
  44. 16 16
      Ryujinx.HLE/HOS/ServiceCtx.cs
  45. 6 4
      Ryujinx.HLE/HOS/Services/IpcService.cs
  46. 1 1
      Ryujinx.HLE/HOS/Services/Psm/IPsmSession.cs
  47. 91 15
      Ryujinx.HLE/HOS/Services/Sm/IUserInterface.cs
  48. 9 0
      Ryujinx.HLE/HOS/Services/Sm/SmErr.cs
  49. 31 29
      Ryujinx.HLE/Loaders/Compression/BackwardsLz.cs
  50. 4 6
      Ryujinx.HLE/Loaders/Executables/KernelInitialProcess.cs
  51. 1 1
      Ryujinx.HLE/Loaders/Npdm/ServiceAccessControl.cs

+ 57 - 0
Ryujinx.HLE/DeviceMemory.cs

@@ -113,6 +113,63 @@ namespace Ryujinx.HLE
             }
         }
 
+        public void Set(ulong address, byte value, ulong size)
+        {
+            if (address + size < address)
+            {
+                throw new ArgumentOutOfRangeException(nameof(size));
+            }
+
+            if (address + size > RamSize)
+            {
+                throw new ArgumentOutOfRangeException(nameof(address));
+            }
+
+            ulong size8 = size & ~7UL;
+
+            ulong valueRep = (ulong)value * 0x0101010101010101;
+
+            for (ulong offs = 0; offs < size8; offs += 8)
+            {
+                WriteUInt64((long)(address + offs), valueRep);
+            }
+
+            for (ulong offs = size8; offs < (size - size8); offs++)
+            {
+                WriteByte((long)(address + offs), value);
+            }
+        }
+
+        public void Copy(ulong dst, ulong src, ulong size)
+        {
+            if (dst + size < dst || src + size < src)
+            {
+                throw new ArgumentOutOfRangeException(nameof(size));
+            }
+
+            if (dst + size > RamSize)
+            {
+                throw new ArgumentOutOfRangeException(nameof(dst));
+            }
+
+            if (src + size > RamSize)
+            {
+                throw new ArgumentOutOfRangeException(nameof(src));
+            }
+
+            ulong size8 = size & ~7UL;
+
+            for (ulong offs = 0; offs < size8; offs += 8)
+            {
+                WriteUInt64((long)(dst + offs), ReadUInt64((long)(src + offs)));
+            }
+
+            for (ulong offs = size8; offs < (size - size8); offs++)
+            {
+                WriteByte((long)(dst + offs), ReadByte((long)(src + offs)));
+            }
+        }
+
         public void Dispose()
         {
             Dispose(true);

+ 13 - 2
Ryujinx.HLE/HOS/Horizon.cs

@@ -7,6 +7,7 @@ using Ryujinx.HLE.HOS.Kernel.Common;
 using Ryujinx.HLE.HOS.Kernel.Memory;
 using Ryujinx.HLE.HOS.Kernel.Process;
 using Ryujinx.HLE.HOS.Kernel.Threading;
+using Ryujinx.HLE.HOS.Services.Sm;
 using Ryujinx.HLE.HOS.SystemState;
 using Ryujinx.HLE.Loaders.Executables;
 using Ryujinx.HLE.Loaders.Npdm;
@@ -157,8 +158,8 @@ namespace Ryujinx.HLE.HOS
             hidPageList .AddRange(hidPa,  HidSize  / KMemoryManager.PageSize);
             fontPageList.AddRange(fontPa, FontSize / KMemoryManager.PageSize);
 
-            HidSharedMem  = new KSharedMemory(hidPageList,  0, 0, MemoryPermission.Read);
-            FontSharedMem = new KSharedMemory(fontPageList, 0, 0, MemoryPermission.Read);
+            HidSharedMem  = new KSharedMemory(this, hidPageList,  0, 0, MemoryPermission.Read);
+            FontSharedMem = new KSharedMemory(this, fontPageList, 0, 0, MemoryPermission.Read);
 
             AppletState = new AppletStateMgr(this);
 
@@ -166,6 +167,8 @@ namespace Ryujinx.HLE.HOS
 
             Font = new SharedFontManager(device, (long)(fontPa - DramMemoryMap.DramBase));
 
+            IUserInterface.InitializePort(this);
+
             VsyncEvent = new KEvent(this);
 
             LoadKeySet();
@@ -259,6 +262,14 @@ namespace Ryujinx.HLE.HOS
             LoadNca(mainNca, controlNca);
         }
 
+        public void LoadKip(string kipFile)
+        {
+            using (FileStream fs = new FileStream(kipFile, FileMode.Open))
+            {
+                ProgramLoader.LoadKernelInitalProcess(this, new KernelInitialProcess(fs));
+            }
+        }
+
         private (Nca Main, Nca Control) GetXciGameData(Xci xci)
         {
             if (xci.SecurePartition == null)

+ 6 - 6
Ryujinx.HLE/HOS/Ipc/IpcHandler.cs

@@ -10,12 +10,12 @@ namespace Ryujinx.HLE.HOS.Ipc
     static class IpcHandler
     {
         public static KernelResult IpcCall(
-            Switch        device,
-            KProcess      process,
-            MemoryManager memory,
-            KSession      session,
-            IpcMessage    request,
-            long          cmdPtr)
+            Switch         device,
+            KProcess       process,
+            MemoryManager  memory,
+            KClientSession session,
+            IpcMessage     request,
+            long           cmdPtr)
         {
             IpcMessage response = new IpcMessage();
 

+ 21 - 0
Ryujinx.HLE/HOS/Kernel/Common/KAutoObject.cs

@@ -1,12 +1,18 @@
+using System.Threading;
+
 namespace Ryujinx.HLE.HOS.Kernel.Common
 {
     class KAutoObject
     {
         protected Horizon System;
 
+        private int _referenceCount;
+
         public KAutoObject(Horizon system)
         {
             System = system;
+
+            _referenceCount = 1;
         }
 
         public virtual KernelResult SetName(string name)
@@ -38,5 +44,20 @@ namespace Ryujinx.HLE.HOS.Kernel.Common
 
             return null;
         }
+
+        public void IncrementReferenceCount()
+        {
+            Interlocked.Increment(ref _referenceCount);
+        }
+
+        public void DecrementReferenceCount()
+        {
+            if (Interlocked.Decrement(ref _referenceCount) == 0)
+            {
+                Destroy();
+            }
+        }
+
+        protected virtual void Destroy() { }
     }
 }

+ 5 - 9
Ryujinx.HLE/HOS/Kernel/Common/KResourceLimit.cs

@@ -4,7 +4,7 @@ using System.Collections.Generic;
 
 namespace Ryujinx.HLE.HOS.Kernel.Common
 {
-    class KResourceLimit
+    class KResourceLimit : KAutoObject
     {
         private const int Time10SecondsMs = 10000;
 
@@ -18,9 +18,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Common
 
         private int _waitingThreadsCount;
 
-        private Horizon _system;
-
-        public KResourceLimit(Horizon system)
+        public KResourceLimit(Horizon system) : base(system)
         {
             _current   = new long[(int)LimitableResource.Count];
             _limit     = new long[(int)LimitableResource.Count];
@@ -29,8 +27,6 @@ namespace Ryujinx.HLE.HOS.Kernel.Common
             _lockObj = new object();
 
             _waitingThreads = new LinkedList<KThread>();
-
-            _system = system;
         }
 
         public bool Reserve(LimitableResource resource, ulong amount)
@@ -61,7 +57,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Common
                 {
                     _waitingThreadsCount++;
 
-                    KConditionVariable.Wait(_system, _waitingThreads, _lockObj, timeout);
+                    KConditionVariable.Wait(System, _waitingThreads, _lockObj, timeout);
 
                     _waitingThreadsCount--;
 
@@ -94,7 +90,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Common
             Release(resource, amount, amount);
         }
 
-        private void Release(LimitableResource resource, long usedAmount, long availableAmount)
+        public void Release(LimitableResource resource, long usedAmount, long availableAmount)
         {
             int index = GetIndex(resource);
 
@@ -105,7 +101,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Common
 
                 if (_waitingThreadsCount > 0)
                 {
-                    KConditionVariable.NotifyAll(_system, _waitingThreads);
+                    KConditionVariable.NotifyAll(System, _waitingThreads);
                 }
             }
         }

+ 1 - 1
Ryujinx.HLE/HOS/Kernel/Common/KSynchronizationObject.cs

@@ -5,7 +5,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Common
 {
     class KSynchronizationObject : KAutoObject
     {
-        public LinkedList<KThread> WaitingThreads;
+        public LinkedList<KThread> WaitingThreads { get; }
 
         public KSynchronizationObject(Horizon system) : base(system)
         {

+ 31 - 26
Ryujinx.HLE/HOS/Kernel/Common/KernelResult.cs

@@ -2,31 +2,36 @@ namespace Ryujinx.HLE.HOS.Kernel.Common
 {
     enum KernelResult
     {
-        Success            = 0,
-        InvalidCapability  = 0x1c01,
-        ThreadNotStarted   = 0x7201,
-        ThreadTerminating  = 0x7601,
-        InvalidSize        = 0xca01,
-        InvalidAddress     = 0xcc01,
-        OutOfResource      = 0xce01,
-        OutOfMemory        = 0xd001,
-        HandleTableFull    = 0xd201,
-        InvalidMemState    = 0xd401,
-        InvalidPermission  = 0xd801,
-        InvalidMemRange    = 0xdc01,
-        InvalidPriority    = 0xe001,
-        InvalidCpuCore     = 0xe201,
-        InvalidHandle      = 0xe401,
-        UserCopyFailed     = 0xe601,
-        InvalidCombination = 0xe801,
-        TimedOut           = 0xea01,
-        Cancelled          = 0xec01,
-        MaximumExceeded    = 0xee01,
-        InvalidEnumValue   = 0xf001,
-        NotFound           = 0xf201,
-        InvalidThread      = 0xf401,
-        InvalidState       = 0xfa01,
-        ReservedValue      = 0xfc01,
-        ResLimitExceeded   = 0x10801
+        Success              = 0,
+        SessionCountExceeded = 0xe01,
+        InvalidCapability    = 0x1c01,
+        ThreadNotStarted     = 0x7201,
+        ThreadTerminating    = 0x7601,
+        InvalidSize          = 0xca01,
+        InvalidAddress       = 0xcc01,
+        OutOfResource        = 0xce01,
+        OutOfMemory          = 0xd001,
+        HandleTableFull      = 0xd201,
+        InvalidMemState      = 0xd401,
+        InvalidPermission    = 0xd801,
+        InvalidMemRange      = 0xdc01,
+        InvalidPriority      = 0xe001,
+        InvalidCpuCore       = 0xe201,
+        InvalidHandle        = 0xe401,
+        UserCopyFailed       = 0xe601,
+        InvalidCombination   = 0xe801,
+        TimedOut             = 0xea01,
+        Cancelled            = 0xec01,
+        MaximumExceeded      = 0xee01,
+        InvalidEnumValue     = 0xf001,
+        NotFound             = 0xf201,
+        InvalidThread        = 0xf401,
+        PortRemoteClosed     = 0xf601,
+        InvalidState         = 0xfa01,
+        ReservedValue        = 0xfc01,
+        PortClosed           = 0x10601,
+        ResLimitExceeded     = 0x10801,
+        OutOfVaSpace         = 0x20601,
+        CmdBufferTooSmall    = 0x20801
     }
 }

+ 20 - 0
Ryujinx.HLE/HOS/Kernel/Common/KernelTransfer.cs

@@ -22,6 +22,26 @@ namespace Ryujinx.HLE.HOS.Kernel.Common
             return false;
         }
 
+        public static bool UserToKernelInt32Array(Horizon system, ulong address, int[] values)
+        {
+            KProcess currentProcess = system.Scheduler.GetCurrentProcess();
+
+            for (int index = 0; index < values.Length; index++, address += 4)
+            {
+                if (currentProcess.CpuMemory.IsMapped((long)address) &&
+                    currentProcess.CpuMemory.IsMapped((long)address + 3))
+                {
+                    values[index]= currentProcess.CpuMemory.ReadInt32((long)address);
+                }
+                else
+                {
+                    return false;
+                }
+            }
+
+            return true;
+        }
+
         public static bool UserToKernelString(Horizon system, ulong address, int size, out string value)
         {
             KProcess currentProcess = system.Scheduler.GetCurrentProcess();

+ 10 - 0
Ryujinx.HLE/HOS/Kernel/Ipc/ChannelState.cs

@@ -0,0 +1,10 @@
+namespace Ryujinx.HLE.HOS.Kernel.Ipc
+{
+    enum ChannelState
+    {
+        NotInitialized,
+        Open,
+        ClientDisconnected,
+        ServerDisconnected
+    }
+}

+ 20 - 0
Ryujinx.HLE/HOS/Kernel/Ipc/KBufferDescriptor.cs

@@ -0,0 +1,20 @@
+using Ryujinx.HLE.HOS.Kernel.Memory;
+
+namespace Ryujinx.HLE.HOS.Kernel.Ipc
+{
+    class KBufferDescriptor
+    {
+        public ulong       ClientAddress { get; }
+        public ulong       ServerAddress { get; }
+        public ulong       Size          { get; }
+        public MemoryState State         { get; }
+
+        public KBufferDescriptor(ulong src, ulong dst, ulong size, MemoryState state)
+        {
+            ClientAddress = src;
+            ServerAddress = dst;
+            Size          = size;
+            State         = state;
+        }
+    }
+}

+ 216 - 0
Ryujinx.HLE/HOS/Kernel/Ipc/KBufferDescriptorTable.cs

@@ -0,0 +1,216 @@
+using Ryujinx.Common;
+using Ryujinx.HLE.HOS.Kernel.Common;
+using Ryujinx.HLE.HOS.Kernel.Memory;
+using System.Collections.Generic;
+
+namespace Ryujinx.HLE.HOS.Kernel.Ipc
+{
+    class KBufferDescriptorTable
+    {
+        private const int MaxInternalBuffersCount = 8;
+
+        private List<KBufferDescriptor> _sendBufferDescriptors;
+        private List<KBufferDescriptor> _receiveBufferDescriptors;
+        private List<KBufferDescriptor> _exchangeBufferDescriptors;
+
+        public KBufferDescriptorTable()
+        {
+            _sendBufferDescriptors     = new List<KBufferDescriptor>(MaxInternalBuffersCount);
+            _receiveBufferDescriptors  = new List<KBufferDescriptor>(MaxInternalBuffersCount);
+            _exchangeBufferDescriptors = new List<KBufferDescriptor>(MaxInternalBuffersCount);
+        }
+
+        public KernelResult AddSendBuffer(ulong src, ulong dst, ulong size, MemoryState state)
+        {
+            return Add(_sendBufferDescriptors, src, dst, size, state);
+        }
+
+        public KernelResult AddReceiveBuffer(ulong src, ulong dst, ulong size, MemoryState state)
+        {
+            return Add(_receiveBufferDescriptors, src, dst, size, state);
+        }
+
+        public KernelResult AddExchangeBuffer(ulong src, ulong dst, ulong size, MemoryState state)
+        {
+            return Add(_exchangeBufferDescriptors, src, dst, size, state);
+        }
+
+        private KernelResult Add(List<KBufferDescriptor> list, ulong src, ulong dst, ulong size, MemoryState state)
+        {
+            if (list.Count < MaxInternalBuffersCount)
+            {
+                list.Add(new KBufferDescriptor(src, dst, size, state));
+
+                return KernelResult.Success;
+            }
+
+            return KernelResult.OutOfMemory;
+        }
+
+        public KernelResult CopyBuffersToClient(KMemoryManager memoryManager)
+        {
+            KernelResult result = CopyToClient(memoryManager, _receiveBufferDescriptors);
+
+            if (result != KernelResult.Success)
+            {
+                return result;
+            }
+
+            return CopyToClient(memoryManager, _exchangeBufferDescriptors);
+        }
+
+        private KernelResult CopyToClient(KMemoryManager memoryManager, List<KBufferDescriptor> list)
+        {
+            foreach (KBufferDescriptor desc in list)
+            {
+                MemoryState stateMask;
+
+                switch (desc.State)
+                {
+                    case MemoryState.IpcBuffer0: stateMask = MemoryState.IpcSendAllowedType0; break;
+                    case MemoryState.IpcBuffer1: stateMask = MemoryState.IpcSendAllowedType1; break;
+                    case MemoryState.IpcBuffer3: stateMask = MemoryState.IpcSendAllowedType3; break;
+
+                    default: return KernelResult.InvalidCombination;
+                }
+
+                MemoryAttribute attributeMask = MemoryAttribute.Borrowed | MemoryAttribute.Uncached;
+
+                if (desc.State == MemoryState.IpcBuffer0)
+                {
+                    attributeMask |= MemoryAttribute.DeviceMapped;
+                }
+
+                ulong clientAddrTruncated = BitUtils.AlignDown(desc.ClientAddress, KMemoryManager.PageSize);
+                ulong clientAddrRounded   = BitUtils.AlignUp  (desc.ClientAddress, KMemoryManager.PageSize);
+
+                //Check if address is not aligned, in this case we need to perform 2 copies.
+                if (clientAddrTruncated != clientAddrRounded)
+                {
+                    ulong copySize = clientAddrRounded - desc.ClientAddress;
+
+                    if (copySize > desc.Size)
+                    {
+                        copySize = desc.Size;
+                    }
+
+                    KernelResult result = memoryManager.CopyDataFromCurrentProcess(
+                        desc.ClientAddress,
+                        copySize,
+                        stateMask,
+                        stateMask,
+                        MemoryPermission.ReadAndWrite,
+                        attributeMask,
+                        MemoryAttribute.None,
+                        desc.ServerAddress);
+
+                    if (result != KernelResult.Success)
+                    {
+                        return result;
+                    }
+                }
+
+                ulong clientEndAddr = desc.ClientAddress + desc.Size;
+                ulong serverEndAddr = desc.ServerAddress + desc.Size;
+
+                ulong clientEndAddrTruncated = BitUtils.AlignDown(clientEndAddr, KMemoryManager.PageSize);
+                ulong clientEndAddrRounded   = BitUtils.AlignUp  (clientEndAddr, KMemoryManager.PageSize);
+                ulong serverEndAddrTruncated = BitUtils.AlignDown(clientEndAddr, KMemoryManager.PageSize);
+
+                if (clientEndAddrTruncated < clientAddrRounded)
+                {
+                    KernelResult result = memoryManager.CopyDataToCurrentProcess(
+                        clientEndAddrTruncated,
+                        clientEndAddr - clientEndAddrTruncated,
+                        serverEndAddrTruncated,
+                        stateMask,
+                        stateMask,
+                        MemoryPermission.ReadAndWrite,
+                        attributeMask,
+                        MemoryAttribute.None);
+
+                    if (result != KernelResult.Success)
+                    {
+                        return result;
+                    }
+                }
+            }
+
+            return KernelResult.Success;
+        }
+
+        public KernelResult UnmapServerBuffers(KMemoryManager memoryManager)
+        {
+            KernelResult result = UnmapServer(memoryManager, _sendBufferDescriptors);
+
+            if (result != KernelResult.Success)
+            {
+                return result;
+            }
+
+            result = UnmapServer(memoryManager, _receiveBufferDescriptors);
+
+            if (result != KernelResult.Success)
+            {
+                return result;
+            }
+
+            return UnmapServer(memoryManager, _exchangeBufferDescriptors);
+        }
+
+        private KernelResult UnmapServer(KMemoryManager memoryManager, List<KBufferDescriptor> list)
+        {
+            foreach (KBufferDescriptor descriptor in list)
+            {
+                KernelResult result = memoryManager.UnmapNoAttributeIfStateEquals(
+                    descriptor.ServerAddress,
+                    descriptor.Size,
+                    descriptor.State);
+
+                if (result != KernelResult.Success)
+                {
+                    return result;
+                }
+            }
+
+            return KernelResult.Success;
+        }
+
+        public KernelResult RestoreClientBuffers(KMemoryManager memoryManager)
+        {
+            KernelResult result = RestoreClient(memoryManager, _sendBufferDescriptors);
+
+            if (result != KernelResult.Success)
+            {
+                return result;
+            }
+
+            result = RestoreClient(memoryManager, _receiveBufferDescriptors);
+
+            if (result != KernelResult.Success)
+            {
+                return result;
+            }
+
+            return RestoreClient(memoryManager, _exchangeBufferDescriptors);
+        }
+
+        private KernelResult RestoreClient(KMemoryManager memoryManager, List<KBufferDescriptor> list)
+        {
+            foreach (KBufferDescriptor descriptor in list)
+            {
+                KernelResult result = memoryManager.UnmapIpcRestorePermission(
+                    descriptor.ClientAddress,
+                    descriptor.Size,
+                    descriptor.State);
+
+                if (result != KernelResult.Success)
+                {
+                    return result;
+                }
+            }
+
+            return KernelResult.Success;
+        }
+    }
+}

+ 108 - 2
Ryujinx.HLE/HOS/Kernel/Ipc/KClientPort.cs

@@ -1,4 +1,6 @@
 using Ryujinx.HLE.HOS.Kernel.Common;
+using Ryujinx.HLE.HOS.Kernel.Process;
+using Ryujinx.HLE.HOS.Services;
 
 namespace Ryujinx.HLE.HOS.Kernel.Ipc
 {
@@ -10,12 +12,116 @@ namespace Ryujinx.HLE.HOS.Kernel.Ipc
 
         private KPort _parent;
 
-        public KClientPort(Horizon system) : base(system) { }
+        public bool IsLight => _parent.IsLight;
 
-        public void Initialize(KPort parent, int maxSessions)
+        private object _countIncLock;
+
+        //TODO: Remove that, we need it for now to allow HLE
+        //SM implementation to work with the new IPC system.
+        public IpcService Service { get; set; }
+
+        public KClientPort(Horizon system, KPort parent, int maxSessions) : base(system)
         {
             _maxSessions = maxSessions;
             _parent      = parent;
+
+            _countIncLock = new object();
+        }
+
+        public KernelResult Connect(out KClientSession clientSession)
+        {
+            clientSession = null;
+
+            KProcess currentProcess = System.Scheduler.GetCurrentProcess();
+
+            if (currentProcess.ResourceLimit != null &&
+               !currentProcess.ResourceLimit.Reserve(LimitableResource.Session, 1))
+            {
+                return KernelResult.ResLimitExceeded;
+            }
+
+            lock (_countIncLock)
+            {
+                if (_sessionsCount < _maxSessions)
+                {
+                    _sessionsCount++;
+                }
+                else
+                {
+                    currentProcess.ResourceLimit?.Release(LimitableResource.Session, 1);
+
+                    return KernelResult.SessionCountExceeded;
+                }
+
+                if (_currentCapacity < _sessionsCount)
+                {
+                    _currentCapacity = _sessionsCount;
+                }
+            }
+
+            KSession session = new KSession(System);
+
+            if (Service != null)
+            {
+                session.ClientSession.Service = Service;
+            }
+
+            KernelResult result = _parent.EnqueueIncomingSession(session.ServerSession);
+
+            if (result != KernelResult.Success)
+            {
+                session.ClientSession.DecrementReferenceCount();
+                session.ServerSession.DecrementReferenceCount();
+
+                return result;
+            }
+
+            clientSession = session.ClientSession;
+
+            return result;
+        }
+
+        public KernelResult ConnectLight(out KLightClientSession clientSession)
+        {
+            clientSession = null;
+
+            KProcess currentProcess = System.Scheduler.GetCurrentProcess();
+
+            if (currentProcess.ResourceLimit != null &&
+               !currentProcess.ResourceLimit.Reserve(LimitableResource.Session, 1))
+            {
+                return KernelResult.ResLimitExceeded;
+            }
+
+            lock (_countIncLock)
+            {
+                if (_sessionsCount < _maxSessions)
+                {
+                    _sessionsCount++;
+                }
+                else
+                {
+                    currentProcess.ResourceLimit?.Release(LimitableResource.Session, 1);
+
+                    return KernelResult.SessionCountExceeded;
+                }
+            }
+
+            KLightSession session = new KLightSession(System);
+
+            KernelResult result = _parent.EnqueueIncomingLightSession(session.ServerSession);
+
+            if (result != KernelResult.Success)
+            {
+                session.ClientSession.DecrementReferenceCount();
+                session.ServerSession.DecrementReferenceCount();
+
+                return result;
+            }
+
+            clientSession = session.ClientSession;
+
+            return result;
         }
 
         public new static KernelResult RemoveName(Horizon system, string name)

+ 60 - 0
Ryujinx.HLE/HOS/Kernel/Ipc/KClientSession.cs

@@ -0,0 +1,60 @@
+using Ryujinx.HLE.HOS.Kernel.Common;
+using Ryujinx.HLE.HOS.Kernel.Process;
+using Ryujinx.HLE.HOS.Kernel.Threading;
+using Ryujinx.HLE.HOS.Services;
+
+namespace Ryujinx.HLE.HOS.Kernel.Ipc
+{
+    class KClientSession : KSynchronizationObject
+    {
+        public KProcess CreatorProcess { get; }
+
+        private KSession _parent;
+
+        public ChannelState State { get; set; }
+
+        //TODO: Remove that, we need it for now to allow HLE
+        //services implementation to work with the new IPC system.
+        public IpcService Service { get; set; }
+
+        public KClientSession(Horizon system, KSession parent) : base(system)
+        {
+            _parent = parent;
+
+            State = ChannelState.Open;
+
+            CreatorProcess = system.Scheduler.GetCurrentProcess();
+
+            CreatorProcess.IncrementReferenceCount();
+        }
+
+        public KernelResult SendSyncRequest(ulong customCmdBuffAddr = 0, ulong customCmdBuffSize = 0)
+        {
+            KThread currentThread = System.Scheduler.GetCurrentThread();
+
+            KSessionRequest request = new KSessionRequest(currentThread, customCmdBuffAddr, customCmdBuffSize);
+
+            System.CriticalSection.Enter();
+
+            currentThread.SignaledObj   = null;
+            currentThread.ObjSyncResult = KernelResult.Success;
+
+            KernelResult result = _parent.ServerSession.EnqueueRequest(request);
+
+            System.CriticalSection.Leave();
+
+            if (result == KernelResult.Success)
+            {
+                result = currentThread.ObjSyncResult;
+            }
+
+            return result;
+        }
+
+        protected override void Destroy()
+        {
+            _parent.DisconnectClient();
+            _parent.DecrementReferenceCount();
+        }
+    }
+}

+ 14 - 0
Ryujinx.HLE/HOS/Kernel/Ipc/KLightClientSession.cs

@@ -0,0 +1,14 @@
+using Ryujinx.HLE.HOS.Kernel.Common;
+
+namespace Ryujinx.HLE.HOS.Kernel.Ipc
+{
+    class KLightClientSession : KAutoObject
+    {
+        private KLightSession _parent;
+
+        public KLightClientSession(Horizon system, KLightSession parent) : base(system)
+        {
+            _parent = parent;
+        }
+    }
+}

+ 14 - 0
Ryujinx.HLE/HOS/Kernel/Ipc/KLightServerSession.cs

@@ -0,0 +1,14 @@
+using Ryujinx.HLE.HOS.Kernel.Common;
+
+namespace Ryujinx.HLE.HOS.Kernel.Ipc
+{
+    class KLightServerSession : KAutoObject
+    {
+        private KLightSession _parent;
+
+        public KLightServerSession(Horizon system, KLightSession parent) : base(system)
+        {
+            _parent = parent;
+        }
+    }
+}

+ 20 - 0
Ryujinx.HLE/HOS/Kernel/Ipc/KLightSession.cs

@@ -0,0 +1,20 @@
+using Ryujinx.HLE.HOS.Kernel.Common;
+
+namespace Ryujinx.HLE.HOS.Kernel.Ipc
+{
+    class KLightSession : KAutoObject
+    {
+        public KLightServerSession ServerSession { get; }
+        public KLightClientSession ClientSession { get; }
+
+        private bool _hasBeenInitialized;
+
+        public KLightSession(Horizon system) : base(system)
+        {
+            ServerSession = new KLightServerSession(system, this);
+            ClientSession = new KLightClientSession(system, this);
+
+            _hasBeenInitialized = true;
+        }
+    }
+}

+ 54 - 11
Ryujinx.HLE/HOS/Kernel/Ipc/KPort.cs

@@ -4,25 +4,68 @@ namespace Ryujinx.HLE.HOS.Kernel.Ipc
 {
     class KPort : KAutoObject
     {
-        public KServerPort ServerPort { get; private set; }
-        public KClientPort ClientPort { get; private set; }
+        public KServerPort ServerPort { get; }
+        public KClientPort ClientPort { get; }
 
         private long _nameAddress;
-        private bool _isLight;
 
-        public KPort(Horizon system) : base(system)
+        private ChannelState _state;
+
+        public bool IsLight { get; private set; }
+
+        public KPort(Horizon system, int maxSessions, bool isLight, long nameAddress) : base(system)
         {
-            ServerPort = new KServerPort(system);
-            ClientPort = new KClientPort(system);
+            ServerPort = new KServerPort(system, this);
+            ClientPort = new KClientPort(system, this, maxSessions);
+
+            IsLight      = isLight;
+            _nameAddress = nameAddress;
+
+            _state = ChannelState.Open;
         }
 
-        public void Initialize(int maxSessions, bool isLight, long nameAddress)
+        public KernelResult EnqueueIncomingSession(KServerSession session)
         {
-            ServerPort.Initialize(this);
-            ClientPort.Initialize(this, maxSessions);
+            KernelResult result;
 
-            _isLight     = isLight;
-            _nameAddress = nameAddress;
+            System.CriticalSection.Enter();
+
+            if (_state == ChannelState.Open)
+            {
+                ServerPort.EnqueueIncomingSession(session);
+
+                result = KernelResult.Success;
+            }
+            else
+            {
+                result = KernelResult.PortClosed;
+            }
+
+            System.CriticalSection.Leave();
+
+            return result;
+        }
+
+        public KernelResult EnqueueIncomingLightSession(KLightServerSession session)
+        {
+            KernelResult result;
+
+            System.CriticalSection.Enter();
+
+            if (_state == ChannelState.Open)
+            {
+                ServerPort.EnqueueIncomingLightSession(session);
+
+                result = KernelResult.Success;
+            }
+            else
+            {
+                result = KernelResult.PortClosed;
+            }
+
+            System.CriticalSection.Leave();
+
+            return result;
         }
     }
 }

+ 73 - 2
Ryujinx.HLE/HOS/Kernel/Ipc/KServerPort.cs

@@ -1,16 +1,87 @@
 using Ryujinx.HLE.HOS.Kernel.Common;
+using System.Collections.Generic;
 
 namespace Ryujinx.HLE.HOS.Kernel.Ipc
 {
     class KServerPort : KSynchronizationObject
     {
+        private LinkedList<KServerSession>      _incomingConnections;
+        private LinkedList<KLightServerSession> _lightIncomingConnections;
+
         private KPort _parent;
 
-        public KServerPort(Horizon system) : base(system) { }
+        public bool IsLight => _parent.IsLight;
 
-        public void Initialize(KPort parent)
+        public KServerPort(Horizon system, KPort parent) : base(system)
         {
             _parent = parent;
+
+            _incomingConnections      = new LinkedList<KServerSession>();
+            _lightIncomingConnections = new LinkedList<KLightServerSession>();
+        }
+
+        public void EnqueueIncomingSession(KServerSession session)
+        {
+            AcceptIncomingConnection(_incomingConnections, session);
+        }
+
+        public void EnqueueIncomingLightSession(KLightServerSession session)
+        {
+            AcceptIncomingConnection(_lightIncomingConnections, session);
+        }
+
+        private void AcceptIncomingConnection<T>(LinkedList<T> list, T session)
+        {
+            System.CriticalSection.Enter();
+
+            list.AddLast(session);
+
+            if (list.Count == 1)
+            {
+                Signal();
+            }
+
+            System.CriticalSection.Leave();
+        }
+
+        public KServerSession AcceptIncomingConnection()
+        {
+            return AcceptIncomingConnection(_incomingConnections);
+        }
+
+        public KLightServerSession AcceptIncomingLightConnection()
+        {
+            return AcceptIncomingConnection(_lightIncomingConnections);
+        }
+
+        private T AcceptIncomingConnection<T>(LinkedList<T> list)
+        {
+            T session = default(T);
+
+            System.CriticalSection.Enter();
+
+            if (list.Count != 0)
+            {
+                session = list.First.Value;
+
+                list.RemoveFirst();
+            }
+
+            System.CriticalSection.Leave();
+
+            return session;
+        }
+
+        public override bool IsSignaled()
+        {
+            if (_parent.IsLight)
+            {
+                return _lightIncomingConnections.Count != 0;
+            }
+            else
+            {
+                return _incomingConnections.Count != 0;
+            }
         }
     }
 }

+ 1262 - 0
Ryujinx.HLE/HOS/Kernel/Ipc/KServerSession.cs

@@ -0,0 +1,1262 @@
+using Ryujinx.Common;
+using Ryujinx.HLE.HOS.Kernel.Common;
+using Ryujinx.HLE.HOS.Kernel.Memory;
+using Ryujinx.HLE.HOS.Kernel.Process;
+using Ryujinx.HLE.HOS.Kernel.Threading;
+using System.Collections.Generic;
+
+namespace Ryujinx.HLE.HOS.Kernel.Ipc
+{
+    class KServerSession : KSynchronizationObject
+    {
+        private static readonly MemoryState[] IpcMemoryStates = new MemoryState[]
+        {
+            MemoryState.IpcBuffer3,
+            MemoryState.IpcBuffer0,
+            MemoryState.IpcBuffer1,
+            (MemoryState)0xfffce5d4 //This is invalid, shouldn't be accessed.
+        };
+
+        private struct Message
+        {
+            public ulong Address     { get; }
+            public ulong DramAddress { get; }
+            public ulong Size        { get; }
+            public bool  IsCustom    { get; }
+
+            public Message(KThread thread, ulong customCmdBuffAddress, ulong customCmdBuffSize)
+            {
+                IsCustom = customCmdBuffAddress != 0;
+
+                if (IsCustom)
+                {
+                    Address = customCmdBuffAddress;
+                    Size    = customCmdBuffSize;
+
+                    KProcess process = thread.Owner;
+
+                    DramAddress = process.MemoryManager.GetDramAddressFromVa(Address);
+                }
+                else
+                {
+                    Address     = thread.TlsAddress;
+                    DramAddress = thread.TlsDramAddress;
+                    Size        = 0x100;
+                }
+            }
+
+            public Message(KSessionRequest request) : this(
+                request.ClientThread,
+                request.CustomCmdBuffAddr,
+                request.CustomCmdBuffSize) { }
+        }
+
+        private struct MessageHeader
+        {
+            public uint Word0 { get; }
+            public uint Word1 { get; }
+            public uint Word2 { get; }
+
+            public uint PointerBuffersCount  { get; }
+            public uint SendBuffersCount     { get; }
+            public uint ReceiveBuffersCount  { get; }
+            public uint ExchangeBuffersCount { get; }
+
+            public uint RawDataSizeInWords { get; }
+
+            public uint ReceiveListType { get; }
+
+            public uint MessageSizeInWords       { get; }
+            public uint ReceiveListOffsetInWords { get; }
+            public uint ReceiveListOffset        { get; }
+
+            public bool HasHandles { get; }
+
+            public bool HasPid { get; }
+
+            public uint CopyHandlesCount { get; }
+            public uint MoveHandlesCount { get; }
+
+            public MessageHeader(uint word0, uint word1, uint word2)
+            {
+                Word0 = word0;
+                Word1 = word1;
+                Word2 = word2;
+
+                HasHandles = word1 >> 31 != 0;
+
+                uint handleDescSizeInWords = 0;
+
+                if (HasHandles)
+                {
+                    uint pidSize = (word2 & 1) * 8;
+
+                    HasPid = pidSize != 0;
+
+                    CopyHandlesCount = (word2 >> 1) & 0xf;
+                    MoveHandlesCount = (word2 >> 5) & 0xf;
+
+                    handleDescSizeInWords = (pidSize + CopyHandlesCount * 4 + MoveHandlesCount * 4) / 4;
+                }
+                else
+                {
+                    HasPid = false;
+
+                    CopyHandlesCount = 0;
+                    MoveHandlesCount = 0;
+                }
+
+                PointerBuffersCount  = (word0 >> 16) & 0xf;
+                SendBuffersCount     = (word0 >> 20) & 0xf;
+                ReceiveBuffersCount  = (word0 >> 24) & 0xf;
+                ExchangeBuffersCount =  word0 >> 28;
+
+                uint pointerDescSizeInWords  = PointerBuffersCount  * 2;
+                uint sendDescSizeInWords     = SendBuffersCount     * 3;
+                uint receiveDescSizeInWords  = ReceiveBuffersCount  * 3;
+                uint exchangeDescSizeInWords = ExchangeBuffersCount * 3;
+
+                RawDataSizeInWords = word1 & 0x3ff;
+
+                ReceiveListType = (word1 >> 10) & 0xf;
+
+                ReceiveListOffsetInWords = (word1 >> 20) & 0x7ff;
+
+                uint paddingSizeInWords = HasHandles ? 3u : 2u;
+
+                MessageSizeInWords = pointerDescSizeInWords  +
+                                     sendDescSizeInWords     +
+                                     receiveDescSizeInWords  +
+                                     exchangeDescSizeInWords +
+                                     RawDataSizeInWords      +
+                                     paddingSizeInWords      +
+                                     handleDescSizeInWords;
+
+                if (ReceiveListOffsetInWords == 0)
+                {
+                    ReceiveListOffsetInWords = MessageSizeInWords;
+                }
+
+                ReceiveListOffset = ReceiveListOffsetInWords * 4;
+            }
+        }
+
+        private struct PointerBufferDesc
+        {
+            public uint ReceiveIndex { get; }
+
+            public uint  BufferSize    { get; }
+            public ulong BufferAddress { get; set; }
+
+            public PointerBufferDesc(ulong dword)
+            {
+                ReceiveIndex = (uint)dword & 0xf;
+                BufferSize   = (uint)dword >> 16;
+
+                BufferAddress  = (dword >> 2)  & 0x70;
+                BufferAddress |= (dword >> 12) & 0xf;
+
+                BufferAddress = (BufferAddress << 32) | (dword >> 32);
+            }
+
+            public ulong Pack()
+            {
+                ulong dword = (ReceiveIndex & 0xf) | ((BufferSize & 0xffff) << 16);
+
+                dword |=  BufferAddress << 32;
+                dword |= (BufferAddress >> 20) & 0xf000;
+                dword |= (BufferAddress >> 30) & 0xffc0;
+
+                return dword;
+            }
+        }
+
+        private KSession _parent;
+
+        private LinkedList<KSessionRequest> _requests;
+
+        private KSessionRequest _activeRequest;
+
+        public KServerSession(Horizon system, KSession parent) : base(system)
+        {
+            _parent = parent;
+
+            _requests = new LinkedList<KSessionRequest>();
+        }
+
+        public KernelResult EnqueueRequest(KSessionRequest request)
+        {
+            if (_parent.ClientSession.State != ChannelState.Open)
+            {
+                return KernelResult.PortRemoteClosed;
+            }
+
+            if (request.AsyncEvent == null)
+            {
+                if (request.ClientThread.ShallBeTerminated ||
+                    request.ClientThread.SchedFlags == ThreadSchedState.TerminationPending)
+                {
+                    return KernelResult.ThreadTerminating;
+                }
+
+                request.ClientThread.Reschedule(ThreadSchedState.Paused);
+            }
+
+            _requests.AddLast(request);
+
+            if (_requests.Count == 1)
+            {
+                Signal();
+            }
+
+            return KernelResult.Success;
+        }
+
+        public KernelResult Receive(ulong customCmdBuffAddr = 0, ulong customCmdBuffSize = 0)
+        {
+            KThread  serverThread  = System.Scheduler.GetCurrentThread();
+            KProcess serverProcess = serverThread.Owner;
+
+            System.CriticalSection.Enter();
+
+            if (_parent.ClientSession.State != ChannelState.Open)
+            {
+                System.CriticalSection.Leave();
+
+                return KernelResult.PortRemoteClosed;
+            }
+
+            if (_activeRequest != null || !DequeueRequest(out KSessionRequest request))
+            {
+                System.CriticalSection.Leave();
+
+                return KernelResult.NotFound;
+            }
+
+            if (request.ClientThread == null)
+            {
+                System.CriticalSection.Leave();
+
+                return KernelResult.PortRemoteClosed;
+            }
+
+            KThread  clientThread  = request.ClientThread;
+            KProcess clientProcess = clientThread.Owner;
+
+            System.CriticalSection.Leave();
+
+            _activeRequest = request;
+
+            request.ServerProcess = serverProcess;
+
+            Message clientMsg = new Message(request);
+            Message serverMsg = new Message(serverThread, customCmdBuffAddr, customCmdBuffSize);
+
+            MessageHeader clientHeader = GetClientMessageHeader(clientMsg);
+            MessageHeader serverHeader = GetServerMessageHeader(serverMsg);
+
+            KernelResult serverResult = KernelResult.NotFound;
+            KernelResult clientResult = KernelResult.Success;
+
+            void CleanUpForError()
+            {
+                if (request.BufferDescriptorTable.UnmapServerBuffers(serverProcess.MemoryManager) == KernelResult.Success)
+                {
+                    request.BufferDescriptorTable.RestoreClientBuffers(clientProcess.MemoryManager);
+                }
+
+                CloseAllHandles(serverMsg, clientHeader, serverProcess);
+
+                System.CriticalSection.Enter();
+
+                _activeRequest = null;
+
+                if (_requests.Count != 0)
+                {
+                    Signal();
+                }
+
+                System.CriticalSection.Leave();
+
+                WakeClientThread(request, clientResult);
+            }
+
+            if (clientHeader.ReceiveListType < 2 &&
+                clientHeader.ReceiveListOffset > clientMsg.Size)
+            {
+                CleanUpForError();
+
+                return KernelResult.InvalidCombination;
+            }
+            else if (clientHeader.ReceiveListType == 2 &&
+                     clientHeader.ReceiveListOffset + 8 > clientMsg.Size)
+            {
+                CleanUpForError();
+
+                return KernelResult.InvalidCombination;
+            }
+            else if (clientHeader.ReceiveListType > 2 &&
+                     clientHeader.ReceiveListType * 8 - 0x10 + clientHeader.ReceiveListOffset > clientMsg.Size)
+            {
+                CleanUpForError();
+
+                return KernelResult.InvalidCombination;
+            }
+
+            if (clientHeader.ReceiveListOffsetInWords < clientHeader.MessageSizeInWords)
+            {
+                CleanUpForError();
+
+                return KernelResult.InvalidCombination;
+            }
+
+            if (clientHeader.MessageSizeInWords * 4 > clientMsg.Size)
+            {
+                CleanUpForError();
+
+                return KernelResult.CmdBufferTooSmall;
+            }
+
+            ulong[] receiveList = GetReceiveList(
+                serverMsg,
+                serverHeader.ReceiveListType,
+                serverHeader.ReceiveListOffset);
+
+            serverProcess.CpuMemory.WriteUInt32((long)serverMsg.Address + 0, clientHeader.Word0);
+            serverProcess.CpuMemory.WriteUInt32((long)serverMsg.Address + 4, clientHeader.Word1);
+
+            uint offset;
+
+            //Copy handles.
+            if (clientHeader.HasHandles)
+            {
+                if (clientHeader.MoveHandlesCount != 0)
+                {
+                    CleanUpForError();
+
+                    return KernelResult.InvalidCombination;
+                }
+
+                serverProcess.CpuMemory.WriteUInt32((long)serverMsg.Address + 8, clientHeader.Word2);
+
+                offset = 3;
+
+                if (clientHeader.HasPid)
+                {
+                    serverProcess.CpuMemory.WriteInt64((long)serverMsg.Address + offset * 4, clientProcess.Pid);
+
+                    offset += 2;
+                }
+
+                for (int index = 0; index < clientHeader.CopyHandlesCount; index++)
+                {
+                    int newHandle = 0;
+
+                    int handle = System.Device.Memory.ReadInt32((long)clientMsg.DramAddress + offset * 4);
+
+                    if (clientResult == KernelResult.Success && handle != 0)
+                    {
+                        clientResult = GetCopyObjectHandle(clientThread, serverProcess, handle, out newHandle);
+                    }
+
+                    serverProcess.CpuMemory.WriteInt32((long)serverMsg.Address + offset * 4, newHandle);
+
+                    offset++;
+                }
+
+                for (int index = 0; index < clientHeader.MoveHandlesCount; index++)
+                {
+                    int newHandle = 0;
+
+                    int handle = System.Device.Memory.ReadInt32((long)clientMsg.DramAddress + offset * 4);
+
+                    if (handle != 0)
+                    {
+                        if (clientResult == KernelResult.Success)
+                        {
+                            clientResult = GetMoveObjectHandle(clientProcess, serverProcess, handle, out newHandle);
+                        }
+                        else
+                        {
+                            clientProcess.HandleTable.CloseHandle(handle);
+                        }
+                    }
+
+                    serverProcess.CpuMemory.WriteInt32((long)serverMsg.Address + offset * 4, newHandle);
+
+                    offset++;
+                }
+
+                if (clientResult != KernelResult.Success)
+                {
+                    CleanUpForError();
+
+                    return serverResult;
+                }
+            }
+            else
+            {
+                offset = 2;
+            }
+
+            //Copy pointer/receive list buffers.
+            uint recvListDstOffset = 0;
+
+            for (int index = 0; index < clientHeader.PointerBuffersCount; index++)
+            {
+                ulong pointerDesc = System.Device.Memory.ReadUInt64((long)clientMsg.DramAddress + offset * 4);
+
+                PointerBufferDesc descriptor = new PointerBufferDesc(pointerDesc);
+
+                if (descriptor.BufferSize != 0)
+                {
+                    clientResult = GetReceiveListAddress(
+                        descriptor,
+                        serverMsg,
+                        serverHeader.ReceiveListType,
+                        clientHeader.MessageSizeInWords,
+                        receiveList,
+                        ref       recvListDstOffset,
+                        out ulong recvListBufferAddress);
+
+                    if (clientResult != KernelResult.Success)
+                    {
+                        CleanUpForError();
+
+                        return serverResult;
+                    }
+
+                    clientResult = clientProcess.MemoryManager.CopyDataToCurrentProcess(
+                        recvListBufferAddress,
+                        descriptor.BufferSize,
+                        descriptor.BufferAddress,
+                        MemoryState.IsPoolAllocated,
+                        MemoryState.IsPoolAllocated,
+                        MemoryPermission.Read,
+                        MemoryAttribute.Uncached,
+                        MemoryAttribute.None);
+
+                    if (clientResult != KernelResult.Success)
+                    {
+                        CleanUpForError();
+
+                        return serverResult;
+                    }
+
+                    descriptor.BufferAddress = recvListBufferAddress;
+                }
+                else
+                {
+                    descriptor.BufferAddress = 0;
+                }
+
+                serverProcess.CpuMemory.WriteUInt64((long)serverMsg.Address + offset * 4, descriptor.Pack());
+
+                offset += 2;
+            }
+
+            //Copy send, receive and exchange buffers.
+            uint totalBuffersCount =
+                clientHeader.SendBuffersCount    +
+                clientHeader.ReceiveBuffersCount +
+                clientHeader.ExchangeBuffersCount;
+
+            for (int index = 0; index < totalBuffersCount; index++)
+            {
+                long clientDescAddress = (long)clientMsg.DramAddress + offset * 4;
+
+                uint descWord0 = System.Device.Memory.ReadUInt32(clientDescAddress + 0);
+                uint descWord1 = System.Device.Memory.ReadUInt32(clientDescAddress + 4);
+                uint descWord2 = System.Device.Memory.ReadUInt32(clientDescAddress + 8);
+
+                bool isSendDesc     = index <  clientHeader.SendBuffersCount;
+                bool isExchangeDesc = index >= clientHeader.SendBuffersCount + clientHeader.ReceiveBuffersCount;
+
+                bool notReceiveDesc = isSendDesc || isExchangeDesc;
+                bool isReceiveDesc  = !notReceiveDesc;
+
+                MemoryPermission permission = index >= clientHeader.SendBuffersCount
+                    ? MemoryPermission.ReadAndWrite
+                    : MemoryPermission.Read;
+
+                uint sizeHigh4 = (descWord2 >> 24) & 0xf;
+
+                ulong bufferSize = descWord0 | (ulong)sizeHigh4 << 32;
+
+                ulong dstAddress = 0;
+
+                if (bufferSize != 0)
+                {
+                    ulong bufferAddress;
+
+                    bufferAddress  =   descWord2 >> 28;
+                    bufferAddress |= ((descWord2 >> 2) & 7) << 4;
+
+                    bufferAddress = (bufferAddress << 32) | descWord1;
+
+                    MemoryState state = IpcMemoryStates[(descWord2 + 1) & 3];
+
+                    clientResult = serverProcess.MemoryManager.MapBufferFromClientProcess(
+                        bufferSize,
+                        bufferAddress,
+                        clientProcess.MemoryManager,
+                        permission,
+                        state,
+                        notReceiveDesc,
+                        out dstAddress);
+
+                    if (clientResult != KernelResult.Success)
+                    {
+                        CleanUpForError();
+
+                        return serverResult;
+                    }
+
+                    if (isSendDesc)
+                    {
+                        clientResult = request.BufferDescriptorTable.AddSendBuffer(bufferAddress, dstAddress, bufferSize, state);
+                    }
+                    else if (isReceiveDesc)
+                    {
+                        clientResult = request.BufferDescriptorTable.AddReceiveBuffer(bufferAddress, dstAddress, bufferSize, state);
+                    }
+                    else /* if (isExchangeDesc) */
+                    {
+                        clientResult = request.BufferDescriptorTable.AddExchangeBuffer(bufferAddress, dstAddress, bufferSize, state);
+                    }
+
+                    if (clientResult != KernelResult.Success)
+                    {
+                        CleanUpForError();
+
+                        return serverResult;
+                    }
+                }
+
+                descWord1 = (uint)dstAddress;
+
+                descWord2 &= 3;
+
+                descWord2 |= sizeHigh4 << 24;
+
+                descWord2 |= (uint)(dstAddress >> 34) & 0x3ffffffc;
+                descWord2 |= (uint)(dstAddress >> 4)  & 0xf0000000;
+
+                long serverDescAddress = (long)serverMsg.Address + offset * 4;
+
+                serverProcess.CpuMemory.WriteUInt32(serverDescAddress + 0, descWord0);
+                serverProcess.CpuMemory.WriteUInt32(serverDescAddress + 4, descWord1);
+                serverProcess.CpuMemory.WriteUInt32(serverDescAddress + 8, descWord2);
+
+                offset += 3;
+            }
+
+            //Copy raw data.
+            if (clientHeader.RawDataSizeInWords != 0)
+            {
+                ulong copySrc = clientMsg.Address + offset * 4;
+                ulong copyDst = serverMsg.Address + offset * 4;
+
+                ulong copySize = clientHeader.RawDataSizeInWords * 4;
+
+                if (serverMsg.IsCustom || clientMsg.IsCustom)
+                {
+                    MemoryPermission permission = clientMsg.IsCustom
+                        ? MemoryPermission.None
+                        : MemoryPermission.Read;
+
+                    clientResult = clientProcess.MemoryManager.CopyDataToCurrentProcess(
+                        copyDst,
+                        copySize,
+                        copySrc,
+                        MemoryState.IsPoolAllocated,
+                        MemoryState.IsPoolAllocated,
+                        permission,
+                        MemoryAttribute.Uncached,
+                        MemoryAttribute.None);
+                }
+                else
+                {
+                    copySrc = clientProcess.MemoryManager.GetDramAddressFromVa(copySrc);
+                    copyDst = serverProcess.MemoryManager.GetDramAddressFromVa(copyDst);
+
+                    System.Device.Memory.Copy(copyDst, copySrc, copySize);
+                }
+
+                if (clientResult != KernelResult.Success)
+                {
+                    CleanUpForError();
+
+                    return serverResult;
+                }
+            }
+
+            return KernelResult.Success;
+        }
+
+        public KernelResult Reply(ulong customCmdBuffAddr = 0, ulong customCmdBuffSize = 0)
+        {
+            KThread  serverThread  = System.Scheduler.GetCurrentThread();
+            KProcess serverProcess = serverThread.Owner;
+
+            System.CriticalSection.Enter();
+
+            if (_activeRequest == null)
+            {
+                System.CriticalSection.Leave();
+
+                return KernelResult.InvalidState;
+            }
+
+            KSessionRequest request = _activeRequest;
+
+            _activeRequest = null;
+
+            if (_requests.Count != 0)
+            {
+                Signal();
+            }
+
+            System.CriticalSection.Leave();
+
+            KThread  clientThread  = request.ClientThread;
+            KProcess clientProcess = clientThread.Owner;
+
+            Message clientMsg = new Message(request);
+            Message serverMsg = new Message(serverThread, customCmdBuffAddr, customCmdBuffSize);
+
+            MessageHeader clientHeader = GetClientMessageHeader(clientMsg);
+            MessageHeader serverHeader = GetServerMessageHeader(serverMsg);
+
+            KernelResult clientResult = KernelResult.Success;
+            KernelResult serverResult = KernelResult.Success;
+
+            void CleanUpForError()
+            {
+                CloseAllHandles(clientMsg, serverHeader, clientProcess);
+
+                CancelRequest(request, clientResult);
+            }
+
+            if (clientHeader.ReceiveListType < 2 &&
+                clientHeader.ReceiveListOffset > clientMsg.Size)
+            {
+                CleanUpForError();
+
+                return KernelResult.InvalidCombination;
+            }
+            else if (clientHeader.ReceiveListType == 2 &&
+                     clientHeader.ReceiveListOffset + 8 > clientMsg.Size)
+            {
+                CleanUpForError();
+
+                return KernelResult.InvalidCombination;
+            }
+            else if (clientHeader.ReceiveListType > 2 &&
+                     clientHeader.ReceiveListType * 8 - 0x10 + clientHeader.ReceiveListOffset > clientMsg.Size)
+            {
+                CleanUpForError();
+
+                return KernelResult.InvalidCombination;
+            }
+
+            if (clientHeader.ReceiveListOffsetInWords < clientHeader.MessageSizeInWords)
+            {
+                CleanUpForError();
+
+                return KernelResult.InvalidCombination;
+            }
+
+            if (serverHeader.MessageSizeInWords * 4 > clientMsg.Size)
+            {
+                CleanUpForError();
+
+                return KernelResult.CmdBufferTooSmall;
+            }
+
+            if (serverHeader.SendBuffersCount     != 0 ||
+                serverHeader.ReceiveBuffersCount  != 0 ||
+                serverHeader.ExchangeBuffersCount != 0)
+            {
+                CleanUpForError();
+
+                return KernelResult.InvalidCombination;
+            }
+
+            //Read receive list.
+            ulong[] receiveList = GetReceiveList(
+                clientMsg,
+                clientHeader.ReceiveListType,
+                clientHeader.ReceiveListOffset);
+
+            //Copy receive and exchange buffers.
+            clientResult = request.BufferDescriptorTable.CopyBuffersToClient(clientProcess.MemoryManager);
+
+            if (clientResult != KernelResult.Success)
+            {
+                CleanUpForError();
+
+                return serverResult;
+            }
+
+            //Copy header.
+            System.Device.Memory.WriteUInt32((long)clientMsg.DramAddress + 0, serverHeader.Word0);
+            System.Device.Memory.WriteUInt32((long)clientMsg.DramAddress + 4, serverHeader.Word1);
+
+            //Copy handles.
+            uint offset;
+
+            if (serverHeader.HasHandles)
+            {
+                offset = 3;
+
+                System.Device.Memory.WriteUInt32((long)clientMsg.DramAddress + 8, serverHeader.Word2);
+
+                if (serverHeader.HasPid)
+                {
+                    System.Device.Memory.WriteInt64((long)clientMsg.DramAddress + offset * 4, serverProcess.Pid);
+
+                    offset += 2;
+                }
+
+                for (int index = 0; index < serverHeader.CopyHandlesCount; index++)
+                {
+                    int newHandle = 0;
+
+                    int handle = serverProcess.CpuMemory.ReadInt32((long)serverMsg.Address + offset * 4);
+
+                    if (handle != 0)
+                    {
+                        GetCopyObjectHandle(serverThread, clientProcess, handle, out newHandle);
+                    }
+
+                    System.Device.Memory.WriteInt32((long)clientMsg.DramAddress + offset * 4, newHandle);
+
+                    offset++;
+                }
+
+                for (int index = 0; index < serverHeader.MoveHandlesCount; index++)
+                {
+                    int newHandle = 0;
+
+                    int handle = serverProcess.CpuMemory.ReadInt32((long)serverMsg.Address + offset * 4);
+
+                    if (handle != 0)
+                    {
+                        if (clientResult == KernelResult.Success)
+                        {
+                            clientResult = GetMoveObjectHandle(serverProcess, clientProcess, handle, out newHandle);
+                        }
+                        else
+                        {
+                            serverProcess.HandleTable.CloseHandle(handle);
+                        }
+                    }
+
+                    System.Device.Memory.WriteInt32((long)clientMsg.DramAddress + offset * 4, newHandle);
+
+                    offset++;
+                }
+            }
+            else
+            {
+                offset = 2;
+            }
+
+            //Copy pointer/receive list buffers.
+            uint recvListDstOffset = 0;
+
+            for (int index = 0; index < serverHeader.PointerBuffersCount; index++)
+            {
+                ulong pointerDesc = serverProcess.CpuMemory.ReadUInt64((long)serverMsg.Address + offset * 4);
+
+                PointerBufferDesc descriptor = new PointerBufferDesc(pointerDesc);
+
+                if (descriptor.BufferSize != 0)
+                {
+                    clientResult = GetReceiveListAddress(
+                        descriptor,
+                        clientMsg,
+                        clientHeader.ReceiveListType,
+                        serverHeader.MessageSizeInWords,
+                        receiveList,
+                        ref       recvListDstOffset,
+                        out ulong recvListBufferAddress);
+
+                    if (clientResult != KernelResult.Success)
+                    {
+                        CleanUpForError();
+
+                        return serverResult;
+                    }
+
+                    clientResult = clientProcess.MemoryManager.CopyDataFromCurrentProcess(
+                        recvListBufferAddress,
+                        descriptor.BufferSize,
+                        MemoryState.IsPoolAllocated,
+                        MemoryState.IsPoolAllocated,
+                        MemoryPermission.Read,
+                        MemoryAttribute.Uncached,
+                        MemoryAttribute.None,
+                        descriptor.BufferAddress);
+
+                    if (clientResult != KernelResult.Success)
+                    {
+                        CleanUpForError();
+
+                        return serverResult;
+                    }
+                }
+
+                offset += 2;
+            }
+
+            //Set send, receive and exchange buffer descriptors to zero.
+            uint totalBuffersCount =
+                serverHeader.SendBuffersCount    +
+                serverHeader.ReceiveBuffersCount +
+                serverHeader.ExchangeBuffersCount;
+
+            for (int index = 0; index < totalBuffersCount; index++)
+            {
+                long dstDescAddress = (long)clientMsg.DramAddress + offset * 4;
+
+                System.Device.Memory.WriteUInt32(dstDescAddress + 0, 0);
+                System.Device.Memory.WriteUInt32(dstDescAddress + 4, 0);
+                System.Device.Memory.WriteUInt32(dstDescAddress + 8, 0);
+
+                offset += 3;
+            }
+
+            //Copy raw data.
+            if (serverHeader.RawDataSizeInWords != 0)
+            {
+                ulong copyDst = clientMsg.Address + offset * 4;
+                ulong copySrc = serverMsg.Address + offset * 4;
+
+                ulong copySize = serverHeader.RawDataSizeInWords * 4;
+
+                if (serverMsg.IsCustom || clientMsg.IsCustom)
+                {
+                    MemoryPermission permission = clientMsg.IsCustom
+                        ? MemoryPermission.None
+                        : MemoryPermission.Read;
+
+                    clientResult = clientProcess.MemoryManager.CopyDataFromCurrentProcess(
+                        copyDst,
+                        copySize,
+                        MemoryState.IsPoolAllocated,
+                        MemoryState.IsPoolAllocated,
+                        permission,
+                        MemoryAttribute.Uncached,
+                        MemoryAttribute.None,
+                        copySrc);
+                }
+                else
+                {
+                    copyDst = clientProcess.MemoryManager.GetDramAddressFromVa(copyDst);
+                    copySrc = serverProcess.MemoryManager.GetDramAddressFromVa(copySrc);
+
+                    System.Device.Memory.Copy(copyDst, copySrc, copySize);
+                }
+            }
+
+            //Unmap buffers from server.
+            clientResult = request.BufferDescriptorTable.UnmapServerBuffers(serverProcess.MemoryManager);
+
+            if (clientResult != KernelResult.Success)
+            {
+                CleanUpForError();
+
+                return serverResult;
+            }
+
+            WakeClientThread(request, clientResult);
+
+            return serverResult;
+        }
+
+        private MessageHeader GetClientMessageHeader(Message clientMsg)
+        {
+            uint word0 = System.Device.Memory.ReadUInt32((long)clientMsg.DramAddress + 0);
+            uint word1 = System.Device.Memory.ReadUInt32((long)clientMsg.DramAddress + 4);
+            uint word2 = System.Device.Memory.ReadUInt32((long)clientMsg.DramAddress + 8);
+
+            return new MessageHeader(word0, word1, word2);
+        }
+
+        private MessageHeader GetServerMessageHeader(Message serverMsg)
+        {
+            KProcess currentProcess = System.Scheduler.GetCurrentProcess();
+
+            uint word0 = currentProcess.CpuMemory.ReadUInt32((long)serverMsg.Address + 0);
+            uint word1 = currentProcess.CpuMemory.ReadUInt32((long)serverMsg.Address + 4);
+            uint word2 = currentProcess.CpuMemory.ReadUInt32((long)serverMsg.Address + 8);
+
+            return new MessageHeader(word0, word1, word2);
+        }
+
+        private KernelResult GetCopyObjectHandle(
+            KThread  srcThread,
+            KProcess dstProcess,
+            int      srcHandle,
+            out int  dstHandle)
+        {
+            dstHandle = 0;
+
+            KProcess srcProcess = srcThread.Owner;
+
+            KAutoObject obj;
+
+            if (srcHandle == KHandleTable.SelfProcessHandle)
+            {
+                obj = srcProcess;
+            }
+            else if (srcHandle == KHandleTable.SelfThreadHandle)
+            {
+                obj = srcThread;
+            }
+            else
+            {
+                obj = srcProcess.HandleTable.GetObject<KAutoObject>(srcHandle);
+            }
+
+            if (obj != null)
+            {
+                return dstProcess.HandleTable.GenerateHandle(obj, out dstHandle);
+            }
+            else
+            {
+                return KernelResult.InvalidHandle;
+            }
+        }
+
+        private KernelResult GetMoveObjectHandle(
+            KProcess srcProcess,
+            KProcess dstProcess,
+            int      srcHandle,
+            out int  dstHandle)
+        {
+            dstHandle = 0;
+
+            KAutoObject obj = srcProcess.HandleTable.GetObject<KAutoObject>(srcHandle);
+
+            if (obj != null)
+            {
+                KernelResult result = dstProcess.HandleTable.GenerateHandle(obj, out dstHandle);
+
+                srcProcess.HandleTable.CloseHandle(srcHandle);
+
+                return result;
+            }
+            else
+            {
+                return KernelResult.InvalidHandle;
+            }
+        }
+
+        private ulong[] GetReceiveList(Message message, uint recvListType, uint recvListOffset)
+        {
+            int recvListSize = 0;
+
+            if (recvListType >= 3)
+            {
+                recvListSize = (int)recvListType - 2;
+            }
+            else if (recvListType == 2)
+            {
+                recvListSize = 1;
+            }
+
+            ulong[] receiveList = new ulong[recvListSize];
+
+            long recvListAddress = (long)message.DramAddress + recvListOffset;
+
+            for (int index = 0; index < recvListSize; index++)
+            {
+                receiveList[index] = System.Device.Memory.ReadUInt64(recvListAddress + index * 8);
+            }
+
+            return receiveList;
+        }
+
+        private KernelResult GetReceiveListAddress(
+            PointerBufferDesc descriptor,
+            Message           message,
+            uint              recvListType,
+            uint              messageSizeInWords,
+            ulong[]           receiveList,
+            ref uint          dstOffset,
+            out ulong         address)
+        {
+            ulong recvListBufferAddress = address = 0;
+
+            if (recvListType == 0)
+            {
+                return KernelResult.OutOfResource;
+            }
+            else if (recvListType == 1 || recvListType == 2)
+            {
+                ulong recvListBaseAddr;
+                ulong recvListEndAddr;
+
+                if (recvListType == 1)
+                {
+                    recvListBaseAddr = message.Address + messageSizeInWords * 4;
+                    recvListEndAddr  = message.Address + message.Size;
+                }
+                else /* if (recvListType == 2) */
+                {
+                    ulong packed = receiveList[0];
+
+                    recvListBaseAddr = packed & 0x7fffffffff;
+
+                    uint size = (uint)(packed >> 48);
+
+                    if (size == 0)
+                    {
+                        return KernelResult.OutOfResource;
+                    }
+
+                    recvListEndAddr = recvListBaseAddr + size;
+                }
+
+                recvListBufferAddress = BitUtils.AlignUp(recvListBaseAddr + dstOffset, 0x10);
+
+                ulong endAddress = recvListBufferAddress + descriptor.BufferSize;
+
+                dstOffset = (uint)endAddress - (uint)recvListBaseAddr;
+
+                if (recvListBufferAddress + descriptor.BufferSize <= recvListBufferAddress ||
+                    recvListBufferAddress + descriptor.BufferSize >  recvListEndAddr)
+                {
+                    return KernelResult.OutOfResource;
+                }
+            }
+            else /* if (recvListType > 2) */
+            {
+                if (descriptor.ReceiveIndex >= receiveList.Length)
+                {
+                    return KernelResult.OutOfResource;
+                }
+
+                ulong packed = receiveList[descriptor.ReceiveIndex];
+
+                recvListBufferAddress = packed & 0x7fffffffff;
+
+                uint size = (uint)(packed >> 48);
+
+                if (recvListBufferAddress == 0 || size == 0 || size < descriptor.BufferSize)
+                {
+                    return KernelResult.OutOfResource;
+                }
+            }
+
+            address = recvListBufferAddress;
+
+            return KernelResult.Success;
+        }
+
+        private void CloseAllHandles(Message message, MessageHeader header, KProcess process)
+        {
+            if (header.HasHandles)
+            {
+                uint totalHandeslCount = header.CopyHandlesCount + header.MoveHandlesCount;
+
+                uint offset = 3;
+
+                if (header.HasPid)
+                {
+                    process.CpuMemory.WriteInt64((long)message.Address + offset * 4, 0);
+
+                    offset += 2;
+                }
+
+                for (int index = 0; index < totalHandeslCount; index++)
+                {
+                    int handle = process.CpuMemory.ReadInt32((long)message.Address + offset * 4);
+
+                    if (handle != 0)
+                    {
+                        process.HandleTable.CloseHandle(handle);
+
+                        process.CpuMemory.WriteInt32((long)message.Address + offset * 4, 0);
+                    }
+
+                    offset++;
+                }
+            }
+        }
+
+        public override bool IsSignaled()
+        {
+            if (_parent.ClientSession.State != ChannelState.Open)
+            {
+                return true;
+            }
+
+            return _requests.Count != 0 && _activeRequest == null;
+        }
+
+        protected override void Destroy()
+        {
+            _parent.DisconnectServer();
+
+            CancelAllRequestsServerDisconnected();
+
+            _parent.DecrementReferenceCount();
+        }
+
+        private void CancelAllRequestsServerDisconnected()
+        {
+            foreach (KSessionRequest request in IterateWithRemovalOfAllRequests())
+            {
+                CancelRequest(request, KernelResult.PortRemoteClosed);
+            }
+        }
+
+        public void CancelAllRequestsClientDisconnected()
+        {
+            foreach (KSessionRequest request in IterateWithRemovalOfAllRequests())
+            {
+                if (request.ClientThread.ShallBeTerminated ||
+                    request.ClientThread.SchedFlags == ThreadSchedState.TerminationPending)
+                {
+                    continue;
+                }
+
+                //Client sessions can only be disconnected on async requests (because
+                //the client would be otherwise blocked waiting for the response), so
+                //we only need to handle the async case here.
+                if (request.AsyncEvent != null)
+                {
+                    SendResultToAsyncRequestClient(request, KernelResult.PortRemoteClosed);
+                }
+            }
+
+            WakeServerThreads(KernelResult.PortRemoteClosed);
+        }
+
+        private IEnumerable<KSessionRequest> IterateWithRemovalOfAllRequests()
+        {
+            System.CriticalSection.Enter();
+
+            if (_activeRequest != null)
+            {
+                KSessionRequest request = _activeRequest;
+
+                _activeRequest = null;
+
+                System.CriticalSection.Leave();
+
+                yield return request;
+            }
+            else
+            {
+                System.CriticalSection.Leave();
+            }
+
+            while (DequeueRequest(out KSessionRequest request))
+            {
+                yield return request;
+            }
+        }
+
+        private bool DequeueRequest(out KSessionRequest request)
+        {
+            request = null;
+
+            System.CriticalSection.Enter();
+
+            bool hasRequest = _requests.First != null;
+
+            if (hasRequest)
+            {
+                request = _requests.First.Value;
+
+                _requests.RemoveFirst();
+            }
+
+            System.CriticalSection.Leave();
+
+            return hasRequest;
+        }
+
+        private void CancelRequest(KSessionRequest request, KernelResult result)
+        {
+            KProcess clientProcess = request.ClientThread.Owner;
+            KProcess serverProcess = request.ServerProcess;
+
+            KernelResult unmapResult = KernelResult.Success;
+
+            if (serverProcess != null)
+            {
+                unmapResult = request.BufferDescriptorTable.UnmapServerBuffers(serverProcess.MemoryManager);
+            }
+
+            if (unmapResult == KernelResult.Success)
+            {
+                request.BufferDescriptorTable.RestoreClientBuffers(clientProcess.MemoryManager);
+            }
+
+            WakeClientThread(request, result);
+        }
+
+        private void WakeClientThread(KSessionRequest request, KernelResult result)
+        {
+            //Wait client thread waiting for a response for the given request.
+            if (request.AsyncEvent != null)
+            {
+                SendResultToAsyncRequestClient(request, result);
+            }
+            else
+            {
+                System.CriticalSection.Enter();
+
+                WakeAndSetResult(request.ClientThread, result);
+
+                System.CriticalSection.Leave();
+            }
+        }
+
+        private void SendResultToAsyncRequestClient(KSessionRequest request, KernelResult result)
+        {
+            KProcess clientProcess = request.ClientThread.Owner;
+
+            ulong address = clientProcess.MemoryManager.GetDramAddressFromVa(request.CustomCmdBuffAddr);
+
+            System.Device.Memory.WriteInt64((long)address + 0, 0);
+            System.Device.Memory.WriteInt32((long)address + 8, (int)result);
+
+            clientProcess.MemoryManager.UnborrowIpcBuffer(
+                request.CustomCmdBuffAddr,
+                request.CustomCmdBuffSize);
+
+            request.AsyncEvent.Signal();
+        }
+
+        private void WakeServerThreads(KernelResult result)
+        {
+            //Wake all server threads waiting for requests.
+            System.CriticalSection.Enter();
+
+            foreach (KThread thread in WaitingThreads)
+            {
+                WakeAndSetResult(thread, result);
+            }
+
+            System.CriticalSection.Leave();
+        }
+
+        private void WakeAndSetResult(KThread thread, KernelResult result)
+        {
+            if ((thread.SchedFlags & ThreadSchedState.LowMask) == ThreadSchedState.Paused)
+            {
+                thread.SignaledObj   = null;
+                thread.ObjSyncResult = result;
+
+                thread.Reschedule(ThreadSchedState.Running);
+            }
+        }
+    }
+}

+ 42 - 8
Ryujinx.HLE/HOS/Kernel/Ipc/KSession.cs

@@ -1,18 +1,40 @@
-using Ryujinx.HLE.HOS.Services;
+using Ryujinx.HLE.HOS.Kernel.Common;
+using Ryujinx.HLE.HOS.Kernel.Process;
 using System;
 
 namespace Ryujinx.HLE.HOS.Kernel.Ipc
 {
-    class KSession : IDisposable
+    class KSession : KAutoObject, IDisposable
     {
-        public IpcService Service { get; private set; }
+        public KServerSession ServerSession { get; }
+        public KClientSession ClientSession { get; }
 
-        public string ServiceName { get; private set; }
+        private bool _hasBeenInitialized;
 
-        public KSession(IpcService service, string serviceName)
+        public KSession(Horizon system) : base(system)
         {
-            Service     = service;
-            ServiceName = serviceName;
+            ServerSession = new KServerSession(system, this);
+            ClientSession = new KClientSession(system, this);
+
+            _hasBeenInitialized = true;
+        }
+
+        public void DisconnectClient()
+        {
+            if (ClientSession.State == ChannelState.Open)
+            {
+                ClientSession.State = ChannelState.ClientDisconnected;
+
+                ServerSession.CancelAllRequestsClientDisconnected();
+            }
+        }
+
+        public void DisconnectServer()
+        {
+            if (ClientSession.State == ChannelState.Open)
+            {
+                ClientSession.State = ChannelState.ServerDisconnected;
+            }
         }
 
         public void Dispose()
@@ -22,10 +44,22 @@ namespace Ryujinx.HLE.HOS.Kernel.Ipc
 
         protected virtual void Dispose(bool disposing)
         {
-            if (disposing && Service is IDisposable disposableService)
+            if (disposing && ClientSession.Service is IDisposable disposableService)
             {
                 disposableService.Dispose();
             }
         }
+
+        protected override void Destroy()
+        {
+            if (_hasBeenInitialized)
+            {
+                KProcess creatorProcess = ClientSession.CreatorProcess;
+
+                creatorProcess.ResourceLimit?.Release(LimitableResource.Session, 1);
+
+                creatorProcess.DecrementReferenceCount();
+            }
+        }
     }
 }

+ 31 - 0
Ryujinx.HLE/HOS/Kernel/Ipc/KSessionRequest.cs

@@ -0,0 +1,31 @@
+using Ryujinx.HLE.HOS.Kernel.Process;
+using Ryujinx.HLE.HOS.Kernel.Threading;
+
+namespace Ryujinx.HLE.HOS.Kernel.Ipc
+{
+    class KSessionRequest
+    {
+        public KBufferDescriptorTable BufferDescriptorTable { get; }
+
+        public KThread ClientThread { get; }
+
+        public KProcess ServerProcess { get; set; }
+
+        public KWritableEvent AsyncEvent { get; }
+
+        public ulong CustomCmdBuffAddr { get; }
+        public ulong CustomCmdBuffSize { get; }
+
+        public KSessionRequest(
+            KThread clientThread,
+            ulong   customCmdBuffAddr,
+            ulong   customCmdBuffSize)
+        {
+            ClientThread      = clientThread;
+            CustomCmdBuffAddr = customCmdBuffAddr;
+            CustomCmdBuffSize = customCmdBuffSize;
+
+            BufferDescriptorTable = new KBufferDescriptorTable();
+        }
+    }
+}

+ 93 - 13
Ryujinx.HLE/HOS/Kernel/Memory/KMemoryBlock.cs

@@ -1,29 +1,108 @@
+using System;
+
 namespace Ryujinx.HLE.HOS.Kernel.Memory
 {
     class KMemoryBlock
     {
-        public ulong BaseAddress { get; set; }
-        public ulong PagesCount  { get; set; }
+        public ulong BaseAddress { get; private set; }
+        public ulong PagesCount  { get; private set; }
 
-        public MemoryState      State      { get; set; }
-        public MemoryPermission Permission { get; set; }
-        public MemoryAttribute  Attribute  { get; set; }
+        public MemoryState      State            { get; private set; }
+        public MemoryPermission Permission       { get; private set; }
+        public MemoryAttribute  Attribute        { get; private set; }
+        public MemoryPermission SourcePermission { get; private set; }
 
-        public int IpcRefCount    { get; set; }
-        public int DeviceRefCount { get; set; }
+        public int IpcRefCount    { get; private set; }
+        public int DeviceRefCount { get; private set; }
 
         public KMemoryBlock(
             ulong            baseAddress,
             ulong            pagesCount,
             MemoryState      state,
             MemoryPermission permission,
-            MemoryAttribute  attribute)
+            MemoryAttribute  attribute,
+            int              ipcRefCount    = 0,
+            int              deviceRefCount = 0)
+        {
+            BaseAddress    = baseAddress;
+            PagesCount     = pagesCount;
+            State          = state;
+            Attribute      = attribute;
+            Permission     = permission;
+            IpcRefCount    = ipcRefCount;
+            DeviceRefCount = deviceRefCount;
+        }
+
+        public void SetState(MemoryPermission permission, MemoryState state, MemoryAttribute attribute)
+        {
+            Permission = permission;
+            State      = state;
+            Attribute &= MemoryAttribute.IpcAndDeviceMapped;
+            Attribute |= attribute;
+        }
+
+        public void SetIpcMappingPermission(MemoryPermission permission)
+        {
+            int oldIpcRefCount = IpcRefCount++;
+
+            if ((ushort)IpcRefCount == 0)
+            {
+                throw new InvalidOperationException("IPC reference count increment overflowed.");
+            }
+
+            if (oldIpcRefCount == 0)
+            {
+                SourcePermission = permission;
+
+                Permission &= ~MemoryPermission.ReadAndWrite;
+                Permission |=  MemoryPermission.ReadAndWrite & permission;
+            }
+
+            Attribute |= MemoryAttribute.IpcMapped;
+        }
+
+        public void RestoreIpcMappingPermission()
+        {
+            int oldIpcRefCount = IpcRefCount--;
+
+            if (oldIpcRefCount == 0)
+            {
+                throw new InvalidOperationException("IPC reference count decrement underflowed.");
+            }
+
+            if (oldIpcRefCount == 1)
+            {
+                Permission = SourcePermission;
+
+                SourcePermission = MemoryPermission.None;
+
+                Attribute &= ~MemoryAttribute.IpcMapped;
+            }
+        }
+
+        public KMemoryBlock SplitRightAtAddress(ulong address)
+        {
+            ulong leftAddress = BaseAddress;
+
+            ulong leftPagesCount = (address - leftAddress) / KMemoryManager.PageSize;
+
+            BaseAddress = address;
+
+            PagesCount -= leftPagesCount;
+
+            return new KMemoryBlock(
+                leftAddress,
+                leftPagesCount,
+                State,
+                Permission,
+                Attribute,
+                IpcRefCount,
+                DeviceRefCount);
+        }
+
+        public void AddPages(ulong pagesCount)
         {
-            BaseAddress = baseAddress;
-            PagesCount  = pagesCount;
-            State       = state;
-            Attribute   = attribute;
-            Permission  = permission;
+            PagesCount += pagesCount;
         }
 
         public KMemoryInfo GetInfo()
@@ -36,6 +115,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory
                 State,
                 Permission,
                 Attribute,
+                SourcePermission,
                 IpcRefCount,
                 DeviceRefCount);
         }

+ 17 - 14
Ryujinx.HLE/HOS/Kernel/Memory/KMemoryInfo.cs

@@ -2,15 +2,16 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory
 {
     class KMemoryInfo
     {
-        public ulong Address { get; private set; }
-        public ulong Size    { get; private set; }
+        public ulong Address { get; }
+        public ulong Size    { get; }
 
-        public MemoryState      State      { get; private set; }
-        public MemoryPermission Permission { get; private set; }
-        public MemoryAttribute  Attribute  { get; private set; }
+        public MemoryState      State            { get; }
+        public MemoryPermission Permission       { get; }
+        public MemoryAttribute  Attribute        { get; }
+        public MemoryPermission SourcePermission { get; }
 
-        public int IpcRefCount    { get; private set; }
-        public int DeviceRefCount { get; private set; }
+        public int IpcRefCount    { get; }
+        public int DeviceRefCount { get; }
 
         public KMemoryInfo(
             ulong            address,
@@ -18,16 +19,18 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory
             MemoryState      state,
             MemoryPermission permission,
             MemoryAttribute  attribute,
+            MemoryPermission sourcePermission,
             int              ipcRefCount,
             int              deviceRefCount)
         {
-            Address        = address;
-            Size           = size;
-            State          = state;
-            Attribute      = attribute;
-            Permission     = permission;
-            IpcRefCount    = ipcRefCount;
-            DeviceRefCount = deviceRefCount;
+            Address          = address;
+            Size             = size;
+            State            = state;
+            Permission       = permission;
+            Attribute        = attribute;
+            SourcePermission = sourcePermission;
+            IpcRefCount      = ipcRefCount;
+            DeviceRefCount   = deviceRefCount;
         }
     }
 }

Plik diff jest za duży
+ 901 - 247
Ryujinx.HLE/HOS/Kernel/Memory/KMemoryManager.cs


+ 172 - 113
Ryujinx.HLE/HOS/Kernel/Memory/KMemoryRegionManager.cs

@@ -94,6 +94,14 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory
             }
         }
 
+        public ulong AllocatePagesContiguous(ulong pagesCount, bool backwards)
+        {
+            lock (_blocks)
+            {
+                return AllocatePagesContiguousImpl(pagesCount, backwards);
+            }
+        }
+
         private KernelResult AllocatePagesImpl(ulong pagesCount, bool backwards, out KPageList pageList)
         {
             pageList = new KPageList();
@@ -122,165 +130,216 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory
                 //If so, try allocating as much requested pages as possible.
                 while (blockPagesCount <= pagesCount)
                 {
-                    ulong address = 0;
+                    ulong address = AllocatePagesForOrder(blockIndex, backwards, bestFitBlockSize);
 
-                    for (int currBlockIndex = blockIndex;
-                             currBlockIndex < _blockOrdersCount && address == 0;
-                             currBlockIndex++)
+                    //The address being zero means that no free space was found on that order,
+                    //just give up and try with the next one.
+                    if (address == 0)
                     {
-                        block = _blocks[currBlockIndex];
+                        break;
+                    }
 
-                        int index = 0;
+                    //Add new allocated page(s) to the pages list.
+                    //If an error occurs, then free all allocated pages and fail.
+                    KernelResult result = pageList.AddRange(address, blockPagesCount);
 
-                        bool zeroMask = false;
+                    if (result != KernelResult.Success)
+                    {
+                        FreePages(address, blockPagesCount);
 
-                        for (int level = 0; level < block.MaxLevel; level++)
+                        foreach (KPageNode pageNode in pageList)
                         {
-                            long mask = block.Masks[level][index];
-
-                            if (mask == 0)
-                            {
-                                zeroMask = true;
-
-                                break;
-                            }
-
-                            if (backwards)
-                            {
-                                index = (index * 64 + 63) - BitUtils.CountLeadingZeros64(mask);
-                            }
-                            else
-                            {
-                                index = index * 64 + BitUtils.CountLeadingZeros64(BitUtils.ReverseBits64(mask));
-                            }
+                            FreePages(pageNode.Address, pageNode.PagesCount);
                         }
 
-                        if (block.SizeInBlocksTruncated <= (ulong)index || zeroMask)
-                        {
-                            continue;
-                        }
+                        return result;
+                    }
 
-                        block.FreeCount--;
+                    pagesCount -= blockPagesCount;
+                }
+            }
 
-                        int tempIdx = index;
+            //Success case, all requested pages were allocated successfully.
+            if (pagesCount == 0)
+            {
+                return KernelResult.Success;
+            }
 
-                        for (int level = block.MaxLevel - 1; level >= 0; level--, tempIdx /= 64)
-                        {
-                            block.Masks[level][tempIdx / 64] &= ~(1L << (tempIdx & 63));
+            //Error case, free allocated pages and return out of memory.
+            foreach (KPageNode pageNode in pageList)
+            {
+                FreePages(pageNode.Address, pageNode.PagesCount);
+            }
 
-                            if (block.Masks[level][tempIdx / 64] != 0)
-                            {
-                                break;
-                            }
-                        }
+            pageList = null;
 
-                        address = block.StartAligned + ((ulong)index << block.Order);
-                    }
+            return KernelResult.OutOfMemory;
+        }
 
-                    for (int currBlockIndex = blockIndex;
-                             currBlockIndex < _blockOrdersCount && address == 0;
-                             currBlockIndex++)
-                    {
-                        block = _blocks[currBlockIndex];
+        private ulong AllocatePagesContiguousImpl(ulong pagesCount, bool backwards)
+        {
+            if (pagesCount == 0 || _blocks.Length < 1)
+            {
+                return 0;
+            }
 
-                        int index = 0;
+            int blockIndex = 0;
 
-                        bool zeroMask = false;
+            while ((1UL << _blocks[blockIndex].Order) / KMemoryManager.PageSize < pagesCount)
+            {
+                if (++blockIndex >= _blocks.Length)
+                {
+                    return 0;
+                }
+            }
 
-                        for (int level = 0; level < block.MaxLevel; level++)
-                        {
-                            long mask = block.Masks[level][index];
-
-                            if (mask == 0)
-                            {
-                                zeroMask = true;
-
-                                break;
-                            }
-
-                            if (backwards)
-                            {
-                                index = index * 64 + BitUtils.CountLeadingZeros64(BitUtils.ReverseBits64(mask));
-                            }
-                            else
-                            {
-                                index = (index * 64 + 63) - BitUtils.CountLeadingZeros64(mask);
-                            }
-                        }
+            ulong tightestFitBlockSize = 1UL << _blocks[blockIndex].Order;
 
-                        if (block.SizeInBlocksTruncated <= (ulong)index || zeroMask)
-                        {
-                            continue;
-                        }
+            ulong address = AllocatePagesForOrder(blockIndex, backwards, tightestFitBlockSize);
 
-                        block.FreeCount--;
+            ulong requiredSize = pagesCount * KMemoryManager.PageSize;
 
-                        int tempIdx = index;
+            if (address != 0 && tightestFitBlockSize > requiredSize)
+            {
+                FreePages(address + requiredSize, (tightestFitBlockSize - requiredSize) / KMemoryManager.PageSize);
+            }
 
-                        for (int level = block.MaxLevel - 1; level >= 0; level--, tempIdx /= 64)
-                        {
-                            block.Masks[level][tempIdx / 64] &= ~(1L << (tempIdx & 63));
+            return address;
+        }
 
-                            if (block.Masks[level][tempIdx / 64] != 0)
-                            {
-                                break;
-                            }
-                        }
+        private ulong AllocatePagesForOrder(int blockIndex, bool backwards, ulong bestFitBlockSize)
+        {
+            ulong address = 0;
 
-                        address = block.StartAligned + ((ulong)index << block.Order);
-                    }
+            KMemoryRegionBlock block = null;
 
-                    //The address being zero means that no free space was found on that order,
-                    //just give up and try with the next one.
-                    if (address == 0)
+            for (int currBlockIndex = blockIndex;
+                     currBlockIndex < _blockOrdersCount && address == 0;
+                     currBlockIndex++)
+            {
+                block = _blocks[currBlockIndex];
+
+                int index = 0;
+
+                bool zeroMask = false;
+
+                for (int level = 0; level < block.MaxLevel; level++)
+                {
+                    long mask = block.Masks[level][index];
+
+                    if (mask == 0)
                     {
+                        zeroMask = true;
+
                         break;
                     }
 
-                    //If we are using a larger order than best fit, then we should
-                    //split it into smaller blocks.
-                    ulong firstFreeBlockSize = 1UL << block.Order;
+                    if (backwards)
+                    {
+                        index = (index * 64 + 63) - BitUtils.CountLeadingZeros64(mask);
+                    }
+                    else
+                    {
+                        index = index * 64 + BitUtils.CountLeadingZeros64(BitUtils.ReverseBits64(mask));
+                    }
+                }
 
-                    if (firstFreeBlockSize > bestFitBlockSize)
+                if (block.SizeInBlocksTruncated <= (ulong)index || zeroMask)
+                {
+                    continue;
+                }
+
+                block.FreeCount--;
+
+                int tempIdx = index;
+
+                for (int level = block.MaxLevel - 1; level >= 0; level--, tempIdx /= 64)
+                {
+                    block.Masks[level][tempIdx / 64] &= ~(1L << (tempIdx & 63));
+
+                    if (block.Masks[level][tempIdx / 64] != 0)
                     {
-                        FreePages(address + bestFitBlockSize, (firstFreeBlockSize - bestFitBlockSize) / KMemoryManager.PageSize);
+                        break;
                     }
+                }
 
-                    //Add new allocated page(s) to the pages list.
-                    //If an error occurs, then free all allocated pages and fail.
-                    KernelResult result = pageList.AddRange(address, blockPagesCount);
+                address = block.StartAligned + ((ulong)index << block.Order);
+            }
 
-                    if (result != KernelResult.Success)
+            for (int currBlockIndex = blockIndex;
+                     currBlockIndex < _blockOrdersCount && address == 0;
+                     currBlockIndex++)
+            {
+                block = _blocks[currBlockIndex];
+
+                int index = 0;
+
+                bool zeroMask = false;
+
+                for (int level = 0; level < block.MaxLevel; level++)
+                {
+                    long mask = block.Masks[level][index];
+
+                    if (mask == 0)
                     {
-                        FreePages(address, blockPagesCount);
+                        zeroMask = true;
 
-                        foreach (KPageNode pageNode in pageList)
-                        {
-                            FreePages(pageNode.Address, pageNode.PagesCount);
-                        }
+                        break;
+                    }
 
-                        return result;
+                    if (backwards)
+                    {
+                        index = index * 64 + BitUtils.CountLeadingZeros64(BitUtils.ReverseBits64(mask));
                     }
+                    else
+                    {
+                        index = (index * 64 + 63) - BitUtils.CountLeadingZeros64(mask);
+                    }
+                }
 
-                    pagesCount -= blockPagesCount;
+                if (block.SizeInBlocksTruncated <= (ulong)index || zeroMask)
+                {
+                    continue;
                 }
-            }
 
-            //Success case, all requested pages were allocated successfully.
-            if (pagesCount == 0)
-            {
-                return KernelResult.Success;
+                block.FreeCount--;
+
+                int tempIdx = index;
+
+                for (int level = block.MaxLevel - 1; level >= 0; level--, tempIdx /= 64)
+                {
+                    block.Masks[level][tempIdx / 64] &= ~(1L << (tempIdx & 63));
+
+                    if (block.Masks[level][tempIdx / 64] != 0)
+                    {
+                        break;
+                    }
+                }
+
+                address = block.StartAligned + ((ulong)index << block.Order);
             }
 
-            //Error case, free allocated pages and return out of memory.
-            foreach (KPageNode pageNode in pageList)
+            if (address != 0)
             {
-                FreePages(pageNode.Address, pageNode.PagesCount);
+                //If we are using a larger order than best fit, then we should
+                //split it into smaller blocks.
+                ulong firstFreeBlockSize = 1UL << block.Order;
+
+                if (firstFreeBlockSize > bestFitBlockSize)
+                {
+                    FreePages(address + bestFitBlockSize, (firstFreeBlockSize - bestFitBlockSize) / KMemoryManager.PageSize);
+                }
             }
 
-            pageList = null;
+            return address;
+        }
 
-            return KernelResult.OutOfMemory;
+        public void FreePage(ulong address)
+        {
+            lock (_blocks)
+            {
+                FreePages(address, 1);
+            }
         }
 
         public void FreePages(KPageList pageList)

+ 3 - 2
Ryujinx.HLE/HOS/Kernel/Memory/KSharedMemory.cs

@@ -4,7 +4,7 @@ using Ryujinx.HLE.HOS.Kernel.Process;
 
 namespace Ryujinx.HLE.HOS.Kernel.Memory
 {
-    class KSharedMemory
+    class KSharedMemory : KAutoObject
     {
         private KPageList _pageList;
 
@@ -14,10 +14,11 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory
         private MemoryPermission _userPermission;
 
         public KSharedMemory(
+            Horizon          system,
             KPageList        pageList,
             long             ownerPid,
             MemoryPermission ownerPermission,
-            MemoryPermission userPermission)
+            MemoryPermission userPermission) : base(system)
         {
             _pageList        = pageList;
             _ownerPid        = ownerPid;

+ 4 - 2
Ryujinx.HLE/HOS/Kernel/Memory/KTransferMemory.cs

@@ -1,11 +1,13 @@
+using Ryujinx.HLE.HOS.Kernel.Common;
+
 namespace Ryujinx.HLE.HOS.Kernel.Memory
 {
-    class KTransferMemory
+    class KTransferMemory : KAutoObject
     {
         public ulong Address { get; private set; }
         public ulong Size    { get; private set; }
 
-        public KTransferMemory(ulong address, ulong size)
+        public KTransferMemory(Horizon system, ulong address, ulong size) : base(system)
         {
             Address = address;
             Size    = size;

+ 4 - 2
Ryujinx.HLE/HOS/Kernel/Process/KHandleEntry.cs

@@ -1,3 +1,5 @@
+using Ryujinx.HLE.HOS.Kernel.Common;
+
 namespace Ryujinx.HLE.HOS.Kernel.Process
 {
     class KHandleEntry
@@ -6,8 +8,8 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
 
         public int Index { get; private set; }
 
-        public ushort HandleId { get; set; }
-        public object Obj      { get; set; }
+        public ushort      HandleId { get; set; }
+        public KAutoObject Obj      { get; set; }
 
         public KHandleEntry(int index)
         {

+ 82 - 7
Ryujinx.HLE/HOS/Kernel/Process/KHandleTable.cs

@@ -6,8 +6,8 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
 {
     class KHandleTable
     {
-        private const int SelfThreadHandle  = (0x1ffff << 15) | 0;
-        private const int SelfProcessHandle = (0x1ffff << 15) | 1;
+        public const int SelfThreadHandle  = (0x1ffff << 15) | 0;
+        public const int SelfProcessHandle = (0x1ffff << 15) | 1;
 
         private Horizon _system;
 
@@ -65,7 +65,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
             return KernelResult.Success;
         }
 
-        public KernelResult GenerateHandle(object obj, out int handle)
+        public KernelResult GenerateHandle(KAutoObject obj, out int handle)
         {
             handle = 0;
 
@@ -85,7 +85,9 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
 
                 _activeSlotsCount++;
 
-                handle = (int)((_idCounter << 15) & 0xffff8000) | entry.Index;
+                handle = (_idCounter << 15) | entry.Index;
+
+                obj.IncrementReferenceCount();
 
                 if ((short)(_idCounter + 1) >= 0)
                 {
@@ -100,6 +102,72 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
             return KernelResult.Success;
         }
 
+        public KernelResult ReserveHandle(out int handle)
+        {
+            handle = 0;
+
+            lock (_table)
+            {
+                if (_activeSlotsCount >= _size)
+                {
+                    return KernelResult.HandleTableFull;
+                }
+
+                KHandleEntry entry = _nextFreeEntry;
+
+                _nextFreeEntry = entry.Next;
+
+                _activeSlotsCount++;
+
+                handle = (_idCounter << 15) | entry.Index;
+
+                if ((short)(_idCounter + 1) >= 0)
+                {
+                    _idCounter++;
+                }
+                else
+                {
+                    _idCounter = 1;
+                }
+            }
+
+            return KernelResult.Success;
+        }
+
+        public void CancelHandleReservation(int handle)
+        {
+            int index    = (handle >> 0) & 0x7fff;
+            int handleId = (handle >> 15);
+
+            lock (_table)
+            {
+                KHandleEntry entry = _table[index];
+
+                entry.Obj  = null;
+                entry.Next = _nextFreeEntry;
+
+                _nextFreeEntry = entry;
+
+                _activeSlotsCount--;
+            }
+        }
+
+        public void SetReservedHandleObj(int handle, KAutoObject obj)
+        {
+            int index    = (handle >> 0) & 0x7fff;
+            int handleId = (handle >> 15);
+
+            lock (_table)
+            {
+                KHandleEntry entry = _table[index];
+
+                entry.Obj      = obj;
+                entry.HandleId = (ushort)(handle >> 15);
+
+                obj.IncrementReferenceCount();
+            }
+        }
+
         public bool CloseHandle(int handle)
         {
             if ((handle >> 30) != 0 ||
@@ -112,6 +180,8 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
             int index    = (handle >> 0) & 0x7fff;
             int handleId = (handle >> 15);
 
+            KAutoObject obj = null;
+
             bool result = false;
 
             lock (_table)
@@ -120,7 +190,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
                 {
                     KHandleEntry entry = _table[index];
 
-                    if (entry.Obj != null && entry.HandleId == handleId)
+                    if ((obj = entry.Obj) != null && entry.HandleId == handleId)
                     {
                         entry.Obj  = null;
                         entry.Next = _nextFreeEntry;
@@ -134,17 +204,22 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
                 }
             }
 
+            if (result)
+            {
+                obj.DecrementReferenceCount();
+            }
+
             return result;
         }
 
-        public T GetObject<T>(int handle)
+        public T GetObject<T>(int handle) where T : KAutoObject
         {
             int index    = (handle >> 0) & 0x7fff;
             int handleId = (handle >> 15);
 
             lock (_table)
             {
-                if ((handle >> 30) == 0 && handleId != 0)
+                if ((handle >> 30) == 0 && handleId != 0 && index < _size)
                 {
                     KHandleEntry entry = _table[index];
 

+ 14 - 6
Ryujinx.HLE/HOS/Kernel/Process/KProcess.cs

@@ -30,7 +30,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
         private SortedDictionary<ulong, KTlsPageInfo> _fullTlsPages;
         private SortedDictionary<ulong, KTlsPageInfo> _freeTlsPages;
 
-        public int DefaultCpuCore { get; private set; }
+        public int DefaultCpuCore { get; set; }
 
         public bool Debug { get; private set; }
 
@@ -557,14 +557,12 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
 
         private KernelResult FreeTlsPage(KTlsPageInfo pageInfo)
         {
-            KernelResult result = MemoryManager.ConvertVaToPa(pageInfo.PageAddr, out ulong tlsPagePa);
-
-            if (result != KernelResult.Success)
+            if (!MemoryManager.ConvertVaToPa(pageInfo.PageAddr, out ulong tlsPagePa))
             {
                 throw new InvalidOperationException("Unexpected failure translating virtual address to physical.");
             }
 
-            result = MemoryManager.UnmapForKernel(pageInfo.PageAddr, 1, MemoryState.ThreadLocal);
+            KernelResult result = MemoryManager.UnmapForKernel(pageInfo.PageAddr, 1, MemoryState.ThreadLocal);
 
             if (result == KernelResult.Success)
             {
@@ -636,9 +634,10 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
 
                 void CleanUpForError()
                 {
-                    mainThread?.Terminate();
                     HandleTable.Destroy();
 
+                    mainThread?.DecrementReferenceCount();
+
                     if (_mainThreadStackSize != 0)
                     {
                         ulong stackBottom = stackTop - _mainThreadStackSize;
@@ -646,6 +645,8 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
                         ulong stackPagesCount = _mainThreadStackSize / KMemoryManager.PageSize;
 
                         MemoryManager.UnmapForKernel(stackBottom, stackPagesCount, MemoryState.Stack);
+
+                        _mainThreadStackSize = 0;
                     }
 
                     memoryResourceLimit?.Release(LimitableResource.Memory, stackSizeRounded);
@@ -756,6 +757,13 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
 
                 mainThread.Reschedule(ThreadSchedState.Running);
 
+                if (result == KernelResult.Success)
+                {
+                    mainThread.IncrementReferenceCount();
+                }
+
+                mainThread.DecrementReferenceCount();
+
                 return result;
             }
         }

+ 5 - 0
Ryujinx.HLE/HOS/Kernel/Process/KProcessCapabilities.cs

@@ -306,6 +306,11 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
         {
             int range = max - min + 1;
 
+            if (range == 64)
+            {
+                return -1L;
+            }
+
             long mask = (1L << range) - 1;
 
             return mask << min;

+ 0 - 23
Ryujinx.HLE/HOS/Kernel/SupervisorCall/SvcHandler.cs

@@ -1,10 +1,7 @@
 using ChocolArm64.Events;
 using ChocolArm64.Memory;
 using ChocolArm64.State;
-using Ryujinx.HLE.HOS.Ipc;
-using Ryujinx.HLE.HOS.Kernel.Ipc;
 using Ryujinx.HLE.HOS.Kernel.Process;
-using Ryujinx.HLE.HOS.Kernel.Threading;
 using System;
 
 namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
@@ -16,26 +13,6 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
         private Horizon       _system;
         private MemoryManager _memory;
 
-        private struct HleIpcMessage
-        {
-            public KThread    Thread     { get; private set; }
-            public KSession   Session    { get; private set; }
-            public IpcMessage Message    { get; private set; }
-            public long       MessagePtr { get; private set; }
-
-            public HleIpcMessage(
-                KThread    thread,
-                KSession   session,
-                IpcMessage message,
-                long       messagePtr)
-            {
-                Thread     = thread;
-                Session    = session;
-                Message    = message;
-                MessagePtr = messagePtr;
-            }
-        }
-
         public SvcHandler(Switch device, KProcess process)
         {
             _device  = device;

+ 532 - 0
Ryujinx.HLE/HOS/Kernel/SupervisorCall/SvcIpc.cs

@@ -0,0 +1,532 @@
+using Ryujinx.Common.Logging;
+using Ryujinx.HLE.HOS.Ipc;
+using Ryujinx.HLE.HOS.Kernel.Common;
+using Ryujinx.HLE.HOS.Kernel.Ipc;
+using Ryujinx.HLE.HOS.Kernel.Process;
+using Ryujinx.HLE.HOS.Kernel.Threading;
+using System.Threading;
+
+namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
+{
+    partial class SvcHandler
+    {
+        private struct HleIpcMessage
+        {
+            public KThread        Thread     { get; private set; }
+            public KClientSession Session    { get; private set; }
+            public IpcMessage     Message    { get; private set; }
+            public long           MessagePtr { get; private set; }
+
+            public HleIpcMessage(
+                KThread        thread,
+                KClientSession session,
+                IpcMessage     message,
+                long           messagePtr)
+            {
+                Thread     = thread;
+                Session    = session;
+                Message    = message;
+                MessagePtr = messagePtr;
+            }
+        }
+
+        public KernelResult ConnectToNamedPort64(ulong namePtr, out int handle)
+        {
+            return ConnectToNamedPort(namePtr, out handle);
+        }
+
+        private KernelResult ConnectToNamedPort(ulong namePtr, out int handle)
+        {
+            handle = 0;
+
+            if (!KernelTransfer.UserToKernelString(_system, namePtr, 12, out string name))
+            {
+                return KernelResult.UserCopyFailed;
+            }
+
+            if (name.Length > 11)
+            {
+                return KernelResult.MaximumExceeded;
+            }
+
+            KAutoObject autoObj = KAutoObject.FindNamedObject(_system, name);
+
+            if (!(autoObj is KClientPort clientPort))
+            {
+                return KernelResult.NotFound;
+            }
+
+            KProcess currentProcess = _system.Scheduler.GetCurrentProcess();
+
+            KernelResult result = currentProcess.HandleTable.ReserveHandle(out handle);
+
+            if (result != KernelResult.Success)
+            {
+                return result;
+            }
+
+            result = clientPort.Connect(out KClientSession clientSession);
+
+            if (result != KernelResult.Success)
+            {
+                currentProcess.HandleTable.CancelHandleReservation(handle);
+
+                return result;
+            }
+
+            currentProcess.HandleTable.SetReservedHandleObj(handle, clientSession);
+
+            clientSession.DecrementReferenceCount();
+
+            return result;
+        }
+
+        public KernelResult SendSyncRequest64(int handle)
+        {
+            return SendSyncRequest((ulong)_system.Scheduler.GetCurrentThread().Context.ThreadState.Tpidr, 0x100, handle);
+        }
+
+        public KernelResult SendSyncRequestWithUserBuffer64(ulong messagePtr, ulong size, int handle)
+        {
+            return SendSyncRequest(messagePtr, size, handle);
+        }
+
+        private KernelResult SendSyncRequest(ulong messagePtr, ulong size, int handle)
+        {
+            byte[] messageData = _memory.ReadBytes((long)messagePtr, (long)size);
+
+            KClientSession clientSession = _process.HandleTable.GetObject<KClientSession>(handle);
+
+            if (clientSession == null || clientSession.Service == null)
+            {
+                return SendSyncRequest_(handle);
+            }
+
+            if (clientSession != null)
+            {
+                _system.CriticalSection.Enter();
+
+                KThread currentThread = _system.Scheduler.GetCurrentThread();
+
+                currentThread.SignaledObj   = null;
+                currentThread.ObjSyncResult = KernelResult.Success;
+
+                currentThread.Reschedule(ThreadSchedState.Paused);
+
+                IpcMessage message = new IpcMessage(messageData, (long)messagePtr);
+
+                ThreadPool.QueueUserWorkItem(ProcessIpcRequest, new HleIpcMessage(
+                    currentThread,
+                    clientSession,
+                    message,
+                    (long)messagePtr));
+
+                _system.ThreadCounter.AddCount();
+
+                _system.CriticalSection.Leave();
+
+                return currentThread.ObjSyncResult;
+            }
+            else
+            {
+                Logger.PrintWarning(LogClass.KernelSvc, $"Invalid session handle 0x{handle:x8}!");
+
+                return KernelResult.InvalidHandle;
+            }
+        }
+
+        private void ProcessIpcRequest(object state)
+        {
+            HleIpcMessage ipcMessage = (HleIpcMessage)state;
+
+            ipcMessage.Thread.ObjSyncResult = IpcHandler.IpcCall(
+                _device,
+                _process,
+                _memory,
+                ipcMessage.Session,
+                ipcMessage.Message,
+                ipcMessage.MessagePtr);
+
+            _system.ThreadCounter.Signal();
+
+            ipcMessage.Thread.Reschedule(ThreadSchedState.Running);
+        }
+
+        private KernelResult SendSyncRequest_(int handle)
+        {
+            KProcess currentProcess = _system.Scheduler.GetCurrentProcess();
+
+            KClientSession session = currentProcess.HandleTable.GetObject<KClientSession>(handle);
+
+            if (session == null)
+            {
+                return KernelResult.InvalidHandle;
+            }
+
+            return session.SendSyncRequest();
+        }
+
+        public KernelResult CreateSession64(
+            bool    isLight,
+            ulong   namePtr,
+            out int serverSessionHandle,
+            out int clientSessionHandle)
+        {
+            return CreateSession(isLight, namePtr, out serverSessionHandle, out clientSessionHandle);
+        }
+
+        private KernelResult CreateSession(
+            bool    isLight,
+            ulong   namePtr,
+            out int serverSessionHandle,
+            out int clientSessionHandle)
+        {
+            serverSessionHandle = 0;
+            clientSessionHandle = 0;
+
+            KProcess currentProcess = _system.Scheduler.GetCurrentProcess();
+
+            KResourceLimit resourceLimit = currentProcess.ResourceLimit;
+
+            KernelResult result = KernelResult.Success;
+
+            if (resourceLimit != null && !resourceLimit.Reserve(LimitableResource.Session, 1))
+            {
+                return KernelResult.ResLimitExceeded;
+            }
+
+            if (isLight)
+            {
+                KLightSession session = new KLightSession(_system);
+
+                result = currentProcess.HandleTable.GenerateHandle(session.ServerSession, out serverSessionHandle);
+
+                if (result == KernelResult.Success)
+                {
+                    result = currentProcess.HandleTable.GenerateHandle(session.ClientSession, out clientSessionHandle);
+
+                    if (result != KernelResult.Success)
+                    {
+                        currentProcess.HandleTable.CloseHandle(serverSessionHandle);
+
+                        serverSessionHandle = 0;
+                    }
+                }
+
+                session.ServerSession.DecrementReferenceCount();
+                session.ClientSession.DecrementReferenceCount();
+            }
+            else
+            {
+                KSession session = new KSession(_system);
+
+                result = currentProcess.HandleTable.GenerateHandle(session.ServerSession, out serverSessionHandle);
+
+                if (result == KernelResult.Success)
+                {
+                    result = currentProcess.HandleTable.GenerateHandle(session.ClientSession, out clientSessionHandle);
+
+                    if (result != KernelResult.Success)
+                    {
+                        currentProcess.HandleTable.CloseHandle(serverSessionHandle);
+
+                        serverSessionHandle = 0;
+                    }
+                }
+
+                session.ServerSession.DecrementReferenceCount();
+                session.ClientSession.DecrementReferenceCount();
+            }
+
+            return result;
+        }
+
+        public KernelResult AcceptSession64(int portHandle, out int sessionHandle)
+        {
+            return AcceptSession(portHandle, out sessionHandle);
+        }
+
+        private KernelResult AcceptSession(int portHandle, out int sessionHandle)
+        {
+            sessionHandle = 0;
+
+            KProcess currentProcess = _system.Scheduler.GetCurrentProcess();
+
+            KServerPort serverPort = currentProcess.HandleTable.GetObject<KServerPort>(portHandle);
+
+            if (serverPort == null)
+            {
+                return KernelResult.InvalidHandle;
+            }
+
+            KernelResult result = currentProcess.HandleTable.ReserveHandle(out int handle);
+
+            if (result != KernelResult.Success)
+            {
+                return result;
+            }
+
+            KAutoObject session;
+
+            if (serverPort.IsLight)
+            {
+                session = serverPort.AcceptIncomingLightConnection();
+            }
+            else
+            {
+                session = serverPort.AcceptIncomingConnection();
+            }
+
+            if (session != null)
+            {
+                currentProcess.HandleTable.SetReservedHandleObj(handle, session);
+
+                session.DecrementReferenceCount();
+
+                sessionHandle = handle;
+
+                result = KernelResult.Success;
+            }
+            else
+            {
+                currentProcess.HandleTable.CancelHandleReservation(handle);
+
+                result = KernelResult.NotFound;
+            }
+
+            return result;
+        }
+
+        public KernelResult ReplyAndReceive64(
+            ulong   handlesPtr,
+            int     handlesCount,
+            int     replyTargetHandle,
+            long    timeout,
+            out int handleIndex)
+        {
+            handleIndex = 0;
+
+            if ((uint)handlesCount > 0x40)
+            {
+                return KernelResult.MaximumExceeded;
+            }
+
+            KProcess currentProcess = _system.Scheduler.GetCurrentProcess();
+
+            ulong copySize = (ulong)((long)handlesCount * 4);
+
+            if (!currentProcess.MemoryManager.InsideAddrSpace(handlesPtr, copySize))
+            {
+                return KernelResult.UserCopyFailed;
+            }
+
+            if (handlesPtr + copySize < handlesPtr)
+            {
+                return KernelResult.UserCopyFailed;
+            }
+
+            int[] handles = new int[handlesCount];
+
+            if (!KernelTransfer.UserToKernelInt32Array(_system, handlesPtr, handles))
+            {
+                return KernelResult.UserCopyFailed;
+            }
+
+            KSynchronizationObject[] syncObjs = new KSynchronizationObject[handlesCount];
+
+            for (int index = 0; index < handlesCount; index++)
+            {
+                KSynchronizationObject obj = currentProcess.HandleTable.GetObject<KSynchronizationObject>(handles[index]);
+
+                if (obj == null)
+                {
+                    return KernelResult.InvalidHandle;
+                }
+
+                syncObjs[index] = obj;
+            }
+
+            KernelResult result;
+
+            if (replyTargetHandle != 0)
+            {
+                KServerSession replyTarget = currentProcess.HandleTable.GetObject<KServerSession>(replyTargetHandle);
+
+                if (replyTarget == null)
+                {
+                    return KernelResult.InvalidHandle;
+                }
+
+                result = replyTarget.Reply();
+
+                if (result != KernelResult.Success)
+                {
+                    return result;
+                }
+            }
+
+            while ((result = _system.Synchronization.WaitFor(syncObjs, timeout, out handleIndex)) == KernelResult.Success)
+            {
+                KServerSession session = currentProcess.HandleTable.GetObject<KServerSession>(handles[handleIndex]);
+
+                if (session == null)
+                {
+                    break;
+                }
+
+                if ((result = session.Receive()) != KernelResult.NotFound)
+                {
+                    break;
+                }
+            }
+
+            return result;
+        }
+
+        public KernelResult CreatePort64(
+            int     maxSessions,
+            bool    isLight,
+            ulong   namePtr,
+            out int serverPortHandle,
+            out int clientPortHandle)
+        {
+            return CreatePort(maxSessions, isLight, namePtr, out serverPortHandle, out clientPortHandle);
+        }
+
+        private KernelResult CreatePort(
+            int     maxSessions,
+            bool    isLight,
+            ulong   namePtr,
+            out int serverPortHandle,
+            out int clientPortHandle)
+        {
+            serverPortHandle = clientPortHandle = 0;
+
+            if (maxSessions < 1)
+            {
+                return KernelResult.MaximumExceeded;
+            }
+
+            KPort port = new KPort(_system, maxSessions, isLight, (long)namePtr);
+
+            KProcess currentProcess = _system.Scheduler.GetCurrentProcess();
+
+            KernelResult result = currentProcess.HandleTable.GenerateHandle(port.ClientPort, out clientPortHandle);
+
+            if (result != KernelResult.Success)
+            {
+                return result;
+            }
+
+            result = currentProcess.HandleTable.GenerateHandle(port.ServerPort, out serverPortHandle);
+
+            if (result != KernelResult.Success)
+            {
+                currentProcess.HandleTable.CloseHandle(clientPortHandle);
+            }
+
+            return result;
+        }
+
+        public KernelResult ManageNamedPort64(ulong namePtr, int maxSessions, out int handle)
+        {
+            return ManageNamedPort(namePtr, maxSessions, out handle);
+        }
+
+        private KernelResult ManageNamedPort(ulong namePtr, int maxSessions, out int handle)
+        {
+            handle = 0;
+
+            if (!KernelTransfer.UserToKernelString(_system, namePtr, 12, out string name))
+            {
+                return KernelResult.UserCopyFailed;
+            }
+
+            if (maxSessions < 0 || name.Length > 11)
+            {
+                return KernelResult.MaximumExceeded;
+            }
+
+            if (maxSessions == 0)
+            {
+                return KClientPort.RemoveName(_system, name);
+            }
+
+            KPort port = new KPort(_system, maxSessions, false, 0);
+
+            KProcess currentProcess = _system.Scheduler.GetCurrentProcess();
+
+            KernelResult result = currentProcess.HandleTable.GenerateHandle(port.ServerPort, out handle);
+
+            if (result != KernelResult.Success)
+            {
+                return result;
+            }
+
+            result = port.ClientPort.SetName(name);
+
+            if (result != KernelResult.Success)
+            {
+                currentProcess.HandleTable.CloseHandle(handle);
+            }
+
+            return result;
+        }
+
+        public KernelResult ConnectToPort64(int clientPortHandle, out int clientSessionHandle)
+        {
+            return ConnectToPort(clientPortHandle, out clientSessionHandle);
+        }
+
+        private KernelResult ConnectToPort(int clientPortHandle, out int clientSessionHandle)
+        {
+            clientSessionHandle = 0;
+
+            KProcess currentProcess = _system.Scheduler.GetCurrentProcess();
+
+            KClientPort clientPort = currentProcess.HandleTable.GetObject<KClientPort>(clientPortHandle);
+
+            if (clientPort == null)
+            {
+                return KernelResult.InvalidHandle;
+            }
+
+            KernelResult result = currentProcess.HandleTable.ReserveHandle(out int handle);
+
+            if (result != KernelResult.Success)
+            {
+                return result;
+            }
+
+            KAutoObject session;
+
+            if (clientPort.IsLight)
+            {
+                result = clientPort.ConnectLight(out KLightClientSession clientSession);
+
+                session = clientSession;
+            }
+            else
+            {
+                result = clientPort.Connect(out KClientSession clientSession);
+
+                session = clientSession;
+            }
+
+            if (result != KernelResult.Success)
+            {
+                currentProcess.HandleTable.CancelHandleReservation(handle);
+
+                return result;
+            }
+
+            currentProcess.HandleTable.SetReservedHandleObj(handle, session);
+
+            session.DecrementReferenceCount();
+
+            clientSessionHandle = handle;
+
+            return result;
+        }
+    }
+}

+ 2 - 2
Ryujinx.HLE/HOS/Kernel/SupervisorCall/SvcMemory.cs

@@ -305,7 +305,7 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
                 return result;
             }
 
-            KTransferMemory transferMemory = new KTransferMemory(address, size);
+            KTransferMemory transferMemory = new KTransferMemory(_system, address, size);
 
             return _process.HandleTable.GenerateHandle(transferMemory, out handle);
         }
@@ -350,7 +350,7 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
 
         public KernelResult UnmapPhysicalMemory64(ulong address, ulong size)
         {
-            return MapPhysicalMemory(address, size);
+            return UnmapPhysicalMemory(address, size);
         }
 
         private KernelResult UnmapPhysicalMemory(ulong address, ulong size)

+ 1 - 180
Ryujinx.HLE/HOS/Kernel/SupervisorCall/SvcSystem.cs

@@ -2,14 +2,11 @@ using ChocolArm64.Memory;
 using Ryujinx.Common;
 using Ryujinx.Common.Logging;
 using Ryujinx.HLE.Exceptions;
-using Ryujinx.HLE.HOS.Ipc;
 using Ryujinx.HLE.HOS.Kernel.Common;
 using Ryujinx.HLE.HOS.Kernel.Ipc;
 using Ryujinx.HLE.HOS.Kernel.Memory;
 using Ryujinx.HLE.HOS.Kernel.Process;
 using Ryujinx.HLE.HOS.Kernel.Threading;
-using Ryujinx.HLE.HOS.Services;
-using System.Threading;
 
 namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
 {
@@ -82,7 +79,7 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
 
         private KernelResult CloseHandle(int handle)
         {
-            object obj = _process.HandleTable.GetObject<object>(handle);
+            KAutoObject obj = _process.HandleTable.GetObject<KAutoObject>(handle);
 
             _process.HandleTable.CloseHandle(handle);
 
@@ -144,88 +141,6 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
             return _system.Scheduler.GetCurrentThread().Context.ThreadState.CntpctEl0;
         }
 
-        public KernelResult ConnectToNamedPort64(ulong namePtr, out int handle)
-        {
-            return ConnectToNamedPort(namePtr, out handle);
-        }
-
-        private KernelResult ConnectToNamedPort(ulong namePtr, out int handle)
-        {
-            string name = MemoryHelper.ReadAsciiString(_memory, (long)namePtr, 8);
-
-            //TODO: Validate that app has perms to access the service, and that the service
-            //actually exists, return error codes otherwise.
-            KSession session = new KSession(ServiceFactory.MakeService(_system, name), name);
-
-            return _process.HandleTable.GenerateHandle(session, out handle);
-        }
-
-        public KernelResult SendSyncRequest64(int handle)
-        {
-            return SendSyncRequest((ulong)_system.Scheduler.GetCurrentThread().Context.ThreadState.Tpidr, 0x100, handle);
-        }
-
-        public KernelResult SendSyncRequestWithUserBuffer64(ulong messagePtr, ulong size, int handle)
-        {
-            return SendSyncRequest(messagePtr, size, handle);
-        }
-
-        private KernelResult SendSyncRequest(ulong messagePtr, ulong size, int handle)
-        {
-            byte[] messageData = _memory.ReadBytes((long)messagePtr, (long)size);
-
-            KSession session = _process.HandleTable.GetObject<KSession>(handle);
-
-            if (session != null)
-            {
-                _system.CriticalSection.Enter();
-
-                KThread currentThread = _system.Scheduler.GetCurrentThread();
-
-                currentThread.SignaledObj   = null;
-                currentThread.ObjSyncResult = KernelResult.Success;
-
-                currentThread.Reschedule(ThreadSchedState.Paused);
-
-                IpcMessage message = new IpcMessage(messageData, (long)messagePtr);
-
-                ThreadPool.QueueUserWorkItem(ProcessIpcRequest, new HleIpcMessage(
-                    currentThread,
-                    session,
-                    message,
-                    (long)messagePtr));
-
-                _system.ThreadCounter.AddCount();
-
-                _system.CriticalSection.Leave();
-
-                return currentThread.ObjSyncResult;
-            }
-            else
-            {
-                Logger.PrintWarning(LogClass.KernelSvc, $"Invalid session handle 0x{handle:x8}!");
-
-                return KernelResult.InvalidHandle;
-            }
-        }
-
-        private void ProcessIpcRequest(object state)
-        {
-            HleIpcMessage ipcMessage = (HleIpcMessage)state;
-
-            ipcMessage.Thread.ObjSyncResult = IpcHandler.IpcCall(
-                _device,
-                _process,
-                _memory,
-                ipcMessage.Session,
-                ipcMessage.Message,
-                ipcMessage.MessagePtr);
-
-            _system.ThreadCounter.Signal();
-
-            ipcMessage.Thread.Reschedule(ThreadSchedState.Running);
-        }
-
         public KernelResult GetProcessId64(int handle, out long pid)
         {
             return GetProcessId(handle, out pid);
@@ -664,99 +579,5 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
 
             return KernelResult.Success;
         }
-
-        public KernelResult CreatePort64(
-            int     maxSessions,
-            bool    isLight,
-            ulong   namePtr,
-            out int serverPortHandle,
-            out int clientPortHandle)
-        {
-            return CreatePort(maxSessions, isLight, namePtr, out serverPortHandle, out clientPortHandle);
-        }
-
-        private KernelResult CreatePort(
-            int     maxSessions,
-            bool    isLight,
-            ulong   namePtr,
-            out int serverPortHandle,
-            out int clientPortHandle)
-        {
-            serverPortHandle = clientPortHandle = 0;
-
-            if (maxSessions < 1)
-            {
-                return KernelResult.MaximumExceeded;
-            }
-
-            KPort port = new KPort(_system);
-
-            port.Initialize(maxSessions, isLight, (long)namePtr);
-
-            KProcess currentProcess = _system.Scheduler.GetCurrentProcess();
-
-            KernelResult result = currentProcess.HandleTable.GenerateHandle(port.ClientPort, out clientPortHandle);
-
-            if (result != KernelResult.Success)
-            {
-                return result;
-            }
-
-            result = currentProcess.HandleTable.GenerateHandle(port.ServerPort, out serverPortHandle);
-
-            if (result != KernelResult.Success)
-            {
-                currentProcess.HandleTable.CloseHandle(clientPortHandle);
-            }
-
-            return result;
-        }
-
-        public KernelResult ManageNamedPort64(ulong namePtr, int maxSessions, out int handle)
-        {
-            return ManageNamedPort(namePtr, maxSessions, out handle);
-        }
-
-        private KernelResult ManageNamedPort(ulong namePtr, int maxSessions, out int handle)
-        {
-            handle = 0;
-
-            if (!KernelTransfer.UserToKernelString(_system, namePtr, 12, out string name))
-            {
-                return KernelResult.UserCopyFailed;
-            }
-
-            if (maxSessions < 0 || name.Length > 11)
-            {
-                return KernelResult.MaximumExceeded;
-            }
-
-            if (maxSessions == 0)
-            {
-                return KClientPort.RemoveName(_system, name);
-            }
-
-            KPort port = new KPort(_system);
-
-            KProcess currentProcess = _system.Scheduler.GetCurrentProcess();
-
-            KernelResult result = currentProcess.HandleTable.GenerateHandle(port.ServerPort, out handle);
-
-            if (result != KernelResult.Success)
-            {
-                return result;
-            }
-
-            port.Initialize(maxSessions, false, 0);
-
-            result = port.SetName(name);
-
-            if (result != KernelResult.Success)
-            {
-                currentProcess.HandleTable.CloseHandle(handle);
-            }
-
-            return result;
-        }
     }
 }

+ 5 - 1
Ryujinx.HLE/HOS/Kernel/SupervisorCall/SvcTable.cs

@@ -63,11 +63,15 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
                 { 0x33, nameof(SvcHandler.GetThreadContext364)             },
                 { 0x34, nameof(SvcHandler.WaitForAddress64)                },
                 { 0x35, nameof(SvcHandler.SignalToAddress64)               },
+                { 0x40, nameof(SvcHandler.CreateSession64)                 },
+                { 0x41, nameof(SvcHandler.AcceptSession64)                 },
+                { 0x43, nameof(SvcHandler.ReplyAndReceive64)               },
                 { 0x45, nameof(SvcHandler.CreateEvent64)                   },
                 { 0x65, nameof(SvcHandler.GetProcessList64)                },
                 { 0x6f, nameof(SvcHandler.GetSystemInfo64)                 },
                 { 0x70, nameof(SvcHandler.CreatePort64)                    },
-                { 0x71, nameof(SvcHandler.ManageNamedPort64)               }
+                { 0x71, nameof(SvcHandler.ManageNamedPort64)               },
+                { 0x72, nameof(SvcHandler.ConnectToPort64)                 }
             };
 
             _svcTable64 = new Action<SvcHandler, CpuThreadState>[0x80];

+ 18 - 12
Ryujinx.HLE/HOS/Kernel/SupervisorCall/SvcThread.cs

@@ -62,22 +62,17 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
                 priority,
                 cpuCore);
 
-            if (result != KernelResult.Success)
+            if (result == KernelResult.Success)
             {
-                currentProcess.ResourceLimit?.Release(LimitableResource.Thread, 1);
-
-                return result;
+                result = _process.HandleTable.GenerateHandle(thread, out handle);
             }
-
-            result = _process.HandleTable.GenerateHandle(thread, out handle);
-
-            if (result != KernelResult.Success)
+            else
             {
-                thread.Terminate();
-
                 currentProcess.ResourceLimit?.Release(LimitableResource.Thread, 1);
             }
 
+            thread.DecrementReferenceCount();
+
             return result;
         }
 
@@ -88,11 +83,22 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
 
         private KernelResult StartThread(int handle)
         {
-            KThread thread = _process.HandleTable.GetObject<KThread>(handle);
+            KThread thread = _process.HandleTable.GetKThread(handle);
 
             if (thread != null)
             {
-                return thread.Start();
+                thread.IncrementReferenceCount();
+
+                KernelResult result = thread.Start();
+
+                if (result == KernelResult.Success)
+                {
+                    thread.IncrementReferenceCount();
+                }
+
+                thread.DecrementReferenceCount();
+
+                return result;
             }
             else
             {

+ 1 - 1
Ryujinx.HLE/HOS/Kernel/Threading/KEvent.cs

@@ -8,7 +8,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
         public KEvent(Horizon system)
         {
             ReadableEvent = new KReadableEvent(system, this);
-            WritableEvent = new KWritableEvent(this);
+            WritableEvent = new KWritableEvent(system, this);
         }
     }
 }

+ 20 - 0
Ryujinx.HLE/HOS/Kernel/Threading/KScheduler.cs

@@ -210,9 +210,29 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
                 }
             }
 
+            return GetDummyThread();
+
             throw new InvalidOperationException("Current thread is not scheduled!");
         }
 
+        private KThread _dummyThread;
+
+        private KThread GetDummyThread()
+        {
+            if (_dummyThread != null)
+            {
+                return _dummyThread;
+            }
+
+            KProcess dummyProcess = new KProcess(_system);
+
+            KThread dummyThread = new KThread(_system);
+
+            dummyThread.Initialize(0, 0, 0, 44, 0, dummyProcess, ThreadType.Dummy);
+
+            return _dummyThread = dummyThread;
+        }
+
         public KProcess GetCurrentProcess()
         {
             return GetCurrentThread().Owner;

+ 1 - 1
Ryujinx.HLE/HOS/Kernel/Threading/KSynchronization.cs

@@ -32,7 +32,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
 
                 _system.CriticalSection.Leave();
 
-                return 0;
+                return KernelResult.Success;
             }
 
             if (timeout == 0)

+ 50 - 12
Ryujinx.HLE/HOS/Kernel/Threading/KThread.cs

@@ -30,6 +30,9 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
 
         private ulong _tlsAddress;
 
+        public ulong TlsAddress => _tlsAddress;
+        public ulong TlsDramAddress { get; private set; }
+
         public long LastScheduledTime { get; set; }
 
         public LinkedListNode<KThread>[] SiblingsPerCore { get; private set; }
@@ -67,6 +70,8 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
         public bool WaitingSync   { get; set; }
 
         private bool _hasExited;
+        private bool _hasBeenInitialized;
+        private bool _hasBeenReleased;
 
         public bool WaitingInArbitration { get; set; }
 
@@ -124,6 +129,8 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
                     return KernelResult.OutOfMemory;
                 }
 
+                TlsDramAddress = owner.MemoryManager.GetDramAddressFromVa(_tlsAddress);
+
                 MemoryHelper.FillWithZeros(owner.CpuMemory, (long)_tlsAddress, KTlsPageInfo.TlsEntrySize);
             }
 
@@ -133,6 +140,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
             {
                 Owner = owner;
 
+                owner.IncrementReferenceCount();
                 owner.IncrementThreadCount();
 
                 is64Bits = (owner.MmuFlags & 1) != 0;
@@ -156,6 +164,8 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
 
             ThreadUid = System.GetThreadUid();
 
+            _hasBeenInitialized = true;
+
             if (owner != null)
             {
                 owner.AddThread(this);
@@ -252,6 +262,15 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
 
         public void Exit()
         {
+            //TODO: Debug event.
+
+            if (Owner != null)
+            {
+                Owner.ResourceLimit?.Release(LimitableResource.Thread, 0, 1);
+
+                _hasBeenReleased = true;
+            }
+
             System.CriticalSection.Enter();
 
             _forcePauseFlags &= ~ThreadSchedState.ForcePauseMask;
@@ -259,6 +278,8 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
             ExitImpl();
 
             System.CriticalSection.Leave();
+
+            DecrementReferenceCount();
         }
 
         private void ExitImpl()
@@ -930,7 +951,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
                 return;
             }
 
-            //Remove from old queues.
+            //Remove thread from the old priority queues.
             for (int core = 0; core < KScheduler.CpuCoresCount; core++)
             {
                 if (((oldAffinityMask >> core) & 1) != 0)
@@ -946,7 +967,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
                 }
             }
 
-            //Insert on new queues.
+            //Add thread to the new priority queues.
             for (int core = 0; core < KScheduler.CpuCoresCount; core++)
             {
                 if (((AffinityMask >> core) & 1) != 0)
@@ -965,11 +986,6 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
             _scheduler.ThreadReselectionRequested = true;
         }
 
-        public override bool IsSignaled()
-        {
-            return _hasExited;
-        }
-
         public void SetEntryArguments(long argsPtr, int threadHandle)
         {
             Context.ThreadState.X0 = (ulong)argsPtr;
@@ -994,13 +1010,36 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
         private void ThreadFinishedHandler(object sender, EventArgs e)
         {
             System.Scheduler.ExitThread(this);
+            System.Scheduler.RemoveThread(this);
+        }
 
-            Terminate();
+        public override bool IsSignaled()
+        {
+            return _hasExited;
+        }
 
-            System.Scheduler.RemoveThread(this);
+        protected override void Destroy()
+        {
+            if (_hasBeenInitialized)
+            {
+                FreeResources();
+
+                bool released = Owner != null || _hasBeenReleased;
+
+                if (Owner != null)
+                {
+                    Owner.ResourceLimit?.Release(LimitableResource.Thread, 1, released ? 0 : 1);
+
+                    Owner.DecrementReferenceCount();
+                }
+                else
+                {
+                    System.ResourceLimit.Release(LimitableResource.Thread, 1, released ? 0 : 1);
+                }
+            }
         }
 
-        public void Terminate()
+        private void FreeResources()
         {
             Owner?.RemoveThread(this);
 
@@ -1011,8 +1050,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
 
             System.CriticalSection.Enter();
 
-            //Wake up all threads that may be waiting for a mutex being held
-            //by this thread.
+            //Wake up all threads that may be waiting for a mutex being held by this thread.
             foreach (KThread thread in _mutexWaiters)
             {
                 thread.MutexOwner             = null;

+ 2 - 2
Ryujinx.HLE/HOS/Kernel/Threading/KWritableEvent.cs

@@ -2,11 +2,11 @@ using Ryujinx.HLE.HOS.Kernel.Common;
 
 namespace Ryujinx.HLE.HOS.Kernel.Threading
 {
-    class KWritableEvent
+    class KWritableEvent : KAutoObject
     {
         private KEvent _parent;
 
-        public KWritableEvent(KEvent parent)
+        public KWritableEvent(Horizon system, KEvent parent) : base(system)
         {
             _parent = parent;
         }

+ 17 - 4
Ryujinx.HLE/HOS/ProgramLoader.cs

@@ -63,11 +63,11 @@ namespace Ryujinx.HLE.HOS
                 0,
                 0);
 
-            MemoryRegion memRegion = kip.IsService
+            MemoryRegion memoryRegion = kip.IsService
                 ? MemoryRegion.Service
                 : MemoryRegion.Application;
 
-            KMemoryRegionManager region = system.MemoryRegions[(int)memRegion];
+            KMemoryRegionManager region = system.MemoryRegions[(int)memoryRegion];
 
             KernelResult result = region.AllocatePages((ulong)codePagesCount, false, out KPageList pageList);
 
@@ -85,7 +85,7 @@ namespace Ryujinx.HLE.HOS
                 kip.Capabilities,
                 pageList,
                 system.ResourceLimit,
-                memRegion);
+                memoryRegion);
 
             if (result != KernelResult.Success)
             {
@@ -103,6 +103,8 @@ namespace Ryujinx.HLE.HOS
                 return false;
             }
 
+            process.DefaultCpuCore = kip.DefaultProcessorId;
+
             result = process.Start(kip.MainThreadPriority, (ulong)kip.MainThreadStackSize);
 
             if (result != KernelResult.Success)
@@ -201,11 +203,20 @@ namespace Ryujinx.HLE.HOS
 
             KProcess process = new KProcess(system);
 
+            MemoryRegion memoryRegion = (MemoryRegion)((metaData.Acid.Flags >> 2) & 0xf);
+
+            if (memoryRegion > MemoryRegion.NvServices)
+            {
+                Logger.PrintError(LogClass.Loader, $"Process initialization failed due to invalid ACID flags.");
+
+                return false;
+            }
+
             result = process.Initialize(
                 creationInfo,
                 metaData.Aci0.KernelAccessControl.Capabilities,
                 resourceLimit,
-                MemoryRegion.Application);
+                memoryRegion);
 
             if (result != KernelResult.Success)
             {
@@ -228,6 +239,8 @@ namespace Ryujinx.HLE.HOS
                 }
             }
 
+            process.DefaultCpuCore = metaData.DefaultCpuId;
+
             result = process.Start(metaData.MainThreadPriority, (ulong)metaData.MainThreadStackSize);
 
             if (result != KernelResult.Success)

+ 16 - 16
Ryujinx.HLE/HOS/ServiceCtx.cs

@@ -8,24 +8,24 @@ namespace Ryujinx.HLE.HOS
 {
     class ServiceCtx
     {
-        public Switch        Device       { get; private set; }
-        public KProcess      Process      { get; private set; }
-        public MemoryManager Memory       { get; private set; }
-        public KSession      Session      { get; private set; }
-        public IpcMessage    Request      { get; private set; }
-        public IpcMessage    Response     { get; private set; }
-        public BinaryReader  RequestData  { get; private set; }
-        public BinaryWriter  ResponseData { get; private set; }
+        public Switch         Device       { get; }
+        public KProcess       Process      { get; }
+        public MemoryManager  Memory       { get; }
+        public KClientSession Session      { get; }
+        public IpcMessage     Request      { get; }
+        public IpcMessage     Response     { get; }
+        public BinaryReader   RequestData  { get; }
+        public BinaryWriter   ResponseData { get; }
 
         public ServiceCtx(
-            Switch        device,
-            KProcess      process,
-            MemoryManager memory,
-            KSession      session,
-            IpcMessage    request,
-            IpcMessage    response,
-            BinaryReader  requestData,
-            BinaryWriter  responseData)
+            Switch         device,
+            KProcess       process,
+            MemoryManager  memory,
+            KClientSession session,
+            IpcMessage     request,
+            IpcMessage     response,
+            BinaryReader   requestData,
+            BinaryWriter   responseData)
         {
             Device       = device;
             Process      = process;

+ 6 - 4
Ryujinx.HLE/HOS/Services/IpcService.cs

@@ -116,7 +116,7 @@ namespace Ryujinx.HLE.HOS.Services
             }
             else
             {
-                string dbgMessage = $"{context.Session.ServiceName} {service.GetType().Name}: {commandId}";
+                string dbgMessage = $"{service.GetType().FullName}: {commandId}";
 
                 throw new ServiceNotImplementedException(context, dbgMessage);
             }
@@ -132,9 +132,11 @@ namespace Ryujinx.HLE.HOS.Services
             }
             else
             {
-                KSession session = new KSession(obj, context.Session.ServiceName);
+                KSession session = new KSession(context.Device.System);
 
-                if (context.Process.HandleTable.GenerateHandle(session, out int handle) != KernelResult.Success)
+                session.ClientSession.Service = obj;
+
+                if (context.Process.HandleTable.GenerateHandle(session.ClientSession, out int handle) != KernelResult.Success)
                 {
                     throw new InvalidOperationException("Out of handles!");
                 }
@@ -151,7 +153,7 @@ namespace Ryujinx.HLE.HOS.Services
             {
                 int handle = context.Request.HandleDesc.ToMove[index];
 
-                KSession session = context.Process.HandleTable.GetObject<KSession>(handle);
+                KClientSession session = context.Process.HandleTable.GetObject<KClientSession>(handle);
 
                 return session?.Service is T ? (T)session.Service : null;
             }

+ 1 - 1
Ryujinx.HLE/HOS/Services/Psm/IPsmSession.cs

@@ -35,7 +35,7 @@ namespace Ryujinx.HLE.HOS.Services.Psm
         {
             if (_stateChangeEventHandle == -1)
             {
-                KernelResult resultCode = context.Process.HandleTable.GenerateHandle(_stateChangeEvent, out int stateChangeEventHandle);
+                KernelResult resultCode = context.Process.HandleTable.GenerateHandle(_stateChangeEvent.ReadableEvent, out int stateChangeEventHandle);
 
                 if (resultCode != KernelResult.Success)
                 {

+ 91 - 15
Ryujinx.HLE/HOS/Services/Sm/IUserInterface.cs

@@ -1,8 +1,11 @@
+using Ryujinx.Common.Logging;
 using Ryujinx.HLE.HOS.Ipc;
 using Ryujinx.HLE.HOS.Kernel.Common;
 using Ryujinx.HLE.HOS.Kernel.Ipc;
 using System;
+using System.Collections.Concurrent;
 using System.Collections.Generic;
+using System.IO;
 
 namespace Ryujinx.HLE.HOS.Services.Sm
 {
@@ -12,18 +15,30 @@ namespace Ryujinx.HLE.HOS.Services.Sm
 
         public override IReadOnlyDictionary<int, ServiceProcessRequest> Commands => _commands;
 
+        private ConcurrentDictionary<string, KPort> _registeredServices;
+
         private bool _isInitialized;
 
         public IUserInterface()
         {
             _commands = new Dictionary<int, ServiceProcessRequest>
             {
-                { 0, Initialize },
-                { 1, GetService }
+                { 0, Initialize      },
+                { 1, GetService      },
+                { 2, RegisterService }
             };
+
+            _registeredServices = new ConcurrentDictionary<string, KPort>();
         }
 
-        private const int SmNotInitialized = 0x415;
+        public static void InitializePort(Horizon system)
+        {
+            KPort port = new KPort(system, 256, false, 0);
+
+            port.ClientPort.SetName("sm:");
+
+            port.ClientPort.Service = new IUserInterface();
+        }
 
         public long Initialize(ServiceCtx context)
         {
@@ -34,34 +49,76 @@ namespace Ryujinx.HLE.HOS.Services.Sm
 
         public long GetService(ServiceCtx context)
         {
-            //Only for kernel version > 3.0.0.
             if (!_isInitialized)
             {
-                //return SmNotInitialized;
+                return ErrorCode.MakeError(ErrorModule.Sm, SmErr.NotInitialized);
             }
 
-            string name = string.Empty;
+            string name = ReadName(context);
 
-            for (int index = 0; index < 8 &&
-                context.RequestData.BaseStream.Position <
-                context.RequestData.BaseStream.Length; index++)
+            if (name == string.Empty)
             {
-                byte chr = context.RequestData.ReadByte();
+                return ErrorCode.MakeError(ErrorModule.Sm, SmErr.InvalidName);
+            }
 
-                if (chr >= 0x20 && chr < 0x7f)
+            KSession session = new KSession(context.Device.System);
+
+            if (_registeredServices.TryGetValue(name, out KPort port))
+            {
+                KernelResult result = port.EnqueueIncomingSession(session.ServerSession);
+
+                if (result != KernelResult.Success)
                 {
-                    name += (char)chr;
+                    throw new InvalidOperationException($"Session enqueue on port returned error \"{result}\".");
                 }
             }
+            else
+            {
+                session.ClientSession.Service = ServiceFactory.MakeService(context.Device.System, name);
+            }
+
+            if (context.Process.HandleTable.GenerateHandle(session.ClientSession, out int handle) != KernelResult.Success)
+            {
+                throw new InvalidOperationException("Out of handles!");
+            }
+
+            context.Response.HandleDesc = IpcHandleDesc.MakeMove(handle);
+
+            return 0;
+        }
+
+        public long RegisterService(ServiceCtx context)
+        {
+            if (!_isInitialized)
+            {
+                return ErrorCode.MakeError(ErrorModule.Sm, SmErr.NotInitialized);
+            }
+
+            long namePosition = context.RequestData.BaseStream.Position;
+
+            string name = ReadName(context);
+
+            context.RequestData.BaseStream.Seek(namePosition + 8, SeekOrigin.Begin);
+
+            bool isLight = (context.RequestData.ReadInt32() & 1) != 0;
+
+            int maxSessions = context.RequestData.ReadInt32();
 
             if (name == string.Empty)
             {
-                return 0;
+                return ErrorCode.MakeError(ErrorModule.Sm, SmErr.InvalidName);
             }
 
-            KSession session = new KSession(ServiceFactory.MakeService(context.Device.System, name), name);
+            Logger.PrintInfo(LogClass.ServiceSm, $"Register \"{name}\".");
+
+            KPort port = new KPort(context.Device.System, maxSessions, isLight, 0);
 
-            if (context.Process.HandleTable.GenerateHandle(session, out int handle) != KernelResult.Success)
+            if (!_registeredServices.TryAdd(name, port))
+            {
+                return ErrorCode.MakeError(ErrorModule.Sm, SmErr.AlreadyRegistered);
+            }
+
+            if (context.Process.HandleTable.GenerateHandle(port.ServerPort, out int handle) != KernelResult.Success)
             {
                 throw new InvalidOperationException("Out of handles!");
             }
@@ -70,5 +127,24 @@ namespace Ryujinx.HLE.HOS.Services.Sm
 
             return 0;
         }
+
+        private static string ReadName(ServiceCtx context)
+        {
+            string name = string.Empty;
+
+            for (int index = 0; index < 8 &&
+                context.RequestData.BaseStream.Position <
+                context.RequestData.BaseStream.Length; index++)
+            {
+                byte chr = context.RequestData.ReadByte();
+
+                if (chr >= 0x20 && chr < 0x7f)
+                {
+                    name += (char)chr;
+                }
+            }
+
+            return name;
+        }
     }
 }

+ 9 - 0
Ryujinx.HLE/HOS/Services/Sm/SmErr.cs

@@ -0,0 +1,9 @@
+namespace Ryujinx.HLE.HOS.Services.Sm
+{
+    static class SmErr
+    {
+        public const int NotInitialized    = 2;
+        public const int AlreadyRegistered = 4;
+        public const int InvalidName       = 6;
+    }
+}

+ 31 - 29
Ryujinx.HLE/Loaders/Compression/BackwardsLz.cs

@@ -1,5 +1,4 @@
 using System;
-using System.IO;
 
 namespace Ryujinx.HLE.Loaders.Compression
 {
@@ -7,22 +6,26 @@ namespace Ryujinx.HLE.Loaders.Compression
     {
         private class BackwardsReader
         {
-            private Stream _baseStream;
+            private byte[] _data;
 
-            public BackwardsReader(Stream baseStream)
+            private int _position;
+
+            public int Position => _position;
+
+            public BackwardsReader(byte[] data, int end)
             {
-                _baseStream = baseStream;
+                _data     = data;
+                _position = end;
             }
 
-            public byte ReadByte()
+            public void SeekCurrent(int offset)
             {
-                _baseStream.Seek(-1, SeekOrigin.Current);
-
-                byte value = (byte)_baseStream.ReadByte();
-
-                _baseStream.Seek(-1, SeekOrigin.Current);
+                _position += offset;
+            }
 
-                return value;
+            public byte ReadByte()
+            {
+                return _data[--_position];
             }
 
             public short ReadInt16()
@@ -39,30 +42,24 @@ namespace Ryujinx.HLE.Loaders.Compression
             }
         }
 
-        public static byte[] Decompress(Stream input, int decompressedLength)
+        public static void DecompressInPlace(byte[] buffer, int headerEnd)
         {
-            long end = input.Position;
-
-            BackwardsReader reader = new BackwardsReader(input);
+            BackwardsReader reader = new BackwardsReader(buffer, headerEnd);
 
             int additionalDecLength = reader.ReadInt32();
             int startOffset         = reader.ReadInt32();
             int compressedLength    = reader.ReadInt32();
 
-            input.Seek(12 - startOffset, SeekOrigin.Current);
-
-            byte[] dec = new byte[decompressedLength];
+            reader.SeekCurrent(12 - startOffset);
 
-            int decompressedLengthUnpadded = compressedLength + additionalDecLength;
+            int decBase = headerEnd - compressedLength;
 
-            int decompressionStart = decompressedLength - decompressedLengthUnpadded;
-
-            int decPos = dec.Length;
+            int decPos = compressedLength + additionalDecLength;
 
             byte mask   = 0;
             byte header = 0;
 
-            while (decPos > decompressionStart)
+            while (decPos > 0)
             {
                 if ((mask >>= 1) == 0)
                 {
@@ -72,7 +69,7 @@ namespace Ryujinx.HLE.Loaders.Compression
 
                 if ((header & mask) == 0)
                 {
-                    dec[--decPos] = reader.ReadByte();
+                    buffer[decBase + --decPos] = reader.ReadByte();
                 }
                 else
                 {
@@ -81,25 +78,30 @@ namespace Ryujinx.HLE.Loaders.Compression
                     int length   = (pair >> 12)   + 3;
                     int position = (pair & 0xfff) + 3;
 
+                    if (length > decPos)
+                    {
+                        length = decPos;
+                    }
+
                     decPos -= length;
 
+                    int dstPos = decBase + decPos;
+
                     if (length <= position)
                     {
-                        int srcPos = decPos + position;
+                        int srcPos = dstPos + position;
 
-                        Buffer.BlockCopy(dec, srcPos, dec, decPos, length);
+                        Buffer.BlockCopy(buffer, srcPos, buffer, dstPos, length);
                     }
                     else
                     {
                         for (int offset = 0; offset < length; offset++)
                         {
-                            dec[decPos + offset] = dec[decPos + position + offset];
+                            buffer[dstPos + offset] = buffer[dstPos + position + offset];
                         }
                     }
                 }
             }
-
-            return dec;
         }
     }
 }

+ 4 - 6
Ryujinx.HLE/Loaders/Executables/KernelInitialProcess.cs

@@ -98,7 +98,7 @@ namespace Ryujinx.HLE.Loaders.Executables
 
             MainThreadStackSize = segments[1].Attribute;
 
-            Capabilities = new int[8];
+            Capabilities = new int[32];
 
             for (int index = 0; index < Capabilities.Length; index++)
             {
@@ -114,13 +114,11 @@ namespace Ryujinx.HLE.Loaders.Executables
 
         private byte[] ReadSegment(SegmentHeader header, Stream input)
         {
-            long end = input.Position + header.CompressedSize;
+            byte[] data = new byte[header.DecompressedSize];
 
-            input.Seek(end, SeekOrigin.Begin);
+            input.Read(data, 0, header.CompressedSize);
 
-            byte[] data = BackwardsLz.Decompress(input, header.DecompressedSize);
-
-            input.Seek(end, SeekOrigin.Begin);
+            BackwardsLz.DecompressInPlace(data, header.CompressedSize);
 
             return data;
         }

+ 1 - 1
Ryujinx.HLE/Loaders/Npdm/ServiceAccessControl.cs

@@ -31,7 +31,7 @@ namespace Ryujinx.HLE.Loaders.Npdm
                 int  length          = (controlByte & 0x07) + 1;
                 bool registerAllowed = (controlByte & 0x80) != 0;
 
-                services.Add(Encoding.ASCII.GetString(reader.ReadBytes(length), 0, length), registerAllowed);
+                services[Encoding.ASCII.GetString(reader.ReadBytes(length))] = registerAllowed;
 
                 byteReaded += length + 1;
             }

Niektóre pliki nie zostały wyświetlone z powodu dużej ilości zmienionych plików