diff --git a/Ryujinx.HLE/HOS/Services/Time/StaticService/ITimeZoneServiceForPsc.cs b/Ryujinx.HLE/HOS/Services/Time/StaticService/ITimeZoneServiceForPsc.cs new file mode 100644 index 0000000000..31c6e98e99 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Time/StaticService/ITimeZoneServiceForPsc.cs @@ -0,0 +1,295 @@ +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.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Text; + +namespace Ryujinx.HLE.HOS.Services.Time.StaticService +{ + class ITimeZoneServiceForPsc : IpcService + { + private TimeZoneManagerForPsc _timeZoneManager; + private bool _writePermission; + + public ITimeZoneServiceForPsc(TimeZoneManagerForPsc 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) + public ResultCode LoadLocationNameList(ServiceCtx context) + { + return ResultCode.NotImplemented; + } + + [Command(4)] + // LoadTimeZoneRule(nn::time::LocationName locationName) -> buffer + 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 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 timeZoneBinary) -> buffer + 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 + public ResultCode GetDeviceLocationNameOperationEventReadableHandle(ServiceCtx context) + { + return ResultCode.NotImplemented; + } + + [Command(100)] + // ToCalendarTime(nn::time::PosixTime time, buffer 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(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 rules) -> (u32 outCount, buffer) + public ResultCode ToPosixTime(ServiceCtx context) + { + long inBufferPosition = context.Request.SendBuff[0].Position; + long inBufferSize = context.Request.SendBuff[0].Size; + + CalendarTime calendarTime = context.RequestData.ReadStruct(); + + 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(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) + public ResultCode ToPosixTimeWithMyRule(ServiceCtx context) + { + CalendarTime calendarTime = context.RequestData.ReadStruct(); + + 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); + } + } + } +} diff --git a/Ryujinx.HLE/HOS/Services/Time/TimeZone/TimeZone.cs b/Ryujinx.HLE/HOS/Services/Time/TimeZone/TimeZone.cs index 3a98013e11..f4c4bd6976 100644 --- a/Ryujinx.HLE/HOS/Services/Time/TimeZone/TimeZone.cs +++ b/Ryujinx.HLE/HOS/Services/Time/TimeZone/TimeZone.cs @@ -903,7 +903,7 @@ namespace Ryujinx.HLE.HOS.Services.Time.TimeZone 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 { diff --git a/Ryujinx.HLE/HOS/Services/Time/TimeZone/TimeZoneManager.cs b/Ryujinx.HLE/HOS/Services/Time/TimeZone/TimeZoneManager.cs index a1fc6646ae..511b025997 100644 --- a/Ryujinx.HLE/HOS/Services/Time/TimeZone/TimeZoneManager.cs +++ b/Ryujinx.HLE/HOS/Services/Time/TimeZone/TimeZoneManager.cs @@ -222,7 +222,7 @@ namespace Ryujinx.HLE.HOS.Services.Time.TimeZone IFileSystem romfs = nca.OpenFileSystem(NcaSectionType.Data, _device.System.FsIntegrityCheckLevel); Stream tzIfStream = romfs.OpenFile($"zoneinfo/{locationName}", OpenMode.Read).AsStream(); - if (!TimeZone.LoadTimeZoneRules(out outRules, tzIfStream)) + if (!TimeZone.ParseTimeZoneBinary(out outRules, tzIfStream)) { return ResultCode.TimeZoneConversionFailed; } diff --git a/Ryujinx.HLE/HOS/Services/Time/TimeZone/TimeZoneManagerForPsc.cs b/Ryujinx.HLE/HOS/Services/Time/TimeZone/TimeZoneManagerForPsc.cs new file mode 100644 index 0000000000..18d01b67ef --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Time/TimeZone/TimeZoneManagerForPsc.cs @@ -0,0 +1,263 @@ +using Ryujinx.HLE.HOS.Services.Time.Clock; +using Ryujinx.HLE.Utilities; +using System.IO; +using System.Threading; +using static Ryujinx.HLE.HOS.Services.Time.TimeZone.TimeZoneRule; + +namespace Ryujinx.HLE.HOS.Services.Time.TimeZone +{ + class TimeZoneManagerForPsc + { + private bool _isInitialized; + private TimeZoneRule _myRules; + private string _deviceLocationName; + private UInt128 _timeZoneRuleVersion; + private uint _totalLocationNameCount; + private SteadyClockTimePoint _timeZoneUpdateTimePoint; + private object _lock; + + public TimeZoneManagerForPsc() + { + _isInitialized = false; + _deviceLocationName = null; + _timeZoneRuleVersion = new UInt128(); + _lock = new object(); + + // Empty rules + _myRules = new TimeZoneRule + { + Ats = new long[TzMaxTimes], + Types = new byte[TzMaxTimes], + Ttis = new TimeTypeInfo[TzMaxTypes], + Chars = new char[TzCharsArraySize] + }; + + _timeZoneUpdateTimePoint = SteadyClockTimePoint.GetRandom(); + } + + public bool IsInitialized() + { + return _isInitialized; + } + + public void MarkInitialized() + { + _isInitialized = true; + } + + public ResultCode GetDeviceLocationName(out string deviceLocationName) + { + ResultCode result = ResultCode.UninitializedClock; + + deviceLocationName = null; + + Monitor.Enter(_lock); + + if (_isInitialized) + { + deviceLocationName = _deviceLocationName; + result = ResultCode.Success; + } + + Monitor.Exit(_lock); + + return result; + } + + public ResultCode SetDeviceLocationNameWithTimeZoneRule(string locationName, Stream timeZoneBinaryStream) + { + ResultCode result = ResultCode.TimeZoneConversionFailed; + + Monitor.Enter(_lock); + + bool timeZoneConversionSuccess = TimeZone.ParseTimeZoneBinary(out TimeZoneRule rules, timeZoneBinaryStream); + + if (timeZoneConversionSuccess) + { + _deviceLocationName = locationName; + _myRules = rules; + result = ResultCode.Success; + } + + Monitor.Exit(_lock); + + return result; + } + + public void SetTotalLocationNameCount(uint totalLocationNameCount) + { + Monitor.Enter(_lock); + _totalLocationNameCount = totalLocationNameCount; + Monitor.Exit(_lock); + } + + public ResultCode GetTotalLocationNameCount(out uint totalLocationNameCount) + { + ResultCode result = ResultCode.UninitializedClock; + + totalLocationNameCount = 0; + + Monitor.Enter(_lock); + + if (_isInitialized) + { + totalLocationNameCount = _totalLocationNameCount; + result = ResultCode.Success; + } + + Monitor.Exit(_lock); + + return result; + } + + public ResultCode SetUpdatedTime(SteadyClockTimePoint timeZoneUpdatedTimePoint) + { + ResultCode result = ResultCode.UninitializedClock; + + Monitor.Enter(_lock); + + if (_isInitialized) + { + _timeZoneUpdateTimePoint = timeZoneUpdatedTimePoint; + result = ResultCode.Success; + } + + Monitor.Exit(_lock); + + return result; + } + + public ResultCode GetUpdatedTime(out SteadyClockTimePoint timeZoneUpdatedTimePoint) + { + ResultCode result; + + Monitor.Enter(_lock); + + if (_isInitialized) + { + timeZoneUpdatedTimePoint = _timeZoneUpdateTimePoint; + result = ResultCode.Success; + } + else + { + timeZoneUpdatedTimePoint = SteadyClockTimePoint.GetRandom(); + result = ResultCode.UninitializedClock; + } + + Monitor.Exit(_lock); + + return result; + } + + public ResultCode ParseTimeZoneRuleBinary(out TimeZoneRule outRules, Stream timeZoneBinaryStream) + { + ResultCode result = ResultCode.Success; + + Monitor.Enter(_lock); + + bool timeZoneConversionSuccess = TimeZone.ParseTimeZoneBinary(out outRules, timeZoneBinaryStream); + + if (!timeZoneConversionSuccess) + { + result = ResultCode.TimeZoneConversionFailed; + } + + Monitor.Exit(_lock); + + return result; + } + + public void SetTimeZoneRuleVersion(UInt128 timeZoneRuleVersion) + { + Monitor.Enter(_lock); + _timeZoneRuleVersion = timeZoneRuleVersion; + Monitor.Exit(_lock); + } + + public ResultCode GetTimeZoneRuleVersion(out UInt128 timeZoneRuleVersion) + { + ResultCode result; + + Monitor.Enter(_lock); + + if (_isInitialized) + { + timeZoneRuleVersion = _timeZoneRuleVersion; + result = ResultCode.Success; + } + else + { + timeZoneRuleVersion = new UInt128(); + result = ResultCode.UninitializedClock; + } + + Monitor.Exit(_lock); + + return result; + } + + public ResultCode ToCalendarTimeWithMyRules(long time, out CalendarInfo calendar) + { + ResultCode result; + + Monitor.Enter(_lock); + + if (_isInitialized) + { + result = ToCalendarTime(_myRules, time, out calendar); + } + else + { + calendar = new CalendarInfo(); + result = ResultCode.UninitializedClock; + } + + Monitor.Exit(_lock); + + return result; + } + + public ResultCode ToCalendarTime(TimeZoneRule rules, long time, out CalendarInfo calendar) + { + Monitor.Enter(_lock); + + ResultCode result = TimeZone.ToCalendarTime(rules, time, out calendar); + + Monitor.Exit(_lock); + + return result; + } + + public ResultCode ToPosixTimeWithMyRules(CalendarTime calendarTime, out long posixTime) + { + ResultCode result; + + Monitor.Enter(_lock); + + if (_isInitialized) + { + result = ToPosixTime(_myRules, calendarTime, out posixTime); + } + else + { + posixTime = 0; + result = ResultCode.UninitializedClock; + } + + Monitor.Exit(_lock); + + return result; + } + + public ResultCode ToPosixTime(TimeZoneRule rules, CalendarTime calendarTime, out long posixTime) + { + Monitor.Enter(_lock); + + ResultCode result = TimeZone.ToPosixTime(rules, calendarTime, out posixTime); + + Monitor.Exit(_lock); + + return result; + } + } +}