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
This commit is contained in:
Thomas Guillemard 2019-10-08 05:48:49 +02:00 committed by jduncanator
parent 16869402bf
commit 1aba033ba7
37 changed files with 2202 additions and 716 deletions

View File

@ -56,7 +56,7 @@ The latest automatic build for Windows, macOS, and Linux can be found on the [Of
- **System Titles** - **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`. You can install them by mounting your nand partition using [HacDiskMount](https://switchtools.sshnuke.net/) and copy the content in `RyuFs/nand/system`.
- **Executables** - **Executables**

View File

@ -59,6 +59,11 @@ namespace Ryujinx.HLE
return *((ulong*)(_ramPtr + position)); return *((ulong*)(_ramPtr + position));
} }
public unsafe T ReadStruct<T>(long position)
{
return Marshal.PtrToStructure<T>((IntPtr)(_ramPtr + position));
}
public void WriteSByte(long position, sbyte value) public void WriteSByte(long position, sbyte value)
{ {
WriteByte(position, (byte)value); WriteByte(position, (byte)value);

View File

@ -0,0 +1,9 @@
using System;
namespace Ryujinx.HLE.Exceptions
{
class InternalServiceException: Exception
{
public InternalServiceException(string message) : base(message) { }
}
}

View File

@ -1,6 +1,6 @@
using LibHac.Fs; using LibHac.Fs;
using LibHac.Fs.NcaUtils; using LibHac.Fs.NcaUtils;
using Ryujinx.HLE.HOS.Services.Time.TimeZone; using Ryujinx.HLE.HOS.Services.Time;
using Ryujinx.HLE.Utilities; using Ryujinx.HLE.Utilities;
using System; using System;
using System.Collections.Generic; 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) public void ClearEntry(long titleId, ContentType contentType, StorageId storageId)

View File

@ -8,12 +8,14 @@ using Ryujinx.HLE.HOS.Kernel.Common;
using Ryujinx.HLE.HOS.Kernel.Memory; using Ryujinx.HLE.HOS.Kernel.Memory;
using Ryujinx.HLE.HOS.Kernel.Process; using Ryujinx.HLE.HOS.Kernel.Process;
using Ryujinx.HLE.HOS.Kernel.Threading; using Ryujinx.HLE.HOS.Kernel.Threading;
using Ryujinx.HLE.HOS.Services.Pcv.Bpc;
using Ryujinx.HLE.HOS.Services.Settings; using Ryujinx.HLE.HOS.Services.Settings;
using Ryujinx.HLE.HOS.Services.Sm; using Ryujinx.HLE.HOS.Services.Sm;
using Ryujinx.HLE.HOS.Services.Time.Clock; using Ryujinx.HLE.HOS.Services.Time.Clock;
using Ryujinx.HLE.HOS.SystemState; using Ryujinx.HLE.HOS.SystemState;
using Ryujinx.HLE.Loaders.Executables; using Ryujinx.HLE.Loaders.Executables;
using Ryujinx.HLE.Loaders.Npdm; using Ryujinx.HLE.Loaders.Npdm;
using Ryujinx.HLE.Utilities;
using System; using System;
using System.Collections.Concurrent; using System.Collections.Concurrent;
using System.Collections.Generic; using System.Collections.Generic;
@ -22,7 +24,8 @@ using System.Linq;
using System.Reflection; using System.Reflection;
using System.Threading; 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 namespace Ryujinx.HLE.HOS
{ {
@ -87,8 +90,6 @@ namespace Ryujinx.HLE.HOS
internal KSharedMemory HidSharedMem { get; private set; } internal KSharedMemory HidSharedMem { get; private set; }
internal KSharedMemory FontSharedMem { get; private set; } internal KSharedMemory FontSharedMem { get; private set; }
internal KSharedMemory IirsSharedMem { get; private set; } internal KSharedMemory IirsSharedMem { get; private set; }
internal KSharedMemory TimeSharedMem { get; private set; }
internal SharedFontManager Font { get; private set; } internal SharedFontManager Font { get; private set; }
internal ContentManager ContentManager { 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); HidSharedMem = new KSharedMemory(this, hidPageList, 0, 0, MemoryPermission.Read);
FontSharedMem = new KSharedMemory(this, fontPageList, 0, 0, MemoryPermission.Read); FontSharedMem = new KSharedMemory(this, fontPageList, 0, 0, MemoryPermission.Read);
IirsSharedMem = new KSharedMemory(this, iirsPageList, 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); AppletState = new AppletStateMgr(this);
@ -200,17 +204,30 @@ namespace Ryujinx.HLE.HOS
ContentManager = new ContentManager(device); 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. // 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)) if (NxSettings.Settings.TryGetValue("time!standard_network_clock_sufficient_accuracy_minutes", out object standardNetworkClockSufficientAccuracyMinutes))
{ {
TimeSpanType standardNetworkClockSufficientAccuracy = new TimeSpanType((int)standardNetworkClockSufficientAccuracyMinutes * 60000000000); 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) public void LoadCart(string exeFsDir, string romFsFile = null)

View File

@ -1704,6 +1704,7 @@ namespace Ryujinx.HLE.HOS.Services.Settings
{ "time!standard_steady_clock_test_offset_minutes", 0 }, { "time!standard_steady_clock_test_offset_minutes", 0 },
{ "time!standard_steady_clock_rtc_update_interval_minutes", 5 }, { "time!standard_steady_clock_rtc_update_interval_minutes", 5 },
{ "time!standard_network_clock_sufficient_accuracy_minutes", 43200 }, { "time!standard_network_clock_sufficient_accuracy_minutes", 43200 },
{ "time!standard_user_clock_initial_year", 2019 },
{ "usb!usb30_force_enabled", false }, { "usb!usb30_force_enabled", false },
{ "wlan_debug!skip_wlan_boot", false } { "wlan_debug!skip_wlan_boot", false }
}; };

View File

@ -0,0 +1,10 @@
namespace Ryujinx.HLE.HOS.Services.Time.Clock
{
class EphemeralNetworkSystemClockContextWriter : SystemClockContextUpdateCallback
{
protected override ResultCode Update()
{
return ResultCode.Success;
}
}
}

View File

@ -2,26 +2,6 @@
{ {
class EphemeralNetworkSystemClockCore : SystemClockCore 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 EphemeralNetworkSystemClockCore(SteadyClockCore steadyClockCore) : base(steadyClockCore) { }
public override ResultCode Flush(SystemClockContext context)
{
return ResultCode.Success;
}
} }
} }

View File

@ -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;
}
}
}

View File

@ -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;
}
}
}

View File

@ -2,28 +2,6 @@
{ {
class StandardLocalSystemClockCore : SystemClockCore 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 StandardLocalSystemClockCore(StandardSteadyClockCore steadyClockCore) : base(steadyClockCore) {}
public override ResultCode Flush(SystemClockContext context)
{
// TODO: set:sys SetUserSystemClockContext
return ResultCode.Success;
}
} }
} }

View File

@ -6,33 +6,11 @@ namespace Ryujinx.HLE.HOS.Services.Time.Clock
{ {
private TimeSpanType _standardNetworkClockSufficientAccuracy; 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) public StandardNetworkSystemClockCore(StandardSteadyClockCore steadyClockCore) : base(steadyClockCore)
{ {
_standardNetworkClockSufficientAccuracy = new TimeSpanType(0); _standardNetworkClockSufficientAccuracy = new TimeSpanType(0);
} }
public override ResultCode Flush(SystemClockContext context)
{
// TODO: set:sys SetNetworkSystemClockContext
return ResultCode.Success;
}
public bool IsStandardNetworkSystemClockAccuracySufficient(KThread thread) public bool IsStandardNetworkSystemClockAccuracySufficient(KThread thread)
{ {
SteadyClockCore steadyClockCore = GetSteadyClockCore(); SteadyClockCore steadyClockCore = GetSteadyClockCore();
@ -40,7 +18,7 @@ namespace Ryujinx.HLE.HOS.Services.Time.Clock
bool isStandardNetworkClockSufficientAccuracy = false; 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) if (result == ResultCode.Success && context.SteadyTimePoint.GetSpanBetween(currentTimePoint, out long outSpan) == ResultCode.Success)
{ {

View File

@ -1,49 +1,30 @@
using Ryujinx.HLE.HOS.Kernel.Threading; using Ryujinx.HLE.HOS.Kernel.Threading;
using Ryujinx.HLE.HOS.Services.Pcv.Bpc;
namespace Ryujinx.HLE.HOS.Services.Time.Clock namespace Ryujinx.HLE.HOS.Services.Time.Clock
{ {
class StandardSteadyClockCore : SteadyClockCore class StandardSteadyClockCore : SteadyClockCore
{ {
private long _setupValue; private TimeSpanType _setupValue;
private ResultCode _setupResultCode;
private bool _isRtcResetDetected;
private TimeSpanType _testOffset; private TimeSpanType _testOffset;
private TimeSpanType _internalOffset; private TimeSpanType _internalOffset;
private TimeSpanType _cachedRawTimePoint;
private static StandardSteadyClockCore _instance; public StandardSteadyClockCore()
public static StandardSteadyClockCore Instance
{ {
get _setupValue = TimeSpanType.Zero;
{ _testOffset = TimeSpanType.Zero;
if (_instance == null) _internalOffset = TimeSpanType.Zero;
{ _cachedRawTimePoint = TimeSpanType.Zero;
_instance = new StandardSteadyClockCore();
}
return _instance;
}
}
private StandardSteadyClockCore()
{
_testOffset = new TimeSpanType(0);
_internalOffset = new TimeSpanType(0);
} }
public override SteadyClockTimePoint GetTimePoint(KThread thread) public override SteadyClockTimePoint GetTimePoint(KThread thread)
{ {
SteadyClockTimePoint result = new SteadyClockTimePoint SteadyClockTimePoint result = new SteadyClockTimePoint
{ {
TimePoint = 0, TimePoint = GetCurrentRawTimePoint(thread).ToSeconds(),
ClockSourceId = GetClockSourceId() ClockSourceId = GetClockSourceId()
}; };
TimeSpanType ticksTimeSpan = TimeSpanType.FromTicks(thread.Context.CntpctEl0, thread.Context.CntfrqEl0);
result.TimePoint = _setupValue + ticksTimeSpan.ToSeconds();
return result; return result;
} }
@ -57,16 +38,6 @@ namespace Ryujinx.HLE.HOS.Services.Time.Clock
_testOffset = testOffset; _testOffset = testOffset;
} }
public override ResultCode GetRtcValue(out ulong rtcValue)
{
return (ResultCode)IRtcManager.GetExternalRtcValue(out rtcValue);
}
public bool IsRtcResetDetected()
{
return _isRtcResetDetected;
}
public override TimeSpanType GetInternalOffset() public override TimeSpanType GetInternalOffset()
{ {
return _internalOffset; return _internalOffset;
@ -77,31 +48,35 @@ namespace Ryujinx.HLE.HOS.Services.Time.Clock
_internalOffset = internalOffset; _internalOffset = internalOffset;
} }
public override ResultCode GetSetupResultValue() public override TimeSpanType GetCurrentRawTimePoint(KThread thread)
{ {
return _setupResultCode; TimeSpanType ticksTimeSpan;
}
public void ConfigureSetupValue() // As this may be called before the guest code, we support passing a null thread to make this api usable.
{ if (thread == null)
int retry = 0;
ResultCode result = ResultCode.Success;
while (retry < 20)
{ {
result = (ResultCode)IRtcManager.GetExternalRtcValue(out ulong rtcValue); ticksTimeSpan = TimeSpanType.FromSeconds(0);
}
if (result == ResultCode.Success) else
{ {
_setupValue = (long)rtcValue; ticksTimeSpan = TimeSpanType.FromTicks(thread.Context.CntpctEl0, thread.Context.CntfrqEl0);
break;
}
retry++;
} }
_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;
} }
} }
} }

View File

@ -1,4 +1,5 @@
using Ryujinx.HLE.HOS.Kernel.Threading; using Ryujinx.HLE.HOS.Kernel.Threading;
using System;
namespace Ryujinx.HLE.HOS.Services.Time.Clock namespace Ryujinx.HLE.HOS.Services.Time.Clock
{ {
@ -7,35 +8,25 @@ namespace Ryujinx.HLE.HOS.Services.Time.Clock
private StandardLocalSystemClockCore _localSystemClockCore; private StandardLocalSystemClockCore _localSystemClockCore;
private StandardNetworkSystemClockCore _networkSystemClockCore; private StandardNetworkSystemClockCore _networkSystemClockCore;
private bool _autoCorrectionEnabled; private bool _autoCorrectionEnabled;
private SteadyClockTimePoint _autoCorrectionTime;
private static StandardUserSystemClockCore _instance; private KEvent _autoCorrectionEvent;
public static StandardUserSystemClockCore Instance
{
get
{
if (_instance == null)
{
_instance = new StandardUserSystemClockCore(StandardLocalSystemClockCore.Instance, StandardNetworkSystemClockCore.Instance);
}
return _instance;
}
}
public StandardUserSystemClockCore(StandardLocalSystemClockCore localSystemClockCore, StandardNetworkSystemClockCore networkSystemClockCore) : base(localSystemClockCore.GetSteadyClockCore()) public StandardUserSystemClockCore(StandardLocalSystemClockCore localSystemClockCore, StandardNetworkSystemClockCore networkSystemClockCore) : base(localSystemClockCore.GetSteadyClockCore())
{ {
_localSystemClockCore = localSystemClockCore; _localSystemClockCore = localSystemClockCore;
_networkSystemClockCore = networkSystemClockCore; _networkSystemClockCore = networkSystemClockCore;
_autoCorrectionEnabled = false; _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); ResultCode result = ApplyAutomaticCorrection(thread, false);
@ -43,13 +34,13 @@ namespace Ryujinx.HLE.HOS.Services.Time.Clock
if (result == ResultCode.Success) if (result == ResultCode.Success)
{ {
return _localSystemClockCore.GetSystemClockContext(thread, out context); return _localSystemClockCore.GetClockContext(thread, out context);
} }
return result; return result;
} }
public override ResultCode SetSystemClockContext(SystemClockContext context) public override ResultCode SetClockContext(SystemClockContext context)
{ {
return ResultCode.NotImplemented; return ResultCode.NotImplemented;
} }
@ -60,17 +51,22 @@ namespace Ryujinx.HLE.HOS.Services.Time.Clock
if (_autoCorrectionEnabled != autoCorrectionEnabled && _networkSystemClockCore.IsClockSetup(thread)) if (_autoCorrectionEnabled != autoCorrectionEnabled && _networkSystemClockCore.IsClockSetup(thread))
{ {
result = _networkSystemClockCore.GetSystemClockContext(thread, out SystemClockContext context); result = _networkSystemClockCore.GetClockContext(thread, out SystemClockContext context);
if (result == ResultCode.Success) if (result == ResultCode.Success)
{ {
_localSystemClockCore.SetSystemClockContext(context); _localSystemClockCore.SetClockContext(context);
} }
} }
return result; return result;
} }
internal void CreateAutomaticCorrectionEvent(Horizon system)
{
_autoCorrectionEvent = new KEvent(system);
}
public ResultCode SetAutomaticCorrectionEnabled(KThread thread, bool autoCorrectionEnabled) public ResultCode SetAutomaticCorrectionEnabled(KThread thread, bool autoCorrectionEnabled)
{ {
ResultCode result = ApplyAutomaticCorrection(thread, autoCorrectionEnabled); ResultCode result = ApplyAutomaticCorrection(thread, autoCorrectionEnabled);
@ -87,5 +83,25 @@ namespace Ryujinx.HLE.HOS.Services.Time.Clock
{ {
return _autoCorrectionEnabled; 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();
}
} }
} }

View File

@ -7,10 +7,14 @@ namespace Ryujinx.HLE.HOS.Services.Time.Clock
abstract class SteadyClockCore abstract class SteadyClockCore
{ {
private UInt128 _clockSourceId; private UInt128 _clockSourceId;
private bool _isRtcResetDetected;
private bool _isInitialized;
public SteadyClockCore() public SteadyClockCore()
{ {
_clockSourceId = new UInt128(Guid.NewGuid().ToByteArray()); _clockSourceId = new UInt128(Guid.NewGuid().ToByteArray());
_isRtcResetDetected = false;
_isInitialized = false;
} }
public UInt128 GetClockSourceId() public UInt128 GetClockSourceId()
@ -18,6 +22,16 @@ namespace Ryujinx.HLE.HOS.Services.Time.Clock
return _clockSourceId; return _clockSourceId;
} }
public void SetClockSourceId(UInt128 clockSourceId)
{
_clockSourceId = clockSourceId;
}
public void SetRtcReset()
{
_isRtcResetDetected = true;
}
public virtual TimeSpanType GetTestOffset() public virtual TimeSpanType GetTestOffset()
{ {
return new TimeSpanType(0); return new TimeSpanType(0);
@ -25,16 +39,21 @@ namespace Ryujinx.HLE.HOS.Services.Time.Clock
public virtual void SetTestOffset(TimeSpanType testOffset) {} public virtual void SetTestOffset(TimeSpanType testOffset) {}
public virtual ResultCode GetRtcValue(out ulong rtcValue) public ResultCode GetRtcValue(out ulong rtcValue)
{ {
rtcValue = 0; rtcValue = 0;
return ResultCode.NotImplemented; 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() public virtual TimeSpanType GetInternalOffset()
@ -49,6 +68,13 @@ namespace Ryujinx.HLE.HOS.Services.Time.Clock
throw new NotImplementedException(); throw new NotImplementedException();
} }
public virtual TimeSpanType GetCurrentRawTimePoint(KThread thread)
{
SteadyClockTimePoint timePoint = GetTimePoint(thread);
return TimeSpanType.FromSeconds(timePoint.TimePoint);
}
public SteadyClockTimePoint GetCurrentTimePoint(KThread thread) public SteadyClockTimePoint GetCurrentTimePoint(KThread thread)
{ {
SteadyClockTimePoint result = GetTimePoint(thread); SteadyClockTimePoint result = GetTimePoint(thread);
@ -58,5 +84,15 @@ namespace Ryujinx.HLE.HOS.Services.Time.Clock
return result; return result;
} }
public bool IsInitialized()
{
return _isInitialized;
}
public void MarkInitialized()
{
_isInitialized = true;
}
} }
} }

View File

@ -0,0 +1,72 @@
using Ryujinx.HLE.HOS.Kernel.Threading;
using System;
using System.Collections.Generic;
using System.Threading;
namespace Ryujinx.HLE.HOS.Services.Time.Clock
{
abstract class SystemClockContextUpdateCallback
{
private List<KWritableEvent> _operationEventList;
protected SystemClockContext _context;
private bool _hasContext;
public SystemClockContextUpdateCallback()
{
_operationEventList = new List<KWritableEvent>();
_context = new SystemClockContext();
_hasContext = false;
}
private bool NeedUpdate(SystemClockContext context)
{
if (_hasContext)
{
return _context.Offset != context.Offset || _context.SteadyTimePoint.ClockSourceId != context.SteadyTimePoint.ClockSourceId;
}
return true;
}
public void RegisterOperationEvent(KWritableEvent writableEvent)
{
Monitor.Enter(_operationEventList);
_operationEventList.Add(writableEvent);
Monitor.Exit(_operationEventList);
}
private void BroadcastOperationEvent()
{
Monitor.Enter(_operationEventList);
foreach (KWritableEvent e in _operationEventList)
{
e.Signal();
}
Monitor.Exit(_operationEventList);
}
protected abstract ResultCode Update();
public ResultCode Update(SystemClockContext context)
{
ResultCode result = ResultCode.Success;
if (NeedUpdate(context))
{
_context = context;
_hasContext = true;
result = Update();
if (result == ResultCode.Success)
{
BroadcastOperationEvent();
}
}
return result;
}
}
}

View File

@ -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 namespace Ryujinx.HLE.HOS.Services.Time.Clock
{ {
abstract class SystemClockCore abstract class SystemClockCore
{ {
private SteadyClockCore _steadyClockCore; private SteadyClockCore _steadyClockCore;
private SystemClockContext _context; private SystemClockContext _context;
private bool _isInitialized;
private SystemClockContextUpdateCallback _systemClockContextUpdateCallback;
public SystemClockCore(SteadyClockCore steadyClockCore) public SystemClockCore(SteadyClockCore steadyClockCore)
{ {
_steadyClockCore = steadyClockCore; _steadyClockCore = steadyClockCore;
_context = new SystemClockContext(); _context = new SystemClockContext();
_isInitialized = false;
_context.SteadyTimePoint.ClockSourceId = steadyClockCore.GetClockSourceId(); _context.SteadyTimePoint.ClockSourceId = steadyClockCore.GetClockSourceId();
_systemClockContextUpdateCallback = null;
} }
public virtual SteadyClockCore GetSteadyClockCore() public virtual SteadyClockCore GetSteadyClockCore()
@ -20,31 +25,115 @@ namespace Ryujinx.HLE.HOS.Services.Time.Clock
return _steadyClockCore; 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; context = _context;
return ResultCode.Success; return ResultCode.Success;
} }
public virtual ResultCode SetSystemClockContext(SystemClockContext context) public virtual ResultCode SetClockContext(SystemClockContext context)
{ {
_context = context; _context = context;
return ResultCode.Success; return ResultCode.Success;
} }
public abstract ResultCode Flush(SystemClockContext context); protected virtual ResultCode Flush(SystemClockContext context)
public bool IsClockSetup(KThread thread)
{ {
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) 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; return steadyClockTimePoint.ClockSourceId == context.SteadyTimePoint.ClockSourceId;
} }

View File

@ -4,22 +4,7 @@ namespace Ryujinx.HLE.HOS.Services.Time.Clock
{ {
class TickBasedSteadyClockCore : SteadyClockCore class TickBasedSteadyClockCore : SteadyClockCore
{ {
private static TickBasedSteadyClockCore _instance; public TickBasedSteadyClockCore() {}
public static TickBasedSteadyClockCore Instance
{
get
{
if (_instance == null)
{
_instance = new TickBasedSteadyClockCore();
}
return _instance;
}
}
private TickBasedSteadyClockCore() {}
public override SteadyClockTimePoint GetTimePoint(KThread thread) public override SteadyClockTimePoint GetTimePoint(KThread thread)
{ {
@ -29,7 +14,17 @@ namespace Ryujinx.HLE.HOS.Services.Time.Clock
ClockSourceId = GetClockSourceId() 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(); result.TimePoint = ticksTimeSpan.ToSeconds();

View File

@ -7,7 +7,7 @@ namespace Ryujinx.HLE.HOS.Services.Time.Clock
[StructLayout(LayoutKind.Sequential)] [StructLayout(LayoutKind.Sequential)]
struct SteadyClockTimePoint struct SteadyClockTimePoint
{ {
public long TimePoint; public long TimePoint;
public UInt128 ClockSourceId; public UInt128 ClockSourceId;
public ResultCode GetSpanBetween(SteadyClockTimePoint other, out long outSpan) public ResultCode GetSpanBetween(SteadyClockTimePoint other, out long outSpan)
@ -30,5 +30,14 @@ namespace Ryujinx.HLE.HOS.Services.Time.Clock
return ResultCode.Overflow; return ResultCode.Overflow;
} }
public static SteadyClockTimePoint GetRandom()
{
return new SteadyClockTimePoint
{
TimePoint = 0,
ClockSourceId = new UInt128(Guid.NewGuid().ToByteArray())
};
}
} }
} }

View File

@ -7,6 +7,8 @@ namespace Ryujinx.HLE.HOS.Services.Time.Clock
{ {
private const long NanoSecondsPerSecond = 1000000000; private const long NanoSecondsPerSecond = 1000000000;
public static readonly TimeSpanType Zero = new TimeSpanType(0);
public long NanoSeconds; public long NanoSeconds;
public TimeSpanType(long nanoSeconds) public TimeSpanType(long nanoSeconds)

View File

@ -1,6 +1,6 @@
namespace Ryujinx.HLE.HOS.Services.Time namespace Ryujinx.HLE.HOS.Services.Time
{ {
[Service("time:m")] // 9.0.0+ [Service("time:p")] // 9.0.0+
class IPowerStateRequestHandler : IpcService class IPowerStateRequestHandler : IpcService
{ {
public IPowerStateRequestHandler(ServiceCtx context) { } public IPowerStateRequestHandler(ServiceCtx context) { }

View File

@ -0,0 +1,182 @@
using Ryujinx.Common;
using Ryujinx.HLE.HOS.Services.Pcv.Bpc;
using Ryujinx.HLE.HOS.Services.Settings;
using Ryujinx.HLE.HOS.Services.Time.Clock;
using Ryujinx.HLE.HOS.Services.Time.StaticService;
using System;
namespace Ryujinx.HLE.HOS.Services.Time
{
[Service("time:a", TimePermissions.Admin)]
[Service("time:r", TimePermissions.Repair)]
[Service("time:u", TimePermissions.User)]
class IStaticServiceForGlue : IpcService
{
private IStaticServiceForPsc _inner;
private TimePermissions _permissions;
public IStaticServiceForGlue(ServiceCtx context, TimePermissions permissions)
{
_permissions = permissions;
_inner = new IStaticServiceForPsc(context, permissions);
}
[Command(0)]
// GetStandardUserSystemClock() -> object<nn::timesrv::detail::service::ISystemClock>
public ResultCode GetStandardUserSystemClock(ServiceCtx context)
{
return _inner.GetStandardUserSystemClock(context);
}
[Command(1)]
// GetStandardNetworkSystemClock() -> object<nn::timesrv::detail::service::ISystemClock>
public ResultCode GetStandardNetworkSystemClock(ServiceCtx context)
{
return _inner.GetStandardNetworkSystemClock(context);
}
[Command(2)]
// GetStandardSteadyClock() -> object<nn::timesrv::detail::service::ISteadyClock>
public ResultCode GetStandardSteadyClock(ServiceCtx context)
{
return _inner.GetStandardSteadyClock(context);
}
[Command(3)]
// GetTimeZoneService() -> object<nn::timesrv::detail::service::ITimeZoneService>
public ResultCode GetTimeZoneService(ServiceCtx context)
{
MakeObject(context, new ITimeZoneServiceForGlue(TimeManager.Instance.TimeZone, (_permissions & TimePermissions.TimeZoneWritableMask) != 0));
return ResultCode.Success;
}
[Command(4)]
// GetStandardLocalSystemClock() -> object<nn::timesrv::detail::service::ISystemClock>
public ResultCode GetStandardLocalSystemClock(ServiceCtx context)
{
return _inner.GetStandardLocalSystemClock(context);
}
[Command(5)] // 4.0.0+
// GetEphemeralNetworkSystemClock() -> object<nn::timesrv::detail::service::ISystemClock>
public ResultCode GetEphemeralNetworkSystemClock(ServiceCtx context)
{
return _inner.GetEphemeralNetworkSystemClock(context);
}
[Command(20)] // 6.0.0+
// GetSharedMemoryNativeHandle() -> handle<copy>
public ResultCode GetSharedMemoryNativeHandle(ServiceCtx context)
{
return _inner.GetSharedMemoryNativeHandle(context);
}
[Command(50)] // 4.0.0+
// SetStandardSteadyClockInternalOffset(nn::TimeSpanType internal_offset)
public ResultCode SetStandardSteadyClockInternalOffset(ServiceCtx context)
{
if ((_permissions & TimePermissions.BypassUninitialized) == 0)
{
return ResultCode.PermissionDenied;
}
TimeSpanType internalOffset = context.RequestData.ReadStruct<TimeSpanType>();
// TODO: set:sys SetExternalSteadyClockInternalOffset(internalOffset.ToSeconds())
return ResultCode.Success;
}
[Command(51)] // 9.0.0+
// GetStandardSteadyClockRtcValue() -> u64
public ResultCode GetStandardSteadyClockRtcValue(ServiceCtx context)
{
ResultCode result = (ResultCode)IRtcManager.GetExternalRtcValue(out ulong rtcValue);
if (result == ResultCode.Success)
{
context.ResponseData.Write(rtcValue);
}
return result;
}
[Command(100)]
// IsStandardUserSystemClockAutomaticCorrectionEnabled() -> bool
public ResultCode IsStandardUserSystemClockAutomaticCorrectionEnabled(ServiceCtx context)
{
return _inner.IsStandardUserSystemClockAutomaticCorrectionEnabled(context);
}
[Command(101)]
// SetStandardUserSystemClockAutomaticCorrectionEnabled(b8)
public ResultCode SetStandardUserSystemClockAutomaticCorrectionEnabled(ServiceCtx context)
{
return _inner.SetStandardUserSystemClockAutomaticCorrectionEnabled(context);
}
[Command(102)] // 5.0.0+
// GetStandardUserSystemClockInitialYear() -> u32
public ResultCode GetStandardUserSystemClockInitialYear(ServiceCtx context)
{
if (!NxSettings.Settings.TryGetValue("time!standard_user_clock_initial_year", out object standardUserSystemClockInitialYear))
{
throw new InvalidOperationException("standard_user_clock_initial_year isn't defined in system settings!");
}
context.ResponseData.Write((int)standardUserSystemClockInitialYear);
return ResultCode.Success;
}
[Command(200)] // 3.0.0+
// IsStandardNetworkSystemClockAccuracySufficient() -> bool
public ResultCode IsStandardNetworkSystemClockAccuracySufficient(ServiceCtx context)
{
return _inner.IsStandardNetworkSystemClockAccuracySufficient(context);
}
[Command(201)] // 6.0.0+
// GetStandardUserSystemClockAutomaticCorrectionUpdatedTime() -> nn::time::SteadyClockTimePoint
public ResultCode GetStandardUserSystemClockAutomaticCorrectionUpdatedTime(ServiceCtx context)
{
return _inner.GetStandardUserSystemClockAutomaticCorrectionUpdatedTime(context);
}
[Command(300)] // 4.0.0+
// CalculateMonotonicSystemClockBaseTimePoint(nn::time::SystemClockContext) -> s64
public ResultCode CalculateMonotonicSystemClockBaseTimePoint(ServiceCtx context)
{
return _inner.CalculateMonotonicSystemClockBaseTimePoint(context);
}
[Command(400)] // 4.0.0+
// GetClockSnapshot(u8) -> buffer<nn::time::sf::ClockSnapshot, 0x1a>
public ResultCode GetClockSnapshot(ServiceCtx context)
{
return _inner.GetClockSnapshot(context);
}
[Command(401)] // 4.0.0+
// GetClockSnapshotFromSystemClockContext(u8, nn::time::SystemClockContext, nn::time::SystemClockContext) -> buffer<nn::time::sf::ClockSnapshot, 0x1a>
public ResultCode GetClockSnapshotFromSystemClockContext(ServiceCtx context)
{
return _inner.GetClockSnapshotFromSystemClockContext(context);
}
[Command(500)] // 4.0.0+
// CalculateStandardUserSystemClockDifferenceByUser(buffer<nn::time::sf::ClockSnapshot, 0x19>, buffer<nn::time::sf::ClockSnapshot, 0x19>) -> nn::TimeSpanType
public ResultCode CalculateStandardUserSystemClockDifferenceByUser(ServiceCtx context)
{
return _inner.CalculateStandardUserSystemClockDifferenceByUser(context);
}
[Command(501)] // 4.0.0+
// CalculateSpanBetween(buffer<nn::time::sf::ClockSnapshot, 0x19>, buffer<nn::time::sf::ClockSnapshot, 0x19>) -> nn::TimeSpanType
public ResultCode CalculateSpanBetween(ServiceCtx context)
{
return _inner.CalculateSpanBetween(context);
}
}
}

View File

@ -12,28 +12,30 @@ using System.Runtime.InteropServices;
namespace Ryujinx.HLE.HOS.Services.Time namespace Ryujinx.HLE.HOS.Services.Time
{ {
[Service("time:a", TimePermissions.Applet)]
[Service("time:s", TimePermissions.System)] [Service("time:s", TimePermissions.System)]
[Service("time:u", TimePermissions.User)] [Service("time:su", TimePermissions.SystemUpdate)]
[Service("time:p", TimePermissions.System)] // 9.0.0+ - TODO: Fix the permission. class IStaticServiceForPsc : IpcService
class IStaticService : IpcService
{ {
private TimeManager _timeManager;
private TimePermissions _permissions; private TimePermissions _permissions;
private int _timeSharedMemoryNativeHandle = 0; 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; _permissions = permissions;
_timeManager = manager;
} }
[Command(0)] [Command(0)]
// GetStandardUserSystemClock() -> object<nn::timesrv::detail::service::ISystemClock> // GetStandardUserSystemClock() -> object<nn::timesrv::detail::service::ISystemClock>
public ResultCode GetStandardUserSystemClock(ServiceCtx context) 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; return ResultCode.Success;
} }
@ -42,7 +44,9 @@ namespace Ryujinx.HLE.HOS.Services.Time
// GetStandardNetworkSystemClock() -> object<nn::timesrv::detail::service::ISystemClock> // GetStandardNetworkSystemClock() -> object<nn::timesrv::detail::service::ISystemClock>
public ResultCode GetStandardNetworkSystemClock(ServiceCtx context) 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; return ResultCode.Success;
} }
@ -51,7 +55,9 @@ namespace Ryujinx.HLE.HOS.Services.Time
// GetStandardSteadyClock() -> object<nn::timesrv::detail::service::ISteadyClock> // GetStandardSteadyClock() -> object<nn::timesrv::detail::service::ISteadyClock>
public ResultCode GetStandardSteadyClock(ServiceCtx context) 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; return ResultCode.Success;
} }
@ -60,7 +66,8 @@ namespace Ryujinx.HLE.HOS.Services.Time
// GetTimeZoneService() -> object<nn::timesrv::detail::service::ITimeZoneService> // GetTimeZoneService() -> object<nn::timesrv::detail::service::ITimeZoneService>
public ResultCode GetTimeZoneService(ServiceCtx context) public ResultCode GetTimeZoneService(ServiceCtx context)
{ {
MakeObject(context, new ITimeZoneService()); MakeObject(context, new ITimeZoneServiceForPsc(_timeManager.TimeZone.Manager,
(_permissions & TimePermissions.TimeZoneWritableMask) != 0));
return ResultCode.Success; return ResultCode.Success;
} }
@ -69,7 +76,9 @@ namespace Ryujinx.HLE.HOS.Services.Time
// GetStandardLocalSystemClock() -> object<nn::timesrv::detail::service::ISystemClock> // GetStandardLocalSystemClock() -> object<nn::timesrv::detail::service::ISystemClock>
public ResultCode GetStandardLocalSystemClock(ServiceCtx context) 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; return ResultCode.Success;
} }
@ -78,7 +87,9 @@ namespace Ryujinx.HLE.HOS.Services.Time
// GetEphemeralNetworkSystemClock() -> object<nn::timesrv::detail::service::ISystemClock> // GetEphemeralNetworkSystemClock() -> object<nn::timesrv::detail::service::ISystemClock>
public ResultCode GetEphemeralNetworkSystemClock(ServiceCtx context) 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; return ResultCode.Success;
} }
@ -89,7 +100,7 @@ namespace Ryujinx.HLE.HOS.Services.Time
{ {
if (_timeSharedMemoryNativeHandle == 0) 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!"); throw new InvalidOperationException("Out of handles!");
} }
@ -100,11 +111,34 @@ namespace Ryujinx.HLE.HOS.Services.Time
return ResultCode.Success; 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)] [Command(100)]
// IsStandardUserSystemClockAutomaticCorrectionEnabled() -> bool // IsStandardUserSystemClockAutomaticCorrectionEnabled() -> bool
public ResultCode IsStandardUserSystemClockAutomaticCorrectionEnabled(ServiceCtx context) 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; return ResultCode.Success;
} }
@ -113,6 +147,14 @@ namespace Ryujinx.HLE.HOS.Services.Time
// SetStandardUserSystemClockAutomaticCorrectionEnabled(b8) // SetStandardUserSystemClockAutomaticCorrectionEnabled(b8)
public ResultCode SetStandardUserSystemClockAutomaticCorrectionEnabled(ServiceCtx context) 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) if ((_permissions & TimePermissions.UserSystemClockWritableMask) == 0)
{ {
return ResultCode.PermissionDenied; return ResultCode.PermissionDenied;
@ -120,14 +162,50 @@ namespace Ryujinx.HLE.HOS.Services.Time
bool autoCorrectionEnabled = context.RequestData.ReadBoolean(); 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+ [Command(200)] // 3.0.0+
// IsStandardNetworkSystemClockAccuracySufficient() -> bool // IsStandardNetworkSystemClockAccuracySufficient() -> bool
public ResultCode IsStandardNetworkSystemClockAccuracySufficient(ServiceCtx context) 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; return ResultCode.Success;
} }
@ -136,8 +214,15 @@ namespace Ryujinx.HLE.HOS.Services.Time
// CalculateMonotonicSystemClockBaseTimePoint(nn::time::SystemClockContext) -> s64 // CalculateMonotonicSystemClockBaseTimePoint(nn::time::SystemClockContext) -> s64
public ResultCode CalculateMonotonicSystemClockBaseTimePoint(ServiceCtx context) public ResultCode CalculateMonotonicSystemClockBaseTimePoint(ServiceCtx context)
{ {
SteadyClockCore steadyClock = _timeManager.StandardSteadyClock;
if (!steadyClock.IsInitialized())
{
return ResultCode.UninitializedClock;
}
SystemClockContext otherContext = context.RequestData.ReadStruct<SystemClockContext>(); SystemClockContext otherContext = context.RequestData.ReadStruct<SystemClockContext>();
SteadyClockTimePoint currentTimePoint = StandardSteadyClockCore.Instance.GetCurrentTimePoint(context.Thread); SteadyClockTimePoint currentTimePoint = steadyClock.GetCurrentTimePoint(context.Thread);
ResultCode result = ResultCode.TimeMismatch; ResultCode result = ResultCode.TimeMismatch;
@ -148,7 +233,7 @@ namespace Ryujinx.HLE.HOS.Services.Time
context.ResponseData.Write(baseTimePoint); context.ResponseData.Write(baseTimePoint);
result = 0; result = ResultCode.Success;
} }
return result; return result;
@ -160,11 +245,11 @@ namespace Ryujinx.HLE.HOS.Services.Time
{ {
byte type = context.RequestData.ReadByte(); 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) 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) if (result == ResultCode.Success)
{ {
@ -205,7 +290,6 @@ namespace Ryujinx.HLE.HOS.Services.Time
// CalculateStandardUserSystemClockDifferenceByUser(buffer<nn::time::sf::ClockSnapshot, 0x19>, buffer<nn::time::sf::ClockSnapshot, 0x19>) -> nn::TimeSpanType // CalculateStandardUserSystemClockDifferenceByUser(buffer<nn::time::sf::ClockSnapshot, 0x19>, buffer<nn::time::sf::ClockSnapshot, 0x19>) -> nn::TimeSpanType
public ResultCode CalculateStandardUserSystemClockDifferenceByUser(ServiceCtx context) public ResultCode CalculateStandardUserSystemClockDifferenceByUser(ServiceCtx context)
{ {
ClockSnapshot clockSnapshotA = ReadClockSnapshotFromBuffer(context, context.Request.ExchangeBuff[0]); ClockSnapshot clockSnapshotA = ReadClockSnapshotFromBuffer(context, context.Request.ExchangeBuff[0]);
ClockSnapshot clockSnapshotB = ReadClockSnapshotFromBuffer(context, context.Request.ExchangeBuff[1]); ClockSnapshot clockSnapshotB = ReadClockSnapshotFromBuffer(context, context.Request.ExchangeBuff[1]);
TimeSpanType difference = TimeSpanType.FromSeconds(clockSnapshotB.UserContext.Offset - clockSnapshotA.UserContext.Offset); TimeSpanType difference = TimeSpanType.FromSeconds(clockSnapshotB.UserContext.Offset - clockSnapshotA.UserContext.Offset);
@ -259,25 +343,32 @@ namespace Ryujinx.HLE.HOS.Services.Time
{ {
clockSnapshot = new ClockSnapshot(); clockSnapshot = new ClockSnapshot();
SteadyClockCore steadyClockCore = StandardSteadyClockCore.Instance; SteadyClockCore steadyClockCore = _timeManager.StandardSteadyClock;
SteadyClockTimePoint currentTimePoint = steadyClockCore.GetCurrentTimePoint(thread); SteadyClockTimePoint currentTimePoint = steadyClockCore.GetCurrentTimePoint(thread);
clockSnapshot.IsAutomaticCorrectionEnabled = StandardUserSystemClockCore.Instance.IsAutomaticCorrectionEnabled(); clockSnapshot.IsAutomaticCorrectionEnabled = _timeManager.StandardUserSystemClock.IsAutomaticCorrectionEnabled();
clockSnapshot.UserContext = userContext; clockSnapshot.UserContext = userContext;
clockSnapshot.NetworkContext = networkContext; 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]; char[] locationName = new char[0x24];
Array.Copy(tzName, locationName, tzName.Length); Array.Copy(tzName, locationName, tzName.Length);
clockSnapshot.LocationName = locationName; 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) 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) if (result == ResultCode.Success)
{ {
@ -289,7 +380,7 @@ namespace Ryujinx.HLE.HOS.Services.Time
clockSnapshot.NetworkTime = 0; 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) if (result == ResultCode.Success)
{ {

View File

@ -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 class ITimeServiceManager : IpcService
{ {
public ITimeServiceManager(ServiceCtx context) { } private TimeManager _timeManager;
private int _automaticCorrectionEvent;
public ITimeServiceManager(ServiceCtx context)
{
_timeManager = TimeManager.Instance;
_automaticCorrectionEvent = 0;
}
[Command(0)]
// GetUserStaticService() -> object<nn::timesrv::detail::service::IStaticService>
public ResultCode GetUserStaticService(ServiceCtx context)
{
MakeObject(context, new IStaticServiceForPsc(_timeManager, TimePermissions.User));
return ResultCode.Success;
}
[Command(5)]
// GetAdminStaticService() -> object<nn::timesrv::detail::service::IStaticService>
public ResultCode GetAdminStaticService(ServiceCtx context)
{
MakeObject(context, new IStaticServiceForPsc(_timeManager, TimePermissions.Admin));
return ResultCode.Success;
}
[Command(6)]
// GetRepairStaticService() -> object<nn::timesrv::detail::service::IStaticService>
public ResultCode GetRepairStaticService(ServiceCtx context)
{
MakeObject(context, new IStaticServiceForPsc(_timeManager, TimePermissions.Repair));
return ResultCode.Success;
}
[Command(9)]
// GetManufactureStaticService() -> object<nn::timesrv::detail::service::IStaticService>
public ResultCode GetManufactureStaticService(ServiceCtx context)
{
MakeObject(context, new IStaticServiceForPsc(_timeManager, TimePermissions.Manufacture));
return ResultCode.Success;
}
[Command(10)]
// SetupStandardSteadyClock(nn::util::Uuid clock_source_id, nn::TimeSpanType setup_value, nn::TimeSpanType internal_offset, nn::TimeSpanType test_offset, bool is_rtc_reset_detected)
public ResultCode SetupStandardSteadyClock(ServiceCtx context)
{
UInt128 clockSourceId = context.RequestData.ReadStruct<UInt128>();
TimeSpanType setupValue = context.RequestData.ReadStruct<TimeSpanType>();
TimeSpanType internalOffset = context.RequestData.ReadStruct<TimeSpanType>();
TimeSpanType testOffset = context.RequestData.ReadStruct<TimeSpanType>();
bool isRtcResetDetected = context.RequestData.ReadBoolean();
_timeManager.SetupStandardSteadyClock(context.Thread, clockSourceId, setupValue, internalOffset, testOffset, isRtcResetDetected);
return ResultCode.Success;
}
[Command(11)]
// SetupStandardLocalSystemClock(nn::time::SystemClockContext context, nn::time::PosixTime posix_time)
public ResultCode SetupStandardLocalSystemClock(ServiceCtx context)
{
SystemClockContext clockContext = context.RequestData.ReadStruct<SystemClockContext>();
long posixTime = context.RequestData.ReadInt64();
_timeManager.SetupStandardLocalSystemClock(context.Thread, clockContext, posixTime);
return ResultCode.Success;
}
[Command(12)]
// SetupStandardNetworkSystemClock(nn::time::SystemClockContext context, nn::TimeSpanType sufficient_accuracy)
public ResultCode SetupStandardNetworkSystemClock(ServiceCtx context)
{
SystemClockContext clockContext = context.RequestData.ReadStruct<SystemClockContext>();
TimeSpanType sufficientAccuracy = context.RequestData.ReadStruct<TimeSpanType>();
_timeManager.SetupStandardNetworkSystemClock(clockContext, sufficientAccuracy);
return ResultCode.Success;
}
[Command(13)]
// SetupStandardUserSystemClock(bool automatic_correction_enabled, nn::time::SteadyClockTimePoint steady_clock_timepoint)
public ResultCode SetupStandardUserSystemClock(ServiceCtx context)
{
bool isAutomaticCorrectionEnabled = context.RequestData.ReadBoolean();
context.RequestData.BaseStream.Position += 7;
SteadyClockTimePoint steadyClockTimePoint = context.RequestData.ReadStruct<SteadyClockTimePoint>();
_timeManager.SetupStandardUserSystemClock(context.Thread, isAutomaticCorrectionEnabled, steadyClockTimePoint);
return ResultCode.Success;
}
[Command(14)]
// SetupTimeZoneManager(nn::time::LocationName location_name, nn::time::SteadyClockTimePoint timezone_update_timepoint, u32 total_location_name_count, nn::time::TimeZoneRuleVersion timezone_rule_version, buffer<nn::time::TimeZoneBinary, 0x21> timezone_binary)
public ResultCode SetupTimeZoneManager(ServiceCtx context)
{
string locationName = Encoding.ASCII.GetString(context.RequestData.ReadBytes(0x24)).TrimEnd('\0');
SteadyClockTimePoint timeZoneUpdateTimePoint = context.RequestData.ReadStruct<SteadyClockTimePoint>();
uint totalLocationNameCount = context.RequestData.ReadUInt32();
UInt128 timeZoneRuleVersion = context.RequestData.ReadStruct<UInt128>();
(long bufferPosition, long bufferSize) = context.Request.GetBufferType0x21();
using (MemoryStream timeZoneBinaryStream = new MemoryStream(context.Memory.ReadBytes(bufferPosition, bufferSize)))
{
_timeManager.SetupTimeZoneManager(locationName, timeZoneUpdateTimePoint, totalLocationNameCount, timeZoneRuleVersion, timeZoneBinaryStream);
}
return ResultCode.Success;
}
[Command(15)]
// SetupEphemeralNetworkSystemClock()
public ResultCode SetupEphemeralNetworkSystemClock(ServiceCtx context)
{
_timeManager.SetupEphemeralNetworkSystemClock();
return ResultCode.Success;
}
[Command(50)]
// Unknown50() -> handle<copy>
public ResultCode Unknown50(ServiceCtx context)
{
// TODO: figure out the usage of this event
throw new ServiceNotImplementedException(context);
}
[Command(51)]
// Unknown51() -> handle<copy>
public ResultCode Unknown51(ServiceCtx context)
{
// TODO: figure out the usage of this event
throw new ServiceNotImplementedException(context);
}
[Command(52)]
// Unknown52() -> handle<copy>
public ResultCode Unknown52(ServiceCtx context)
{
// TODO: figure out the usage of this event
throw new ServiceNotImplementedException(context);
}
[Command(60)]
// GetStandardUserSystemClockAutomaticCorrectionEvent() -> handle<copy>
public ResultCode GetStandardUserSystemClockAutomaticCorrectionEvent(ServiceCtx context)
{
if (_automaticCorrectionEvent == 0)
{
if (context.Process.HandleTable.GenerateHandle(_timeManager.StandardUserSystemClock.GetAutomaticCorrectionReadableEvent(), out _automaticCorrectionEvent) != KernelResult.Success)
{
throw new InvalidOperationException("Out of handles!");
}
}
context.Response.HandleDesc = IpcHandleDesc.MakeCopy(_automaticCorrectionEvent);
return ResultCode.Success;
}
[Command(100)]
// SetStandardSteadyClockRtcOffset(nn::TimeSpanType rtc_offset)
public ResultCode SetStandardSteadyClockRtcOffset(ServiceCtx context)
{
TimeSpanType rtcOffset = context.RequestData.ReadStruct<TimeSpanType>();
_timeManager.SetStandardSteadyClockRtcOffset(context.Thread, rtcOffset);
return ResultCode.Success;
}
[Command(200)]
// GetAlarmRegistrationEvent() -> handle<copy>
public ResultCode GetAlarmRegistrationEvent(ServiceCtx context)
{
// TODO
throw new ServiceNotImplementedException(context);
}
[Command(201)]
// UpdateSteadyAlarms()
public ResultCode UpdateSteadyAlarms(ServiceCtx context)
{
// TODO
throw new ServiceNotImplementedException(context);
}
[Command(202)]
// TryGetNextSteadyClockAlarmSnapshot() -> (bool, nn::time::SteadyClockAlarmSnapshot)
public ResultCode TryGetNextSteadyClockAlarmSnapshot(ServiceCtx context)
{
// TODO
throw new ServiceNotImplementedException(context);
}
} }
} }

View File

@ -9,6 +9,7 @@
PermissionDenied = (1 << ErrorCodeShift) | ModuleId, PermissionDenied = (1 << ErrorCodeShift) | ModuleId,
TimeMismatch = (102 << ErrorCodeShift) | ModuleId, TimeMismatch = (102 << ErrorCodeShift) | ModuleId,
UninitializedClock = (103 << ErrorCodeShift) | ModuleId,
TimeNotFound = (200 << ErrorCodeShift) | ModuleId, TimeNotFound = (200 << ErrorCodeShift) | ModuleId,
Overflow = (201 << ErrorCodeShift) | ModuleId, Overflow = (201 << ErrorCodeShift) | ModuleId,
LocationNameTooLong = (801 << ErrorCodeShift) | ModuleId, LocationNameTooLong = (801 << ErrorCodeShift) | ModuleId,

View File

@ -5,42 +5,78 @@ namespace Ryujinx.HLE.HOS.Services.Time.StaticService
{ {
class ISteadyClock : IpcService 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)] [Command(0)]
// GetCurrentTimePoint() -> nn::time::SteadyClockTimePoint // GetCurrentTimePoint() -> nn::time::SteadyClockTimePoint
public ResultCode GetCurrentTimePoint(ServiceCtx context) 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); context.ResponseData.WriteStruct(currentTimePoint);
return ResultCode.Success; return ResultCode.Success;
} }
[Command(1)] [Command(2)]
// GetTestOffset() -> nn::TimeSpanType // GetTestOffset() -> nn::TimeSpanType
public ResultCode GetTestOffset(ServiceCtx context) 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; return ResultCode.Success;
} }
[Command(2)] [Command(3)]
// SetTestOffset(nn::TimeSpanType) // SetTestOffset(nn::TimeSpanType)
public ResultCode SetTestOffset(ServiceCtx context) public ResultCode SetTestOffset(ServiceCtx context)
{ {
if (!_writePermission)
{
return ResultCode.PermissionDenied;
}
if (!_bypassUninitializedClock && !_steadyClock.IsInitialized())
{
return ResultCode.UninitializedClock;
}
TimeSpanType testOffset = context.RequestData.ReadStruct<TimeSpanType>(); TimeSpanType testOffset = context.RequestData.ReadStruct<TimeSpanType>();
StandardSteadyClockCore.Instance.SetTestOffset(testOffset); _steadyClock.SetTestOffset(testOffset);
return 0; return ResultCode.Success;
} }
[Command(100)] // 2.0.0+ [Command(100)] // 2.0.0+
// GetRtcValue() -> u64 // GetRtcValue() -> u64
public ResultCode GetRtcValue(ServiceCtx context) 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) if (result == ResultCode.Success)
{ {
@ -54,7 +90,12 @@ namespace Ryujinx.HLE.HOS.Services.Time.StaticService
// IsRtcResetDetected() -> bool // IsRtcResetDetected() -> bool
public ResultCode IsRtcResetDetected(ServiceCtx context) 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; return ResultCode.Success;
} }
@ -63,7 +104,12 @@ namespace Ryujinx.HLE.HOS.Services.Time.StaticService
// GetSetupResultValue() -> u32 // GetSetupResultValue() -> u32
public ResultCode GetSetupResultValue(ServiceCtx context) 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; return ResultCode.Success;
} }
@ -72,7 +118,12 @@ namespace Ryujinx.HLE.HOS.Services.Time.StaticService
// GetInternalOffset() -> nn::TimeSpanType // GetInternalOffset() -> nn::TimeSpanType
public ResultCode GetInternalOffset(ServiceCtx context) 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; return ResultCode.Success;
} }
@ -81,9 +132,19 @@ namespace Ryujinx.HLE.HOS.Services.Time.StaticService
// SetInternalOffset(nn::TimeSpanType) // SetInternalOffset(nn::TimeSpanType)
public ResultCode SetInternalOffset(ServiceCtx context) public ResultCode SetInternalOffset(ServiceCtx context)
{ {
if (!_writePermission)
{
return ResultCode.PermissionDenied;
}
if (!_bypassUninitializedClock && !_steadyClock.IsInitialized())
{
return ResultCode.UninitializedClock;
}
TimeSpanType internalOffset = context.RequestData.ReadStruct<TimeSpanType>(); TimeSpanType internalOffset = context.RequestData.ReadStruct<TimeSpanType>();
StandardSteadyClockCore.Instance.SetInternalOffset(internalOffset); _steadyClock.SetInternalOffset(internalOffset);
return ResultCode.Success; return ResultCode.Success;
} }

View File

@ -1,5 +1,9 @@
using Ryujinx.Common; 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 Ryujinx.HLE.HOS.Services.Time.Clock;
using System;
namespace Ryujinx.HLE.HOS.Services.Time.StaticService namespace Ryujinx.HLE.HOS.Services.Time.StaticService
{ {
@ -7,34 +11,31 @@ namespace Ryujinx.HLE.HOS.Services.Time.StaticService
{ {
private SystemClockCore _clockCore; private SystemClockCore _clockCore;
private bool _writePermission; 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; _clockCore = clockCore;
_writePermission = writePermission; _writePermission = writePermission;
_bypassUninitializedClock = bypassUninitializedClock;
_operationEventReadableHandle = 0;
} }
[Command(0)] [Command(0)]
// GetCurrentTime() -> nn::time::PosixTime // GetCurrentTime() -> nn::time::PosixTime
public ResultCode GetCurrentTime(ServiceCtx context) public ResultCode GetCurrentTime(ServiceCtx context)
{ {
SteadyClockCore steadyClockCore = _clockCore.GetSteadyClockCore(); if (!_bypassUninitializedClock && !_clockCore.IsInitialized())
SteadyClockTimePoint currentTimePoint = steadyClockCore.GetCurrentTimePoint(context.Thread); {
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) if (result == ResultCode.Success)
{ {
result = ResultCode.TimeMismatch; context.ResponseData.Write(posixTime);
if (currentTimePoint.ClockSourceId == clockContext.SteadyTimePoint.ClockSourceId)
{
long posixTime = clockContext.Offset + currentTimePoint.TimePoint;
context.ResponseData.Write(posixTime);
result = 0;
}
} }
return result; return result;
@ -49,31 +50,26 @@ namespace Ryujinx.HLE.HOS.Services.Time.StaticService
return ResultCode.PermissionDenied; return ResultCode.PermissionDenied;
} }
long posixTime = context.RequestData.ReadInt64(); if (!_bypassUninitializedClock && !_clockCore.IsInitialized())
SteadyClockCore steadyClockCore = _clockCore.GetSteadyClockCore();
SteadyClockTimePoint currentTimePoint = steadyClockCore.GetCurrentTimePoint(context.Thread);
SystemClockContext clockContext = new SystemClockContext()
{ {
Offset = posixTime - currentTimePoint.TimePoint, return ResultCode.UninitializedClock;
SteadyTimePoint = currentTimePoint
};
ResultCode result = _clockCore.SetSystemClockContext(clockContext);
if (result == ResultCode.Success)
{
result = _clockCore.Flush(clockContext);
} }
return result; long posixTime = context.RequestData.ReadInt64();
return _clockCore.SetCurrentTime(context.Thread, posixTime);
} }
[Command(2)] [Command(2)]
// GetSystemClockContext() -> nn::time::SystemClockContext // GetClockContext() -> nn::time::SystemClockContext
public ResultCode GetSystemClockContext(ServiceCtx context) 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) if (result == ResultCode.Success)
{ {
@ -84,7 +80,7 @@ namespace Ryujinx.HLE.HOS.Services.Time.StaticService
} }
[Command(3)] [Command(3)]
// SetSystemClockContext(nn::time::SystemClockContext) // SetClockContext(nn::time::SystemClockContext)
public ResultCode SetSystemClockContext(ServiceCtx context) public ResultCode SetSystemClockContext(ServiceCtx context)
{ {
if (!_writePermission) if (!_writePermission)
@ -92,16 +88,37 @@ namespace Ryujinx.HLE.HOS.Services.Time.StaticService
return ResultCode.PermissionDenied; return ResultCode.PermissionDenied;
} }
if (!_bypassUninitializedClock && !_clockCore.IsInitialized())
{
return ResultCode.UninitializedClock;
}
SystemClockContext clockContext = context.RequestData.ReadStruct<SystemClockContext>(); SystemClockContext clockContext = context.RequestData.ReadStruct<SystemClockContext>();
ResultCode result = _clockCore.SetSystemClockContext(clockContext); ResultCode result = _clockCore.SetSystemClockContext(clockContext);
if (result == ResultCode.Success) return result;
}
[Command(4)] // 9.0.0+
// GetOperationEventReadableHandle() -> handle<copy>
public ResultCode GetOperationEventReadableHandle(ServiceCtx context)
{
if (_operationEventReadableHandle == 0)
{ {
result = _clockCore.Flush(clockContext); KEvent kEvent = new KEvent(context.Device.System);
_clockCore.RegisterOperationEvent(kEvent.WritableEvent);
if (context.Process.HandleTable.GenerateHandle(kEvent.ReadableEvent, out _operationEventReadableHandle) != KernelResult.Success)
{
throw new InvalidOperationException("Out of handles!");
}
} }
return result; context.Response.HandleDesc = IpcHandleDesc.MakeCopy(_operationEventReadableHandle);
return ResultCode.Success;
} }
} }
} }

View File

@ -1,218 +0,0 @@
using ARMeilleure.Memory;
using Ryujinx.Common;
using Ryujinx.Common.Logging;
using Ryujinx.HLE.HOS.Services.Time.TimeZone;
using System;
using System.Text;
namespace Ryujinx.HLE.HOS.Services.Time.StaticService
{
class ITimeZoneService : IpcService
{
public ITimeZoneService() { }
[Command(0)]
// GetDeviceLocationName() -> nn::time::LocationName
public ResultCode GetDeviceLocationName(ServiceCtx context)
{
char[] tzName = TimeZoneManager.Instance.GetDeviceLocationName().ToCharArray();
int padding = 0x24 - tzName.Length;
if (padding < 0)
{
return ResultCode.LocationNameTooLong;
}
context.ResponseData.Write(tzName);
for (int index = 0; index < padding; index++)
{
context.ResponseData.Write((byte)0);
}
return ResultCode.Success;
}
[Command(1)]
// SetDeviceLocationName(nn::time::LocationName)
public ResultCode SetDeviceLocationName(ServiceCtx context)
{
string locationName = Encoding.ASCII.GetString(context.RequestData.ReadBytes(0x24)).TrimEnd('\0');
return TimeZoneManager.Instance.SetDeviceLocationName(locationName);
}
[Command(2)]
// GetTotalLocationNameCount() -> u32
public ResultCode GetTotalLocationNameCount(ServiceCtx context)
{
context.ResponseData.Write(TimeZoneManager.Instance.GetTotalLocationNameCount());
return ResultCode.Success;
}
[Command(3)]
// LoadLocationNameList(u32 index) -> (u32 outCount, buffer<nn::time::LocationName, 6>)
public ResultCode LoadLocationNameList(ServiceCtx context)
{
uint index = context.RequestData.ReadUInt32();
long bufferPosition = context.Request.ReceiveBuff[0].Position;
long bufferSize = context.Request.ReceiveBuff[0].Size;
ResultCode errorCode = TimeZoneManager.Instance.LoadLocationNameList(index, out string[] locationNameArray, (uint)bufferSize / 0x24);
if (errorCode == 0)
{
uint offset = 0;
foreach (string locationName in locationNameArray)
{
int padding = 0x24 - locationName.Length;
if (padding < 0)
{
return ResultCode.LocationNameTooLong;
}
context.Memory.WriteBytes(bufferPosition + offset, Encoding.ASCII.GetBytes(locationName));
MemoryHelper.FillWithZeros(context.Memory, bufferPosition + offset + locationName.Length, padding);
offset += 0x24;
}
context.ResponseData.Write((uint)locationNameArray.Length);
}
return errorCode;
}
[Command(4)]
// LoadTimeZoneRule(nn::time::LocationName locationName) -> buffer<nn::time::TimeZoneRule, 0x16>
public ResultCode LoadTimeZoneRule(ServiceCtx context)
{
long bufferPosition = context.Request.ReceiveBuff[0].Position;
long bufferSize = context.Request.ReceiveBuff[0].Size;
if (bufferSize != 0x4000)
{
// TODO: find error code here
Logger.PrintError(LogClass.ServiceTime, $"TimeZoneRule buffer size is 0x{bufferSize:x} (expected 0x4000)");
throw new InvalidOperationException();
}
string locationName = Encoding.ASCII.GetString(context.RequestData.ReadBytes(0x24)).TrimEnd('\0');
ResultCode resultCode = TimeZoneManager.Instance.LoadTimeZoneRules(out TimeZoneRule rules, locationName);
// Write TimeZoneRule if success
if (resultCode == 0)
{
MemoryHelper.Write(context.Memory, bufferPosition, rules);
}
return resultCode;
}
[Command(100)]
// ToCalendarTime(nn::time::PosixTime time, buffer<nn::time::TimeZoneRule, 0x15> rules) -> (nn::time::CalendarTime, nn::time::sf::CalendarAdditionalInfo)
public ResultCode ToCalendarTime(ServiceCtx context)
{
long posixTime = context.RequestData.ReadInt64();
long bufferPosition = context.Request.SendBuff[0].Position;
long bufferSize = context.Request.SendBuff[0].Size;
if (bufferSize != 0x4000)
{
// TODO: find error code here
Logger.PrintError(LogClass.ServiceTime, $"TimeZoneRule buffer size is 0x{bufferSize:x} (expected 0x4000)");
throw new InvalidOperationException();
}
TimeZoneRule rules = MemoryHelper.Read<TimeZoneRule>(context.Memory, bufferPosition);
ResultCode resultCode = TimeZoneManager.ToCalendarTime(rules, posixTime, out CalendarInfo calendar);
if (resultCode == 0)
{
context.ResponseData.WriteStruct(calendar);
}
return resultCode;
}
[Command(101)]
// ToCalendarTimeWithMyRule(nn::time::PosixTime) -> (nn::time::CalendarTime, nn::time::sf::CalendarAdditionalInfo)
public ResultCode ToCalendarTimeWithMyRule(ServiceCtx context)
{
long posixTime = context.RequestData.ReadInt64();
ResultCode resultCode = TimeZoneManager.Instance.ToCalendarTimeWithMyRules(posixTime, out CalendarInfo calendar);
if (resultCode == 0)
{
context.ResponseData.WriteStruct(calendar);
}
return resultCode;
}
[Command(201)]
// ToPosixTime(nn::time::CalendarTime calendarTime, buffer<nn::time::TimeZoneRule, 0x15> rules) -> (u32 outCount, buffer<nn::time::PosixTime, 0xa>)
public ResultCode ToPosixTime(ServiceCtx context)
{
long inBufferPosition = context.Request.SendBuff[0].Position;
long inBufferSize = context.Request.SendBuff[0].Size;
CalendarTime calendarTime = context.RequestData.ReadStruct<CalendarTime>();
if (inBufferSize != 0x4000)
{
// TODO: find error code here
Logger.PrintError(LogClass.ServiceTime, $"TimeZoneRule buffer size is 0x{inBufferSize:x} (expected 0x4000)");
throw new InvalidOperationException();
}
TimeZoneRule rules = MemoryHelper.Read<TimeZoneRule>(context.Memory, inBufferPosition);
ResultCode resultCode = TimeZoneManager.ToPosixTime(rules, calendarTime, out long posixTime);
if (resultCode == 0)
{
long outBufferPosition = context.Request.RecvListBuff[0].Position;
long outBufferSize = context.Request.RecvListBuff[0].Size;
context.Memory.WriteInt64(outBufferPosition, posixTime);
context.ResponseData.Write(1);
}
return resultCode;
}
[Command(202)]
// ToPosixTimeWithMyRule(nn::time::CalendarTime calendarTime) -> (u32 outCount, buffer<nn::time::PosixTime, 0xa>)
public ResultCode ToPosixTimeWithMyRule(ServiceCtx context)
{
CalendarTime calendarTime = context.RequestData.ReadStruct<CalendarTime>();
ResultCode resultCode = TimeZoneManager.Instance.ToPosixTimeWithMyRules(calendarTime, out long posixTime);
if (resultCode == 0)
{
long outBufferPosition = context.Request.RecvListBuff[0].Position;
long outBufferSize = context.Request.RecvListBuff[0].Size;
context.Memory.WriteInt64(outBufferPosition, posixTime);
// There could be only one result on one calendar as leap seconds aren't supported.
context.ResponseData.Write(1);
}
return resultCode;
}
}
}

View File

@ -0,0 +1,142 @@
using ARMeilleure.Memory;
using Ryujinx.Common;
using Ryujinx.Common.Logging;
using Ryujinx.HLE.HOS.Services.Time.TimeZone;
using System;
using System.Text;
namespace Ryujinx.HLE.HOS.Services.Time.StaticService
{
class ITimeZoneServiceForGlue : IpcService
{
private TimeZoneContentManager _timeZoneContentManager;
private ITimeZoneServiceForPsc _inner;
private bool _writePermission;
public ITimeZoneServiceForGlue(TimeZoneContentManager timeZoneContentManager, bool writePermission)
{
_timeZoneContentManager = timeZoneContentManager;
_writePermission = writePermission;
_inner = new ITimeZoneServiceForPsc(timeZoneContentManager.Manager, writePermission);
}
[Command(0)]
// GetDeviceLocationName() -> nn::time::LocationName
public ResultCode GetDeviceLocationName(ServiceCtx context)
{
return _inner.GetDeviceLocationName(context);
}
[Command(1)]
// SetDeviceLocationName(nn::time::LocationName)
public ResultCode SetDeviceLocationName(ServiceCtx context)
{
if (!_writePermission)
{
return ResultCode.PermissionDenied;
}
string locationName = Encoding.ASCII.GetString(context.RequestData.ReadBytes(0x24)).TrimEnd('\0');
return _timeZoneContentManager.SetDeviceLocationName(locationName);
}
[Command(2)]
// GetTotalLocationNameCount() -> u32
public ResultCode GetTotalLocationNameCount(ServiceCtx context)
{
return _inner.GetTotalLocationNameCount(context);
}
[Command(3)]
// LoadLocationNameList(u32 index) -> (u32 outCount, buffer<nn::time::LocationName, 6>)
public ResultCode LoadLocationNameList(ServiceCtx context)
{
uint index = context.RequestData.ReadUInt32();
long bufferPosition = context.Request.ReceiveBuff[0].Position;
long bufferSize = context.Request.ReceiveBuff[0].Size;
ResultCode errorCode = _timeZoneContentManager.LoadLocationNameList(index, out string[] locationNameArray, (uint)bufferSize / 0x24);
if (errorCode == 0)
{
uint offset = 0;
foreach (string locationName in locationNameArray)
{
int padding = 0x24 - locationName.Length;
if (padding < 0)
{
return ResultCode.LocationNameTooLong;
}
context.Memory.WriteBytes(bufferPosition + offset, Encoding.ASCII.GetBytes(locationName));
MemoryHelper.FillWithZeros(context.Memory, bufferPosition + offset + locationName.Length, padding);
offset += 0x24;
}
context.ResponseData.Write((uint)locationNameArray.Length);
}
return errorCode;
}
[Command(4)]
// LoadTimeZoneRule(nn::time::LocationName locationName) -> buffer<nn::time::TimeZoneRule, 0x16>
public ResultCode LoadTimeZoneRule(ServiceCtx context)
{
long bufferPosition = context.Request.ReceiveBuff[0].Position;
long bufferSize = context.Request.ReceiveBuff[0].Size;
if (bufferSize != 0x4000)
{
// TODO: find error code here
Logger.PrintError(LogClass.ServiceTime, $"TimeZoneRule buffer size is 0x{bufferSize:x} (expected 0x4000)");
throw new InvalidOperationException();
}
string locationName = Encoding.ASCII.GetString(context.RequestData.ReadBytes(0x24)).TrimEnd('\0');
ResultCode resultCode = _timeZoneContentManager.LoadTimeZoneRule(out TimeZoneRule rules, locationName);
// Write TimeZoneRule if success
if (resultCode == ResultCode.Success)
{
MemoryHelper.Write(context.Memory, bufferPosition, rules);
}
return resultCode;
}
[Command(100)]
// ToCalendarTime(nn::time::PosixTime time, buffer<nn::time::TimeZoneRule, 0x15> rules) -> (nn::time::CalendarTime, nn::time::sf::CalendarAdditionalInfo)
public ResultCode ToCalendarTime(ServiceCtx context)
{
return _inner.ToCalendarTime(context);
}
[Command(101)]
// ToCalendarTimeWithMyRule(nn::time::PosixTime) -> (nn::time::CalendarTime, nn::time::sf::CalendarAdditionalInfo)
public ResultCode ToCalendarTimeWithMyRule(ServiceCtx context)
{
return _inner.ToCalendarTimeWithMyRule(context);
}
[Command(201)]
// ToPosixTime(nn::time::CalendarTime calendarTime, buffer<nn::time::TimeZoneRule, 0x15> rules) -> (u32 outCount, buffer<nn::time::PosixTime, 0xa>)
public ResultCode ToPosixTime(ServiceCtx context)
{
return _inner.ToPosixTime(context);
}
[Command(202)]
// ToPosixTimeWithMyRule(nn::time::CalendarTime calendarTime) -> (u32 outCount, buffer<nn::time::PosixTime, 0xa>)
public ResultCode ToPosixTimeWithMyRule(ServiceCtx context)
{
return _inner.ToPosixTimeWithMyRule(context);
}
}
}

View File

@ -0,0 +1,294 @@
using ARMeilleure.Memory;
using Ryujinx.Common;
using Ryujinx.Common.Logging;
using Ryujinx.HLE.HOS.Services.Time.Clock;
using Ryujinx.HLE.HOS.Services.Time.TimeZone;
using Ryujinx.HLE.Utilities;
using System;
using System.Diagnostics;
using System.IO;
using System.Text;
namespace Ryujinx.HLE.HOS.Services.Time.StaticService
{
class ITimeZoneServiceForPsc : IpcService
{
private TimeZoneManager _timeZoneManager;
private bool _writePermission;
public ITimeZoneServiceForPsc(TimeZoneManager timeZoneManager, bool writePermission)
{
_timeZoneManager = timeZoneManager;
_writePermission = writePermission;
}
[Command(0)]
// GetDeviceLocationName() -> nn::time::LocationName
public ResultCode GetDeviceLocationName(ServiceCtx context)
{
ResultCode result = _timeZoneManager.GetDeviceLocationName(out string deviceLocationName);
if (result == ResultCode.Success)
{
WriteLocationName(context, deviceLocationName);
}
return result;
}
[Command(1)]
// SetDeviceLocationName(nn::time::LocationName)
public ResultCode SetDeviceLocationName(ServiceCtx context)
{
if (!_writePermission)
{
return ResultCode.PermissionDenied;
}
return ResultCode.NotImplemented;
}
[Command(2)]
// GetTotalLocationNameCount() -> u32
public ResultCode GetTotalLocationNameCount(ServiceCtx context)
{
ResultCode result = _timeZoneManager.GetTotalLocationNameCount(out uint totalLocationNameCount);
if (result == ResultCode.Success)
{
context.ResponseData.Write(totalLocationNameCount);
}
return ResultCode.Success;
}
[Command(3)]
// LoadLocationNameList(u32 index) -> (u32 outCount, buffer<nn::time::LocationName, 6>)
public ResultCode LoadLocationNameList(ServiceCtx context)
{
return ResultCode.NotImplemented;
}
[Command(4)]
// LoadTimeZoneRule(nn::time::LocationName locationName) -> buffer<nn::time::TimeZoneRule, 0x16>
public ResultCode LoadTimeZoneRule(ServiceCtx context)
{
return ResultCode.NotImplemented;
}
[Command(5)] // 2.0.0+
// GetTimeZoneRuleVersion() -> nn::time::TimeZoneRuleVersion
public ResultCode GetTimeZoneRuleVersion(ServiceCtx context)
{
ResultCode result = _timeZoneManager.GetTimeZoneRuleVersion(out UInt128 timeZoneRuleVersion);
if (result == ResultCode.Success)
{
context.ResponseData.WriteStruct(timeZoneRuleVersion);
}
return result;
}
[Command(6)] // 5.0.0+
// GetDeviceLocationNameAndUpdatedTime() -> (nn::time::LocationName, nn::time::SteadyClockTimePoint)
public ResultCode GetDeviceLocationNameAndUpdatedTime(ServiceCtx context)
{
ResultCode result = _timeZoneManager.GetDeviceLocationName(out string deviceLocationName);
if (result == ResultCode.Success)
{
result = _timeZoneManager.GetUpdatedTime(out SteadyClockTimePoint timeZoneUpdateTimePoint);
if (result == ResultCode.Success)
{
WriteLocationName(context, deviceLocationName);
// Skip padding
context.ResponseData.BaseStream.Position += 0x4;
context.ResponseData.WriteStruct(timeZoneUpdateTimePoint);
}
}
return result;
}
[Command(7)] // 9.0.0+
// SetDeviceLocationNameWithTimeZoneRule(nn::time::LocationName locationName, buffer<nn::time::TimeZoneBinary, 0x21> timeZoneBinary)
public ResultCode SetDeviceLocationNameWithTimeZoneRule(ServiceCtx context)
{
if (!_writePermission)
{
return ResultCode.PermissionDenied;
}
(long bufferPosition, long bufferSize) = context.Request.GetBufferType0x21();
string locationName = Encoding.ASCII.GetString(context.RequestData.ReadBytes(0x24)).TrimEnd('\0');
ResultCode result;
using (MemoryStream timeZoneBinaryStream = new MemoryStream(context.Memory.ReadBytes(bufferPosition, bufferSize)))
{
result = _timeZoneManager.SetDeviceLocationNameWithTimeZoneRule(locationName, timeZoneBinaryStream);
}
return result;
}
[Command(8)] // 9.0.0+
// ParseTimeZoneBinary(buffer<nn::time::TimeZoneBinary, 0x21> timeZoneBinary) -> buffer<nn::time::TimeZoneRule, 0x16>
public ResultCode ParseTimeZoneBinary(ServiceCtx context)
{
(long bufferPosition, long bufferSize) = context.Request.GetBufferType0x21();
long timeZoneRuleBufferPosition = context.Request.ReceiveBuff[0].Position;
long timeZoneRuleBufferSize = context.Request.ReceiveBuff[0].Size;
if (timeZoneRuleBufferSize != 0x4000)
{
// TODO: find error code here
Logger.PrintError(LogClass.ServiceTime, $"TimeZoneRule buffer size is 0x{timeZoneRuleBufferSize:x} (expected 0x4000)");
throw new InvalidOperationException();
}
ResultCode result;
using (MemoryStream timeZoneBinaryStream = new MemoryStream(context.Memory.ReadBytes(bufferPosition, bufferSize)))
{
result = _timeZoneManager.ParseTimeZoneRuleBinary(out TimeZoneRule timeZoneRule, timeZoneBinaryStream);
if (result == ResultCode.Success)
{
MemoryHelper.Write(context.Memory, timeZoneRuleBufferPosition, timeZoneRule);
}
}
return result;
}
[Command(20)] // 9.0.0+
// GetDeviceLocationNameOperationEventReadableHandle() -> handle<copy>
public ResultCode GetDeviceLocationNameOperationEventReadableHandle(ServiceCtx context)
{
return ResultCode.NotImplemented;
}
[Command(100)]
// ToCalendarTime(nn::time::PosixTime time, buffer<nn::time::TimeZoneRule, 0x15> rules) -> (nn::time::CalendarTime, nn::time::sf::CalendarAdditionalInfo)
public ResultCode ToCalendarTime(ServiceCtx context)
{
long posixTime = context.RequestData.ReadInt64();
long bufferPosition = context.Request.SendBuff[0].Position;
long bufferSize = context.Request.SendBuff[0].Size;
if (bufferSize != 0x4000)
{
// TODO: find error code here
Logger.PrintError(LogClass.ServiceTime, $"TimeZoneRule buffer size is 0x{bufferSize:x} (expected 0x4000)");
throw new InvalidOperationException();
}
TimeZoneRule rules = MemoryHelper.Read<TimeZoneRule>(context.Memory, bufferPosition);
ResultCode resultCode = _timeZoneManager.ToCalendarTime(rules, posixTime, out CalendarInfo calendar);
if (resultCode == 0)
{
context.ResponseData.WriteStruct(calendar);
}
return resultCode;
}
[Command(101)]
// ToCalendarTimeWithMyRule(nn::time::PosixTime) -> (nn::time::CalendarTime, nn::time::sf::CalendarAdditionalInfo)
public ResultCode ToCalendarTimeWithMyRule(ServiceCtx context)
{
long posixTime = context.RequestData.ReadInt64();
ResultCode resultCode = _timeZoneManager.ToCalendarTimeWithMyRules(posixTime, out CalendarInfo calendar);
if (resultCode == 0)
{
context.ResponseData.WriteStruct(calendar);
}
return resultCode;
}
[Command(201)]
// ToPosixTime(nn::time::CalendarTime calendarTime, buffer<nn::time::TimeZoneRule, 0x15> rules) -> (u32 outCount, buffer<nn::time::PosixTime, 0xa>)
public ResultCode ToPosixTime(ServiceCtx context)
{
long inBufferPosition = context.Request.SendBuff[0].Position;
long inBufferSize = context.Request.SendBuff[0].Size;
CalendarTime calendarTime = context.RequestData.ReadStruct<CalendarTime>();
if (inBufferSize != 0x4000)
{
// TODO: find error code here
Logger.PrintError(LogClass.ServiceTime, $"TimeZoneRule buffer size is 0x{inBufferSize:x} (expected 0x4000)");
throw new InvalidOperationException();
}
TimeZoneRule rules = MemoryHelper.Read<TimeZoneRule>(context.Memory, inBufferPosition);
ResultCode resultCode = _timeZoneManager.ToPosixTime(rules, calendarTime, out long posixTime);
if (resultCode == 0)
{
long outBufferPosition = context.Request.RecvListBuff[0].Position;
long outBufferSize = context.Request.RecvListBuff[0].Size;
context.Memory.WriteInt64(outBufferPosition, posixTime);
context.ResponseData.Write(1);
}
return resultCode;
}
[Command(202)]
// ToPosixTimeWithMyRule(nn::time::CalendarTime calendarTime) -> (u32 outCount, buffer<nn::time::PosixTime, 0xa>)
public ResultCode ToPosixTimeWithMyRule(ServiceCtx context)
{
CalendarTime calendarTime = context.RequestData.ReadStruct<CalendarTime>();
ResultCode resultCode = _timeZoneManager.ToPosixTimeWithMyRules(calendarTime, out long posixTime);
if (resultCode == 0)
{
long outBufferPosition = context.Request.RecvListBuff[0].Position;
long outBufferSize = context.Request.RecvListBuff[0].Size;
context.Memory.WriteInt64(outBufferPosition, posixTime);
// There could be only one result on one calendar as leap seconds aren't supported.
context.ResponseData.Write(1);
}
return resultCode;
}
private void WriteLocationName(ServiceCtx context, string locationName)
{
char[] locationNameArray = locationName.ToCharArray();
int padding = 0x24 - locationNameArray.Length;
Debug.Assert(padding >= 0, "LocationName exceeded limit (0x24 bytes)");
context.ResponseData.Write(locationNameArray);
for (int index = 0; index < padding; index++)
{
context.ResponseData.Write((byte)0);
}
}
}
}

View File

@ -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);
}
}
}

View File

@ -0,0 +1,126 @@
using System;
using System.Runtime.InteropServices;
using System.Threading;
using Ryujinx.HLE.HOS.Kernel.Memory;
using Ryujinx.HLE.HOS.Kernel.Threading;
using Ryujinx.HLE.HOS.Services.Time.Clock;
using Ryujinx.HLE.HOS.Services.Time.Types;
using Ryujinx.HLE.Utilities;
namespace Ryujinx.HLE.HOS.Services.Time
{
class TimeSharedMemory
{
private Switch _device;
private KSharedMemory _sharedMemory;
private long _timeSharedMemoryAddress;
private int _timeSharedMemorySize;
private const uint SteadyClockContextOffset = 0x00;
private const uint LocalSystemClockContextOffset = 0x38;
private const uint NetworkSystemClockContextOffset = 0x80;
private const uint AutomaticCorrectionEnabledOffset = 0xC8;
public void Initialize(Switch device, KSharedMemory sharedMemory, long timeSharedMemoryAddress, int timeSharedMemorySize)
{
_device = device;
_sharedMemory = sharedMemory;
_timeSharedMemoryAddress = timeSharedMemoryAddress;
_timeSharedMemorySize = timeSharedMemorySize;
// Clean the shared memory
_device.Memory.FillWithZeros(_timeSharedMemoryAddress, _timeSharedMemorySize);
}
public KSharedMemory GetSharedMemory()
{
return _sharedMemory;
}
public void SetupStandardSteadyClock(KThread thread, UInt128 clockSourceId, TimeSpanType currentTimePoint)
{
TimeSpanType ticksTimeSpan;
// As this may be called before the guest code, we support passing a null thread to make this api usable.
if (thread == null)
{
ticksTimeSpan = TimeSpanType.FromSeconds(0);
}
else
{
ticksTimeSpan = TimeSpanType.FromTicks(thread.Context.CntpctEl0, thread.Context.CntfrqEl0);
}
SteadyClockContext context = new SteadyClockContext
{
InternalOffset = (ulong)(currentTimePoint.NanoSeconds - ticksTimeSpan.NanoSeconds),
ClockSourceId = clockSourceId
};
WriteObjectToSharedMemory(SteadyClockContextOffset, 4, context);
}
public void SetAutomaticCorrectionEnabled(bool isAutomaticCorrectionEnabled)
{
// We convert the bool to byte here as a bool in C# takes 4 bytes...
WriteObjectToSharedMemory(AutomaticCorrectionEnabledOffset, 0, Convert.ToByte(isAutomaticCorrectionEnabled));
}
public void SetSteadyClockRawTimePoint(KThread thread, TimeSpanType currentTimePoint)
{
SteadyClockContext context = ReadObjectFromSharedMemory<SteadyClockContext>(SteadyClockContextOffset, 4);
TimeSpanType ticksTimeSpan = TimeSpanType.FromTicks(thread.Context.CntpctEl0, thread.Context.CntfrqEl0);
context.InternalOffset = (ulong)(currentTimePoint.NanoSeconds - ticksTimeSpan.NanoSeconds);
WriteObjectToSharedMemory(SteadyClockContextOffset, 4, context);
}
public void UpdateLocalSystemClockContext(SystemClockContext context)
{
WriteObjectToSharedMemory(LocalSystemClockContextOffset, 4, context);
}
public void UpdateNetworkSystemClockContext(SystemClockContext context)
{
WriteObjectToSharedMemory(NetworkSystemClockContextOffset, 4, context);
}
private T ReadObjectFromSharedMemory<T>(long offset, long padding)
{
long indexOffset = _timeSharedMemoryAddress + offset;
T result;
uint index;
uint possiblyNewIndex;
do
{
index = _device.Memory.ReadUInt32(indexOffset);
long objectOffset = indexOffset + 4 + padding + (index & 1) * Marshal.SizeOf<T>();
result = _device.Memory.ReadStruct<T>(objectOffset);
Thread.MemoryBarrier();
possiblyNewIndex = _device.Memory.ReadUInt32(indexOffset);
} while (index != possiblyNewIndex);
return result;
}
private void WriteObjectToSharedMemory<T>(long offset, long padding, T value)
{
long indexOffset = _timeSharedMemoryAddress + offset;
uint newIndex = _device.Memory.ReadUInt32(indexOffset) + 1;
long objectOffset = indexOffset + 4 + padding + (newIndex & 1) * Marshal.SizeOf<T>();
_device.Memory.WriteStruct(objectOffset, value);
Thread.MemoryBarrier();
_device.Memory.WriteUInt32(indexOffset, newIndex);
}
}
}

View File

@ -903,7 +903,7 @@ namespace Ryujinx.HLE.HOS.Services.Time.TimeZone
return ParsePosixName(name.ToCharArray(), out outRules, false); 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 outRules = new TimeZoneRule
{ {
@ -1357,10 +1357,8 @@ namespace Ryujinx.HLE.HOS.Services.Time.TimeZone
int[] ip = MonthsLengths[IsLeap((int)year)]; 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]; dayOfYear -= ip[calendarTime.Month];
} }
@ -1709,7 +1707,7 @@ namespace Ryujinx.HLE.HOS.Services.Time.TimeZone
Time = new CalendarTime() Time = new CalendarTime()
{ {
Year = (short)calendarTime.Year, Year = (short)calendarTime.Year,
Month = calendarTime.Month, Month = (sbyte)(calendarTime.Month + 1),
Day = calendarTime.Day, Day = calendarTime.Day,
Hour = calendarTime.Hour, Hour = calendarTime.Hour,
Minute = calendarTime.Minute, Minute = calendarTime.Minute,

View File

@ -0,0 +1,191 @@
using LibHac.Fs;
using LibHac.Fs.NcaUtils;
using Ryujinx.Common.Logging;
using Ryujinx.HLE.FileSystem;
using Ryujinx.HLE.HOS.Services.Time.Clock;
using Ryujinx.HLE.Resource;
using Ryujinx.HLE.Utilities;
using System.Collections.Generic;
using System.IO;
using static Ryujinx.HLE.HOS.Services.Time.TimeZone.TimeZoneRule;
namespace Ryujinx.HLE.HOS.Services.Time.TimeZone
{
class TimeZoneContentManager
{
private const long TimeZoneBinaryTitleId = 0x010000000000080E;
private Switch _device;
private string[] _locationNameCache;
public TimeZoneManager Manager { get; private set; }
public TimeZoneContentManager()
{
Manager = new TimeZoneManager();
}
internal void Initialize(TimeManager timeManager, Switch device)
{
_device = device;
InitializeLocationNameCache();
SteadyClockTimePoint timeZoneUpdatedTimePoint = timeManager.StandardSteadyClock.GetCurrentTimePoint(null);
ResultCode result = GetTimeZoneBinary("UTC", out Stream timeZoneBinaryStream);
if (result == ResultCode.Success)
{
// TODO: Read TimeZoneVersion from sysarchive.
timeManager.SetupTimeZoneManager("UTC", timeZoneUpdatedTimePoint, (uint)_locationNameCache.Length, new UInt128(), timeZoneBinaryStream);
}
else
{
// In the case the user don't have the timezone system archive, we just mark the manager as initialized.
Manager.MarkInitialized();
}
}
private void InitializeLocationNameCache()
{
if (HasTimeZoneBinaryTitle())
{
using (IStorage ncaFileStream = new LocalStorage(_device.FileSystem.SwitchPathToSystemPath(GetTimeZoneBinaryTitleContentPath()), FileAccess.Read, FileMode.Open))
{
Nca nca = new Nca(_device.System.KeySet, ncaFileStream);
IFileSystem romfs = nca.OpenFileSystem(NcaSectionType.Data, _device.System.FsIntegrityCheckLevel);
Stream binaryListStream = romfs.OpenFile("binaryList.txt", OpenMode.Read).AsStream();
StreamReader reader = new StreamReader(binaryListStream);
List<string> locationNameList = new List<string>();
string locationName;
while ((locationName = reader.ReadLine()) != null)
{
locationNameList.Add(locationName);
}
_locationNameCache = locationNameList.ToArray();
}
}
else
{
_locationNameCache = new string[0];
Logger.PrintWarning(LogClass.ServiceTime, "TimeZoneBinary system title not found! TimeZone conversions will not work, provide the system archive to fix this warning. (See https://github.com/Ryujinx/Ryujinx#requirements for more informations)");
}
}
private bool IsLocationNameValid(string locationName)
{
foreach (string cachedLocationName in _locationNameCache)
{
if (cachedLocationName.Equals(locationName))
{
return true;
}
}
return false;
}
public ResultCode SetDeviceLocationName(string locationName)
{
ResultCode result = GetTimeZoneBinary(locationName, out Stream timeZoneBinaryStream);
if (result == ResultCode.Success)
{
result = Manager.SetDeviceLocationNameWithTimeZoneRule(locationName, timeZoneBinaryStream);
}
return result;
}
public ResultCode LoadLocationNameList(uint index, out string[] outLocationNameArray, uint maxLength)
{
List<string> locationNameList = new List<string>();
for (int i = 0; i < _locationNameCache.Length && i < maxLength; i++)
{
if (i < index)
{
continue;
}
string locationName = _locationNameCache[i];
// If the location name is too long, error out.
if (locationName.Length > 0x24)
{
outLocationNameArray = new string[0];
return ResultCode.LocationNameTooLong;
}
locationNameList.Add(locationName);
}
outLocationNameArray = locationNameList.ToArray();
return ResultCode.Success;
}
public string GetTimeZoneBinaryTitleContentPath()
{
return _device.System.ContentManager.GetInstalledContentPath(TimeZoneBinaryTitleId, StorageId.NandSystem, ContentType.Data);
}
public bool HasTimeZoneBinaryTitle()
{
return !string.IsNullOrEmpty(GetTimeZoneBinaryTitleContentPath());
}
internal ResultCode GetTimeZoneBinary(string locationName, out Stream timeZoneBinaryStream)
{
timeZoneBinaryStream = null;
if (!IsLocationNameValid(locationName))
{
return ResultCode.TimeZoneNotFound;
}
using (IStorage ncaFileStream = new LocalStorage(_device.FileSystem.SwitchPathToSystemPath(GetTimeZoneBinaryTitleContentPath()), FileAccess.Read, FileMode.Open))
{
Nca nca = new Nca(_device.System.KeySet, ncaFileStream);
IFileSystem romfs = nca.OpenFileSystem(NcaSectionType.Data, _device.System.FsIntegrityCheckLevel);
timeZoneBinaryStream = romfs.OpenFile($"zoneinfo/{locationName}", OpenMode.Read).AsStream();
}
return ResultCode.Success;
}
internal ResultCode LoadTimeZoneRule(out TimeZoneRule outRules, string locationName)
{
outRules = new TimeZoneRule
{
Ats = new long[TzMaxTimes],
Types = new byte[TzMaxTimes],
Ttis = new TimeTypeInfo[TzMaxTypes],
Chars = new char[TzCharsArraySize]
};
if (!HasTimeZoneBinaryTitle())
{
throw new InvalidSystemResourceException($"TimeZoneBinary system title not found! Please provide it. (See https://github.com/Ryujinx/Ryujinx#requirements for more informations)");
}
ResultCode result = GetTimeZoneBinary(locationName, out Stream timeZoneBinaryStream);
if (result == ResultCode.Success)
{
result = Manager.ParseTimeZoneRuleBinary(out outRules, timeZoneBinaryStream);
}
return result;
}
}
}

View File

@ -1,50 +1,28 @@
using LibHac.Fs; using Ryujinx.HLE.HOS.Services.Time.Clock;
using LibHac.Fs.NcaUtils; using Ryujinx.HLE.Utilities;
using Ryujinx.Common.Logging;
using Ryujinx.HLE.FileSystem;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.IO; using System.IO;
using TimeZoneConverter;
using TimeZoneConverter.Posix;
using static Ryujinx.HLE.HOS.Services.Time.TimeZone.TimeZoneRule; using static Ryujinx.HLE.HOS.Services.Time.TimeZone.TimeZoneRule;
namespace Ryujinx.HLE.HOS.Services.Time.TimeZone 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; public TimeZoneManager()
private static object instanceLock = new object();
private Switch _device;
private TimeZoneRule _myRules;
private string _deviceLocationName;
private string[] _locationNameCache;
public static TimeZoneManager Instance
{ {
get _isInitialized = false;
{ _deviceLocationName = "UTC";
lock (instanceLock) _timeZoneRuleVersion = new UInt128();
{ _lock = new object();
if (instance == null)
{
instance = new TimeZoneManager();
}
return instance; // Empty rules
}
}
}
TimeZoneManager()
{
// Empty rules (UTC)
_myRules = new TimeZoneRule _myRules = new TimeZoneRule
{ {
Ats = new long[TzMaxTimes], Ats = new long[TzMaxTimes],
@ -53,236 +31,237 @@ namespace Ryujinx.HLE.HOS.Services.Time.TimeZone
Chars = new char[TzCharsArraySize] Chars = new char[TzCharsArraySize]
}; };
_deviceLocationName = "UTC"; _timeZoneUpdateTimePoint = SteadyClockTimePoint.GetRandom();
} }
internal void Initialize(Switch device) public bool IsInitialized()
{ {
_device = device; bool res;
InitializeLocationNameCache(); lock (_lock)
}
private void InitializeLocationNameCache()
{
if (HasTimeZoneBinaryTitle())
{ {
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); deviceLocationName = _deviceLocationName;
IFileSystem romfs = nca.OpenFileSystem(NcaSectionType.Data, _device.System.FsIntegrityCheckLevel); result = ResultCode.Success;
Stream binaryListStream = romfs.OpenFile("binaryList.txt", OpenMode.Read).AsStream();
StreamReader reader = new StreamReader(binaryListStream);
List<string> locationNameList = new List<string>();
string locationName;
while ((locationName = reader.ReadLine()) != null)
{
locationNameList.Add(locationName);
}
_locationNameCache = locationNameList.ToArray();
} }
} }
else
{
ReadOnlyCollection<TimeZoneInfo> timeZoneInfos = TimeZoneInfo.GetSystemTimeZones();
_locationNameCache = new string[timeZoneInfos.Count];
int i = 0; return result;
foreach (TimeZoneInfo timeZoneInfo in timeZoneInfos)
{
bool needConversion = TZConvert.TryWindowsToIana(timeZoneInfo.Id, out string convertedName);
if (needConversion)
{
_locationNameCache[i] = convertedName;
}
else
{
_locationNameCache[i] = timeZoneInfo.Id;
}
i++;
}
// 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;
}
} }
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; lock (_lock)
}
public ResultCode SetDeviceLocationName(string locationName)
{
ResultCode resultCode = LoadTimeZoneRules(out TimeZoneRule rules, locationName);
if (resultCode == 0)
{ {
_myRules = rules; _totalLocationNameCount = totalLocationNameCount;
_deviceLocationName = locationName;
} }
return resultCode;
} }
public ResultCode LoadLocationNameList(uint index, out string[] outLocationNameArray, uint maxLength) public ResultCode GetTotalLocationNameCount(out uint totalLocationNameCount)
{ {
List<string> locationNameList = new List<string>(); ResultCode result = ResultCode.UninitializedClock;
for (int i = 0; i < _locationNameCache.Length && i < maxLength; i++) totalLocationNameCount = 0;
lock (_lock)
{ {
if (i < index) if (_isInitialized)
{ {
continue; totalLocationNameCount = _totalLocationNameCount;
} result = ResultCode.Success;
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;
} }
} }
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); _timeZoneUpdateTimePoint = timeZoneUpdatedTimePoint;
IFileSystem romfs = nca.OpenFileSystem(NcaSectionType.Data, _device.System.FsIntegrityCheckLevel); result = ResultCode.Success;
Stream tzIfStream = romfs.OpenFile($"zoneinfo/{locationName}", OpenMode.Read).AsStream();
if (!TimeZone.LoadTimeZoneRules(out outRules, tzIfStream))
{
return ResultCode.TimeZoneConversionFailed;
}
} }
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) lock (_lock)
{
ResultCode error = TimeZone.ToCalendarTime(rules, time, out calendar);
if (error != ResultCode.Success)
{ {
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) lock (_lock)
{
ResultCode error = TimeZone.ToPosixTime(rules, calendarTime, out posixTime);
if (error != ResultCode.Success)
{ {
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;
} }
} }
} }

View File

@ -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;
}
}

View File

@ -8,10 +8,15 @@ namespace Ryujinx.HLE.HOS.Services.Time
LocalSystemClockWritableMask = 0x1, LocalSystemClockWritableMask = 0x1,
UserSystemClockWritableMask = 0x2, UserSystemClockWritableMask = 0x2,
NetworkSystemClockWritableMask = 0x4, NetworkSystemClockWritableMask = 0x4,
UnknownPermissionMask = 0x8, TimeZoneWritableMask = 0x8,
SteadyClockWritableMask = 0x10,
BypassUninitialized = 0x20,
User = 0, User = 0,
Applet = LocalSystemClockWritableMask | UserSystemClockWritableMask | UnknownPermissionMask, Admin = LocalSystemClockWritableMask | UserSystemClockWritableMask | TimeZoneWritableMask,
System = NetworkSystemClockWritableMask System = NetworkSystemClockWritableMask,
SystemUpdate = BypassUninitialized,
Repair = SteadyClockWritableMask,
Manufacture = LocalSystemClockWritableMask | UserSystemClockWritableMask | NetworkSystemClockWritableMask | TimeZoneWritableMask | SteadyClockWritableMask
} }
} }