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.Process;
|
||||||
using Ryujinx.HLE.HOS.Kernel.Threading;
|
using Ryujinx.HLE.HOS.Kernel.Threading;
|
||||||
using Ryujinx.HLE.HOS.Services.Sm;
|
using Ryujinx.HLE.HOS.Services.Sm;
|
||||||
|
using Ryujinx.HLE.HOS.Services.Time.TimeZone;
|
||||||
using Ryujinx.HLE.HOS.SystemState;
|
using Ryujinx.HLE.HOS.SystemState;
|
||||||
using Ryujinx.HLE.Loaders.Executables;
|
using Ryujinx.HLE.Loaders.Executables;
|
||||||
using Ryujinx.HLE.Loaders.Npdm;
|
using Ryujinx.HLE.Loaders.Npdm;
|
||||||
|
@ -195,6 +196,8 @@ namespace Ryujinx.HLE.HOS
|
||||||
LoadKeySet();
|
LoadKeySet();
|
||||||
|
|
||||||
ContentManager = new ContentManager(device);
|
ContentManager = new ContentManager(device);
|
||||||
|
|
||||||
|
TimeZoneManager.Instance.Initialize(ContentManager);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void LoadCart(string exeFsDir, string romFsFile = null)
|
public void LoadCart(string exeFsDir, string romFsFile = null)
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
|
using ChocolArm64.Memory;
|
||||||
using Ryujinx.Common;
|
using Ryujinx.Common;
|
||||||
using Ryujinx.Common.Logging;
|
using Ryujinx.Common.Logging;
|
||||||
using Ryujinx.HLE.HOS.Ipc;
|
using Ryujinx.HLE.HOS.Ipc;
|
||||||
|
using Ryujinx.HLE.HOS.Services.Time.TimeZone;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
@ -120,31 +122,20 @@ namespace Ryujinx.HLE.HOS.Services.Time
|
||||||
|
|
||||||
if (bufferSize != 0x4000)
|
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');
|
long resultCode = TimeZoneManager.Instance.LoadTimeZoneRules(out TimeZoneRule rules, locationName);
|
||||||
|
|
||||||
// Check if the Time Zone exists, otherwise error out.
|
// Write TimeZoneRule if success
|
||||||
try
|
if (resultCode == 0)
|
||||||
{
|
{
|
||||||
TimeZoneInfo info = TimeZoneInfo.FindSystemTimeZoneById(tzId);
|
MemoryHelper.Write(context.Memory, bufferPosition, rules);
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return resultCode;
|
return resultCode;
|
||||||
|
@ -159,28 +150,18 @@ namespace Ryujinx.HLE.HOS.Services.Time
|
||||||
|
|
||||||
if (bufferSize != 0x4000)
|
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.
|
TimeZoneRule rules = MemoryHelper.Read<TimeZoneRule>(context.Memory, bufferPosition);
|
||||||
byte[] tzData = context.Memory.ReadBytes(bufferPosition, 0x24);
|
|
||||||
|
|
||||||
string tzId = Encoding.ASCII.GetString(tzData).TrimEnd('\0');
|
long resultCode = TimeZoneManager.ToCalendarTime(rules, posixTime, out CalendarInfo calendar);
|
||||||
|
|
||||||
long resultCode;
|
if (resultCode == 0)
|
||||||
|
|
||||||
// Check if the Time Zone exists, otherwise error out.
|
|
||||||
try
|
|
||||||
{
|
{
|
||||||
TimeZoneInfo info = TimeZoneInfo.FindSystemTimeZoneById(tzId);
|
context.ResponseData.WriteStruct(calendar);
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return resultCode;
|
return resultCode;
|
||||||
|
@ -191,43 +172,42 @@ namespace Ryujinx.HLE.HOS.Services.Time
|
||||||
{
|
{
|
||||||
long posixTime = context.RequestData.ReadInt64();
|
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>)
|
// ToPosixTime(nn::time::CalendarTime calendarTime, buffer<nn::time::TimeZoneRule, 0x15> rules) -> (u32 outCount, buffer<nn::time::PosixTime, 0xa>)
|
||||||
public long ToPosixTime(ServiceCtx context)
|
public long ToPosixTime(ServiceCtx context)
|
||||||
{
|
{
|
||||||
long bufferPosition = context.Request.SendBuff[0].Position;
|
long inBufferPosition = context.Request.SendBuff[0].Position;
|
||||||
long bufferSize = context.Request.SendBuff[0].Size;
|
long inBufferSize = context.Request.SendBuff[0].Size;
|
||||||
|
|
||||||
CalendarTime calendarTime = context.RequestData.ReadStruct<CalendarTime>();
|
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 (inBufferSize != 0x4000)
|
||||||
|
|
||||||
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{inBufferSize:x} (expected 0x4000)");
|
||||||
|
throw new InvalidOperationException();
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Reverse the TZif2 conversion in PCV to make this match with real hardware.
|
TimeZoneRule rules = MemoryHelper.Read<TimeZoneRule>(context.Memory, inBufferPosition);
|
||||||
byte[] tzData = context.Memory.ReadBytes(bufferPosition, 0x24);
|
|
||||||
|
|
||||||
string tzId = Encoding.ASCII.GetString(tzData).TrimEnd('\0');
|
long resultCode = TimeZoneManager.ToPosixTime(rules, calendarTime, out long posixTime);
|
||||||
|
|
||||||
long resultCode = 0;
|
if (resultCode == 0)
|
||||||
|
|
||||||
// Check if the Time Zone exists, otherwise error out.
|
|
||||||
try
|
|
||||||
{
|
{
|
||||||
TimeZoneInfo info = TimeZoneInfo.FindSystemTimeZoneById(tzId);
|
long outBufferPosition = context.Request.RecvListBuff[0].Position;
|
||||||
|
long outBufferSize = context.Request.RecvListBuff[0].Size;
|
||||||
|
|
||||||
return ToPosixTimeWithTz(context, dateTime, info);
|
context.Memory.WriteInt64(outBufferPosition, posixTime);
|
||||||
}
|
context.ResponseData.Write(1);
|
||||||
catch (TimeZoneNotFoundException)
|
|
||||||
{
|
|
||||||
Logger.PrintWarning(LogClass.ServiceTime, $"Timezone not found for string: {tzId} (len: {tzId.Length})");
|
|
||||||
|
|
||||||
resultCode = MakeError(ErrorModule.Time, TimeError.TimeZoneNotFound);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return resultCode;
|
return resultCode;
|
||||||
|
@ -238,57 +218,18 @@ namespace Ryujinx.HLE.HOS.Services.Time
|
||||||
{
|
{
|
||||||
CalendarTime calendarTime = context.RequestData.ReadStruct<CalendarTime>();
|
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);
|
if (resultCode == 0)
|
||||||
}
|
|
||||||
|
|
||||||
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()
|
|
||||||
{
|
{
|
||||||
time = new CalendarTime()
|
long outBufferPosition = context.Request.RecvListBuff[0].Position;
|
||||||
{
|
long outBufferSize = context.Request.RecvListBuff[0].Size;
|
||||||
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.Memory.WriteInt64(outBufferPosition, posixTime);
|
||||||
|
context.ResponseData.Write(1);
|
||||||
|
}
|
||||||
|
|
||||||
context.ResponseData.WriteStruct(calendar);
|
return resultCode;
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
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
Add a link
Reference in a new issue