2019-10-17 06:17:44 +00:00
using LibHac ;
2020-03-25 08:14:35 +00:00
using LibHac.Common ;
2019-10-17 06:17:44 +00:00
using LibHac.Fs ;
2020-09-01 20:08:59 +00:00
using LibHac.Fs.Fsa ;
2019-10-17 06:17:44 +00:00
using LibHac.FsSystem ;
2022-03-22 19:46:16 +00:00
using LibHac.Ncm ;
2022-01-12 11:22:19 +00:00
using LibHac.Tools.FsSystem ;
using LibHac.Tools.FsSystem.NcaUtils ;
2019-10-08 03:48:49 +00:00
using Ryujinx.Common.Logging ;
2022-05-31 19:29:35 +00:00
using Ryujinx.Cpu ;
2019-10-16 00:30:36 +00:00
using Ryujinx.HLE.Exceptions ;
2019-10-08 03:48:49 +00:00
using Ryujinx.HLE.FileSystem ;
using Ryujinx.HLE.HOS.Services.Time.Clock ;
using Ryujinx.HLE.Utilities ;
2020-07-21 04:14:42 +00:00
using System ;
2019-10-08 03:48:49 +00:00
using System.Collections.Generic ;
using System.IO ;
2022-06-29 20:08:30 +00:00
using System.Text ;
2022-06-24 17:04:57 +00:00
using TimeZoneRuleBox = Ryujinx . Common . Memory . Box < Ryujinx . HLE . HOS . Services . Time . TimeZone . TimeZoneRule > ;
2019-10-08 03:48:49 +00:00
namespace Ryujinx.HLE.HOS.Services.Time.TimeZone
{
2020-03-25 22:23:21 +00:00
public class TimeZoneContentManager
2019-10-08 03:48:49 +00:00
{
private const long TimeZoneBinaryTitleId = 0x010000000000080E ;
2020-08-30 16:35:42 +00:00
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/wiki/Ryujinx-Setup-&-Configuration-Guide#initial-setup-continued---installation-of-firmware for more information)" ;
2020-03-29 21:23:05 +00:00
2020-03-25 22:23:21 +00:00
private VirtualFileSystem _virtualFileSystem ;
private IntegrityCheckLevel _fsIntegrityCheckLevel ;
private ContentManager _contentManager ;
2019-10-08 03:48:49 +00:00
2020-03-25 22:23:21 +00:00
public string [ ] LocationNameCache { get ; private set ; }
internal TimeZoneManager Manager { get ; private set ; }
2019-10-08 03:48:49 +00:00
public TimeZoneContentManager ( )
{
Manager = new TimeZoneManager ( ) ;
}
2020-03-25 22:23:21 +00:00
public void InitializeInstance ( VirtualFileSystem virtualFileSystem , ContentManager contentManager , IntegrityCheckLevel fsIntegrityCheckLevel )
2019-10-08 03:48:49 +00:00
{
2020-03-25 22:23:21 +00:00
_virtualFileSystem = virtualFileSystem ;
_contentManager = contentManager ;
_fsIntegrityCheckLevel = fsIntegrityCheckLevel ;
2019-10-08 03:48:49 +00:00
InitializeLocationNameCache ( ) ;
2020-03-25 22:23:21 +00:00
}
2021-05-16 15:12:14 +00:00
public string SanityCheckDeviceLocationName ( string locationName )
2020-03-25 22:23:21 +00:00
{
if ( IsLocationNameValid ( locationName ) )
{
return locationName ;
}
2020-08-03 23:32:53 +00:00
Logger . Warning ? . Print ( LogClass . ServiceTime , $"Invalid device TimeZone {locationName}, switching back to UTC" ) ;
2020-03-25 22:23:21 +00:00
return "UTC" ;
}
internal void Initialize ( TimeManager timeManager , Switch device )
{
InitializeInstance ( device . FileSystem , device . System . ContentManager , device . System . FsIntegrityCheckLevel ) ;
2019-10-08 03:48:49 +00:00
2022-05-31 19:29:35 +00:00
ITickSource tickSource = device . System . TickSource ;
SteadyClockTimePoint timeZoneUpdatedTimePoint = timeManager . StandardSteadyClock . GetCurrentTimePoint ( tickSource ) ;
2019-10-08 03:48:49 +00:00
2021-05-16 15:12:14 +00:00
string deviceLocationName = SanityCheckDeviceLocationName ( device . Configuration . TimeZone ) ;
2020-03-25 22:23:21 +00:00
ResultCode result = GetTimeZoneBinary ( deviceLocationName , out Stream timeZoneBinaryStream , out LocalStorage ncaFile ) ;
2019-10-08 03:48:49 +00:00
if ( result = = ResultCode . Success )
{
// TODO: Read TimeZoneVersion from sysarchive.
2020-03-25 22:23:21 +00:00
timeManager . SetupTimeZoneManager ( deviceLocationName , timeZoneUpdatedTimePoint , ( uint ) LocationNameCache . Length , new UInt128 ( ) , timeZoneBinaryStream ) ;
2019-10-11 16:05:10 +00:00
ncaFile . Dispose ( ) ;
2019-10-08 03:48:49 +00:00
}
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 ( ) )
{
2020-03-25 22:23:21 +00:00
using ( IStorage ncaFileStream = new LocalStorage ( _virtualFileSystem . SwitchPathToSystemPath ( GetTimeZoneBinaryTitleContentPath ( ) ) , FileAccess . Read , FileMode . Open ) )
2019-10-08 03:48:49 +00:00
{
2020-03-25 22:23:21 +00:00
Nca nca = new Nca ( _virtualFileSystem . KeySet , ncaFileStream ) ;
IFileSystem romfs = nca . OpenFileSystem ( NcaSectionType . Data , _fsIntegrityCheckLevel ) ;
2019-10-08 03:48:49 +00:00
2021-12-23 16:55:50 +00:00
using var binaryListFile = new UniqueRef < IFile > ( ) ;
2019-10-17 06:17:44 +00:00
2021-12-23 16:55:50 +00:00
romfs . OpenFile ( ref binaryListFile . Ref ( ) , "/binaryList.txt" . ToU8Span ( ) , OpenMode . Read ) . ThrowIfFailure ( ) ;
StreamReader reader = new StreamReader ( binaryListFile . Get . AsStream ( ) ) ;
2019-10-08 03:48:49 +00:00
List < string > locationNameList = new List < string > ( ) ;
string locationName ;
while ( ( locationName = reader . ReadLine ( ) ) ! = null )
{
locationNameList . Add ( locationName ) ;
}
2020-03-25 22:23:21 +00:00
LocationNameCache = locationNameList . ToArray ( ) ;
2019-10-08 03:48:49 +00:00
}
}
else
{
2020-03-25 22:23:21 +00:00
LocationNameCache = new string [ ] { "UTC" } ;
2019-10-08 03:48:49 +00:00
2020-08-03 23:32:53 +00:00
Logger . Error ? . Print ( LogClass . ServiceTime , TimeZoneSystemTitleMissingErrorMessage ) ;
2019-10-08 03:48:49 +00:00
}
}
2020-07-21 04:14:42 +00:00
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 ;
}
2021-12-23 16:55:50 +00:00
using var tzif = new UniqueRef < IFile > ( ) ;
if ( romfs . OpenFile ( ref tzif . Ref ( ) , $"/zoneinfo/{locName}" . ToU8Span ( ) , OpenMode . Read ) . IsFailure ( ) )
2020-07-21 04:14:42 +00:00
{
2020-08-03 23:32:53 +00:00
Logger . Error ? . Print ( LogClass . ServiceTime , $"Error opening /zoneinfo/{locName}" ) ;
2020-07-21 04:14:42 +00:00
continue ;
}
2022-06-24 17:04:57 +00:00
TimeZoneRuleBox tzRuleBox = new TimeZoneRuleBox ( ) ;
ref TimeZoneRule tzRule = ref tzRuleBox . Data ;
TimeZone . ParseTimeZoneBinary ( ref tzRule , tzif . Get . AsStream ( ) ) ;
2020-07-21 04:14:42 +00:00
2021-12-23 16:55:50 +00:00
TimeTypeInfo ttInfo ;
if ( tzRule . TimeCount > 0 ) // Find the current transition period
{
int fin = 0 ;
for ( int i = 0 ; i < tzRule . TimeCount ; + + i )
2020-07-21 04:14:42 +00:00
{
2021-12-23 16:55:50 +00:00
if ( tzRule . Ats [ i ] < = now )
2020-07-21 04:14:42 +00:00
{
2021-12-23 16:55:50 +00:00
fin = i ;
2020-07-21 04:14:42 +00:00
}
}
2021-12-23 16:55:50 +00:00
ttInfo = tzRule . Ttis [ tzRule . Types [ fin ] ] ;
}
else if ( tzRule . TypeCount > = 1 ) // Otherwise, use the first offset in TTInfo
{
ttInfo = tzRule . Ttis [ 0 ] ;
}
else
{
Logger . Error ? . Print ( LogClass . ServiceTime , $"Couldn't find UTC offset for zone {locName}" ) ;
continue ;
}
2020-07-21 04:14:42 +00:00
2022-06-24 17:04:57 +00:00
var abbrStart = tzRule . Chars [ ttInfo . AbbreviationListIndex . . ] ;
int abbrEnd = abbrStart . IndexOf ( ( byte ) 0 ) ;
2020-07-21 04:14:42 +00:00
2022-06-29 20:08:30 +00:00
outList . Add ( ( ttInfo . GmtOffset , locName , Encoding . UTF8 . GetString ( abbrStart [ . . abbrEnd ] ) ) ) ;
2020-07-21 04:14:42 +00:00
}
}
outList . Sort ( ) ;
return outList ;
}
2019-10-08 03:48:49 +00:00
private bool IsLocationNameValid ( string locationName )
{
2020-03-25 22:23:21 +00:00
foreach ( string cachedLocationName in LocationNameCache )
2019-10-08 03:48:49 +00:00
{
if ( cachedLocationName . Equals ( locationName ) )
{
return true ;
}
}
return false ;
}
public ResultCode SetDeviceLocationName ( string locationName )
{
2019-10-11 16:05:10 +00:00
ResultCode result = GetTimeZoneBinary ( locationName , out Stream timeZoneBinaryStream , out LocalStorage ncaFile ) ;
2019-10-08 03:48:49 +00:00
if ( result = = ResultCode . Success )
{
result = Manager . SetDeviceLocationNameWithTimeZoneRule ( locationName , timeZoneBinaryStream ) ;
2019-10-11 16:05:10 +00:00
ncaFile . Dispose ( ) ;
2019-10-08 03:48:49 +00:00
}
return result ;
}
public ResultCode LoadLocationNameList ( uint index , out string [ ] outLocationNameArray , uint maxLength )
{
List < string > locationNameList = new List < string > ( ) ;
2020-03-25 22:23:21 +00:00
for ( int i = 0 ; i < LocationNameCache . Length & & i < maxLength ; i + + )
2019-10-08 03:48:49 +00:00
{
if ( i < index )
{
continue ;
}
2020-03-25 22:23:21 +00:00
string locationName = LocationNameCache [ i ] ;
2019-10-08 03:48:49 +00:00
// 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 ( )
{
2022-03-22 19:46:16 +00:00
return _contentManager . GetInstalledContentPath ( TimeZoneBinaryTitleId , StorageId . BuiltInSystem , NcaContentType . Data ) ;
2019-10-08 03:48:49 +00:00
}
public bool HasTimeZoneBinaryTitle ( )
{
return ! string . IsNullOrEmpty ( GetTimeZoneBinaryTitleContentPath ( ) ) ;
}
2019-10-11 16:05:10 +00:00
internal ResultCode GetTimeZoneBinary ( string locationName , out Stream timeZoneBinaryStream , out LocalStorage ncaFile )
2019-10-08 03:48:49 +00:00
{
timeZoneBinaryStream = null ;
2019-10-11 16:05:10 +00:00
ncaFile = null ;
2019-10-08 03:48:49 +00:00
2020-03-29 21:23:05 +00:00
if ( ! HasTimeZoneBinaryTitle ( ) | | ! IsLocationNameValid ( locationName ) )
2019-10-08 03:48:49 +00:00
{
return ResultCode . TimeZoneNotFound ;
}
2020-03-25 22:23:21 +00:00
ncaFile = new LocalStorage ( _virtualFileSystem . SwitchPathToSystemPath ( GetTimeZoneBinaryTitleContentPath ( ) ) , FileAccess . Read , FileMode . Open ) ;
2019-10-08 03:48:49 +00:00
2020-03-25 22:23:21 +00:00
Nca nca = new Nca ( _virtualFileSystem . KeySet , ncaFile ) ;
IFileSystem romfs = nca . OpenFileSystem ( NcaSectionType . Data , _fsIntegrityCheckLevel ) ;
2019-10-11 16:05:10 +00:00
2021-12-23 16:55:50 +00:00
using var timeZoneBinaryFile = new UniqueRef < IFile > ( ) ;
Result result = romfs . OpenFile ( ref timeZoneBinaryFile . Ref ( ) , $"/zoneinfo/{locationName}" . ToU8Span ( ) , OpenMode . Read ) ;
2019-10-08 03:48:49 +00:00
2021-12-23 16:55:50 +00:00
timeZoneBinaryStream = timeZoneBinaryFile . Release ( ) . AsStream ( ) ;
2019-10-17 06:17:44 +00:00
return ( ResultCode ) result . Value ;
2019-10-08 03:48:49 +00:00
}
2022-06-24 17:04:57 +00:00
internal ResultCode LoadTimeZoneRule ( ref TimeZoneRule rules , string locationName )
2019-10-08 03:48:49 +00:00
{
2022-06-24 17:04:57 +00:00
rules = default ;
2019-10-08 03:48:49 +00:00
if ( ! HasTimeZoneBinaryTitle ( ) )
{
2020-03-29 21:23:05 +00:00
throw new InvalidSystemResourceException ( TimeZoneSystemTitleMissingErrorMessage ) ;
2019-10-08 03:48:49 +00:00
}
2019-10-11 16:05:10 +00:00
ResultCode result = GetTimeZoneBinary ( locationName , out Stream timeZoneBinaryStream , out LocalStorage ncaFile ) ;
2019-10-08 03:48:49 +00:00
if ( result = = ResultCode . Success )
{
2022-06-24 17:04:57 +00:00
result = Manager . ParseTimeZoneRuleBinary ( ref rules , timeZoneBinaryStream ) ;
2019-10-11 16:05:10 +00:00
ncaFile . Dispose ( ) ;
2019-10-08 03:48:49 +00:00
}
return result ;
}
}
2020-08-30 16:35:42 +00:00
}