aboutsummaryrefslogtreecommitdiff
path: root/src/Ryujinx.HLE/HOS/Services/Time/Clock
diff options
context:
space:
mode:
authorTSR Berry <20988865+TSRBerry@users.noreply.github.com>2023-04-08 01:22:00 +0200
committerMary <thog@protonmail.com>2023-04-27 23:51:14 +0200
commitcee712105850ac3385cd0091a923438167433f9f (patch)
tree4a5274b21d8b7f938c0d0ce18736d3f2993b11b1 /src/Ryujinx.HLE/HOS/Services/Time/Clock
parentcd124bda587ef09668a971fa1cac1c3f0cfc9f21 (diff)
Move solution and projects to src
Diffstat (limited to 'src/Ryujinx.HLE/HOS/Services/Time/Clock')
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Time/Clock/EphemeralNetworkSystemClockContextWriter.cs10
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Time/Clock/EphemeralNetworkSystemClockCore.cs7
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Time/Clock/LocalSystemClockContextWriter.cs19
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Time/Clock/NetworkSystemClockContextWriter.cs19
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Time/Clock/StandardLocalSystemClockCore.cs7
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Time/Clock/StandardNetworkSystemClockCore.cs36
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Time/Clock/StandardSteadyClockCore.cs72
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Time/Clock/StandardUserSystemClockCore.cs108
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Time/Clock/SteadyClockCore.cs98
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Time/Clock/SystemClockContextUpdateCallback.cs71
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Time/Clock/SystemClockCore.cs144
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Time/Clock/TickBasedSteadyClockCore.cs24
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Time/Clock/Types/ClockSnapshot.cs50
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Time/Clock/Types/SteadyClockTimePoint.cs43
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Time/Clock/Types/SystemClockContext.cs11
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Time/Clock/Types/TimeSpanType.cs50
16 files changed, 769 insertions, 0 deletions
diff --git a/src/Ryujinx.HLE/HOS/Services/Time/Clock/EphemeralNetworkSystemClockContextWriter.cs b/src/Ryujinx.HLE/HOS/Services/Time/Clock/EphemeralNetworkSystemClockContextWriter.cs
new file mode 100644
index 00000000..14d3cb24
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Services/Time/Clock/EphemeralNetworkSystemClockContextWriter.cs
@@ -0,0 +1,10 @@
+namespace Ryujinx.HLE.HOS.Services.Time.Clock
+{
+ class EphemeralNetworkSystemClockContextWriter : SystemClockContextUpdateCallback
+ {
+ protected override ResultCode Update()
+ {
+ return ResultCode.Success;
+ }
+ }
+}
diff --git a/src/Ryujinx.HLE/HOS/Services/Time/Clock/EphemeralNetworkSystemClockCore.cs b/src/Ryujinx.HLE/HOS/Services/Time/Clock/EphemeralNetworkSystemClockCore.cs
new file mode 100644
index 00000000..003863e4
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Services/Time/Clock/EphemeralNetworkSystemClockCore.cs
@@ -0,0 +1,7 @@
+namespace Ryujinx.HLE.HOS.Services.Time.Clock
+{
+ class EphemeralNetworkSystemClockCore : SystemClockCore
+ {
+ public EphemeralNetworkSystemClockCore(SteadyClockCore steadyClockCore) : base(steadyClockCore) { }
+ }
+}
diff --git a/src/Ryujinx.HLE/HOS/Services/Time/Clock/LocalSystemClockContextWriter.cs b/src/Ryujinx.HLE/HOS/Services/Time/Clock/LocalSystemClockContextWriter.cs
new file mode 100644
index 00000000..fb7ebdc5
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Services/Time/Clock/LocalSystemClockContextWriter.cs
@@ -0,0 +1,19 @@
+namespace Ryujinx.HLE.HOS.Services.Time.Clock
+{
+ class LocalSystemClockContextWriter : SystemClockContextUpdateCallback
+ {
+ private TimeSharedMemory _sharedMemory;
+
+ public LocalSystemClockContextWriter(TimeSharedMemory sharedMemory)
+ {
+ _sharedMemory = sharedMemory;
+ }
+
+ protected override ResultCode Update()
+ {
+ _sharedMemory.UpdateLocalSystemClockContext(_context);
+
+ return ResultCode.Success;
+ }
+ }
+}
diff --git a/src/Ryujinx.HLE/HOS/Services/Time/Clock/NetworkSystemClockContextWriter.cs b/src/Ryujinx.HLE/HOS/Services/Time/Clock/NetworkSystemClockContextWriter.cs
new file mode 100644
index 00000000..36468ec1
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Services/Time/Clock/NetworkSystemClockContextWriter.cs
@@ -0,0 +1,19 @@
+namespace Ryujinx.HLE.HOS.Services.Time.Clock
+{
+ class NetworkSystemClockContextWriter : SystemClockContextUpdateCallback
+ {
+ private TimeSharedMemory _sharedMemory;
+
+ public NetworkSystemClockContextWriter(TimeSharedMemory sharedMemory)
+ {
+ _sharedMemory = sharedMemory;
+ }
+
+ protected override ResultCode Update()
+ {
+ _sharedMemory.UpdateNetworkSystemClockContext(_context);
+
+ return ResultCode.Success;
+ }
+ }
+}
diff --git a/src/Ryujinx.HLE/HOS/Services/Time/Clock/StandardLocalSystemClockCore.cs b/src/Ryujinx.HLE/HOS/Services/Time/Clock/StandardLocalSystemClockCore.cs
new file mode 100644
index 00000000..20c334e8
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Services/Time/Clock/StandardLocalSystemClockCore.cs
@@ -0,0 +1,7 @@
+namespace Ryujinx.HLE.HOS.Services.Time.Clock
+{
+ class StandardLocalSystemClockCore : SystemClockCore
+ {
+ public StandardLocalSystemClockCore(StandardSteadyClockCore steadyClockCore) : base(steadyClockCore) {}
+ }
+}
diff --git a/src/Ryujinx.HLE/HOS/Services/Time/Clock/StandardNetworkSystemClockCore.cs b/src/Ryujinx.HLE/HOS/Services/Time/Clock/StandardNetworkSystemClockCore.cs
new file mode 100644
index 00000000..aec03485
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Services/Time/Clock/StandardNetworkSystemClockCore.cs
@@ -0,0 +1,36 @@
+using Ryujinx.Cpu;
+
+namespace Ryujinx.HLE.HOS.Services.Time.Clock
+{
+ class StandardNetworkSystemClockCore : SystemClockCore
+ {
+ private TimeSpanType _standardNetworkClockSufficientAccuracy;
+
+ public StandardNetworkSystemClockCore(StandardSteadyClockCore steadyClockCore) : base(steadyClockCore)
+ {
+ _standardNetworkClockSufficientAccuracy = new TimeSpanType(0);
+ }
+
+ public bool IsStandardNetworkSystemClockAccuracySufficient(ITickSource tickSource)
+ {
+ SteadyClockCore steadyClockCore = GetSteadyClockCore();
+ SteadyClockTimePoint currentTimePoint = steadyClockCore.GetCurrentTimePoint(tickSource);
+
+ bool isStandardNetworkClockSufficientAccuracy = false;
+
+ ResultCode result = GetClockContext(tickSource, out SystemClockContext context);
+
+ if (result == ResultCode.Success && context.SteadyTimePoint.GetSpanBetween(currentTimePoint, out long outSpan) == ResultCode.Success)
+ {
+ isStandardNetworkClockSufficientAccuracy = outSpan * 1000000000 < _standardNetworkClockSufficientAccuracy.NanoSeconds;
+ }
+
+ return isStandardNetworkClockSufficientAccuracy;
+ }
+
+ public void SetStandardNetworkClockSufficientAccuracy(TimeSpanType standardNetworkClockSufficientAccuracy)
+ {
+ _standardNetworkClockSufficientAccuracy = standardNetworkClockSufficientAccuracy;
+ }
+ }
+}
diff --git a/src/Ryujinx.HLE/HOS/Services/Time/Clock/StandardSteadyClockCore.cs b/src/Ryujinx.HLE/HOS/Services/Time/Clock/StandardSteadyClockCore.cs
new file mode 100644
index 00000000..8392c4b5
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Services/Time/Clock/StandardSteadyClockCore.cs
@@ -0,0 +1,72 @@
+using Ryujinx.Cpu;
+
+namespace Ryujinx.HLE.HOS.Services.Time.Clock
+{
+ class StandardSteadyClockCore : SteadyClockCore
+ {
+ private TimeSpanType _setupValue;
+ private TimeSpanType _testOffset;
+ private TimeSpanType _internalOffset;
+ private TimeSpanType _cachedRawTimePoint;
+
+ public StandardSteadyClockCore()
+ {
+ _setupValue = TimeSpanType.Zero;
+ _testOffset = TimeSpanType.Zero;
+ _internalOffset = TimeSpanType.Zero;
+ _cachedRawTimePoint = TimeSpanType.Zero;
+ }
+
+ public override SteadyClockTimePoint GetTimePoint(ITickSource tickSource)
+ {
+ SteadyClockTimePoint result = new SteadyClockTimePoint
+ {
+ TimePoint = GetCurrentRawTimePoint(tickSource).ToSeconds(),
+ ClockSourceId = GetClockSourceId()
+ };
+
+ return result;
+ }
+
+ public override TimeSpanType GetTestOffset()
+ {
+ return _testOffset;
+ }
+
+ public override void SetTestOffset(TimeSpanType testOffset)
+ {
+ _testOffset = testOffset;
+ }
+
+ public override TimeSpanType GetInternalOffset()
+ {
+ return _internalOffset;
+ }
+
+ public override void SetInternalOffset(TimeSpanType internalOffset)
+ {
+ _internalOffset = internalOffset;
+ }
+
+ public override TimeSpanType GetCurrentRawTimePoint(ITickSource tickSource)
+ {
+ TimeSpanType ticksTimeSpan = TimeSpanType.FromTicks(tickSource.Counter, tickSource.Frequency);
+
+ TimeSpanType rawTimePoint = new TimeSpanType(_setupValue.NanoSeconds + ticksTimeSpan.NanoSeconds);
+
+ if (rawTimePoint.NanoSeconds < _cachedRawTimePoint.NanoSeconds)
+ {
+ rawTimePoint.NanoSeconds = _cachedRawTimePoint.NanoSeconds;
+ }
+
+ _cachedRawTimePoint = rawTimePoint;
+
+ return rawTimePoint;
+ }
+
+ public void SetSetupValue(TimeSpanType setupValue)
+ {
+ _setupValue = setupValue;
+ }
+ }
+}
diff --git a/src/Ryujinx.HLE/HOS/Services/Time/Clock/StandardUserSystemClockCore.cs b/src/Ryujinx.HLE/HOS/Services/Time/Clock/StandardUserSystemClockCore.cs
new file mode 100644
index 00000000..fa485437
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Services/Time/Clock/StandardUserSystemClockCore.cs
@@ -0,0 +1,108 @@
+using Ryujinx.Cpu;
+using Ryujinx.HLE.HOS.Kernel.Threading;
+using System;
+
+namespace Ryujinx.HLE.HOS.Services.Time.Clock
+{
+ class StandardUserSystemClockCore : SystemClockCore
+ {
+ private StandardLocalSystemClockCore _localSystemClockCore;
+ private StandardNetworkSystemClockCore _networkSystemClockCore;
+ private bool _autoCorrectionEnabled;
+ private SteadyClockTimePoint _autoCorrectionTime;
+ private KEvent _autoCorrectionEvent;
+
+ public StandardUserSystemClockCore(StandardLocalSystemClockCore localSystemClockCore, StandardNetworkSystemClockCore networkSystemClockCore) : base(localSystemClockCore.GetSteadyClockCore())
+ {
+ _localSystemClockCore = localSystemClockCore;
+ _networkSystemClockCore = networkSystemClockCore;
+ _autoCorrectionEnabled = false;
+ _autoCorrectionTime = SteadyClockTimePoint.GetRandom();
+ _autoCorrectionEvent = null;
+ }
+
+ protected override ResultCode Flush(SystemClockContext context)
+ {
+ // As UserSystemClock isn't a real system clock, this shouldn't happens.
+ throw new NotImplementedException();
+ }
+
+ public override ResultCode GetClockContext(ITickSource tickSource, out SystemClockContext context)
+ {
+ ResultCode result = ApplyAutomaticCorrection(tickSource, false);
+
+ context = new SystemClockContext();
+
+ if (result == ResultCode.Success)
+ {
+ return _localSystemClockCore.GetClockContext(tickSource, out context);
+ }
+
+ return result;
+ }
+
+ public override ResultCode SetClockContext(SystemClockContext context)
+ {
+ return ResultCode.NotImplemented;
+ }
+
+ private ResultCode ApplyAutomaticCorrection(ITickSource tickSource, bool autoCorrectionEnabled)
+ {
+ ResultCode result = ResultCode.Success;
+
+ if (_autoCorrectionEnabled != autoCorrectionEnabled && _networkSystemClockCore.IsClockSetup(tickSource))
+ {
+ result = _networkSystemClockCore.GetClockContext(tickSource, out SystemClockContext context);
+
+ if (result == ResultCode.Success)
+ {
+ _localSystemClockCore.SetClockContext(context);
+ }
+ }
+
+ return result;
+ }
+
+ internal void CreateAutomaticCorrectionEvent(Horizon system)
+ {
+ _autoCorrectionEvent = new KEvent(system.KernelContext);
+ }
+
+ public ResultCode SetAutomaticCorrectionEnabled(ITickSource tickSource, bool autoCorrectionEnabled)
+ {
+ ResultCode result = ApplyAutomaticCorrection(tickSource, autoCorrectionEnabled);
+
+ if (result == ResultCode.Success)
+ {
+ _autoCorrectionEnabled = autoCorrectionEnabled;
+ }
+
+ return result;
+ }
+
+ public bool IsAutomaticCorrectionEnabled()
+ {
+ return _autoCorrectionEnabled;
+ }
+
+ public KReadableEvent GetAutomaticCorrectionReadableEvent()
+ {
+ return _autoCorrectionEvent.ReadableEvent;
+ }
+
+ public void SetAutomaticCorrectionUpdatedTime(SteadyClockTimePoint steadyClockTimePoint)
+ {
+ _autoCorrectionTime = steadyClockTimePoint;
+ }
+
+ public SteadyClockTimePoint GetAutomaticCorrectionUpdatedTime()
+ {
+ return _autoCorrectionTime;
+ }
+
+ public void SignalAutomaticCorrectionEvent()
+ {
+ _autoCorrectionEvent.WritableEvent.Signal();
+ }
+ }
+}
diff --git a/src/Ryujinx.HLE/HOS/Services/Time/Clock/SteadyClockCore.cs b/src/Ryujinx.HLE/HOS/Services/Time/Clock/SteadyClockCore.cs
new file mode 100644
index 00000000..18da4ed3
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Services/Time/Clock/SteadyClockCore.cs
@@ -0,0 +1,98 @@
+using Ryujinx.Common.Utilities;
+using Ryujinx.Cpu;
+using System;
+
+namespace Ryujinx.HLE.HOS.Services.Time.Clock
+{
+ abstract class SteadyClockCore
+ {
+ private UInt128 _clockSourceId;
+ private bool _isRtcResetDetected;
+ private bool _isInitialized;
+
+ public SteadyClockCore()
+ {
+ _clockSourceId = UInt128Utils.CreateRandom();
+ _isRtcResetDetected = false;
+ _isInitialized = false;
+ }
+
+ public UInt128 GetClockSourceId()
+ {
+ return _clockSourceId;
+ }
+
+ public void SetClockSourceId(UInt128 clockSourceId)
+ {
+ _clockSourceId = clockSourceId;
+ }
+
+ public void SetRtcReset()
+ {
+ _isRtcResetDetected = true;
+ }
+
+ public virtual TimeSpanType GetTestOffset()
+ {
+ return new TimeSpanType(0);
+ }
+
+ public virtual void SetTestOffset(TimeSpanType testOffset) {}
+
+ public ResultCode GetRtcValue(out ulong rtcValue)
+ {
+ rtcValue = 0;
+
+ return ResultCode.NotImplemented;
+ }
+
+ public bool IsRtcResetDetected()
+ {
+ return _isRtcResetDetected;
+ }
+
+ public ResultCode GetSetupResultValue()
+ {
+ return ResultCode.Success;
+ }
+
+ public virtual TimeSpanType GetInternalOffset()
+ {
+ return new TimeSpanType(0);
+ }
+
+ public virtual void SetInternalOffset(TimeSpanType internalOffset) {}
+
+ public virtual SteadyClockTimePoint GetTimePoint(ITickSource tickSource)
+ {
+ throw new NotImplementedException();
+ }
+
+ public virtual TimeSpanType GetCurrentRawTimePoint(ITickSource tickSource)
+ {
+ SteadyClockTimePoint timePoint = GetTimePoint(tickSource);
+
+ return TimeSpanType.FromSeconds(timePoint.TimePoint);
+ }
+
+ public SteadyClockTimePoint GetCurrentTimePoint(ITickSource tickSource)
+ {
+ SteadyClockTimePoint result = GetTimePoint(tickSource);
+
+ result.TimePoint += GetTestOffset().ToSeconds();
+ result.TimePoint += GetInternalOffset().ToSeconds();
+
+ return result;
+ }
+
+ public bool IsInitialized()
+ {
+ return _isInitialized;
+ }
+
+ public void MarkInitialized()
+ {
+ _isInitialized = true;
+ }
+ }
+}
diff --git a/src/Ryujinx.HLE/HOS/Services/Time/Clock/SystemClockContextUpdateCallback.cs b/src/Ryujinx.HLE/HOS/Services/Time/Clock/SystemClockContextUpdateCallback.cs
new file mode 100644
index 00000000..6229f5ed
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Services/Time/Clock/SystemClockContextUpdateCallback.cs
@@ -0,0 +1,71 @@
+using Ryujinx.HLE.HOS.Kernel.Threading;
+using System.Collections.Generic;
+using System.Threading;
+
+namespace Ryujinx.HLE.HOS.Services.Time.Clock
+{
+ abstract class SystemClockContextUpdateCallback
+ {
+ private List<KWritableEvent> _operationEventList;
+ protected SystemClockContext _context;
+ private bool _hasContext;
+
+ public SystemClockContextUpdateCallback()
+ {
+ _operationEventList = new List<KWritableEvent>();
+ _context = new SystemClockContext();
+ _hasContext = false;
+ }
+
+ private bool NeedUpdate(SystemClockContext context)
+ {
+ if (_hasContext)
+ {
+ return _context.Offset != context.Offset || _context.SteadyTimePoint.ClockSourceId != context.SteadyTimePoint.ClockSourceId;
+ }
+
+ return true;
+ }
+
+ public void RegisterOperationEvent(KWritableEvent writableEvent)
+ {
+ Monitor.Enter(_operationEventList);
+ _operationEventList.Add(writableEvent);
+ Monitor.Exit(_operationEventList);
+ }
+
+ private void BroadcastOperationEvent()
+ {
+ Monitor.Enter(_operationEventList);
+
+ foreach (KWritableEvent e in _operationEventList)
+ {
+ e.Signal();
+ }
+
+ Monitor.Exit(_operationEventList);
+ }
+
+ protected abstract ResultCode Update();
+
+ public ResultCode Update(SystemClockContext context)
+ {
+ ResultCode result = ResultCode.Success;
+
+ if (NeedUpdate(context))
+ {
+ _context = context;
+ _hasContext = true;
+
+ result = Update();
+
+ if (result == ResultCode.Success)
+ {
+ BroadcastOperationEvent();
+ }
+ }
+
+ return result;
+ }
+ }
+}
diff --git a/src/Ryujinx.HLE/HOS/Services/Time/Clock/SystemClockCore.cs b/src/Ryujinx.HLE/HOS/Services/Time/Clock/SystemClockCore.cs
new file mode 100644
index 00000000..f4bbaa60
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Services/Time/Clock/SystemClockCore.cs
@@ -0,0 +1,144 @@
+using Ryujinx.Cpu;
+using Ryujinx.HLE.HOS.Kernel.Threading;
+
+namespace Ryujinx.HLE.HOS.Services.Time.Clock
+{
+ abstract class SystemClockCore
+ {
+ private SteadyClockCore _steadyClockCore;
+ private SystemClockContext _context;
+ private bool _isInitialized;
+ private SystemClockContextUpdateCallback _systemClockContextUpdateCallback;
+
+ public SystemClockCore(SteadyClockCore steadyClockCore)
+ {
+ _steadyClockCore = steadyClockCore;
+ _context = new SystemClockContext();
+ _isInitialized = false;
+
+ _context.SteadyTimePoint.ClockSourceId = steadyClockCore.GetClockSourceId();
+ _systemClockContextUpdateCallback = null;
+ }
+
+ public virtual SteadyClockCore GetSteadyClockCore()
+ {
+ return _steadyClockCore;
+ }
+
+ public ResultCode GetCurrentTime(ITickSource tickSource, out long posixTime)
+ {
+ posixTime = 0;
+
+ SteadyClockTimePoint currentTimePoint = _steadyClockCore.GetCurrentTimePoint(tickSource);
+
+ ResultCode result = GetClockContext(tickSource, out SystemClockContext clockContext);
+
+ if (result == ResultCode.Success)
+ {
+ result = ResultCode.TimeMismatch;
+
+ if (currentTimePoint.ClockSourceId == clockContext.SteadyTimePoint.ClockSourceId)
+ {
+ posixTime = clockContext.Offset + currentTimePoint.TimePoint;
+
+ result = 0;
+ }
+ }
+
+ return result;
+ }
+
+ public ResultCode SetCurrentTime(ITickSource tickSource, long posixTime)
+ {
+ SteadyClockTimePoint currentTimePoint = _steadyClockCore.GetCurrentTimePoint(tickSource);
+
+ SystemClockContext clockContext = new SystemClockContext()
+ {
+ Offset = posixTime - currentTimePoint.TimePoint,
+ SteadyTimePoint = currentTimePoint
+ };
+
+ ResultCode result = SetClockContext(clockContext);
+
+ if (result == ResultCode.Success)
+ {
+ result = Flush(clockContext);
+ }
+
+ return result;
+ }
+
+ public virtual ResultCode GetClockContext(ITickSource tickSource, out SystemClockContext context)
+ {
+ context = _context;
+
+ return ResultCode.Success;
+ }
+
+ public virtual ResultCode SetClockContext(SystemClockContext context)
+ {
+ _context = context;
+
+ return ResultCode.Success;
+ }
+
+ protected virtual ResultCode Flush(SystemClockContext context)
+ {
+ if (_systemClockContextUpdateCallback == null)
+ {
+ return ResultCode.Success;
+ }
+
+ return _systemClockContextUpdateCallback.Update(context);
+ }
+
+ public void SetUpdateCallbackInstance(SystemClockContextUpdateCallback systemClockContextUpdateCallback)
+ {
+ _systemClockContextUpdateCallback = systemClockContextUpdateCallback;
+ }
+
+ public void RegisterOperationEvent(KWritableEvent writableEvent)
+ {
+ if (_systemClockContextUpdateCallback != null)
+ {
+ _systemClockContextUpdateCallback.RegisterOperationEvent(writableEvent);
+ }
+ }
+
+ public ResultCode SetSystemClockContext(SystemClockContext context)
+ {
+ ResultCode result = SetClockContext(context);
+
+ if (result == ResultCode.Success)
+ {
+ result = Flush(context);
+ }
+
+ return result;
+ }
+
+ public bool IsInitialized()
+ {
+ return _isInitialized;
+ }
+
+ public void MarkInitialized()
+ {
+ _isInitialized = true;
+ }
+
+ public bool IsClockSetup(ITickSource tickSource)
+ {
+ ResultCode result = GetClockContext(tickSource, out SystemClockContext context);
+
+ if (result == ResultCode.Success)
+ {
+ SteadyClockTimePoint steadyClockTimePoint = _steadyClockCore.GetCurrentTimePoint(tickSource);
+
+ return steadyClockTimePoint.ClockSourceId == context.SteadyTimePoint.ClockSourceId;
+ }
+
+ return false;
+ }
+ }
+}
diff --git a/src/Ryujinx.HLE/HOS/Services/Time/Clock/TickBasedSteadyClockCore.cs b/src/Ryujinx.HLE/HOS/Services/Time/Clock/TickBasedSteadyClockCore.cs
new file mode 100644
index 00000000..fe74da7e
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Services/Time/Clock/TickBasedSteadyClockCore.cs
@@ -0,0 +1,24 @@
+using Ryujinx.Cpu;
+
+namespace Ryujinx.HLE.HOS.Services.Time.Clock
+{
+ class TickBasedSteadyClockCore : SteadyClockCore
+ {
+ public TickBasedSteadyClockCore() {}
+
+ public override SteadyClockTimePoint GetTimePoint(ITickSource tickSource)
+ {
+ SteadyClockTimePoint result = new SteadyClockTimePoint
+ {
+ TimePoint = 0,
+ ClockSourceId = GetClockSourceId()
+ };
+
+ TimeSpanType ticksTimeSpan = TimeSpanType.FromTicks(tickSource.Counter, tickSource.Frequency);
+
+ result.TimePoint = ticksTimeSpan.ToSeconds();
+
+ return result;
+ }
+ }
+}
diff --git a/src/Ryujinx.HLE/HOS/Services/Time/Clock/Types/ClockSnapshot.cs b/src/Ryujinx.HLE/HOS/Services/Time/Clock/Types/ClockSnapshot.cs
new file mode 100644
index 00000000..07c1b405
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Services/Time/Clock/Types/ClockSnapshot.cs
@@ -0,0 +1,50 @@
+using Ryujinx.HLE.HOS.Services.Time.TimeZone;
+using System;
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.HLE.HOS.Services.Time.Clock
+{
+ [StructLayout(LayoutKind.Sequential, Size = 0xD0)]
+ struct ClockSnapshot
+ {
+ public SystemClockContext UserContext;
+ public SystemClockContext NetworkContext;
+ public long UserTime;
+ public long NetworkTime;
+ public CalendarTime UserCalendarTime;
+ public CalendarTime NetworkCalendarTime;
+ public CalendarAdditionalInfo UserCalendarAdditionalTime;
+ public CalendarAdditionalInfo NetworkCalendarAdditionalTime;
+ public SteadyClockTimePoint SteadyClockTimePoint;
+
+ private LocationNameStorageHolder _locationName;
+
+ public Span<byte> LocationName => MemoryMarshal.Cast<LocationNameStorageHolder, byte>(MemoryMarshal.CreateSpan(ref _locationName, LocationNameStorageHolder.Size));
+
+ [MarshalAs(UnmanagedType.I1)]
+ public bool IsAutomaticCorrectionEnabled;
+ public byte Type;
+ public ushort Unknown;
+
+
+ [StructLayout(LayoutKind.Sequential, Pack = 1, Size = Size)]
+ private struct LocationNameStorageHolder
+ {
+ public const int Size = 0x24;
+ }
+
+ public static ResultCode GetCurrentTime(out long currentTime, SteadyClockTimePoint steadyClockTimePoint, SystemClockContext context)
+ {
+ currentTime = 0;
+
+ if (steadyClockTimePoint.ClockSourceId == context.SteadyTimePoint.ClockSourceId)
+ {
+ currentTime = steadyClockTimePoint.TimePoint + context.Offset;
+
+ return ResultCode.Success;
+ }
+
+ return ResultCode.TimeMismatch;
+ }
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.HLE/HOS/Services/Time/Clock/Types/SteadyClockTimePoint.cs b/src/Ryujinx.HLE/HOS/Services/Time/Clock/Types/SteadyClockTimePoint.cs
new file mode 100644
index 00000000..729e11b6
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Services/Time/Clock/Types/SteadyClockTimePoint.cs
@@ -0,0 +1,43 @@
+using Ryujinx.Common.Utilities;
+using System;
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.HLE.HOS.Services.Time.Clock
+{
+ [StructLayout(LayoutKind.Sequential, Pack = 1)]
+ struct SteadyClockTimePoint
+ {
+ public long TimePoint;
+ public UInt128 ClockSourceId;
+
+ public ResultCode GetSpanBetween(SteadyClockTimePoint other, out long outSpan)
+ {
+ outSpan = 0;
+
+ if (ClockSourceId == other.ClockSourceId)
+ {
+ try
+ {
+ outSpan = checked(other.TimePoint - TimePoint);
+
+ return ResultCode.Success;
+ }
+ catch (OverflowException)
+ {
+ return ResultCode.Overflow;
+ }
+ }
+
+ return ResultCode.Overflow;
+ }
+
+ public static SteadyClockTimePoint GetRandom()
+ {
+ return new SteadyClockTimePoint
+ {
+ TimePoint = 0,
+ ClockSourceId = UInt128Utils.CreateRandom()
+ };
+ }
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.HLE/HOS/Services/Time/Clock/Types/SystemClockContext.cs b/src/Ryujinx.HLE/HOS/Services/Time/Clock/Types/SystemClockContext.cs
new file mode 100644
index 00000000..6b589c28
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Services/Time/Clock/Types/SystemClockContext.cs
@@ -0,0 +1,11 @@
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.HLE.HOS.Services.Time.Clock
+{
+ [StructLayout(LayoutKind.Sequential, Pack = 1)]
+ struct SystemClockContext
+ {
+ public long Offset;
+ public SteadyClockTimePoint SteadyTimePoint;
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.HLE/HOS/Services/Time/Clock/Types/TimeSpanType.cs b/src/Ryujinx.HLE/HOS/Services/Time/Clock/Types/TimeSpanType.cs
new file mode 100644
index 00000000..0070193f
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Services/Time/Clock/Types/TimeSpanType.cs
@@ -0,0 +1,50 @@
+using System;
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.HLE.HOS.Services.Time.Clock
+{
+ [StructLayout(LayoutKind.Sequential)]
+ struct TimeSpanType
+ {
+ private const long NanoSecondsPerSecond = 1000000000;
+
+ public static readonly TimeSpanType Zero = new TimeSpanType(0);
+
+ public long NanoSeconds;
+
+ public TimeSpanType(long nanoSeconds)
+ {
+ NanoSeconds = nanoSeconds;
+ }
+
+ public long ToSeconds()
+ {
+ return NanoSeconds / NanoSecondsPerSecond;
+ }
+
+ public TimeSpanType AddSeconds(long seconds)
+ {
+ return new TimeSpanType(NanoSeconds + (seconds * NanoSecondsPerSecond));
+ }
+
+ public bool IsDaylightSavingTime()
+ {
+ return DateTime.UnixEpoch.AddSeconds(ToSeconds()).ToLocalTime().IsDaylightSavingTime();
+ }
+
+ public static TimeSpanType FromSeconds(long seconds)
+ {
+ return new TimeSpanType(seconds * NanoSecondsPerSecond);
+ }
+
+ public static TimeSpanType FromTimeSpan(TimeSpan timeSpan)
+ {
+ return new TimeSpanType((long)(timeSpan.TotalMilliseconds * 1000000));
+ }
+
+ public static TimeSpanType FromTicks(ulong ticks, ulong frequency)
+ {
+ return FromSeconds((long)ticks / (long)frequency);
+ }
+ }
+} \ No newline at end of file