| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303 |
- using LibHac;
- using LibHac.Common;
- using LibHac.Fs;
- using LibHac.FsSystem;
- using LibHac.FsSystem.NcaUtils;
- using Ryujinx.Common.Logging;
- using Ryujinx.Configuration;
- using Ryujinx.HLE.Exceptions;
- using Ryujinx.HLE.FileSystem;
- using Ryujinx.HLE.FileSystem.Content;
- using Ryujinx.HLE.HOS.Services.Time.Clock;
- using Ryujinx.HLE.Utilities;
- using System;
- using System.Collections.Generic;
- using System.IO;
- using static Ryujinx.HLE.HOS.Services.Time.TimeZone.TimeZoneRule;
- namespace Ryujinx.HLE.HOS.Services.Time.TimeZone
- {
- public class TimeZoneContentManager
- {
- private const long TimeZoneBinaryTitleId = 0x010000000000080E;
- private readonly string TimeZoneSystemTitleMissingErrorMessage = "TimeZoneBinary system title not found! TimeZone conversions will not work, provide the system archive to fix this error. (See https://github.com/Ryujinx/Ryujinx#requirements for more information)";
- private VirtualFileSystem _virtualFileSystem;
- private IntegrityCheckLevel _fsIntegrityCheckLevel;
- private ContentManager _contentManager;
- public string[] LocationNameCache { get; private set; }
- internal TimeZoneManager Manager { get; private set; }
- public TimeZoneContentManager()
- {
- Manager = new TimeZoneManager();
- }
- public void InitializeInstance(VirtualFileSystem virtualFileSystem, ContentManager contentManager, IntegrityCheckLevel fsIntegrityCheckLevel)
- {
- _virtualFileSystem = virtualFileSystem;
- _contentManager = contentManager;
- _fsIntegrityCheckLevel = fsIntegrityCheckLevel;
- InitializeLocationNameCache();
- }
- public string SanityCheckDeviceLocationName()
- {
- string locationName = ConfigurationState.Instance.System.TimeZone;
- if (IsLocationNameValid(locationName))
- {
- return locationName;
- }
- Logger.PrintWarning(LogClass.ServiceTime, $"Invalid device TimeZone {locationName}, switching back to UTC");
- ConfigurationState.Instance.System.TimeZone.Value = "UTC";
- return "UTC";
- }
- internal void Initialize(TimeManager timeManager, Switch device)
- {
- InitializeInstance(device.FileSystem, device.System.ContentManager, device.System.FsIntegrityCheckLevel);
- SteadyClockTimePoint timeZoneUpdatedTimePoint = timeManager.StandardSteadyClock.GetCurrentTimePoint(null);
- string deviceLocationName = SanityCheckDeviceLocationName();
- ResultCode result = GetTimeZoneBinary(deviceLocationName, out Stream timeZoneBinaryStream, out LocalStorage ncaFile);
- if (result == ResultCode.Success)
- {
- // TODO: Read TimeZoneVersion from sysarchive.
- timeManager.SetupTimeZoneManager(deviceLocationName, timeZoneUpdatedTimePoint, (uint)LocationNameCache.Length, new UInt128(), timeZoneBinaryStream);
- ncaFile.Dispose();
- }
- else
- {
- // In the case the user don't have the timezone system archive, we just mark the manager as initialized.
- Manager.MarkInitialized();
- }
- }
- private void InitializeLocationNameCache()
- {
- if (HasTimeZoneBinaryTitle())
- {
- using (IStorage ncaFileStream = new LocalStorage(_virtualFileSystem.SwitchPathToSystemPath(GetTimeZoneBinaryTitleContentPath()), FileAccess.Read, FileMode.Open))
- {
- Nca nca = new Nca(_virtualFileSystem.KeySet, ncaFileStream);
- IFileSystem romfs = nca.OpenFileSystem(NcaSectionType.Data, _fsIntegrityCheckLevel);
- romfs.OpenFile(out IFile binaryListFile, "/binaryList.txt".ToU8Span(), OpenMode.Read).ThrowIfFailure();
- StreamReader reader = new StreamReader(binaryListFile.AsStream());
- List<string> locationNameList = new List<string>();
- string locationName;
- while ((locationName = reader.ReadLine()) != null)
- {
- locationNameList.Add(locationName);
- }
- LocationNameCache = locationNameList.ToArray();
- }
- }
- else
- {
- LocationNameCache = new string[] { "UTC" };
- Logger.PrintError(LogClass.ServiceTime, TimeZoneSystemTitleMissingErrorMessage);
- }
- }
- public IEnumerable<(int Offset, string Location, string Abbr)> ParseTzOffsets()
- {
- var tzBinaryContentPath = GetTimeZoneBinaryTitleContentPath();
- if (string.IsNullOrEmpty(tzBinaryContentPath))
- {
- return new[] { (0, "UTC", "UTC") };
- }
- List<(int Offset, string Location, string Abbr)> outList = new List<(int Offset, string Location, string Abbr)>();
- var now = System.DateTimeOffset.Now.ToUnixTimeSeconds();
- using (IStorage ncaStorage = new LocalStorage(_virtualFileSystem.SwitchPathToSystemPath(tzBinaryContentPath), FileAccess.Read, FileMode.Open))
- using (IFileSystem romfs = new Nca(_virtualFileSystem.KeySet, ncaStorage).OpenFileSystem(NcaSectionType.Data, _fsIntegrityCheckLevel))
- {
- foreach (string locName in LocationNameCache)
- {
- if (locName.StartsWith("Etc"))
- {
- continue;
- }
- if (romfs.OpenFile(out IFile tzif, $"/zoneinfo/{locName}".ToU8Span(), OpenMode.Read).IsFailure())
- {
- Logger.PrintError(LogClass.ServiceTime, $"Error opening /zoneinfo/{locName}");
- continue;
- }
- using (tzif)
- {
- TimeZone.ParseTimeZoneBinary(out TimeZoneRule tzRule, tzif.AsStream());
- TimeTypeInfo ttInfo;
- if (tzRule.TimeCount > 0) // Find the current transition period
- {
- int fin = 0;
- for (int i = 0; i < tzRule.TimeCount; ++i)
- {
- if (tzRule.Ats[i] <= now)
- {
- fin = i;
- }
- }
- ttInfo = tzRule.Ttis[tzRule.Types[fin]];
- }
- else if (tzRule.TypeCount >= 1) // Otherwise, use the first offset in TTInfo
- {
- ttInfo = tzRule.Ttis[0];
- }
- else
- {
- Logger.PrintError(LogClass.ServiceTime, $"Couldn't find UTC offset for zone {locName}");
- continue;
- }
- var abbrStart = tzRule.Chars.AsSpan(ttInfo.AbbreviationListIndex);
- int abbrEnd = abbrStart.IndexOf('\0');
- outList.Add((ttInfo.GmtOffset, locName, abbrStart.Slice(0, abbrEnd).ToString()));
- }
- }
- }
- outList.Sort();
- return outList;
- }
- private bool IsLocationNameValid(string locationName)
- {
- foreach (string cachedLocationName in LocationNameCache)
- {
- if (cachedLocationName.Equals(locationName))
- {
- return true;
- }
- }
- return false;
- }
- public ResultCode SetDeviceLocationName(string locationName)
- {
- ResultCode result = GetTimeZoneBinary(locationName, out Stream timeZoneBinaryStream, out LocalStorage ncaFile);
- if (result == ResultCode.Success)
- {
- result = Manager.SetDeviceLocationNameWithTimeZoneRule(locationName, timeZoneBinaryStream);
- ncaFile.Dispose();
- }
- return result;
- }
- 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 string GetTimeZoneBinaryTitleContentPath()
- {
- return _contentManager.GetInstalledContentPath(TimeZoneBinaryTitleId, StorageId.NandSystem, NcaContentType.Data);
- }
- public bool HasTimeZoneBinaryTitle()
- {
- return !string.IsNullOrEmpty(GetTimeZoneBinaryTitleContentPath());
- }
- internal ResultCode GetTimeZoneBinary(string locationName, out Stream timeZoneBinaryStream, out LocalStorage ncaFile)
- {
- timeZoneBinaryStream = null;
- ncaFile = null;
- if (!HasTimeZoneBinaryTitle() || !IsLocationNameValid(locationName))
- {
- return ResultCode.TimeZoneNotFound;
- }
- ncaFile = new LocalStorage(_virtualFileSystem.SwitchPathToSystemPath(GetTimeZoneBinaryTitleContentPath()), FileAccess.Read, FileMode.Open);
- Nca nca = new Nca(_virtualFileSystem.KeySet, ncaFile);
- IFileSystem romfs = nca.OpenFileSystem(NcaSectionType.Data, _fsIntegrityCheckLevel);
- Result result = romfs.OpenFile(out IFile timeZoneBinaryFile, $"/zoneinfo/{locationName}".ToU8Span(), OpenMode.Read);
- timeZoneBinaryStream = timeZoneBinaryFile.AsStream();
- return (ResultCode)result.Value;
- }
- internal ResultCode LoadTimeZoneRule(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 (!HasTimeZoneBinaryTitle())
- {
- throw new InvalidSystemResourceException(TimeZoneSystemTitleMissingErrorMessage);
- }
- ResultCode result = GetTimeZoneBinary(locationName, out Stream timeZoneBinaryStream, out LocalStorage ncaFile);
- if (result == ResultCode.Success)
- {
- result = Manager.ParseTimeZoneRuleBinary(out outRules, timeZoneBinaryStream);
- ncaFile.Dispose();
- }
- return result;
- }
- }
- }
|