2019-10-17 08:17:44 +02:00
using LibHac ;
using LibHac.Fs ;
using LibHac.FsSystem ;
using LibHac.FsSystem.NcaUtils ;
2019-10-08 05:48:49 +02:00
using Ryujinx.Common.Logging ;
2019-10-16 02:30:36 +02:00
using Ryujinx.HLE.Exceptions ;
2019-10-08 05:48:49 +02:00
using Ryujinx.HLE.FileSystem ;
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
{
class TimeZoneContentManager
{
private const long TimeZoneBinaryTitleId = 0x010000000000080E ;
private Switch _device ;
private string [ ] _locationNameCache ;
public TimeZoneManager Manager { get ; private set ; }
public TimeZoneContentManager ( )
{
Manager = new TimeZoneManager ( ) ;
}
internal void Initialize ( TimeManager timeManager , Switch device )
{
_device = device ;
InitializeLocationNameCache ( ) ;
SteadyClockTimePoint timeZoneUpdatedTimePoint = timeManager . StandardSteadyClock . GetCurrentTimePoint ( null ) ;
2019-10-11 18:05:10 +02:00
ResultCode result = GetTimeZoneBinary ( "UTC" , out Stream timeZoneBinaryStream , out LocalStorage ncaFile ) ;
2019-10-08 05:48:49 +02:00
if ( result = = ResultCode . Success )
{
// TODO: Read TimeZoneVersion from sysarchive.
timeManager . SetupTimeZoneManager ( "UTC" , 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 ( ) )
{
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 ) ;
2019-10-17 08:17:44 +02:00
romfs . OpenFile ( out IFile binaryListFile , "/binaryList.txt" , OpenMode . Read ) . ThrowIfFailure ( ) ;
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 ) ;
}
_locationNameCache = locationNameList . ToArray ( ) ;
}
}
else
{
_locationNameCache = new string [ 0 ] ;
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 )
{
foreach ( string cachedLocationName in _locationNameCache )
{
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 > ( ) ;
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 ( )
{
2019-10-17 08:17:44 +02:00
return _device . System . 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 ;
}
2019-10-11 18:05:10 +02:00
ncaFile = new LocalStorage ( _device . FileSystem . SwitchPathToSystemPath ( GetTimeZoneBinaryTitleContentPath ( ) ) , FileAccess . Read , FileMode . Open ) ;
2019-10-08 05:48:49 +02:00
2019-10-11 18:05:10 +02:00
Nca nca = new Nca ( _device . System . KeySet , ncaFile ) ;
IFileSystem romfs = nca . OpenFileSystem ( NcaSectionType . Data , _device . System . FsIntegrityCheckLevel ) ;
2019-10-17 08:17:44 +02:00
Result result = romfs . OpenFile ( out IFile timeZoneBinaryFile , $"/zoneinfo/{locationName}" , 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 ;
}
}
}