From cfcc360d0610c66e9b9986f7aab96f79df0da79e Mon Sep 17 00:00:00 2001
From: Ac_K <Acoustik666@gmail.com>
Date: Thu, 21 Nov 2019 13:24:06 +0100
Subject: [PATCH] ldn: Implement calls of UserLocalCommunicationService (#829)

* ldn: Implement calls of UserLocalCommunicationService

- Implement `IUserServiceCreator: CreateUserLocalCommunicationService` according to RE.
- Implement `IUserLocalCommunicationService` calls:
  - Every calls in this interface are layered to `NetworkInterface`.
  - `GetState` according to RE.
  - `InitializeOld`, `Initialize` and `Finalize` stubbed with the appropriate result code and some TODO according to RE.
  - `AttachStateChangeEvent` according to RE.

* Fix var name and TODO comments

* Fix review
---
 Ryujinx.Common/Logging/LogClass.cs            |  1 +
 .../HOS/Services/Ldn/IUserServiceCreator.cs   | 13 ++-
 .../HOS/Services/Ldn/NetworkInterface.cs      | 59 +++++++++++++
 Ryujinx.HLE/HOS/Services/Ldn/ResultCode.cs    | 16 ++++
 .../HOS/Services/Ldn/Types/NetworkState.cs    | 13 +++
 .../IUserLocalCommunicationService.cs         | 88 +++++++++++++++++++
 6 files changed, 189 insertions(+), 1 deletion(-)
 create mode 100644 Ryujinx.HLE/HOS/Services/Ldn/NetworkInterface.cs
 create mode 100644 Ryujinx.HLE/HOS/Services/Ldn/ResultCode.cs
 create mode 100644 Ryujinx.HLE/HOS/Services/Ldn/Types/NetworkState.cs
 create mode 100644 Ryujinx.HLE/HOS/Services/Ldn/UserServiceCreator/IUserLocalCommunicationService.cs

diff --git a/Ryujinx.Common/Logging/LogClass.cs b/Ryujinx.Common/Logging/LogClass.cs
index b056f3836..43efd8d58 100644
--- a/Ryujinx.Common/Logging/LogClass.cs
+++ b/Ryujinx.Common/Logging/LogClass.cs
@@ -26,6 +26,7 @@ namespace Ryujinx.Common.Logging
         ServiceFs,
         ServiceHid,
         ServiceIrs,
+        ServiceLdn,
         ServiceLdr,
         ServiceLm,
         ServiceMm,
diff --git a/Ryujinx.HLE/HOS/Services/Ldn/IUserServiceCreator.cs b/Ryujinx.HLE/HOS/Services/Ldn/IUserServiceCreator.cs
index 052727dd3..3fc9ce1c7 100644
--- a/Ryujinx.HLE/HOS/Services/Ldn/IUserServiceCreator.cs
+++ b/Ryujinx.HLE/HOS/Services/Ldn/IUserServiceCreator.cs
@@ -1,8 +1,19 @@
-namespace Ryujinx.HLE.HOS.Services.Ldn
+using Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator;
+
+namespace Ryujinx.HLE.HOS.Services.Ldn
 {
     [Service("ldn:u")]
     class IUserServiceCreator : IpcService
     {
         public IUserServiceCreator(ServiceCtx context) { }
+
+        [Command(0)]
+        // CreateUserLocalCommunicationService() -> object<nn::ldn::detail::IUserLocalCommunicationService>
+        public ResultCode CreateUserLocalCommunicationService(ServiceCtx context)
+        {
+            MakeObject(context, new IUserLocalCommunicationService(context));
+
+            return ResultCode.Success;
+        }
     }
 }
\ No newline at end of file
diff --git a/Ryujinx.HLE/HOS/Services/Ldn/NetworkInterface.cs b/Ryujinx.HLE/HOS/Services/Ldn/NetworkInterface.cs
new file mode 100644
index 000000000..f06c66446
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Services/Ldn/NetworkInterface.cs
@@ -0,0 +1,59 @@
+using Ryujinx.Common.Logging;
+using Ryujinx.HLE.HOS.Kernel.Threading;
+using Ryujinx.HLE.HOS.Services.Ldn.Types;
+using System.Net;
+
+namespace Ryujinx.HLE.HOS.Services.Ldn
+{
+    internal class NetworkInterface
+    {
+        public ResultCode NifmState        { get; set; }
+        public KEvent     StateChangeEvent { get; private set; }
+
+        private NetworkState _state;
+
+        public NetworkInterface(Horizon system)
+        {
+            // TODO(Ac_K): Determine where the internal state is set.
+            NifmState        = ResultCode.Success;
+            StateChangeEvent = new KEvent(system);
+
+            _state = NetworkState.None;
+        }
+
+        public ResultCode Initialize(int unknown, int version, IPAddress ipv4Address, IPAddress subnetMaskAddress)
+        {
+            // TODO(Ac_K): Call nn::nifm::InitializeSystem().
+            //             If the call failed, it returns the result code.
+            //             If the call succeed, it signal and clear an event then start a new thread named nn.ldn.NetworkInterfaceMonitor.
+
+            Logger.PrintStub(LogClass.ServiceLdn, new { version });
+
+            // NOTE: Since we don't support ldn for now, we can return this following result code to make it disabled.
+            return ResultCode.DeviceDisabled;
+        }
+
+        public ResultCode GetState(out NetworkState state)
+        {
+            // Return ResultCode.InvalidArgument if _state is null, doesn't occur in our case.
+
+            state = _state;
+
+            return ResultCode.Success;
+        }
+
+        public ResultCode Finalize()
+        {
+            // TODO(Ac_K): Finalize nifm service then kill the thread named nn.ldn.NetworkInterfaceMonitor.
+
+            _state = NetworkState.None;
+
+            StateChangeEvent.WritableEvent.Signal();
+            StateChangeEvent.WritableEvent.Clear();
+
+            Logger.PrintStub(LogClass.ServiceLdn);
+
+            return ResultCode.Success;
+        }
+    }
+}
\ No newline at end of file
diff --git a/Ryujinx.HLE/HOS/Services/Ldn/ResultCode.cs b/Ryujinx.HLE/HOS/Services/Ldn/ResultCode.cs
new file mode 100644
index 000000000..0c9f6209c
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Services/Ldn/ResultCode.cs
@@ -0,0 +1,16 @@
+namespace Ryujinx.HLE.HOS.Services.Ldn
+{
+    enum ResultCode
+    {
+        ModuleId       = 203,
+        ErrorCodeShift = 9,
+
+        Success = 0,
+
+        DeviceDisabled  = (22 << ErrorCodeShift) | ModuleId,
+        InvalidState    = (32 << ErrorCodeShift) | ModuleId,
+        Unknown1        = (48 << ErrorCodeShift) | ModuleId,
+        InvalidArgument = (96 << ErrorCodeShift) | ModuleId,
+        InvalidOjbect   = (97 << ErrorCodeShift) | ModuleId,
+    }
+}
\ No newline at end of file
diff --git a/Ryujinx.HLE/HOS/Services/Ldn/Types/NetworkState.cs b/Ryujinx.HLE/HOS/Services/Ldn/Types/NetworkState.cs
new file mode 100644
index 000000000..6ac204833
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Services/Ldn/Types/NetworkState.cs
@@ -0,0 +1,13 @@
+namespace Ryujinx.HLE.HOS.Services.Ldn.Types
+{
+    enum NetworkState
+    {
+        None,
+        Initialized,
+        AccessPoint,
+        AccessPointCreated,
+        Station,
+        StationConnected,
+        Error
+    }
+}
\ No newline at end of file
diff --git a/Ryujinx.HLE/HOS/Services/Ldn/UserServiceCreator/IUserLocalCommunicationService.cs b/Ryujinx.HLE/HOS/Services/Ldn/UserServiceCreator/IUserLocalCommunicationService.cs
new file mode 100644
index 000000000..b1ae2d6ee
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Services/Ldn/UserServiceCreator/IUserLocalCommunicationService.cs
@@ -0,0 +1,88 @@
+using Ryujinx.HLE.HOS.Ipc;
+using Ryujinx.HLE.HOS.Kernel.Common;
+using Ryujinx.HLE.HOS.Services.Ldn.Types;
+using System;
+using System.Net;
+
+namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator
+{
+    class IUserLocalCommunicationService : IpcService
+    {
+        // TODO(Ac_K): Determine what the hardcoded unknown value is.
+        private const int UnknownValue = 90;
+
+        private NetworkInterface _networkInterface;
+
+        private int _stateChangeEventHandle = 0;
+
+        public IUserLocalCommunicationService(ServiceCtx context)
+        {
+            _networkInterface = new NetworkInterface(context.Device.System);
+        }
+
+        [Command(0)]
+        // GetState() -> s32 state
+        public ResultCode GetState(ServiceCtx context)
+        {
+            if (_networkInterface.NifmState != ResultCode.Success)
+            {
+                context.ResponseData.Write((int)NetworkState.Error);
+
+                return ResultCode.Success;
+            }
+
+            ResultCode result = _networkInterface.GetState(out NetworkState state);
+
+            if (result == ResultCode.Success)
+            {
+                context.ResponseData.Write((int)state);
+            }
+
+            return result;
+        }
+
+        [Command(100)]
+        // AttachStateChangeEvent() -> handle<copy>
+        public ResultCode AttachStateChangeEvent(ServiceCtx context)
+        {
+            if (_stateChangeEventHandle == 0)
+            {
+                if (context.Process.HandleTable.GenerateHandle(_networkInterface.StateChangeEvent.ReadableEvent, out _stateChangeEventHandle) != KernelResult.Success)
+                {
+                    throw new InvalidOperationException("Out of handles!");
+                }
+            }
+
+            context.Response.HandleDesc = IpcHandleDesc.MakeCopy(_stateChangeEventHandle);
+
+            // Return ResultCode.InvalidArgument if handle is null, doesn't occur in our case since we already throw an Exception.
+
+            return ResultCode.Success;
+        }
+
+        [Command(400)]
+        // InitializeOld(u64, pid)
+        public ResultCode InitializeOld(ServiceCtx context)
+        {
+            return _networkInterface.Initialize(UnknownValue, 0, null, null);
+        }
+
+        [Command(401)]
+        // Finalize()
+        public ResultCode Finalize(ServiceCtx context)
+        {
+            return _networkInterface.Finalize();
+        }
+
+        [Command(402)] // 7.0.0+
+        // Initialize(u64 ip_addresses, u64, pid)
+        public ResultCode Initialize(ServiceCtx context)
+        {
+            // TODO(Ac_K): Determine what addresses are.
+            IPAddress unknownAddress1 = new IPAddress(context.RequestData.ReadUInt32());
+            IPAddress unknownAddress2 = new IPAddress(context.RequestData.ReadUInt32());
+
+            return _networkInterface.Initialize(UnknownValue, version: 1, unknownAddress1, unknownAddress2);
+        }
+    }
+}
\ No newline at end of file