Add accurate timezone logic
TOOD: LoadTimeZoneRule and location name cmds.
This commit is contained in:
parent
9aa4d2ba6b
commit
eff597e426
4 changed files with 1903 additions and 106 deletions
|
@ -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)
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
1716
Ryujinx.HLE/HOS/Services/Time/TimeZone/TimeZone.cs
Normal file
1716
Ryujinx.HLE/HOS/Services/Time/TimeZone/TimeZone.cs
Normal file
File diff suppressed because it is too large
Load diff
137
Ryujinx.HLE/HOS/Services/Time/TimeZone/TimeZoneManager.cs
Normal file
137
Ryujinx.HLE/HOS/Services/Time/TimeZone/TimeZoneManager.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Add table
Reference in a new issue