From 97d0c6242368f443c50395b2fa9d99a59f1df1e8 Mon Sep 17 00:00:00 2001
From: Thomas Guillemard <me@thog.eu>
Date: Sun, 14 Jul 2019 22:50:11 +0200
Subject: [PATCH] Accurately implement steady & system clocks (#732)

* Improve SteadyClock implementation accuracy

* Rewrite system clocks to be accurate

* Implement IStaticService 100 & 101

* Add time:* permissions

* Address comments

* Realign TimePermissions definitions

* Address gdk's comments

* Fix after rebase
---
 Ryujinx.HLE/HOS/Horizon.cs                    |   6 +
 .../HOS/Services/Time/Clock/ClockTypes.cs     |  40 +++++++
 .../Clock/StandardLocalSystemClockCore.cs     |  59 ++++++++++
 .../Clock/StandardNetworkSystemClockCore.cs   |  59 ++++++++++
 .../Time/Clock/StandardUserSystemClockCore.cs |  96 ++++++++++++++++
 .../Services/Time/Clock/SteadyClockCore.cs    |  86 ++++++++++++++
 .../Services/Time/Clock/SystemClockCore.cs    |  31 +++++
 .../HOS/Services/Time/IStaticService.cs       |  44 +++++--
 Ryujinx.HLE/HOS/Services/Time/ISteadyClock.cs |  43 ++++---
 Ryujinx.HLE/HOS/Services/Time/ISystemClock.cs | 108 ++++++++++--------
 Ryujinx.HLE/HOS/Services/Time/ResultCode.cs   |   5 +-
 .../HOS/Services/Time/SystemClockType.cs      |  10 --
 .../HOS/Services/Time/TimePermissions.cs      |  17 +++
 13 files changed, 522 insertions(+), 82 deletions(-)
 create mode 100644 Ryujinx.HLE/HOS/Services/Time/Clock/ClockTypes.cs
 create mode 100644 Ryujinx.HLE/HOS/Services/Time/Clock/StandardLocalSystemClockCore.cs
 create mode 100644 Ryujinx.HLE/HOS/Services/Time/Clock/StandardNetworkSystemClockCore.cs
 create mode 100644 Ryujinx.HLE/HOS/Services/Time/Clock/StandardUserSystemClockCore.cs
 create mode 100644 Ryujinx.HLE/HOS/Services/Time/Clock/SteadyClockCore.cs
 create mode 100644 Ryujinx.HLE/HOS/Services/Time/Clock/SystemClockCore.cs
 delete mode 100644 Ryujinx.HLE/HOS/Services/Time/SystemClockType.cs
 create mode 100644 Ryujinx.HLE/HOS/Services/Time/TimePermissions.cs

diff --git a/Ryujinx.HLE/HOS/Horizon.cs b/Ryujinx.HLE/HOS/Horizon.cs
index 496fbdbe4..7523d2bee 100644
--- a/Ryujinx.HLE/HOS/Horizon.cs
+++ b/Ryujinx.HLE/HOS/Horizon.cs
@@ -9,6 +9,7 @@ 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.Services.Time.Clock;
 using Ryujinx.HLE.HOS.SystemState;
 using Ryujinx.HLE.Loaders.Executables;
 using Ryujinx.HLE.Loaders.Npdm;
@@ -195,6 +196,11 @@ namespace Ryujinx.HLE.HOS
             LoadKeySet();
 
             ContentManager = new ContentManager(device);
+
+            // NOTE: Now we set the default internal offset of the steady clock like Nintendo does... even if it's strange this is accurate.
+            // TODO: use bpc:r and set:sys (and set external clock source id from settings)
+            DateTime UnixEpoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
+            SteadyClockCore.Instance.SetInternalOffset(new TimeSpanType(((ulong)(DateTime.Now.ToUniversalTime() - UnixEpoch).TotalSeconds) * 1000000000));
         }
 
         public void LoadCart(string exeFsDir, string romFsFile = null)
