From e2ffa5a125fcbe8a25c73d8e04c08c08ef378860 Mon Sep 17 00:00:00 2001
From: Ac_K <Acoustik666@gmail.com>
Date: Tue, 15 Mar 2022 04:07:07 +0100
Subject: [PATCH] ntc: Implement IEnsureNetworkClockAvailabilityService (#3192)

* ntc: Implement IEnsureNetworkClockAvailabilityService

This PR implement a basic `IEnsureNetworkClockAvailabilityService` checked by RE. It's needed by Splatoon 2 with Guest Internet Access enabled. Game is now playable with this setting.

* Update Ryujinx.HLE/HOS/Services/Nim/Ntc/StaticService/IEnsureNetworkClockAvailabilityService.cs

Co-authored-by: gdkchan <gab.dark.100@gmail.com>

Co-authored-by: gdkchan <gab.dark.100@gmail.com>
---
 Ryujinx.Common/Logging/LogClass.cs            |  1 +
 .../HOS/Services/Nim/Ntc/IStaticService.cs    | 18 ++++-
 .../IEnsureNetworkClockAvailabilityService.cs | 77 +++++++++++++++++++
 Ryujinx.HLE/HOS/Services/Time/ResultCode.cs   | 24 +++---
 4 files changed, 108 insertions(+), 12 deletions(-)
 create mode 100644 Ryujinx.HLE/HOS/Services/Nim/Ntc/StaticService/IEnsureNetworkClockAvailabilityService.cs

diff --git a/Ryujinx.Common/Logging/LogClass.cs b/Ryujinx.Common/Logging/LogClass.cs
index a7d36765dc..20c8da3f81 100644
--- a/Ryujinx.Common/Logging/LogClass.cs
+++ b/Ryujinx.Common/Logging/LogClass.cs
@@ -47,6 +47,7 @@ namespace Ryujinx.Common.Logging
         ServiceNim,
         ServiceNs,
         ServiceNsd,
+        ServiceNtc,
         ServiceNv,
         ServiceOlsc,
         ServicePctl,
diff --git a/Ryujinx.HLE/HOS/Services/Nim/Ntc/IStaticService.cs b/Ryujinx.HLE/HOS/Services/Nim/Ntc/IStaticService.cs
index f5a3bc7b45..1a4100ee39 100644
--- a/Ryujinx.HLE/HOS/Services/Nim/Ntc/IStaticService.cs
+++ b/Ryujinx.HLE/HOS/Services/Nim/Ntc/IStaticService.cs
@@ -1,8 +1,24 @@
-namespace Ryujinx.HLE.HOS.Services.Nim.Ntc
+using Ryujinx.Common.Logging;
+using Ryujinx.HLE.HOS.Services.Nim.Ntc.StaticService;
+
+namespace Ryujinx.HLE.HOS.Services.Nim.Ntc
 {
     [Service("ntc")]
     class IStaticService : IpcService
     {
         public IStaticService(ServiceCtx context) { }
+
+        [CommandHipc(0)]
+        // OpenEnsureNetworkClockAvailabilityService(u64) -> object<nn::ntc::detail::service::IEnsureNetworkClockAvailabilityService>
+        public ResultCode CreateAsyncInterface(ServiceCtx context)
+        {
+            ulong unknown = context.RequestData.ReadUInt64();
+
+            MakeObject(context, new IEnsureNetworkClockAvailabilityService(context));
+
+            Logger.Stub?.PrintStub(LogClass.ServiceNtc, new { unknown });
+
+            return ResultCode.Success;
+        }
     }
 }
\ No newline at end of file
diff --git a/Ryujinx.HLE/HOS/Services/Nim/Ntc/StaticService/IEnsureNetworkClockAvailabilityService.cs b/Ryujinx.HLE/HOS/Services/Nim/Ntc/StaticService/IEnsureNetworkClockAvailabilityService.cs
new file mode 100644
index 0000000000..fb31bd1f0d
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Services/Nim/Ntc/StaticService/IEnsureNetworkClockAvailabilityService.cs
@@ -0,0 +1,77 @@
+using Ryujinx.Common.Logging;
+using Ryujinx.HLE.HOS.Ipc;
+using Ryujinx.HLE.HOS.Kernel.Common;
+using Ryujinx.HLE.HOS.Kernel.Threading;
+using System;
+
+namespace Ryujinx.HLE.HOS.Services.Nim.Ntc.StaticService
+{
+    class IEnsureNetworkClockAvailabilityService : IpcService 
+    {
+        private KEvent     _finishNotificationEvent;
+        private ResultCode _taskResultCode;
+
+        public IEnsureNetworkClockAvailabilityService(ServiceCtx context)
+        {
+            _finishNotificationEvent = new KEvent(context.Device.System.KernelContext);
+            _taskResultCode          = ResultCode.Success;
+
+            // NOTE: The service starts a thread that polls Nintendo NTP server and syncs the time with it.
+            //       Additionnally it gets and uses some settings too:
+            //       autonomic_correction_interval_seconds, autonomic_correction_failed_retry_interval_seconds,
+            //       autonomic_correction_immediate_try_count_max, autonomic_correction_immediate_try_interval_milliseconds
+        }
+
+        [CommandHipc(0)]
+        // StartTask()
+        public ResultCode StartTask(ServiceCtx context)
+        {
+            if (!context.Device.Configuration.EnableInternetAccess)
+            {
+                return (ResultCode)Time.ResultCode.NetworkTimeNotAvailable;
+            }
+
+            // NOTE: Since we don't support the Nintendo NTP server, we can signal the event now to confirm the update task is done.
+            _finishNotificationEvent.ReadableEvent.Signal();
+
+            Logger.Stub?.PrintStub(LogClass.ServiceNtc);
+
+            return ResultCode.Success;
+        }
+
+        [CommandHipc(1)]
+        // GetFinishNotificationEvent() -> handle<copy>
+        public ResultCode GetFinishNotificationEvent(ServiceCtx context)
+        {
+            if (context.Process.HandleTable.GenerateHandle(_finishNotificationEvent.ReadableEvent, out int finishNotificationEventHandle) != KernelResult.Success)
+            {
+                throw new InvalidOperationException("Out of handles!");
+            }
+
+            context.Response.HandleDesc = IpcHandleDesc.MakeCopy(finishNotificationEventHandle);
+
+            return ResultCode.Success;
+        }
+
+        [CommandHipc(2)]
+        // GetResult()
+        public ResultCode GetResult(ServiceCtx context)
+        {
+            return _taskResultCode;
+        }
+
+        [CommandHipc(3)]
+        // Cancel()
+        public ResultCode Cancel(ServiceCtx context)
+        {
+            // NOTE: The update task should be canceled here.
+            _finishNotificationEvent.ReadableEvent.Signal();
+
+            _taskResultCode = (ResultCode)Time.ResultCode.NetworkTimeTaskCanceled;
+
+            Logger.Stub?.PrintStub(LogClass.ServiceNtc);
+
+            return ResultCode.Success;
+        }
+    }
+}
\ No newline at end of file
diff --git a/Ryujinx.HLE/HOS/Services/Time/ResultCode.cs b/Ryujinx.HLE/HOS/Services/Time/ResultCode.cs
index 57bf42060b..3b042ec036 100644
--- a/Ryujinx.HLE/HOS/Services/Time/ResultCode.cs
+++ b/Ryujinx.HLE/HOS/Services/Time/ResultCode.cs
@@ -7,16 +7,18 @@
 
         Success = 0,
 
-        TimeServiceNotInitialized = (0   << ErrorCodeShift) | ModuleId,
-        PermissionDenied          = (1   << ErrorCodeShift) | ModuleId,
-        TimeMismatch              = (102 << ErrorCodeShift) | ModuleId,
-        UninitializedClock        = (103 << ErrorCodeShift) | ModuleId,
-        TimeNotFound              = (200 << ErrorCodeShift) | ModuleId,
-        Overflow                  = (201 << ErrorCodeShift) | ModuleId,
-        LocationNameTooLong       = (801 << ErrorCodeShift) | ModuleId,
-        OutOfRange                = (902 << ErrorCodeShift) | ModuleId,
-        TimeZoneConversionFailed  = (903 << ErrorCodeShift) | ModuleId,
-        TimeZoneNotFound          = (989 << ErrorCodeShift) | ModuleId,
-        NotImplemented            = (990 << ErrorCodeShift) | ModuleId,
+        TimeServiceNotInitialized = (0    << ErrorCodeShift) | ModuleId,
+        PermissionDenied          = (1    << ErrorCodeShift) | ModuleId,
+        TimeMismatch              = (102  << ErrorCodeShift) | ModuleId,
+        UninitializedClock        = (103  << ErrorCodeShift) | ModuleId,
+        TimeNotFound              = (200  << ErrorCodeShift) | ModuleId,
+        Overflow                  = (201  << ErrorCodeShift) | ModuleId,
+        LocationNameTooLong       = (801  << ErrorCodeShift) | ModuleId,
+        OutOfRange                = (902  << ErrorCodeShift) | ModuleId,
+        TimeZoneConversionFailed  = (903  << ErrorCodeShift) | ModuleId,
+        TimeZoneNotFound          = (989  << ErrorCodeShift) | ModuleId,
+        NotImplemented            = (990  << ErrorCodeShift) | ModuleId,
+        NetworkTimeNotAvailable   = (1000 << ErrorCodeShift) | ModuleId,
+        NetworkTimeTaskCanceled   = (1003 << ErrorCodeShift) | ModuleId,
     }
 }