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.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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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>
|
||||
|
|
Loading…
Add table
Reference in a new issue