| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288 |
- using LibHac.Fs.NcaUtils;
- using Ryujinx.Common.Logging;
- using Ryujinx.HLE.FileSystem;
- 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;
- namespace Ryujinx.HLE.HOS.Services.Time.TimeZone
- {
- public sealed class TimeZoneManager
- {
- private const long TimeZoneBinaryTitleId = 0x010000000000080E;
- private static TimeZoneManager instance;
- private static object instanceLock = new object();
- private Switch _device;
- private TimeZoneRule _myRules;
- private string _deviceLocationName;
- private string[] _locationNameCache;
- public static TimeZoneManager Instance
- {
- get
- {
- lock (instanceLock)
- {
- if (instance == null)
- {
- instance = new TimeZoneManager();
- }
- return instance;
- }
- }
- }
- TimeZoneManager()
- {
- // Empty rules (UTC)
- _myRules = new TimeZoneRule
- {
- Ats = new long[TzMaxTimes],
- Types = new byte[TzMaxTimes],
- Ttis = new TimeTypeInfo[TzMaxTypes],
- Chars = new char[TzCharsArraySize]
- };
- _deviceLocationName = "UTC";
- }
- internal void Initialize(Switch device)
- {
- _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 ResultCode SetDeviceLocationName(string locationName)
- {
- ResultCode resultCode = LoadTimeZoneRules(out TimeZoneRule rules, locationName);
- if (resultCode == 0)
- {
- _myRules = rules;
- _deviceLocationName = locationName;
- }
- return resultCode;
- }
- public ResultCode 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 ResultCode.LocationNameTooLong;
- }
- locationNameList.Add(locationName);
- }
- outLocationNameArray = locationNameList.ToArray();
- return ResultCode.Success;
- }
- public uint GetTotalLocationNameCount()
- {
- return (uint)_locationNameCache.Length;
- }
- public string GetTimeZoneBinaryTitleContentPath()
- {
- return _device.System.ContentManager.GetInstalledContentPath(TimeZoneBinaryTitleId, StorageId.NandSystem, ContentType.Data);
- }
- public bool HasTimeZoneBinaryTitle()
- {
- return !string.IsNullOrEmpty(GetTimeZoneBinaryTitleContentPath());
- }
- internal ResultCode LoadTimeZoneRules(out TimeZoneRule outRules, string locationName)
- {
- outRules = new TimeZoneRule
- {
- Ats = new long[TzMaxTimes],
- Types = new byte[TzMaxTimes],
- Ttis = new TimeTypeInfo[TzMaxTypes],
- Chars = new char[TzCharsArraySize]
- };
- if (!IsLocationNameValid(locationName))
- {
- return ResultCode.TimeZoneNotFound;
- }
- if (!HasTimeZoneBinaryTitle())
- {
- // 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 = TZConvert.GetTimeZoneInfo(locationName);
- string posixRule = PosixTimeZone.FromTimeZoneInfo(info);
- if (!TimeZone.ParsePosixName(posixRule, out outRules))
- {
- return ResultCode.TimeZoneConversionFailed;
- }
- return 0;
- }
- catch (TimeZoneNotFoundException)
- {
- Logger.PrintWarning(LogClass.ServiceTime, $"Timezone not found for string: {locationName})");
- return ResultCode.TimeZoneNotFound;
- }
- }
- else
- {
- 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 ResultCode.TimeZoneConversionFailed;
- }
- }
- return 0;
- }
- }
- internal ResultCode ToCalendarTimeWithMyRules(long time, out CalendarInfo calendar)
- {
- return ToCalendarTime(_myRules, time, out calendar);
- }
- internal static ResultCode ToCalendarTime(TimeZoneRule rules, long time, out CalendarInfo calendar)
- {
- ResultCode error = TimeZone.ToCalendarTime(rules, time, out calendar);
- if (error != ResultCode.Success)
- {
- return error;
- }
- return ResultCode.Success;
- }
- internal ResultCode ToPosixTimeWithMyRules(CalendarTime calendarTime, out long posixTime)
- {
- return ToPosixTime(_myRules, calendarTime, out posixTime);
- }
- internal static ResultCode ToPosixTime(TimeZoneRule rules, CalendarTime calendarTime, out long posixTime)
- {
- ResultCode error = TimeZone.ToPosixTime(rules, calendarTime, out posixTime);
- if (error != ResultCode.Success)
- {
- return error;
- }
- return ResultCode.Success;
- }
- }
- }
|