From 689c5854aef854c0d3f6c35e38803455eac5061a Mon Sep 17 00:00:00 2001 From: Thog Date: Sun, 14 Jul 2019 16:52:54 +0200 Subject: [PATCH] Rewrite system clocks to be accurate --- Ryujinx.HLE/HOS/Horizon.cs | 2 +- .../HOS/Services/Time/Clock/ClockTypes.cs | 14 ++- .../Clock/StandardLocalSystemClockCore.cs | 59 +++++++++++ .../Clock/StandardNetworkSystemClockCore.cs | 59 +++++++++++ .../Time/Clock/StandardUserSystemClockCore.cs | 96 ++++++++++++++++++ .../Services/Time/Clock/SteadyClockCore.cs | 9 +- .../Services/Time/Clock/SystemClockCore.cs | 31 ++++++ .../HOS/Services/Time/IStaticService.cs | 8 +- Ryujinx.HLE/HOS/Services/Time/ISteadyClock.cs | 6 +- Ryujinx.HLE/HOS/Services/Time/ISystemClock.cs | 99 ++++++++++--------- Ryujinx.HLE/HOS/Services/Time/ResultCode.cs | 4 +- .../HOS/Services/Time/SystemClockType.cs | 10 -- 12 files changed, 327 insertions(+), 70 deletions(-) create mode 100644 Ryujinx.HLE/HOS/Services/Time/Clock/StandardLocalSystemClockCore.cs create mode 100644 Ryujinx.HLE/HOS/Services/Time/Clock/StandardNetworkSystemClockCore.cs create mode 100644 Ryujinx.HLE/HOS/Services/Time/Clock/StandardUserSystemClockCore.cs create mode 100644 Ryujinx.HLE/HOS/Services/Time/Clock/SystemClockCore.cs delete mode 100644 Ryujinx.HLE/HOS/Services/Time/SystemClockType.cs diff --git a/Ryujinx.HLE/HOS/Horizon.cs b/Ryujinx.HLE/HOS/Horizon.cs index a7d603bd27..7523d2bee5 100644 --- a/Ryujinx.HLE/HOS/Horizon.cs +++ b/Ryujinx.HLE/HOS/Horizon.cs @@ -200,7 +200,7 @@ namespace Ryujinx.HLE.HOS // 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) * 1000)); + SteadyClockCore.Instance.SetInternalOffset(new TimeSpanType(((ulong)(DateTime.Now.ToUniversalTime() - UnixEpoch).TotalSeconds) * 1000000000)); } public void LoadCart(string exeFsDir, string romFsFile = null) diff --git a/Ryujinx.HLE/HOS/Services/Time/Clock/ClockTypes.cs b/Ryujinx.HLE/HOS/Services/Time/Clock/ClockTypes.cs index b1edbbf2e7..c14fc92612 100644 --- a/Ryujinx.HLE/HOS/Services/Time/Clock/ClockTypes.cs +++ b/Ryujinx.HLE/HOS/Services/Time/Clock/ClockTypes.cs @@ -16,13 +16,14 @@ namespace Ryujinx.HLE.HOS.Services.Time.Clock public ulong ToSeconds() { - return NanoSeconds * 1000000000; + return NanoSeconds / 1000000000; } public static TimeSpanType FromTicks(ulong ticks, ulong frequency) { - return new TimeSpanType((ticks * 1000) / frequency); + return new TimeSpanType(ticks * 1000000000 / frequency); } + } [StructLayout(LayoutKind.Sequential)] @@ -31,4 +32,13 @@ namespace Ryujinx.HLE.HOS.Services.Time.Clock public ulong TimePoint; public UInt128 ClockSourceId; } + + [StructLayout(LayoutKind.Sequential)] + public struct SystemClockContext + { + public ulong Offset; + public SteadyClockTimePoint SteadyTimePoint; + } + + } diff --git a/Ryujinx.HLE/HOS/Services/Time/Clock/StandardLocalSystemClockCore.cs b/Ryujinx.HLE/HOS/Services/Time/Clock/StandardLocalSystemClockCore.cs new file mode 100644 index 0000000000..16550199b3 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Time/Clock/StandardLocalSystemClockCore.cs @@ -0,0 +1,59 @@ +using Ryujinx.HLE.HOS.Kernel.Threading; + +namespace Ryujinx.HLE.HOS.Services.Time.Clock +{ + class StandardLocalSystemClockCore : SystemClockCore + { + private SteadyClockCore _steadyClockCore; + private SystemClockContext _context; + + private static StandardLocalSystemClockCore instance; + + public static StandardLocalSystemClockCore Instance + { + get + { + if (instance == null) + { + instance = new StandardLocalSystemClockCore(SteadyClockCore.Instance); + } + + return instance; + } + } + + public StandardLocalSystemClockCore(SteadyClockCore steadyClockCore) + { + _steadyClockCore = steadyClockCore; + _context = new SystemClockContext(); + + _context.SteadyTimePoint.ClockSourceId = steadyClockCore.GetClockSourceId(); + } + + public override ResultCode Flush(SystemClockContext context) + { + // TODO: set:sys SetUserSystemClockContext + + return ResultCode.Success; + } + + public override SteadyClockCore GetSteadyClockCore() + { + return _steadyClockCore; + } + + public override ResultCode GetSystemClockContext(KThread thread, out SystemClockContext context) + { + context = _context; + + return ResultCode.Success; + } + + public override ResultCode SetSystemClockContext(SystemClockContext context) + { + _context = context; + + return ResultCode.Success; + } + } +} diff --git a/Ryujinx.HLE/HOS/Services/Time/Clock/StandardNetworkSystemClockCore.cs b/Ryujinx.HLE/HOS/Services/Time/Clock/StandardNetworkSystemClockCore.cs new file mode 100644 index 0000000000..59bc822ceb --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Time/Clock/StandardNetworkSystemClockCore.cs @@ -0,0 +1,59 @@ +using Ryujinx.HLE.HOS.Kernel.Threading; + +namespace Ryujinx.HLE.HOS.Services.Time.Clock +{ + class StandardNetworkSystemClockCore : SystemClockCore + { + private SteadyClockCore _steadyClockCore; + private SystemClockContext _context; + + private static StandardNetworkSystemClockCore instance; + + public static StandardNetworkSystemClockCore Instance + { + get + { + if (instance == null) + { + instance = new StandardNetworkSystemClockCore(SteadyClockCore.Instance); + } + + return instance; + } + } + + public StandardNetworkSystemClockCore(SteadyClockCore steadyClockCore) + { + _steadyClockCore = steadyClockCore; + _context = new SystemClockContext(); + + _context.SteadyTimePoint.ClockSourceId = steadyClockCore.GetClockSourceId(); + } + + public override ResultCode Flush(SystemClockContext context) + { + // TODO: set:sys SetNetworkSystemClockContext + + return ResultCode.Success; + } + + public override SteadyClockCore GetSteadyClockCore() + { + return _steadyClockCore; + } + + public override ResultCode GetSystemClockContext(KThread thread, out SystemClockContext context) + { + context = _context; + + return ResultCode.Success; + } + + public override ResultCode SetSystemClockContext(SystemClockContext context) + { + _context = context; + + return ResultCode.Success; + } + } +} diff --git a/Ryujinx.HLE/HOS/Services/Time/Clock/StandardUserSystemClockCore.cs b/Ryujinx.HLE/HOS/Services/Time/Clock/StandardUserSystemClockCore.cs new file mode 100644 index 0000000000..8751532e31 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Time/Clock/StandardUserSystemClockCore.cs @@ -0,0 +1,96 @@ +using Ryujinx.HLE.HOS.Kernel.Threading; + +namespace Ryujinx.HLE.HOS.Services.Time.Clock +{ + class StandardUserSystemClockCore : SystemClockCore + { + private StandardLocalSystemClockCore _localSystemClockCore; + private StandardNetworkSystemClockCore _networkSystemClockCore; + private bool _autoCorrectionEnabled; + + private static StandardUserSystemClockCore instance; + + public static StandardUserSystemClockCore Instance + { + get + { + if (instance == null) + { + instance = new StandardUserSystemClockCore(StandardLocalSystemClockCore.Instance, StandardNetworkSystemClockCore.Instance); + } + + return instance; + } + } + + public StandardUserSystemClockCore(StandardLocalSystemClockCore localSystemClockCore, StandardNetworkSystemClockCore networkSystemClockCore) + { + _localSystemClockCore = localSystemClockCore; + _networkSystemClockCore = networkSystemClockCore; + _autoCorrectionEnabled = false; + } + + public override ResultCode Flush(SystemClockContext context) + { + return ResultCode.NotImplemented; + } + + public override SteadyClockCore GetSteadyClockCore() + { + return _localSystemClockCore.GetSteadyClockCore(); + } + + public override ResultCode GetSystemClockContext(KThread thread, out SystemClockContext context) + { + ResultCode result = ApplyAutomaticCorrection(thread, false); + + context = new SystemClockContext(); + + if (result == 0) + { + return _localSystemClockCore.GetSystemClockContext(thread, out context); + } + + return result; + } + + public override ResultCode SetSystemClockContext(SystemClockContext context) + { + return ResultCode.NotImplemented; + } + + private ResultCode ApplyAutomaticCorrection(KThread thread, bool autoCorrectionEnabled) + { + ResultCode result = ResultCode.Success; + + if (_autoCorrectionEnabled != autoCorrectionEnabled && _networkSystemClockCore.IsClockSetup(thread)) + { + result = _networkSystemClockCore.GetSystemClockContext(thread, out SystemClockContext context); + + if (result == 0) + { + _localSystemClockCore.SetSystemClockContext(context); + } + } + + return result; + } + + public ResultCode SetAutomaticCorrectionEnabled(KThread thread, bool autoCorrectionEnabled) + { + ResultCode result = ApplyAutomaticCorrection(thread, autoCorrectionEnabled); + + if (result == 0) + { + _autoCorrectionEnabled = autoCorrectionEnabled; + } + + return result; + } + + public bool IsAutomaticCorrectionEnabled() + { + return _autoCorrectionEnabled; + } + } +} diff --git a/Ryujinx.HLE/HOS/Services/Time/Clock/SteadyClockCore.cs b/Ryujinx.HLE/HOS/Services/Time/Clock/SteadyClockCore.cs index c3081adf24..15e96508cf 100644 --- a/Ryujinx.HLE/HOS/Services/Time/Clock/SteadyClockCore.cs +++ b/Ryujinx.HLE/HOS/Services/Time/Clock/SteadyClockCore.cs @@ -40,10 +40,17 @@ namespace Ryujinx.HLE.HOS.Services.Time.Clock ClockSourceId = _clockSourceId }; - result.TimePoint = _internalOffset.ToSeconds() + TimeSpanType.FromTicks(thread.Context.ThreadState.CntpctEl0, thread.Context.ThreadState.CntfrqEl0).ToSeconds(); + TimeSpanType ticksTimeSpan = TimeSpanType.FromTicks(thread.Context.ThreadState.CntpctEl0, thread.Context.ThreadState.CntfrqEl0); + + result.TimePoint = _internalOffset.ToSeconds() + ticksTimeSpan.ToSeconds(); return result; } + public UInt128 GetClockSourceId() + { + return _clockSourceId; + } + public SteadyClockTimePoint GetCurrentTimePoint(KThread thread) { SteadyClockTimePoint result = GetTimePoint(thread); diff --git a/Ryujinx.HLE/HOS/Services/Time/Clock/SystemClockCore.cs b/Ryujinx.HLE/HOS/Services/Time/Clock/SystemClockCore.cs new file mode 100644 index 0000000000..d3a056e438 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Time/Clock/SystemClockCore.cs @@ -0,0 +1,31 @@ +using Ryujinx.HLE.HOS.Kernel.Threading; + +namespace Ryujinx.HLE.HOS.Services.Time.Clock +{ + abstract class SystemClockCore + { + public abstract SteadyClockCore GetSteadyClockCore(); + + public abstract ResultCode GetSystemClockContext(KThread thread, out SystemClockContext context); + + public abstract ResultCode SetSystemClockContext(SystemClockContext context); + + public abstract ResultCode Flush(SystemClockContext context); + + public bool IsClockSetup(KThread thread) + { + ResultCode result = GetSystemClockContext(thread, out SystemClockContext context); + + if (result == ResultCode.Success) + { + SteadyClockCore steadyClockCore = GetSteadyClockCore(); + + SteadyClockTimePoint steadyClockTimePoint = steadyClockCore.GetCurrentTimePoint(thread); + + return steadyClockTimePoint.ClockSourceId == context.SteadyTimePoint.ClockSourceId; + } + + return false; + } + } +} diff --git a/Ryujinx.HLE/HOS/Services/Time/IStaticService.cs b/Ryujinx.HLE/HOS/Services/Time/IStaticService.cs index 420e691281..58010f1f46 100644 --- a/Ryujinx.HLE/HOS/Services/Time/IStaticService.cs +++ b/Ryujinx.HLE/HOS/Services/Time/IStaticService.cs @@ -1,5 +1,6 @@ using Ryujinx.HLE.HOS.Ipc; using Ryujinx.HLE.HOS.Kernel.Common; +using Ryujinx.HLE.HOS.Services.Time.Clock; using System; namespace Ryujinx.HLE.HOS.Services.Time @@ -19,7 +20,7 @@ namespace Ryujinx.HLE.HOS.Services.Time // GetStandardUserSystemClock() -> object public ResultCode GetStandardUserSystemClock(ServiceCtx context) { - MakeObject(context, new ISystemClock(SystemClockType.User)); + MakeObject(context, new ISystemClock(StandardUserSystemClockCore.Instance)); return ResultCode.Success; } @@ -28,7 +29,7 @@ namespace Ryujinx.HLE.HOS.Services.Time // GetStandardNetworkSystemClock() -> object public ResultCode GetStandardNetworkSystemClock(ServiceCtx context) { - MakeObject(context, new ISystemClock(SystemClockType.Network)); + MakeObject(context, new ISystemClock(StandardNetworkSystemClockCore.Instance)); return ResultCode.Success; } @@ -55,7 +56,7 @@ namespace Ryujinx.HLE.HOS.Services.Time // GetStandardLocalSystemClock() -> object public ResultCode GetStandardLocalSystemClock(ServiceCtx context) { - MakeObject(context, new ISystemClock(SystemClockType.Local)); + MakeObject(context, new ISystemClock(StandardLocalSystemClockCore.Instance)); return ResultCode.Success; } @@ -81,6 +82,7 @@ namespace Ryujinx.HLE.HOS.Services.Time // CalculateMonotonicSystemClockBaseTimePoint(nn::time::SystemClockContext) -> u64 public ResultCode CalculateMonotonicSystemClockBaseTimePoint(ServiceCtx context) { + // TODO: reimplement this long timeOffset = (long)(DateTime.UtcNow - StartupDate).TotalSeconds; long systemClockContextEpoch = context.RequestData.ReadInt64(); diff --git a/Ryujinx.HLE/HOS/Services/Time/ISteadyClock.cs b/Ryujinx.HLE/HOS/Services/Time/ISteadyClock.cs index 82248e8f0b..e96954de35 100644 --- a/Ryujinx.HLE/HOS/Services/Time/ISteadyClock.cs +++ b/Ryujinx.HLE/HOS/Services/Time/ISteadyClock.cs @@ -39,16 +39,16 @@ namespace Ryujinx.HLE.HOS.Services.Time [Command(200)] // 3.0.0+ // GetInternalOffset() -> nn::TimeSpanType - public long GetInternalOffset(ServiceCtx context) + public ResultCode GetInternalOffset(ServiceCtx context) { context.ResponseData.WriteStruct(SteadyClockCore.Instance.GetInternalOffset()); - return 0; + return ResultCode.Success; } [Command(201)] // 3.0.0-3.0.2 // SetInternalOffset(nn::TimeSpanType) - public long SetInternalOffset(ServiceCtx context) + public ResultCode SetInternalOffset(ServiceCtx context) { TimeSpanType internalOffset = context.RequestData.ReadStruct(); diff --git a/Ryujinx.HLE/HOS/Services/Time/ISystemClock.cs b/Ryujinx.HLE/HOS/Services/Time/ISystemClock.cs index 0ed34d1791..62e10da952 100644 --- a/Ryujinx.HLE/HOS/Services/Time/ISystemClock.cs +++ b/Ryujinx.HLE/HOS/Services/Time/ISystemClock.cs @@ -1,97 +1,98 @@ -using System; +using Ryujinx.Common; +using Ryujinx.HLE.HOS.Services.Time.Clock; namespace Ryujinx.HLE.HOS.Services.Time { class ISystemClock : IpcService { - private static readonly DateTime Epoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc); + private SystemClockCore _clockCore; - private SystemClockType _clockType; - private DateTime _systemClockContextEpoch; - private long _systemClockTimePoint; - private byte[] _systemClockContextEnding; - private long _timeOffset; - - public ISystemClock(SystemClockType clockType) + public ISystemClock(SystemClockCore clockCore) { - _clockType = clockType; - _systemClockContextEpoch = System.Diagnostics.Process.GetCurrentProcess().StartTime; - _systemClockContextEnding = new byte[0x10]; - _timeOffset = 0; - - if (clockType == SystemClockType.User || - clockType == SystemClockType.Network) - { - _systemClockContextEpoch = _systemClockContextEpoch.ToUniversalTime(); - } - - _systemClockTimePoint = (long)(_systemClockContextEpoch - Epoch).TotalSeconds; + _clockCore = clockCore; } [Command(0)] // GetCurrentTime() -> nn::time::PosixTime public ResultCode GetCurrentTime(ServiceCtx context) { - DateTime currentTime = DateTime.Now; + SteadyClockCore steadyClockCore = _clockCore.GetSteadyClockCore(); - if (_clockType == SystemClockType.User || - _clockType == SystemClockType.Network) + SteadyClockTimePoint currentTimePoint = steadyClockCore.GetCurrentTimePoint(context.Thread); + + ResultCode result = _clockCore.GetSystemClockContext(context.Thread, out SystemClockContext clockContext); + + if (result == ResultCode.Success) { - currentTime = currentTime.ToUniversalTime(); + result = ResultCode.TimeMismatch; + + if (currentTimePoint.ClockSourceId == clockContext.SteadyTimePoint.ClockSourceId) + { + ulong posixTime = clockContext.Offset + currentTimePoint.TimePoint; + + context.ResponseData.Write(posixTime); + + result = 0; + } } - context.ResponseData.Write((long)((currentTime - Epoch).TotalSeconds) + _timeOffset); - - return ResultCode.Success; + return result; } [Command(1)] // SetCurrentTime(nn::time::PosixTime) public ResultCode SetCurrentTime(ServiceCtx context) { - DateTime currentTime = DateTime.Now; + ulong posixTime = context.RequestData.ReadUInt64(); - if (_clockType == SystemClockType.User || - _clockType == SystemClockType.Network) + SteadyClockCore steadyClockCore = _clockCore.GetSteadyClockCore(); + + SteadyClockTimePoint currentTimePoint = steadyClockCore.GetCurrentTimePoint(context.Thread); + + SystemClockContext clockContext = new SystemClockContext() { - currentTime = currentTime.ToUniversalTime(); + Offset = posixTime - currentTimePoint.TimePoint, + SteadyTimePoint = currentTimePoint + }; + + ResultCode result = _clockCore.SetSystemClockContext(clockContext); + + if (result == ResultCode.Success) + { + result = _clockCore.Flush(clockContext); } - _timeOffset = (context.RequestData.ReadInt64() - (long)(currentTime - Epoch).TotalSeconds); - - return ResultCode.Success; + return result; } [Command(2)] // GetSystemClockContext() -> nn::time::SystemClockContext public ResultCode GetSystemClockContext(ServiceCtx context) { - context.ResponseData.Write((long)(_systemClockContextEpoch - Epoch).TotalSeconds); + ResultCode result = _clockCore.GetSystemClockContext(context.Thread, out SystemClockContext clockContext); - // The point in time, TODO: is there a link between epoch and this? - context.ResponseData.Write(_systemClockTimePoint); - - // This seems to be some kind of identifier? - for (int i = 0; i < 0x10; i++) + if (result == ResultCode.Success) { - context.ResponseData.Write(_systemClockContextEnding[i]); + context.ResponseData.WriteStruct(clockContext); } - return ResultCode.Success; + return result; } [Command(3)] // SetSystemClockContext(nn::time::SystemClockContext) public ResultCode SetSystemClockContext(ServiceCtx context) { - long newSystemClockEpoch = context.RequestData.ReadInt64(); - long newSystemClockTimePoint = context.RequestData.ReadInt64(); + SystemClockContext clockContext = context.RequestData.ReadStruct(); - _systemClockContextEpoch = Epoch.Add(TimeSpan.FromSeconds(newSystemClockEpoch)); - _systemClockTimePoint = newSystemClockTimePoint; - _systemClockContextEnding = context.RequestData.ReadBytes(0x10); + ResultCode result = _clockCore.SetSystemClockContext(clockContext); - return ResultCode.Success; + if (result == ResultCode.Success) + { + result = _clockCore.Flush(clockContext); + } + + return result; } } } \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Time/ResultCode.cs b/Ryujinx.HLE/HOS/Services/Time/ResultCode.cs index 5868a458f0..f9c324b68f 100644 --- a/Ryujinx.HLE/HOS/Services/Time/ResultCode.cs +++ b/Ryujinx.HLE/HOS/Services/Time/ResultCode.cs @@ -7,11 +7,13 @@ Success = 0, + TimeMismatch = (102 << ErrorCodeShift) | ModuleId, TimeNotFound = (200 << ErrorCodeShift) | ModuleId, Overflow = (201 << ErrorCodeShift) | ModuleId, LocationNameTooLong = (801 << ErrorCodeShift) | ModuleId, OutOfRange = (902 << ErrorCodeShift) | ModuleId, TimeZoneConversionFailed = (903 << ErrorCodeShift) | ModuleId, - TimeZoneNotFound = (989 << ErrorCodeShift) | ModuleId + TimeZoneNotFound = (989 << ErrorCodeShift) | ModuleId, + NotImplemented = (990 << ErrorCodeShift) | ModuleId, } } \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Time/SystemClockType.cs b/Ryujinx.HLE/HOS/Services/Time/SystemClockType.cs deleted file mode 100644 index 54b7df3f81..0000000000 --- a/Ryujinx.HLE/HOS/Services/Time/SystemClockType.cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace Ryujinx.HLE.HOS.Services.Time -{ - enum SystemClockType - { - User, - Network, - Local, - EphemeralNetwork - } -} \ No newline at end of file