diff --git a/Ryujinx.HLE/HOS/Horizon.cs b/Ryujinx.HLE/HOS/Horizon.cs index 7523d2bee5..01b87b425d 100644 --- a/Ryujinx.HLE/HOS/Horizon.cs +++ b/Ryujinx.HLE/HOS/Horizon.cs @@ -197,10 +197,17 @@ namespace Ryujinx.HLE.HOS ContentManager = new ContentManager(device); - // NOTE: Now we set the default internal offset of the steady clock like Nintendo does... even if it's strange this is accurate. - // TODO: use bpc:r and set:sys (and set external clock source id from settings) - DateTime UnixEpoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc); - SteadyClockCore.Instance.SetInternalOffset(new TimeSpanType(((ulong)(DateTime.Now.ToUniversalTime() - UnixEpoch).TotalSeconds) * 1000000000)); + // TODO: use set:sys (and set external clock source id from settings) + // TODO: use "time!standard_steady_clock_rtc_update_interval_minutes" and implement a worker thread to be accurate. + SteadyClockCore.Instance.ConfigureSetupValue(); + + if (Services.Set.NxSettings.Settings.TryGetValue("time!standard_network_clock_sufficient_accuracy_minutes", out object standardNetworkClockSufficientAccuracyMinutes)) + { + TimeSpanType standardNetworkClockSufficientAccuracy = new TimeSpanType((int)standardNetworkClockSufficientAccuracyMinutes * 60000000000); + + StandardNetworkSystemClockCore.Instance.SetStandardNetworkClockSufficientAccuracy(standardNetworkClockSufficientAccuracy); + } + } public void LoadCart(string exeFsDir, string romFsFile = null) diff --git a/Ryujinx.HLE/HOS/Services/Bpc/IRtcManager.cs b/Ryujinx.HLE/HOS/Services/Bpc/IRtcManager.cs new file mode 100644 index 0000000000..5ff9410a26 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Bpc/IRtcManager.cs @@ -0,0 +1,34 @@ +using System; + +namespace Ryujinx.HLE.HOS.Services.Bpc +{ + [Service("bpc:r")] + class IRtcManager : IpcService + { + public IRtcManager(ServiceCtx context) { } + + [Command(0)] + // GetRtcTime() -> u64 + public ResultCode GetRtcTime(ServiceCtx context) + { + ResultCode result = GetExternalRtcValue(out ulong rtcValue); + + if (result == ResultCode.Success) + { + context.ResponseData.Write(rtcValue); + } + + return result; + } + + public static ResultCode GetExternalRtcValue(out ulong rtcValue) + { + // TODO: emulate MAX77620/MAX77812 RTC + DateTime unixEpoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc); + + rtcValue = (ulong)(DateTime.Now.ToUniversalTime() - unixEpoch).TotalSeconds; + + return ResultCode.Success; + } + } +} diff --git a/Ryujinx.HLE/HOS/Services/Time/Clock/ClockTypes.cs b/Ryujinx.HLE/HOS/Services/Time/Clock/ClockTypes.cs index 1e527c002e..860f5ad2bb 100644 --- a/Ryujinx.HLE/HOS/Services/Time/Clock/ClockTypes.cs +++ b/Ryujinx.HLE/HOS/Services/Time/Clock/ClockTypes.cs @@ -1,4 +1,5 @@ using Ryujinx.HLE.Utilities; +using System; using System.Runtime.InteropServices; namespace Ryujinx.HLE.HOS.Services.Time.Clock @@ -6,35 +7,56 @@ namespace Ryujinx.HLE.HOS.Services.Time.Clock [StructLayout(LayoutKind.Sequential)] struct TimeSpanType { - public ulong NanoSeconds; + public long NanoSeconds; - public TimeSpanType(ulong nanoSeconds) + public TimeSpanType(long nanoSeconds) { NanoSeconds = nanoSeconds; } - public ulong ToSeconds() + public long ToSeconds() { return NanoSeconds / 1000000000; } public static TimeSpanType FromTicks(ulong ticks, ulong frequency) { - return new TimeSpanType(ticks * 1000000000 / frequency); + return new TimeSpanType((long)ticks * 1000000000 / (long)frequency); } } [StructLayout(LayoutKind.Sequential)] struct SteadyClockTimePoint { - public ulong TimePoint; + public long TimePoint; public UInt128 ClockSourceId; + + public ResultCode GetSpanBetween(SteadyClockTimePoint other, out long outSpan) + { + outSpan = 0; + + if (ClockSourceId == other.ClockSourceId) + { + try + { + outSpan = checked(other.TimePoint - TimePoint); + + return ResultCode.Success; + } + catch (OverflowException) + { + return ResultCode.Overflow; + } + } + + return ResultCode.Overflow; + } } [StructLayout(LayoutKind.Sequential)] struct SystemClockContext { - public ulong Offset; + public long Offset; public SteadyClockTimePoint SteadyTimePoint; } } diff --git a/Ryujinx.HLE/HOS/Services/Time/Clock/StandardNetworkSystemClockCore.cs b/Ryujinx.HLE/HOS/Services/Time/Clock/StandardNetworkSystemClockCore.cs index 59bc822ceb..c00f460ef5 100644 --- a/Ryujinx.HLE/HOS/Services/Time/Clock/StandardNetworkSystemClockCore.cs +++ b/Ryujinx.HLE/HOS/Services/Time/Clock/StandardNetworkSystemClockCore.cs @@ -1,4 +1,5 @@ -using Ryujinx.HLE.HOS.Kernel.Threading; +using System; +using Ryujinx.HLE.HOS.Kernel.Threading; namespace Ryujinx.HLE.HOS.Services.Time.Clock { @@ -6,6 +7,7 @@ namespace Ryujinx.HLE.HOS.Services.Time.Clock { private SteadyClockCore _steadyClockCore; private SystemClockContext _context; + private TimeSpanType _standardNetworkClockSufficientAccuracy; private static StandardNetworkSystemClockCore instance; @@ -27,7 +29,8 @@ namespace Ryujinx.HLE.HOS.Services.Time.Clock _steadyClockCore = steadyClockCore; _context = new SystemClockContext(); - _context.SteadyTimePoint.ClockSourceId = steadyClockCore.GetClockSourceId(); + _context.SteadyTimePoint.ClockSourceId = steadyClockCore.GetClockSourceId(); + _standardNetworkClockSufficientAccuracy = new TimeSpanType(0); } public override ResultCode Flush(SystemClockContext context) @@ -55,5 +58,25 @@ namespace Ryujinx.HLE.HOS.Services.Time.Clock return ResultCode.Success; } + + public bool IsStandardNetworkSystemClockAccuracySufficient(KThread thread) + { + SteadyClockCore steadyClockCore = GetSteadyClockCore(); + SteadyClockTimePoint currentTimePoint = steadyClockCore.GetCurrentTimePoint(thread); + + bool isStandardNetworkClockSufficientAccuracy = false; + + if (_context.SteadyTimePoint.GetSpanBetween(currentTimePoint, out long outSpan) == ResultCode.Success) + { + isStandardNetworkClockSufficientAccuracy = outSpan * 1000000000 < _standardNetworkClockSufficientAccuracy.NanoSeconds; + } + + return isStandardNetworkClockSufficientAccuracy; + } + + public void SetStandardNetworkClockSufficientAccuracy(TimeSpanType standardNetworkClockSufficientAccuracy) + { + _standardNetworkClockSufficientAccuracy = standardNetworkClockSufficientAccuracy; + } } } diff --git a/Ryujinx.HLE/HOS/Services/Time/Clock/SteadyClockCore.cs b/Ryujinx.HLE/HOS/Services/Time/Clock/SteadyClockCore.cs index b69b7d3cbf..e661ab5380 100644 --- a/Ryujinx.HLE/HOS/Services/Time/Clock/SteadyClockCore.cs +++ b/Ryujinx.HLE/HOS/Services/Time/Clock/SteadyClockCore.cs @@ -1,4 +1,5 @@ using Ryujinx.HLE.HOS.Kernel.Threading; +using Ryujinx.HLE.HOS.Services.Bpc; using Ryujinx.HLE.Utilities; using System; @@ -6,6 +7,9 @@ namespace Ryujinx.HLE.HOS.Services.Time.Clock { class SteadyClockCore { + private long _setupValue; + private ResultCode _setupResultCode; + private bool _isRtcResetDetected; private TimeSpanType _testOffset; private TimeSpanType _internalOffset; private UInt128 _clockSourceId; @@ -42,7 +46,7 @@ namespace Ryujinx.HLE.HOS.Services.Time.Clock TimeSpanType ticksTimeSpan = TimeSpanType.FromTicks(thread.Context.ThreadState.CntpctEl0, thread.Context.ThreadState.CntfrqEl0); - result.TimePoint = _internalOffset.ToSeconds() + ticksTimeSpan.ToSeconds(); + result.TimePoint = _setupValue + ticksTimeSpan.ToSeconds(); return result; } @@ -57,6 +61,7 @@ namespace Ryujinx.HLE.HOS.Services.Time.Clock SteadyClockTimePoint result = GetTimePoint(thread); result.TimePoint += _testOffset.ToSeconds(); + result.TimePoint += _internalOffset.ToSeconds(); return result; } @@ -71,16 +76,56 @@ namespace Ryujinx.HLE.HOS.Services.Time.Clock _testOffset = testOffset; } - // TODO: check if this is accurate + public ResultCode GetRtcValue(out ulong rtcValue) + { + return (ResultCode)IRtcManager.GetExternalRtcValue(out rtcValue); + } + + public bool IsRtcResetDetected() + { + return _isRtcResetDetected; + } + + public ResultCode GetSetupResultCode() + { + return _setupResultCode; + } + public TimeSpanType GetInternalOffset() { return _internalOffset; } - // TODO: check if this is accurate public void SetInternalOffset(TimeSpanType internalOffset) { _internalOffset = internalOffset; } + + public ResultCode GetSetupResultValue() + { + return _setupResultCode; + } + + public void ConfigureSetupValue() + { + int retry = 0; + + ResultCode result = ResultCode.Success; + + while (retry < 20) + { + result = (ResultCode)IRtcManager.GetExternalRtcValue(out ulong rtcValue); + + if (result == ResultCode.Success) + { + _setupValue = (long)rtcValue; + break; + } + + retry++; + } + + _setupResultCode = result; + } } } diff --git a/Ryujinx.HLE/HOS/Services/Time/IStaticService.cs b/Ryujinx.HLE/HOS/Services/Time/IStaticService.cs index 10c659c5cc..8c5ae65c6c 100644 --- a/Ryujinx.HLE/HOS/Services/Time/IStaticService.cs +++ b/Ryujinx.HLE/HOS/Services/Time/IStaticService.cs @@ -106,6 +106,15 @@ namespace Ryujinx.HLE.HOS.Services.Time return StandardUserSystemClockCore.Instance.SetAutomaticCorrectionEnabled(context.Thread, autoCorrectionEnabled); } + [Command(200)] // 3.0.0+ + // IsStandardNetworkSystemClockAccuracySufficient() -> bool + public ResultCode IsStandardNetworkSystemClockAccuracySufficient(ServiceCtx context) + { + context.ResponseData.Write(StandardNetworkSystemClockCore.Instance.IsStandardNetworkSystemClockAccuracySufficient(context.Thread)); + + return ResultCode.Success; + } + [Command(300)] // 4.0.0+ // CalculateMonotonicSystemClockBaseTimePoint(nn::time::SystemClockContext) -> u64 public ResultCode CalculateMonotonicSystemClockBaseTimePoint(ServiceCtx context) diff --git a/Ryujinx.HLE/HOS/Services/Time/ISteadyClock.cs b/Ryujinx.HLE/HOS/Services/Time/ISteadyClock.cs index d9f05f29cc..2772b45d66 100644 --- a/Ryujinx.HLE/HOS/Services/Time/ISteadyClock.cs +++ b/Ryujinx.HLE/HOS/Services/Time/ISteadyClock.cs @@ -36,6 +36,38 @@ namespace Ryujinx.HLE.HOS.Services.Time return 0; } + [Command(100)] // 2.0.0+ + // GetRtcValue() -> u64 + public ResultCode GetRtcValue(ServiceCtx context) + { + ResultCode result = SteadyClockCore.Instance.GetRtcValue(out ulong rtcValue); + + if (result == ResultCode.Success) + { + context.ResponseData.Write(rtcValue); + } + + return result; + } + + [Command(101)] // 2.0.0+ + // IsRtcResetDetected() -> bool + public ResultCode IsRtcResetDetected(ServiceCtx context) + { + context.ResponseData.Write(SteadyClockCore.Instance.IsRtcResetDetected()); + + return ResultCode.Success; + } + + [Command(102)] // 2.0.0+ + // GetSetupResultValue() -> u32 + public ResultCode GetSetupResultValue(ServiceCtx context) + { + context.ResponseData.Write((uint)SteadyClockCore.Instance.GetSetupResultCode()); + + return ResultCode.Success; + } + [Command(200)] // 3.0.0+ // GetInternalOffset() -> nn::TimeSpanType public ResultCode GetInternalOffset(ServiceCtx context) diff --git a/Ryujinx.HLE/HOS/Services/Time/ISystemClock.cs b/Ryujinx.HLE/HOS/Services/Time/ISystemClock.cs index ddd67a4214..d496dcdc39 100644 --- a/Ryujinx.HLE/HOS/Services/Time/ISystemClock.cs +++ b/Ryujinx.HLE/HOS/Services/Time/ISystemClock.cs @@ -29,7 +29,7 @@ namespace Ryujinx.HLE.HOS.Services.Time if (currentTimePoint.ClockSourceId == clockContext.SteadyTimePoint.ClockSourceId) { - ulong posixTime = clockContext.Offset + currentTimePoint.TimePoint; + long posixTime = clockContext.Offset + currentTimePoint.TimePoint; context.ResponseData.Write(posixTime); @@ -49,7 +49,7 @@ namespace Ryujinx.HLE.HOS.Services.Time return ResultCode.PermissionDenied; } - ulong posixTime = context.RequestData.ReadUInt64(); + long posixTime = context.RequestData.ReadInt64(); SteadyClockCore steadyClockCore = _clockCore.GetSteadyClockCore(); SteadyClockTimePoint currentTimePoint = steadyClockCore.GetCurrentTimePoint(context.Thread);