From 37bf02f0572ff5535695ffa9527b1e651d0a1b7d Mon Sep 17 00:00:00 2001 From: Thomas Guillemard Date: Fri, 13 Jul 2018 23:35:19 +0200 Subject: [PATCH 1/2] TimeZone implements cmd 0, 1, 2, 3, 4 and 100 (#250) The implementation of the TimezoneRule isn't matching hardware but doesn't need to be accurate (games are only passing the value) --- .../OsHle/Services/Time/ITimeZoneService.cs | 174 +++++++++++++++--- 1 file changed, 146 insertions(+), 28 deletions(-) diff --git a/Ryujinx.HLE/OsHle/Services/Time/ITimeZoneService.cs b/Ryujinx.HLE/OsHle/Services/Time/ITimeZoneService.cs index 39454d4335..a2206a126e 100644 --- a/Ryujinx.HLE/OsHle/Services/Time/ITimeZoneService.cs +++ b/Ryujinx.HLE/OsHle/Services/Time/ITimeZoneService.cs @@ -2,6 +2,7 @@ using Ryujinx.HLE.Logging; using Ryujinx.HLE.OsHle.Ipc; using System; using System.Collections.Generic; +using System.Text; namespace Ryujinx.HLE.OsHle.Services.Time { @@ -13,20 +14,31 @@ namespace Ryujinx.HLE.OsHle.Services.Time private static readonly DateTime Epoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc); + private TimeZoneInfo TimeZone = TimeZoneInfo.Local; + public ITimeZoneService() { m_Commands = new Dictionary() { - { 0, GetDeviceLocationName }, - { 101, ToCalendarTimeWithMyRule } + { 0, GetDeviceLocationName }, + { 1, SetDeviceLocationName }, + { 2, GetTotalLocationNameCount }, + { 3, LoadLocationNameList }, + { 4, LoadTimeZoneRule }, + { 100, ToCalendarTime }, + { 101, ToCalendarTimeWithMyRule } }; } public long GetDeviceLocationName(ServiceCtx Context) { - Context.Ns.Log.PrintStub(LogClass.ServiceTime, "Stubbed."); + char[] TzName = TimeZone.Id.ToCharArray(); - for (int Index = 0; Index < 0x24; Index++) + Context.ResponseData.Write(TzName); + + int Padding = 0x24 - TzName.Length; + + for (int Index = 0; Index < Padding; Index++) { Context.ResponseData.Write((byte)0); } @@ -34,11 +46,94 @@ namespace Ryujinx.HLE.OsHle.Services.Time return 0; } - public long ToCalendarTimeWithMyRule(ServiceCtx Context) + public long SetDeviceLocationName(ServiceCtx Context) { - long PosixTime = Context.RequestData.ReadInt64(); + byte[] LocationName = Context.RequestData.ReadBytes(0x24); + string TzID = Encoding.ASCII.GetString(LocationName).TrimEnd('\0'); - DateTime CurrentTime = Epoch.AddSeconds(PosixTime).ToLocalTime(); + long ResultCode = 0; + + try + { + TimeZone = TimeZoneInfo.FindSystemTimeZoneById(TzID); + } + catch (TimeZoneNotFoundException e) + { + ResultCode = 0x7BA74; + } + + return ResultCode; + } + + public long GetTotalLocationNameCount(ServiceCtx Context) + { + Context.ResponseData.Write(TimeZoneInfo.GetSystemTimeZones().Count); + + return 0; + } + + public long LoadLocationNameList(ServiceCtx Context) + { + long BufferPosition = Context.Response.SendBuff[0].Position; + long BufferSize = Context.Response.SendBuff[0].Size; + + int i = 0; + foreach (TimeZoneInfo info in TimeZoneInfo.GetSystemTimeZones()) + { + byte[] TzData = Encoding.ASCII.GetBytes(info.Id); + + Context.Memory.WriteBytes(BufferPosition + i, TzData); + + int Padding = 0x24 - TzData.Length; + + for (int Index = 0; Index < Padding; Index++) + { + Context.ResponseData.Write((byte)0); + } + + i += 0x24; + } + return 0; + } + + public long LoadTimeZoneRule(ServiceCtx Context) + { + long BufferPosition = Context.Request.ReceiveBuff[0].Position; + long BufferSize = Context.Request.ReceiveBuff[0].Size; + + if (BufferSize != 0x4000) + { + Context.Ns.Log.PrintWarning(LogClass.ServiceTime, $"TimeZoneRule buffer size is 0x{BufferSize:x} (expected 0x4000)"); + } + + long ResultCode = 0; + + byte[] LocationName = Context.RequestData.ReadBytes(0x24); + string TzID = Encoding.ASCII.GetString(LocationName).TrimEnd('\0'); + + // Check if the Time Zone exists, otherwise error out. + try + { + TimeZoneInfo Info = TimeZoneInfo.FindSystemTimeZoneById(TzID); + byte[] TzData = Encoding.ASCII.GetBytes(Info.Id); + + // FIXME: This is not in ANY cases accurate, but the games don't about the content of the buffer, they only pass it. + // TODO: Reverse the TZif2 conversion in PCV to make this match with real hardware. + Context.Memory.WriteBytes(BufferPosition, TzData); + } + catch (TimeZoneNotFoundException e) + { + Context.Ns.Log.PrintWarning(LogClass.ServiceTime, $"Timezone not found for string: {TzID} (len: {TzID.Length})"); + ResultCode = 0x7BA74; + } + + 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); @@ -46,31 +141,54 @@ namespace Ryujinx.HLE.OsHle.Services.Time Context.ResponseData.Write((byte)CurrentTime.Hour); Context.ResponseData.Write((byte)CurrentTime.Minute); Context.ResponseData.Write((byte)CurrentTime.Second); - Context.ResponseData.Write((byte)0); - - /* Thanks to TuxSH - struct CalendarAdditionalInfo { - u32 tm_wday; //day of week [0,6] (Sunday = 0) - s32 tm_yday; //day of year [0,365] - struct timezone { - char[8] tz_name; - bool isDaylightSavingTime; - s32 utcOffsetSeconds; - }; - }; - */ + Context.ResponseData.Write((byte)0); //MilliSecond ? Context.ResponseData.Write((int)CurrentTime.DayOfWeek); - Context.ResponseData.Write(CurrentTime.DayOfYear - 1); - - //TODO: Find out the names used. - Context.ResponseData.Write(new byte[8]); - + Context.ResponseData.Write(new byte[8]); //TODO: Find out the names used. Context.ResponseData.Write((byte)(CurrentTime.IsDaylightSavingTime() ? 1 : 0)); - - Context.ResponseData.Write((int)TimeZoneInfo.Local.GetUtcOffset(CurrentTime).TotalSeconds); + Context.ResponseData.Write((int)Info.GetUtcOffset(CurrentTime).TotalSeconds); return 0; } + + public long 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) + { + Context.Ns.Log.PrintWarning(LogClass.ServiceTime, $"TimeZoneRule buffer size is 0x{BufferSize:x} (expected 0x4000)"); + } + + // TODO: Reverse the TZif2 conversion in PCV to make this match with real hardware. + byte[] TzData = Context.Memory.ReadBytes(BufferPosition, 0x24); + string TzID = Encoding.ASCII.GetString(TzData).TrimEnd('\0'); + + long ResultCode = 0; + + // Check if the Time Zone exists, otherwise error out. + try + { + TimeZoneInfo Info = TimeZoneInfo.FindSystemTimeZoneById(TzID); + + ResultCode = ToCalendarTimeWithTz(Context, PosixTime, Info); + } + catch (TimeZoneNotFoundException e) + { + Context.Ns.Log.PrintWarning(LogClass.ServiceTime, $"Timezone not found for string: {TzID} (len: {TzID.Length})"); + ResultCode = 0x7BA74; + } + + return ResultCode; + } + + public long ToCalendarTimeWithMyRule(ServiceCtx Context) + { + long PosixTime = Context.RequestData.ReadInt64(); + + return ToCalendarTimeWithTz(Context, PosixTime, TimeZone); + } } -} \ No newline at end of file +} From 494f8f0248e7daf3fdfb89a6d90f1598232b6a87 Mon Sep 17 00:00:00 2001 From: Starlet Date: Fri, 13 Jul 2018 16:36:57 -0500 Subject: [PATCH 2/2] Implement CSRNG (Cryptographically Secure Random Bytes) (#216) * Implement CSRNG (Cryptographically Secure Random Bytes) * Compliant with review. * Dispose Rng --- Ryujinx.HLE/OsHle/Services/ServiceFactory.cs | 4 ++ .../OsHle/Services/Spl/IRandomInterface.cs | 50 +++++++++++++++++++ 2 files changed, 54 insertions(+) create mode 100644 Ryujinx.HLE/OsHle/Services/Spl/IRandomInterface.cs diff --git a/Ryujinx.HLE/OsHle/Services/ServiceFactory.cs b/Ryujinx.HLE/OsHle/Services/ServiceFactory.cs index 914c84490d..b69fc9f884 100644 --- a/Ryujinx.HLE/OsHle/Services/ServiceFactory.cs +++ b/Ryujinx.HLE/OsHle/Services/ServiceFactory.cs @@ -18,6 +18,7 @@ using Ryujinx.HLE.OsHle.Services.Prepo; using Ryujinx.HLE.OsHle.Services.Set; using Ryujinx.HLE.OsHle.Services.Sfdnsres; using Ryujinx.HLE.OsHle.Services.Sm; +using Ryujinx.HLE.OsHle.Services.Spl; using Ryujinx.HLE.OsHle.Services.Ssl; using Ryujinx.HLE.OsHle.Services.Vi; using System; @@ -66,6 +67,9 @@ namespace Ryujinx.HLE.OsHle.Services case "caps:ss": return new IScreenshotService(); + case "csrng": + return new IRandomInterface(); + case "friend:a": return new IServiceCreator(); diff --git a/Ryujinx.HLE/OsHle/Services/Spl/IRandomInterface.cs b/Ryujinx.HLE/OsHle/Services/Spl/IRandomInterface.cs new file mode 100644 index 0000000000..489ca52ce6 --- /dev/null +++ b/Ryujinx.HLE/OsHle/Services/Spl/IRandomInterface.cs @@ -0,0 +1,50 @@ +using Ryujinx.HLE.OsHle.Ipc; +using System; +using System.Collections.Generic; +using System.Security.Cryptography; + +namespace Ryujinx.HLE.OsHle.Services.Spl +{ + class IRandomInterface : IpcService, IDisposable + { + private Dictionary m_Commands; + + public override IReadOnlyDictionary Commands => m_Commands; + + private RNGCryptoServiceProvider Rng; + + public IRandomInterface() + { + m_Commands = new Dictionary() + { + { 0, GetRandomBytes } + }; + + Rng = new RNGCryptoServiceProvider(); + } + + public long GetRandomBytes(ServiceCtx Context) + { + byte[] RandomBytes = new byte[Context.Request.ReceiveBuff[0].Size]; + + Rng.GetBytes(RandomBytes); + + Context.Memory.WriteBytes(Context.Request.ReceiveBuff[0].Position, RandomBytes); + + return 0; + } + + public void Dispose() + { + Dispose(true); + } + + protected virtual void Dispose(bool Disposing) + { + if (Disposing) + { + Rng.Dispose(); + } + } + } +} \ No newline at end of file