2019-10-17 08:17:44 +02:00
using LibHac ;
2020-03-25 09:14:35 +01:00
using LibHac.Common ;
2019-10-17 08:17:44 +02:00
using LibHac.Fs ;
using LibHac.FsSystem ;
using LibHac.FsSystem.NcaUtils ;
2019-10-08 05:48:49 +02:00
using Ryujinx.Common.Logging ;
2020-03-25 23:23:21 +01:00
using Ryujinx.Configuration ;
2019-10-16 02:30:36 +02:00
using Ryujinx.HLE.Exceptions ;
2019-10-08 05:48:49 +02:00
using Ryujinx.HLE.FileSystem ;
2020-03-25 23:23:21 +01:00
using Ryujinx.HLE.FileSystem.Content ;
2019-10-08 05:48:49 +02:00
using Ryujinx.HLE.HOS.Services.Time.Clock ;
using Ryujinx.HLE.Utilities ;
using System.Collections.Generic ;
using System.IO ;
using static Ryujinx . HLE . HOS . Services . Time . TimeZone . TimeZoneRule ;
namespace Ryujinx.HLE.HOS.Services.Time.TimeZone
{
2020-03-25 23:23:21 +01:00
public class TimeZoneContentManager
2019-10-08 05:48:49 +02:00
{
private const long TimeZoneBinaryTitleId = 0x010000000000080E ;
2020-03-25 23:23:21 +01:00
private VirtualFileSystem _virtualFileSystem ;
private IntegrityCheckLevel _fsIntegrityCheckLevel ;
private ContentManager _contentManager ;
2019-10-08 05:48:49 +02:00
2020-03-25 23:23:21 +01:00
public string [ ] LocationNameCache { get ; private set ; }
internal TimeZoneManager Manager { get ; private set ; }
2019-10-08 05:48:49 +02:00
public TimeZoneContentManager ( )
{
Manager = new TimeZoneManager ( ) ;
}
2020-03-25 23:23:21 +01:00
public void InitializeInstance ( VirtualFileSystem virtualFileSystem , ContentManager contentManager , IntegrityCheckLevel fsIntegrityCheckLevel )
2019-10-08 05:48:49 +02:00
{
2020-03-25 23:23:21 +01:00
_virtualFileSystem = virtualFileSystem ;
_contentManager = contentManager ;
_fsIntegrityCheckLevel = fsIntegrityCheckLevel ;
2019-10-08 05:48:49 +02:00
InitializeLocationNameCache ( ) ;
2020-03-25 23:23:21 +01:00
}
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 ) ;
2019-10-08 05:48:49 +02:00
SteadyClockTimePoint timeZoneUpdatedTimePoint = timeManager . StandardSteadyClock . GetCurrentTimePoint ( null ) ;
2020-03-25 23:23:21 +01:00
string deviceLocationName = SanityCheckDeviceLocationName ( ) ;
ResultCode result = GetTimeZoneBinary ( deviceLocationName , out Stream timeZoneBinaryStream , out LocalStorage ncaFile ) ;
2019-10-08 05:48:49 +02:00
if ( result = = ResultCode . Success )
{
// TODO: Read TimeZoneVersion from sysarchive.
2020-03-25 23:23:21 +01:00
timeManager . SetupTimeZoneManager ( deviceLocationName , timeZoneUpdatedTimePoint , ( uint ) LocationNameCache . Length , new UInt128 ( ) , timeZoneBinaryStream ) ;
2019-10-11 18:05:10 +02:00
ncaFile . Dispose ( ) ;
2019-10-08 05:48:49 +02: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 23:23:21 +01:00
using ( IStorage ncaFileStream = new LocalStorage ( _virtualFileSystem . SwitchPathToSystemPath ( GetTimeZoneBinaryTitleContentPath ( ) ) , FileAccess . Read , FileMode . Open ) )
2019-10-08 05:48:49 +02:00
{
2020-03-25 23:23:21 +01:00
Nca nca = new Nca ( _virtualFileSystem . KeySet , ncaFileStream ) ;
IFileSystem romfs = nca . OpenFileSystem ( NcaSectionType . Data , _fsIntegrityCheckLevel ) ;
2019-10-08 05:48:49 +02:00
2020-03-25 09:14:35 +01:00
romfs . OpenFile ( out IFile binaryListFile , "/binaryList.txt" . ToU8Span ( ) , OpenMode . Read ) . ThrowIfFailure ( ) ;
2019-10-17 08:17:44 +02:00
StreamReader reader = new StreamReader ( binaryListFile . AsStream ( ) ) ;
2019-10-08 05:48:49 +02:00
List < string > locationNameList = new List < string > ( ) ;
string locationName ;
while ( ( locationName = reader . ReadLine ( ) ) ! = null )
{
locationNameList . Add ( locationName ) ;
}
2020-03-25 23:23:21 +01:00
LocationNameCache = locationNameList . ToArray ( ) ;
2019-10-08 05:48:49 +02:00
}
}
else
{
2020-03-25 23:23:21 +01:00
LocationNameCache = new string [ ] { "UTC" } ;
2019-10-08 05:48:49 +02:00
Logger . PrintWarning ( LogClass . ServiceTime , "TimeZoneBinary system title not found! TimeZone conversions will not work, provide the system archive to fix this warning. (See https://github.com/Ryujinx/Ryujinx#requirements for more informations)" ) ;
}
}
private bool IsLocationNameValid ( string locationName )
{
2020-03-25 23:23:21 +01:00
foreach ( string cachedLocationName in LocationNameCache )
2019-10-08 05:48:49 +02:00
{
if ( cachedLocationName . Equals ( locationName ) )
{
return true ;
}
}
return false ;
}
public ResultCode SetDeviceLocationName ( string locationName )
{
2019-10-11 18:05:10 +02:00
ResultCode result = GetTimeZoneBinary ( locationName , out Stream timeZoneBinaryStream , out LocalStorage ncaFile ) ;
2019-10-08 05:48:49 +02:00
if ( result = = ResultCode . Success )
{
result = Manager . SetDeviceLocationNameWithTimeZoneRule ( locationName , timeZoneBinaryStream ) ;
2019-10-11 18:05:10 +02:00
ncaFile . Dispose ( ) ;
2019-10-08 05:48:49 +02:00
}
return result ;
}
public ResultCode LoadLocationNameList ( uint index , out string [ ] outLocationNameArray , uint maxLength )
{
List < string > locationNameList = new List < string > ( ) ;
2020-03-25 23:23:21 +01:00
for ( int i = 0 ; i < LocationNameCache . Length & & i < maxLength ; i + + )
2019-10-08 05:48:49 +02:00
{
if ( i < index )
{
continue ;
}
2020-03-25 23:23:21 +01:00
string locationName = LocationNameCache [ i ] ;
2019-10-08 05:48:49 +02: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 ( )
{
2020-03-25 23:23:21 +01:00
return _contentManager . GetInstalledContentPath ( TimeZoneBinaryTitleId , StorageId . NandSystem , NcaContentType . Data ) ;
2019-10-08 05:48:49 +02:00
}
public bool HasTimeZoneBinaryTitle ( )
{
return ! string . IsNullOrEmpty ( GetTimeZoneBinaryTitleContentPath ( ) ) ;
}
2019-10-11 18:05:10 +02:00
internal ResultCode GetTimeZoneBinary ( string locationName , out Stream timeZoneBinaryStream , out LocalStorage ncaFile )
2019-10-08 05:48:49 +02:00
{
timeZoneBinaryStream = null ;
2019-10-11 18:05:10 +02:00
ncaFile = null ;
2019-10-08 05:48:49 +02:00
if ( ! IsLocationNameValid ( locationName ) )
{
return ResultCode . TimeZoneNotFound ;
}
2020-03-25 23:23:21 +01:00
ncaFile = new LocalStorage ( _virtualFileSystem . SwitchPathToSystemPath ( GetTimeZoneBinaryTitleContentPath ( ) ) , FileAccess . Read , FileMode . Open ) ;
2019-10-08 05:48:49 +02:00
2020-03-25 23:23:21 +01:00
Nca nca = new Nca ( _virtualFileSystem . KeySet , ncaFile ) ;
IFileSystem romfs = nca . OpenFileSystem ( NcaSectionType . Data , _fsIntegrityCheckLevel ) ;
2019-10-11 18:05:10 +02:00
2020-03-25 09:14:35 +01:00
Result result = romfs . OpenFile ( out IFile timeZoneBinaryFile , $"/zoneinfo/{locationName}" . ToU8Span ( ) , OpenMode . Read ) ;
2019-10-08 05:48:49 +02:00
2019-10-17 08:17:44 +02:00
timeZoneBinaryStream = timeZoneBinaryFile . AsStream ( ) ;
return ( ResultCode ) result . Value ;
2019-10-08 05:48:49 +02:00
}
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 ( $"TimeZoneBinary system title not found! Please provide it. (See https://github.com/Ryujinx/Ryujinx#requirements for more informations)" ) ;
}
2019-10-11 18:05:10 +02:00
ResultCode result = GetTimeZoneBinary ( locationName , out Stream timeZoneBinaryStream , out LocalStorage ncaFile ) ;
2019-10-08 05:48:49 +02:00
if ( result = = ResultCode . Success )
{
result = Manager . ParseTimeZoneRuleBinary ( out outRules , timeZoneBinaryStream ) ;
2019-10-11 18:05:10 +02:00
ncaFile . Dispose ( ) ;
2019-10-08 05:48:49 +02:00
}
return result ;
}
}
}