aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorThomas Guillemard <me@thog.eu>2019-10-08 05:48:49 +0200
committerjduncanator <1518948+jduncanator@users.noreply.github.com>2019-10-08 14:48:49 +1100
commit1aba033ba7868d29f5f840c99ee11dd29d689972 (patch)
treea11692bbf3ba7d8f7d2780193356f112cfbb3356
parent16869402bf81716ba3d1410621a3b1847bee389c (diff)
Update time implementation to 9.0.0 (#783)
* Fix 9.0.0 related services bindings This was wrong because of a mistake on switchbrew. * Fix wronog cmdid for ISteadyClock::GetTestOffset/SetTestOffset * Update ClockCore logics to 9.0.0 Also apply 9.0.0 permissions and comment time:u, and time:a (as those are going to be moved) * Move every clocks instances + timezone to a global manager * Start implementing time:m Also prepare the skeleton of the shared memory * Implement SystemClockContextUpdateCallback and co * Update StaticService to 9.0.0 * Update ISystemClock to 9.0.0 * Rename IStaticService and add glue's IStaticService * Implement psc's ITimeZoneService * Integrate psc layer into glue for TimeZoneService * Rename TimeZoneManagerForPsc => TimeZoneManager * Use correct TimeZoneService interface for both StaticService implementations * Accurately implement time shared memory operations * Fix two critical flaws in TimeZone logic The first one was the month range being different fron Nintendo one (0-11 instead of 1-12) The other flaw was a bad incrementation order during days & months computation. * Follow Nintendo's abort logic for TimeManager * Avoid crashing when timezone sysarchive isn't present * Update Readme * Address comments * Correctly align fields in ISystemClock * Fix code style and some typos * Improve timezone system archive warning/error messages * Rearrange using definitions in Horizon.cs * Address comments
-rw-r--r--README.md2
-rw-r--r--Ryujinx.HLE/DeviceMemory.cs5
-rw-r--r--Ryujinx.HLE/Exceptions/InternalServiceException.cs9
-rw-r--r--Ryujinx.HLE/FileSystem/Content/ContentManager.cs4
-rw-r--r--Ryujinx.HLE/HOS/Horizon.cs31
-rw-r--r--Ryujinx.HLE/HOS/Services/Settings/NxSettings.cs1
-rw-r--r--Ryujinx.HLE/HOS/Services/Time/Clock/EphemeralNetworkSystemClockContextWriter.cs10
-rw-r--r--Ryujinx.HLE/HOS/Services/Time/Clock/EphemeralNetworkSystemClockCore.cs20
-rw-r--r--Ryujinx.HLE/HOS/Services/Time/Clock/LocalSystemClockContextWriter.cs19
-rw-r--r--Ryujinx.HLE/HOS/Services/Time/Clock/NetworkSystemClockContextWriter.cs19
-rw-r--r--Ryujinx.HLE/HOS/Services/Time/Clock/StandardLocalSystemClockCore.cs22
-rw-r--r--Ryujinx.HLE/HOS/Services/Time/Clock/StandardNetworkSystemClockCore.cs24
-rw-r--r--Ryujinx.HLE/HOS/Services/Time/Clock/StandardSteadyClockCore.cs83
-rw-r--r--Ryujinx.HLE/HOS/Services/Time/Clock/StandardUserSystemClockCore.cs60
-rw-r--r--Ryujinx.HLE/HOS/Services/Time/Clock/SteadyClockCore.cs44
-rw-r--r--Ryujinx.HLE/HOS/Services/Time/Clock/SystemClockContextUpdateCallback.cs72
-rw-r--r--Ryujinx.HLE/HOS/Services/Time/Clock/SystemClockCore.cs109
-rw-r--r--Ryujinx.HLE/HOS/Services/Time/Clock/TickBasedSteadyClockCore.cs29
-rw-r--r--Ryujinx.HLE/HOS/Services/Time/Clock/Types/SteadyClockTimePoint.cs11
-rw-r--r--Ryujinx.HLE/HOS/Services/Time/Clock/Types/TimeSpanType.cs2
-rw-r--r--Ryujinx.HLE/HOS/Services/Time/IPowerStateRequestHandler.cs2
-rw-r--r--Ryujinx.HLE/HOS/Services/Time/IStaticServiceForGlue.cs182
-rw-r--r--Ryujinx.HLE/HOS/Services/Time/IStaticServiceForPsc.cs (renamed from Ryujinx.HLE/HOS/Services/Time/IStaticService.cs)145
-rw-r--r--Ryujinx.HLE/HOS/Services/Time/ITimeServiceManager.cs219
-rw-r--r--Ryujinx.HLE/HOS/Services/Time/ResultCode.cs1
-rw-r--r--Ryujinx.HLE/HOS/Services/Time/StaticService/ISteadyClock.cs83
-rw-r--r--Ryujinx.HLE/HOS/Services/Time/StaticService/ISystemClock.cs91
-rw-r--r--Ryujinx.HLE/HOS/Services/Time/StaticService/ITimeZoneService.cs218
-rw-r--r--Ryujinx.HLE/HOS/Services/Time/StaticService/ITimeZoneServiceForGlue.cs142
-rw-r--r--Ryujinx.HLE/HOS/Services/Time/StaticService/ITimeZoneServiceForPsc.cs294
-rw-r--r--Ryujinx.HLE/HOS/Services/Time/TimeManager.cs184
-rw-r--r--Ryujinx.HLE/HOS/Services/Time/TimeSharedMemory.cs126
-rw-r--r--Ryujinx.HLE/HOS/Services/Time/TimeZone/TimeZone.cs8
-rw-r--r--Ryujinx.HLE/HOS/Services/Time/TimeZone/TimeZoneContentManager.cs191
-rw-r--r--Ryujinx.HLE/HOS/Services/Time/TimeZone/TimeZoneManager.cs349
-rw-r--r--Ryujinx.HLE/HOS/Services/Time/Types/SteadyClockContext.cs12
-rw-r--r--Ryujinx.HLE/HOS/Services/Time/Types/TimePermissions.cs13
37 files changed, 2161 insertions, 675 deletions
diff --git a/README.md b/README.md
index 426ee40a..be3d1fea 100644
--- a/README.md
+++ b/README.md
@@ -56,7 +56,7 @@ The latest automatic build for Windows, macOS, and Linux can be found on the [Of
- **System Titles**
- Some of our System Modules implementation require [System Data Archives](https://switchbrew.org/wiki/Title_list#System_Data_Archives).
+ Some of our System Modules implementation (like time) require [System Data Archives](https://switchbrew.org/wiki/Title_list#System_Data_Archives).
You can install them by mounting your nand partition using [HacDiskMount](https://switchtools.sshnuke.net/) and copy the content in `RyuFs/nand/system`.
- **Executables**
diff --git a/Ryujinx.HLE/DeviceMemory.cs b/Ryujinx.HLE/DeviceMemory.cs
index 0ead1747..38864bc2 100644
--- a/Ryujinx.HLE/DeviceMemory.cs
+++ b/Ryujinx.HLE/DeviceMemory.cs
@@ -59,6 +59,11 @@ namespace Ryujinx.HLE
return *((ulong*)(_ramPtr + position));
}
+ public unsafe T ReadStruct<T>(long position)
+ {
+ return Marshal.PtrToStructure<T>((IntPtr)(_ramPtr + position));
+ }
+
public void WriteSByte(long position, sbyte value)
{
WriteByte(position, (byte)value);
diff --git a/Ryujinx.HLE/Exceptions/InternalServiceException.cs b/Ryujinx.HLE/Exceptions/InternalServiceException.cs
new file mode 100644
index 00000000..b940c51c
--- /dev/null
+++ b/Ryujinx.HLE/Exceptions/InternalServiceException.cs
@@ -0,0 +1,9 @@
+using System;
+
+namespace Ryujinx.HLE.Exceptions
+{
+ class InternalServiceException: Exception
+ {
+ public InternalServiceException(string message) : base(message) { }
+ }
+}
diff --git a/Ryujinx.HLE/FileSystem/Content/ContentManager.cs b/Ryujinx.HLE/FileSystem/Content/ContentManager.cs
index fe6642c3..9ed2e142 100644
--- a/Ryujinx.HLE/FileSystem/Content/ContentManager.cs
+++ b/Ryujinx.HLE/FileSystem/Content/ContentManager.cs
@@ -1,6 +1,6 @@
using LibHac.Fs;
using LibHac.Fs.NcaUtils;
-using Ryujinx.HLE.HOS.Services.Time.TimeZone;
+using Ryujinx.HLE.HOS.Services.Time;
using Ryujinx.HLE.Utilities;
using System;
using System.Collections.Generic;
@@ -143,7 +143,7 @@ namespace Ryujinx.HLE.FileSystem.Content
}
}
- TimeZoneManager.Instance.Initialize(_device);
+ TimeManager.Instance.InitializeTimeZone(_device);
}
public void ClearEntry(long titleId, ContentType contentType, StorageId storageId)
diff --git a/Ryujinx.HLE/HOS/Horizon.cs b/Ryujinx.HLE/HOS/Horizon.cs
index 86b28320..80c9ef0c 100644
--- a/Ryujinx.HLE/HOS/Horizon.cs
+++ b/Ryujinx.HLE/HOS/Horizon.cs
@@ -8,12 +8,14 @@ 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.Pcv.Bpc;
using Ryujinx.HLE.HOS.Services.Settings;
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;
+using Ryujinx.HLE.Utilities;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
@@ -22,7 +24,8 @@ using System.Linq;
using System.Reflection;
using System.Threading;
-using NxStaticObject = Ryujinx.HLE.Loaders.Executables.NxStaticObject;
+using TimeServiceManager = Ryujinx.HLE.HOS.Services.Time.TimeManager;
+using NxStaticObject = Ryujinx.HLE.Loaders.Executables.NxStaticObject;
namespace Ryujinx.HLE.HOS
{
@@ -87,8 +90,6 @@ namespace Ryujinx.HLE.HOS
internal KSharedMemory HidSharedMem { get; private set; }
internal KSharedMemory FontSharedMem { get; private set; }
internal KSharedMemory IirsSharedMem { get; private set; }
- internal KSharedMemory TimeSharedMem { get; private set; }
-
internal SharedFontManager Font { get; private set; }
internal ContentManager ContentManager { get; private set; }
@@ -184,7 +185,10 @@ namespace Ryujinx.HLE.HOS
HidSharedMem = new KSharedMemory(this, hidPageList, 0, 0, MemoryPermission.Read);
FontSharedMem = new KSharedMemory(this, fontPageList, 0, 0, MemoryPermission.Read);
IirsSharedMem = new KSharedMemory(this, iirsPageList, 0, 0, MemoryPermission.Read);
- TimeSharedMem = new KSharedMemory(this, timePageList, 0, 0, MemoryPermission.Read);
+
+ KSharedMemory timeSharedMemory = new KSharedMemory(this, timePageList, 0, 0, MemoryPermission.Read);
+
+ TimeServiceManager.Instance.Initialize(device, this, timeSharedMemory, (long)(timePa - DramMemoryMap.DramBase), TimeSize);
AppletState = new AppletStateMgr(this);
@@ -200,17 +204,30 @@ namespace Ryujinx.HLE.HOS
ContentManager = new ContentManager(device);
- // TODO: use set:sys (and set external clock source id from settings)
+ // TODO: use set:sys (and get external clock source id from settings)
// TODO: use "time!standard_steady_clock_rtc_update_interval_minutes" and implement a worker thread to be accurate.
- StandardSteadyClockCore.Instance.ConfigureSetupValue();
+ UInt128 clockSourceId = new UInt128(Guid.NewGuid().ToByteArray());
+ IRtcManager.GetExternalRtcValue(out ulong rtcValue);
+
+ // We assume the rtc is system time.
+ TimeSpanType systemTime = TimeSpanType.FromSeconds((long)rtcValue);
+
+ // First init the standard steady clock
+ TimeServiceManager.Instance.SetupStandardSteadyClock(null, clockSourceId, systemTime, TimeSpanType.Zero, TimeSpanType.Zero, false);
+ TimeServiceManager.Instance.SetupStandardLocalSystemClock(null, new SystemClockContext(), systemTime.ToSeconds());
if (NxSettings.Settings.TryGetValue("time!standard_network_clock_sufficient_accuracy_minutes", out object standardNetworkClockSufficientAccuracyMinutes))
{
TimeSpanType standardNetworkClockSufficientAccuracy = new TimeSpanType((int)standardNetworkClockSufficientAccuracyMinutes * 60000000000);
- StandardNetworkSystemClockCore.Instance.SetStandardNetworkClockSufficientAccuracy(standardNetworkClockSufficientAccuracy);
+ TimeServiceManager.Instance.SetupStandardNetworkSystemClock(new SystemClockContext(), standardNetworkClockSufficientAccuracy);
}
+ TimeServiceManager.Instance.SetupStandardUserSystemClock(null, false, SteadyClockTimePoint.GetRandom());
+
+ // FIXME: TimeZone shoud be init here but it's actually done in ContentManager
+
+ TimeServiceManager.Instance.SetupEphemeralNetworkSystemClock();
}
public void LoadCart(string exeFsDir, string romFsFile = null)
diff --git a/Ryujinx.HLE/HOS/Services/Settings/NxSettings.cs b/Ryujinx.HLE/HOS/Services/Settings/NxSettings.cs
index b679005e..ca3853e8 100644
--- a/Ryujinx.HLE/HOS/Services/Settings/NxSettings.cs
+++ b/Ryujinx.HLE/HOS/Services/Settings/NxSettings.cs
@@ -1704,6 +1704,7 @@ namespace Ryujinx.HLE.HOS.Services.Settings
{ "time!standard_steady_clock_test_offset_minutes", 0 },
{ "time!standard_steady_clock_rtc_update_interval_minutes", 5 },
{ "time!standard_network_clock_sufficient_accuracy_minutes", 43200 },
+ { "time!standard_user_clock_initial_year", 2019 },
{ "usb!usb30_force_enabled", false },
{ "wlan_debug!skip_wlan_boot", false }
};
diff --git a/Ryujinx.HLE/HOS/Services/Time/Clock/EphemeralNetworkSystemClockContextWriter.cs b/Ryujinx.HLE/HOS/Services/Time/Clock/EphemeralNetworkSystemClockContextWriter.cs
new file mode 100644
index 00000000..14d3cb24
--- /dev/null
+++ b/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/Ryujinx.HLE/HOS/Services/Time/Clock/EphemeralNetworkSystemClockCore.cs b/Ryujinx.HLE/HOS/Services/Time/Clock/EphemeralNetworkSystemClockCore.cs
index 8e9073a4..003863e4 100644
--- a/Ryujinx.HLE/HOS/Services/Time/Clock/EphemeralNetworkSystemClockCore.cs
+++ b/Ryujinx.HLE/HOS/Services/Time/Clock/EphemeralNetworkSystemClockCore.cs
@@ -2,26 +2,6 @@
{
class EphemeralNetworkSystemClockCore : SystemClockCore
{
- private static EphemeralNetworkSystemClockCore _instance;
-
- public static EphemeralNetworkSystemClockCore Instance
- {
- get
- {
- if (_instance == null)
- {
- _instance = new EphemeralNetworkSystemClockCore(TickBasedSteadyClockCore.Instance);
- }
-
- return _instance;
- }
- }
-
public EphemeralNetworkSystemClockCore(SteadyClockCore steadyClockCore) : base(steadyClockCore) { }
-
- public override ResultCode Flush(SystemClockContext context)
- {
- return ResultCode.Success;
- }
}
}
diff --git a/Ryujinx.HLE/HOS/Services/Time/Clock/LocalSystemClockContextWriter.cs b/Ryujinx.HLE/HOS/Services/Time/Clock/LocalSystemClockContextWriter.cs
new file mode 100644
index 00000000..fb7ebdc5
--- /dev/null
+++ b/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/Ryujinx.HLE/HOS/Services/Time/Clock/NetworkSystemClockContextWriter.cs b/Ryujinx.HLE/HOS/Services/Time/Clock/NetworkSystemClockContextWriter.cs
new file mode 100644
index 00000000..36468ec1
--- /dev/null
+++ b/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/Ryujinx.HLE/HOS/Services/Time/Clock/StandardLocalSystemClockCore.cs b/Ryujinx.HLE/HOS/Services/Time/Clock/StandardLocalSystemClockCore.cs
index a1727976..20c334e8 100644
--- a/Ryujinx.HLE/HOS/Services/Time/Clock/StandardLocalSystemClockCore.cs
+++ b/Ryujinx.HLE/HOS/Services/Time/Clock/StandardLocalSystemClockCore.cs
@@ -2,28 +2,6 @@
{
class StandardLocalSystemClockCore : SystemClockCore
{
- private static StandardLocalSystemClockCore _instance;
-
- public static StandardLocalSystemClockCore Instance
- {
- get
- {
- if (_instance == null)
- {
- _instance = new StandardLocalSystemClockCore(StandardSteadyClockCore.Instance);
- }
-
- return _instance;
- }
- }
-
public StandardLocalSystemClockCore(StandardSteadyClockCore steadyClockCore) : base(steadyClockCore) {}
-
- public override ResultCode Flush(SystemClockContext context)
- {
- // TODO: set:sys SetUserSystemClockContext
-
- return ResultCode.Success;
- }
}
}
diff --git a/Ryujinx.HLE/HOS/Services/Time/Clock/StandardNetworkSystemClockCore.cs b/Ryujinx.HLE/HOS/Services/Time/Clock/StandardNetworkSystemClockCore.cs
index cc21dd9a..b86f703d 100644
--- a/Ryujinx.HLE/HOS/Services/Time/Clock/StandardNetworkSystemClockCore.cs
+++ b/Ryujinx.HLE/HOS/Services/Time/Clock/StandardNetworkSystemClockCore.cs
@@ -6,33 +6,11 @@ namespace Ryujinx.HLE.HOS.Services.Time.Clock
{
private TimeSpanType _standardNetworkClockSufficientAccuracy;
- private static StandardNetworkSystemClockCore _instance;
-
- public static StandardNetworkSystemClockCore Instance
- {
- get
- {
- if (_instance == null)
- {
- _instance = new StandardNetworkSystemClockCore(StandardSteadyClockCore.Instance);
- }
-
- return _instance;
- }
- }
-
public StandardNetworkSystemClockCore(StandardSteadyClockCore steadyClockCore) : base(steadyClockCore)
{
_standardNetworkClockSufficientAccuracy = new TimeSpanType(0);
}
- public override ResultCode Flush(SystemClockContext context)
- {
- // TODO: set:sys SetNetworkSystemClockContext
-
- return ResultCode.Success;
- }
-
public bool IsStandardNetworkSystemClockAccuracySufficient(KThread thread)
{
SteadyClockCore steadyClockCore = GetSteadyClockCore();
@@ -40,7 +18,7 @@ namespace Ryujinx.HLE.HOS.Services.Time.Clock
bool isStandardNetworkClockSufficientAccuracy = false;
- ResultCode result = GetSystemClockContext(thread, out SystemClockContext context);
+ ResultCode result = GetClockContext(thread, out SystemClockContext context);
if (result == ResultCode.Success && context.SteadyTimePoint.GetSpanBetween(currentTimePoint, out long outSpan) == ResultCode.Success)
{
diff --git a/Ryujinx.HLE/HOS/Services/Time/Clock/StandardSteadyClockCore.cs b/Ryujinx.HLE/HOS/Services/Time/Clock/StandardSteadyClockCore.cs
index 1bc5bee7..370e7d73 100644
--- a/Ryujinx.HLE/HOS/Services/Time/Clock/StandardSteadyClockCore.cs
+++ b/Ryujinx.HLE/HOS/Services/Time/Clock/StandardSteadyClockCore.cs
@@ -1,49 +1,30 @@
using Ryujinx.HLE.HOS.Kernel.Threading;
-using Ryujinx.HLE.HOS.Services.Pcv.Bpc;
namespace Ryujinx.HLE.HOS.Services.Time.Clock
{
class StandardSteadyClockCore : SteadyClockCore
{
- private long _setupValue;
- private ResultCode _setupResultCode;
- private bool _isRtcResetDetected;
+ private TimeSpanType _setupValue;
private TimeSpanType _testOffset;
private TimeSpanType _internalOffset;
+ private TimeSpanType _cachedRawTimePoint;
- private static StandardSteadyClockCore _instance;
-
- public static StandardSteadyClockCore Instance
- {
- get
- {
- if (_instance == null)
- {
- _instance = new StandardSteadyClockCore();
- }
-
- return _instance;
- }
- }
-
- private StandardSteadyClockCore()
+ public StandardSteadyClockCore()
{
- _testOffset = new TimeSpanType(0);
- _internalOffset = new TimeSpanType(0);
+ _setupValue = TimeSpanType.Zero;
+ _testOffset = TimeSpanType.Zero;
+ _internalOffset = TimeSpanType.Zero;
+ _cachedRawTimePoint = TimeSpanType.Zero;
}
public override SteadyClockTimePoint GetTimePoint(KThread thread)
{
SteadyClockTimePoint result = new SteadyClockTimePoint
{
- TimePoint = 0,
+ TimePoint = GetCurrentRawTimePoint(thread).ToSeconds(),
ClockSourceId = GetClockSourceId()
};
- TimeSpanType ticksTimeSpan = TimeSpanType.FromTicks(thread.Context.CntpctEl0, thread.Context.CntfrqEl0);
-
- result.TimePoint = _setupValue + ticksTimeSpan.ToSeconds();
-
return result;
}
@@ -57,16 +38,6 @@ namespace Ryujinx.HLE.HOS.Services.Time.Clock
_testOffset = testOffset;
}
- public override ResultCode GetRtcValue(out ulong rtcValue)
- {
- return (ResultCode)IRtcManager.GetExternalRtcValue(out rtcValue);
- }
-
- public bool IsRtcResetDetected()
- {
- return _isRtcResetDetected;
- }
-
public override TimeSpanType GetInternalOffset()
{
return _internalOffset;
@@ -77,31 +48,35 @@ namespace Ryujinx.HLE.HOS.Services.Time.Clock
_internalOffset = internalOffset;
}
- public override ResultCode GetSetupResultValue()
+ public override TimeSpanType GetCurrentRawTimePoint(KThread thread)
{
- return _setupResultCode;
- }
+ TimeSpanType ticksTimeSpan;
- public void ConfigureSetupValue()
- {
- int retry = 0;
+ // As this may be called before the guest code, we support passing a null thread to make this api usable.
+ if (thread == null)
+ {
+ ticksTimeSpan = TimeSpanType.FromSeconds(0);
+ }
+ else
+ {
+ ticksTimeSpan = TimeSpanType.FromTicks(thread.Context.CntpctEl0, thread.Context.CntfrqEl0);
+ }
- ResultCode result = ResultCode.Success;
+ TimeSpanType rawTimePoint = new TimeSpanType(_setupValue.NanoSeconds + ticksTimeSpan.NanoSeconds);
- while (retry < 20)
+ if (rawTimePoint.NanoSeconds < _cachedRawTimePoint.NanoSeconds)
{
- result = (ResultCode)IRtcManager.GetExternalRtcValue(out ulong rtcValue);
+ rawTimePoint.NanoSeconds = _cachedRawTimePoint.NanoSeconds;
+ }
- if (result == ResultCode.Success)
- {
- _setupValue = (long)rtcValue;
- break;
- }
+ _cachedRawTimePoint = rawTimePoint;
- retry++;
- }
+ return rawTimePoint;
+ }
- _setupResultCode = result;
+ public void SetSetupValue(TimeSpanType setupValue)
+ {
+ _setupValue = setupValue;
}
}
}
diff --git a/Ryujinx.HLE/HOS/Services/Time/Clock/StandardUserSystemClockCore.cs b/Ryujinx.HLE/HOS/Services/Time/Clock/StandardUserSystemClockCore.cs
index c98b0064..42bc05fa 100644
--- a/Ryujinx.HLE/HOS/Services/Time/Clock/StandardUserSystemClockCore.cs
+++ b/Ryujinx.HLE/HOS/Services/Time/Clock/StandardUserSystemClockCore.cs
@@ -1,4 +1,5 @@
using Ryujinx.HLE.HOS.Kernel.Threading;
+using System;
namespace Ryujinx.HLE.HOS.Services.Time.Clock
{
@@ -7,35 +8,25 @@ namespace Ryujinx.HLE.HOS.Services.Time.Clock
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;
- }
- }
+ 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;
}
- public override ResultCode Flush(SystemClockContext context)
+ protected override ResultCode Flush(SystemClockContext context)
{
- return ResultCode.NotImplemented;
+ // As UserSystemClock isn't a real system clock, this shouldn't happens.
+ throw new NotImplementedException();
}
- public override ResultCode GetSystemClockContext(KThread thread, out SystemClockContext context)
+ public override ResultCode GetClockContext(KThread thread, out SystemClockContext context)
{
ResultCode result = ApplyAutomaticCorrection(thread, false);
@@ -43,13 +34,13 @@ namespace Ryujinx.HLE.HOS.Services.Time.Clock
if (result == ResultCode.Success)
{
- return _localSystemClockCore.GetSystemClockContext(thread, out context);
+ return _localSystemClockCore.GetClockContext(thread, out context);
}
return result;
}
- public override ResultCode SetSystemClockContext(SystemClockContext context)
+ public override ResultCode SetClockContext(SystemClockContext context)
{
return ResultCode.NotImplemented;
}
@@ -60,17 +51,22 @@ namespace Ryujinx.HLE.HOS.Services.Time.Clock
if (_autoCorrectionEnabled != autoCorrectionEnabled && _networkSystemClockCore.IsClockSetup(thread))
{
- result = _networkSystemClockCore.GetSystemClockContext(thread, out SystemClockContext context);
+ result = _networkSystemClockCore.GetClockContext(thread, out SystemClockContext context);
if (result == ResultCode.Success)
{
- _localSystemClockCore.SetSystemClockContext(context);
+ _localSystemClockCore.SetClockContext(context);
}
}
return result;
}
+ internal void CreateAutomaticCorrectionEvent(Horizon system)
+ {
+ _autoCorrectionEvent = new KEvent(system);
+ }
+
public ResultCode SetAutomaticCorrectionEnabled(KThread thread, bool autoCorrectionEnabled)
{
ResultCode result = ApplyAutomaticCorrection(thread, autoCorrectionEnabled);
@@ -87,5 +83,25 @@ namespace Ryujinx.HLE.HOS.Services.Time.Clock
{
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/Ryujinx.HLE/HOS/Services/Time/Clock/SteadyClockCore.cs b/Ryujinx.HLE/HOS/Services/Time/Clock/SteadyClockCore.cs
index 54d9accf..83ace981 100644
--- a/Ryujinx.HLE/HOS/Services/Time/Clock/SteadyClockCore.cs
+++ b/Ryujinx.HLE/HOS/Services/Time/Clock/SteadyClockCore.cs
@@ -7,10 +7,14 @@ namespace Ryujinx.HLE.HOS.Services.Time.Clock
abstract class SteadyClockCore
{
private UInt128 _clockSourceId;
+ private bool _isRtcResetDetected;
+ private bool _isInitialized;
public SteadyClockCore()
{
- _clockSourceId = new UInt128(Guid.NewGuid().ToByteArray());
+ _clockSourceId = new UInt128(Guid.NewGuid().ToByteArray());
+ _isRtcResetDetected = false;
+ _isInitialized = false;
}
public UInt128 GetClockSourceId()
@@ -18,6 +22,16 @@ namespace Ryujinx.HLE.HOS.Services.Time.Clock
return _clockSourceId;
}
+ public void SetClockSourceId(UInt128 clockSourceId)
+ {
+ _clockSourceId = clockSourceId;
+ }
+
+ public void SetRtcReset()
+ {
+ _isRtcResetDetected = true;
+ }
+
public virtual TimeSpanType GetTestOffset()
{
return new TimeSpanType(0);
@@ -25,16 +39,21 @@ namespace Ryujinx.HLE.HOS.Services.Time.Clock
public virtual void SetTestOffset(TimeSpanType testOffset) {}
- public virtual ResultCode GetRtcValue(out ulong rtcValue)
+ public ResultCode GetRtcValue(out ulong rtcValue)
{
rtcValue = 0;
return ResultCode.NotImplemented;
}
- public virtual ResultCode GetSetupResultValue()
+ public bool IsRtcResetDetected()
{
- return ResultCode.NotImplemented;
+ return _isRtcResetDetected;
+ }
+
+ public ResultCode GetSetupResultValue()
+ {
+ return ResultCode.Success;
}
public virtual TimeSpanType GetInternalOffset()
@@ -49,6 +68,13 @@ namespace Ryujinx.HLE.HOS.Services.Time.Clock
throw new NotImplementedException();
}
+ public virtual TimeSpanType GetCurrentRawTimePoint(KThread thread)
+ {
+ SteadyClockTimePoint timePoint = GetTimePoint(thread);
+
+ return TimeSpanType.FromSeconds(timePoint.TimePoint);
+ }
+
public SteadyClockTimePoint GetCurrentTimePoint(KThread thread)
{
SteadyClockTimePoint result = GetTimePoint(thread);
@@ -58,5 +84,15 @@ namespace Ryujinx.HLE.HOS.Services.Time.Clock
return result;
}
+
+ public bool IsInitialized()
+ {
+ return _isInitialized;
+ }
+
+ public void MarkInitialized()
+ {
+ _isInitialized = true;
+ }
}
}
diff --git a/Ryujinx.HLE/HOS/Services/Time/Clock/SystemClockContextUpdateCallback.cs b/Ryujinx.HLE/HOS/Services/Time/Clock/SystemClockContextUpdateCallback.cs
new file mode 100644
index 00000000..629d8ee1
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Services/Time/Clock/SystemClockContextUpdateCallback.cs
@@ -0,0 +1,72 @@
+using Ryujinx.HLE.HOS.Kernel.Threading;
+using System;
+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/Ryujinx.HLE/HOS/Services/Time/Clock/SystemClockCore.cs b/Ryujinx.HLE/HOS/Services/Time/Clock/SystemClockCore.cs
index 52f3c908..865b1c09 100644
--- a/Ryujinx.HLE/HOS/Services/Time/Clock/SystemClockCore.cs
+++ b/Ryujinx.HLE/HOS/Services/Time/Clock/SystemClockCore.cs
@@ -1,18 +1,23 @@
-using Ryujinx.HLE.HOS.Kernel.Threading;
+using System;
+using Ryujinx.HLE.HOS.Kernel.Threading;
namespace Ryujinx.HLE.HOS.Services.Time.Clock
{
abstract class SystemClockCore
{
- private SteadyClockCore _steadyClockCore;
- private SystemClockContext _context;
+ 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()
@@ -20,31 +25,115 @@ namespace Ryujinx.HLE.HOS.Services.Time.Clock
return _steadyClockCore;
}
- public virtual ResultCode GetSystemClockContext(KThread thread, out SystemClockContext context)
+ public ResultCode GetCurrentTime(KThread thread, out long posixTime)
+ {
+ posixTime = 0;
+
+ SteadyClockTimePoint currentTimePoint = _steadyClockCore.GetCurrentTimePoint(thread);
+
+ ResultCode result = GetClockContext(thread, 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(KThread thread, long posixTime)
+ {
+ SteadyClockTimePoint currentTimePoint = _steadyClockCore.GetCurrentTimePoint(thread);
+
+ 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(KThread thread, out SystemClockContext context)
{
context = _context;
return ResultCode.Success;
}
- public virtual ResultCode SetSystemClockContext(SystemClockContext context)
+ public virtual ResultCode SetClockContext(SystemClockContext context)
{
_context = context;
return ResultCode.Success;
}
- public abstract ResultCode Flush(SystemClockContext context);
+ protected virtual ResultCode Flush(SystemClockContext context)
+ {
+ if (_systemClockContextUpdateCallback == null)
+ {
+ return ResultCode.Success;
+ }
- public bool IsClockSetup(KThread thread)
+ return _systemClockContextUpdateCallback.Update(context);
+ }
+
+ public void SetUpdateCallbackInstance(SystemClockContextUpdateCallback systemClockContextUpdateCallback)
{
- ResultCode result = GetSystemClockContext(thread, out SystemClockContext context);
+ _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)
{
- SteadyClockCore steadyClockCore = GetSteadyClockCore();
+ result = Flush(context);
+ }
- SteadyClockTimePoint steadyClockTimePoint = steadyClockCore.GetCurrentTimePoint(thread);
+ return result;
+ }
+
+ public bool IsInitialized()
+ {
+ return _isInitialized;
+ }
+
+ public void MarkInitialized()
+ {
+ _isInitialized = true;
+ }
+
+ public bool IsClockSetup(KThread thread)
+ {
+ ResultCode result = GetClockContext(thread, out SystemClockContext context);
+
+ if (result == ResultCode.Success)
+ {
+ SteadyClockTimePoint steadyClockTimePoint = _steadyClockCore.GetCurrentTimePoint(thread);
return steadyClockTimePoint.ClockSourceId == context.SteadyTimePoint.ClockSourceId;
}
diff --git a/Ryujinx.HLE/HOS/Services/Time/Clock/TickBasedSteadyClockCore.cs b/Ryujinx.HLE/HOS/Services/Time/Clock/TickBasedSteadyClockCore.cs
index e5baba25..06502082 100644
--- a/Ryujinx.HLE/HOS/Services/Time/Clock/TickBasedSteadyClockCore.cs
+++ b/Ryujinx.HLE/HOS/Services/Time/Clock/TickBasedSteadyClockCore.cs
@@ -4,22 +4,7 @@ namespace Ryujinx.HLE.HOS.Services.Time.Clock
{
class TickBasedSteadyClockCore : SteadyClockCore
{
- private static TickBasedSteadyClockCore _instance;
-
- public static TickBasedSteadyClockCore Instance
- {
- get
- {
- if (_instance == null)
- {
- _instance = new TickBasedSteadyClockCore();
- }
-
- return _instance;
- }
- }
-
- private TickBasedSteadyClockCore() {}
+ public TickBasedSteadyClockCore() {}
public override SteadyClockTimePoint GetTimePoint(KThread thread)
{
@@ -29,7 +14,17 @@ namespace Ryujinx.HLE.HOS.Services.Time.Clock
ClockSourceId = GetClockSourceId()
};
- TimeSpanType ticksTimeSpan = TimeSpanType.FromTicks(thread.Context.CntpctEl0, thread.Context.CntfrqEl0);
+ TimeSpanType ticksTimeSpan;
+
+ // As this may be called before the guest code, we support passing a null thread to make this api usable.
+ if (thread == null)
+ {
+ ticksTimeSpan = TimeSpanType.FromSeconds(0);
+ }
+ else
+ {
+ ticksTimeSpan = TimeSpanType.FromTicks(thread.Context.CntpctEl0, thread.Context.CntfrqEl0);
+ }
result.TimePoint = ticksTimeSpan.ToSeconds();
diff --git a/Ryujinx.HLE/HOS/Services/Time/Clock/Types/SteadyClockTimePoint.cs b/Ryujinx.HLE/HOS/Services/Time/Clock/Types/SteadyClockTimePoint.cs
index 0055b5ea..71fb4521 100644
--- a/Ryujinx.HLE/HOS/Services/Time/Clock/Types/SteadyClockTimePoint.cs
+++ b/Ryujinx.HLE/HOS/Services/Time/Clock/Types/SteadyClockTimePoint.cs
@@ -7,7 +7,7 @@ namespace Ryujinx.HLE.HOS.Services.Time.Clock
[StructLayout(LayoutKind.Sequential)]
struct SteadyClockTimePoint
{
- public long TimePoint;
+ public long TimePoint;
public UInt128 ClockSourceId;
public ResultCode GetSpanBetween(SteadyClockTimePoint other, out long outSpan)
@@ -30,5 +30,14 @@ namespace Ryujinx.HLE.HOS.Services.Time.Clock
return ResultCode.Overflow;
}
+
+ public static SteadyClockTimePoint GetRandom()
+ {
+ return new SteadyClockTimePoint
+ {
+ TimePoint = 0,
+ ClockSourceId = new UInt128(Guid.NewGuid().ToByteArray())
+ };
+ }
}
} \ No newline at end of file
diff --git a/Ryujinx.HLE/HOS/Services/Time/Clock/Types/TimeSpanType.cs b/Ryujinx.HLE/HOS/Services/Time/Clock/Types/TimeSpanType.cs
index 93579709..c336ad41 100644
--- a/Ryujinx.HLE/HOS/Services/Time/Clock/Types/TimeSpanType.cs
+++ b/Ryujinx.HLE/HOS/Services/Time/Clock/Types/TimeSpanType.cs
@@ -7,6 +7,8 @@ namespace Ryujinx.HLE.HOS.Services.Time.Clock
{
private const long NanoSecondsPerSecond = 1000000000;
+ public static readonly TimeSpanType Zero = new TimeSpanType(0);
+
public long NanoSeconds;
public TimeSpanType(long nanoSeconds)
diff --git a/Ryujinx.HLE/HOS/Services/Time/IPowerStateRequestHandler.cs b/Ryujinx.HLE/HOS/Services/Time/IPowerStateRequestHandler.cs
index cb10da47..8ec55c15 100644
--- a/Ryujinx.HLE/HOS/Services/Time/IPowerStateRequestHandler.cs
+++ b/Ryujinx.HLE/HOS/Services/Time/IPowerStateRequestHandler.cs
@@ -1,6 +1,6 @@
namespace Ryujinx.HLE.HOS.Services.Time
{
- [Service("time:m")] // 9.0.0+
+ [Service("time:p")] // 9.0.0+
class IPowerStateRequestHandler : IpcService
{
public IPowerStateRequestHandler(ServiceCtx context) { }
diff --git a/Ryujinx.HLE/HOS/Services/Time/IStaticServiceForGlue.cs b/Ryujinx.HLE/HOS/Services/Time/IStaticServiceForGlue.cs
new file mode 100644
index 00000000..605cbbbd
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Services/Time/IStaticServiceForGlue.cs
@@ -0,0 +1,182 @@
+using Ryujinx.Common;
+using Ryujinx.HLE.HOS.Services.Pcv.Bpc;
+using Ryujinx.HLE.HOS.Services.Settings;
+using Ryujinx.HLE.HOS.Services.Time.Clock;
+using Ryujinx.HLE.HOS.Services.Time.StaticService;
+using System;
+
+namespace Ryujinx.HLE.HOS.Services.Time
+{
+ [Service("time:a", TimePermissions.Admin)]
+ [Service("time:r", TimePermissions.Repair)]
+ [Service("time:u", TimePermissions.User)]
+ class IStaticServiceForGlue : IpcService
+ {
+ private IStaticServiceForPsc _inner;
+ private TimePermissions _permissions;
+
+ public IStaticServiceForGlue(ServiceCtx context, TimePermissions permissions)
+ {
+ _permissions = permissions;
+ _inner = new IStaticServiceForPsc(context, permissions);
+ }
+
+ [Command(0)]
+ // GetStandardUserSystemClock() -> object<nn::timesrv::detail::service::ISystemClock>
+ public ResultCode GetStandardUserSystemClock(ServiceCtx context)
+ {
+ return _inner.GetStandardUserSystemClock(context);
+ }
+
+ [Command(1)]
+ // GetStandardNetworkSystemClock() -> object<nn::timesrv::detail::service::ISystemClock>
+ public ResultCode GetStandardNetworkSystemClock(ServiceCtx context)
+ {
+ return _inner.GetStandardNetworkSystemClock(context);
+ }
+
+ [Command(2)]
+ // GetStandardSteadyClock() -> object<nn::timesrv::detail::service::ISteadyClock>
+ public ResultCode GetStandardSteadyClock(ServiceCtx context)
+ {
+ return _inner.GetStandardSteadyClock(context);
+ }
+
+ [Command(3)]
+ // GetTimeZoneService() -> object<nn::timesrv::detail::service::ITimeZoneService>
+ public ResultCode GetTimeZoneService(ServiceCtx context)
+ {
+ MakeObject(context, new ITimeZoneServiceForGlue(TimeManager.Instance.TimeZone, (_permissions & TimePermissions.TimeZoneWritableMask) != 0));
+
+ return ResultCode.Success;
+ }
+
+ [Command(4)]
+ // GetStandardLocalSystemClock() -> object<nn::timesrv::detail::service::ISystemClock>
+ public ResultCode GetStandardLocalSystemClock(ServiceCtx context)
+ {
+ return _inner.GetStandardLocalSystemClock(context);
+ }
+
+ [Command(5)] // 4.0.0+
+ // GetEphemeralNetworkSystemClock() -> object<nn::timesrv::detail::service::ISystemClock>
+ public ResultCode GetEphemeralNetworkSystemClock(ServiceCtx context)
+ {
+ return _inner.GetEphemeralNetworkSystemClock(context);
+ }
+
+ [Command(20)] // 6.0.0+
+ // GetSharedMemoryNativeHandle() -> handle<copy>
+ public ResultCode GetSharedMemoryNativeHandle(ServiceCtx context)
+ {
+ return _inner.GetSharedMemoryNativeHandle(context);
+ }
+
+ [Command(50)] // 4.0.0+
+ // SetStandardSteadyClockInternalOffset(nn::TimeSpanType internal_offset)
+ public ResultCode SetStandardSteadyClockInternalOffset(ServiceCtx context)
+ {
+ if ((_permissions & TimePermissions.BypassUninitialized) == 0)
+ {
+ return ResultCode.PermissionDenied;
+ }
+
+ TimeSpanType internalOffset = context.RequestData.ReadStruct<TimeSpanType>();
+
+ // TODO: set:sys SetExternalSteadyClockInternalOffset(internalOffset.ToSeconds())
+
+ return ResultCode.Success;
+ }
+
+ [Command(51)] // 9.0.0+
+ // GetStandardSteadyClockRtcValue() -> u64
+ public ResultCode GetStandardSteadyClockRtcValue(ServiceCtx context)
+ {
+ ResultCode result = (ResultCode)IRtcManager.GetExternalRtcValue(out ulong rtcValue);
+
+ if (result == ResultCode.Success)
+ {
+ context.ResponseData.Write(rtcValue);
+ }
+
+ return result;
+ }
+
+ [Command(100)]
+ // IsStandardUserSystemClockAutomaticCorrectionEnabled() -> bool
+ public ResultCode IsStandardUserSystemClockAutomaticCorrectionEnabled(ServiceCtx context)
+ {
+ return _inner.IsStandardUserSystemClockAutomaticCorrectionEnabled(context);
+ }
+
+ [Command(101)]
+ // SetStandardUserSystemClockAutomaticCorrectionEnabled(b8)
+ public ResultCode SetStandardUserSystemClockAutomaticCorrectionEnabled(ServiceCtx context)
+ {
+ return _inner.SetStandardUserSystemClockAutomaticCorrectionEnabled(context);
+ }
+
+ [Command(102)] // 5.0.0+
+ // GetStandardUserSystemClockInitialYear() -> u32
+ public ResultCode GetStandardUserSystemClockInitialYear(ServiceCtx context)
+ {
+ if (!NxSettings.Settings.TryGetValue("time!standard_user_clock_initial_year", out object standardUserSystemClockInitialYear))
+ {
+ throw new InvalidOperationException("standard_user_clock_initial_year isn't defined in system settings!");
+ }
+
+ context.ResponseData.Write((int)standardUserSystemClockInitialYear);
+
+ return ResultCode.Success;
+ }
+
+ [Command(200)] // 3.0.0+
+ // IsStandardNetworkSystemClockAccuracySufficient() -> bool
+ public ResultCode IsStandardNetworkSystemClockAccuracySufficient(ServiceCtx context)
+ {
+ return _inner.IsStandardNetworkSystemClockAccuracySufficient(context);
+ }
+
+ [Command(201)] // 6.0.0+
+ // GetStandardUserSystemClockAutomaticCorrectionUpdatedTime() -> nn::time::SteadyClockTimePoint
+ public ResultCode GetStandardUserSystemClockAutomaticCorrectionUpdatedTime(ServiceCtx context)
+ {
+ return _inner.GetStandardUserSystemClockAutomaticCorrectionUpdatedTime(context);
+ }
+
+ [Command(300)] // 4.0.0+
+ // CalculateMonotonicSystemClockBaseTimePoint(nn::time::SystemClockContext) -> s64
+ public ResultCode CalculateMonotonicSystemClockBaseTimePoint(ServiceCtx context)
+ {
+ return _inner.CalculateMonotonicSystemClockBaseTimePoint(context);
+ }
+
+ [Command(400)] // 4.0.0+
+ // GetClockSnapshot(u8) -> buffer<nn::time::sf::ClockSnapshot, 0x1a>
+ public ResultCode GetClockSnapshot(ServiceCtx context)
+ {
+ return _inner.GetClockSnapshot(context);
+ }
+
+ [Command(401)] // 4.0.0+
+ // GetClockSnapshotFromSystemClockContext(u8, nn::time::SystemClockContext, nn::time::SystemClockContext) -> buffer<nn::time::sf::ClockSnapshot, 0x1a>
+ public ResultCode GetClockSnapshotFromSystemClockContext(ServiceCtx context)
+ {
+ return _inner.GetClockSnapshotFromSystemClockContext(context);
+ }
+
+ [Command(500)] // 4.0.0+
+ // CalculateStandardUserSystemClockDifferenceByUser(buffer<nn::time::sf::ClockSnapshot, 0x19>, buffer<nn::time::sf::ClockSnapshot, 0x19>) -> nn::TimeSpanType
+ public ResultCode CalculateStandardUserSystemClockDifferenceByUser(ServiceCtx context)
+ {
+ return _inner.CalculateStandardUserSystemClockDifferenceByUser(context);
+ }
+
+ [Command(501)] // 4.0.0+
+ // CalculateSpanBetween(buffer<nn::time::sf::ClockSnapshot, 0x19>, buffer<nn::time::sf::ClockSnapshot, 0x19>) -> nn::TimeSpanType
+ public ResultCode CalculateSpanBetween(ServiceCtx context)
+ {
+ return _inner.CalculateSpanBetween(context);
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.HLE/HOS/Services/Time/IStaticService.cs b/Ryujinx.HLE/HOS/Services/Time/IStaticServiceForPsc.cs
index 0cfdebcf..5ea3910f 100644
--- a/Ryujinx.HLE/HOS/Services/Time/IStaticService.cs
+++ b/Ryujinx.HLE/HOS/Services/Time/IStaticServiceForPsc.cs
@@ -12,28 +12,30 @@ using System.Runtime.InteropServices;
namespace Ryujinx.HLE.HOS.Services.Time
{
- [Service("time:a", TimePermissions.Applet)]
[Service("time:s", TimePermissions.System)]
- [Service("time:u", TimePermissions.User)]
- [Service("time:p", TimePermissions.System)] // 9.0.0+ - TODO: Fix the permission.
- class IStaticService : IpcService
+ [Service("time:su", TimePermissions.SystemUpdate)]
+ class IStaticServiceForPsc : IpcService
{
+ private TimeManager _timeManager;
private TimePermissions _permissions;
private int _timeSharedMemoryNativeHandle = 0;
- private static readonly DateTime StartupDate = DateTime.UtcNow;
+ public IStaticServiceForPsc(ServiceCtx context, TimePermissions permissions) : this(TimeManager.Instance, permissions) {}
- public IStaticService(ServiceCtx context, TimePermissions permissions)
+ public IStaticServiceForPsc(TimeManager manager, TimePermissions permissions)
{
_permissions = permissions;
+ _timeManager = manager;
}
[Command(0)]
// GetStandardUserSystemClock() -> object<nn::timesrv::detail::service::ISystemClock>
public ResultCode GetStandardUserSystemClock(ServiceCtx context)
{
- MakeObject(context, new ISystemClock(StandardUserSystemClockCore.Instance, (_permissions & TimePermissions.UserSystemClockWritableMask) != 0));
+ MakeObject(context, new ISystemClock(_timeManager.StandardUserSystemClock,
+ (_permissions & TimePermissions.UserSystemClockWritableMask) != 0,
+ (_permissions & TimePermissions.BypassUninitialized) != 0));
return ResultCode.Success;
}
@@ -42,7 +44,9 @@ namespace Ryujinx.HLE.HOS.Services.Time
// GetStandardNetworkSystemClock() -> object<nn::timesrv::detail::service::ISystemClock>
public ResultCode GetStandardNetworkSystemClock(ServiceCtx context)
{
- MakeObject(context, new ISystemClock(StandardNetworkSystemClockCore.Instance, (_permissions & TimePermissions.NetworkSystemClockWritableMask) != 0));
+ MakeObject(context, new ISystemClock(_timeManager.StandardNetworkSystemClock,
+ (_permissions & TimePermissions.NetworkSystemClockWritableMask) != 0,
+ (_permissions & TimePermissions.BypassUninitialized) != 0));
return ResultCode.Success;
}
@@ -51,7 +55,9 @@ namespace Ryujinx.HLE.HOS.Services.Time
// GetStandardSteadyClock() -> object<nn::timesrv::detail::service::ISteadyClock>
public ResultCode GetStandardSteadyClock(ServiceCtx context)
{
- MakeObject(context, new ISteadyClock());
+ MakeObject(context, new ISteadyClock(_timeManager.StandardSteadyClock,
+ (_permissions & TimePermissions.SteadyClockWritableMask) != 0,
+ (_permissions & TimePermissions.BypassUninitialized) != 0));
return ResultCode.Success;
}
@@ -60,7 +66,8 @@ namespace Ryujinx.HLE.HOS.Services.Time
// GetTimeZoneService() -> object<nn::timesrv::detail::service::ITimeZoneService>
public ResultCode GetTimeZoneService(ServiceCtx context)
{
- MakeObject(context, new ITimeZoneService());
+ MakeObject(context, new ITimeZoneServiceForPsc(_timeManager.TimeZone.Manager,
+ (_permissions & TimePermissions.TimeZoneWritableMask) != 0));
return ResultCode.Success;
}
@@ -69,7 +76,9 @@ namespace Ryujinx.HLE.HOS.Services.Time
// GetStandardLocalSystemClock() -> object<nn::timesrv::detail::service::ISystemClock>
public ResultCode GetStandardLocalSystemClock(ServiceCtx context)
{
- MakeObject(context, new ISystemClock(StandardLocalSystemClockCore.Instance, (_permissions & TimePermissions.LocalSystemClockWritableMask) != 0));
+ MakeObject(context, new ISystemClock(_timeManager.StandardLocalSystemClock,
+ (_permissions & TimePermissions.LocalSystemClockWritableMask) != 0,
+ (_permissions & TimePermissions.BypassUninitialized) != 0));
return ResultCode.Success;
}
@@ -78,7 +87,9 @@ namespace Ryujinx.HLE.HOS.Services.Time
// GetEphemeralNetworkSystemClock() -> object<nn::timesrv::detail::service::ISystemClock>
public ResultCode GetEphemeralNetworkSystemClock(ServiceCtx context)
{
- MakeObject(context, new ISystemClock(StandardNetworkSystemClockCore.Instance, false));
+ MakeObject(context, new ISystemClock(_timeManager.StandardNetworkSystemClock,
+ (_permissions & TimePermissions.NetworkSystemClockWritableMask) != 0,
+ (_permissions & TimePermissions.BypassUninitialized) != 0));
return ResultCode.Success;
}
@@ -89,7 +100,7 @@ namespace Ryujinx.HLE.HOS.Services.Time
{
if (_timeSharedMemoryNativeHandle == 0)
{
- if (context.Process.HandleTable.GenerateHandle(context.Device.System.TimeSharedMem, out _timeSharedMemoryNativeHandle) != KernelResult.Success)
+ if (context.Process.HandleTable.GenerateHandle(_timeManager.SharedMemory.GetSharedMemory(), out _timeSharedMemoryNativeHandle) != KernelResult.Success)
{
throw new InvalidOperationException("Out of handles!");
}
@@ -100,11 +111,34 @@ namespace Ryujinx.HLE.HOS.Services.Time
return ResultCode.Success;
}
+ [Command(50)] // 4.0.0+
+ // SetStandardSteadyClockInternalOffset(nn::TimeSpanType internal_offset)
+ public ResultCode SetStandardSteadyClockInternalOffset(ServiceCtx context)
+ {
+ // This is only implemented in glue's StaticService.
+ return ResultCode.NotImplemented;
+ }
+
+ [Command(51)] // 9.0.0+
+ // GetStandardSteadyClockRtcValue() -> u64
+ public ResultCode GetStandardSteadyClockRtcValue(ServiceCtx context)
+ {
+ // This is only implemented in glue's StaticService.
+ return ResultCode.NotImplemented;
+ }
+
[Command(100)]
// IsStandardUserSystemClockAutomaticCorrectionEnabled() -> bool
public ResultCode IsStandardUserSystemClockAutomaticCorrectionEnabled(ServiceCtx context)
{
- context.ResponseData.Write(StandardUserSystemClockCore.Instance.IsAutomaticCorrectionEnabled());
+ StandardUserSystemClockCore userClock = _timeManager.StandardUserSystemClock;
+
+ if (!userClock.IsInitialized())
+ {
+ return ResultCode.UninitializedClock;
+ }
+
+ context.ResponseData.Write(userClock.IsAutomaticCorrectionEnabled());
return ResultCode.Success;
}
@@ -113,6 +147,14 @@ namespace Ryujinx.HLE.HOS.Services.Time
// SetStandardUserSystemClockAutomaticCorrectionEnabled(b8)
public ResultCode SetStandardUserSystemClockAutomaticCorrectionEnabled(ServiceCtx context)
{
+ SteadyClockCore steadyClock = _timeManager.StandardSteadyClock;
+ StandardUserSystemClockCore userClock = _timeManager.StandardUserSystemClock;
+
+ if (!userClock.IsInitialized() || !steadyClock.IsInitialized())
+ {
+ return ResultCode.UninitializedClock;
+ }
+
if ((_permissions & TimePermissions.UserSystemClockWritableMask) == 0)
{
return ResultCode.PermissionDenied;
@@ -120,14 +162,50 @@ namespace Ryujinx.HLE.HOS.Services.Time
bool autoCorrectionEnabled = context.RequestData.ReadBoolean();
- return StandardUserSystemClockCore.Instance.SetAutomaticCorrectionEnabled(context.Thread, autoCorrectionEnabled);
+ ResultCode result = userClock.SetAutomaticCorrectionEnabled(context.Thread, autoCorrectionEnabled);
+
+ if (result == ResultCode.Success)
+ {
+ _timeManager.SharedMemory.SetAutomaticCorrectionEnabled(autoCorrectionEnabled);
+
+ SteadyClockTimePoint currentTimePoint = userClock.GetSteadyClockCore().GetCurrentTimePoint(context.Thread);
+
+ userClock.SetAutomaticCorrectionUpdatedTime(currentTimePoint);
+ userClock.SignalAutomaticCorrectionEvent();
+ }
+
+ return result;
+ }
+
+ [Command(102)] // 5.0.0+
+ // GetStandardUserSystemClockInitialYear() -> u32
+ public ResultCode GetStandardUserSystemClockInitialYear(ServiceCtx context)
+ {
+ // This is only implemented in glue's StaticService.
+ return ResultCode.NotImplemented;
}
[Command(200)] // 3.0.0+
// IsStandardNetworkSystemClockAccuracySufficient() -> bool
public ResultCode IsStandardNetworkSystemClockAccuracySufficient(ServiceCtx context)
{
- context.ResponseData.Write(StandardNetworkSystemClockCore.Instance.IsStandardNetworkSystemClockAccuracySufficient(context.Thread));
+ context.ResponseData.Write(_timeManager.StandardNetworkSystemClock.IsStandardNetworkSystemClockAccuracySufficient(context.Thread));
+
+ return ResultCode.Success;
+ }
+
+ [Command(201)] // 6.0.0+
+ // GetStandardUserSystemClockAutomaticCorrectionUpdatedTime() -> nn::time::SteadyClockTimePoint
+ public ResultCode GetStandardUserSystemClockAutomaticCorrectionUpdatedTime(ServiceCtx context)
+ {
+ StandardUserSystemClockCore userClock = _timeManager.StandardUserSystemClock;
+
+ if (!userClock.IsInitialized())
+ {
+ return ResultCode.UninitializedClock;
+ }
+
+ context.ResponseData.WriteStruct(userClock.GetAutomaticCorrectionUpdatedTime());
return ResultCode.Success;
}
@@ -136,8 +214,15 @@ namespace Ryujinx.HLE.HOS.Services.Time
// CalculateMonotonicSystemClockBaseTimePoint(nn::time::SystemClockContext) -> s64
public ResultCode CalculateMonotonicSystemClockBaseTimePoint(ServiceCtx context)
{
+ SteadyClockCore steadyClock = _timeManager.StandardSteadyClock;
+
+ if (!steadyClock.IsInitialized())
+ {
+ return ResultCode.UninitializedClock;
+ }
+
SystemClockContext otherContext = context.RequestData.ReadStruct<SystemClockContext>();
- SteadyClockTimePoint currentTimePoint = StandardSteadyClockCore.Instance.GetCurrentTimePoint(context.Thread);
+ SteadyClockTimePoint currentTimePoint = steadyClock.GetCurrentTimePoint(context.Thread);
ResultCode result = ResultCode.TimeMismatch;
@@ -148,7 +233,7 @@ namespace Ryujinx.HLE.HOS.Services.Time
context.ResponseData.Write(baseTimePoint);
- result = 0;
+ result = ResultCode.Success;
}
return result;
@@ -160,11 +245,11 @@ namespace Ryujinx.HLE.HOS.Services.Time
{
byte type = context.RequestData.ReadByte();
- ResultCode result = StandardUserSystemClockCore.Instance.GetSystemClockContext(context.Thread, out SystemClockContext userContext);
+ ResultCode result = _timeManager.StandardUserSystemClock.GetClockContext(context.Thread, out SystemClockContext userContext);
if (result == ResultCode.Success)
{
- result = StandardNetworkSystemClockCore.Instance.GetSystemClockContext(context.Thread, out SystemClockContext networkContext);
+ result = _timeManager.StandardNetworkSystemClock.GetClockContext(context.Thread, out SystemClockContext networkContext);
if (result == ResultCode.Success)
{
@@ -205,7 +290,6 @@ namespace Ryujinx.HLE.HOS.Services.Time
// CalculateStandardUserSystemClockDifferenceByUser(buffer<nn::time::sf::ClockSnapshot, 0x19>, buffer<nn::time::sf::ClockSnapshot, 0x19>) -> nn::TimeSpanType
public ResultCode CalculateStandardUserSystemClockDifferenceByUser(ServiceCtx context)
{
-
ClockSnapshot clockSnapshotA = ReadClockSnapshotFromBuffer(context, context.Request.ExchangeBuff[0]);
ClockSnapshot clockSnapshotB = ReadClockSnapshotFromBuffer(context, context.Request.ExchangeBuff[1]);
TimeSpanType difference = TimeSpanType.FromSeconds(clockSnapshotB.UserContext.Offset - clockSnapshotA.UserContext.Offset);
@@ -259,25 +343,32 @@ namespace Ryujinx.HLE.HOS.Services.Time
{
clockSnapshot = new ClockSnapshot();
- SteadyClockCore steadyClockCore = StandardSteadyClockCore.Instance;
+ SteadyClockCore steadyClockCore = _timeManager.StandardSteadyClock;
SteadyClockTimePoint currentTimePoint = steadyClockCore.GetCurrentTimePoint(thread);
- clockSnapshot.IsAutomaticCorrectionEnabled = StandardUserSystemClockCore.Instance.IsAutomaticCorrectionEnabled();
+ clockSnapshot.IsAutomaticCorrectionEnabled = _timeManager.StandardUserSystemClock.IsAutomaticCorrectionEnabled();
clockSnapshot.UserContext = userContext;
clockSnapshot.NetworkContext = networkContext;
- char[] tzName = TimeZoneManager.Instance.GetDeviceLocationName().ToCharArray();
+ ResultCode result = _timeManager.TimeZone.Manager.GetDeviceLocationName(out string deviceLocationName);
+
+ if (result != ResultCode.Success)
+ {
+ return result;
+ }
+
+ char[] tzName = deviceLocationName.ToCharArray();
char[] locationName = new char[0x24];
Array.Copy(tzName, locationName, tzName.Length);
clockSnapshot.LocationName = locationName;
- ResultCode result = ClockSnapshot.GetCurrentTime(out clockSnapshot.UserTime, currentTimePoint, clockSnapshot.UserContext);
+ result = ClockSnapshot.GetCurrentTime(out clockSnapshot.UserTime, currentTimePoint, clockSnapshot.UserContext);
if (result == ResultCode.Success)
{
- result = TimeZoneManager.Instance.ToCalendarTimeWithMyRules(clockSnapshot.UserTime, out CalendarInfo userCalendarInfo);
+ result = _timeManager.TimeZone.Manager.ToCalendarTimeWithMyRules(clockSnapshot.UserTime, out CalendarInfo userCalendarInfo);
if (result == ResultCode.Success)
{
@@ -289,7 +380,7 @@ namespace Ryujinx.HLE.HOS.Services.Time
clockSnapshot.NetworkTime = 0;
}
- result = TimeZoneManager.Instance.ToCalendarTimeWithMyRules(clockSnapshot.NetworkTime, out CalendarInfo networkCalendarInfo);
+ result = _timeManager.TimeZone.Manager.ToCalendarTimeWithMyRules(clockSnapshot.NetworkTime, out CalendarInfo networkCalendarInfo);
if (result == ResultCode.Success)
{
diff --git a/Ryujinx.HLE/HOS/Services/Time/ITimeServiceManager.cs b/Ryujinx.HLE/HOS/Services/Time/ITimeServiceManager.cs
index 514e901e..a897b3f7 100644
--- a/Ryujinx.HLE/HOS/Services/Time/ITimeServiceManager.cs
+++ b/Ryujinx.HLE/HOS/Services/Time/ITimeServiceManager.cs
@@ -1,8 +1,219 @@
-namespace Ryujinx.HLE.HOS.Services.Time
+using Ryujinx.Common;
+using Ryujinx.HLE.Exceptions;
+using Ryujinx.HLE.HOS.Ipc;
+using Ryujinx.HLE.HOS.Kernel.Common;
+using Ryujinx.HLE.HOS.Services.Time.Clock;
+using Ryujinx.HLE.Utilities;
+using System;
+using System.IO;
+using System.Text;
+
+namespace Ryujinx.HLE.HOS.Services.Time
{
- [Service("time:su")] // 9.0.0+
+ [Service("time:m")] // 9.0.0+
class ITimeServiceManager : IpcService
{
- public ITimeServiceManager(ServiceCtx context) { }
+ private TimeManager _timeManager;
+ private int _automaticCorrectionEvent;
+
+ public ITimeServiceManager(ServiceCtx context)
+ {
+ _timeManager = TimeManager.Instance;
+ _automaticCorrectionEvent = 0;
+ }
+
+ [Command(0)]
+ // GetUserStaticService() -> object<nn::timesrv::detail::service::IStaticService>
+ public ResultCode GetUserStaticService(ServiceCtx context)
+ {
+ MakeObject(context, new IStaticServiceForPsc(_timeManager, TimePermissions.User));
+
+ return ResultCode.Success;
+ }
+
+ [Command(5)]
+ // GetAdminStaticService() -> object<nn::timesrv::detail::service::IStaticService>
+ public ResultCode GetAdminStaticService(ServiceCtx context)
+ {
+ MakeObject(context, new IStaticServiceForPsc(_timeManager, TimePermissions.Admin));
+
+ return ResultCode.Success;
+ }
+
+ [Command(6)]
+ // GetRepairStaticService() -> object<nn::timesrv::detail::service::IStaticService>
+ public ResultCode GetRepairStaticService(ServiceCtx context)
+ {
+ MakeObject(context, new IStaticServiceForPsc(_timeManager, TimePermissions.Repair));
+
+ return ResultCode.Success;
+ }
+
+ [Command(9)]
+ // GetManufactureStaticService() -> object<nn::timesrv::detail::service::IStaticService>
+ public ResultCode GetManufactureStaticService(ServiceCtx context)
+ {
+ MakeObject(context, new IStaticServiceForPsc(_timeManager, TimePermissions.Manufacture));
+
+ return ResultCode.Success;
+ }
+
+ [Command(10)]
+ // SetupStandardSteadyClock(nn::util::Uuid clock_source_id, nn::TimeSpanType setup_value, nn::TimeSpanType internal_offset, nn::TimeSpanType test_offset, bool is_rtc_reset_detected)
+ public ResultCode SetupStandardSteadyClock(ServiceCtx context)
+ {
+ UInt128 clockSourceId = context.RequestData.ReadStruct<UInt128>();
+ TimeSpanType setupValue = context.RequestData.ReadStruct<TimeSpanType>();
+ TimeSpanType internalOffset = context.RequestData.ReadStruct<TimeSpanType>();
+ TimeSpanType testOffset = context.RequestData.ReadStruct<TimeSpanType>();
+ bool isRtcResetDetected = context.RequestData.ReadBoolean();
+
+ _timeManager.SetupStandardSteadyClock(context.Thread, clockSourceId, setupValue, internalOffset, testOffset, isRtcResetDetected);
+
+ return ResultCode.Success;
+ }
+
+ [Command(11)]
+ // SetupStandardLocalSystemClock(nn::time::SystemClockContext context, nn::time::PosixTime posix_time)
+ public ResultCode SetupStandardLocalSystemClock(ServiceCtx context)
+ {
+ SystemClockContext clockContext = context.RequestData.ReadStruct<SystemClockContext>();
+ long posixTime = context.RequestData.ReadInt64();
+
+ _timeManager.SetupStandardLocalSystemClock(context.Thread, clockContext, posixTime);
+
+ return ResultCode.Success;
+ }
+
+ [Command(12)]
+ // SetupStandardNetworkSystemClock(nn::time::SystemClockContext context, nn::TimeSpanType sufficient_accuracy)
+ public ResultCode SetupStandardNetworkSystemClock(ServiceCtx context)
+ {
+ SystemClockContext clockContext = context.RequestData.ReadStruct<SystemClockContext>();
+ TimeSpanType sufficientAccuracy = context.RequestData.ReadStruct<TimeSpanType>();
+
+ _timeManager.SetupStandardNetworkSystemClock(clockContext, sufficientAccuracy);
+
+ return ResultCode.Success;
+ }
+
+ [Command(13)]
+ // SetupStandardUserSystemClock(bool automatic_correction_enabled, nn::time::SteadyClockTimePoint steady_clock_timepoint)
+ public ResultCode SetupStandardUserSystemClock(ServiceCtx context)
+ {
+ bool isAutomaticCorrectionEnabled = context.RequestData.ReadBoolean();
+
+ context.RequestData.BaseStream.Position += 7;
+
+ SteadyClockTimePoint steadyClockTimePoint = context.RequestData.ReadStruct<SteadyClockTimePoint>();
+
+ _timeManager.SetupStandardUserSystemClock(context.Thread, isAutomaticCorrectionEnabled, steadyClockTimePoint);
+
+ return ResultCode.Success;
+ }
+
+ [Command(14)]
+ // SetupTimeZoneManager(nn::time::LocationName location_name, nn::time::SteadyClockTimePoint timezone_update_timepoint, u32 total_location_name_count, nn::time::TimeZoneRuleVersion timezone_rule_version, buffer<nn::time::TimeZoneBinary, 0x21> timezone_binary)
+ public ResultCode SetupTimeZoneManager(ServiceCtx context)
+ {
+ string locationName = Encoding.ASCII.GetString(context.RequestData.ReadBytes(0x24)).TrimEnd('\0');
+ SteadyClockTimePoint timeZoneUpdateTimePoint = context.RequestData.ReadStruct<SteadyClockTimePoint>();
+ uint totalLocationNameCount = context.RequestData.ReadUInt32();
+ UInt128 timeZoneRuleVersion = context.RequestData.ReadStruct<UInt128>();
+
+ (long bufferPosition, long bufferSize) = context.Request.GetBufferType0x21();
+
+ using (MemoryStream timeZoneBinaryStream = new MemoryStream(context.Memory.ReadBytes(bufferPosition, bufferSize)))
+ {
+ _timeManager.SetupTimeZoneManager(locationName, timeZoneUpdateTimePoint, totalLocationNameCount, timeZoneRuleVersion, timeZoneBinaryStream);
+ }
+
+ return ResultCode.Success;
+ }
+
+ [Command(15)]
+ // SetupEphemeralNetworkSystemClock()
+ public ResultCode SetupEphemeralNetworkSystemClock(ServiceCtx context)
+ {
+ _timeManager.SetupEphemeralNetworkSystemClock();
+
+ return ResultCode.Success;
+ }
+
+ [Command(50)]
+ // Unknown50() -> handle<copy>
+ public ResultCode Unknown50(ServiceCtx context)
+ {
+ // TODO: figure out the usage of this event
+ throw new ServiceNotImplementedException(context);
+ }
+
+ [Command(51)]
+ // Unknown51() -> handle<copy>
+ public ResultCode Unknown51(ServiceCtx context)
+ {
+ // TODO: figure out the usage of this event
+ throw new ServiceNotImplementedException(context);
+ }
+
+ [Command(52)]
+ // Unknown52() -> handle<copy>
+ public ResultCode Unknown52(ServiceCtx context)
+ {
+ // TODO: figure out the usage of this event
+ throw new ServiceNotImplementedException(context);
+ }
+
+ [Command(60)]
+ // GetStandardUserSystemClockAutomaticCorrectionEvent() -> handle<copy>
+ public ResultCode GetStandardUserSystemClockAutomaticCorrectionEvent(ServiceCtx context)
+ {
+ if (_automaticCorrectionEvent == 0)
+ {
+ if (context.Process.HandleTable.GenerateHandle(_timeManager.StandardUserSystemClock.GetAutomaticCorrectionReadableEvent(), out _automaticCorrectionEvent) != KernelResult.Success)
+ {
+ throw new InvalidOperationException("Out of handles!");
+ }
+ }
+
+ context.Response.HandleDesc = IpcHandleDesc.MakeCopy(_automaticCorrectionEvent);
+
+ return ResultCode.Success;
+ }
+
+ [Command(100)]
+ // SetStandardSteadyClockRtcOffset(nn::TimeSpanType rtc_offset)
+ public ResultCode SetStandardSteadyClockRtcOffset(ServiceCtx context)
+ {
+ TimeSpanType rtcOffset = context.RequestData.ReadStruct<TimeSpanType>();
+
+ _timeManager.SetStandardSteadyClockRtcOffset(context.Thread, rtcOffset);
+
+ return ResultCode.Success;
+ }
+
+ [Command(200)]
+ // GetAlarmRegistrationEvent() -> handle<copy>
+ public ResultCode GetAlarmRegistrationEvent(ServiceCtx context)
+ {
+ // TODO
+ throw new ServiceNotImplementedException(context);
+ }
+
+ [Command(201)]
+ // UpdateSteadyAlarms()
+ public ResultCode UpdateSteadyAlarms(ServiceCtx context)
+ {
+ // TODO
+ throw new ServiceNotImplementedException(context);
+ }
+
+ [Command(202)]
+ // TryGetNextSteadyClockAlarmSnapshot() -> (bool, nn::time::SteadyClockAlarmSnapshot)
+ public ResultCode TryGetNextSteadyClockAlarmSnapshot(ServiceCtx context)
+ {
+ // TODO
+ throw new ServiceNotImplementedException(context);
+ }
}
-} \ 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 ed7130f3..1ef895c2 100644
--- a/Ryujinx.HLE/HOS/Services/Time/ResultCode.cs
+++ b/Ryujinx.HLE/HOS/Services/Time/ResultCode.cs
@@ -9,6 +9,7 @@
PermissionDenied = (1 << ErrorCodeShift) | ModuleId,
TimeMismatch = (102 << ErrorCodeShift) | ModuleId,
+ UninitializedClock = (103 << ErrorCodeShift) | ModuleId,
TimeNotFound = (200 << ErrorCodeShift) | ModuleId,
Overflow = (201 << ErrorCodeShift) | ModuleId,
LocationNameTooLong = (801 << ErrorCodeShift) | ModuleId,
diff --git a/Ryujinx.HLE/HOS/Services/Time/StaticService/ISteadyClock.cs b/Ryujinx.HLE/HOS/Services/Time/StaticService/ISteadyClock.cs
index 31f119df..bf6a4fd1 100644
--- a/Ryujinx.HLE/HOS/Services/Time/StaticService/ISteadyClock.cs
+++ b/Ryujinx.HLE/HOS/Services/Time/StaticService/ISteadyClock.cs
@@ -5,42 +5,78 @@ namespace Ryujinx.HLE.HOS.Services.Time.StaticService
{
class ISteadyClock : IpcService
{
+ private SteadyClockCore _steadyClock;
+ private bool _writePermission;
+ private bool _bypassUninitializedClock;
+
+ public ISteadyClock(SteadyClockCore steadyClock, bool writePermission, bool bypassUninitializedClock)
+ {
+ _steadyClock = steadyClock;
+ _writePermission = writePermission;
+ _bypassUninitializedClock = bypassUninitializedClock;
+ }
+
[Command(0)]
// GetCurrentTimePoint() -> nn::time::SteadyClockTimePoint
public ResultCode GetCurrentTimePoint(ServiceCtx context)
{
- SteadyClockTimePoint currentTimePoint = StandardSteadyClockCore.Instance.GetCurrentTimePoint(context.Thread);
+ if (!_bypassUninitializedClock && !_steadyClock.IsInitialized())
+ {
+ return ResultCode.UninitializedClock;
+ }
+
+ SteadyClockTimePoint currentTimePoint = _steadyClock.GetCurrentTimePoint(context.Thread);
context.ResponseData.WriteStruct(currentTimePoint);
return ResultCode.Success;
}
- [Command(1)]
+ [Command(2)]
// GetTestOffset() -> nn::TimeSpanType
public ResultCode GetTestOffset(ServiceCtx context)
{
- context.ResponseData.WriteStruct(StandardSteadyClockCore.Instance.GetTestOffset());
+ if (!_bypassUninitializedClock && !_steadyClock.IsInitialized())
+ {
+ return ResultCode.UninitializedClock;
+ }
+
+ context.ResponseData.WriteStruct(_steadyClock.GetTestOffset());
return ResultCode.Success;
}
- [Command(2)]
+ [Command(3)]
// SetTestOffset(nn::TimeSpanType)
public ResultCode SetTestOffset(ServiceCtx context)
{
+ if (!_writePermission)
+ {
+ return ResultCode.PermissionDenied;
+ }
+
+ if (!_bypassUninitializedClock && !_steadyClock.IsInitialized())
+ {
+ return ResultCode.UninitializedClock;
+ }
+
TimeSpanType testOffset = context.RequestData.ReadStruct<TimeSpanType>();
- StandardSteadyClockCore.Instance.SetTestOffset(testOffset);
+ _steadyClock.SetTestOffset(testOffset);
- return 0;
+ return ResultCode.Success;
}
[Command(100)] // 2.0.0+
// GetRtcValue() -> u64
public ResultCode GetRtcValue(ServiceCtx context)
{
- ResultCode result = StandardSteadyClockCore.Instance.GetRtcValue(out ulong rtcValue);
+ if (!_bypassUninitializedClock && !_steadyClock.IsInitialized())
+ {
+ return ResultCode.UninitializedClock;
+ }
+
+ ResultCode result = _steadyClock.GetRtcValue(out ulong rtcValue);
if (result == ResultCode.Success)
{
@@ -54,7 +90,12 @@ namespace Ryujinx.HLE.HOS.Services.Time.StaticService
// IsRtcResetDetected() -> bool
public ResultCode IsRtcResetDetected(ServiceCtx context)
{
- context.ResponseData.Write(StandardSteadyClockCore.Instance.IsRtcResetDetected());
+ if (!_bypassUninitializedClock && !_steadyClock.IsInitialized())
+ {
+ return ResultCode.UninitializedClock;
+ }
+
+ context.ResponseData.Write(_steadyClock.IsRtcResetDetected());
return ResultCode.Success;
}
@@ -63,7 +104,12 @@ namespace Ryujinx.HLE.HOS.Services.Time.StaticService
// GetSetupResultValue() -> u32
public ResultCode GetSetupResultValue(ServiceCtx context)
{
- context.ResponseData.Write((uint)StandardSteadyClockCore.Instance.GetSetupResultValue());
+ if (!_bypassUninitializedClock && !_steadyClock.IsInitialized())
+ {
+ return ResultCode.UninitializedClock;
+ }
+
+ context.ResponseData.Write((uint)_steadyClock.GetSetupResultValue());
return ResultCode.Success;
}
@@ -72,7 +118,12 @@ namespace Ryujinx.HLE.HOS.Services.Time.StaticService
// GetInternalOffset() -> nn::TimeSpanType
public ResultCode GetInternalOffset(ServiceCtx context)
{
- context.ResponseData.WriteStruct(StandardSteadyClockCore.Instance.GetInternalOffset());
+ if (!_bypassUninitializedClock && !_steadyClock.IsInitialized())
+ {
+ return ResultCode.UninitializedClock;
+ }
+
+ context.ResponseData.WriteStruct(_steadyClock.GetInternalOffset());
return ResultCode.Success;
}
@@ -81,9 +132,19 @@ namespace Ryujinx.HLE.HOS.Services.Time.StaticService
// SetInternalOffset(nn::TimeSpanType)
public ResultCode SetInternalOffset(ServiceCtx context)
{
+ if (!_writePermission)
+ {
+ return ResultCode.PermissionDenied;
+ }
+
+ if (!_bypassUninitializedClock && !_steadyClock.IsInitialized())
+ {
+ return ResultCode.UninitializedClock;
+ }
+
TimeSpanType internalOffset = context.RequestData.ReadStruct<TimeSpanType>();
- StandardSteadyClockCore.Instance.SetInternalOffset(internalOffset);
+ _steadyClock.SetInternalOffset(internalOffset);
return ResultCode.Success;
}
diff --git a/Ryujinx.HLE/HOS/Services/Time/StaticService/ISystemClock.cs b/Ryujinx.HLE/HOS/Services/Time/StaticService/ISystemClock.cs
index 0d866177..d5b21f8c 100644
--- a/Ryujinx.HLE/HOS/Services/Time/StaticService/ISystemClock.cs
+++ b/Ryujinx.HLE/HOS/Services/Time/StaticService/ISystemClock.cs
@@ -1,5 +1,9 @@
using Ryujinx.Common;
+using Ryujinx.HLE.HOS.Ipc;
+using Ryujinx.HLE.HOS.Kernel.Common;
+using Ryujinx.HLE.HOS.Kernel.Threading;
using Ryujinx.HLE.HOS.Services.Time.Clock;
+using System;
namespace Ryujinx.HLE.HOS.Services.Time.StaticService
{
@@ -7,34 +11,31 @@ namespace Ryujinx.HLE.HOS.Services.Time.StaticService
{
private SystemClockCore _clockCore;
private bool _writePermission;
+ private bool _bypassUninitializedClock;
+ private int _operationEventReadableHandle;
- public ISystemClock(SystemClockCore clockCore, bool writePermission)
+ public ISystemClock(SystemClockCore clockCore, bool writePermission, bool bypassUninitializedClock)
{
- _clockCore = clockCore;
- _writePermission = writePermission;
+ _clockCore = clockCore;
+ _writePermission = writePermission;
+ _bypassUninitializedClock = bypassUninitializedClock;
+ _operationEventReadableHandle = 0;
}
[Command(0)]
// GetCurrentTime() -> nn::time::PosixTime
public ResultCode GetCurrentTime(ServiceCtx context)
{
- SteadyClockCore steadyClockCore = _clockCore.GetSteadyClockCore();
- SteadyClockTimePoint currentTimePoint = steadyClockCore.GetCurrentTimePoint(context.Thread);
+ if (!_bypassUninitializedClock && !_clockCore.IsInitialized())
+ {
+ return ResultCode.UninitializedClock;
+ }
- ResultCode result = _clockCore.GetSystemClockContext(context.Thread, out SystemClockContext clockContext);
+ ResultCode result = _clockCore.GetCurrentTime(context.Thread, out long posixTime);
if (result == ResultCode.Success)
{
- result = ResultCode.TimeMismatch;
-
- if (currentTimePoint.ClockSourceId == clockContext.SteadyTimePoint.ClockSourceId)
- {
- long posixTime = clockContext.Offset + currentTimePoint.TimePoint;
-
- context.ResponseData.Write(posixTime);
-
- result = 0;
- }
+ context.ResponseData.Write(posixTime);
}
return result;
@@ -49,31 +50,26 @@ namespace Ryujinx.HLE.HOS.Services.Time.StaticService
return ResultCode.PermissionDenied;
}
- long posixTime = context.RequestData.ReadInt64();
- SteadyClockCore steadyClockCore = _clockCore.GetSteadyClockCore();
- SteadyClockTimePoint currentTimePoint = steadyClockCore.GetCurrentTimePoint(context.Thread);
-
- SystemClockContext clockContext = new SystemClockContext()
+ if (!_bypassUninitializedClock && !_clockCore.IsInitialized())
{
- Offset = posixTime - currentTimePoint.TimePoint,
- SteadyTimePoint = currentTimePoint
- };
-
- ResultCode result = _clockCore.SetSystemClockContext(clockContext);
-
- if (result == ResultCode.Success)
- {
- result = _clockCore.Flush(clockContext);
+ return ResultCode.UninitializedClock;
}
- return result;
+ long posixTime = context.RequestData.ReadInt64();
+
+ return _clockCore.SetCurrentTime(context.Thread, posixTime);
}
[Command(2)]
- // GetSystemClockContext() -> nn::time::SystemClockContext
+ // GetClockContext() -> nn::time::SystemClockContext
public ResultCode GetSystemClockContext(ServiceCtx context)
{
- ResultCode result = _clockCore.GetSystemClockContext(context.Thread, out SystemClockContext clockContext);
+ if (!_bypassUninitializedClock && !_clockCore.IsInitialized())
+ {
+ return ResultCode.UninitializedClock;
+ }
+
+ ResultCode result = _clockCore.GetClockContext(context.Thread, out SystemClockContext clockContext);
if (result == ResultCode.Success)
{
@@ -84,7 +80,7 @@ namespace Ryujinx.HLE.HOS.Services.Time.StaticService
}
[Command(3)]
- // SetSystemClockContext(nn::time::SystemClockContext)
+ // SetClockContext(nn::time::SystemClockContext)
public ResultCode SetSystemClockContext(ServiceCtx context)
{
if (!_writePermission)
@@ -92,16 +88,37 @@ namespace Ryujinx.HLE.HOS.Services.Time.StaticService
return ResultCode.PermissionDenied;
}
+ if (!_bypassUninitializedClock && !_clockCore.IsInitialized())
+ {
+ return ResultCode.UninitializedClock;
+ }
+
SystemClockContext clockContext = context.RequestData.ReadStruct<SystemClockContext>();
ResultCode result = _clockCore.SetSystemClockContext(clockContext);
- if (result == ResultCode.Success)
+ return result;
+ }
+
+ [Command(4)] // 9.0.0+
+ // GetOperationEventReadableHandle() -> handle<copy>
+ public ResultCode GetOperationEventReadableHandle(ServiceCtx context)
+ {
+ if (_operationEventReadableHandle == 0)
{
- result = _clockCore.Flush(clockContext);
+ KEvent kEvent = new KEvent(context.Device.System);
+
+ _clockCore.RegisterOperationEvent(kEvent.WritableEvent);
+
+ if (context.Process.HandleTable.GenerateHandle(kEvent.ReadableEvent, out _operationEventReadableHandle) != KernelResult.Success)
+ {
+ throw new InvalidOperationException("Out of handles!");
+ }
}
- return result;
+ context.Response.HandleDesc = IpcHandleDesc.MakeCopy(_operationEventReadableHandle);
+
+ return ResultCode.Success;
}
}
} \ No newline at end of file
diff --git a/Ryujinx.HLE/HOS/Services/Time/StaticService/ITimeZoneService.cs b/Ryujinx.HLE/HOS/Services/Time/StaticService/ITimeZoneService.cs
deleted file mode 100644
index c65107df..00000000
--- a/Ryujinx.HLE/HOS/Services/Time/StaticService/ITimeZoneService.cs
+++ /dev/null
@@ -1,218 +0,0 @@
-using ARMeilleure.Memory;
-using Ryujinx.Common;
-using Ryujinx.Common.Logging;
-using Ryujinx.HLE.HOS.Services.Time.TimeZone;
-using System;
-using System.Text;
-
-namespace Ryujinx.HLE.HOS.Services.Time.StaticService
-{
- class ITimeZoneService : IpcService
- {
- public ITimeZoneService() { }
-
- [Command(0)]
- // GetDeviceLocationName() -> nn::time::LocationName
- public ResultCode GetDeviceLocationName(ServiceCtx context)
- {
- char[] tzName = TimeZoneManager.Instance.GetDeviceLocationName().ToCharArray();
-
- int padding = 0x24 - tzName.Length;
-
- if (padding < 0)
- {
- return ResultCode.LocationNameTooLong;
- }
-
- context.ResponseData.Write(tzName);
-
- for (int index = 0; index < padding; index++)
- {
- context.ResponseData.Write((byte)0);
- }
-
- return ResultCode.Success;
- }
-
- [Command(1)]
- // SetDeviceLocationName(nn::time::LocationName)
- public ResultCode SetDeviceLocationName(ServiceCtx context)
- {
- string locationName = Encoding.ASCII.GetString(context.RequestData.ReadBytes(0x24)).TrimEnd('\0');
-
- return TimeZoneManager.Instance.SetDeviceLocationName(locationName);
- }
-
- [Command(2)]
- // GetTotalLocationNameCount() -> u32
- public ResultCode GetTotalLocationNameCount(ServiceCtx context)
- {
- context.ResponseData.Write(TimeZoneManager.Instance.GetTotalLocationNameCount());
-
- return ResultCode.Success;
- }
-
- [Command(3)]
- // LoadLocationNameList(u32 index) -> (u32 outCount, buffer<nn::time::LocationName, 6>)
- public ResultCode LoadLocationNameList(ServiceCtx context)
- {
- uint index = context.RequestData.ReadUInt32();
- long bufferPosition = context.Request.ReceiveBuff[0].Position;
- long bufferSize = context.Request.ReceiveBuff[0].Size;
-
- ResultCode errorCode = TimeZoneManager.Instance.LoadLocationNameList(index, out string[] locationNameArray, (uint)bufferSize / 0x24);
-
- if (errorCode == 0)
- {
- uint offset = 0;
-
- foreach (string locationName in locationNameArray)
- {
- int padding = 0x24 - locationName.Length;
-
- if (padding < 0)
- {
- return ResultCode.LocationNameTooLong;
- }
-
- context.Memory.WriteBytes(bufferPosition + offset, Encoding.ASCII.GetBytes(locationName));
- MemoryHelper.FillWithZeros(context.Memory, bufferPosition + offset + locationName.Length, padding);
-
- offset += 0x24;
- }
-
- context.ResponseData.Write((uint)locationNameArray.Length);
- }
-
- return errorCode;
- }
-
- [Command(4)]
- // LoadTimeZoneRule(nn::time::LocationName locationName) -> buffer<nn::time::TimeZoneRule, 0x16>
- public ResultCode LoadTimeZoneRule(ServiceCtx context)
- {
- long bufferPosition = context.Request.ReceiveBuff[0].Position;
- long bufferSize = context.Request.ReceiveBuff[0].Size;
-
- if (bufferSize != 0x4000)
- {
- // TODO: find error code here
- Logger.PrintError(LogClass.ServiceTime, $"TimeZoneRule buffer size is 0x{bufferSize:x} (expected 0x4000)");
-
- throw new InvalidOperationException();
- }
-
-
- string locationName = Encoding.ASCII.GetString(context.RequestData.ReadBytes(0x24)).TrimEnd('\0');
-
- ResultCode resultCode = TimeZoneManager.Instance.LoadTimeZoneRules(out TimeZoneRule rules, locationName);
-
- // Write TimeZoneRule if success
- if (resultCode == 0)
- {
- MemoryHelper.Write(context.Memory, bufferPosition, rules);
- }
-
- return resultCode;
- }
-
- [Command(100)]
- // ToCalendarTime(nn::time::PosixTime time, buffer<nn::time::TimeZoneRule, 0x15> rules) -> (nn::time::CalendarTime, nn::time::sf::CalendarAdditionalInfo)
- public ResultCode ToCalendarTime(ServiceCtx context)
- {
- long posixTime = context.RequestData.ReadInt64();
- long bufferPosition = context.Request.SendBuff[0].Position;
- long bufferSize = context.Request.SendBuff[0].Size;
-
- if (bufferSize != 0x4000)
- {
- // TODO: find error code here
- Logger.PrintError(LogClass.ServiceTime, $"TimeZoneRule buffer size is 0x{bufferSize:x} (expected 0x4000)");
-
- throw new InvalidOperationException();
- }
-
- TimeZoneRule rules = MemoryHelper.Read<TimeZoneRule>(context.Memory, bufferPosition);
-
- ResultCode resultCode = TimeZoneManager.ToCalendarTime(rules, posixTime, out CalendarInfo calendar);
-
- if (resultCode == 0)
- {
- context.ResponseData.WriteStruct(calendar);
- }
-
- return resultCode;
- }
-
- [Command(101)]
- // ToCalendarTimeWithMyRule(nn::time::PosixTime) -> (nn::time::CalendarTime, nn::time::sf::CalendarAdditionalInfo)
- public ResultCode ToCalendarTimeWithMyRule(ServiceCtx context)
- {
- long posixTime = context.RequestData.ReadInt64();
-
- ResultCode resultCode = TimeZoneManager.Instance.ToCalendarTimeWithMyRules(posixTime, out CalendarInfo calendar);
-
- if (resultCode == 0)
- {
- context.ResponseData.WriteStruct(calendar);
- }
-
- return resultCode;
- }
-
- [Command(201)]
- // ToPosixTime(nn::time::CalendarTime calendarTime, buffer<nn::time::TimeZoneRule, 0x15> rules) -> (u32 outCount, buffer<nn::time::PosixTime, 0xa>)
- public ResultCode ToPosixTime(ServiceCtx context)
- {
- long inBufferPosition = context.Request.SendBuff[0].Position;
- long inBufferSize = context.Request.SendBuff[0].Size;
-
- CalendarTime calendarTime = context.RequestData.ReadStruct<CalendarTime>();
-
- if (inBufferSize != 0x4000)
- {
- // TODO: find error code here
- Logger.PrintError(LogClass.ServiceTime, $"TimeZoneRule buffer size is 0x{inBufferSize:x} (expected 0x4000)");
-
- throw new InvalidOperationException();
- }
-
- TimeZoneRule rules = MemoryHelper.Read<TimeZoneRule>(context.Memory, inBufferPosition);
-
- ResultCode resultCode = TimeZoneManager.ToPosixTime(rules, calendarTime, out long posixTime);
-
- if (resultCode == 0)
- {
- long outBufferPosition = context.Request.RecvListBuff[0].Position;
- long outBufferSize = context.Request.RecvListBuff[0].Size;
-
- context.Memory.WriteInt64(outBufferPosition, posixTime);
- context.ResponseData.Write(1);
- }
-
- return resultCode;
- }
-
- [Command(202)]
- // ToPosixTimeWithMyRule(nn::time::CalendarTime calendarTime) -> (u32 outCount, buffer<nn::time::PosixTime, 0xa>)
- public ResultCode ToPosixTimeWithMyRule(ServiceCtx context)
- {
- CalendarTime calendarTime = context.RequestData.ReadStruct<CalendarTime>();
-
- ResultCode resultCode = TimeZoneManager.Instance.ToPosixTimeWithMyRules(calendarTime, out long posixTime);
-
- if (resultCode == 0)
- {
- long outBufferPosition = context.Request.RecvListBuff[0].Position;
- long outBufferSize = context.Request.RecvListBuff[0].Size;
-
- context.Memory.WriteInt64(outBufferPosition, posixTime);
-
- // There could be only one result on one calendar as leap seconds aren't supported.
- context.ResponseData.Write(1);
- }
-
- return resultCode;
- }
- }
-} \ No newline at end of file
diff --git a/Ryujinx.HLE/HOS/Services/Time/StaticService/ITimeZoneServiceForGlue.cs b/Ryujinx.HLE/HOS/Services/Time/StaticService/ITimeZoneServiceForGlue.cs
new file mode 100644
index 00000000..7acb0257
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Services/Time/StaticService/ITimeZoneServiceForGlue.cs
@@ -0,0 +1,142 @@
+using ARMeilleure.Memory;
+using Ryujinx.Common;
+using Ryujinx.Common.Logging;
+using Ryujinx.HLE.HOS.Services.Time.TimeZone;
+using System;
+using System.Text;
+
+namespace Ryujinx.HLE.HOS.Services.Time.StaticService
+{
+ class ITimeZoneServiceForGlue : IpcService
+ {
+ private TimeZoneContentManager _timeZoneContentManager;
+ private ITimeZoneServiceForPsc _inner;
+ private bool _writePermission;
+
+ public ITimeZoneServiceForGlue(TimeZoneContentManager timeZoneContentManager, bool writePermission)
+ {
+ _timeZoneContentManager = timeZoneContentManager;
+ _writePermission = writePermission;
+ _inner = new ITimeZoneServiceForPsc(timeZoneContentManager.Manager, writePermission);
+ }
+
+ [Command(0)]
+ // GetDeviceLocationName() -> nn::time::LocationName
+ public ResultCode GetDeviceLocationName(ServiceCtx context)
+ {
+ return _inner.GetDeviceLocationName(context);
+ }
+
+ [Command(1)]
+ // SetDeviceLocationName(nn::time::LocationName)
+ public ResultCode SetDeviceLocationName(ServiceCtx context)
+ {
+ if (!_writePermission)
+ {
+ return ResultCode.PermissionDenied;
+ }
+
+ string locationName = Encoding.ASCII.GetString(context.RequestData.ReadBytes(0x24)).TrimEnd('\0');
+
+ return _timeZoneContentManager.SetDeviceLocationName(locationName);
+ }
+
+ [Command(2)]
+ // GetTotalLocationNameCount() -> u32
+ public ResultCode GetTotalLocationNameCount(ServiceCtx context)
+ {
+ return _inner.GetTotalLocationNameCount(context);
+ }
+
+ [Command(3)]
+ // LoadLocationNameList(u32 index) -> (u32 outCount, buffer<nn::time::LocationName, 6>)
+ public ResultCode LoadLocationNameList(ServiceCtx context)
+ {
+ uint index = context.RequestData.ReadUInt32();
+ long bufferPosition = context.Request.ReceiveBuff[0].Position;
+ long bufferSize = context.Request.ReceiveBuff[0].Size;
+
+ ResultCode errorCode = _timeZoneContentManager.LoadLocationNameList(index, out string[] locationNameArray, (uint)bufferSize / 0x24);
+
+ if (errorCode == 0)
+ {
+ uint offset = 0;
+
+ foreach (string locationName in locationNameArray)
+ {
+ int padding = 0x24 - locationName.Length;
+
+ if (padding < 0)
+ {
+ return ResultCode.LocationNameTooLong;
+ }
+
+ context.Memory.WriteBytes(bufferPosition + offset, Encoding.ASCII.GetBytes(locationName));
+ MemoryHelper.FillWithZeros(context.Memory, bufferPosition + offset + locationName.Length, padding);
+
+ offset += 0x24;
+ }
+
+ context.ResponseData.Write((uint)locationNameArray.Length);
+ }
+
+ return errorCode;
+ }
+
+ [Command(4)]
+ // LoadTimeZoneRule(nn::time::LocationName locationName) -> buffer<nn::time::TimeZoneRule, 0x16>
+ public ResultCode LoadTimeZoneRule(ServiceCtx context)
+ {
+ long bufferPosition = context.Request.ReceiveBuff[0].Position;
+ long bufferSize = context.Request.ReceiveBuff[0].Size;
+
+ if (bufferSize != 0x4000)
+ {
+ // TODO: find error code here
+ Logger.PrintError(LogClass.ServiceTime, $"TimeZoneRule buffer size is 0x{bufferSize:x} (expected 0x4000)");
+
+ throw new InvalidOperationException();
+ }
+
+ string locationName = Encoding.ASCII.GetString(context.RequestData.ReadBytes(0x24)).TrimEnd('\0');
+
+ ResultCode resultCode = _timeZoneContentManager.LoadTimeZoneRule(out TimeZoneRule rules, locationName);
+
+ // Write TimeZoneRule if success
+ if (resultCode == ResultCode.Success)
+ {
+ MemoryHelper.Write(context.Memory, bufferPosition, rules);
+ }
+
+ return resultCode;
+ }
+
+ [Command(100)]
+ // ToCalendarTime(nn::time::PosixTime time, buffer<nn::time::TimeZoneRule, 0x15> rules) -> (nn::time::CalendarTime, nn::time::sf::CalendarAdditionalInfo)
+ public ResultCode ToCalendarTime(ServiceCtx context)
+ {
+ return _inner.ToCalendarTime(context);
+ }
+
+ [Command(101)]
+ // ToCalendarTimeWithMyRule(nn::time::PosixTime) -> (nn::time::CalendarTime, nn::time::sf::CalendarAdditionalInfo)
+ public ResultCode ToCalendarTimeWithMyRule(ServiceCtx context)
+ {
+ return _inner.ToCalendarTimeWithMyRule(context);
+ }
+
+ [Command(201)]
+ // ToPosixTime(nn::time::CalendarTime calendarTime, buffer<nn::time::TimeZoneRule, 0x15> rules) -> (u32 outCount, buffer<nn::time::PosixTime, 0xa>)
+ public ResultCode ToPosixTime(ServiceCtx context)
+ {
+ return _inner.ToPosixTime(context);
+ }
+
+ [Command(202)]
+ // ToPosixTimeWithMyRule(nn::time::CalendarTime calendarTime) -> (u32 outCount, buffer<nn::time::PosixTime, 0xa>)
+ public ResultCode ToPosixTimeWithMyRule(ServiceCtx context)
+ {
+ return _inner.ToPosixTimeWithMyRule(context);
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.HLE/HOS/Services/Time/StaticService/ITimeZoneServiceForPsc.cs b/Ryujinx.HLE/HOS/Services/Time/StaticService/ITimeZoneServiceForPsc.cs
new file mode 100644
index 00000000..ed31fe7c
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Services/Time/StaticService/ITimeZoneServiceForPsc.cs
@@ -0,0 +1,294 @@
+using ARMeilleure.Memory;
+using Ryujinx.Common;
+using Ryujinx.Common.Logging;
+using Ryujinx.HLE.HOS.Services.Time.Clock;
+using Ryujinx.HLE.HOS.Services.Time.TimeZone;
+using Ryujinx.HLE.Utilities;
+using System;
+using System.Diagnostics;
+using System.IO;
+using System.Text;
+
+namespace Ryujinx.HLE.HOS.Services.Time.StaticService
+{
+ class ITimeZoneServiceForPsc : IpcService
+ {
+ private TimeZoneManager _timeZoneManager;
+ private bool _writePermission;
+
+ public ITimeZoneServiceForPsc(TimeZoneManager timeZoneManager, bool writePermission)
+ {
+ _timeZoneManager = timeZoneManager;
+ _writePermission = writePermission;
+ }
+
+ [Command(0)]
+ // GetDeviceLocationName() -> nn::time::LocationName
+ public ResultCode GetDeviceLocationName(ServiceCtx context)
+ {
+ ResultCode result = _timeZoneManager.GetDeviceLocationName(out string deviceLocationName);
+
+ if (result == ResultCode.Success)
+ {
+ WriteLocationName(context, deviceLocationName);
+ }
+
+ return result;
+ }
+
+ [Command(1)]
+ // SetDeviceLocationName(nn::time::LocationName)
+ public ResultCode SetDeviceLocationName(ServiceCtx context)
+ {
+ if (!_writePermission)
+ {
+ return ResultCode.PermissionDenied;
+ }
+
+ return ResultCode.NotImplemented;
+ }
+
+ [Command(2)]
+ // GetTotalLocationNameCount() -> u32
+ public ResultCode GetTotalLocationNameCount(ServiceCtx context)
+ {
+ ResultCode result = _timeZoneManager.GetTotalLocationNameCount(out uint totalLocationNameCount);
+
+ if (result == ResultCode.Success)
+ {
+ context.ResponseData.Write(totalLocationNameCount);
+ }
+
+ return ResultCode.Success;
+ }
+
+ [Command(3)]
+ // LoadLocationNameList(u32 index) -> (u32 outCount, buffer<nn::time::LocationName, 6>)
+ public ResultCode LoadLocationNameList(ServiceCtx context)
+ {
+ return ResultCode.NotImplemented;
+ }
+
+ [Command(4)]
+ // LoadTimeZoneRule(nn::time::LocationName locationName) -> buffer<nn::time::TimeZoneRule, 0x16>
+ public ResultCode LoadTimeZoneRule(ServiceCtx context)
+ {
+ return ResultCode.NotImplemented;
+ }
+
+ [Command(5)] // 2.0.0+
+ // GetTimeZoneRuleVersion() -> nn::time::TimeZoneRuleVersion
+ public ResultCode GetTimeZoneRuleVersion(ServiceCtx context)
+ {
+ ResultCode result = _timeZoneManager.GetTimeZoneRuleVersion(out UInt128 timeZoneRuleVersion);
+
+ if (result == ResultCode.Success)
+ {
+ context.ResponseData.WriteStruct(timeZoneRuleVersion);
+ }
+
+ return result;
+ }
+
+ [Command(6)] // 5.0.0+
+ // GetDeviceLocationNameAndUpdatedTime() -> (nn::time::LocationName, nn::time::SteadyClockTimePoint)
+ public ResultCode GetDeviceLocationNameAndUpdatedTime(ServiceCtx context)
+ {
+ ResultCode result = _timeZoneManager.GetDeviceLocationName(out string deviceLocationName);
+
+ if (result == ResultCode.Success)
+ {
+ result = _timeZoneManager.GetUpdatedTime(out SteadyClockTimePoint timeZoneUpdateTimePoint);
+
+ if (result == ResultCode.Success)
+ {
+ WriteLocationName(context, deviceLocationName);
+
+ // Skip padding
+ context.ResponseData.BaseStream.Position += 0x4;
+
+ context.ResponseData.WriteStruct(timeZoneUpdateTimePoint);
+ }
+ }
+
+ return result;
+ }
+
+ [Command(7)] // 9.0.0+
+ // SetDeviceLocationNameWithTimeZoneRule(nn::time::LocationName locationName, buffer<nn::time::TimeZoneBinary, 0x21> timeZoneBinary)
+ public ResultCode SetDeviceLocationNameWithTimeZoneRule(ServiceCtx context)
+ {
+ if (!_writePermission)
+ {
+ return ResultCode.PermissionDenied;
+ }
+
+ (long bufferPosition, long bufferSize) = context.Request.GetBufferType0x21();
+
+ string locationName = Encoding.ASCII.GetString(context.RequestData.ReadBytes(0x24)).TrimEnd('\0');
+
+ ResultCode result;
+
+ using (MemoryStream timeZoneBinaryStream = new MemoryStream(context.Memory.ReadBytes(bufferPosition, bufferSize)))
+ {
+ result = _timeZoneManager.SetDeviceLocationNameWithTimeZoneRule(locationName, timeZoneBinaryStream);
+ }
+
+ return result;
+ }
+
+ [Command(8)] // 9.0.0+
+ // ParseTimeZoneBinary(buffer<nn::time::TimeZoneBinary, 0x21> timeZoneBinary) -> buffer<nn::time::TimeZoneRule, 0x16>
+ public ResultCode ParseTimeZoneBinary(ServiceCtx context)
+ {
+ (long bufferPosition, long bufferSize) = context.Request.GetBufferType0x21();
+
+ long timeZoneRuleBufferPosition = context.Request.ReceiveBuff[0].Position;
+ long timeZoneRuleBufferSize = context.Request.ReceiveBuff[0].Size;
+
+ if (timeZoneRuleBufferSize != 0x4000)
+ {
+ // TODO: find error code here
+ Logger.PrintError(LogClass.ServiceTime, $"TimeZoneRule buffer size is 0x{timeZoneRuleBufferSize:x} (expected 0x4000)");
+
+ throw new InvalidOperationException();
+ }
+
+ ResultCode result;
+
+ using (MemoryStream timeZoneBinaryStream = new MemoryStream(context.Memory.ReadBytes(bufferPosition, bufferSize)))
+ {
+ result = _timeZoneManager.ParseTimeZoneRuleBinary(out TimeZoneRule timeZoneRule, timeZoneBinaryStream);
+
+ if (result == ResultCode.Success)
+ {
+ MemoryHelper.Write(context.Memory, timeZoneRuleBufferPosition, timeZoneRule);
+ }
+ }
+
+ return result;
+ }
+
+ [Command(20)] // 9.0.0+
+ // GetDeviceLocationNameOperationEventReadableHandle() -> handle<copy>
+ public ResultCode GetDeviceLocationNameOperationEventReadableHandle(ServiceCtx context)
+ {
+ return ResultCode.NotImplemented;
+ }
+
+ [Command(100)]
+ // ToCalendarTime(nn::time::PosixTime time, buffer<nn::time::TimeZoneRule, 0x15> rules) -> (nn::time::CalendarTime, nn::time::sf::CalendarAdditionalInfo)
+ public ResultCode ToCalendarTime(ServiceCtx context)
+ {
+ long posixTime = context.RequestData.ReadInt64();
+ long bufferPosition = context.Request.SendBuff[0].Position;
+ long bufferSize = context.Request.SendBuff[0].Size;
+
+ if (bufferSize != 0x4000)
+ {
+ // TODO: find error code here
+ Logger.PrintError(LogClass.ServiceTime, $"TimeZoneRule buffer size is 0x{bufferSize:x} (expected 0x4000)");
+
+ throw new InvalidOperationException();
+ }
+
+ TimeZoneRule rules = MemoryHelper.Read<TimeZoneRule>(context.Memory, bufferPosition);
+
+ ResultCode resultCode = _timeZoneManager.ToCalendarTime(rules, posixTime, out CalendarInfo calendar);
+
+ if (resultCode == 0)
+ {
+ context.ResponseData.WriteStruct(calendar);
+ }
+
+ return resultCode;
+ }
+
+ [Command(101)]
+ // ToCalendarTimeWithMyRule(nn::time::PosixTime) -> (nn::time::CalendarTime, nn::time::sf::CalendarAdditionalInfo)
+ public ResultCode ToCalendarTimeWithMyRule(ServiceCtx context)
+ {
+ long posixTime = context.RequestData.ReadInt64();
+
+ ResultCode resultCode = _timeZoneManager.ToCalendarTimeWithMyRules(posixTime, out CalendarInfo calendar);
+
+ if (resultCode == 0)
+ {
+ context.ResponseData.WriteStruct(calendar);
+ }
+
+ return resultCode;
+ }
+
+ [Command(201)]
+ // ToPosixTime(nn::time::CalendarTime calendarTime, buffer<nn::time::TimeZoneRule, 0x15> rules) -> (u32 outCount, buffer<nn::time::PosixTime, 0xa>)
+ public ResultCode ToPosixTime(ServiceCtx context)
+ {
+ long inBufferPosition = context.Request.SendBuff[0].Position;
+ long inBufferSize = context.Request.SendBuff[0].Size;
+
+ CalendarTime calendarTime = context.RequestData.ReadStruct<CalendarTime>();
+
+ if (inBufferSize != 0x4000)
+ {
+ // TODO: find error code here
+ Logger.PrintError(LogClass.ServiceTime, $"TimeZoneRule buffer size is 0x{inBufferSize:x} (expected 0x4000)");
+
+ throw new InvalidOperationException();
+ }
+
+ TimeZoneRule rules = MemoryHelper.Read<TimeZoneRule>(context.Memory, inBufferPosition);
+
+ ResultCode resultCode = _timeZoneManager.ToPosixTime(rules, calendarTime, out long posixTime);
+
+ if (resultCode == 0)
+ {
+ long outBufferPosition = context.Request.RecvListBuff[0].Position;
+ long outBufferSize = context.Request.RecvListBuff[0].Size;
+
+ context.Memory.WriteInt64(outBufferPosition, posixTime);
+ context.ResponseData.Write(1);
+ }
+
+ return resultCode;
+ }
+
+ [Command(202)]
+ // ToPosixTimeWithMyRule(nn::time::CalendarTime calendarTime) -> (u32 outCount, buffer<nn::time::PosixTime, 0xa>)
+ public ResultCode ToPosixTimeWithMyRule(ServiceCtx context)
+ {
+ CalendarTime calendarTime = context.RequestData.ReadStruct<CalendarTime>();
+
+ ResultCode resultCode = _timeZoneManager.ToPosixTimeWithMyRules(calendarTime, out long posixTime);
+
+ if (resultCode == 0)
+ {
+ long outBufferPosition = context.Request.RecvListBuff[0].Position;
+ long outBufferSize = context.Request.RecvListBuff[0].Size;
+
+ context.Memory.WriteInt64(outBufferPosition, posixTime);
+
+ // There could be only one result on one calendar as leap seconds aren't supported.
+ context.ResponseData.Write(1);
+ }
+
+ return resultCode;
+ }
+
+ private void WriteLocationName(ServiceCtx context, string locationName)
+ {
+ char[] locationNameArray = locationName.ToCharArray();
+
+ int padding = 0x24 - locationNameArray.Length;
+
+ Debug.Assert(padding >= 0, "LocationName exceeded limit (0x24 bytes)");
+
+ context.ResponseData.Write(locationNameArray);
+
+ for (int index = 0; index < padding; index++)
+ {
+ context.ResponseData.Write((byte)0);
+ }
+ }
+ }
+}
diff --git a/Ryujinx.HLE/HOS/Services/Time/TimeManager.cs b/Ryujinx.HLE/HOS/Services/Time/TimeManager.cs
new file mode 100644
index 00000000..7c5d7163
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Services/Time/TimeManager.cs
@@ -0,0 +1,184 @@
+using System;
+using System.IO;
+using Ryujinx.HLE.Exceptions;
+using Ryujinx.HLE.HOS.Kernel.Memory;
+using Ryujinx.HLE.HOS.Kernel.Threading;
+using Ryujinx.HLE.HOS.Services.Time.Clock;
+using Ryujinx.HLE.HOS.Services.Time.TimeZone;
+using Ryujinx.HLE.Utilities;
+
+namespace Ryujinx.HLE.HOS.Services.Time
+{
+ class TimeManager
+ {
+ private static TimeManager _instance;
+
+ public static TimeManager Instance
+ {
+ get
+ {
+ if (_instance == null)
+ {
+ _instance = new TimeManager();
+ }
+
+ return _instance;
+ }
+ }
+
+ public StandardSteadyClockCore StandardSteadyClock { get; }
+ public TickBasedSteadyClockCore TickBasedSteadyClock { get; }
+ public StandardLocalSystemClockCore StandardLocalSystemClock { get; }
+ public StandardNetworkSystemClockCore StandardNetworkSystemClock { get; }
+ public StandardUserSystemClockCore StandardUserSystemClock { get; }
+ public TimeZoneContentManager TimeZone { get; }
+ public EphemeralNetworkSystemClockCore EphemeralNetworkSystemClock { get; }
+ public TimeSharedMemory SharedMemory { get; }
+ public LocalSystemClockContextWriter LocalClockContextWriter { get; }
+ public NetworkSystemClockContextWriter NetworkClockContextWriter { get; }
+ public EphemeralNetworkSystemClockContextWriter EphemeralClockContextWriter { get; }
+
+ // TODO: 9.0.0+ power states and alarms
+
+ public TimeManager()
+ {
+ StandardSteadyClock = new StandardSteadyClockCore();
+ TickBasedSteadyClock = new TickBasedSteadyClockCore();
+ StandardLocalSystemClock = new StandardLocalSystemClockCore(StandardSteadyClock);
+ StandardNetworkSystemClock = new StandardNetworkSystemClockCore(StandardSteadyClock);
+ StandardUserSystemClock = new StandardUserSystemClockCore(StandardLocalSystemClock, StandardNetworkSystemClock);
+ TimeZone = new TimeZoneContentManager();
+ EphemeralNetworkSystemClock = new EphemeralNetworkSystemClockCore(StandardSteadyClock);
+ SharedMemory = new TimeSharedMemory();
+ LocalClockContextWriter = new LocalSystemClockContextWriter(SharedMemory);
+ NetworkClockContextWriter = new NetworkSystemClockContextWriter(SharedMemory);
+ EphemeralClockContextWriter = new EphemeralNetworkSystemClockContextWriter();
+ }
+
+ public void Initialize(Switch device, Horizon system, KSharedMemory sharedMemory, long timeSharedMemoryAddress, int timeSharedMemorySize)
+ {
+ SharedMemory.Initialize(device, sharedMemory, timeSharedMemoryAddress, timeSharedMemorySize);
+
+ // Here we use system on purpose as device. System isn't initialized at this point.
+ StandardUserSystemClock.CreateAutomaticCorrectionEvent(system);
+ }
+
+ public void InitializeTimeZone(Switch device)
+ {
+ TimeZone.Initialize(this, device);
+ }
+
+
+ public void SetupStandardSteadyClock(KThread thread, UInt128 clockSourceId, TimeSpanType setupValue, TimeSpanType internalOffset, TimeSpanType testOffset, bool isRtcResetDetected)
+ {
+ SetupInternalStandardSteadyClock(clockSourceId, setupValue, internalOffset, testOffset, isRtcResetDetected);
+
+ TimeSpanType currentTimePoint = StandardSteadyClock.GetCurrentRawTimePoint(thread);
+
+ SharedMemory.SetupStandardSteadyClock(thread, clockSourceId, currentTimePoint);
+
+ // TODO: propagate IPC late binding of "time:s" and "time:p"
+ }
+
+ private void SetupInternalStandardSteadyClock(UInt128 clockSourceId, TimeSpanType setupValue, TimeSpanType internalOffset, TimeSpanType testOffset, bool isRtcResetDetected)
+ {
+ StandardSteadyClock.SetClockSourceId(clockSourceId);
+ StandardSteadyClock.SetSetupValue(setupValue);
+ StandardSteadyClock.SetInternalOffset(internalOffset);
+ StandardSteadyClock.SetTestOffset(testOffset);
+
+ if (isRtcResetDetected)
+ {
+ StandardSteadyClock.SetRtcReset();
+ }
+
+ StandardSteadyClock.MarkInitialized();
+
+ // TODO: propagate IPC late binding of "time:s" and "time:p"
+ }
+
+ public void SetupStandardLocalSystemClock(KThread thread, SystemClockContext clockContext, long posixTime)
+ {
+ StandardLocalSystemClock.SetUpdateCallbackInstance(LocalClockContextWriter);
+
+ SteadyClockTimePoint currentTimePoint = StandardLocalSystemClock.GetSteadyClockCore().GetCurrentTimePoint(thread);
+ if (currentTimePoint.ClockSourceId == clockContext.SteadyTimePoint.ClockSourceId)
+ {
+ StandardLocalSystemClock.SetSystemClockContext(clockContext);
+ }
+ else
+ {
+ if (StandardLocalSystemClock.SetCurrentTime(thread, posixTime) != ResultCode.Success)
+ {
+ throw new InternalServiceException("Cannot set current local time");
+ }
+ }
+
+ StandardLocalSystemClock.MarkInitialized();
+
+ // TODO: propagate IPC late binding of "time:s" and "time:p"
+ }
+
+ public void SetupStandardNetworkSystemClock(SystemClockContext clockContext, TimeSpanType sufficientAccuracy)
+ {
+ StandardNetworkSystemClock.SetUpdateCallbackInstance(NetworkClockContextWriter);
+
+ if (StandardNetworkSystemClock.SetSystemClockContext(clockContext) != ResultCode.Success)
+ {
+ throw new InternalServiceException("Cannot set network SystemClockContext");
+ }
+
+ StandardNetworkSystemClock.SetStandardNetworkClockSufficientAccuracy(sufficientAccuracy);
+ StandardNetworkSystemClock.MarkInitialized();
+
+ // TODO: propagate IPC late binding of "time:s" and "time:p"
+ }
+
+ public void SetupTimeZoneManager(string locationName, SteadyClockTimePoint timeZoneUpdatedTimePoint, uint totalLocationNameCount, UInt128 timeZoneRuleVersion, Stream timeZoneBinaryStream)
+ {
+ if (TimeZone.Manager.SetDeviceLocationNameWithTimeZoneRule(locationName, timeZoneBinaryStream) != ResultCode.Success)
+ {
+ throw new InternalServiceException("Cannot set DeviceLocationName with a given TimeZoneBinary");
+ }
+
+ TimeZone.Manager.SetUpdatedTime(timeZoneUpdatedTimePoint, true);
+ TimeZone.Manager.SetTotalLocationNameCount(totalLocationNameCount);
+ TimeZone.Manager.SetTimeZoneRuleVersion(timeZoneRuleVersion);
+ TimeZone.Manager.MarkInitialized();
+
+ // TODO: propagate IPC late binding of "time:s" and "time:p"
+ }
+
+ public void SetupEphemeralNetworkSystemClock()
+ {
+ EphemeralNetworkSystemClock.SetUpdateCallbackInstance(EphemeralClockContextWriter);
+ EphemeralNetworkSystemClock.MarkInitialized();
+
+ // TODO: propagate IPC late binding of "time:s" and "time:p"
+ }
+
+ public void SetupStandardUserSystemClock(KThread thread, bool isAutomaticCorrectionEnabled, SteadyClockTimePoint steadyClockTimePoint)
+ {
+ if (StandardUserSystemClock.SetAutomaticCorrectionEnabled(thread, isAutomaticCorrectionEnabled) != ResultCode.Success)
+ {
+ throw new InternalServiceException("Cannot set automatic user time correction state");
+ }
+
+ StandardUserSystemClock.SetAutomaticCorrectionUpdatedTime(steadyClockTimePoint);
+ StandardUserSystemClock.MarkInitialized();
+
+ SharedMemory.SetAutomaticCorrectionEnabled(isAutomaticCorrectionEnabled);
+
+ // TODO: propagate IPC late binding of "time:s" and "time:p"
+ }
+
+ public void SetStandardSteadyClockRtcOffset(KThread thread, TimeSpanType rtcOffset)
+ {
+ StandardSteadyClock.SetSetupValue(rtcOffset);
+
+ TimeSpanType currentTimePoint = StandardSteadyClock.GetCurrentRawTimePoint(thread);
+
+ SharedMemory.SetSteadyClockRawTimePoint(thread, currentTimePoint);
+ }
+ }
+}
diff --git a/Ryujinx.HLE/HOS/Services/Time/TimeSharedMemory.cs b/Ryujinx.HLE/HOS/Services/Time/TimeSharedMemory.cs
new file mode 100644
index 00000000..f714c662
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Services/Time/TimeSharedMemory.cs
@@ -0,0 +1,126 @@
+using System;
+using System.Runtime.InteropServices;
+using System.Threading;
+using Ryujinx.HLE.HOS.Kernel.Memory;
+using Ryujinx.HLE.HOS.Kernel.Threading;
+using Ryujinx.HLE.HOS.Services.Time.Clock;
+using Ryujinx.HLE.HOS.Services.Time.Types;
+using Ryujinx.HLE.Utilities;
+
+namespace Ryujinx.HLE.HOS.Services.Time
+{
+ class TimeSharedMemory
+ {
+ private Switch _device;
+ private KSharedMemory _sharedMemory;
+ private long _timeSharedMemoryAddress;
+ private int _timeSharedMemorySize;
+
+ private const uint SteadyClockContextOffset = 0x00;
+ private const uint LocalSystemClockContextOffset = 0x38;
+ private const uint NetworkSystemClockContextOffset = 0x80;
+ private const uint AutomaticCorrectionEnabledOffset = 0xC8;
+
+ public void Initialize(Switch device, KSharedMemory sharedMemory, long timeSharedMemoryAddress, int timeSharedMemorySize)
+ {
+ _device = device;
+ _sharedMemory = sharedMemory;
+ _timeSharedMemoryAddress = timeSharedMemoryAddress;
+ _timeSharedMemorySize = timeSharedMemorySize;
+
+ // Clean the shared memory
+ _device.Memory.FillWithZeros(_timeSharedMemoryAddress, _timeSharedMemorySize);
+ }
+
+ public KSharedMemory GetSharedMemory()
+ {
+ return _sharedMemory;
+ }
+
+ public void SetupStandardSteadyClock(KThread thread, UInt128 clockSourceId, TimeSpanType currentTimePoint)
+ {
+ TimeSpanType ticksTimeSpan;
+
+ // As this may be called before the guest code, we support passing a null thread to make this api usable.
+ if (thread == null)
+ {
+ ticksTimeSpan = TimeSpanType.FromSeconds(0);
+ }
+ else
+ {
+ ticksTimeSpan = TimeSpanType.FromTicks(thread.Context.CntpctEl0, thread.Context.CntfrqEl0);
+ }
+
+ SteadyClockContext context = new SteadyClockContext
+ {
+ InternalOffset = (ulong)(currentTimePoint.NanoSeconds - ticksTimeSpan.NanoSeconds),
+ ClockSourceId = clockSourceId
+ };
+
+ WriteObjectToSharedMemory(SteadyClockContextOffset, 4, context);
+ }
+
+ public void SetAutomaticCorrectionEnabled(bool isAutomaticCorrectionEnabled)
+ {
+ // We convert the bool to byte here as a bool in C# takes 4 bytes...
+ WriteObjectToSharedMemory(AutomaticCorrectionEnabledOffset, 0, Convert.ToByte(isAutomaticCorrectionEnabled));
+ }
+
+ public void SetSteadyClockRawTimePoint(KThread thread, TimeSpanType currentTimePoint)
+ {
+ SteadyClockContext context = ReadObjectFromSharedMemory<SteadyClockContext>(SteadyClockContextOffset, 4);
+ TimeSpanType ticksTimeSpan = TimeSpanType.FromTicks(thread.Context.CntpctEl0, thread.Context.CntfrqEl0);
+
+ context.InternalOffset = (ulong)(currentTimePoint.NanoSeconds - ticksTimeSpan.NanoSeconds);
+
+ WriteObjectToSharedMemory(SteadyClockContextOffset, 4, context);
+ }
+
+ public void UpdateLocalSystemClockContext(SystemClockContext context)
+ {
+ WriteObjectToSharedMemory(LocalSystemClockContextOffset, 4, context);
+ }
+
+ public void UpdateNetworkSystemClockContext(SystemClockContext context)
+ {
+ WriteObjectToSharedMemory(NetworkSystemClockContextOffset, 4, context);
+ }
+
+ private T ReadObjectFromSharedMemory<T>(long offset, long padding)
+ {
+ long indexOffset = _timeSharedMemoryAddress + offset;
+
+ T result;
+ uint index;
+ uint possiblyNewIndex;
+
+ do
+ {
+ index = _device.Memory.ReadUInt32(indexOffset);
+
+ long objectOffset = indexOffset + 4 + padding + (index & 1) * Marshal.SizeOf<T>();
+
+ result = _device.Memory.ReadStruct<T>(objectOffset);
+
+ Thread.MemoryBarrier();
+
+ possiblyNewIndex = _device.Memory.ReadUInt32(indexOffset);
+ } while (index != possiblyNewIndex);
+
+ return result;
+ }
+
+ private void WriteObjectToSharedMemory<T>(long offset, long padding, T value)
+ {
+ long indexOffset = _timeSharedMemoryAddress + offset;
+ uint newIndex = _device.Memory.ReadUInt32(indexOffset) + 1;
+ long objectOffset = indexOffset + 4 + padding + (newIndex & 1) * Marshal.SizeOf<T>();
+
+ _device.Memory.WriteStruct(objectOffset, value);
+
+ Thread.MemoryBarrier();
+
+ _device.Memory.WriteUInt32(indexOffset, newIndex);
+ }
+ }
+}
diff --git a/Ryujinx.HLE/HOS/Services/Time/TimeZone/TimeZone.cs b/Ryujinx.HLE/HOS/Services/Time/TimeZone/TimeZone.cs
index 3a98013e..b32a9795 100644
--- a/Ryujinx.HLE/HOS/Services/Time/TimeZone/TimeZone.cs
+++ b/Ryujinx.HLE/HOS/Services/Time/TimeZone/TimeZone.cs
@@ -903,7 +903,7 @@ namespace Ryujinx.HLE.HOS.Services.Time.TimeZone
return ParsePosixName(name.ToCharArray(), out outRules, false);
}
- internal static unsafe bool LoadTimeZoneRules(out TimeZoneRule outRules, Stream inputData)
+ internal static unsafe bool ParseTimeZoneBinary(out TimeZoneRule outRules, Stream inputData)
{
outRules = new TimeZoneRule
{
@@ -1357,10 +1357,8 @@ namespace Ryujinx.HLE.HOS.Services.Time.TimeZone
int[] ip = MonthsLengths[IsLeap((int)year)];
- while (dayOfYear >= ip[calendarTime.Month])
+ for (calendarTime.Month = 0; dayOfYear >= ip[calendarTime.Month]; ++calendarTime.Month)
{
- calendarTime.Month += 1;
-
dayOfYear -= ip[calendarTime.Month];
}
@@ -1709,7 +1707,7 @@ namespace Ryujinx.HLE.HOS.Services.Time.TimeZone
Time = new CalendarTime()
{
Year = (short)calendarTime.Year,
- Month = calendarTime.Month,
+ Month = (sbyte)(calendarTime.Month + 1),
Day = calendarTime.Day,
Hour = calendarTime.Hour,
Minute = calendarTime.Minute,
diff --git a/Ryujinx.HLE/HOS/Services/Time/TimeZone/TimeZoneContentManager.cs b/Ryujinx.HLE/HOS/Services/Time/TimeZone/TimeZoneContentManager.cs
new file mode 100644
index 00000000..f02781b3
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Services/Time/TimeZone/TimeZoneContentManager.cs
@@ -0,0 +1,191 @@
+using LibHac.Fs;
+using LibHac.Fs.NcaUtils;
+using Ryujinx.Common.Logging;
+using Ryujinx.HLE.FileSystem;
+using Ryujinx.HLE.HOS.Services.Time.Clock;
+using Ryujinx.HLE.Resource;
+using Ryujinx.HLE.Utilities;
+using System.Collections.Generic;
+using System.IO;
+
+using static Ryujinx.HLE.HOS.Services.Time.TimeZone.TimeZoneRule;
+
+namespace Ryujinx.HLE.HOS.Services.Time.TimeZone
+{
+ class TimeZoneContentManager
+ {
+ private const long TimeZoneBinaryTitleId = 0x010000000000080E;
+
+ private Switch _device;
+ private string[] _locationNameCache;
+
+ public TimeZoneManager Manager { get; private set; }
+
+ public TimeZoneContentManager()
+ {
+ Manager = new TimeZoneManager();
+ }
+
+ internal void Initialize(TimeManager timeManager, Switch device)
+ {
+ _device = device;
+
+ InitializeLocationNameCache();
+
+ SteadyClockTimePoint timeZoneUpdatedTimePoint = timeManager.StandardSteadyClock.GetCurrentTimePoint(null);
+
+ ResultCode result = GetTimeZoneBinary("UTC", out Stream timeZoneBinaryStream);
+
+ if (result == ResultCode.Success)
+ {
+ // TODO: Read TimeZoneVersion from sysarchive.
+ timeManager.SetupTimeZoneManager("UTC", timeZoneUpdatedTimePoint, (uint)_locationNameCache.Length, new UInt128(), timeZoneBinaryStream);
+ }
+ else
+ {
+ // In the case the user don't have the timezone system archive, we just mark the manager as initialized.
+ Manager.MarkInitialized();
+ }
+ }
+
+ private void InitializeLocationNameCache()
+ {
+ if (HasTimeZoneBinaryTitle())
+ {
+ using (IStorage ncaFileStream = new LocalStorage(_device.FileSystem.SwitchPathToSystemPath(GetTimeZoneBinaryTitleContentPath()), FileAccess.Read, FileMode.Open))
+ {
+ Nca nca = new Nca(_device.System.KeySet, ncaFileStream);
+ IFileSystem romfs = nca.OpenFileSystem(NcaSectionType.Data, _device.System.FsIntegrityCheckLevel);
+ Stream binaryListStream = romfs.OpenFile("binaryList.txt", OpenMode.Read).AsStream();
+
+ StreamReader reader = new StreamReader(binaryListStream);
+
+ List<string> locationNameList = new List<string>();
+
+ string locationName;
+ while ((locationName = reader.ReadLine()) != null)
+ {
+ locationNameList.Add(locationName);
+ }
+
+ _locationNameCache = locationNameList.ToArray();
+ }
+ }
+ else
+ {
+ _locationNameCache = new string[0];
+
+ Logger.PrintWarning(LogClass.ServiceTime, "TimeZoneBinary system title not found! TimeZone conversions will not work, provide the system archive to fix this warning. (See https://github.com/Ryujinx/Ryujinx#requirements for more informations)");
+ }
+ }
+
+ private bool IsLocationNameValid(string locationName)
+ {
+ foreach (string cachedLocationName in _locationNameCache)
+ {
+ if (cachedLocationName.Equals(locationName))
+ {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ public ResultCode SetDeviceLocationName(string locationName)
+ {
+ ResultCode result = GetTimeZoneBinary(locationName, out Stream timeZoneBinaryStream);
+
+ if (result == ResultCode.Success)
+ {
+ result = Manager.SetDeviceLocationNameWithTimeZoneRule(locationName, timeZoneBinaryStream);
+ }
+
+ return result;
+ }
+
+ public ResultCode LoadLocationNameList(uint index, out string[] outLocationNameArray, uint maxLength)
+ {
+ List<string> locationNameList = new List<string>();
+
+ for (int i = 0; i < _locationNameCache.Length && i < maxLength; i++)
+ {
+ if (i < index)
+ {
+ continue;
+ }
+
+ string locationName = _locationNameCache[i];
+
+ // If the location name is too long, error out.
+ if (locationName.Length > 0x24)
+ {
+ outLocationNameArray = new string[0];
+
+ return ResultCode.LocationNameTooLong;
+ }
+
+ locationNameList.Add(locationName);
+ }
+
+ outLocationNameArray = locationNameList.ToArray();
+
+ return ResultCode.Success;
+ }
+
+ public string GetTimeZoneBinaryTitleContentPath()
+ {
+ return _device.System.ContentManager.GetInstalledContentPath(TimeZoneBinaryTitleId, StorageId.NandSystem, ContentType.Data);
+ }
+
+ public bool HasTimeZoneBinaryTitle()
+ {
+ return !string.IsNullOrEmpty(GetTimeZoneBinaryTitleContentPath());
+ }
+
+ internal ResultCode GetTimeZoneBinary(string locationName, out Stream timeZoneBinaryStream)
+ {
+ timeZoneBinaryStream = null;
+
+ if (!IsLocationNameValid(locationName))
+ {
+ return ResultCode.TimeZoneNotFound;
+ }
+
+ using (IStorage ncaFileStream = new LocalStorage(_device.FileSystem.SwitchPathToSystemPath(GetTimeZoneBinaryTitleContentPath()), FileAccess.Read, FileMode.Open))
+ {
+ Nca nca = new Nca(_device.System.KeySet, ncaFileStream);
+ IFileSystem romfs = nca.OpenFileSystem(NcaSectionType.Data, _device.System.FsIntegrityCheckLevel);
+
+ timeZoneBinaryStream = romfs.OpenFile($"zoneinfo/{locationName}", OpenMode.Read).AsStream();
+ }
+
+ return ResultCode.Success;
+ }
+
+ internal ResultCode LoadTimeZoneRule(out TimeZoneRule outRules, string locationName)
+ {
+ outRules = new TimeZoneRule
+ {
+ Ats = new long[TzMaxTimes],
+ Types = new byte[TzMaxTimes],
+ Ttis = new TimeTypeInfo[TzMaxTypes],
+ Chars = new char[TzCharsArraySize]
+ };
+
+ if (!HasTimeZoneBinaryTitle())
+ {
+ throw new InvalidSystemResourceException($"TimeZoneBinary system title not found! Please provide it. (See https://github.com/Ryujinx/Ryujinx#requirements for more informations)");
+ }
+
+ ResultCode result = GetTimeZoneBinary(locationName, out Stream timeZoneBinaryStream);
+
+ if (result == ResultCode.Success)
+ {
+ result = Manager.ParseTimeZoneRuleBinary(out outRules, timeZoneBinaryStream);
+ }
+
+ return result;
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.HLE/HOS/Services/Time/TimeZone/TimeZoneManager.cs b/Ryujinx.HLE/HOS/Services/Time/TimeZone/TimeZoneManager.cs
index 2497f6a3..1a80365a 100644
--- a/Ryujinx.HLE/HOS/Services/Time/TimeZone/TimeZoneManager.cs
+++ b/Ryujinx.HLE/HOS/Services/Time/TimeZone/TimeZoneManager.cs
@@ -1,50 +1,28 @@
-using LibHac.Fs;
-using LibHac.Fs.NcaUtils;
-using Ryujinx.Common.Logging;
-using Ryujinx.HLE.FileSystem;
-using System;
-using System.Collections.Generic;
-using System.Collections.ObjectModel;
+using Ryujinx.HLE.HOS.Services.Time.Clock;
+using Ryujinx.HLE.Utilities;
using System.IO;
-using TimeZoneConverter;
-using TimeZoneConverter.Posix;
-
using static Ryujinx.HLE.HOS.Services.Time.TimeZone.TimeZoneRule;
namespace Ryujinx.HLE.HOS.Services.Time.TimeZone
{
- public sealed class TimeZoneManager
+ class TimeZoneManager
{
- private const long TimeZoneBinaryTitleId = 0x010000000000080E;
-
- private static TimeZoneManager instance;
-
- private static object instanceLock = new object();
-
- private Switch _device;
- private TimeZoneRule _myRules;
- private string _deviceLocationName;
- private string[] _locationNameCache;
-
- public static TimeZoneManager Instance
+ private bool _isInitialized;
+ private TimeZoneRule _myRules;
+ private string _deviceLocationName;
+ private UInt128 _timeZoneRuleVersion;
+ private uint _totalLocationNameCount;
+ private SteadyClockTimePoint _timeZoneUpdateTimePoint;
+ private object _lock;
+
+ public TimeZoneManager()
{
- get
- {
- lock (instanceLock)
- {
- if (instance == null)
- {
- instance = new TimeZoneManager();
- }
-
- return instance;
- }
- }
- }
+ _isInitialized = false;
+ _deviceLocationName = "UTC";
+ _timeZoneRuleVersion = new UInt128();
+ _lock = new object();
- TimeZoneManager()
- {
- // Empty rules (UTC)
+ // Empty rules
_myRules = new TimeZoneRule
{
Ats = new long[TzMaxTimes],
@@ -53,236 +31,237 @@ namespace Ryujinx.HLE.HOS.Services.Time.TimeZone
Chars = new char[TzCharsArraySize]
};
- _deviceLocationName = "UTC";
+ _timeZoneUpdateTimePoint = SteadyClockTimePoint.GetRandom();
}
- internal void Initialize(Switch device)
+ public bool IsInitialized()
{
- _device = device;
+ bool res;
+
+ lock (_lock)
+ {
+ res = _isInitialized;
+ }
- InitializeLocationNameCache();
+ return res;
}
- private void InitializeLocationNameCache()
+ public void MarkInitialized()
{
- if (HasTimeZoneBinaryTitle())
+ lock (_lock)
{
- using (IStorage ncaFileStream = new LocalStorage(_device.FileSystem.SwitchPathToSystemPath(GetTimeZoneBinaryTitleContentPath()), FileAccess.Read, FileMode.Open))
- {
- Nca nca = new Nca(_device.System.KeySet, ncaFileStream);
- IFileSystem romfs = nca.OpenFileSystem(NcaSectionType.Data, _device.System.FsIntegrityCheckLevel);
- Stream binaryListStream = romfs.OpenFile("binaryList.txt", OpenMode.Read).AsStream();
-
- StreamReader reader = new StreamReader(binaryListStream);
+ _isInitialized = true;
+ }
+ }
- List<string> locationNameList = new List<string>();
+ public ResultCode GetDeviceLocationName(out string deviceLocationName)
+ {
+ ResultCode result = ResultCode.UninitializedClock;
- string locationName;
- while ((locationName = reader.ReadLine()) != null)
- {
- locationNameList.Add(locationName);
- }
+ deviceLocationName = null;
- _locationNameCache = locationNameList.ToArray();
+ lock (_lock)
+ {
+ if (_isInitialized)
+ {
+ deviceLocationName = _deviceLocationName;
+ result = ResultCode.Success;
}
}
- else
- {
- ReadOnlyCollection<TimeZoneInfo> timeZoneInfos = TimeZoneInfo.GetSystemTimeZones();
- _locationNameCache = new string[timeZoneInfos.Count];
- int i = 0;
+ return result;
+ }
- foreach (TimeZoneInfo timeZoneInfo in timeZoneInfos)
- {
- bool needConversion = TZConvert.TryWindowsToIana(timeZoneInfo.Id, out string convertedName);
- if (needConversion)
- {
- _locationNameCache[i] = convertedName;
- }
- else
- {
- _locationNameCache[i] = timeZoneInfo.Id;
- }
- i++;
- }
+ public ResultCode SetDeviceLocationNameWithTimeZoneRule(string locationName, Stream timeZoneBinaryStream)
+ {
+ ResultCode result = ResultCode.TimeZoneConversionFailed;
- // As we aren't using the system archive, "UTC" might not exist on the host system.
- // Load from C# TimeZone APIs UTC id.
- string utcId = TimeZoneInfo.Utc.Id;
- bool utcNeedConversion = TZConvert.TryWindowsToIana(utcId, out string utcConvertedName);
- if (utcNeedConversion)
+ lock (_lock)
+ {
+ bool timeZoneConversionSuccess = TimeZone.ParseTimeZoneBinary(out TimeZoneRule rules, timeZoneBinaryStream);
+
+ if (timeZoneConversionSuccess)
{
- utcId = utcConvertedName;
+ _deviceLocationName = locationName;
+ _myRules = rules;
+ result = ResultCode.Success;
}
+ }
- _deviceLocationName = utcId;
+ return result;
+ }
+
+ public void SetTotalLocationNameCount(uint totalLocationNameCount)
+ {
+ lock (_lock)
+ {
+ _totalLocationNameCount = totalLocationNameCount;
}
}
- private bool IsLocationNameValid(string locationName)
+ public ResultCode GetTotalLocationNameCount(out uint totalLocationNameCount)
{
- foreach (string cachedLocationName in _locationNameCache)
+ ResultCode result = ResultCode.UninitializedClock;
+
+ totalLocationNameCount = 0;
+
+ lock (_lock)
{
- if (cachedLocationName.Equals(locationName))
+ if (_isInitialized)
{
- return true;
+ totalLocationNameCount = _totalLocationNameCount;
+ result = ResultCode.Success;
}
}
- return false;
- }
- public string GetDeviceLocationName()
- {
- return _deviceLocationName;
+ return result;
}
- public ResultCode SetDeviceLocationName(string locationName)
+ public ResultCode SetUpdatedTime(SteadyClockTimePoint timeZoneUpdatedTimePoint, bool bypassUninitialized = false)
{
- ResultCode resultCode = LoadTimeZoneRules(out TimeZoneRule rules, locationName);
+ ResultCode result = ResultCode.UninitializedClock;
- if (resultCode == 0)
+ lock (_lock)
{
- _myRules = rules;
- _deviceLocationName = locationName;
+ if (_isInitialized || bypassUninitialized)
+ {
+ _timeZoneUpdateTimePoint = timeZoneUpdatedTimePoint;
+ result = ResultCode.Success;
+ }
}
- return resultCode;
+ return result;
}
- public ResultCode LoadLocationNameList(uint index, out string[] outLocationNameArray, uint maxLength)
+ public ResultCode GetUpdatedTime(out SteadyClockTimePoint timeZoneUpdatedTimePoint)
{
- List<string> locationNameList = new List<string>();
+ ResultCode result;
- for (int i = 0; i < _locationNameCache.Length && i < maxLength; i++)
+ lock (_lock)
{
- if (i < index)
+ if (_isInitialized)
{
- continue;
+ timeZoneUpdatedTimePoint = _timeZoneUpdateTimePoint;
+ result = ResultCode.Success;
}
-
- string locationName = _locationNameCache[i];
-
- // If the location name is too long, error out.
- if (locationName.Length > 0x24)
+ else
{
- outLocationNameArray = new string[0];
-
- return ResultCode.LocationNameTooLong;
+ timeZoneUpdatedTimePoint = SteadyClockTimePoint.GetRandom();
+ result = ResultCode.UninitializedClock;
}
-
- locationNameList.Add(locationName);
}
- outLocationNameArray = locationNameList.ToArray();
-
- return ResultCode.Success;
+ return result;
}
- public uint GetTotalLocationNameCount()
+ public ResultCode ParseTimeZoneRuleBinary(out TimeZoneRule outRules, Stream timeZoneBinaryStream)
{
- return (uint)_locationNameCache.Length;
- }
+ ResultCode result = ResultCode.Success;
- public string GetTimeZoneBinaryTitleContentPath()
- {
- return _device.System.ContentManager.GetInstalledContentPath(TimeZoneBinaryTitleId, StorageId.NandSystem, ContentType.Data);
- }
+ lock (_lock)
+ {
+ bool timeZoneConversionSuccess = TimeZone.ParseTimeZoneBinary(out outRules, timeZoneBinaryStream);
- public bool HasTimeZoneBinaryTitle()
- {
- return !string.IsNullOrEmpty(GetTimeZoneBinaryTitleContentPath());
+ if (!timeZoneConversionSuccess)
+ {
+ result = ResultCode.TimeZoneConversionFailed;
+ }
+ }
+
+ return result;
}
- internal ResultCode LoadTimeZoneRules(out TimeZoneRule outRules, string locationName)
+ public void SetTimeZoneRuleVersion(UInt128 timeZoneRuleVersion)
{
- outRules = new TimeZoneRule
+ lock (_lock)
{
- Ats = new long[TzMaxTimes],
- Types = new byte[TzMaxTimes],
- Ttis = new TimeTypeInfo[TzMaxTypes],
- Chars = new char[TzCharsArraySize]
- };
-
- if (!IsLocationNameValid(locationName))
- {
- return ResultCode.TimeZoneNotFound;
+ _timeZoneRuleVersion = timeZoneRuleVersion;
}
+ }
+
+ public ResultCode GetTimeZoneRuleVersion(out UInt128 timeZoneRuleVersion)
+ {
+ ResultCode result;
- if (!HasTimeZoneBinaryTitle())
+ lock (_lock)
{
- // If the user doesn't have the system archives, we generate a POSIX rule string and parse it to generate a incomplete TimeZoneRule
- // TODO: As for now not having system archives is fine, we should enforce the usage of system archives later.
- Logger.PrintWarning(LogClass.ServiceTime, "TimeZoneBinary system archive not found! Time conversions will not be accurate!");
- try
+ if (_isInitialized)
{
- TimeZoneInfo info = TZConvert.GetTimeZoneInfo(locationName);
- string posixRule = PosixTimeZone.FromTimeZoneInfo(info);
-
- if (!TimeZone.ParsePosixName(posixRule, out outRules))
- {
- return ResultCode.TimeZoneConversionFailed;
- }
-
- return 0;
+ timeZoneRuleVersion = _timeZoneRuleVersion;
+ result = ResultCode.Success;
}
- catch (TimeZoneNotFoundException)
+ else
{
- Logger.PrintWarning(LogClass.ServiceTime, $"Timezone not found for string: {locationName})");
-
- return ResultCode.TimeZoneNotFound;
+ timeZoneRuleVersion = new UInt128();
+ result = ResultCode.UninitializedClock;
}
}
- else
+
+ return result;
+ }
+
+ public ResultCode ToCalendarTimeWithMyRules(long time, out CalendarInfo calendar)
+ {
+ ResultCode result;
+
+ lock (_lock)
{
- using (IStorage ncaFileStream = new LocalStorage(_device.FileSystem.SwitchPathToSystemPath(GetTimeZoneBinaryTitleContentPath()), FileAccess.Read, FileMode.Open))
+ if (_isInitialized)
{
- Nca nca = new Nca(_device.System.KeySet, ncaFileStream);
- IFileSystem romfs = nca.OpenFileSystem(NcaSectionType.Data, _device.System.FsIntegrityCheckLevel);
- Stream tzIfStream = romfs.OpenFile($"zoneinfo/{locationName}", OpenMode.Read).AsStream();
-
- if (!TimeZone.LoadTimeZoneRules(out outRules, tzIfStream))
- {
- return ResultCode.TimeZoneConversionFailed;
- }
+ result = ToCalendarTime(_myRules, time, out calendar);
+ }
+ else
+ {
+ calendar = new CalendarInfo();
+ result = ResultCode.UninitializedClock;
}
-
- return 0;
}
- }
- internal ResultCode ToCalendarTimeWithMyRules(long time, out CalendarInfo calendar)
- {
- return ToCalendarTime(_myRules, time, out calendar);
+ return result;
}
- internal static ResultCode ToCalendarTime(TimeZoneRule rules, long time, out CalendarInfo calendar)
+ public ResultCode ToCalendarTime(TimeZoneRule rules, long time, out CalendarInfo calendar)
{
- ResultCode error = TimeZone.ToCalendarTime(rules, time, out calendar);
+ ResultCode result;
- if (error != ResultCode.Success)
+ lock (_lock)
{
- return error;
+ result = TimeZone.ToCalendarTime(rules, time, out calendar);
}
- return ResultCode.Success;
+ return result;
}
- internal ResultCode ToPosixTimeWithMyRules(CalendarTime calendarTime, out long posixTime)
+ public ResultCode ToPosixTimeWithMyRules(CalendarTime calendarTime, out long posixTime)
{
- return ToPosixTime(_myRules, calendarTime, out posixTime);
+ ResultCode result;
+
+ lock (_lock)
+ {
+ if (_isInitialized)
+ {
+ result = ToPosixTime(_myRules, calendarTime, out posixTime);
+ }
+ else
+ {
+ posixTime = 0;
+ result = ResultCode.UninitializedClock;
+ }
+ }
+
+ return result;
}
- internal static ResultCode ToPosixTime(TimeZoneRule rules, CalendarTime calendarTime, out long posixTime)
+ public ResultCode ToPosixTime(TimeZoneRule rules, CalendarTime calendarTime, out long posixTime)
{
- ResultCode error = TimeZone.ToPosixTime(rules, calendarTime, out posixTime);
+ ResultCode result;
- if (error != ResultCode.Success)
+ lock (_lock)
{
- return error;
+ result = TimeZone.ToPosixTime(rules, calendarTime, out posixTime);
}
- return ResultCode.Success;
+ return result;
}
}
-} \ No newline at end of file
+}
diff --git a/Ryujinx.HLE/HOS/Services/Time/Types/SteadyClockContext.cs b/Ryujinx.HLE/HOS/Services/Time/Types/SteadyClockContext.cs
new file mode 100644
index 00000000..4cf1fc99
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Services/Time/Types/SteadyClockContext.cs
@@ -0,0 +1,12 @@
+using Ryujinx.HLE.Utilities;
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.HLE.HOS.Services.Time.Types
+{
+ [StructLayout(LayoutKind.Sequential)]
+ struct SteadyClockContext
+ {
+ public ulong InternalOffset;
+ public UInt128 ClockSourceId;
+ }
+}
diff --git a/Ryujinx.HLE/HOS/Services/Time/Types/TimePermissions.cs b/Ryujinx.HLE/HOS/Services/Time/Types/TimePermissions.cs
index 823c8288..3fcd3a14 100644
--- a/Ryujinx.HLE/HOS/Services/Time/Types/TimePermissions.cs
+++ b/Ryujinx.HLE/HOS/Services/Time/Types/TimePermissions.cs
@@ -8,10 +8,15 @@ namespace Ryujinx.HLE.HOS.Services.Time
LocalSystemClockWritableMask = 0x1,
UserSystemClockWritableMask = 0x2,
NetworkSystemClockWritableMask = 0x4,
- UnknownPermissionMask = 0x8,
+ TimeZoneWritableMask = 0x8,
+ SteadyClockWritableMask = 0x10,
+ BypassUninitialized = 0x20,
- User = 0,
- Applet = LocalSystemClockWritableMask | UserSystemClockWritableMask | UnknownPermissionMask,
- System = NetworkSystemClockWritableMask
+ User = 0,
+ Admin = LocalSystemClockWritableMask | UserSystemClockWritableMask | TimeZoneWritableMask,
+ System = NetworkSystemClockWritableMask,
+ SystemUpdate = BypassUninitialized,
+ Repair = SteadyClockWritableMask,
+ Manufacture = LocalSystemClockWritableMask | UserSystemClockWritableMask | NetworkSystemClockWritableMask | TimeZoneWritableMask | SteadyClockWritableMask
}
}