2022-05-15 11:30:15 +00:00
using LibHac ;
using LibHac.Common ;
using LibHac.Common.Keys ;
using LibHac.Fs ;
using LibHac.Fs.Fsa ;
using LibHac.FsSystem ;
using LibHac.Ns ;
using LibHac.Tools.Fs ;
using LibHac.Tools.FsSystem ;
using LibHac.Tools.FsSystem.NcaUtils ;
using Ryujinx.Common.Configuration ;
using Ryujinx.Common.Logging ;
2023-04-03 10:14:19 +00:00
using Ryujinx.Common.Utilities ;
2022-05-15 11:30:15 +00:00
using Ryujinx.HLE.FileSystem ;
using Ryujinx.HLE.HOS.SystemState ;
using Ryujinx.HLE.Loaders.Npdm ;
2023-11-11 20:56:57 +00:00
using Ryujinx.HLE.Loaders.Processes.Extensions ;
2023-04-16 01:03:35 +00:00
using Ryujinx.Ui.Common.Configuration ;
2022-05-15 11:30:15 +00:00
using Ryujinx.Ui.Common.Configuration.System ;
using System ;
using System.Collections.Generic ;
using System.IO ;
2023-06-27 23:18:19 +00:00
using System.Linq ;
2022-05-15 11:30:15 +00:00
using System.Reflection ;
using System.Text ;
using System.Text.Json ;
using System.Threading ;
2023-11-11 20:56:57 +00:00
using ContentType = LibHac . Ncm . ContentType ;
2022-05-15 11:30:15 +00:00
using Path = System . IO . Path ;
2023-06-29 00:39:22 +00:00
using TimeSpan = System . TimeSpan ;
2022-05-15 11:30:15 +00:00
namespace Ryujinx.Ui.App.Common
{
public class ApplicationLibrary
{
2023-06-29 00:39:22 +00:00
public event EventHandler < ApplicationAddedEventArgs > ApplicationAdded ;
2022-05-15 11:30:15 +00:00
public event EventHandler < ApplicationCountUpdatedEventArgs > ApplicationCountUpdated ;
private readonly byte [ ] _nspIcon ;
private readonly byte [ ] _xciIcon ;
private readonly byte [ ] _ncaIcon ;
private readonly byte [ ] _nroIcon ;
private readonly byte [ ] _nsoIcon ;
2023-01-15 23:11:16 +00:00
private readonly VirtualFileSystem _virtualFileSystem ;
2023-11-11 20:56:57 +00:00
private readonly IntegrityCheckLevel _checkLevel ;
2023-06-29 00:39:22 +00:00
private Language _desiredTitleLanguage ;
private CancellationTokenSource _cancellationToken ;
2022-05-15 11:30:15 +00:00
2023-06-29 00:39:22 +00:00
private static readonly ApplicationJsonSerializerContext _serializerContext = new ( JsonHelper . GetDefaultSerializerOptions ( ) ) ;
2023-04-03 10:14:19 +00:00
2023-11-11 20:56:57 +00:00
public ApplicationLibrary ( VirtualFileSystem virtualFileSystem , IntegrityCheckLevel checkLevel )
2022-05-15 11:30:15 +00:00
{
_virtualFileSystem = virtualFileSystem ;
2023-11-11 20:56:57 +00:00
_checkLevel = checkLevel ;
2022-05-15 11:30:15 +00:00
_nspIcon = GetResourceBytes ( "Ryujinx.Ui.Common.Resources.Icon_NSP.png" ) ;
_xciIcon = GetResourceBytes ( "Ryujinx.Ui.Common.Resources.Icon_XCI.png" ) ;
_ncaIcon = GetResourceBytes ( "Ryujinx.Ui.Common.Resources.Icon_NCA.png" ) ;
_nroIcon = GetResourceBytes ( "Ryujinx.Ui.Common.Resources.Icon_NRO.png" ) ;
_nsoIcon = GetResourceBytes ( "Ryujinx.Ui.Common.Resources.Icon_NSO.png" ) ;
}
2023-01-15 23:11:16 +00:00
private static byte [ ] GetResourceBytes ( string resourceName )
2022-05-15 11:30:15 +00:00
{
2023-06-29 00:39:22 +00:00
Stream resourceStream = Assembly . GetCallingAssembly ( ) . GetManifestResourceStream ( resourceName ) ;
2022-05-15 11:30:15 +00:00
byte [ ] resourceByteArray = new byte [ resourceStream . Length ] ;
resourceStream . Read ( resourceByteArray ) ;
return resourceByteArray ;
}
2023-11-11 20:56:57 +00:00
private ApplicationData GetApplicationFromExeFs ( PartitionFileSystem pfs , string filePath )
2022-05-15 11:30:15 +00:00
{
2023-11-11 20:56:57 +00:00
ApplicationData data = new ( )
{
Icon = _nspIcon ,
} ;
2022-05-15 11:30:15 +00:00
2023-11-11 20:56:57 +00:00
using UniqueRef < IFile > npdmFile = new ( ) ;
2022-05-15 11:30:15 +00:00
2023-11-11 20:56:57 +00:00
try
{
Result result = pfs . OpenFile ( ref npdmFile . Ref , "/main.npdm" . ToU8Span ( ) , OpenMode . Read ) ;
2022-05-15 11:30:15 +00:00
2023-11-11 20:56:57 +00:00
if ( ResultFs . PathNotFound . Includes ( result ) )
{
Npdm npdm = new ( npdmFile . Get . AsStream ( ) ) ;
2022-05-15 11:30:15 +00:00
2023-11-11 20:56:57 +00:00
data . Name = npdm . TitleName ;
data . Id = npdm . Aci0 . TitleId ;
}
2022-05-15 11:30:15 +00:00
2023-11-11 20:56:57 +00:00
return data ;
}
catch ( Exception exception )
{
Logger . Warning ? . Print ( LogClass . Application , $"The file encountered was not of a valid type. File: '{filePath}' Error: {exception.Message}" ) ;
2022-05-15 11:30:15 +00:00
2023-11-11 20:56:57 +00:00
return null ;
}
}
2022-05-15 11:30:15 +00:00
2023-11-11 20:56:57 +00:00
private ApplicationData GetApplicationFromNsp ( PartitionFileSystem pfs , string filePath )
{
bool isExeFs = false ;
// If the NSP doesn't have a main NCA, decrement the number of applications found and then continue to the next application.
bool hasMainNca = false ;
foreach ( DirectoryEntryEx fileEntry in pfs . EnumerateEntries ( "/" , "*" ) )
2022-05-15 11:30:15 +00:00
{
2023-11-11 20:56:57 +00:00
if ( Path . GetExtension ( fileEntry . FullPath ) ? . ToLower ( ) = = ".nca" )
2022-05-15 11:30:15 +00:00
{
2023-11-11 20:56:57 +00:00
using UniqueRef < IFile > ncaFile = new ( ) ;
2022-05-15 11:30:15 +00:00
2023-11-11 20:56:57 +00:00
pfs . OpenFile ( ref ncaFile . Ref , fileEntry . FullPath . ToU8Span ( ) , OpenMode . Read ) . ThrowIfFailure ( ) ;
2022-05-15 11:30:15 +00:00
2023-11-11 20:56:57 +00:00
Nca nca = new ( _virtualFileSystem . KeySet , ncaFile . Get . AsStorage ( ) ) ;
int dataIndex = Nca . GetSectionIndexFromType ( NcaSectionType . Data , NcaContentType . Program ) ;
2022-05-15 11:30:15 +00:00
2023-11-11 20:56:57 +00:00
// Some main NCAs don't have a data partition, so check if the partition exists before opening it
if ( nca . Header . ContentType = = NcaContentType . Program & &
! ( nca . SectionExists ( NcaSectionType . Data ) & &
nca . Header . GetFsHeader ( dataIndex ) . IsPatchSection ( ) ) )
2022-05-15 11:30:15 +00:00
{
2023-11-11 20:56:57 +00:00
hasMainNca = true ;
2023-03-31 19:16:46 +00:00
2023-11-11 20:56:57 +00:00
break ;
2022-12-29 15:52:30 +00:00
}
2022-05-15 11:30:15 +00:00
}
2023-11-11 20:56:57 +00:00
else if ( Path . GetFileNameWithoutExtension ( fileEntry . FullPath ) = = "main" )
{
isExeFs = true ;
}
}
2022-05-15 11:30:15 +00:00
2023-11-11 20:56:57 +00:00
if ( hasMainNca )
{
List < ApplicationData > applications = GetApplicationsFromPfs ( pfs , filePath ) ;
switch ( applications . Count )
2022-05-15 11:30:15 +00:00
{
2023-11-11 20:56:57 +00:00
case 1 :
return applications [ 0 ] ;
case > = 1 :
Logger . Warning ? . Print ( LogClass . Application , $"File '{filePath}' contains more applications than expected: {applications.Count}" ) ;
return applications [ 0 ] ;
default :
return null ;
}
}
2022-05-15 11:30:15 +00:00
2023-11-11 20:56:57 +00:00
if ( isExeFs )
{
return GetApplicationFromExeFs ( pfs , filePath ) ;
}
2022-05-15 11:30:15 +00:00
2023-11-11 20:56:57 +00:00
return null ;
}
2022-05-15 11:30:15 +00:00
2023-11-11 20:56:57 +00:00
private List < ApplicationData > GetApplicationsFromPfs ( IFileSystem pfs , string filePath )
{
var applications = new List < ApplicationData > ( ) ;
string extension = Path . GetExtension ( filePath ) . ToLower ( ) ;
2022-05-15 11:30:15 +00:00
2023-11-11 20:56:57 +00:00
foreach ( ( ulong titleId , ContentCollection content ) in pfs . GetApplicationData ( _virtualFileSystem , _checkLevel ) )
{
ApplicationData applicationData = new ( )
{
Id = titleId ,
} ;
2023-01-15 23:11:16 +00:00
2023-11-11 20:56:57 +00:00
try
{
Nca mainNca = content . GetNcaByType ( _virtualFileSystem . KeySet , ContentType . Program ) ;
Nca controlNca = content . GetNcaByType ( _virtualFileSystem . KeySet , ContentType . Control ) ;
2022-05-15 11:30:15 +00:00
2023-11-11 20:56:57 +00:00
BlitStruct < ApplicationControlProperty > controlHolder = new ( 1 ) ;
2022-05-15 11:30:15 +00:00
2023-11-11 20:56:57 +00:00
IFileSystem controlFs = controlNca ? . OpenFileSystem ( NcaSectionType . Data , IntegrityCheckLevel . None ) ;
2022-05-15 11:30:15 +00:00
2023-11-11 20:56:57 +00:00
// Check if there is an update available.
if ( IsUpdateApplied ( mainNca , out IFileSystem updatedControlFs ) )
{
// Replace the original ControlFs by the updated one.
controlFs = updatedControlFs ;
}
2022-05-15 11:30:15 +00:00
2023-11-11 20:56:57 +00:00
ReadControlData ( controlFs , controlHolder . ByteSpan ) ;
2022-05-15 11:30:15 +00:00
2023-11-11 20:56:57 +00:00
GetApplicationInformation ( ref controlHolder . Value , ref applicationData ) ;
2022-05-15 11:30:15 +00:00
2023-11-11 20:56:57 +00:00
// Read the icon from the ControlFS and store it as a byte array
try
{
using UniqueRef < IFile > icon = new ( ) ;
2022-05-15 11:30:15 +00:00
2023-11-11 20:56:57 +00:00
controlFs . OpenFile ( ref icon . Ref , $"/icon_{_desiredTitleLanguage}.dat" . ToU8Span ( ) , OpenMode . Read ) . ThrowIfFailure ( ) ;
2022-05-15 11:30:15 +00:00
2023-11-11 20:56:57 +00:00
using MemoryStream stream = new ( ) ;
2023-01-15 23:11:16 +00:00
2023-11-11 20:56:57 +00:00
icon . Get . AsStream ( ) . CopyTo ( stream ) ;
applicationData . Icon = stream . ToArray ( ) ;
}
catch ( HorizonResultException )
{
foreach ( DirectoryEntryEx entry in controlFs . EnumerateEntries ( "/" , "*" ) )
{
if ( entry . Name = = "control.nacp" )
{
continue ;
}
2022-05-15 11:30:15 +00:00
2023-11-11 20:56:57 +00:00
using var icon = new UniqueRef < IFile > ( ) ;
2023-01-15 23:11:16 +00:00
2023-11-11 20:56:57 +00:00
controlFs . OpenFile ( ref icon . Ref , entry . FullPath . ToU8Span ( ) , OpenMode . Read ) . ThrowIfFailure ( ) ;
2023-01-15 23:11:16 +00:00
2023-11-11 20:56:57 +00:00
using MemoryStream stream = new ( ) ;
2022-05-15 11:30:15 +00:00
2023-11-11 20:56:57 +00:00
icon . Get . AsStream ( ) . CopyTo ( stream ) ;
applicationData . Icon = stream . ToArray ( ) ;
2022-05-15 11:30:15 +00:00
2023-11-11 20:56:57 +00:00
if ( applicationData . Icon ! = null )
{
break ;
}
}
2022-05-15 11:30:15 +00:00
2023-11-11 20:56:57 +00:00
applicationData . Icon ? ? = extension = = ".xci" ? _xciIcon : _nspIcon ;
}
2022-05-15 11:30:15 +00:00
2023-11-11 20:56:57 +00:00
applicationData . ControlHolder = controlHolder ;
2023-01-15 23:11:16 +00:00
2023-11-11 20:56:57 +00:00
applications . Add ( applicationData ) ;
}
catch ( MissingKeyException exception )
{
applicationData . Icon = extension = = ".xci" ? _xciIcon : _nspIcon ;
2022-05-15 11:30:15 +00:00
2023-11-11 20:56:57 +00:00
Logger . Warning ? . Print ( LogClass . Application , $"Your key set is missing a key with the name: {exception.Name}" ) ;
}
catch ( InvalidDataException )
{
applicationData . Icon = extension = = ".xci" ? _xciIcon : _nspIcon ;
2022-05-15 11:30:15 +00:00
2023-11-11 20:56:57 +00:00
Logger . Warning ? . Print ( LogClass . Application , $"The header key is incorrect or missing and therefore the NCA header content type check has failed. Errored File: {filePath}" ) ;
}
catch ( Exception exception )
{
Logger . Warning ? . Print ( LogClass . Application , $"The file encountered was not of a valid type. File: '{filePath}' Error: {exception}" ) ;
}
}
2022-05-15 11:30:15 +00:00
2023-11-11 20:56:57 +00:00
return applications ;
}
2022-05-15 11:30:15 +00:00
2023-11-11 20:56:57 +00:00
private bool TryGetApplicationsFromFile ( string applicationPath , out List < ApplicationData > applications )
{
applications = new List < ApplicationData > ( ) ;
2022-05-15 11:30:15 +00:00
2023-11-11 20:56:57 +00:00
long fileSizeBytes = new FileInfo ( applicationPath ) . Length ;
2022-05-15 11:30:15 +00:00
2023-11-11 20:56:57 +00:00
double fileSize = fileSizeBytes * 0.000000000931 ;
2022-05-15 11:30:15 +00:00
2023-11-11 20:56:57 +00:00
BlitStruct < ApplicationControlProperty > controlHolder = new ( 1 ) ;
2022-05-15 11:30:15 +00:00
2023-11-11 20:56:57 +00:00
try
{
string extension = Path . GetExtension ( applicationPath ) . ToLower ( ) ;
2022-05-15 11:30:15 +00:00
2023-11-11 20:56:57 +00:00
using FileStream file = new ( applicationPath , FileMode . Open , FileAccess . Read ) ;
2023-03-31 19:16:46 +00:00
2023-11-11 20:56:57 +00:00
switch ( extension )
{
case ".xci" :
{
Xci xci = new ( _virtualFileSystem . KeySet , file . AsStorage ( ) ) ;
2022-05-15 11:30:15 +00:00
2023-11-11 20:56:57 +00:00
applications = GetApplicationsFromPfs ( xci . OpenPartition ( XciPartitionType . Secure ) , applicationPath ) ;
2023-01-15 23:11:16 +00:00
2023-11-11 20:56:57 +00:00
if ( applications . Count = = 0 )
2023-01-15 23:11:16 +00:00
{
2023-11-11 20:56:57 +00:00
return false ;
2023-01-15 23:11:16 +00:00
}
2022-05-15 11:30:15 +00:00
2023-11-11 20:56:57 +00:00
break ;
}
case ".nsp" :
case ".pfs0" :
var pfs = new PartitionFileSystem ( ) ;
pfs . Initialize ( file . AsStorage ( ) ) . ThrowIfFailure ( ) ;
2022-05-15 11:30:15 +00:00
2023-11-11 20:56:57 +00:00
ApplicationData result = GetApplicationFromNsp ( pfs , applicationPath ) ;
2022-05-15 11:30:15 +00:00
2023-11-11 20:56:57 +00:00
if ( result = = null )
{
return false ;
2023-01-15 23:11:16 +00:00
}
2023-11-11 20:56:57 +00:00
applications . Add ( result ) ;
break ;
case ".nro" :
2023-01-15 23:11:16 +00:00
{
BinaryReader reader = new ( file ) ;
2023-11-11 20:56:57 +00:00
ApplicationData application = new ( ) ;
2022-05-15 11:30:15 +00:00
2023-01-15 23:11:16 +00:00
byte [ ] Read ( long position , int size )
{
file . Seek ( position , SeekOrigin . Begin ) ;
2022-05-15 11:30:15 +00:00
2023-01-15 23:11:16 +00:00
return reader . ReadBytes ( size ) ;
}
2022-05-15 11:30:15 +00:00
2023-01-15 23:11:16 +00:00
try
{
file . Seek ( 24 , SeekOrigin . Begin ) ;
2022-05-15 11:30:15 +00:00
2023-01-15 23:11:16 +00:00
int assetOffset = reader . ReadInt32 ( ) ;
2022-05-15 11:30:15 +00:00
2023-01-15 23:11:16 +00:00
if ( Encoding . ASCII . GetString ( Read ( assetOffset , 4 ) ) = = "ASET" )
{
byte [ ] iconSectionInfo = Read ( assetOffset + 8 , 0x10 ) ;
2022-05-15 11:30:15 +00:00
2023-01-15 23:11:16 +00:00
long iconOffset = BitConverter . ToInt64 ( iconSectionInfo , 0 ) ;
long iconSize = BitConverter . ToInt64 ( iconSectionInfo , 8 ) ;
2022-05-15 11:30:15 +00:00
2023-01-15 23:11:16 +00:00
ulong nacpOffset = reader . ReadUInt64 ( ) ;
ulong nacpSize = reader . ReadUInt64 ( ) ;
2022-05-15 11:30:15 +00:00
2023-01-15 23:11:16 +00:00
// Reads and stores game icon as byte array
2023-06-01 16:24:00 +00:00
if ( iconSize > 0 )
{
2023-11-11 20:56:57 +00:00
application . Icon = Read ( assetOffset + iconOffset , ( int ) iconSize ) ;
2023-06-01 16:24:00 +00:00
}
else
{
2023-11-11 20:56:57 +00:00
application . Icon = _nroIcon ;
2023-06-01 16:24:00 +00:00
}
2022-05-15 11:30:15 +00:00
2023-01-15 23:11:16 +00:00
// Read the NACP data
Read ( assetOffset + ( int ) nacpOffset , ( int ) nacpSize ) . AsSpan ( ) . CopyTo ( controlHolder . ByteSpan ) ;
2022-05-15 11:30:15 +00:00
2023-11-11 20:56:57 +00:00
GetApplicationInformation ( ref controlHolder . Value , ref application ) ;
2022-05-15 11:30:15 +00:00
}
2023-01-15 23:11:16 +00:00
else
2022-05-15 11:30:15 +00:00
{
2023-11-11 20:56:57 +00:00
application . Icon = _nroIcon ;
application . Name = Path . GetFileNameWithoutExtension ( applicationPath ) ;
2022-05-15 11:30:15 +00:00
}
2023-11-11 20:56:57 +00:00
application . ControlHolder = controlHolder ;
applications . Add ( application ) ;
2022-05-15 11:30:15 +00:00
}
2023-01-15 23:11:16 +00:00
catch
2022-05-15 11:30:15 +00:00
{
2023-01-15 23:11:16 +00:00
Logger . Warning ? . Print ( LogClass . Application , $"The file encountered was not of a valid type. Errored File: {applicationPath}" ) ;
2022-05-15 11:30:15 +00:00
2023-11-11 20:56:57 +00:00
return false ;
2023-01-15 23:11:16 +00:00
}
2023-11-11 20:56:57 +00:00
break ;
2023-01-15 23:11:16 +00:00
}
2023-11-11 20:56:57 +00:00
case ".nca" :
2023-01-15 23:11:16 +00:00
{
try
{
2023-11-11 20:56:57 +00:00
ApplicationData application = new ( ) ;
2023-01-15 23:11:16 +00:00
Nca nca = new ( _virtualFileSystem . KeySet , new FileStream ( applicationPath , FileMode . Open , FileAccess . Read ) . AsStorage ( ) ) ;
2022-05-15 11:30:15 +00:00
2023-11-11 20:56:57 +00:00
if ( ! nca . IsProgram ( ) | | nca . IsPatch ( ) )
2023-01-15 23:11:16 +00:00
{
2023-11-11 20:56:57 +00:00
return false ;
2022-05-15 11:30:15 +00:00
}
2023-11-11 20:56:57 +00:00
application . Icon = _ncaIcon ;
application . Name = Path . GetFileNameWithoutExtension ( applicationPath ) ;
application . ControlHolder = controlHolder ;
applications . Add ( application ) ;
2022-05-15 11:30:15 +00:00
}
2023-01-15 23:11:16 +00:00
catch ( InvalidDataException )
2022-05-15 11:30:15 +00:00
{
2023-01-15 23:11:16 +00:00
Logger . Warning ? . Print ( LogClass . Application , $"The NCA header content type check has failed. This is usually because the header key is incorrect or missing. Errored File: {applicationPath}" ) ;
2022-05-15 11:30:15 +00:00
}
2023-01-15 23:11:16 +00:00
catch
{
Logger . Warning ? . Print ( LogClass . Application , $"The file encountered was not of a valid type. Errored File: {applicationPath}" ) ;
2023-11-11 20:56:57 +00:00
return false ;
2023-01-15 23:11:16 +00:00
}
2023-11-11 20:56:57 +00:00
break ;
2023-01-15 23:11:16 +00:00
}
2023-11-11 20:56:57 +00:00
// If its an NSO we just set defaults
case ".nso" :
2023-01-15 23:11:16 +00:00
{
2023-11-11 20:56:57 +00:00
ApplicationData application = new ( )
{
Icon = _nsoIcon ,
Name = Path . GetFileNameWithoutExtension ( applicationPath ) ,
} ;
applications . Add ( application ) ;
break ;
2022-05-15 11:30:15 +00:00
}
2023-11-11 20:56:57 +00:00
}
}
catch ( IOException exception )
{
Logger . Warning ? . Print ( LogClass . Application , exception . Message ) ;
return false ;
}
foreach ( var data in applications )
{
ApplicationMetadata appMetadata = LoadAndSaveMetaData ( data . IdString , appMetadata = >
{
appMetadata . Title = data . Name ;
// Only do the migration if time_played has a value and timespan_played hasn't been updated yet.
if ( appMetadata . TimePlayedOld ! = default & & appMetadata . TimePlayed = = TimeSpan . Zero )
{
appMetadata . TimePlayed = TimeSpan . FromSeconds ( appMetadata . TimePlayedOld ) ;
appMetadata . TimePlayedOld = default ;
2022-05-15 11:30:15 +00:00
}
2023-11-11 20:56:57 +00:00
// Only do the migration if last_played has a value and last_played_utc doesn't exist yet.
if ( appMetadata . LastPlayedOld ! = default & & ! appMetadata . LastPlayed . HasValue )
2022-05-15 11:30:15 +00:00
{
2023-11-11 20:56:57 +00:00
// Migrate from string-based last_played to DateTime-based last_played_utc.
if ( DateTime . TryParse ( appMetadata . LastPlayedOld , out DateTime lastPlayedOldParsed ) )
{
appMetadata . LastPlayed = lastPlayedOldParsed ;
2022-05-15 11:30:15 +00:00
2023-11-11 20:56:57 +00:00
// Migration successful: deleting last_played from the metadata file.
appMetadata . LastPlayedOld = default ;
}
2022-05-15 11:30:15 +00:00
2023-11-11 20:56:57 +00:00
}
} ) ;
data . Favorite = appMetadata . Favorite ;
data . TimePlayed = appMetadata . TimePlayed ;
data . LastPlayed = appMetadata . LastPlayed ;
data . FileExtension = Path . GetExtension ( applicationPath ) . TrimStart ( '.' ) . ToUpper ( ) ;
data . FileSize = new FileInfo ( applicationPath ) . Length ;
data . Path = applicationPath ;
}
return true ;
}
public void CancelLoading ( )
{
_cancellationToken ? . Cancel ( ) ;
}
public static void ReadControlData ( IFileSystem controlFs , Span < byte > outProperty )
{
using UniqueRef < IFile > controlFile = new ( ) ;
controlFs . OpenFile ( ref controlFile . Ref , "/control.nacp" . ToU8Span ( ) , OpenMode . Read ) . ThrowIfFailure ( ) ;
controlFile . Get . Read ( out _ , 0 , outProperty , ReadOption . None ) . ThrowIfFailure ( ) ;
}
public void LoadApplications ( List < string > appDirs , Language desiredTitleLanguage )
{
int numApplicationsFound = 0 ;
int numApplicationsLoaded = 0 ;
_desiredTitleLanguage = desiredTitleLanguage ;
_cancellationToken = new CancellationTokenSource ( ) ;
// Builds the applications list with paths to found applications
List < string > applicationPaths = new ( ) ;
try
{
foreach ( string appDir in appDirs )
{
if ( _cancellationToken . Token . IsCancellationRequested )
{
return ;
2022-05-15 11:30:15 +00:00
}
2023-11-11 20:56:57 +00:00
if ( ! Directory . Exists ( appDir ) )
2022-12-02 13:16:43 +00:00
{
2023-11-11 20:56:57 +00:00
Logger . Warning ? . Print ( LogClass . Application , $"The \" game_dirs \ " section in \"Config.json\" contains an invalid directory: \"{appDir}\"" ) ;
2022-05-15 11:30:15 +00:00
2023-11-11 20:56:57 +00:00
continue ;
}
try
{
IEnumerable < string > files = Directory . EnumerateFiles ( appDir , "*" , SearchOption . AllDirectories ) . Where ( file = >
2023-01-15 23:11:16 +00:00
{
2023-11-11 20:56:57 +00:00
return
( Path . GetExtension ( file ) . ToLower ( ) is ".nsp" & & ConfigurationState . Instance . Ui . ShownFileTypes . NSP . Value ) | |
( Path . GetExtension ( file ) . ToLower ( ) is ".pfs0" & & ConfigurationState . Instance . Ui . ShownFileTypes . PFS0 . Value ) | |
( Path . GetExtension ( file ) . ToLower ( ) is ".xci" & & ConfigurationState . Instance . Ui . ShownFileTypes . XCI . Value ) | |
( Path . GetExtension ( file ) . ToLower ( ) is ".nca" & & ConfigurationState . Instance . Ui . ShownFileTypes . NCA . Value ) | |
( Path . GetExtension ( file ) . ToLower ( ) is ".nro" & & ConfigurationState . Instance . Ui . ShownFileTypes . NRO . Value ) | |
( Path . GetExtension ( file ) . ToLower ( ) is ".nso" & & ConfigurationState . Instance . Ui . ShownFileTypes . NSO . Value ) ;
} ) ;
2022-05-15 11:30:15 +00:00
2023-11-11 20:56:57 +00:00
foreach ( string app in files )
2023-05-11 23:56:37 +00:00
{
2023-11-11 20:56:57 +00:00
if ( _cancellationToken . Token . IsCancellationRequested )
Overhaul of string formatting/parsing/sorting logic for TimeSpans, DateTimes, and file sizes (#4956)
* Fixed formatting/parsing issues with ApplicationData properties TimePlayed, LastPlayed, and FileSize
Replaced double-based TimePlayed property with TimeSpan?-based one in ApplicationData and ApplicationMetadata
Added a migration for TimePlayed, just like in #4861
Consolidated ApplicationData's FileSize* properties into one FileSize property
Added a formatting/parsing helper class ValueFormatUtils for TimeSpans, DateTimes, and file sizes
Added new value converters for TimeSpans and file sizes for the Avalonia UI
Added TimePlayedSortComparer
Fixed sort order in LastPlayedSortComparer
Fixed sort order for ApplicationData fields TimePlayed, LastPlayed, and FileSize
Fixed crashes caused by SortHelper
Replaced SystemInfo.ToMiBString with ToGiBString backed by ValueFormatUtils
Replaced SaveModel.GetSizeString() with ValueFormatUtils
* Additional ApplicationLibrary changes that got lost in the last commit
* Removed unneeded usings
* Removed converters as they are no longer needed
* Updated comment on FormatDateTime
* Removed base10 parameter from ValueFormatUtils
FormatFileSize now always returns base 2 values with base 10 units
Made ParseFileSize capable of parsing both base 2 and base 10 units
* Removed nullable attribute from TimePlayed property
Centralized TimePlayed update code into ApplicationMetadata
* Changed UpdateTimePlayed() to use TimeSpan logic
* Removed JsonIgnore attributes from ApplicationData
* Implemented requested format changes
* Fixed mistakes in method documentation comments
* Made it so the Last Played value "Never" is localized in the Avalonia UI
* Implemented suggestions
* Remove unused import
* Did a comment refinement pass in ValueFormatUtils.cs
* Reordered ValueFormatUtils methods and sorted them into #regions
* Integrated functionality from #5056
Also removed Logger print from last_played migration code
* Implemented suggestions
* Moved ValueFormatUtils and SystemInfo to namespace Ryujinx.Ui.Common
* common: Respect proper value format convention and use base10 by default
This could be discuss again in another issue/PR, for now revert to the previous behavior.
Signed-off-by: Mary Guillemard <mary@mary.zone>
---------
Signed-off-by: Mary Guillemard <mary@mary.zone>
Co-authored-by: TSR Berry <20988865+TSRBerry@users.noreply.github.com>
Co-authored-by: Mary Guillemard <mary@mary.zone>
2023-11-06 21:47:44 +00:00
{
2023-11-11 20:56:57 +00:00
return ;
Overhaul of string formatting/parsing/sorting logic for TimeSpans, DateTimes, and file sizes (#4956)
* Fixed formatting/parsing issues with ApplicationData properties TimePlayed, LastPlayed, and FileSize
Replaced double-based TimePlayed property with TimeSpan?-based one in ApplicationData and ApplicationMetadata
Added a migration for TimePlayed, just like in #4861
Consolidated ApplicationData's FileSize* properties into one FileSize property
Added a formatting/parsing helper class ValueFormatUtils for TimeSpans, DateTimes, and file sizes
Added new value converters for TimeSpans and file sizes for the Avalonia UI
Added TimePlayedSortComparer
Fixed sort order in LastPlayedSortComparer
Fixed sort order for ApplicationData fields TimePlayed, LastPlayed, and FileSize
Fixed crashes caused by SortHelper
Replaced SystemInfo.ToMiBString with ToGiBString backed by ValueFormatUtils
Replaced SaveModel.GetSizeString() with ValueFormatUtils
* Additional ApplicationLibrary changes that got lost in the last commit
* Removed unneeded usings
* Removed converters as they are no longer needed
* Updated comment on FormatDateTime
* Removed base10 parameter from ValueFormatUtils
FormatFileSize now always returns base 2 values with base 10 units
Made ParseFileSize capable of parsing both base 2 and base 10 units
* Removed nullable attribute from TimePlayed property
Centralized TimePlayed update code into ApplicationMetadata
* Changed UpdateTimePlayed() to use TimeSpan logic
* Removed JsonIgnore attributes from ApplicationData
* Implemented requested format changes
* Fixed mistakes in method documentation comments
* Made it so the Last Played value "Never" is localized in the Avalonia UI
* Implemented suggestions
* Remove unused import
* Did a comment refinement pass in ValueFormatUtils.cs
* Reordered ValueFormatUtils methods and sorted them into #regions
* Integrated functionality from #5056
Also removed Logger print from last_played migration code
* Implemented suggestions
* Moved ValueFormatUtils and SystemInfo to namespace Ryujinx.Ui.Common
* common: Respect proper value format convention and use base10 by default
This could be discuss again in another issue/PR, for now revert to the previous behavior.
Signed-off-by: Mary Guillemard <mary@mary.zone>
---------
Signed-off-by: Mary Guillemard <mary@mary.zone>
Co-authored-by: TSR Berry <20988865+TSRBerry@users.noreply.github.com>
Co-authored-by: Mary Guillemard <mary@mary.zone>
2023-11-06 21:47:44 +00:00
}
2023-05-11 23:56:37 +00:00
2023-11-11 20:56:57 +00:00
var fileInfo = new FileInfo ( app ) ;
string extension = fileInfo . Extension . ToLower ( ) ;
if ( ! fileInfo . Attributes . HasFlag ( FileAttributes . Hidden ) & & extension is ".nsp" or ".pfs0" or ".xci" or ".nca" or ".nro" or ".nso" )
{
var fullPath = fileInfo . ResolveLinkTarget ( true ) ? . FullName ? ? fileInfo . FullName ;
applicationPaths . Add ( fullPath ) ;
numApplicationsFound + + ;
}
2023-01-15 23:11:16 +00:00
}
2023-11-11 20:56:57 +00:00
}
catch ( UnauthorizedAccessException )
{
Logger . Warning ? . Print ( LogClass . Application , $"Failed to get access to directory: \" { appDir } \ "" ) ;
}
}
2022-05-15 11:30:15 +00:00
2023-11-11 20:56:57 +00:00
// Loops through applications list, creating a struct and then firing an event containing the struct for each application
foreach ( string applicationPath in applicationPaths )
{
if ( _cancellationToken . Token . IsCancellationRequested )
2022-05-15 11:30:15 +00:00
{
2023-11-11 20:56:57 +00:00
return ;
}
if ( TryGetApplicationsFromFile ( applicationPath , out List < ApplicationData > applications ) )
2022-05-15 11:30:15 +00:00
{
2023-11-11 20:56:57 +00:00
foreach ( var application in applications )
{
OnApplicationAdded ( new ApplicationAddedEventArgs
{
AppData = application ,
} ) ;
}
if ( applications . Count > 1 )
{
numApplicationsFound + = applications . Count - 1 ;
}
numApplicationsLoaded + = applications . Count ;
}
else
{
numApplicationsFound - - ;
}
2022-05-15 11:30:15 +00:00
2023-06-29 00:39:22 +00:00
OnApplicationCountUpdated ( new ApplicationCountUpdatedEventArgs
2022-05-15 11:30:15 +00:00
{
NumAppsFound = numApplicationsFound ,
2023-06-29 00:39:22 +00:00
NumAppsLoaded = numApplicationsLoaded ,
2022-05-15 11:30:15 +00:00
} ) ;
}
2023-06-29 00:39:22 +00:00
OnApplicationCountUpdated ( new ApplicationCountUpdatedEventArgs
2022-05-15 11:30:15 +00:00
{
NumAppsFound = numApplicationsFound ,
2023-06-29 00:39:22 +00:00
NumAppsLoaded = numApplicationsLoaded ,
2022-05-15 11:30:15 +00:00
} ) ;
}
finally
{
_cancellationToken . Dispose ( ) ;
_cancellationToken = null ;
}
}
protected void OnApplicationAdded ( ApplicationAddedEventArgs e )
{
ApplicationAdded ? . Invoke ( null , e ) ;
}
protected void OnApplicationCountUpdated ( ApplicationCountUpdatedEventArgs e )
{
ApplicationCountUpdated ? . Invoke ( null , e ) ;
}
2023-06-29 00:39:22 +00:00
public static ApplicationMetadata LoadAndSaveMetaData ( string titleId , Action < ApplicationMetadata > modifyFunction = null )
2022-05-15 11:30:15 +00:00
{
string metadataFolder = Path . Combine ( AppDataManager . GamesDirPath , titleId , "gui" ) ;
2023-06-29 00:39:22 +00:00
string metadataFile = Path . Combine ( metadataFolder , "metadata.json" ) ;
2022-05-15 11:30:15 +00:00
ApplicationMetadata appMetadata ;
if ( ! File . Exists ( metadataFile ) )
{
Directory . CreateDirectory ( metadataFolder ) ;
appMetadata = new ApplicationMetadata ( ) ;
2023-06-29 00:39:22 +00:00
JsonHelper . SerializeToFile ( metadataFile , appMetadata , _serializerContext . ApplicationMetadata ) ;
2022-05-15 11:30:15 +00:00
}
try
{
2023-06-29 00:39:22 +00:00
appMetadata = JsonHelper . DeserializeFromFile ( metadataFile , _serializerContext . ApplicationMetadata ) ;
2022-05-15 11:30:15 +00:00
}
catch ( JsonException )
{
Logger . Warning ? . Print ( LogClass . Application , $"Failed to parse metadata json for {titleId}. Loading defaults." ) ;
appMetadata = new ApplicationMetadata ( ) ;
}
if ( modifyFunction ! = null )
{
modifyFunction ( appMetadata ) ;
2023-06-29 00:39:22 +00:00
JsonHelper . SerializeToFile ( metadataFile , appMetadata , _serializerContext . ApplicationMetadata ) ;
2022-05-15 11:30:15 +00:00
}
return appMetadata ;
}
2023-11-11 20:56:57 +00:00
public byte [ ] GetApplicationIcon ( string applicationPath , Language desiredTitleLanguage , ulong titleId )
2022-05-15 11:30:15 +00:00
{
byte [ ] applicationIcon = null ;
2023-11-11 20:56:57 +00:00
if ( titleId = = 0 )
{
if ( Directory . Exists ( applicationPath ) )
{
return _ncaIcon ;
}
return Path . GetExtension ( applicationPath ) . ToLower ( ) switch
{
".nsp" = > _nspIcon ,
".pfs0" = > _nspIcon ,
".xci" = > _xciIcon ,
".nso" = > _nsoIcon ,
".nro" = > _nroIcon ,
".nca" = > _ncaIcon ,
_ = > _ncaIcon ,
} ;
}
2022-05-15 11:30:15 +00:00
try
{
// Look for icon only if applicationPath is not a directory
if ( ! Directory . Exists ( applicationPath ) )
{
string extension = Path . GetExtension ( applicationPath ) . ToLower ( ) ;
2023-01-15 23:11:16 +00:00
using FileStream file = new ( applicationPath , FileMode . Open , FileAccess . Read ) ;
if ( extension = = ".nsp" | | extension = = ".pfs0" | | extension = = ".xci" )
2022-05-15 11:30:15 +00:00
{
2023-01-15 23:11:16 +00:00
try
2022-05-15 11:30:15 +00:00
{
2023-10-22 23:30:46 +00:00
IFileSystem pfs ;
2022-05-15 11:30:15 +00:00
2023-01-15 23:11:16 +00:00
bool isExeFs = false ;
2022-05-15 11:30:15 +00:00
2023-01-15 23:11:16 +00:00
if ( extension = = ".xci" )
{
Xci xci = new ( _virtualFileSystem . KeySet , file . AsStorage ( ) ) ;
2022-05-15 11:30:15 +00:00
2023-01-15 23:11:16 +00:00
pfs = xci . OpenPartition ( XciPartitionType . Secure ) ;
}
else
{
2023-10-22 23:30:46 +00:00
var pfsTemp = new PartitionFileSystem ( ) ;
pfsTemp . Initialize ( file . AsStorage ( ) ) . ThrowIfFailure ( ) ;
pfs = pfsTemp ;
2022-05-15 11:30:15 +00:00
2023-01-15 23:11:16 +00:00
foreach ( DirectoryEntryEx fileEntry in pfs . EnumerateEntries ( "/" , "*" ) )
{
if ( Path . GetFileNameWithoutExtension ( fileEntry . FullPath ) = = "main" )
2022-05-15 11:30:15 +00:00
{
2023-01-15 23:11:16 +00:00
isExeFs = true ;
2022-05-15 11:30:15 +00:00
}
}
2023-01-15 23:11:16 +00:00
}
2022-05-15 11:30:15 +00:00
2023-01-15 23:11:16 +00:00
if ( isExeFs )
{
applicationIcon = _nspIcon ;
}
else
{
// Store the ControlFS in variable called controlFs
2023-11-11 20:56:57 +00:00
Dictionary < ulong , ContentCollection > programs = pfs . GetApplicationData ( _virtualFileSystem , _checkLevel ) ;
IFileSystem controlFs = null ;
if ( programs . ContainsKey ( titleId ) )
{
if ( programs [ titleId ] . GetNcaByType ( _virtualFileSystem . KeySet , ContentType . Control ) is { } controlNca )
{
controlFs = controlNca . OpenFileSystem ( NcaSectionType . Data , IntegrityCheckLevel . None ) ;
}
}
2023-01-15 23:11:16 +00:00
// Read the icon from the ControlFS and store it as a byte array
try
2022-05-15 11:30:15 +00:00
{
2023-01-15 23:11:16 +00:00
using var icon = new UniqueRef < IFile > ( ) ;
2023-10-20 18:51:15 +00:00
controlFs . OpenFile ( ref icon . Ref , $"/icon_{desiredTitleLanguage}.dat" . ToU8Span ( ) , OpenMode . Read ) . ThrowIfFailure ( ) ;
2023-01-15 23:11:16 +00:00
using MemoryStream stream = new ( ) ;
icon . Get . AsStream ( ) . CopyTo ( stream ) ;
applicationIcon = stream . ToArray ( ) ;
2022-05-15 11:30:15 +00:00
}
2023-01-15 23:11:16 +00:00
catch ( HorizonResultException )
2022-05-15 11:30:15 +00:00
{
2023-01-15 23:11:16 +00:00
foreach ( DirectoryEntryEx entry in controlFs . EnumerateEntries ( "/" , "*" ) )
2022-05-15 11:30:15 +00:00
{
2023-01-15 23:11:16 +00:00
if ( entry . Name = = "control.nacp" )
{
continue ;
}
2022-05-15 11:30:15 +00:00
using var icon = new UniqueRef < IFile > ( ) ;
2023-03-02 02:42:27 +00:00
controlFs . OpenFile ( ref icon . Ref , entry . FullPath . ToU8Span ( ) , OpenMode . Read ) . ThrowIfFailure ( ) ;
2022-05-15 11:30:15 +00:00
2023-11-11 20:56:57 +00:00
using MemoryStream stream = new ( ) ;
icon . Get . AsStream ( ) . CopyTo ( stream ) ;
applicationIcon = stream . ToArray ( ) ;
2022-05-15 11:30:15 +00:00
2023-11-11 20:56:57 +00:00
break ;
2022-05-15 11:30:15 +00:00
}
2023-01-15 23:11:16 +00:00
applicationIcon ? ? = extension = = ".xci" ? _xciIcon : _nspIcon ;
2022-05-15 11:30:15 +00:00
}
}
}
2023-01-15 23:11:16 +00:00
catch ( MissingKeyException )
2022-05-15 11:30:15 +00:00
{
2023-01-15 23:11:16 +00:00
applicationIcon = extension = = ".xci" ? _xciIcon : _nspIcon ;
}
catch ( InvalidDataException )
{
applicationIcon = extension = = ".xci" ? _xciIcon : _nspIcon ;
}
catch ( Exception exception )
{
Logger . Warning ? . Print ( LogClass . Application , $"The file encountered was not of a valid type. File: '{applicationPath}' Error: {exception}" ) ;
}
}
else if ( extension = = ".nro" )
{
BinaryReader reader = new ( file ) ;
2022-05-15 11:30:15 +00:00
2023-01-15 23:11:16 +00:00
byte [ ] Read ( long position , int size )
{
file . Seek ( position , SeekOrigin . Begin ) ;
2022-05-15 11:30:15 +00:00
2023-01-15 23:11:16 +00:00
return reader . ReadBytes ( size ) ;
}
2022-05-15 11:30:15 +00:00
2023-01-15 23:11:16 +00:00
try
{
file . Seek ( 24 , SeekOrigin . Begin ) ;
2022-05-15 11:30:15 +00:00
2023-01-15 23:11:16 +00:00
int assetOffset = reader . ReadInt32 ( ) ;
2022-05-15 11:30:15 +00:00
2023-01-15 23:11:16 +00:00
if ( Encoding . ASCII . GetString ( Read ( assetOffset , 4 ) ) = = "ASET" )
{
byte [ ] iconSectionInfo = Read ( assetOffset + 8 , 0x10 ) ;
2022-05-15 11:30:15 +00:00
2023-01-15 23:11:16 +00:00
long iconOffset = BitConverter . ToInt64 ( iconSectionInfo , 0 ) ;
long iconSize = BitConverter . ToInt64 ( iconSectionInfo , 8 ) ;
2022-05-15 11:30:15 +00:00
2023-01-15 23:11:16 +00:00
// Reads and stores game icon as byte array
2023-06-01 16:24:00 +00:00
if ( iconSize > 0 )
{
applicationIcon = Read ( assetOffset + iconOffset , ( int ) iconSize ) ;
}
else
{
applicationIcon = _nroIcon ;
}
2022-05-15 11:30:15 +00:00
}
2023-01-15 23:11:16 +00:00
else
2022-05-15 11:30:15 +00:00
{
2023-01-15 23:11:16 +00:00
applicationIcon = _nroIcon ;
2022-05-15 11:30:15 +00:00
}
}
2023-01-15 23:11:16 +00:00
catch
2022-05-15 11:30:15 +00:00
{
2023-01-15 23:11:16 +00:00
Logger . Warning ? . Print ( LogClass . Application , $"The file encountered was not of a valid type. Errored File: {applicationPath}" ) ;
2022-05-15 11:30:15 +00:00
}
}
2023-01-15 23:11:16 +00:00
else if ( extension = = ".nca" )
{
applicationIcon = _ncaIcon ;
}
// If its an NSO we just set defaults
else if ( extension = = ".nso" )
{
applicationIcon = _nsoIcon ;
}
2022-05-15 11:30:15 +00:00
}
}
2023-06-29 00:39:22 +00:00
catch ( Exception )
2022-05-15 11:30:15 +00:00
{
2023-01-15 23:11:16 +00:00
Logger . Warning ? . Print ( LogClass . Application , $"Could not retrieve a valid icon for the app. Default icon will be used. Errored File: {applicationPath}" ) ;
2022-05-15 11:30:15 +00:00
}
return applicationIcon ? ? _ncaIcon ;
}
2023-11-11 20:56:57 +00:00
private void GetApplicationInformation ( ref ApplicationControlProperty controlData , ref ApplicationData data )
2022-05-15 11:30:15 +00:00
{
_ = Enum . TryParse ( _desiredTitleLanguage . ToString ( ) , out TitleLanguage desiredTitleLanguage ) ;
if ( controlData . Title . ItemsRo . Length > ( int ) desiredTitleLanguage )
{
2023-11-11 20:56:57 +00:00
data . Name = controlData . Title [ ( int ) desiredTitleLanguage ] . NameString . ToString ( ) ;
data . Developer = controlData . Title [ ( int ) desiredTitleLanguage ] . PublisherString . ToString ( ) ;
2022-05-15 11:30:15 +00:00
}
else
{
2023-11-11 20:56:57 +00:00
data . Name = null ;
data . Developer = null ;
2022-05-15 11:30:15 +00:00
}
2023-11-11 20:56:57 +00:00
if ( string . IsNullOrWhiteSpace ( data . Name ) )
2022-05-15 11:30:15 +00:00
{
foreach ( ref readonly var controlTitle in controlData . Title . ItemsRo )
{
if ( ! controlTitle . NameString . IsEmpty ( ) )
{
2023-11-11 20:56:57 +00:00
data . Name = controlTitle . NameString . ToString ( ) ;
2022-05-15 11:30:15 +00:00
break ;
}
}
}
2023-11-11 20:56:57 +00:00
if ( string . IsNullOrWhiteSpace ( data . Developer ) )
2022-05-15 11:30:15 +00:00
{
foreach ( ref readonly var controlTitle in controlData . Title . ItemsRo )
{
if ( ! controlTitle . PublisherString . IsEmpty ( ) )
{
2023-11-11 20:56:57 +00:00
data . Developer = controlTitle . PublisherString . ToString ( ) ;
2022-05-15 11:30:15 +00:00
break ;
}
}
}
if ( controlData . PresenceGroupId ! = 0 )
{
2023-11-11 20:56:57 +00:00
data . Id = controlData . PresenceGroupId ;
2022-05-15 11:30:15 +00:00
}
else if ( controlData . SaveDataOwnerId ! = 0 )
{
2023-11-11 20:56:57 +00:00
data . Id = controlData . SaveDataOwnerId ;
2022-05-15 11:30:15 +00:00
}
else if ( controlData . AddOnContentBaseId ! = 0 )
{
2023-11-11 20:56:57 +00:00
data . Id = ( controlData . AddOnContentBaseId - 0x1000 ) ;
2022-05-15 11:30:15 +00:00
}
2023-11-11 20:56:57 +00:00
data . Version = controlData . DisplayVersionString . ToString ( ) ;
2022-05-15 11:30:15 +00:00
}
2023-11-11 20:56:57 +00:00
private bool IsUpdateApplied ( Nca mainNca , out IFileSystem updatedControlFs )
2022-05-15 11:30:15 +00:00
{
updatedControlFs = null ;
2023-03-31 19:16:46 +00:00
2022-05-15 11:30:15 +00:00
string updatePath = "(unknown)" ;
try
{
2023-11-11 20:56:57 +00:00
( Nca patchNca , Nca controlNca ) = mainNca . GetUpdateData ( _virtualFileSystem , _checkLevel , 0 , out updatePath ) ;
2022-05-15 11:30:15 +00:00
if ( patchNca ! = null & & controlNca ! = null )
{
2023-11-11 20:56:57 +00:00
updatedControlFs = controlNca . OpenFileSystem ( NcaSectionType . Data , IntegrityCheckLevel . None ) ;
2022-05-15 11:30:15 +00:00
return true ;
}
}
catch ( InvalidDataException )
{
2023-01-15 23:11:16 +00:00
Logger . Warning ? . Print ( LogClass . Application , $"The header key is incorrect or missing and therefore the NCA header content type check has failed. Errored File: {updatePath}" ) ;
2022-05-15 11:30:15 +00:00
}
catch ( MissingKeyException exception )
{
Logger . Warning ? . Print ( LogClass . Application , $"Your key set is missing a key with the name: {exception.Name}. Errored File: {updatePath}" ) ;
}
return false ;
}
}
2023-04-15 16:11:24 +00:00
}