Add accurate timezone logic

TOOD: LoadTimeZoneRule and location name cmds.
This commit is contained in:
Thog 2019-07-01 23:08:20 +02:00
parent 9aa4d2ba6b
commit eff597e426
No known key found for this signature in database
GPG key ID: 0CD291558FAFDBC6
4 changed files with 1903 additions and 106 deletions

View file

@ -9,6 +9,7 @@ using Ryujinx.HLE.HOS.Kernel.Memory;
using Ryujinx.HLE.HOS.Kernel.Process;
using Ryujinx.HLE.HOS.Kernel.Threading;
using Ryujinx.HLE.HOS.Services.Sm;
using Ryujinx.HLE.HOS.Services.Time.TimeZone;
using Ryujinx.HLE.HOS.SystemState;
using Ryujinx.HLE.Loaders.Executables;
using Ryujinx.HLE.Loaders.Npdm;
@ -195,6 +196,8 @@ namespace Ryujinx.HLE.HOS
LoadKeySet();
ContentManager = new ContentManager(device);
TimeZoneManager.Instance.Initialize(ContentManager);
}
public void LoadCart(string exeFsDir, string romFsFile = null)

View file

@ -1,6 +1,8 @@
using ChocolArm64.Memory;
using Ryujinx.Common;
using Ryujinx.Common.Logging;
using Ryujinx.HLE.HOS.Ipc;
using Ryujinx.HLE.HOS.Services.Time.TimeZone;
using System;
using System.Collections.Generic;
using System.Text;
@ -120,31 +122,20 @@ namespace Ryujinx.HLE.HOS.Services.Time
if (bufferSize != 0x4000)
{
Logger.PrintWarning(LogClass.ServiceTime, $"TimeZoneRule buffer size is 0x{bufferSize:x} (expected 0x4000)");
// TODO: find error code here
Logger.PrintError(LogClass.ServiceTime, $"TimeZoneRule buffer size is 0x{bufferSize:x} (expected 0x4000)");
throw new InvalidOperationException();
}
long resultCode = 0;
byte[] locationName = context.RequestData.ReadBytes(0x24);
string locationName = Encoding.ASCII.GetString(context.RequestData.ReadBytes(0x24)).TrimEnd('\0');
string tzId = Encoding.ASCII.GetString(locationName).TrimEnd('\0');
// Check if the Time Zone exists, otherwise error out.
try
long resultCode = TimeZoneManager.Instance.LoadTimeZoneRules(out TimeZoneRule rules, locationName);
// Write TimeZoneRule if success
if (resultCode == 0)
{
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 care 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)
{
Logger.PrintWarning(LogClass.ServiceTime, $"Timezone not found for string: {tzId} (len: {tzId.Length})");
resultCode = MakeError(ErrorModule.Time, TimeError.TimeZoneNotFound);
MemoryHelper.Write(context.Memory, bufferPosition, rules);
}
return resultCode;
@ -159,28 +150,18 @@ namespace Ryujinx.HLE.HOS.Services.Time
if (bufferSize != 0x4000)
{
Logger.PrintWarning(LogClass.ServiceTime, $"TimeZoneRule buffer size is 0x{bufferSize:x} (expected 0x4000)");
// TODO: find error code here
Logger.PrintError(LogClass.ServiceTime, $"TimeZoneRule buffer size is 0x{bufferSize:x} (expected 0x4000)");
throw new InvalidOperationException();
}
// TODO: Reverse the TZif2 conversion in PCV to make this match with real hardware.
byte[] tzData = context.Memory.ReadBytes(bufferPosition, 0x24);
TimeZoneRule rules = MemoryHelper.Read<TimeZoneRule>(context.Memory, bufferPosition);
string tzId = Encoding.ASCII.GetString(tzData).TrimEnd('\0');
long resultCode = TimeZoneManager.ToCalendarTime(rules, posixTime, out CalendarInfo calendar);
long resultCode;
// Check if the Time Zone exists, otherwise error out.
try
if (resultCode == 0)
{
TimeZoneInfo info = TimeZoneInfo.FindSystemTimeZoneById(tzId);
resultCode = ToCalendarTimeWithTz(context, posixTime, info);
}
catch (TimeZoneNotFoundException)
{
Logger.PrintWarning(LogClass.ServiceTime, $"Timezone not found for string: {tzId} (len: {tzId.Length})");
resultCode = MakeError(ErrorModule.Time, TimeError.TimeZoneNotFound);
context.ResponseData.WriteStruct(calendar);
}
return resultCode;
@ -191,43 +172,42 @@ namespace Ryujinx.HLE.HOS.Services.Time
{
long posixTime = context.RequestData.ReadInt64();
return ToCalendarTimeWithTz(context, posixTime, _timeZone);
long resultCode = TimeZoneManager.Instance.ToCalendarTimeWithMyRules(posixTime, out CalendarInfo calendar);
if (resultCode == 0)
{
context.ResponseData.WriteStruct(calendar);
}
return resultCode;
}
// ToPosixTime(nn::time::CalendarTime calendarTime, buffer<nn::time::TimeZoneRule, 0x15> rules) -> (u32 outCount, buffer<nn::time::PosixTime, 0xa>)
public long ToPosixTime(ServiceCtx context)
{
long bufferPosition = context.Request.SendBuff[0].Position;
long bufferSize = context.Request.SendBuff[0].Size;
long inBufferPosition = context.Request.SendBuff[0].Position;
long inBufferSize = context.Request.SendBuff[0].Size;
CalendarTime calendarTime = context.RequestData.ReadStruct<CalendarTime>();
DateTime dateTime = new DateTime(calendarTime.year, calendarTime.month, calendarTime.day, calendarTime.hour, calendarTime.minute, calendarTime.second, DateTimeKind.Local);
if (bufferSize != 0x4000)
if (inBufferSize != 0x4000)
{
Logger.PrintWarning(LogClass.ServiceTime, $"TimeZoneRule buffer size is 0x{bufferSize:x} (expected 0x4000)");
// TODO: find error code here
Logger.PrintError(LogClass.ServiceTime, $"TimeZoneRule buffer size is 0x{inBufferSize:x} (expected 0x4000)");
throw new InvalidOperationException();
}
// TODO: Reverse the TZif2 conversion in PCV to make this match with real hardware.
byte[] tzData = context.Memory.ReadBytes(bufferPosition, 0x24);
TimeZoneRule rules = MemoryHelper.Read<TimeZoneRule>(context.Memory, inBufferPosition);
string tzId = Encoding.ASCII.GetString(tzData).TrimEnd('\0');
long resultCode = TimeZoneManager.ToPosixTime(rules, calendarTime, out long posixTime);
long resultCode = 0;
// Check if the Time Zone exists, otherwise error out.
try
if (resultCode == 0)
{
TimeZoneInfo info = TimeZoneInfo.FindSystemTimeZoneById(tzId);
long outBufferPosition = context.Request.RecvListBuff[0].Position;
long outBufferSize = context.Request.RecvListBuff[0].Size;
return ToPosixTimeWithTz(context, dateTime, info);
}
catch (TimeZoneNotFoundException)
{
Logger.PrintWarning(LogClass.ServiceTime, $"Timezone not found for string: {tzId} (len: {tzId.Length})");
resultCode = MakeError(ErrorModule.Time, TimeError.TimeZoneNotFound);
context.Memory.WriteInt64(outBufferPosition, posixTime);
context.ResponseData.Write(1);
}
return resultCode;
@ -238,57 +218,18 @@ namespace Ryujinx.HLE.HOS.Services.Time
{
CalendarTime calendarTime = context.RequestData.ReadStruct<CalendarTime>();
DateTime dateTime = new DateTime(calendarTime.year, calendarTime.month, calendarTime.day, calendarTime.hour, calendarTime.minute, calendarTime.second, DateTimeKind.Local);
long resultCode = TimeZoneManager.Instance.ToPosixTimeWithMyRules(calendarTime, out long posixTime);
return ToPosixTimeWithTz(context, dateTime, _timeZone);
}
private long ToPosixTimeWithTz(ServiceCtx context, DateTime calendarTime, TimeZoneInfo info)
{
DateTime calenderTimeUtc = TimeZoneInfo.ConvertTimeToUtc(calendarTime, info);
long posixTime = ((DateTimeOffset)calenderTimeUtc).ToUnixTimeSeconds();
long position = context.Request.RecvListBuff[0].Position;
long size = context.Request.RecvListBuff[0].Size;
context.Memory.WriteInt64(position, posixTime);
context.ResponseData.Write(1);
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()
if (resultCode == 0)
{
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,
}
long outBufferPosition = context.Request.RecvListBuff[0].Position;
long outBufferSize = context.Request.RecvListBuff[0].Size;
};
context.Memory.WriteInt64(outBufferPosition, posixTime);
context.ResponseData.Write(1);
}
context.ResponseData.WriteStruct(calendar);
return 0;
return resultCode;
}
}
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,137 @@
using LibHac.Fs.NcaUtils;
using Ryujinx.Common.Logging;
using Ryujinx.HLE.FileSystem;
using Ryujinx.HLE.FileSystem.Content;
using System;
using static Ryujinx.HLE.HOS.Services.Time.TimeZoneRule;
using static Ryujinx.HLE.HOS.ErrorCode;
namespace Ryujinx.HLE.HOS.Services.Time.TimeZone
{
public sealed class TimeZoneManager
{
private const long TimeZoneBinaryTitleId = 0x010000000000080E;
private static TimeZoneManager instance = null;
private static object instanceLock = new object();
private ContentManager _contentManager;
private TimeZoneRule _myRules;
TimeZoneManager()
{
_contentManager = null;
// Empty rules
_myRules = new TimeZoneRule
{
ats = new long[TZ_MAX_TIMES],
types = new byte[TZ_MAX_TIMES],
ttis = new TimeTypeInfo[TZ_MAX_TYPES],
chars = new char[TZ_NAME_MAX]
};
}
internal void Initialize(ContentManager contentManager)
{
_contentManager = contentManager;
}
public string GetTimeZoneBinaryTitleContentPath()
{
return _contentManager.GetInstalledContentPath(TimeZoneBinaryTitleId, StorageId.NandSystem, ContentType.Data);
}
public bool HasTimeZoneBinaryTitle()
{
return !string.IsNullOrEmpty(GetTimeZoneBinaryTitleContentPath());
}
public uint LoadTimeZoneRules(out TimeZoneRule outRules, string locationName)
{
outRules = new TimeZoneRule
{
ats = new long[TZ_MAX_TIMES],
types = new byte[TZ_MAX_TIMES],
ttis = new TimeTypeInfo[TZ_MAX_TYPES],
chars = new char[TZ_NAME_MAX]
};
if (!HasTimeZoneBinaryTitle())
{
Logger.PrintWarning(LogClass.ServiceTime, "TimeZoneBinary system archive not found! Time conversion might not be accurate!");
try
{
TimeZoneInfo info = TimeZoneInfo.FindSystemTimeZoneById(locationName);
// TODO convert TimeZoneInfo to a TimeZoneRule
throw new NotImplementedException();
}
catch (TimeZoneNotFoundException)
{
Logger.PrintWarning(LogClass.ServiceTime, $"Timezone not found for string: {locationName})");
return MakeError(ErrorModule.Time, TimeError.TimeZoneNotFound);
}
}
else
{
// TODO: system archive loading
throw new NotImplementedException();
}
}
public uint ToCalendarTimeWithMyRules(long time, out CalendarInfo calendar)
{
return ToCalendarTime(_myRules, time, out calendar);
}
public static uint ToCalendarTime(TimeZoneRule rules, long time, out CalendarInfo calendar)
{
int error = TimeZone.ToCalendarTime(rules, time, out calendar);
if (error != 0)
{
return MakeError(ErrorModule.Time, error);
}
return 0;
}
public uint ToPosixTimeWithMyRules(CalendarTime calendarTime, out long posixTime)
{
return ToPosixTime(_myRules, calendarTime, out posixTime);
}
public static uint ToPosixTime(TimeZoneRule rules, CalendarTime calendarTime, out long posixTime)
{
int error = TimeZone.ToPosixTime(rules, calendarTime, out posixTime);
if (error != 0)
{
return MakeError(ErrorModule.Time, error);
}
return 0;
}
public static TimeZoneManager Instance
{
get
{
lock (instanceLock)
{
if (instance == null)
{
instance = new TimeZoneManager();
}
return instance;
}
}
}
}
}