diff --git a/Ryujinx.HLE/HOS/Services/Time/ITimeZoneService.cs b/Ryujinx.HLE/HOS/Services/Time/ITimeZoneService.cs index 563a9753cc..92681f9cad 100644 --- a/Ryujinx.HLE/HOS/Services/Time/ITimeZoneService.cs +++ b/Ryujinx.HLE/HOS/Services/Time/ITimeZoneService.cs @@ -1,3 +1,4 @@ +using Ryujinx.Common; using Ryujinx.Common.Logging; using Ryujinx.HLE.HOS.Ipc; using System; @@ -22,18 +23,21 @@ namespace Ryujinx.HLE.HOS.Services.Time { _commands = new Dictionary { - { 0, GetDeviceLocationName }, - { 1, SetDeviceLocationName }, - { 2, GetTotalLocationNameCount }, - { 3, LoadLocationNameList }, - { 4, LoadTimeZoneRule }, - { 100, ToCalendarTime }, - { 101, ToCalendarTimeWithMyRule }, - { 201, ToPosixTime }, - { 202, ToPosixTimeWithMyRule } + { 0, GetDeviceLocationName }, + { 1, SetDeviceLocationName }, + { 2, GetTotalLocationNameCount }, + { 3, LoadLocationNameList }, + { 4, LoadTimeZoneRule }, + //{ 5, GetTimeZoneRuleVersion }, // 2.0.0+ + //{ 6, GetDeviceLocationNameAndUpdatedTime }, // 5.0.0+ + { 100, ToCalendarTime }, + { 101, ToCalendarTimeWithMyRule }, + { 201, ToPosixTime }, + { 202, ToPosixTimeWithMyRule } }; } + // GetDeviceLocationName() -> nn::time::LocationName public long GetDeviceLocationName(ServiceCtx context) { char[] tzName = _timeZone.Id.ToCharArray(); @@ -50,6 +54,7 @@ namespace Ryujinx.HLE.HOS.Services.Time return 0; } + // SetDeviceLocationName(nn::time::LocationName) public long SetDeviceLocationName(ServiceCtx context) { byte[] locationName = context.RequestData.ReadBytes(0x24); @@ -64,12 +69,13 @@ namespace Ryujinx.HLE.HOS.Services.Time } catch (TimeZoneNotFoundException) { - resultCode = MakeError(ErrorModule.Time, 0x3dd); + resultCode = MakeError(ErrorModule.Time, TimeError.TimeZoneNotFound); } return resultCode; } + // GetTotalLocationNameCount() -> u32 public long GetTotalLocationNameCount(ServiceCtx context) { context.ResponseData.Write(TimeZoneInfo.GetSystemTimeZones().Count); @@ -77,8 +83,11 @@ namespace Ryujinx.HLE.HOS.Services.Time return 0; } + // LoadLocationNameList(u32 index) -> (u32 outCount, buffer) public long LoadLocationNameList(ServiceCtx context) { + // TODO: fix logic to use index + uint index = context.RequestData.ReadUInt32(); long bufferPosition = context.Response.SendBuff[0].Position; long bufferSize = context.Response.SendBuff[0].Size; @@ -92,7 +101,7 @@ namespace Ryujinx.HLE.HOS.Services.Time int padding = 0x24 - tzData.Length; - for (int index = 0; index < padding; index++) + for (int i = 0; i < padding; i++) { context.ResponseData.Write((byte)0); } @@ -103,6 +112,7 @@ namespace Ryujinx.HLE.HOS.Services.Time return 0; } + // LoadTimeZoneRule(nn::time::LocationName locationName) -> buffer public long LoadTimeZoneRule(ServiceCtx context) { long bufferPosition = context.Request.ReceiveBuff[0].Position; @@ -134,34 +144,13 @@ namespace Ryujinx.HLE.HOS.Services.Time { Logger.PrintWarning(LogClass.ServiceTime, $"Timezone not found for string: {tzId} (len: {tzId.Length})"); - resultCode = MakeError(ErrorModule.Time, 0x3dd); + resultCode = MakeError(ErrorModule.Time, TimeError.TimeZoneNotFound); } return resultCode; } - private long ToCalendarTimeWithTz(ServiceCtx context, long posixTime, TimeZoneInfo info) - { - DateTime currentTime = Epoch.AddSeconds(posixTime); - - currentTime = TimeZoneInfo.ConvertTimeFromUtc(currentTime, info); - - context.ResponseData.Write((ushort)currentTime.Year); - context.ResponseData.Write((byte)currentTime.Month); - context.ResponseData.Write((byte)currentTime.Day); - context.ResponseData.Write((byte)currentTime.Hour); - context.ResponseData.Write((byte)currentTime.Minute); - context.ResponseData.Write((byte)currentTime.Second); - context.ResponseData.Write((byte)0); //MilliSecond ? - context.ResponseData.Write((int)currentTime.DayOfWeek); - context.ResponseData.Write(currentTime.DayOfYear - 1); - context.ResponseData.Write(new byte[8]); //TODO: Find out the names used. - context.ResponseData.Write((byte)(currentTime.IsDaylightSavingTime() ? 1 : 0)); - context.ResponseData.Write((int)info.GetUtcOffset(currentTime).TotalSeconds); - - return 0; - } - + // ToCalendarTime(nn::time::PosixTime time, buffer rules) -> (nn::time::CalendarTime, nn::time::sf::CalendarAdditionalInfo) public long ToCalendarTime(ServiceCtx context) { long posixTime = context.RequestData.ReadInt64(); @@ -178,7 +167,7 @@ namespace Ryujinx.HLE.HOS.Services.Time string tzId = Encoding.ASCII.GetString(tzData).TrimEnd('\0'); - long resultCode = 0; + long resultCode; // Check if the Time Zone exists, otherwise error out. try @@ -191,12 +180,13 @@ namespace Ryujinx.HLE.HOS.Services.Time { Logger.PrintWarning(LogClass.ServiceTime, $"Timezone not found for string: {tzId} (len: {tzId.Length})"); - resultCode = MakeError(ErrorModule.Time, 0x3dd); + resultCode = MakeError(ErrorModule.Time, TimeError.TimeZoneNotFound); } return resultCode; } + // ToCalendarTimeWithMyRule(nn::time::PosixTime) -> (nn::time::CalendarTime, nn::time::sf::CalendarAdditionalInfo) public long ToCalendarTimeWithMyRule(ServiceCtx context) { long posixTime = context.RequestData.ReadInt64(); @@ -204,19 +194,15 @@ namespace Ryujinx.HLE.HOS.Services.Time return ToCalendarTimeWithTz(context, posixTime, _timeZone); } + // ToPosixTime(nn::time::CalendarTime calendarTime, buffer rules) -> (u32 outCount, buffer) public long ToPosixTime(ServiceCtx context) { long bufferPosition = context.Request.SendBuff[0].Position; long bufferSize = context.Request.SendBuff[0].Size; - ushort year = context.RequestData.ReadUInt16(); - byte month = context.RequestData.ReadByte(); - byte day = context.RequestData.ReadByte(); - byte hour = context.RequestData.ReadByte(); - byte minute = context.RequestData.ReadByte(); - byte second = context.RequestData.ReadByte(); + CalendarTime calendarTime = context.RequestData.ReadStruct(); - DateTime calendarTime = new DateTime(year, month, day, hour, minute, second); + DateTime dateTime = new DateTime(calendarTime.year, calendarTime.month, calendarTime.day, calendarTime.hour, calendarTime.minute, calendarTime.second, DateTimeKind.Local); if (bufferSize != 0x4000) { @@ -235,30 +221,26 @@ namespace Ryujinx.HLE.HOS.Services.Time { TimeZoneInfo info = TimeZoneInfo.FindSystemTimeZoneById(tzId); - return ToPosixTimeWithTz(context, calendarTime, info); + return ToPosixTimeWithTz(context, dateTime, info); } catch (TimeZoneNotFoundException) { Logger.PrintWarning(LogClass.ServiceTime, $"Timezone not found for string: {tzId} (len: {tzId.Length})"); - resultCode = MakeError(ErrorModule.Time, 0x3dd); + resultCode = MakeError(ErrorModule.Time, TimeError.TimeZoneNotFound); } return resultCode; } + // ToPosixTimeWithMyRule(nn::time::CalendarTime calendarTime) -> (u32 outCount, buffer) public long ToPosixTimeWithMyRule(ServiceCtx context) { - ushort year = context.RequestData.ReadUInt16(); - byte month = context.RequestData.ReadByte(); - byte day = context.RequestData.ReadByte(); - byte hour = context.RequestData.ReadByte(); - byte minute = context.RequestData.ReadByte(); - byte second = context.RequestData.ReadByte(); + CalendarTime calendarTime = context.RequestData.ReadStruct(); - DateTime calendarTime = new DateTime(year, month, day, hour, minute, second, DateTimeKind.Local); + DateTime dateTime = new DateTime(calendarTime.year, calendarTime.month, calendarTime.day, calendarTime.hour, calendarTime.minute, calendarTime.second, DateTimeKind.Local); - return ToPosixTimeWithTz(context, calendarTime, _timeZone); + return ToPosixTimeWithTz(context, dateTime, _timeZone); } private long ToPosixTimeWithTz(ServiceCtx context, DateTime calendarTime, TimeZoneInfo info) @@ -276,5 +258,37 @@ namespace Ryujinx.HLE.HOS.Services.Time return 0; } + + private long ToCalendarTimeWithTz(ServiceCtx context, long posixTime, TimeZoneInfo info) + { + DateTime currentTime = Epoch.AddSeconds(posixTime); + + currentTime = TimeZoneInfo.ConvertTimeFromUtc(currentTime, info); + + CalendarInfo calendar = new CalendarInfo() + { + time = new CalendarTime() + { + year = (short)currentTime.Year, + month = (sbyte)currentTime.Month, + day = (sbyte)currentTime.Day, + hour = (sbyte)currentTime.Hour, + minute = (sbyte)currentTime.Minute, + second = (sbyte)currentTime.Second, + }, + additionalInfo = new CalendarAdditionalInfo() + { + dayOfWeek = (uint)currentTime.DayOfWeek, + dayOfYear = (uint)(currentTime.DayOfYear - 1), + isDaySavingTime = currentTime.IsDaylightSavingTime(), + gmtOffset = info.GetUtcOffset(currentTime).Seconds, + } + + }; + + context.ResponseData.WriteStruct(calendar); + + return 0; + } } } diff --git a/Ryujinx.HLE/HOS/Services/Time/ITimeZoneServiceTypes.cs b/Ryujinx.HLE/HOS/Services/Time/ITimeZoneServiceTypes.cs new file mode 100644 index 0000000000..5bf225d625 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Time/ITimeZoneServiceTypes.cs @@ -0,0 +1,128 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Time +{ + [StructLayout(LayoutKind.Sequential, Size = 0x10, Pack = 4)] + public struct TimeTypeInfo + { + public int gmtOffset; + + [MarshalAs(UnmanagedType.I1)] + public bool isDaySavingTime; + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 3)] + char[] padding1; + + public int abbreviationListIndex; + + [MarshalAs(UnmanagedType.I1)] + public bool isStandardTimeDaylight; + + [MarshalAs(UnmanagedType.I1)] + public bool isGMT; + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 2)] + char[] padding2; + } + + [StructLayout(LayoutKind.Sequential, Pack = 4, Size = 0x4000, CharSet = CharSet.Ansi)] + public struct TimeZoneRule + { + public const int TZ_MAX_TYPES = 128; + public const int TZ_MAX_CHARS = 50; + public const int TZ_MAX_LEAPS = 50; + public const int TZ_MAX_TIMES = 1000; + public const int TZNAME_MAX = 255; + public const int TZ_NAME_MAX = 2 * (TZNAME_MAX + 1); + + public int timeCount; + public int typeCount; + public int charCount; + + [MarshalAs(UnmanagedType.I1)] + public bool goBack; + + [MarshalAs(UnmanagedType.I1)] + public bool goAhead; + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = TZ_MAX_TIMES)] + public long[] ats; + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = TZ_MAX_TIMES)] + public byte[] types; + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = TZ_MAX_TYPES)] + public TimeTypeInfo[] ttis; + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = TZ_NAME_MAX)] + public char[] chars; + + public int defaultType; + } + + [StructLayout(LayoutKind.Sequential, Pack = 0x4, Size = 0x2C)] + public struct TzifHeader + { + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)] + public char[] magic; + + public char version; + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 15)] + public byte[] reserved; + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)] + public byte[] ttisGMTCount; + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)] + public byte[] ttisSTDCount; + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)] + public byte[] leapCount; + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)] + public byte[] timeCount; + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)] + public byte[] typeCount; + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)] + public byte[] charCount; + } + + [StructLayout(LayoutKind.Sequential, Pack = 0x4, Size = 0x8)] + public struct CalendarTime + { + public short year; + public sbyte month; + public sbyte day; + public sbyte hour; + public sbyte minute; + public sbyte second; + } + + [StructLayout(LayoutKind.Sequential, Pack = 0x4, Size = 0x18, CharSet = CharSet.Ansi)] + public struct CalendarAdditionalInfo + { + public uint dayOfWeek; + public uint dayOfYear; + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)] + public char[] timezoneName; + + [MarshalAs(UnmanagedType.I1)] + public bool isDaySavingTime; + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 3)] + char[] padding; + + public int gmtOffset; + } + + [StructLayout(LayoutKind.Sequential, Pack = 0x4, Size = 0x20, CharSet = CharSet.Ansi)] + public struct CalendarInfo + { + public CalendarTime time; + public CalendarAdditionalInfo additionalInfo; + } +} diff --git a/Ryujinx.HLE/HOS/Services/Time/TimeError.cs b/Ryujinx.HLE/HOS/Services/Time/TimeError.cs new file mode 100644 index 0000000000..334cfa1287 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Time/TimeError.cs @@ -0,0 +1,11 @@ +namespace Ryujinx.HLE.HOS.Services.Time +{ + static class TimeError + { + public const int TimeNotFound = 200; + public const int Overflow = 201; + public const int OutOfRange = 902; + public const int TimeZoneConversionFailed = 903; + public const int TimeZoneNotFound = 989; + } +}