Integrate the new TimeZone logic
This commit is contained in:
parent
eff597e426
commit
90b62c6f31
7 changed files with 213 additions and 54 deletions
|
@ -1,5 +1,6 @@
|
||||||
using LibHac.Fs;
|
using LibHac.Fs;
|
||||||
using LibHac.Fs.NcaUtils;
|
using LibHac.Fs.NcaUtils;
|
||||||
|
using Ryujinx.HLE.HOS.Services.Time.TimeZone;
|
||||||
using Ryujinx.HLE.Utilities;
|
using Ryujinx.HLE.Utilities;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
@ -141,6 +142,8 @@ namespace Ryujinx.HLE.FileSystem.Content
|
||||||
_locationEntries.Add(storageId, locationList);
|
_locationEntries.Add(storageId, locationList);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TimeZoneManager.Instance.Initialize(_device);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void ClearEntry(long titleId, ContentType contentType, StorageId storageId)
|
public void ClearEntry(long titleId, ContentType contentType, StorageId storageId)
|
||||||
|
|
|
@ -9,7 +9,6 @@ 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;
|
||||||
|
@ -196,8 +195,6 @@ 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)
|
||||||
|
|
|
@ -17,10 +17,6 @@ namespace Ryujinx.HLE.HOS.Services.Time
|
||||||
|
|
||||||
public override IReadOnlyDictionary<int, ServiceProcessRequest> Commands => _commands;
|
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()
|
public ITimeZoneService()
|
||||||
{
|
{
|
||||||
_commands = new Dictionary<int, ServiceProcessRequest>
|
_commands = new Dictionary<int, ServiceProcessRequest>
|
||||||
|
@ -42,12 +38,17 @@ namespace Ryujinx.HLE.HOS.Services.Time
|
||||||
// GetDeviceLocationName() -> nn::time::LocationName
|
// GetDeviceLocationName() -> nn::time::LocationName
|
||||||
public long GetDeviceLocationName(ServiceCtx context)
|
public long GetDeviceLocationName(ServiceCtx context)
|
||||||
{
|
{
|
||||||
char[] tzName = _timeZone.Id.ToCharArray();
|
char[] tzName = TimeZoneManager.Instance.GetDeviceLocationName().ToCharArray();
|
||||||
|
|
||||||
context.ResponseData.Write(tzName);
|
|
||||||
|
|
||||||
int padding = 0x24 - tzName.Length;
|
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++)
|
for (int index = 0; index < padding; index++)
|
||||||
{
|
{
|
||||||
context.ResponseData.Write((byte)0);
|
context.ResponseData.Write((byte)0);
|
||||||
|
@ -59,28 +60,14 @@ namespace Ryujinx.HLE.HOS.Services.Time
|
||||||
// SetDeviceLocationName(nn::time::LocationName)
|
// SetDeviceLocationName(nn::time::LocationName)
|
||||||
public long SetDeviceLocationName(ServiceCtx context)
|
public long SetDeviceLocationName(ServiceCtx context)
|
||||||
{
|
{
|
||||||
byte[] locationName = context.RequestData.ReadBytes(0x24);
|
string locationName = Encoding.ASCII.GetString(context.RequestData.ReadBytes(0x24)).TrimEnd('\0');
|
||||||
|
return TimeZoneManager.Instance.SetDeviceLocationName(locationName);
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetTotalLocationNameCount() -> u32
|
// GetTotalLocationNameCount() -> u32
|
||||||
public long GetTotalLocationNameCount(ServiceCtx context)
|
public long GetTotalLocationNameCount(ServiceCtx context)
|
||||||
{
|
{
|
||||||
context.ResponseData.Write(TimeZoneInfo.GetSystemTimeZones().Count);
|
context.ResponseData.Write(TimeZoneManager.Instance.GetTotalLocationNameCount());
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
@ -90,28 +77,33 @@ namespace Ryujinx.HLE.HOS.Services.Time
|
||||||
{
|
{
|
||||||
// TODO: fix logic to use index
|
// TODO: fix logic to use index
|
||||||
uint index = context.RequestData.ReadUInt32();
|
uint index = context.RequestData.ReadUInt32();
|
||||||
long bufferPosition = context.Response.SendBuff[0].Position;
|
long bufferPosition = context.Request.ReceiveBuff[0].Position;
|
||||||
long bufferSize = context.Response.SendBuff[0].Size;
|
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);
|
foreach (string locationName in locationNameArray)
|
||||||
|
|
||||||
int padding = 0x24 - tzData.Length;
|
|
||||||
|
|
||||||
for (int i = 0; i < padding; i++)
|
|
||||||
{
|
{
|
||||||
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>
|
// LoadTimeZoneRule(nn::time::LocationName locationName) -> buffer<nn::time::TimeZoneRule, 0x16>
|
||||||
|
|
|
@ -4,6 +4,7 @@
|
||||||
{
|
{
|
||||||
public const int TimeNotFound = 200;
|
public const int TimeNotFound = 200;
|
||||||
public const int Overflow = 201;
|
public const int Overflow = 201;
|
||||||
|
public const int LocationNameTooLong = 801;
|
||||||
public const int OutOfRange = 902;
|
public const int OutOfRange = 902;
|
||||||
public const int TimeZoneConversionFailed = 903;
|
public const int TimeZoneConversionFailed = 903;
|
||||||
public const int TimeZoneNotFound = 989;
|
public const int TimeZoneNotFound = 989;
|
||||||
|
|
|
@ -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)
|
public static unsafe bool LoadTimeZoneRules(out TimeZoneRule outRules, Stream inputData)
|
||||||
{
|
{
|
||||||
outRules = new TimeZoneRule
|
outRules = new TimeZoneRule
|
||||||
|
|
|
@ -1,8 +1,13 @@
|
||||||
using LibHac.Fs.NcaUtils;
|
using LibHac.Fs.NcaUtils;
|
||||||
using Ryujinx.Common.Logging;
|
using Ryujinx.Common.Logging;
|
||||||
using Ryujinx.HLE.FileSystem;
|
using Ryujinx.HLE.FileSystem;
|
||||||
using Ryujinx.HLE.FileSystem.Content;
|
|
||||||
using System;
|
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.Services.Time.TimeZoneRule;
|
||||||
using static Ryujinx.HLE.HOS.ErrorCode;
|
using static Ryujinx.HLE.HOS.ErrorCode;
|
||||||
|
@ -18,14 +23,16 @@ namespace Ryujinx.HLE.HOS.Services.Time.TimeZone
|
||||||
private static object instanceLock = new object();
|
private static object instanceLock = new object();
|
||||||
|
|
||||||
|
|
||||||
private ContentManager _contentManager;
|
private Switch _device;
|
||||||
private TimeZoneRule _myRules;
|
private TimeZoneRule _myRules;
|
||||||
|
private string _deviceLocationName;
|
||||||
|
private string[] _locationNameCache;
|
||||||
|
|
||||||
TimeZoneManager()
|
TimeZoneManager()
|
||||||
{
|
{
|
||||||
_contentManager = null;
|
_device = null;
|
||||||
|
|
||||||
// Empty rules
|
// Empty rules (UTC)
|
||||||
_myRules = new TimeZoneRule
|
_myRules = new TimeZoneRule
|
||||||
{
|
{
|
||||||
ats = new long[TZ_MAX_TIMES],
|
ats = new long[TZ_MAX_TIMES],
|
||||||
|
@ -33,16 +40,139 @@ namespace Ryujinx.HLE.HOS.Services.Time.TimeZone
|
||||||
ttis = new TimeTypeInfo[TZ_MAX_TYPES],
|
ttis = new TimeTypeInfo[TZ_MAX_TYPES],
|
||||||
chars = new char[TZ_NAME_MAX]
|
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()
|
public string GetTimeZoneBinaryTitleContentPath()
|
||||||
{
|
{
|
||||||
return _contentManager.GetInstalledContentPath(TimeZoneBinaryTitleId, StorageId.NandSystem, ContentType.Data);
|
return _device.System.ContentManager.GetInstalledContentPath(TimeZoneBinaryTitleId, StorageId.NandSystem, ContentType.Data);
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool HasTimeZoneBinaryTitle()
|
public bool HasTimeZoneBinaryTitle()
|
||||||
|
@ -60,15 +190,27 @@ namespace Ryujinx.HLE.HOS.Services.Time.TimeZone
|
||||||
chars = new char[TZ_NAME_MAX]
|
chars = new char[TZ_NAME_MAX]
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (!IsLocationNameValid(locationName))
|
||||||
|
{
|
||||||
|
return MakeError(ErrorModule.Time, TimeError.TimeZoneNotFound);
|
||||||
|
}
|
||||||
|
|
||||||
if (!HasTimeZoneBinaryTitle())
|
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
|
try
|
||||||
{
|
{
|
||||||
TimeZoneInfo info = TimeZoneInfo.FindSystemTimeZoneById(locationName);
|
TimeZoneInfo info = TZConvert.GetTimeZoneInfo(locationName);
|
||||||
|
string posixRule = PosixTimeZone.FromTimeZoneInfo(info);
|
||||||
|
|
||||||
// TODO convert TimeZoneInfo to a TimeZoneRule
|
if (!TimeZone.ParsePosixName(posixRule, out outRules))
|
||||||
throw new NotImplementedException();
|
{
|
||||||
|
return MakeError(ErrorModule.Time, TimeError.TimeZoneConversionFailed);
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
catch (TimeZoneNotFoundException)
|
catch (TimeZoneNotFoundException)
|
||||||
{
|
{
|
||||||
|
@ -79,8 +221,20 @@ namespace Ryujinx.HLE.HOS.Services.Time.TimeZone
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// TODO: system archive loading
|
using (IStorage ncaFileStream = new LocalStorage(_device.FileSystem.SwitchPathToSystemPath(GetTimeZoneBinaryTitleContentPath()), FileAccess.Read, FileMode.Open))
|
||||||
throw new NotImplementedException();
|
{
|
||||||
|
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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -47,6 +47,7 @@
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Concentus" Version="1.1.7" />
|
<PackageReference Include="Concentus" Version="1.1.7" />
|
||||||
<PackageReference Include="LibHac" Version="0.4.1" />
|
<PackageReference Include="LibHac" Version="0.4.1" />
|
||||||
|
<PackageReference Include="TimeZoneConverter.Posix" Version="2.1.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue