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:
parent
16869402bf
commit
1aba033ba7
37 changed files with 2202 additions and 716 deletions
|
@ -5,42 +5,78 @@ namespace Ryujinx.HLE.HOS.Services.Time.StaticService
|
|||
{
|
||||
class ISteadyClock : IpcService
|
||||
{
|
||||
private SteadyClockCore _steadyClock;
|
||||
private bool _writePermission;
|
||||
private bool _bypassUninitializedClock;
|
||||
|
||||
public ISteadyClock(SteadyClockCore steadyClock, bool writePermission, bool bypassUninitializedClock)
|
||||
{
|
||||
_steadyClock = steadyClock;
|
||||
_writePermission = writePermission;
|
||||
_bypassUninitializedClock = bypassUninitializedClock;
|
||||
}
|
||||
|
||||
[Command(0)]
|
||||
// GetCurrentTimePoint() -> nn::time::SteadyClockTimePoint
|
||||
public ResultCode GetCurrentTimePoint(ServiceCtx context)
|
||||
{
|
||||
SteadyClockTimePoint currentTimePoint = StandardSteadyClockCore.Instance.GetCurrentTimePoint(context.Thread);
|
||||
if (!_bypassUninitializedClock && !_steadyClock.IsInitialized())
|
||||
{
|
||||
return ResultCode.UninitializedClock;
|
||||
}
|
||||
|
||||
SteadyClockTimePoint currentTimePoint = _steadyClock.GetCurrentTimePoint(context.Thread);
|
||||
|
||||
context.ResponseData.WriteStruct(currentTimePoint);
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
[Command(1)]
|
||||
[Command(2)]
|
||||
// GetTestOffset() -> nn::TimeSpanType
|
||||
public ResultCode GetTestOffset(ServiceCtx context)
|
||||
{
|
||||
context.ResponseData.WriteStruct(StandardSteadyClockCore.Instance.GetTestOffset());
|
||||
if (!_bypassUninitializedClock && !_steadyClock.IsInitialized())
|
||||
{
|
||||
return ResultCode.UninitializedClock;
|
||||
}
|
||||
|
||||
context.ResponseData.WriteStruct(_steadyClock.GetTestOffset());
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
[Command(2)]
|
||||
[Command(3)]
|
||||
// SetTestOffset(nn::TimeSpanType)
|
||||
public ResultCode SetTestOffset(ServiceCtx context)
|
||||
{
|
||||
if (!_writePermission)
|
||||
{
|
||||
return ResultCode.PermissionDenied;
|
||||
}
|
||||
|
||||
if (!_bypassUninitializedClock && !_steadyClock.IsInitialized())
|
||||
{
|
||||
return ResultCode.UninitializedClock;
|
||||
}
|
||||
|
||||
TimeSpanType testOffset = context.RequestData.ReadStruct<TimeSpanType>();
|
||||
|
||||
StandardSteadyClockCore.Instance.SetTestOffset(testOffset);
|
||||
_steadyClock.SetTestOffset(testOffset);
|
||||
|
||||
return 0;
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
[Command(100)] // 2.0.0+
|
||||
// GetRtcValue() -> u64
|
||||
public ResultCode GetRtcValue(ServiceCtx context)
|
||||
{
|
||||
ResultCode result = StandardSteadyClockCore.Instance.GetRtcValue(out ulong rtcValue);
|
||||
if (!_bypassUninitializedClock && !_steadyClock.IsInitialized())
|
||||
{
|
||||
return ResultCode.UninitializedClock;
|
||||
}
|
||||
|
||||
ResultCode result = _steadyClock.GetRtcValue(out ulong rtcValue);
|
||||
|
||||
if (result == ResultCode.Success)
|
||||
{
|
||||
|
@ -54,7 +90,12 @@ namespace Ryujinx.HLE.HOS.Services.Time.StaticService
|
|||
// IsRtcResetDetected() -> bool
|
||||
public ResultCode IsRtcResetDetected(ServiceCtx context)
|
||||
{
|
||||
context.ResponseData.Write(StandardSteadyClockCore.Instance.IsRtcResetDetected());
|
||||
if (!_bypassUninitializedClock && !_steadyClock.IsInitialized())
|
||||
{
|
||||
return ResultCode.UninitializedClock;
|
||||
}
|
||||
|
||||
context.ResponseData.Write(_steadyClock.IsRtcResetDetected());
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
@ -63,7 +104,12 @@ namespace Ryujinx.HLE.HOS.Services.Time.StaticService
|
|||
// GetSetupResultValue() -> u32
|
||||
public ResultCode GetSetupResultValue(ServiceCtx context)
|
||||
{
|
||||
context.ResponseData.Write((uint)StandardSteadyClockCore.Instance.GetSetupResultValue());
|
||||
if (!_bypassUninitializedClock && !_steadyClock.IsInitialized())
|
||||
{
|
||||
return ResultCode.UninitializedClock;
|
||||
}
|
||||
|
||||
context.ResponseData.Write((uint)_steadyClock.GetSetupResultValue());
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
@ -72,7 +118,12 @@ namespace Ryujinx.HLE.HOS.Services.Time.StaticService
|
|||
// GetInternalOffset() -> nn::TimeSpanType
|
||||
public ResultCode GetInternalOffset(ServiceCtx context)
|
||||
{
|
||||
context.ResponseData.WriteStruct(StandardSteadyClockCore.Instance.GetInternalOffset());
|
||||
if (!_bypassUninitializedClock && !_steadyClock.IsInitialized())
|
||||
{
|
||||
return ResultCode.UninitializedClock;
|
||||
}
|
||||
|
||||
context.ResponseData.WriteStruct(_steadyClock.GetInternalOffset());
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
@ -81,9 +132,19 @@ namespace Ryujinx.HLE.HOS.Services.Time.StaticService
|
|||
// SetInternalOffset(nn::TimeSpanType)
|
||||
public ResultCode SetInternalOffset(ServiceCtx context)
|
||||
{
|
||||
if (!_writePermission)
|
||||
{
|
||||
return ResultCode.PermissionDenied;
|
||||
}
|
||||
|
||||
if (!_bypassUninitializedClock && !_steadyClock.IsInitialized())
|
||||
{
|
||||
return ResultCode.UninitializedClock;
|
||||
}
|
||||
|
||||
TimeSpanType internalOffset = context.RequestData.ReadStruct<TimeSpanType>();
|
||||
|
||||
StandardSteadyClockCore.Instance.SetInternalOffset(internalOffset);
|
||||
_steadyClock.SetInternalOffset(internalOffset);
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
|
|
@ -1,5 +1,9 @@
|
|||
using Ryujinx.Common;
|
||||
using Ryujinx.HLE.HOS.Ipc;
|
||||
using Ryujinx.HLE.HOS.Kernel.Common;
|
||||
using Ryujinx.HLE.HOS.Kernel.Threading;
|
||||
using Ryujinx.HLE.HOS.Services.Time.Clock;
|
||||
using System;
|
||||
|
||||
namespace Ryujinx.HLE.HOS.Services.Time.StaticService
|
||||
{
|
||||
|
@ -7,34 +11,31 @@ namespace Ryujinx.HLE.HOS.Services.Time.StaticService
|
|||
{
|
||||
private SystemClockCore _clockCore;
|
||||
private bool _writePermission;
|
||||
private bool _bypassUninitializedClock;
|
||||
private int _operationEventReadableHandle;
|
||||
|
||||
public ISystemClock(SystemClockCore clockCore, bool writePermission)
|
||||
public ISystemClock(SystemClockCore clockCore, bool writePermission, bool bypassUninitializedClock)
|
||||
{
|
||||
_clockCore = clockCore;
|
||||
_writePermission = writePermission;
|
||||
_clockCore = clockCore;
|
||||
_writePermission = writePermission;
|
||||
_bypassUninitializedClock = bypassUninitializedClock;
|
||||
_operationEventReadableHandle = 0;
|
||||
}
|
||||
|
||||
[Command(0)]
|
||||
// GetCurrentTime() -> nn::time::PosixTime
|
||||
public ResultCode GetCurrentTime(ServiceCtx context)
|
||||
{
|
||||
SteadyClockCore steadyClockCore = _clockCore.GetSteadyClockCore();
|
||||
SteadyClockTimePoint currentTimePoint = steadyClockCore.GetCurrentTimePoint(context.Thread);
|
||||
if (!_bypassUninitializedClock && !_clockCore.IsInitialized())
|
||||
{
|
||||
return ResultCode.UninitializedClock;
|
||||
}
|
||||
|
||||
ResultCode result = _clockCore.GetSystemClockContext(context.Thread, out SystemClockContext clockContext);
|
||||
ResultCode result = _clockCore.GetCurrentTime(context.Thread, out long posixTime);
|
||||
|
||||
if (result == ResultCode.Success)
|
||||
{
|
||||
result = ResultCode.TimeMismatch;
|
||||
|
||||
if (currentTimePoint.ClockSourceId == clockContext.SteadyTimePoint.ClockSourceId)
|
||||
{
|
||||
long posixTime = clockContext.Offset + currentTimePoint.TimePoint;
|
||||
|
||||
context.ResponseData.Write(posixTime);
|
||||
|
||||
result = 0;
|
||||
}
|
||||
context.ResponseData.Write(posixTime);
|
||||
}
|
||||
|
||||
return result;
|
||||
|
@ -49,31 +50,26 @@ namespace Ryujinx.HLE.HOS.Services.Time.StaticService
|
|||
return ResultCode.PermissionDenied;
|
||||
}
|
||||
|
||||
long posixTime = context.RequestData.ReadInt64();
|
||||
SteadyClockCore steadyClockCore = _clockCore.GetSteadyClockCore();
|
||||
SteadyClockTimePoint currentTimePoint = steadyClockCore.GetCurrentTimePoint(context.Thread);
|
||||
|
||||
SystemClockContext clockContext = new SystemClockContext()
|
||||
if (!_bypassUninitializedClock && !_clockCore.IsInitialized())
|
||||
{
|
||||
Offset = posixTime - currentTimePoint.TimePoint,
|
||||
SteadyTimePoint = currentTimePoint
|
||||
};
|
||||
|
||||
ResultCode result = _clockCore.SetSystemClockContext(clockContext);
|
||||
|
||||
if (result == ResultCode.Success)
|
||||
{
|
||||
result = _clockCore.Flush(clockContext);
|
||||
return ResultCode.UninitializedClock;
|
||||
}
|
||||
|
||||
return result;
|
||||
long posixTime = context.RequestData.ReadInt64();
|
||||
|
||||
return _clockCore.SetCurrentTime(context.Thread, posixTime);
|
||||
}
|
||||
|
||||
[Command(2)]
|
||||
// GetSystemClockContext() -> nn::time::SystemClockContext
|
||||
// GetClockContext() -> nn::time::SystemClockContext
|
||||
public ResultCode GetSystemClockContext(ServiceCtx context)
|
||||
{
|
||||
ResultCode result = _clockCore.GetSystemClockContext(context.Thread, out SystemClockContext clockContext);
|
||||
if (!_bypassUninitializedClock && !_clockCore.IsInitialized())
|
||||
{
|
||||
return ResultCode.UninitializedClock;
|
||||
}
|
||||
|
||||
ResultCode result = _clockCore.GetClockContext(context.Thread, out SystemClockContext clockContext);
|
||||
|
||||
if (result == ResultCode.Success)
|
||||
{
|
||||
|
@ -84,7 +80,7 @@ namespace Ryujinx.HLE.HOS.Services.Time.StaticService
|
|||
}
|
||||
|
||||
[Command(3)]
|
||||
// SetSystemClockContext(nn::time::SystemClockContext)
|
||||
// SetClockContext(nn::time::SystemClockContext)
|
||||
public ResultCode SetSystemClockContext(ServiceCtx context)
|
||||
{
|
||||
if (!_writePermission)
|
||||
|
@ -92,16 +88,37 @@ namespace Ryujinx.HLE.HOS.Services.Time.StaticService
|
|||
return ResultCode.PermissionDenied;
|
||||
}
|
||||
|
||||
if (!_bypassUninitializedClock && !_clockCore.IsInitialized())
|
||||
{
|
||||
return ResultCode.UninitializedClock;
|
||||
}
|
||||
|
||||
SystemClockContext clockContext = context.RequestData.ReadStruct<SystemClockContext>();
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue