diff --git a/Ryujinx.HLE/HOS/Horizon.cs b/Ryujinx.HLE/HOS/Horizon.cs index 496fbdbe43..a7d603bd27 100644 --- a/Ryujinx.HLE/HOS/Horizon.cs +++ b/Ryujinx.HLE/HOS/Horizon.cs @@ -9,6 +9,7 @@ using Ryujinx.HLE.HOS.Kernel.Memory; using Ryujinx.HLE.HOS.Kernel.Process; using Ryujinx.HLE.HOS.Kernel.Threading; 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; @@ -195,6 +196,11 @@ namespace Ryujinx.HLE.HOS LoadKeySet(); 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) * 1000)); } 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 new file mode 100644 index 0000000000..b1edbbf2e7 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Time/Clock/ClockTypes.cs @@ -0,0 +1,34 @@ +using Ryujinx.HLE.Utilities; +using System; +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Time.Clock +{ + [StructLayout(LayoutKind.Sequential)] + public struct TimeSpanType + { + public ulong NanoSeconds; + + public TimeSpanType(ulong nanoSeconds) + { + NanoSeconds = nanoSeconds; + } + + public ulong ToSeconds() + { + return NanoSeconds * 1000000000; + } + + public static TimeSpanType FromTicks(ulong ticks, ulong frequency) + { + return new TimeSpanType((ticks * 1000) / frequency); + } + } + + [StructLayout(LayoutKind.Sequential)] + public struct SteadyClockTimePoint + { + public ulong TimePoint; + public UInt128 ClockSourceId; + } +} diff --git a/Ryujinx.HLE/HOS/Services/Time/Clock/SteadyClockCore.cs b/Ryujinx.HLE/HOS/Services/Time/Clock/SteadyClockCore.cs new file mode 100644 index 0000000000..c3081adf24 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Time/Clock/SteadyClockCore.cs @@ -0,0 +1,78 @@ +using Ryujinx.HLE.HOS.Kernel.Threading; +using Ryujinx.HLE.Utilities; +using System; + +namespace Ryujinx.HLE.HOS.Services.Time.Clock +{ + class SteadyClockCore + { + private TimeSpanType _testOffset; + private TimeSpanType _internalOffset; + private UInt128 _clockSourceId; + + private static SteadyClockCore instance; + + public static SteadyClockCore Instance + { + get + { + if (instance == null) + { + instance = new SteadyClockCore(); + } + + return instance; + } + } + + private SteadyClockCore() + { + _testOffset = new TimeSpanType(0); + _internalOffset = new TimeSpanType(0); + _clockSourceId = new UInt128(Guid.NewGuid().ToByteArray()); + } + + private SteadyClockTimePoint GetTimePoint(KThread thread) + { + SteadyClockTimePoint result = new SteadyClockTimePoint + { + TimePoint = 0, + ClockSourceId = _clockSourceId + }; + + result.TimePoint = _internalOffset.ToSeconds() + TimeSpanType.FromTicks(thread.Context.ThreadState.CntpctEl0, thread.Context.ThreadState.CntfrqEl0).ToSeconds(); + return result; + } + + public SteadyClockTimePoint GetCurrentTimePoint(KThread thread) + { + SteadyClockTimePoint result = GetTimePoint(thread); + + result.TimePoint += _testOffset.ToSeconds(); + + return result; + } + + public TimeSpanType GetTestOffset() + { + return _testOffset; + } + + public void SetTestOffset(TimeSpanType testOffset) + { + _testOffset = testOffset; + } + + // TODO: check if this is accurate + public TimeSpanType GetInternalOffset() + { + return _internalOffset; + } + + // TODO: check if this is accurate + public void SetInternalOffset(TimeSpanType internalOffset) + { + _internalOffset = internalOffset; + } + } +} diff --git a/Ryujinx.HLE/HOS/Services/Time/ISteadyClock.cs b/Ryujinx.HLE/HOS/Services/Time/ISteadyClock.cs index ccf19d7d53..82248e8f0b 100644 --- a/Ryujinx.HLE/HOS/Services/Time/ISteadyClock.cs +++ b/Ryujinx.HLE/HOS/Services/Time/ISteadyClock.cs @@ -1,26 +1,18 @@ -using System; +using Ryujinx.Common; +using Ryujinx.HLE.HOS.Services.Time.Clock; namespace Ryujinx.HLE.HOS.Services.Time { class ISteadyClock : IpcService { - private ulong _testOffset; - - public ISteadyClock() - { - _testOffset = 0; - } [Command(0)] // GetCurrentTimePoint() -> nn::time::SteadyClockTimePoint public ResultCode GetCurrentTimePoint(ServiceCtx context) { - context.ResponseData.Write((long)(System.Diagnostics.Process.GetCurrentProcess().StartTime - DateTime.Now).TotalSeconds); + SteadyClockTimePoint currentTimePoint = SteadyClockCore.Instance.GetCurrentTimePoint(context.Thread); - for (int i = 0; i < 0x10; i++) - { - context.ResponseData.Write((byte)0); - } + context.ResponseData.WriteStruct(currentTimePoint); return ResultCode.Success; } @@ -29,7 +21,7 @@ namespace Ryujinx.HLE.HOS.Services.Time // GetTestOffset() -> nn::TimeSpanType public ResultCode GetTestOffset(ServiceCtx context) { - context.ResponseData.Write(_testOffset); + context.ResponseData.WriteStruct(SteadyClockCore.Instance.GetTestOffset()); return ResultCode.Success; } @@ -38,7 +30,29 @@ namespace Ryujinx.HLE.HOS.Services.Time // SetTestOffset(nn::TimeSpanType) public ResultCode SetTestOffset(ServiceCtx context) { - _testOffset = context.RequestData.ReadUInt64(); + TimeSpanType testOffset = context.RequestData.ReadStruct(); + + SteadyClockCore.Instance.SetTestOffset(testOffset); + + return 0; + } + + [Command(200)] // 3.0.0+ + // GetInternalOffset() -> nn::TimeSpanType + public long GetInternalOffset(ServiceCtx context) + { + context.ResponseData.WriteStruct(SteadyClockCore.Instance.GetInternalOffset()); + + return 0; + } + + [Command(201)] // 3.0.0-3.0.2 + // SetInternalOffset(nn::TimeSpanType) + public long SetInternalOffset(ServiceCtx context) + { + TimeSpanType internalOffset = context.RequestData.ReadStruct(); + + SteadyClockCore.Instance.SetInternalOffset(internalOffset); return ResultCode.Success; }