diff --git a/Ryujinx.HLE/HOS/Services/Time/Clock/ClockTypes.cs b/Ryujinx.HLE/HOS/Services/Time/Clock/ClockTypes.cs
new file mode 100644
index 000000000..1e527c002
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Services/Time/Clock/ClockTypes.cs
@@ -0,0 +1,40 @@
+using Ryujinx.HLE.Utilities;
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.HLE.HOS.Services.Time.Clock
+{
+    [StructLayout(LayoutKind.Sequential)]
+    struct TimeSpanType
+    {
+        public ulong NanoSeconds;
+
+        public TimeSpanType(ulong nanoSeconds)
+        {
+            NanoSeconds = nanoSeconds;
+        }
+
+        public ulong ToSeconds()
+        {
+            return NanoSeconds / 1000000000;
+        }
+
+        public static TimeSpanType FromTicks(ulong ticks, ulong frequency)
+        {
+            return new TimeSpanType(ticks * 1000000000 / frequency);
+        }
+    }
+
+    [StructLayout(LayoutKind.Sequential)]
+    struct SteadyClockTimePoint
+    {
+        public ulong   TimePoint;
+        public UInt128 ClockSourceId;
+    }
+
+    [StructLayout(LayoutKind.Sequential)]
+    struct SystemClockContext
+    {
+        public ulong                Offset;
+        public SteadyClockTimePoint SteadyTimePoint;
+    }
+}
diff --git a/Ryujinx.HLE/HOS/Services/Time/Clock/StandardLocalSystemClockCore.cs b/Ryujinx.HLE/HOS/Services/Time/Clock/StandardLocalSystemClockCore.cs
new file mode 100644
index 000000000..16550199b
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Services/Time/Clock/StandardLocalSystemClockCore.cs
@@ -0,0 +1,59 @@
+using Ryujinx.HLE.HOS.Kernel.Threading;
+
+namespace Ryujinx.HLE.HOS.Services.Time.Clock
+{
+    class StandardLocalSystemClockCore : SystemClockCore
+    {
+        private SteadyClockCore    _steadyClockCore;
+        private SystemClockContext _context;
+
+        private static StandardLocalSystemClockCore instance;
+
+        public static StandardLocalSystemClockCore Instance
+        {
+            get
+            {
+                if (instance == null)
+                {
+                    instance = new StandardLocalSystemClockCore(SteadyClockCore.Instance);
+                }
+
+                return instance;
+            }
+        }
+
+        public StandardLocalSystemClockCore(SteadyClockCore steadyClockCore)
+        {
+            _steadyClockCore = steadyClockCore;
+            _context         = new SystemClockContext();
+
+            _context.SteadyTimePoint.ClockSourceId = steadyClockCore.GetClockSourceId();
+        }
+
+        public override ResultCode Flush(SystemClockContext context)
+        {
+            // TODO: set:sys SetUserSystemClockContext
+
+            return ResultCode.Success;
+        }
+
+        public override SteadyClockCore GetSteadyClockCore()
+        {
+            return _steadyClockCore;
+        }
+
+        public override ResultCode GetSystemClockContext(KThread thread, out SystemClockContext context)
+        {
+            context = _context;
+
+            return ResultCode.Success;
+        }
+
+        public override ResultCode SetSystemClockContext(SystemClockContext context)
+        {
+            _context = context;
+
+            return ResultCode.Success;
+        }
+    }
+}
diff --git a/Ryujinx.HLE/HOS/Services/Time/Clock/StandardNetworkSystemClockCore.cs b/Ryujinx.HLE/HOS/Services/Time/Clock/StandardNetworkSystemClockCore.cs
new file mode 100644
index 000000000..59bc822ce
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Services/Time/Clock/StandardNetworkSystemClockCore.cs
@@ -0,0 +1,59 @@
+using Ryujinx.HLE.HOS.Kernel.Threading;
+
+namespace Ryujinx.HLE.HOS.Services.Time.Clock
+{
+    class StandardNetworkSystemClockCore : SystemClockCore
+    {
+        private SteadyClockCore    _steadyClockCore;
+        private SystemClockContext _context;
+
+        private static StandardNetworkSystemClockCore instance;
+
+        public static StandardNetworkSystemClockCore Instance
+        {
+            get
+            {
+                if (instance == null)
+                {
+                    instance = new StandardNetworkSystemClockCore(SteadyClockCore.Instance);
+                }
+
+                return instance;
+            }
+        }
+
+        public StandardNetworkSystemClockCore(SteadyClockCore steadyClockCore)
+        {
+            _steadyClockCore = steadyClockCore;
+            _context         = new SystemClockContext();
+
+            _context.SteadyTimePoint.ClockSourceId = steadyClockCore.GetClockSourceId();
+        }
+
+        public override ResultCode Flush(SystemClockContext context)
+        {
+            // TODO: set:sys SetNetworkSystemClockContext
+
+            return ResultCode.Success;
+        }
+
+        public override SteadyClockCore GetSteadyClockCore()
+        {
+            return _steadyClockCore;
+        }
+
+        public override ResultCode GetSystemClockContext(KThread thread, out SystemClockContext context)
+        {
+            context = _context;
+
+            return ResultCode.Success;
+        }
+
+        public override ResultCode SetSystemClockContext(SystemClockContext context)
+        {
+            _context = context;
+
+            return ResultCode.Success;
+        }
+    }
+}
diff --git a/Ryujinx.HLE/HOS/Services/Time/Clock/StandardUserSystemClockCore.cs b/Ryujinx.HLE/HOS/Services/Time/Clock/StandardUserSystemClockCore.cs
new file mode 100644
index 000000000..00f296ad0
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Services/Time/Clock/StandardUserSystemClockCore.cs
@@ -0,0 +1,96 @@
+using Ryujinx.HLE.HOS.Kernel.Threading;
+
+namespace Ryujinx.HLE.HOS.Services.Time.Clock
+{
+    class StandardUserSystemClockCore : SystemClockCore
+    {
+        private StandardLocalSystemClockCore   _localSystemClockCore;
+        private StandardNetworkSystemClockCore _networkSystemClockCore;
+        private bool                           _autoCorrectionEnabled;
+
+        private static StandardUserSystemClockCore instance;
+
+        public static StandardUserSystemClockCore Instance
+        {
+            get
+            {
+                if (instance == null)
+                {
+                    instance = new StandardUserSystemClockCore(StandardLocalSystemClockCore.Instance, StandardNetworkSystemClockCore.Instance);
+                }
+
+                return instance;
+            }
+        }
+
+        public StandardUserSystemClockCore(StandardLocalSystemClockCore localSystemClockCore, StandardNetworkSystemClockCore networkSystemClockCore)
+        {
+            _localSystemClockCore   = localSystemClockCore;
+            _networkSystemClockCore = networkSystemClockCore;
+            _autoCorrectionEnabled  = false;
+        }
+
+        public override ResultCode Flush(SystemClockContext context)
+        {
+            return ResultCode.NotImplemented;
+        }
+
+        public override SteadyClockCore GetSteadyClockCore()
+        {
+            return _localSystemClockCore.GetSteadyClockCore();
+        }
+
+        public override ResultCode GetSystemClockContext(KThread thread, out SystemClockContext context)
+        {
+            ResultCode result = ApplyAutomaticCorrection(thread, false);
+
+            context = new SystemClockContext();
+
+            if (result == ResultCode.Success)
+            {
+                return _localSystemClockCore.GetSystemClockContext(thread, out context);
+            }
+
+            return result;
+        }
+
+        public override ResultCode SetSystemClockContext(SystemClockContext context)
+        {
+            return ResultCode.NotImplemented;
+        }
+
+        private ResultCode ApplyAutomaticCorrection(KThread thread, bool autoCorrectionEnabled)
+        {
+            ResultCode result = ResultCode.Success;
+
+            if (_autoCorrectionEnabled != autoCorrectionEnabled && _networkSystemClockCore.IsClockSetup(thread))
+            {
+                result = _networkSystemClockCore.GetSystemClockContext(thread, out SystemClockContext context);
+
+                if (result == ResultCode.Success)
+                {
+                    _localSystemClockCore.SetSystemClockContext(context);
+                }
+            }
+
+            return result;
+        }
+
+        public ResultCode SetAutomaticCorrectionEnabled(KThread thread, bool autoCorrectionEnabled)
+        {
+            ResultCode result = ApplyAutomaticCorrection(thread, autoCorrectionEnabled);
+
+            if (result == ResultCode.Success)
+            {
+                _autoCorrectionEnabled = autoCorrectionEnabled;
+            }
+
+            return result;
+        }
+
+        public bool IsAutomaticCorrectionEnabled()
+        {
+            return _autoCorrectionEnabled;
+        }
+    }
+}
diff --git a/Ryujinx.HLE/HOS/Services/Time/Clock/SteadyClockCore.cs b/Ryujinx.HLE/HOS/Services/Time/Clock/SteadyClockCore.cs
new file mode 100644
index 000000000..b69b7d3cb
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Services/Time/Clock/SteadyClockCore.cs
@@ -0,0 +1,86 @@
+using Ryujinx.HLE.HOS.Kernel.Threading;
+using Ryujinx.HLE.Utilities;
+using System;
+
+namespace Ryujinx.HLE.HOS.Services.Time.Clock
+{
+    class SteadyClockCore
+    {
+        private TimeSpanType _testOffset;
+        private TimeSpanType _internalOffset;
+        private UInt128      _clockSourceId;
+
+        private static SteadyClockCore instance;
+
+        public static SteadyClockCore Instance
+        {
+            get
+            {
+                if (instance == null)
+                {
+                    instance = new SteadyClockCore();
+                }
+
+                return instance;
+            }
+        }
+
+        private SteadyClockCore()
+        {
+            _testOffset     = new TimeSpanType(0);
+            _internalOffset = new TimeSpanType(0);
+            _clockSourceId  = new UInt128(Guid.NewGuid().ToByteArray());
+        }
+
+        private SteadyClockTimePoint GetTimePoint(KThread thread)
+        {
+            SteadyClockTimePoint result = new SteadyClockTimePoint
+            {
+                TimePoint     = 0,
+                ClockSourceId = _clockSourceId
+            };
+
+            TimeSpanType ticksTimeSpan = TimeSpanType.FromTicks(thread.Context.ThreadState.CntpctEl0, thread.Context.ThreadState.CntfrqEl0);
+
+            result.TimePoint = _internalOffset.ToSeconds() + ticksTimeSpan.ToSeconds();
+
+            return result;
+        }
+
+        public UInt128 GetClockSourceId()
+        {
+            return _clockSourceId;
+        }
+
+        public SteadyClockTimePoint GetCurrentTimePoint(KThread thread)
+        {
+            SteadyClockTimePoint result = GetTimePoint(thread);
+
+            result.TimePoint += _testOffset.ToSeconds();
+
+            return result;
+        }
+
+        public TimeSpanType GetTestOffset()
+        {
+            return _testOffset;
+        }
+
+        public void SetTestOffset(TimeSpanType testOffset)
+        {
+            _testOffset = testOffset;
+        }
+
+        // TODO: check if this is accurate
+        public TimeSpanType GetInternalOffset()
+        {
+            return _internalOffset;
+        }
+
+        // TODO: check if this is accurate
+        public void SetInternalOffset(TimeSpanType internalOffset)
+        {
+            _internalOffset = internalOffset;
+        }
+    }
+}
diff --git a/Ryujinx.HLE/HOS/Services/Time/Clock/SystemClockCore.cs b/Ryujinx.HLE/HOS/Services/Time/Clock/SystemClockCore.cs
new file mode 100644
index 000000000..d3a056e43
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Services/Time/Clock/SystemClockCore.cs
@@ -0,0 +1,31 @@
+using Ryujinx.HLE.HOS.Kernel.Threading;
+
+namespace Ryujinx.HLE.HOS.Services.Time.Clock
+{
+    abstract class SystemClockCore
+    {
+        public abstract SteadyClockCore GetSteadyClockCore();
+
+        public abstract ResultCode GetSystemClockContext(KThread thread, out SystemClockContext context);
+
+        public abstract ResultCode SetSystemClockContext(SystemClockContext context);
+
+        public abstract ResultCode Flush(SystemClockContext context);
+
+        public bool IsClockSetup(KThread thread)
+        {
+            ResultCode result = GetSystemClockContext(thread, out SystemClockContext context);
+
+            if (result == ResultCode.Success)
+            {
+                SteadyClockCore steadyClockCore = GetSteadyClockCore();
+
+                SteadyClockTimePoint steadyClockTimePoint = steadyClockCore.GetCurrentTimePoint(thread);
+
+                return steadyClockTimePoint.ClockSourceId == context.SteadyTimePoint.ClockSourceId;
+            }
+
+            return false;
+        }
+    }
+}
diff --git a/Ryujinx.HLE/HOS/Services/Time/IStaticService.cs b/Ryujinx.HLE/HOS/Services/Time/IStaticService.cs
index 420e69128..10c659c5c 100644
--- a/Ryujinx.HLE/HOS/Services/Time/IStaticService.cs
+++ b/Ryujinx.HLE/HOS/Services/Time/IStaticService.cs
@@ -1,25 +1,31 @@
 using Ryujinx.HLE.HOS.Ipc;
 using Ryujinx.HLE.HOS.Kernel.Common;
