From 1aba033ba7868d29f5f840c99ee11dd29d689972 Mon Sep 17 00:00:00 2001 From: Thomas Guillemard Date: Tue, 8 Oct 2019 05:48:49 +0200 Subject: [PATCH] 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 --- README.md | 2 +- Ryujinx.HLE/DeviceMemory.cs | 5 + .../Exceptions/InternalServiceException.cs | 9 + .../FileSystem/Content/ContentManager.cs | 4 +- Ryujinx.HLE/HOS/Horizon.cs | 31 +- .../HOS/Services/Settings/NxSettings.cs | 1 + ...phemeralNetworkSystemClockContextWriter.cs | 10 + .../Clock/EphemeralNetworkSystemClockCore.cs | 20 - .../Clock/LocalSystemClockContextWriter.cs | 19 + .../Clock/NetworkSystemClockContextWriter.cs | 19 + .../Clock/StandardLocalSystemClockCore.cs | 22 - .../Clock/StandardNetworkSystemClockCore.cs | 24 +- .../Time/Clock/StandardSteadyClockCore.cs | 89 ++-- .../Time/Clock/StandardUserSystemClockCore.cs | 60 ++- .../Services/Time/Clock/SteadyClockCore.cs | 44 +- .../Clock/SystemClockContextUpdateCallback.cs | 72 +++ .../Services/Time/Clock/SystemClockCore.cs | 111 ++++- .../Time/Clock/TickBasedSteadyClockCore.cs | 29 +- .../Time/Clock/Types/SteadyClockTimePoint.cs | 11 +- .../Services/Time/Clock/Types/TimeSpanType.cs | 2 + .../Time/IPowerStateRequestHandler.cs | 2 +- .../Services/Time/IStaticServiceForGlue.cs | 182 ++++++++ ...aticService.cs => IStaticServiceForPsc.cs} | 145 ++++-- .../HOS/Services/Time/ITimeServiceManager.cs | 219 ++++++++- Ryujinx.HLE/HOS/Services/Time/ResultCode.cs | 1 + .../Time/StaticService/ISteadyClock.cs | 83 +++- .../Time/StaticService/ISystemClock.cs | 91 ++-- .../Time/StaticService/ITimeZoneService.cs | 218 --------- .../StaticService/ITimeZoneServiceForGlue.cs | 142 ++++++ .../StaticService/ITimeZoneServiceForPsc.cs | 294 ++++++++++++ Ryujinx.HLE/HOS/Services/Time/TimeManager.cs | 184 ++++++++ .../HOS/Services/Time/TimeSharedMemory.cs | 126 ++++++ .../HOS/Services/Time/TimeZone/TimeZone.cs | 8 +- .../Time/TimeZone/TimeZoneContentManager.cs | 191 ++++++++ .../Services/Time/TimeZone/TimeZoneManager.cs | 423 +++++++++--------- .../Services/Time/Types/SteadyClockContext.cs | 12 + .../Services/Time/Types/TimePermissions.cs | 13 +- 37 files changed, 2202 insertions(+), 716 deletions(-) create mode 100644 Ryujinx.HLE/Exceptions/InternalServiceException.cs create mode 100644 Ryujinx.HLE/HOS/Services/Time/Clock/EphemeralNetworkSystemClockContextWriter.cs create mode 100644 Ryujinx.HLE/HOS/Services/Time/Clock/LocalSystemClockContextWriter.cs create mode 100644 Ryujinx.HLE/HOS/Services/Time/Clock/NetworkSystemClockContextWriter.cs create mode 100644 Ryujinx.HLE/HOS/Services/Time/Clock/SystemClockContextUpdateCallback.cs create mode 100644 Ryujinx.HLE/HOS/Services/Time/IStaticServiceForGlue.cs rename Ryujinx.HLE/HOS/Services/Time/{IStaticService.cs => IStaticServiceForPsc.cs} (64%) delete mode 100644 Ryujinx.HLE/HOS/Services/Time/StaticService/ITimeZoneService.cs create mode 100644 Ryujinx.HLE/HOS/Services/Time/StaticService/ITimeZoneServiceForGlue.cs create mode 100644 Ryujinx.HLE/HOS/Services/Time/StaticService/ITimeZoneServiceForPsc.cs create mode 100644 Ryujinx.HLE/HOS/Services/Time/TimeManager.cs create mode 100644 Ryujinx.HLE/HOS/Services/Time/TimeSharedMemory.cs create mode 100644 Ryujinx.HLE/HOS/Services/Time/TimeZone/TimeZoneContentManager.cs create mode 100644 Ryujinx.HLE/HOS/Services/Time/Types/SteadyClockContext.cs diff --git a/README.md b/README.md index 426ee40a2..be3d1fea1 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 0ead17473..38864bc2d 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(long position) + { + return Marshal.PtrToStructure((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 000000000..b940c51c8 --- /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 fe6642c3e..9ed2e1422 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 86b283205..80c9ef0cb 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 b679005e6..ca3853e8b 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 000000000..14d3cb244 --- /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 8e9073a40..003863e4c 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 000000000..fb7ebdc5b --- /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 000000000..36468ec14 --- /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 a17279760..20c334e86 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 cc21dd9a5..b86f703db 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 1bc5bee78..370e7d73c 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 + public StandardSteadyClockCore() { - get - { - if (_instance == null) - { - _instance = new StandardSteadyClockCore(); - } - - return _instance; - } - } - - private 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; - - ResultCode result = ResultCode.Success; - - while (retry < 20) + // As this may be called before the guest code, we support passing a null thread to make this api usable. + if (thread == null) { - result = (ResultCode)IRtcManager.GetExternalRtcValue(out ulong rtcValue); - - if (result == ResultCode.Success) - { - _setupValue = (long)rtcValue; - break; - } - - retry++; + ticksTimeSpan = TimeSpanType.FromSeconds(0); + } + else + { + ticksTimeSpan = TimeSpanType.FromTicks(thread.Context.CntpctEl0, thread.Context.CntfrqEl0); } - _setupResultCode = result; + TimeSpanType rawTimePoint = new TimeSpanType(_setupValue.NanoSeconds + ticksTimeSpan.NanoSeconds); + + if (rawTimePoint.NanoSeconds < _cachedRawTimePoint.NanoSeconds) + { + rawTimePoint.NanoSeconds = _cachedRawTimePoint.NanoSeconds; + } + + _cachedRawTimePoint = rawTimePoint; + + return rawTimePoint; + } + + public void SetSetupValue(TimeSpanType setupValue) + { + _setupValue = setupValue; } } } diff --git a/Ryujinx.HLE/HOS/Services/Time/Clock/StandardUserSystemClockCore.cs b/Ryujinx.HLE/HOS/Services/Time/Clock/StandardUserSystemClockCore.cs index c98b0064b..42bc05fab 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 54d9accf4..83ace9814 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 000000000..629d8ee11 --- /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 _operationEventList; + protected SystemClockContext _context; + private bool _hasContext; + + public SystemClockContextUpdateCallback() + { + _operationEventList = new List(); + _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 52f3c9083..865b1c098 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); - - public bool IsClockSetup(KThread thread) + protected virtual ResultCode Flush(SystemClockContext context) { - ResultCode result = GetSystemClockContext(thread, out SystemClockContext context); + if (_systemClockContextUpdateCallback == null) + { + return ResultCode.Success; + } + + return _systemClockContextUpdateCallback.Update(context); + } + + public void SetUpdateCallbackInstance(SystemClockContextUpdateCallback systemClockContextUpdateCallback) + { + _systemClockContextUpdateCallback = systemClockContextUpdateCallback; + } + + public void RegisterOperationEvent(KWritableEvent writableEvent) + { + if (_systemClockContextUpdateCallback != null) + { + _systemClockContextUpdateCallback.RegisterOperationEvent(writableEvent); + } + } + + public ResultCode SetSystemClockContext(SystemClockContext context) + { + ResultCode result = SetClockContext(context); if (result == ResultCode.Success) { - 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 e5baba25d..065020827 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 0055b5ead..71fb45212 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 93579709f..c336ad419 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 cb10da47b..8ec55c159 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 000000000..605cbbbd7 --- /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 + public ResultCode GetStandardUserSystemClock(ServiceCtx context) + { + return _inner.GetStandardUserSystemClock(context); + } + + [Command(1)] + // GetStandardNetworkSystemClock() -> object + public ResultCode GetStandardNetworkSystemClock(ServiceCtx context) + { + return _inner.GetStandardNetworkSystemClock(context); + } + + [Command(2)] + // GetStandardSteadyClock() -> object + public ResultCode GetStandardSteadyClock(ServiceCtx context) + { + return _inner.GetStandardSteadyClock(context); + } + + [Command(3)] + // GetTimeZoneService() -> object + public ResultCode GetTimeZoneService(ServiceCtx context) + { + MakeObject(context, new ITimeZoneServiceForGlue(TimeManager.Instance.TimeZone, (_permissions & TimePermissions.TimeZoneWritableMask) != 0)); + + return ResultCode.Success; + } + + [Command(4)] + // GetStandardLocalSystemClock() -> object + public ResultCode GetStandardLocalSystemClock(ServiceCtx context) + { + return _inner.GetStandardLocalSystemClock(context); + } + + [Command(5)] // 4.0.0+ + // GetEphemeralNetworkSystemClock() -> object + public ResultCode GetEphemeralNetworkSystemClock(ServiceCtx context) + { + return _inner.GetEphemeralNetworkSystemClock(context); + } + + [Command(20)] // 6.0.0+ + // GetSharedMemoryNativeHandle() -> handle + 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(); + + // 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 + public ResultCode GetClockSnapshot(ServiceCtx context) + { + return _inner.GetClockSnapshot(context); + } + + [Command(401)] // 4.0.0+ + // GetClockSnapshotFromSystemClockContext(u8, nn::time::SystemClockContext, nn::time::SystemClockContext) -> buffer + public ResultCode GetClockSnapshotFromSystemClockContext(ServiceCtx context) + { + return _inner.GetClockSnapshotFromSystemClockContext(context); + } + + [Command(500)] // 4.0.0+ + // CalculateStandardUserSystemClockDifferenceByUser(buffer, buffer) -> nn::TimeSpanType + public ResultCode CalculateStandardUserSystemClockDifferenceByUser(ServiceCtx context) + { + return _inner.CalculateStandardUserSystemClockDifferenceByUser(context); + } + + [Command(501)] // 4.0.0+ + // CalculateSpanBetween(buffer, buffer) -> 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 similarity index 64% rename from Ryujinx.HLE/HOS/Services/Time/IStaticService.cs rename to Ryujinx.HLE/HOS/Services/Time/IStaticServiceForPsc.cs index 0cfdebcfb..5ea3910f3 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 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 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 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 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 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 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(); - 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, buffer) -> 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 514e901ea..a897b3f78 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 + public ResultCode GetUserStaticService(ServiceCtx context) + { + MakeObject(context, new IStaticServiceForPsc(_timeManager, TimePermissions.User)); + + return ResultCode.Success; + } + + [Command(5)] + // GetAdminStaticService() -> object + public ResultCode GetAdminStaticService(ServiceCtx context) + { + MakeObject(context, new IStaticServiceForPsc(_timeManager, TimePermissions.Admin)); + + return ResultCode.Success; + } + + [Command(6)] + // GetRepairStaticService() -> object + public ResultCode GetRepairStaticService(ServiceCtx context) + { + MakeObject(context, new IStaticServiceForPsc(_timeManager, TimePermissions.Repair)); + + return ResultCode.Success; + } + + [Command(9)] + // GetManufactureStaticService() -> object + 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(); + TimeSpanType setupValue = context.RequestData.ReadStruct(); + TimeSpanType internalOffset = context.RequestData.ReadStruct(); + TimeSpanType testOffset = context.RequestData.ReadStruct(); + 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(); + 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(); + TimeSpanType sufficientAccuracy = context.RequestData.ReadStruct(); + + _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(); + + _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 timezone_binary) + public ResultCode SetupTimeZoneManager(ServiceCtx context) + { + string locationName = Encoding.ASCII.GetString(context.RequestData.ReadBytes(0x24)).TrimEnd('\0'); + SteadyClockTimePoint timeZoneUpdateTimePoint = context.RequestData.ReadStruct(); + uint totalLocationNameCount = context.RequestData.ReadUInt32(); + UInt128 timeZoneRuleVersion = context.RequestData.ReadStruct(); + + (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 + public ResultCode Unknown50(ServiceCtx context) + { + // TODO: figure out the usage of this event + throw new ServiceNotImplementedException(context); + } + + [Command(51)] + // Unknown51() -> handle + public ResultCode Unknown51(ServiceCtx context) + { + // TODO: figure out the usage of this event + throw new ServiceNotImplementedException(context); + } + + [Command(52)] + // Unknown52() -> handle + public ResultCode Unknown52(ServiceCtx context) + { + // TODO: figure out the usage of this event + throw new ServiceNotImplementedException(context); + } + + [Command(60)] + // GetStandardUserSystemClockAutomaticCorrectionEvent() -> handle + 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(); + + _timeManager.SetStandardSteadyClockRtcOffset(context.Thread, rtcOffset); + + return ResultCode.Success; + } + + [Command(200)] + // GetAlarmRegistrationEvent() -> handle + 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 ed7130f36..1ef895c25 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 31f119df5..bf6a4fd10 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(); - 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(); - 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 0d8661775..d5b21f8c9 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(); ResultCode result = _clockCore.SetSystemClockContext(clockContext); - if (result == ResultCode.Success) + return result; + } + + [Command(4)] // 9.0.0+ + // GetOperationEventReadableHandle() -> handle + 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 c65107dfe..000000000 --- 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) - 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 - 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 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(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 rules) -> (u32 outCount, buffer) - public ResultCode ToPosixTime(ServiceCtx context) - { - long inBufferPosition = context.Request.SendBuff[0].Position; - long inBufferSize = context.Request.SendBuff[0].Size; - - CalendarTime calendarTime = context.RequestData.ReadStruct(); - - 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(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) - public ResultCode ToPosixTimeWithMyRule(ServiceCtx context) - { - CalendarTime calendarTime = context.RequestData.ReadStruct(); - - 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 000000000..7acb02572 --- /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) + 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 + 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 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 rules) -> (u32 outCount, buffer) + public ResultCode ToPosixTime(ServiceCtx context) + { + return _inner.ToPosixTime(context); + } + + [Command(202)] + // ToPosixTimeWithMyRule(nn::time::CalendarTime calendarTime) -> (u32 outCount, buffer) + 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 000000000..ed31fe7c5 --- /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) + public ResultCode LoadLocationNameList(ServiceCtx context) + { + return ResultCode.NotImplemented; + } + + [Command(4)] + // LoadTimeZoneRule(nn::time::LocationName locationName) -> buffer + 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 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 timeZoneBinary) -> buffer + 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 + public ResultCode GetDeviceLocationNameOperationEventReadableHandle(ServiceCtx context) + { + return ResultCode.NotImplemented; + } + + [Command(100)] + // ToCalendarTime(nn::time::PosixTime time, buffer 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(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 rules) -> (u32 outCount, buffer) + public ResultCode ToPosixTime(ServiceCtx context) + { + long inBufferPosition = context.Request.SendBuff[0].Position; + long inBufferSize = context.Request.SendBuff[0].Size; + + CalendarTime calendarTime = context.RequestData.ReadStruct(); + + 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(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) + public ResultCode ToPosixTimeWithMyRule(ServiceCtx context) + { + CalendarTime calendarTime = context.RequestData.ReadStruct(); + + 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 000000000..7c5d71639 --- /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 000000000..f714c6623 --- /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(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(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(); + + result = _device.Memory.ReadStruct(objectOffset); + + Thread.MemoryBarrier(); + + possiblyNewIndex = _device.Memory.ReadUInt32(indexOffset); + } while (index != possiblyNewIndex); + + return result; + } + + private void WriteObjectToSharedMemory(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(); + + _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 3a98013e1..b32a97957 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 000000000..f02781b31 --- /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 locationNameList = new List(); + + 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 locationNameList = new List(); + + 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 2497f6a37..1a80365ad 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 bool _isInitialized; + private TimeZoneRule _myRules; + private string _deviceLocationName; + private UInt128 _timeZoneRuleVersion; + private uint _totalLocationNameCount; + private SteadyClockTimePoint _timeZoneUpdateTimePoint; + private object _lock; - 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 + public TimeZoneManager() { - get - { - lock (instanceLock) - { - if (instance == null) - { - instance = new TimeZoneManager(); - } + _isInitialized = false; + _deviceLocationName = "UTC"; + _timeZoneRuleVersion = new UInt128(); + _lock = new object(); - return instance; - } - } - } - - 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; - InitializeLocationNameCache(); - } - - private void InitializeLocationNameCache() - { - if (HasTimeZoneBinaryTitle()) + lock (_lock) { - using (IStorage ncaFileStream = new LocalStorage(_device.FileSystem.SwitchPathToSystemPath(GetTimeZoneBinaryTitleContentPath()), FileAccess.Read, FileMode.Open)) + res = _isInitialized; + } + + return res; + } + + public void MarkInitialized() + { + lock (_lock) + { + _isInitialized = true; + } + } + + public ResultCode GetDeviceLocationName(out string deviceLocationName) + { + ResultCode result = ResultCode.UninitializedClock; + + deviceLocationName = null; + + lock (_lock) + { + if (_isInitialized) { - 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 locationNameList = new List(); - - string locationName; - while ((locationName = reader.ReadLine()) != null) - { - locationNameList.Add(locationName); - } - - _locationNameCache = locationNameList.ToArray(); + deviceLocationName = _deviceLocationName; + result = ResultCode.Success; } } - else - { - ReadOnlyCollection timeZoneInfos = TimeZoneInfo.GetSystemTimeZones(); - _locationNameCache = new string[timeZoneInfos.Count]; - int i = 0; - - 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++; - } - - // 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) - { - utcId = utcConvertedName; - } - - _deviceLocationName = utcId; - } + return result; } - private bool IsLocationNameValid(string locationName) + public ResultCode SetDeviceLocationNameWithTimeZoneRule(string locationName, Stream timeZoneBinaryStream) { - foreach (string cachedLocationName in _locationNameCache) + ResultCode result = ResultCode.TimeZoneConversionFailed; + + lock (_lock) { - if (cachedLocationName.Equals(locationName)) + bool timeZoneConversionSuccess = TimeZone.ParseTimeZoneBinary(out TimeZoneRule rules, timeZoneBinaryStream); + + if (timeZoneConversionSuccess) { - return true; + _deviceLocationName = locationName; + _myRules = rules; + result = ResultCode.Success; } } - return false; + + return result; } - public string GetDeviceLocationName() + public void SetTotalLocationNameCount(uint totalLocationNameCount) { - return _deviceLocationName; - } - - public ResultCode SetDeviceLocationName(string locationName) - { - ResultCode resultCode = LoadTimeZoneRules(out TimeZoneRule rules, locationName); - - if (resultCode == 0) + lock (_lock) { - _myRules = rules; - _deviceLocationName = locationName; + _totalLocationNameCount = totalLocationNameCount; } - - return resultCode; } - public ResultCode LoadLocationNameList(uint index, out string[] outLocationNameArray, uint maxLength) + public ResultCode GetTotalLocationNameCount(out uint totalLocationNameCount) { - List locationNameList = new List(); + ResultCode result = ResultCode.UninitializedClock; - for (int i = 0; i < _locationNameCache.Length && i < maxLength; i++) + totalLocationNameCount = 0; + + lock (_lock) { - if (i < index) + if (_isInitialized) { - 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 uint GetTotalLocationNameCount() - { - return (uint)_locationNameCache.Length; - } - - public string GetTimeZoneBinaryTitleContentPath() - { - return _device.System.ContentManager.GetInstalledContentPath(TimeZoneBinaryTitleId, StorageId.NandSystem, ContentType.Data); - } - - public bool HasTimeZoneBinaryTitle() - { - return !string.IsNullOrEmpty(GetTimeZoneBinaryTitleContentPath()); - } - - internal ResultCode LoadTimeZoneRules(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 (!IsLocationNameValid(locationName)) - { - return ResultCode.TimeZoneNotFound; - } - - if (!HasTimeZoneBinaryTitle()) - { - // 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 - { - TimeZoneInfo info = TZConvert.GetTimeZoneInfo(locationName); - string posixRule = PosixTimeZone.FromTimeZoneInfo(info); - - if (!TimeZone.ParsePosixName(posixRule, out outRules)) - { - return ResultCode.TimeZoneConversionFailed; - } - - return 0; - } - catch (TimeZoneNotFoundException) - { - Logger.PrintWarning(LogClass.ServiceTime, $"Timezone not found for string: {locationName})"); - - return ResultCode.TimeZoneNotFound; + totalLocationNameCount = _totalLocationNameCount; + result = ResultCode.Success; } } - else + + return result; + } + + public ResultCode SetUpdatedTime(SteadyClockTimePoint timeZoneUpdatedTimePoint, bool bypassUninitialized = false) + { + ResultCode result = ResultCode.UninitializedClock; + + lock (_lock) { - using (IStorage ncaFileStream = new LocalStorage(_device.FileSystem.SwitchPathToSystemPath(GetTimeZoneBinaryTitleContentPath()), FileAccess.Read, FileMode.Open)) + if (_isInitialized || bypassUninitialized) { - 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; - } + _timeZoneUpdateTimePoint = timeZoneUpdatedTimePoint; + result = ResultCode.Success; } - - return 0; } + + return result; } - internal ResultCode ToCalendarTimeWithMyRules(long time, out CalendarInfo calendar) + public ResultCode GetUpdatedTime(out SteadyClockTimePoint timeZoneUpdatedTimePoint) { - return ToCalendarTime(_myRules, time, out calendar); - } + ResultCode result; - internal static ResultCode ToCalendarTime(TimeZoneRule rules, long time, out CalendarInfo calendar) - { - ResultCode error = TimeZone.ToCalendarTime(rules, time, out calendar); - - if (error != ResultCode.Success) + lock (_lock) { - return error; + if (_isInitialized) + { + timeZoneUpdatedTimePoint = _timeZoneUpdateTimePoint; + result = ResultCode.Success; + } + else + { + timeZoneUpdatedTimePoint = SteadyClockTimePoint.GetRandom(); + result = ResultCode.UninitializedClock; + } } - return ResultCode.Success; + return result; } - internal ResultCode ToPosixTimeWithMyRules(CalendarTime calendarTime, out long posixTime) + public ResultCode ParseTimeZoneRuleBinary(out TimeZoneRule outRules, Stream timeZoneBinaryStream) { - return ToPosixTime(_myRules, calendarTime, out posixTime); - } + ResultCode result = ResultCode.Success; - internal static ResultCode ToPosixTime(TimeZoneRule rules, CalendarTime calendarTime, out long posixTime) - { - ResultCode error = TimeZone.ToPosixTime(rules, calendarTime, out posixTime); - - if (error != ResultCode.Success) + lock (_lock) { - return error; + bool timeZoneConversionSuccess = TimeZone.ParseTimeZoneBinary(out outRules, timeZoneBinaryStream); + + if (!timeZoneConversionSuccess) + { + result = ResultCode.TimeZoneConversionFailed; + } } - return ResultCode.Success; + return result; + } + + public void SetTimeZoneRuleVersion(UInt128 timeZoneRuleVersion) + { + lock (_lock) + { + _timeZoneRuleVersion = timeZoneRuleVersion; + } + } + + public ResultCode GetTimeZoneRuleVersion(out UInt128 timeZoneRuleVersion) + { + ResultCode result; + + lock (_lock) + { + if (_isInitialized) + { + timeZoneRuleVersion = _timeZoneRuleVersion; + result = ResultCode.Success; + } + else + { + timeZoneRuleVersion = new UInt128(); + result = ResultCode.UninitializedClock; + } + } + + return result; + } + + public ResultCode ToCalendarTimeWithMyRules(long time, out CalendarInfo calendar) + { + ResultCode result; + + lock (_lock) + { + if (_isInitialized) + { + result = ToCalendarTime(_myRules, time, out calendar); + } + else + { + calendar = new CalendarInfo(); + result = ResultCode.UninitializedClock; + } + } + + return result; + } + + public ResultCode ToCalendarTime(TimeZoneRule rules, long time, out CalendarInfo calendar) + { + ResultCode result; + + lock (_lock) + { + result = TimeZone.ToCalendarTime(rules, time, out calendar); + } + + return result; + } + + public ResultCode ToPosixTimeWithMyRules(CalendarTime calendarTime, out long posixTime) + { + ResultCode result; + + lock (_lock) + { + if (_isInitialized) + { + result = ToPosixTime(_myRules, calendarTime, out posixTime); + } + else + { + posixTime = 0; + result = ResultCode.UninitializedClock; + } + } + + return result; + } + + public ResultCode ToPosixTime(TimeZoneRule rules, CalendarTime calendarTime, out long posixTime) + { + ResultCode result; + + lock (_lock) + { + result = TimeZone.ToPosixTime(rules, calendarTime, out posixTime); + } + + 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 000000000..4cf1fc991 --- /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 823c82888..3fcd3a144 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 } }