Integrate the new TimeZone logic

This commit is contained in:
Thog 2019-07-03 00:22:07 +02:00
parent eff597e426
commit 90b62c6f31
No known key found for this signature in database
GPG key ID: 0CD291558FAFDBC6
7 changed files with 213 additions and 54 deletions

View file

@ -1,5 +1,6 @@
using LibHac.Fs;
using LibHac.Fs.NcaUtils;
using Ryujinx.HLE.HOS.Services.Time.TimeZone;
using Ryujinx.HLE.Utilities;
using System;
using System.Collections.Generic;
@ -141,6 +142,8 @@ namespace Ryujinx.HLE.FileSystem.Content
_locationEntries.Add(storageId, locationList);
}
}
TimeZoneManager.Instance.Initialize(_device);
}
public void ClearEntry(long titleId, ContentType contentType, StorageId storageId)

View file

@ -9,7 +9,6 @@ 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;
@ -196,8 +195,6 @@ namespace Ryujinx.HLE.HOS
LoadKeySet();
ContentManager = new ContentManager(device);
TimeZoneManager.Instance.Initialize(ContentManager);
}
public void LoadCart(string exeFsDir, string romFsFile = null)

View file

@ -17,10 +17,6 @@ namespace Ryujinx.HLE.HOS.Services.Time
public override IReadOnlyDictionary<int, ServiceProcessRequest> Commands => _commands;
private static readonly DateTime Epoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
private TimeZoneInfo _timeZone = TimeZoneInfo.Local;
public ITimeZoneService()
{
_commands = new Dictionary<int, ServiceProcessRequest>
@ -42,12 +38,17 @@ namespace Ryujinx.HLE.HOS.Services.Time
// GetDeviceLocationName() -> nn::time::LocationName
public long GetDeviceLocationName(ServiceCtx context)
{
char[] tzName = _timeZone.Id.ToCharArray();
context.ResponseData.Write(tzName);
char[] tzName = TimeZoneManager.Instance.GetDeviceLocationName().ToCharArray();
int padding = 0x24 - tzName.Length;
if (padding < 0)
{
return MakeError(ErrorModule.Time, TimeError.LocationNameTooLong);
}
context.ResponseData.Write(tzName);
for (int index = 0; index < padding; index++)
{
context.ResponseData.Write((byte)0);
@ -59,28 +60,14 @@ namespace Ryujinx.HLE.HOS.Services.Time
// SetDeviceLocationName(nn::time::LocationName)
public long SetDeviceLocationName(ServiceCtx context)
{
byte[] locationName = context.RequestData.ReadBytes(0x24);
string tzId = Encoding.ASCII.GetString(locationName).TrimEnd('\0');
long resultCode = 0;
try
{
_timeZone = TimeZoneInfo.FindSystemTimeZoneById(tzId);
}
catch (TimeZoneNotFoundException)
{
resultCode = MakeError(ErrorModule.Time, TimeError.TimeZoneNotFound);
}
return resultCode;
string locationName = Encoding.ASCII.GetString(context.RequestData.ReadBytes(0x24)).TrimEnd('\0');
return TimeZoneManager.Instance.SetDeviceLocationName(locationName);
}
// GetTotalLocationNameCount() -> u32
public long GetTotalLocationNameCount(ServiceCtx context)
{
context.ResponseData.Write(TimeZoneInfo.GetSystemTimeZones().Count);
context.ResponseData.Write(TimeZoneManager.Instance.GetTotalLocationNameCount());
return 0;
}
@ -90,28 +77,33 @@ namespace Ryujinx.HLE.HOS.Services.Time
{
// 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;
long bufferPosition = context.Request.ReceiveBuff[0].Position;
long bufferSize = context.Request.ReceiveBuff[0].Size;
int offset = 0;
uint errorCode = TimeZoneManager.Instance.LoadLocationNameList(index, out string[] locationNameArray, (uint)bufferSize / 0x24);
foreach (TimeZoneInfo info in TimeZoneInfo.GetSystemTimeZones())
if (errorCode == 0)
{
byte[] tzData = Encoding.ASCII.GetBytes(info.Id);
uint offset = 0;
context.Memory.WriteBytes(bufferPosition + offset, tzData);
int padding = 0x24 - tzData.Length;
for (int i = 0; i < padding; i++)
foreach (string locationName in locationNameArray)
{
context.ResponseData.Write((byte)0);
int padding = 0x24 - locationName.Length;
if (padding < 0)
{
return MakeError(ErrorModule.Time, TimeError.LocationNameTooLong);
}
context.Memory.WriteBytes(bufferPosition + offset, Encoding.ASCII.GetBytes(locationName));
MemoryHelper.FillWithZeros(context.Memory, bufferPosition + offset + locationName.Length, padding);
offset += 0x24;
}
offset += 0x24;
context.ResponseData.Write((uint)locationNameArray.Length);
}
return 0;
return errorCode;
}
// LoadTimeZoneRule(nn::time::LocationName locationName) -> buffer<nn::time::TimeZoneRule, 0x16>

View file

@ -4,6 +4,7 @@
{
public const int TimeNotFound = 200;
public const int Overflow = 201;
public const int LocationNameTooLong = 801;
public const int OutOfRange = 902;
public const int TimeZoneConversionFailed = 903;
public const int TimeZoneNotFound = 989;

View file

@ -880,6 +880,17 @@ namespace Ryujinx.HLE.HOS.Services.Time.TimeZone
}
}
public static bool ParsePosixName(string name, out TimeZoneRule outRules)
{
unsafe
{
fixed (char *namePtr = name.ToCharArray())
{
return ParsePosixName(namePtr, out outRules, false);
}
}
}
public static unsafe bool LoadTimeZoneRules(out TimeZoneRule outRules, Stream inputData)
{
outRules = new TimeZoneRule

View file

@ -1,8 +1,13 @@
using LibHac.Fs.NcaUtils;
using Ryujinx.Common.Logging;
using Ryujinx.HLE.FileSystem;
using Ryujinx.HLE.FileSystem.Content;
using System;
using System.Collections.ObjectModel;
using LibHac.Fs;
using System.IO;
using System.Collections.Generic;
using TimeZoneConverter.Posix;
using TimeZoneConverter;
using static Ryujinx.HLE.HOS.Services.Time.TimeZoneRule;
using static Ryujinx.HLE.HOS.ErrorCode;
@ -18,14 +23,16 @@ namespace Ryujinx.HLE.HOS.Services.Time.TimeZone
private static object instanceLock = new object();
private ContentManager _contentManager;
private TimeZoneRule _myRules;
private Switch _device;
private TimeZoneRule _myRules;
private string _deviceLocationName;
private string[] _locationNameCache;
TimeZoneManager()
{
_contentManager = null;
_device = null;
// Empty rules
// Empty rules (UTC)
_myRules = new TimeZoneRule
{
ats = new long[TZ_MAX_TIMES],
@ -33,16 +40,139 @@ namespace Ryujinx.HLE.HOS.Services.Time.TimeZone
ttis = new TimeTypeInfo[TZ_MAX_TYPES],
chars = new char[TZ_NAME_MAX]
};
_deviceLocationName = "UTC";
}
internal void Initialize(ContentManager contentManager)
internal void Initialize(Switch device)
{
_contentManager = contentManager;
_device = device;
InitializeLocationNameCache();
}
private void InitializeLocationNameCache()
{
if (HasTimeZoneBinaryTitle())
{
using (IStorage ncaFileStream = new LocalStorage(_device.FileSystem.SwitchPathToSystemPath(GetTimeZoneBinaryTitleContentPath()), FileAccess.Read, FileMode.Open))
{
Nca nca = new Nca(_device.System.KeySet, ncaFileStream);
IFileSystem romfs = nca.OpenFileSystem(NcaSectionType.Data, _device.System.FsIntegrityCheckLevel);
Stream binaryListStream = romfs.OpenFile("binaryList.txt", OpenMode.Read).AsStream();
StreamReader reader = new StreamReader(binaryListStream);
List<string> locationNameList = new List<string>();
string locationName;
while ((locationName = reader.ReadLine()) != null)
{
locationNameList.Add(locationName);
}
_locationNameCache = locationNameList.ToArray();
}
}
else
{
ReadOnlyCollection<TimeZoneInfo> timeZoneInfos = TimeZoneInfo.GetSystemTimeZones();
_locationNameCache = new string[timeZoneInfos.Count];
int i = 0;
foreach (TimeZoneInfo timeZoneInfo in timeZoneInfos)
{
bool needConversion = TZConvert.TryWindowsToIana(timeZoneInfo.Id, out string convertedName);
if (needConversion)
{
_locationNameCache[i] = convertedName;
}
else
{
_locationNameCache[i] = timeZoneInfo.Id;
}
i++;
}
// As we aren't using the system archive, "UTC" might not exist on the host system.
// Load from C# TimeZone APIs UTC id.
string utcId = TimeZoneInfo.Utc.Id;
bool utcNeedConversion = TZConvert.TryWindowsToIana(utcId, out string utcConvertedName);
if (utcNeedConversion)
{
utcId = utcConvertedName;
}
_deviceLocationName = utcId;
}
}
private bool IsLocationNameValid(string locationName)
{
foreach (string cachedLocationName in _locationNameCache)
{
if (cachedLocationName.Equals(locationName))
{
return true;
}
}
return false;
}
public string GetDeviceLocationName()
{
return _deviceLocationName;
}
public uint SetDeviceLocationName(string locationName)
{
uint resultCode = LoadTimeZoneRules(out TimeZoneRule rules, locationName);
if (resultCode == 0)
{
_myRules = rules;
_deviceLocationName = locationName;
}
return resultCode;
}
public uint LoadLocationNameList(uint index, out string[] outLocationNameArray, uint maxLength)
{
List<string> locationNameList = new List<string>();
for (int i = 0; i < _locationNameCache.Length && i < maxLength; i++)
{
if (i < index)
{
continue;
}
string locationName = _locationNameCache[i];
// If the location name is too long, error out.
if (locationName.Length > 0x24)
{
outLocationNameArray = new string[0];
return MakeError(ErrorModule.Time, TimeError.LocationNameTooLong);
}
locationNameList.Add(locationName);
}
outLocationNameArray = locationNameList.ToArray();
return 0;
}
public uint GetTotalLocationNameCount()
{
return (uint)_locationNameCache.Length;
}
public string GetTimeZoneBinaryTitleContentPath()
{
return _contentManager.GetInstalledContentPath(TimeZoneBinaryTitleId, StorageId.NandSystem, ContentType.Data);
return _device.System.ContentManager.GetInstalledContentPath(TimeZoneBinaryTitleId, StorageId.NandSystem, ContentType.Data);
}
public bool HasTimeZoneBinaryTitle()
@ -60,15 +190,27 @@ namespace Ryujinx.HLE.HOS.Services.Time.TimeZone
chars = new char[TZ_NAME_MAX]
};
if (!IsLocationNameValid(locationName))
{
return MakeError(ErrorModule.Time, TimeError.TimeZoneNotFound);
}
if (!HasTimeZoneBinaryTitle())
{
Logger.PrintWarning(LogClass.ServiceTime, "TimeZoneBinary system archive not found! Time conversion might not be accurate!");
// If the user doesn't have the system archives, we generate a POSIX rule string and parse it to generate a incomplete TimeZoneRule
// TODO: As for now not having system archives is fine, we should enforce the usage of system archives later.
Logger.PrintWarning(LogClass.ServiceTime, "TimeZoneBinary system archive not found! Time conversions will not be accurate!");
try
{
TimeZoneInfo info = TimeZoneInfo.FindSystemTimeZoneById(locationName);
TimeZoneInfo info = TZConvert.GetTimeZoneInfo(locationName);
string posixRule = PosixTimeZone.FromTimeZoneInfo(info);
// TODO convert TimeZoneInfo to a TimeZoneRule
throw new NotImplementedException();
if (!TimeZone.ParsePosixName(posixRule, out outRules))
{
return MakeError(ErrorModule.Time, TimeError.TimeZoneConversionFailed);
}
return 0;
}
catch (TimeZoneNotFoundException)
{
@ -79,8 +221,20 @@ namespace Ryujinx.HLE.HOS.Services.Time.TimeZone
}
else
{
// TODO: system archive loading
throw new NotImplementedException();
using (IStorage ncaFileStream = new LocalStorage(_device.FileSystem.SwitchPathToSystemPath(GetTimeZoneBinaryTitleContentPath()), FileAccess.Read, FileMode.Open))
{
Nca nca = new Nca(_device.System.KeySet, ncaFileStream);
IFileSystem romfs = nca.OpenFileSystem(NcaSectionType.Data, _device.System.FsIntegrityCheckLevel);
Stream tzIfStream = romfs.OpenFile($"zoneinfo/{locationName}", OpenMode.Read).AsStream();
if (!TimeZone.LoadTimeZoneRules(out outRules, tzIfStream))
{
return MakeError(ErrorModule.Time, TimeError.TimeZoneConversionFailed);
}
}
return 0;
}
}

View file

@ -47,6 +47,7 @@
<ItemGroup>
<PackageReference Include="Concentus" Version="1.1.7" />
<PackageReference Include="LibHac" Version="0.4.1" />
<PackageReference Include="TimeZoneConverter.Posix" Version="2.1.0" />
</ItemGroup>
</Project>