+using Ryujinx.HLE.HOS.Services.Time.Clock;
 using System;
 
 namespace Ryujinx.HLE.HOS.Services.Time
 {
-    [Service("time:a")]
-    [Service("time:s")]
-    [Service("time:u")]
+    [Service("time:a", TimePermissions.Applet)]
+    [Service("time:s", TimePermissions.System)]
+    [Service("time:u", TimePermissions.User)]
     class IStaticService : IpcService
     {
+        private TimePermissions _permissions;
+
         private int _timeSharedMemoryNativeHandle = 0;
 
         private static readonly DateTime StartupDate = DateTime.UtcNow;
 
-        public IStaticService(ServiceCtx context) { }
+        public IStaticService(ServiceCtx context, TimePermissions permissions)
+        {
+            _permissions = permissions;
+        }
 
         [Command(0)]
         // GetStandardUserSystemClock() -> object<nn::timesrv::detail::service::ISystemClock>
         public ResultCode GetStandardUserSystemClock(ServiceCtx context)
         {
-            MakeObject(context, new ISystemClock(SystemClockType.User));
+            MakeObject(context, new ISystemClock(StandardUserSystemClockCore.Instance, (_permissions & TimePermissions.UserSystemClockWritableMask) != 0));
 
             return ResultCode.Success;
         }
@@ -28,7 +34,7 @@ namespace Ryujinx.HLE.HOS.Services.Time
         // GetStandardNetworkSystemClock() -> object<nn::timesrv::detail::service::ISystemClock>
         public ResultCode GetStandardNetworkSystemClock(ServiceCtx context)
         {
-            MakeObject(context, new ISystemClock(SystemClockType.Network));
+            MakeObject(context, new ISystemClock(StandardNetworkSystemClockCore.Instance, (_permissions & TimePermissions.NetworkSystemClockWritableMask) != 0));
 
             return ResultCode.Success;
         }
@@ -55,7 +61,7 @@ namespace Ryujinx.HLE.HOS.Services.Time
         // GetStandardLocalSystemClock() -> object<nn::timesrv::detail::service::ISystemClock>
         public ResultCode GetStandardLocalSystemClock(ServiceCtx context)
         {
-            MakeObject(context, new ISystemClock(SystemClockType.Local));
+            MakeObject(context, new ISystemClock(StandardLocalSystemClockCore.Instance, (_permissions & TimePermissions.LocalSystemClockWritableMask) != 0));
 
             return ResultCode.Success;
         }
@@ -77,10 +83,34 @@ namespace Ryujinx.HLE.HOS.Services.Time
             return ResultCode.Success;
         }
 
+        [Command(100)]
+        // IsStandardUserSystemClockAutomaticCorrectionEnabled() -> bool
+        public ResultCode IsStandardUserSystemClockAutomaticCorrectionEnabled(ServiceCtx context)
+        {
+            context.ResponseData.Write(StandardUserSystemClockCore.Instance.IsAutomaticCorrectionEnabled());
+
+            return ResultCode.Success;
+        }
+
+        [Command(101)]
+        // SetStandardUserSystemClockAutomaticCorrectionEnabled(b8)
+        public ResultCode SetStandardUserSystemClockAutomaticCorrectionEnabled(ServiceCtx context)
+        {
+            if ((_permissions & TimePermissions.UserSystemClockWritableMask) == 0)
+            {
+                return ResultCode.PermissionDenied;
+            }
+
+            bool autoCorrectionEnabled = context.RequestData.ReadBoolean();
+
+            return StandardUserSystemClockCore.Instance.SetAutomaticCorrectionEnabled(context.Thread, autoCorrectionEnabled);
+        }
+
         [Command(300)] // 4.0.0+
         // CalculateMonotonicSystemClockBaseTimePoint(nn::time::SystemClockContext) -> u64
         public ResultCode CalculateMonotonicSystemClockBaseTimePoint(ServiceCtx context)
         {
+            // TODO: reimplement this
             long timeOffset              = (long)(DateTime.UtcNow - StartupDate).TotalSeconds;
             long systemClockContextEpoch = context.RequestData.ReadInt64();
 
diff --git a/Ryujinx.HLE/HOS/Services/Time/ISteadyClock.cs b/Ryujinx.HLE/HOS/Services/Time/ISteadyClock.cs
index ccf19d7d5..d9f05f29c 100644
--- a/Ryujinx.HLE/HOS/Services/Time/ISteadyClock.cs
+++ b/Ryujinx.HLE/HOS/Services/Time/ISteadyClock.cs
@@ -1,26 +1,17 @@
-using System;
+using Ryujinx.Common;
+using Ryujinx.HLE.HOS.Services.Time.Clock;
 
 namespace Ryujinx.HLE.HOS.Services.Time
 {
     class ISteadyClock : IpcService
     {
-        private ulong _testOffset;
-
-        public ISteadyClock()
-        {
-            _testOffset = 0;
-        }
-
         [Command(0)]
         // GetCurrentTimePoint() -> nn::time::SteadyClockTimePoint
         public ResultCode GetCurrentTimePoint(ServiceCtx context)
         {
-            context.ResponseData.Write((long)(System.Diagnostics.Process.GetCurrentProcess().StartTime - DateTime.Now).TotalSeconds);
+            SteadyClockTimePoint currentTimePoint = SteadyClockCore.Instance.GetCurrentTimePoint(context.Thread);
 
-            for (int i = 0; i < 0x10; i++)
-            {
-                context.ResponseData.Write((byte)0);
-            }
+            context.ResponseData.WriteStruct(currentTimePoint);
 
             return ResultCode.Success;
         }
@@ -29,7 +20,7 @@ namespace Ryujinx.HLE.HOS.Services.Time
         // GetTestOffset() -> nn::TimeSpanType
         public ResultCode GetTestOffset(ServiceCtx context)
         {
-            context.ResponseData.Write(_testOffset);
+            context.ResponseData.WriteStruct(SteadyClockCore.Instance.GetTestOffset());
 
             return ResultCode.Success;
         }
@@ -38,7 +29,29 @@ namespace Ryujinx.HLE.HOS.Services.Time
         // SetTestOffset(nn::TimeSpanType)
         public ResultCode SetTestOffset(ServiceCtx context)
         {
-            _testOffset = context.RequestData.ReadUInt64();
+            TimeSpanType testOffset = context.RequestData.ReadStruct<TimeSpanType>();
+
+            SteadyClockCore.Instance.SetTestOffset(testOffset);
+
+            return 0;
+        }
+
+        [Command(200)] // 3.0.0+
+        // GetInternalOffset() -> nn::TimeSpanType
+        public ResultCode GetInternalOffset(ServiceCtx context)
+        {
+            context.ResponseData.WriteStruct(SteadyClockCore.Instance.GetInternalOffset());
+
+            return ResultCode.Success;
+        }
+
+        [Command(201)] // 3.0.0-3.0.2
+        // SetInternalOffset(nn::TimeSpanType)
+        public ResultCode SetInternalOffset(ServiceCtx context)
+        {
+            TimeSpanType internalOffset = context.RequestData.ReadStruct<TimeSpanType>();
+
+            SteadyClockCore.Instance.SetInternalOffset(internalOffset);
 
             return ResultCode.Success;
         }
diff --git a/Ryujinx.HLE/HOS/Services/Time/ISystemClock.cs b/Ryujinx.HLE/HOS/Services/Time/ISystemClock.cs
index 0ed34d179..ddd67a421 100644
--- a/Ryujinx.HLE/HOS/Services/Time/ISystemClock.cs
+++ b/Ryujinx.HLE/HOS/Services/Time/ISystemClock.cs
@@ -1,97 +1,107 @@
-using System;
+using Ryujinx.Common;
+using Ryujinx.HLE.HOS.Services.Time.Clock;
 
 namespace Ryujinx.HLE.HOS.Services.Time
 {
     class ISystemClock : IpcService
     {
-        private static readonly DateTime Epoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
+        private SystemClockCore _clockCore;
+        private bool            _writePermission;
 
-        private SystemClockType _clockType;
-        private DateTime        _systemClockContextEpoch;
-        private long            _systemClockTimePoint;
-        private byte[]          _systemClockContextEnding;
-        private long            _timeOffset;
-
-        public ISystemClock(SystemClockType clockType)
+        public ISystemClock(SystemClockCore clockCore, bool writePermission)
         {
-            _clockType                = clockType;
-            _systemClockContextEpoch  = System.Diagnostics.Process.GetCurrentProcess().StartTime;
-            _systemClockContextEnding = new byte[0x10];
-            _timeOffset               = 0;
-
-            if (clockType == SystemClockType.User ||
-                clockType == SystemClockType.Network)
-            {
-                _systemClockContextEpoch = _systemClockContextEpoch.ToUniversalTime();
-            }
-
-            _systemClockTimePoint = (long)(_systemClockContextEpoch - Epoch).TotalSeconds;
+            _clockCore       = clockCore;
+            _writePermission = writePermission;
         }
 
         [Command(0)]
         // GetCurrentTime() -> nn::time::PosixTime
         public ResultCode GetCurrentTime(ServiceCtx context)
         {
-            DateTime currentTime = DateTime.Now;
+            SteadyClockCore      steadyClockCore  = _clockCore.GetSteadyClockCore();
+            SteadyClockTimePoint currentTimePoint = steadyClockCore.GetCurrentTimePoint(context.Thread);
 
-            if (_clockType == SystemClockType.User ||
-                _clockType == SystemClockType.Network)
+            ResultCode result = _clockCore.GetSystemClockContext(context.Thread, out SystemClockContext clockContext);
+
+            if (result == ResultCode.Success)
             {
-                currentTime = currentTime.ToUniversalTime();
+                result = ResultCode.TimeMismatch;
+
+                if (currentTimePoint.ClockSourceId == clockContext.SteadyTimePoint.ClockSourceId)
+                {
+                    ulong posixTime = clockContext.Offset + currentTimePoint.TimePoint;
+
+                    context.ResponseData.Write(posixTime);
+
+                    result = 0;
+                }
             }
 
-            context.ResponseData.Write((long)((currentTime - Epoch).TotalSeconds) + _timeOffset);
-
-            return ResultCode.Success;
+            return result;
         }
 
         [Command(1)]
         // SetCurrentTime(nn::time::PosixTime)
         public ResultCode SetCurrentTime(ServiceCtx context)
         {
-            DateTime currentTime = DateTime.Now;
-
-            if (_clockType == SystemClockType.User ||
-                _clockType == SystemClockType.Network)
+            if (!_writePermission)
             {
-                currentTime = currentTime.ToUniversalTime();
+                return ResultCode.PermissionDenied;
             }
 
-            _timeOffset = (context.RequestData.ReadInt64() - (long)(currentTime - Epoch).TotalSeconds);
+            ulong                posixTime        = context.RequestData.ReadUInt64();
+            SteadyClockCore      steadyClockCore  = _clockCore.GetSteadyClockCore();
+            SteadyClockTimePoint currentTimePoint = steadyClockCore.GetCurrentTimePoint(context.Thread);
 
-            return ResultCode.Success;
+            SystemClockContext clockContext = new SystemClockContext()
+            {
+                Offset          = posixTime - currentTimePoint.TimePoint,
+                SteadyTimePoint = currentTimePoint
+            };
+
+            ResultCode result = _clockCore.SetSystemClockContext(clockContext);
+
+            if (result == ResultCode.Success)
+            {
+                result = _clockCore.Flush(clockContext);
+            }
+
+            return result;
         }
 
         [Command(2)]
         // GetSystemClockContext() -> nn::time::SystemClockContext
         public ResultCode GetSystemClockContext(ServiceCtx context)
         {
-            context.ResponseData.Write((long)(_systemClockContextEpoch - Epoch).TotalSeconds);
+            ResultCode result = _clockCore.GetSystemClockContext(context.Thread, out SystemClockContext clockContext);
 
-            // The point in time, TODO: is there a link between epoch and this?
-            context.ResponseData.Write(_systemClockTimePoint);
-
-            // This seems to be some kind of identifier?
-            for (int i = 0; i < 0x10; i++)
+            if (result == ResultCode.Success)
             {
-                context.ResponseData.Write(_systemClockContextEnding[i]);
+                context.ResponseData.WriteStruct(clockContext);
             }
 
-            return ResultCode.Success;
+            return result;
         }
 
         [Command(3)]
         // SetSystemClockContext(nn::time::SystemClockContext)
         public ResultCode SetSystemClockContext(ServiceCtx context)
         {
-            long newSystemClockEpoch     = context.RequestData.ReadInt64();
-            long newSystemClockTimePoint = context.RequestData.ReadInt64();
+            if (!_writePermission)
+            {
+                return ResultCode.PermissionDenied;
+            }
 
-            _systemClockContextEpoch     = Epoch.Add(TimeSpan.FromSeconds(newSystemClockEpoch));
-            _systemClockTimePoint        = newSystemClockTimePoint;
-            _systemClockContextEnding    = context.RequestData.ReadBytes(0x10);
+            SystemClockContext clockContext = context.RequestData.ReadStruct<SystemClockContext>();
 
-            return ResultCode.Success;
+            ResultCode result = _clockCore.SetSystemClockContext(clockContext);
+
+            if (result == ResultCode.Success)
+            {
+                result = _clockCore.Flush(clockContext);
+            }
+
+            return result;
         }
     }
 }
\ 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 5868a458f..ed7130f36 100644
--- a/Ryujinx.HLE/HOS/Services/Time/ResultCode.cs
+++ b/Ryujinx.HLE/HOS/Services/Time/ResultCode.cs
@@ -7,11 +7,14 @@
 
         Success = 0,
 
+        PermissionDenied         = (1   << ErrorCodeShift) | ModuleId,
+        TimeMismatch             = (102 << 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
+        TimeZoneNotFound         = (989 << ErrorCodeShift) | ModuleId,
+        NotImplemented           = (990 << ErrorCodeShift) | ModuleId,
     }
 }
\ No newline at end of file
diff --git a/Ryujinx.HLE/HOS/Services/Time/SystemClockType.cs b/Ryujinx.HLE/HOS/Services/Time/SystemClockType.cs
deleted file mode 100644
index 54b7df3f8..000000000
--- a/Ryujinx.HLE/HOS/Services/Time/SystemClockType.cs
+++ /dev/null
@@ -1,10 +0,0 @@
-namespace Ryujinx.HLE.HOS.Services.Time
-{
-    enum SystemClockType
-    {
-        User,
-        Network,
-        Local,
-        EphemeralNetwork
-    }
-}
\ No newline at end of file
diff --git a/Ryujinx.HLE/HOS/Services/Time/TimePermissions.cs b/Ryujinx.HLE/HOS/Services/Time/TimePermissions.cs
new file mode 100644
index 000000000..823c82888
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Services/Time/TimePermissions.cs
@@ -0,0 +1,17 @@
+using System;
+
+namespace Ryujinx.HLE.HOS.Services.Time
+{
+    [Flags]
+    enum TimePermissions
+    {
+        LocalSystemClockWritableMask   = 0x1,
+        UserSystemClockWritableMask    = 0x2,
+        NetworkSystemClockWritableMask = 0x4,
+        UnknownPermissionMask          = 0x8,
+
+        User   = 0,
+        Applet = LocalSystemClockWritableMask | UserSystemClockWritableMask | UnknownPermissionMask,
+        System = NetworkSystemClockWritableMask
+    }
+}