2020-07-25 01:56:35 -04:00
/*
* title . c
*
* Copyright ( c ) 2020 , DarkMatterCore < pabloacurielz @ gmail . com > .
*
* This file is part of nxdumptool ( https : //github.com/DarkMatterCore/nxdumptool).
*
* nxdumptool is free software ; you can redistribute it and / or modify it
* under the terms and conditions of the GNU General Public License ,
* version 2 , as published by the Free Software Foundation .
*
* nxdumptool is distributed in the hope it will be useful , but WITHOUT
* ANY WARRANTY ; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE . See the GNU General Public License for
* more details .
*
* You should have received a copy of the GNU General Public License
* along with this program . If not , see < http : //www.gnu.org/licenses/>.
*/
# include "utils.h"
# include "title.h"
# include "gamecard.h"
# define NS_APPLICATION_RECORD_LIMIT 4096
2020-07-26 04:00:54 -04:00
/* Type definitions. */
typedef struct {
u64 title_id ;
char name [ 32 ] ;
} SystemTitleName ;
2020-07-25 01:56:35 -04:00
/* Global variables. */
static Mutex g_titleMutex = 0 ;
static bool g_titleInterfaceInit = false , g_titleGameCardAvailable = false ;
static NsApplicationControlData * g_nsAppControlData = NULL ;
static TitleApplicationMetadata * g_appMetadata = NULL ;
static u32 g_appMetadataCount = 0 ;
static NcmContentMetaDatabase g_ncmDbGameCard = { 0 } , g_ncmDbEmmcSystem = { 0 } , g_ncmDbEmmcUser = { 0 } , g_ncmDbSdCard = { 0 } ;
static NcmContentStorage g_ncmStorageGameCard = { 0 } , g_ncmStorageEmmcSystem = { 0 } , g_ncmStorageEmmcUser = { 0 } , g_ncmStorageSdCard = { 0 } ;
static TitleInfo * g_titleInfo = NULL ;
2020-07-25 14:50:42 -04:00
static u32 g_titleInfoCount = 0 , g_titleInfoGameCardStartIndex = 0 , g_titleInfoGameCardCount = 0 ;
2020-07-25 01:56:35 -04:00
2020-07-26 04:00:54 -04:00
/* Info retrieved from https://switchbrew.org/wiki/Title_list. */
/* Titles bundled with the kernel are excluded. */
static const SystemTitleName g_systemTitles [ ] = {
/* System modules. */
/* Meta + Program NCAs. */
{ 0x0100000000000006 , " usb " } ,
{ 0x0100000000000007 , " tma " } ,
{ 0x0100000000000008 , " boot2 " } ,
{ 0x0100000000000009 , " settings " } ,
{ 0x010000000000000A , " bus " } ,
{ 0x010000000000000B , " bluetooth " } ,
{ 0x010000000000000C , " bcat " } ,
{ 0x010000000000000D , " dmnt " } ,
{ 0x010000000000000E , " friends " } ,
{ 0x010000000000000F , " nifm " } ,
{ 0x0100000000000010 , " ptm " } ,
{ 0x0100000000000011 , " shell " } ,
{ 0x0100000000000012 , " bsdsockets " } ,
{ 0x0100000000000013 , " hid " } ,
{ 0x0100000000000014 , " audio " } ,
{ 0x0100000000000015 , " LogManager " } ,
{ 0x0100000000000016 , " wlan " } ,
{ 0x0100000000000017 , " cs " } ,
{ 0x0100000000000018 , " ldn " } ,
{ 0x0100000000000019 , " nvservices " } ,
{ 0x010000000000001A , " pcv " } ,
{ 0x010000000000001B , " ppc " } ,
{ 0x010000000000001C , " nvnflinger " } ,
{ 0x010000000000001D , " pcie " } ,
{ 0x010000000000001E , " account " } ,
{ 0x010000000000001F , " ns " } ,
{ 0x0100000000000020 , " nfc " } ,
{ 0x0100000000000021 , " psc " } ,
{ 0x0100000000000022 , " capsrv " } ,
{ 0x0100000000000023 , " am " } ,
{ 0x0100000000000024 , " ssl " } ,
{ 0x0100000000000025 , " nim " } ,
{ 0x0100000000000026 , " cec " } ,
{ 0x0100000000000027 , " tspm " } ,
{ 0x0100000000000029 , " lbl " } ,
{ 0x010000000000002A , " btm " } ,
{ 0x010000000000002B , " erpt " } ,
{ 0x010000000000002C , " time " } ,
{ 0x010000000000002D , " vi " } ,
{ 0x010000000000002E , " pctl " } ,
{ 0x010000000000002F , " npns " } ,
{ 0x0100000000000030 , " eupld " } ,
{ 0x0100000000000031 , " glue " } ,
{ 0x0100000000000032 , " eclct " } ,
{ 0x0100000000000033 , " es " } ,
{ 0x0100000000000034 , " fatal " } ,
{ 0x0100000000000035 , " grc " } ,
{ 0x0100000000000036 , " creport " } ,
{ 0x0100000000000037 , " ro " } ,
{ 0x0100000000000038 , " profiler " } ,
{ 0x0100000000000039 , " sdb " } ,
{ 0x010000000000003A , " migration " } ,
{ 0x010000000000003B , " jit " } ,
{ 0x010000000000003C , " jpegdec " } ,
{ 0x010000000000003D , " safemode " } ,
{ 0x010000000000003E , " olsc " } ,
{ 0x010000000000003F , " dt " } ,
{ 0x0100000000000040 , " nd " } ,
{ 0x0100000000000041 , " ngct " } ,
{ 0x0100000000000042 , " pgl " } ,
/* System data archives. */
/* Meta + Data NCAs. */
{ 0x0100000000000800 , " CertStore " } ,
{ 0x0100000000000801 , " ErrorMessage " } ,
{ 0x0100000000000802 , " MiiModel " } ,
{ 0x0100000000000803 , " BrowserDll " } ,
{ 0x0100000000000804 , " Help " } ,
{ 0x0100000000000805 , " SharedFont " } ,
{ 0x0100000000000806 , " NgWord " } ,
{ 0x0100000000000807 , " SsidList " } ,
{ 0x0100000000000808 , " Dictionary " } ,
{ 0x0100000000000809 , " SystemVersion " } ,
{ 0x010000000000080A , " AvatarImage " } ,
{ 0x010000000000080B , " LocalNews " } ,
{ 0x010000000000080C , " Eula " } ,
{ 0x010000000000080D , " UrlBlackList " } ,
{ 0x010000000000080E , " TimeZoneBinary " } ,
{ 0x010000000000080F , " CertStoreCruiser " } ,
{ 0x0100000000000810 , " FontNintendoExtension " } ,
{ 0x0100000000000811 , " FontStandard " } ,
{ 0x0100000000000812 , " FontKorean " } ,
{ 0x0100000000000813 , " FontChineseTraditional " } ,
{ 0x0100000000000814 , " FontChineseSimple " } ,
{ 0x0100000000000815 , " FontBfcpx " } ,
{ 0x0100000000000816 , " SystemUpdate " } ,
{ 0x0100000000000818 , " FirmwareDebugSettings " } ,
{ 0x0100000000000819 , " BootImagePackage " } ,
{ 0x010000000000081A , " BootImagePackageSafe " } ,
{ 0x010000000000081B , " BootImagePackageExFat " } ,
{ 0x010000000000081C , " BootImagePackageExFatSafe " } ,
{ 0x010000000000081D , " FatalMessage " } ,
{ 0x010000000000081E , " ControllerIcon " } ,
{ 0x010000000000081F , " PlatformConfigIcosa " } ,
{ 0x0100000000000820 , " PlatformConfigCopper " } ,
{ 0x0100000000000821 , " PlatformConfigHoag " } ,
{ 0x0100000000000822 , " ControllerFirmware " } ,
{ 0x0100000000000823 , " NgWord2 " } ,
{ 0x0100000000000824 , " PlatformConfigIcosaMariko " } ,
{ 0x0100000000000825 , " ApplicationBlackList " } ,
{ 0x0100000000000826 , " RebootlessSystemUpdateVersion " } ,
{ 0x0100000000000827 , " ContentActionTable " } ,
{ 0x0100000000000828 , " FunctionBlackList " } ,
{ 0x0100000000000830 , " NgWordT " } ,
/* System applets. */
/* Meta + Program NCAs. */
{ 0x0100000000001000 , " qlaunch " } ,
{ 0x0100000000001001 , " auth " } ,
{ 0x0100000000001002 , " cabinet " } ,
{ 0x0100000000001003 , " controller " } ,
{ 0x0100000000001004 , " dataErase " } ,
{ 0x0100000000001005 , " error " } ,
{ 0x0100000000001006 , " netConnect " } ,
{ 0x0100000000001007 , " playerSelect " } ,
{ 0x0100000000001008 , " swkbd " } ,
{ 0x0100000000001009 , " miiEdit " } ,
{ 0x010000000000100A , " web " } ,
{ 0x010000000000100B , " shop " } ,
{ 0x010000000000100C , " overlayDisp " } ,
{ 0x010000000000100D , " photoViewer " } ,
{ 0x010000000000100E , " set " } ,
{ 0x010000000000100F , " offlineWeb " } ,
{ 0x0100000000001010 , " loginShare " } ,
{ 0x0100000000001011 , " wifiWebAuth " } ,
{ 0x0100000000001012 , " starter " } ,
{ 0x0100000000001013 , " myPage " } ,
{ 0x0100000000001014 , " PlayReport " } ,
{ 0x0100000000001015 , " MaintenanceMenu " } ,
{ 0x010000000000101A , " gift " } ,
{ 0x010000000000101B , " DummyECApplet " } ,
{ 0x010000000000101C , " userMigration " } ,
{ 0x010000000000101D , " EncounterSys " } ,
{ 0x0100000000001020 , " story " } ,
{ 0x0100000000001023 , " statistics " } ,
{ 0x0100000000001033 , " promotion " } ,
{ 0x0100000000001038 , " sample " } ,
{ 0x0100000000001FFF , " EndOceanProgramId " } ,
/* System debug applets. */
{ 0x0100000000002000 , " A2BoardFunction " } ,
{ 0x0100000000002001 , " A3Wireless " } ,
{ 0x0100000000002002 , " C1LcdAndKey " } ,
{ 0x0100000000002003 , " C2UsbHpmic " } ,
{ 0x0100000000002004 , " C3Aging " } ,
{ 0x0100000000002005 , " C4SixAxis " } ,
{ 0x0100000000002006 , " C5Wireless " } ,
{ 0x0100000000002007 , " C7FinalCheck " } ,
{ 0x010000000000203F , " AutoCapture " } ,
{ 0x0100000000002040 , " DevMenuCommandSystem " } ,
{ 0x0100000000002041 , " recovery " } ,
{ 0x0100000000002042 , " DevMenuSystem " } ,
{ 0x0100000000002044 , " HB-TBIntegrationTest " } ,
{ 0x010000000000204D , " BackupSaveData " } ,
{ 0x010000000000204E , " A4BoardCalWriti " } ,
{ 0x0100000000002054 , " RepairSslCertificate " } ,
{ 0x0100000000002055 , " GameCardWriter " } ,
{ 0x0100000000002056 , " UsbPdTestTool " } ,
{ 0x0100000000002057 , " RepairDeletePctl " } ,
{ 0x0100000000002058 , " RepairBackup " } ,
{ 0x0100000000002059 , " RepairRestore " } ,
{ 0x010000000000205A , " RepairAccountTransfer " } ,
{ 0x010000000000205B , " RepairAutoNetworkUpdater " } ,
{ 0x010000000000205C , " RefurbishReset " } ,
{ 0x010000000000205D , " RepairAssistCup " } ,
{ 0x010000000000205E , " RepairPairingCutter " } ,
{ 0x0100000000002064 , " DevMenu " } ,
{ 0x0100000000002065 , " DevMenuApp " } ,
{ 0x0100000000002066 , " GetGameCardAsicInfo " } ,
{ 0x0100000000002068 , " NfpDebugToolSystem " } ,
{ 0x0100000000002069 , " AlbumSynchronizer " } ,
{ 0x0100000000002071 , " SnapShotDumper " } ,
{ 0x0100000000002073 , " DevMenuSystemApp " } ,
{ 0x0100000000002099 , " DevOverlayDisp " } ,
{ 0x010000000000209A , " NandVerifier " } ,
{ 0x010000000000209B , " GpuCoreDumper " } ,
{ 0x010000000000209C , " TestApplication " } ,
{ 0x010000000000209E , " HelloWorld " } ,
{ 0x01000000000020A0 , " XcieWriter " } ,
{ 0x01000000000020A1 , " GpuOverrunNotifier " } ,
{ 0x01000000000020C8 , " NfpDebugTool " } ,
{ 0x01000000000020CA , " NoftWriter " } ,
{ 0x01000000000020D0 , " BcatSystemDebugTool " } ,
{ 0x01000000000020D1 , " DevSafeModeUpdater " } ,
{ 0x01000000000020D3 , " ControllerConnectionAnalyzer " } ,
{ 0x01000000000020D4 , " DevKitUpdater " } ,
{ 0x01000000000020D6 , " RepairTimeReviser " } ,
{ 0x01000000000020D7 , " RepairReinitializeFuelGauge " } ,
{ 0x01000000000020DA , " RepairAbortMigration " } ,
{ 0x01000000000020DC , " RepairShowDeviceId " } ,
{ 0x01000000000020DD , " RepairSetCycleCountReliability " } ,
{ 0x01000000000020E0 , " Interface " } ,
{ 0x01000000000020E1 , " AlbumDownloader " } ,
{ 0x01000000000020E3 , " FuelGaugeDumper " } ,
{ 0x01000000000020E4 , " UnsafeExtract " } ,
{ 0x01000000000020E5 , " UnsafeEngrave " } ,
{ 0x01000000000020EE , " BluetoothSettingTool " } ,
{ 0x01000000000020F0 , " ApplicationInstallerRomfs " } ,
{ 0x0100000000002100 , " DevMenuLotcheckDownloader " } ,
{ 0x0100000000002101 , " DevMenuCommand " } ,
{ 0x0100000000002102 , " ExportPartition " } ,
{ 0x0100000000002103 , " SystemInitializer " } ,
{ 0x0100000000002104 , " SystemUpdaterHostFs " } ,
{ 0x0100000000002105 , " WriteToStorage " } ,
{ 0x0100000000002106 , " CalWriter " } ,
{ 0x0100000000002107 , " SettingsManager " } ,
{ 0x0100000000002109 , " testBuildSystemIris " } ,
{ 0x010000000000210A , " SystemUpdater " } ,
{ 0x010000000000210B , " nvnflinger_util " } ,
{ 0x010000000000210C , " ControllerFirmwareUpdater " } ,
{ 0x010000000000210D , " testBuildSystemNintendoWare " } ,
{ 0x0100000000002110 , " TestSaveDataCreator " } ,
{ 0x0100000000002111 , " C9LcdSpker " } ,
{ 0x0100000000002114 , " RankTurn " } ,
{ 0x0100000000002116 , " BleTestTool " } ,
{ 0x010000000000211A , " PreinstallAppWriter " } ,
{ 0x010000000000211C , " ControllerSerialFlashTool " } ,
{ 0x010000000000211D , " ControllerFlashWriter " } ,
{ 0x0100000000002120 , " ControllerTestApp " } ,
{ 0x0100000000002121 , " HidInspectionTool " } ,
{ 0x0100000000002124 , " BatteryCyclesEditor " } ,
{ 0x0100000000002125 , " UsbFirmwareUpdater " } ,
{ 0x0100000000002126 , " PalmaSerialCodeTool " } ,
{ 0x0100000000002127 , " renderdoccmd " } ,
{ 0x0100000000002128 , " HidInspectionToolProd " } ,
{ 0x010000000000212C , " ExhibitionMenu " } ,
{ 0x010000000000212F , " ExhibitionSaveData " } ,
{ 0x0100000000002130 , " LuciaConverter " } ,
{ 0x0100000000002133 , " CalDumper " } ,
{ 0x0100000000002134 , " AnalogStickEvaluationTool " } ,
/* System debug modules. */
{ 0x0100000000003002 , " DummyProcess " } ,
{ 0x0100000000003003 , " DebugMonitor0 " } ,
{ 0x0100000000003004 , " SystemHelloWorld " } ,
/* Target tools. */
{ 0x1000000000000001 , " SystemInitializer " } ,
{ 0x1000000000000004 , " CalWriter " } ,
{ 0x1000000000000005 , " DevMenuCommand " } ,
{ 0x1000000000000006 , " SettingsManager " } ,
{ 0x1000000000000007 , " ApplicationLauncer " } ,
{ 0x100000000000000B , " SnapShotDumper " } ,
{ 0x100000000000000C , " SystemUpdater " } ,
{ 0x100000000000000E , " ControllerFirmwareUpdater " } ,
/* Factory system modules. */
{ 0x010000000000B120 , " nvdbgsvc " } ,
{ 0x010000000000B14A , " manu " } ,
{ 0x010000000000B14B , " ManuUsbLoopBack " } ,
{ 0x010000000000B1B8 , " DevFwdbgHbPackage " } ,
{ 0x010000000000B1B9 , " DevFwdbgUsbPackage " } ,
{ 0x010000000000B1BA , " ProdFwdbgPackage " } ,
{ 0x010000000000B22A , " scs " } ,
{ 0x010000000000B22B , " ControllerFirmwareDebug " } ,
{ 0x010000000000B240 , " htc " } ,
{ 0x010000000000C600 , " BdkSample01 " } ,
{ 0x010000000000C601 , " BdkSample02 " } ,
{ 0x010000000000C602 , " BdkSample03 " } ,
{ 0x010000000000C603 , " BdkSample04 " } ,
{ 0x010000000000D609 , " dmnt.gen2 " } ,
/* System applications. */
{ 0x01008BB00013C000 , " flog " } ,
{ 0x0100069000078000 , " RetailInteractiveDisplayMenu " } ,
{ 0x010000B003486000 , " AudioUsbMicDebugTool " } ,
{ 0x0100458001E04000 , " BcatTestApp01 " } ,
{ 0x0100F910020F8000 , " BcatTestApp02 " } ,
{ 0x0100B7D0020FC000 , " BcatTestApp03 " } ,
{ 0x0100132002100000 , " BcatTestApp04 " } ,
{ 0x0100935002116000 , " BcatTestApp05 " } ,
{ 0x0100DA4002130000 , " BcatTestApp06 " } ,
{ 0x0100B0F002104000 , " BcatTestApp07 " } ,
{ 0x010051E002132000 , " BcatTestApp08 " } ,
{ 0x01004CB0015C8000 , " BcatTestApp09 " } ,
{ 0x01009720015CA000 , " BcatTestApp10 " } ,
{ 0x01002F20015C6000 , " BcatTestApp11 " } ,
{ 0x0100204001F90000 , " BcatTestApp12 " } ,
{ 0x0100060001F92000 , " BcatTestApp13 " } ,
{ 0x0100C26001F94000 , " BcatTestApp14 " } ,
{ 0x0100462001F96000 , " BcatTestApp15 " } ,
{ 0x01005C6001F98000 , " BcatTestApp16 " } ,
{ 0x010070000E3C0000 , " EncounterUsr " } ,
{ 0x010086000E49C000 , " EncounterUsrDummy " } ,
{ 0x0100810002D5A000 , " ShopMonitaringTool " } ,
{ 0x010023D002B98000 , " DeltaStress " }
} ;
static const u32 g_systemTitlesCount = MAX_ELEMENTS ( g_systemTitles ) ;
2020-07-25 01:56:35 -04:00
/* Function prototypes. */
2020-07-26 00:57:12 -04:00
NX_INLINE void titleFreeApplicationMetadata ( void ) ;
NX_INLINE void titleFreeTitleInfo ( void ) ;
NX_INLINE TitleApplicationMetadata * titleFindApplicationMetadataByTitleId ( u64 title_id ) ;
2020-07-25 01:56:35 -04:00
static bool titleRetrieveApplicationMetadataFromNsRecords ( void ) ;
2020-07-26 04:00:54 -04:00
static bool titleGenerateMetadataEntriesForSystemTitles ( void ) ;
2020-07-25 01:56:35 -04:00
static bool titleRetrieveApplicationMetadataByTitleId ( u64 title_id , TitleApplicationMetadata * out ) ;
static bool titleOpenNcmDatabases ( void ) ;
static void titleCloseNcmDatabases ( void ) ;
static bool titleOpenNcmStorages ( void ) ;
static void titleCloseNcmStorages ( void ) ;
2020-07-25 14:50:42 -04:00
static bool titleOpenNcmDatabaseAndStorageFromGameCard ( void ) ;
static void titleCloseNcmDatabaseAndStorageFromGameCard ( void ) ;
2020-07-25 01:56:35 -04:00
static bool titleLoadTitleInfo ( void ) ;
2020-07-26 00:57:12 -04:00
static bool titleRetrieveContentMetaKeysFromDatabase ( u8 storage_id ) ;
static bool titleGetContentInfosFromTitle ( u8 storage_id , const NcmContentMetaKey * meta_key , NcmContentInfo * * out_content_infos , u32 * out_content_count ) ;
2020-07-25 01:56:35 -04:00
2020-07-25 14:50:42 -04:00
static bool _titleRefreshGameCardTitleInfo ( bool lock ) ;
static void titleRemoveGameCardTitleInfoEntries ( void ) ;
2020-07-25 01:56:35 -04:00
bool titleInitialize ( void )
{
mutexLock ( & g_titleMutex ) ;
bool ret = g_titleInterfaceInit ;
if ( ret ) goto end ;
/* Allocate memory for the ns application control data. */
2020-07-26 00:57:12 -04:00
/* This will be used each time we need to retrieve the metadata from an application. */
2020-07-25 01:56:35 -04:00
g_nsAppControlData = calloc ( 1 , sizeof ( NsApplicationControlData ) ) ;
if ( ! g_nsAppControlData )
{
LOGFILE ( " Failed to allocate memory for the ns application control data! " ) ;
goto end ;
}
/* Retrieve application metadata from ns records. */
/* Theoretically speaking, we should only need to do this once. */
/* However, if any new gamecard is inserted while the application is running, we *will* have to retrieve the metadata from its application(s). */
if ( ! titleRetrieveApplicationMetadataFromNsRecords ( ) )
{
LOGFILE ( " Failed to retrieve application metadata from ns records! " ) ;
goto end ;
}
2020-07-26 04:00:54 -04:00
/* Generate application metadata entries for system titles, since we can't retrieve their names via ns. */
if ( ! titleGenerateMetadataEntriesForSystemTitles ( ) )
{
LOGFILE ( " Failed to generate application metadata for system titles! " ) ;
goto end ;
}
2020-07-25 01:56:35 -04:00
/* Open eMMC System, eMMC User and SD card ncm databases. */
if ( ! titleOpenNcmDatabases ( ) )
{
LOGFILE ( " Failed to open ncm databases! " ) ;
goto end ;
}
/* Open eMMC System, eMMC User and SD card ncm storages. */
if ( ! titleOpenNcmStorages ( ) )
{
LOGFILE ( " Failed to open ncm storages! " ) ;
goto end ;
}
/* Load title info by retrieving content meta keys from available eMMC System, eMMC User and SD card titles. */
if ( ! titleLoadTitleInfo ( ) )
{
LOGFILE ( " Failed to load title info! " ) ;
goto end ;
}
2020-07-25 14:50:42 -04:00
/* Initial gamecard title info retrieval. */
_titleRefreshGameCardTitleInfo ( false ) ;
2020-07-25 01:56:35 -04:00
if ( g_titleInfo & & g_titleInfoCount )
{
mkdir ( " sdmc:/records " , 0777 ) ;
FILE * title_infos_txt = NULL , * icon_jpg = NULL ;
char icon_path [ FS_MAX_PATH ] = { 0 } ;
title_infos_txt = fopen ( " sdmc:/records/title_infos.txt " , " wb " ) ;
if ( title_infos_txt )
{
for ( u32 i = 0 ; i < g_titleInfoCount ; i + + )
{
fprintf ( title_infos_txt , " Storage ID: 0x%02X \r \n " , g_titleInfo [ i ] . storage_id ) ;
fprintf ( title_infos_txt , " Title ID: %016lX \r \n " , g_titleInfo [ i ] . meta_key . id ) ;
2020-07-26 00:57:12 -04:00
fprintf ( title_infos_txt , " Version: %u (%u.%u.%u-%u.%u) \r \n " , g_titleInfo [ i ] . meta_key . version , g_titleInfo [ i ] . dot_version . TitleVersion_Major , \
g_titleInfo [ i ] . dot_version . TitleVersion_Minor , g_titleInfo [ i ] . dot_version . TitleVersion_Micro , g_titleInfo [ i ] . dot_version . TitleVersion_MajorRelstep , \
g_titleInfo [ i ] . dot_version . TitleVersion_MinorRelstep ) ;
2020-07-25 01:56:35 -04:00
fprintf ( title_infos_txt , " Type: 0x%02X \r \n " , g_titleInfo [ i ] . meta_key . type ) ;
fprintf ( title_infos_txt , " Install Type: 0x%02X \r \n " , g_titleInfo [ i ] . meta_key . install_type ) ;
2020-07-26 00:57:12 -04:00
fprintf ( title_infos_txt , " Title Size: %s (0x%lX) \r \n " , g_titleInfo [ i ] . title_size_str , g_titleInfo [ i ] . title_size ) ;
fprintf ( title_infos_txt , " Content Count: %u \r \n " , g_titleInfo [ i ] . content_count ) ;
for ( u32 j = 0 ; j < g_titleInfo [ i ] . content_count ; j + + )
{
char content_id_str [ SHA256_HASH_SIZE + 1 ] = { 0 } ;
utilsGenerateHexStringFromData ( content_id_str , sizeof ( content_id_str ) , g_titleInfo [ i ] . content_infos [ j ] . content_id . c , SHA256_HASH_SIZE / 2 ) ;
u64 content_size = 0 ;
titleConvertNcmContentSizeToU64 ( g_titleInfo [ i ] . content_infos [ j ] . size , & content_size ) ;
char content_size_str [ 32 ] = { 0 } ;
utilsGenerateFormattedSizeString ( content_size , content_size_str , sizeof ( content_size_str ) ) ;
fprintf ( title_infos_txt , " Content #%u: \r \n " , j + 1 ) ;
fprintf ( title_infos_txt , " Content ID: %s \r \n " , content_id_str ) ;
fprintf ( title_infos_txt , " Content Size: %s (0x%lX) \r \n " , content_size_str , content_size ) ;
fprintf ( title_infos_txt , " Content Type: 0x%02X \r \n " , g_titleInfo [ i ] . content_infos [ j ] . content_type ) ;
fprintf ( title_infos_txt , " ID Offset: 0x%02X \r \n " , g_titleInfo [ i ] . content_infos [ j ] . id_offset ) ;
}
2020-07-25 01:56:35 -04:00
if ( g_titleInfo [ i ] . app_metadata )
{
2020-07-26 04:00:54 -04:00
if ( strlen ( g_titleInfo [ i ] . app_metadata - > lang_entry . name ) ) fprintf ( title_infos_txt , " Application Name: %s \r \n " , g_titleInfo [ i ] . app_metadata - > lang_entry . name ) ;
if ( strlen ( g_titleInfo [ i ] . app_metadata - > lang_entry . author ) ) fprintf ( title_infos_txt , " Application Author: %s \r \n " , g_titleInfo [ i ] . app_metadata - > lang_entry . author ) ;
if ( g_titleInfo [ i ] . app_metadata - > icon_size ) fprintf ( title_infos_txt , " JPEG Icon Size: 0x%X \r \n " , g_titleInfo [ i ] . app_metadata - > icon_size ) ;
2020-07-25 01:56:35 -04:00
2020-07-26 04:00:54 -04:00
if ( g_titleInfo [ i ] . app_metadata - > icon )
2020-07-25 01:56:35 -04:00
{
sprintf ( icon_path , " sdmc:/records/%016lX.jpg " , g_titleInfo [ i ] . app_metadata - > title_id ) ;
2020-07-26 04:00:54 -04:00
2020-07-25 01:56:35 -04:00
icon_jpg = fopen ( icon_path , " wb " ) ;
if ( icon_jpg )
{
fwrite ( g_titleInfo [ i ] . app_metadata - > icon , 1 , g_titleInfo [ i ] . app_metadata - > icon_size , icon_jpg ) ;
fclose ( icon_jpg ) ;
icon_jpg = NULL ;
}
}
}
fprintf ( title_infos_txt , " \r \n " ) ;
fflush ( title_infos_txt ) ;
}
fclose ( title_infos_txt ) ;
title_infos_txt = NULL ;
}
}
ret = g_titleInterfaceInit = true ;
end :
mutexUnlock ( & g_titleMutex ) ;
return ret ;
}
void titleExit ( void )
{
mutexLock ( & g_titleMutex ) ;
2020-07-26 00:57:12 -04:00
/* Free title info. */
titleFreeTitleInfo ( ) ;
2020-07-25 14:50:42 -04:00
/* Close gamecard ncm database and storage. */
titleCloseNcmDatabaseAndStorageFromGameCard ( ) ;
2020-07-25 01:56:35 -04:00
/* Close eMMC System, eMMC User and SD card ncm storages. */
titleCloseNcmStorages ( ) ;
/* Close eMMC System, eMMC User and SD card ncm databases. */
titleCloseNcmDatabases ( ) ;
/* Free application metadata. */
2020-07-26 00:57:12 -04:00
titleFreeApplicationMetadata ( ) ;
2020-07-25 01:56:35 -04:00
/* Free ns application control data. */
if ( g_nsAppControlData ) free ( g_nsAppControlData ) ;
g_titleInterfaceInit = false ;
mutexUnlock ( & g_titleMutex ) ;
}
NcmContentMetaDatabase * titleGetNcmDatabaseByStorageId ( u8 storage_id )
{
NcmContentMetaDatabase * ncm_db = NULL ;
switch ( storage_id )
{
case NcmStorageId_GameCard :
ncm_db = & g_ncmDbGameCard ;
break ;
case NcmStorageId_BuiltInSystem :
ncm_db = & g_ncmDbEmmcSystem ;
break ;
case NcmStorageId_BuiltInUser :
ncm_db = & g_ncmDbEmmcUser ;
break ;
case NcmStorageId_SdCard :
ncm_db = & g_ncmDbSdCard ;
break ;
default :
break ;
}
return ncm_db ;
}
NcmContentStorage * titleGetNcmStorageByStorageId ( u8 storage_id )
{
NcmContentStorage * ncm_storage = NULL ;
switch ( storage_id )
{
case NcmStorageId_GameCard :
ncm_storage = & g_ncmStorageGameCard ;
break ;
case NcmStorageId_BuiltInSystem :
ncm_storage = & g_ncmStorageEmmcSystem ;
break ;
case NcmStorageId_BuiltInUser :
ncm_storage = & g_ncmStorageEmmcUser ;
break ;
case NcmStorageId_SdCard :
ncm_storage = & g_ncmStorageSdCard ;
break ;
default :
break ;
}
return ncm_storage ;
}
2020-07-25 14:50:42 -04:00
bool titleRefreshGameCardTitleInfo ( void )
{
return _titleRefreshGameCardTitleInfo ( true ) ;
}
2020-07-25 01:56:35 -04:00
2020-07-26 00:57:12 -04:00
TitleInfo * titleGetInfoFromStorageByTitleId ( u8 storage_id , u64 title_id )
{
mutexLock ( & g_titleMutex ) ;
TitleInfo * info = NULL ;
if ( ! g_titleInfo | | ! g_titleInfoCount | | storage_id < NcmStorageId_GameCard | | storage_id > NcmStorageId_Any | | ! title_id )
{
LOGFILE ( " Invalid parameters! " ) ;
goto end ;
}
for ( u32 i = 0 ; i < g_titleInfoCount ; i + + )
{
if ( g_titleInfo [ i ] . meta_key . id = = title_id & & ( storage_id = = NcmStorageId_Any | | ( storage_id ! = NcmStorageId_Any & & g_titleInfo [ i ] . storage_id = = storage_id ) ) )
{
info = & ( g_titleInfo [ i ] ) ;
break ;
}
}
if ( ! info ) LOGFILE ( " Unable to find TitleInfo entry with ID \" %016lX \" ! (storage ID %u). " , title_id , storage_id ) ;
end :
mutexUnlock ( & g_titleMutex ) ;
return info ;
}
2020-07-25 01:56:35 -04:00
2020-07-26 00:57:12 -04:00
NX_INLINE void titleFreeApplicationMetadata ( void )
{
if ( g_appMetadata )
{
2020-07-26 04:00:54 -04:00
for ( u32 i = 0 ; i < g_appMetadataCount ; i + + )
{
if ( g_appMetadata [ i ] . icon ) free ( g_appMetadata [ i ] . icon ) ;
}
2020-07-26 00:57:12 -04:00
free ( g_appMetadata ) ;
g_appMetadata = NULL ;
}
g_appMetadataCount = 0 ;
}
2020-07-25 01:56:35 -04:00
2020-07-26 00:57:12 -04:00
NX_INLINE void titleFreeTitleInfo ( void )
{
if ( g_titleInfo )
{
for ( u32 i = 0 ; i < g_titleInfoCount ; i + + )
{
if ( g_titleInfo [ i ] . content_infos ) free ( g_titleInfo [ i ] . content_infos ) ;
}
free ( g_titleInfo ) ;
g_titleInfo = NULL ;
}
g_titleInfoCount = g_titleInfoGameCardStartIndex = g_titleInfoGameCardCount = 0 ;
}
2020-07-25 01:56:35 -04:00
2020-07-26 00:57:12 -04:00
NX_INLINE TitleApplicationMetadata * titleFindApplicationMetadataByTitleId ( u64 title_id )
{
if ( ! g_appMetadata | | ! g_appMetadataCount | | ! title_id ) return NULL ;
for ( u32 i = 0 ; i < g_appMetadataCount ; i + + )
{
if ( g_appMetadata [ i ] . title_id = = title_id ) return & ( g_appMetadata [ i ] ) ;
}
return NULL ;
}
2020-07-25 01:56:35 -04:00
static bool titleRetrieveApplicationMetadataFromNsRecords ( void )
{
Result rc = 0 ;
NsApplicationRecord * app_records = NULL ;
u32 app_records_count = 0 ;
bool success = false ;
/* Allocate memory for the ns application records. */
app_records = calloc ( NS_APPLICATION_RECORD_LIMIT , sizeof ( NsApplicationRecord ) ) ;
if ( ! app_records )
{
LOGFILE ( " Failed to allocate memory for ns application records! " ) ;
goto end ;
}
/* Retrieve ns application records. */
rc = nsListApplicationRecord ( app_records , NS_APPLICATION_RECORD_LIMIT , 0 , ( s32 * ) & app_records_count ) ;
if ( R_FAILED ( rc ) )
{
LOGFILE ( " nsListApplicationRecord failed! (0x%08X). " , rc ) ;
goto end ;
}
/* Return right away if no records were retrieved. */
if ( ! app_records_count )
{
success = true ;
goto end ;
}
/* Allocate memory for the application metadata. */
g_appMetadata = calloc ( app_records_count , sizeof ( TitleApplicationMetadata ) ) ;
if ( ! g_appMetadata )
{
LOGFILE ( " Failed to allocate memory for application metadata! (%u %s). " , app_records_count , app_records_count > 1 ? " entries " : " entry " ) ;
goto end ;
}
/* Retrieve application metadata for each ns application record. */
g_appMetadataCount = 0 ;
for ( u32 i = 0 ; i < app_records_count ; i + + )
{
if ( ! titleRetrieveApplicationMetadataByTitleId ( app_records [ i ] . application_id , & ( g_appMetadata [ g_appMetadataCount ] ) ) ) continue ;
g_appMetadataCount + + ;
}
/* Check retrieved application metadata count. */
if ( ! g_appMetadataCount )
{
LOGFILE ( " Unable to retrieve application metadata from ns application records! (%u %s). " , app_records_count , app_records_count > 1 ? " entries " : " entry " ) ;
goto end ;
}
2020-07-25 14:50:42 -04:00
/* Decrease application metadata buffer size if needed. */
2020-07-25 01:56:35 -04:00
if ( g_appMetadataCount < app_records_count )
{
TitleApplicationMetadata * tmp_app_metadata = realloc ( g_appMetadata , g_appMetadataCount * sizeof ( TitleApplicationMetadata ) ) ;
if ( ! tmp_app_metadata )
{
LOGFILE ( " Failed to reallocate application metadata buffer! (%u %s). " , g_appMetadataCount , g_appMetadataCount > 1 ? " entries " : " entry " ) ;
goto end ;
}
g_appMetadata = tmp_app_metadata ;
tmp_app_metadata = NULL ;
}
success = true ;
end :
if ( ! success )
{
if ( g_appMetadata )
{
free ( g_appMetadata ) ;
g_appMetadata = NULL ;
}
g_appMetadataCount = 0 ;
}
if ( app_records ) free ( app_records ) ;
return success ;
}
2020-07-26 04:00:54 -04:00
static bool titleGenerateMetadataEntriesForSystemTitles ( void )
{
TitleApplicationMetadata * tmp_app_metadata = NULL ;
/* Reallocate application metadata buffer */
tmp_app_metadata = realloc ( g_appMetadata , ( g_appMetadataCount + g_systemTitlesCount ) * sizeof ( TitleApplicationMetadata ) ) ;
if ( ! tmp_app_metadata )
{
LOGFILE ( " Failed to reallocate application metadata buffer! (%u %s). " , g_appMetadataCount + g_systemTitlesCount , ( g_appMetadataCount + g_systemTitlesCount ) > 1 ? " entries " : " entry " ) ;
return false ;
}
g_appMetadata = tmp_app_metadata ;
tmp_app_metadata = 0 ;
/* Clear new application metadata buffer area. */
memset ( g_appMetadata + g_appMetadataCount , 0 , g_systemTitlesCount * sizeof ( TitleApplicationMetadata ) ) ;
/* Fill new application metadata entries. */
for ( u32 i = 0 ; i < g_systemTitlesCount ; i + + )
{
g_appMetadata [ g_appMetadataCount + i ] . title_id = g_systemTitles [ i ] . title_id ;
sprintf ( g_appMetadata [ g_appMetadataCount + i ] . lang_entry . name , g_systemTitles [ i ] . name ) ;
}
/* Update application metadata count. */
g_appMetadataCount + = g_systemTitlesCount ;
return true ;
}
2020-07-25 01:56:35 -04:00
static bool titleRetrieveApplicationMetadataByTitleId ( u64 title_id , TitleApplicationMetadata * out )
{
if ( ! g_nsAppControlData | | ! title_id | | ! out )
{
LOGFILE ( " Invalid parameters! " ) ;
return false ;
}
Result rc = 0 ;
u64 write_size = 0 ;
NacpLanguageEntry * lang_entry = NULL ;
2020-07-26 04:00:54 -04:00
u32 icon_size = 0 ;
u8 * icon = NULL ;
2020-07-25 01:56:35 -04:00
/* Retrieve ns application control data. */
rc = nsGetApplicationControlData ( NsApplicationControlSource_Storage , title_id , g_nsAppControlData , sizeof ( NsApplicationControlData ) , & write_size ) ;
if ( R_FAILED ( rc ) )
{
LOGFILE ( " nsGetApplicationControlData failed for title ID \" %016lX \" ! (0x%08X). " , rc , title_id ) ;
return false ;
}
if ( write_size < sizeof ( NacpStruct ) )
{
LOGFILE ( " Retrieved application control data buffer is too small! (0x%lX). " , write_size ) ;
return false ;
}
/* Get language entry. */
rc = nacpGetLanguageEntry ( & ( g_nsAppControlData - > nacp ) , & lang_entry ) ;
if ( R_FAILED ( rc ) )
{
LOGFILE ( " nacpGetLanguageEntry failed! (0x%08X). " , rc ) ;
return false ;
}
2020-07-26 04:00:54 -04:00
/* Get icon. */
icon_size = ( u32 ) ( write_size - sizeof ( NacpStruct ) ) ;
if ( icon_size )
{
icon = malloc ( icon_size ) ;
if ( ! icon )
{
LOGFILE ( " Error allocating memory for the icon buffer! (0x%X). " , icon_size ) ;
return false ;
}
memcpy ( icon , g_nsAppControlData - > icon , icon_size ) ;
}
2020-07-25 01:56:35 -04:00
/* Copy data. */
out - > title_id = title_id ;
memcpy ( & ( out - > lang_entry ) , lang_entry , sizeof ( NacpLanguageEntry ) ) ;
utilsTrimString ( out - > lang_entry . name ) ;
utilsTrimString ( out - > lang_entry . author ) ;
2020-07-26 04:00:54 -04:00
out - > icon_size = icon_size ;
out - > icon = icon ;
2020-07-25 01:56:35 -04:00
return true ;
}
static bool titleOpenNcmDatabases ( void )
{
Result rc = 0 ;
NcmContentMetaDatabase * ncm_db = NULL ;
for ( u8 i = NcmStorageId_BuiltInSystem ; i < = NcmStorageId_SdCard ; i + + )
{
/* Retrieve ncm database pointer. */
ncm_db = titleGetNcmDatabaseByStorageId ( i ) ;
if ( ! ncm_db )
{
LOGFILE ( " Failed to retrieve ncm database pointer for storage ID %u! " , i ) ;
return false ;
}
/* Check if the ncm database handle has already been retrieved. */
if ( serviceIsActive ( & ( ncm_db - > s ) ) ) continue ;
/* Open ncm database. */
rc = ncmOpenContentMetaDatabase ( ncm_db , i ) ;
if ( R_FAILED ( rc ) )
{
/* If the SD card is mounted, but it isn't currently being used by HOS, 0x21005 will be returned, so we'll just filter this particular error and continue. */
/* This can occur when using the "Nintendo" directory from a different console, or when the "sdmc:/Nintendo/Contents/private" file is corrupted. */
LOGFILE ( " ncmOpenContentMetaDatabase failed for storage ID %u! (0x%08X). " , i , rc ) ;
if ( i = = NcmStorageId_SdCard & & rc = = 0x21005 ) continue ;
return false ;
}
}
return true ;
}
static void titleCloseNcmDatabases ( void )
{
NcmContentMetaDatabase * ncm_db = NULL ;
for ( u8 i = NcmStorageId_BuiltInSystem ; i < = NcmStorageId_SdCard ; i + + )
{
/* Retrieve ncm database pointer. */
ncm_db = titleGetNcmDatabaseByStorageId ( i ) ;
if ( ! ncm_db ) continue ;
/* Check if the ncm database handle has already been retrieved. */
if ( serviceIsActive ( & ( ncm_db - > s ) ) ) ncmContentMetaDatabaseClose ( ncm_db ) ;
}
}
static bool titleOpenNcmStorages ( void )
{
Result rc = 0 ;
NcmContentStorage * ncm_storage = NULL ;
for ( u8 i = NcmStorageId_BuiltInSystem ; i < = NcmStorageId_SdCard ; i + + )
{
/* Retrieve ncm storage pointer. */
ncm_storage = titleGetNcmStorageByStorageId ( i ) ;
if ( ! ncm_storage )
{
LOGFILE ( " Failed to retrieve ncm storage pointer for storage ID %u! " , i ) ;
return false ;
}
/* Check if the ncm storage handle has already been retrieved. */
if ( serviceIsActive ( & ( ncm_storage - > s ) ) ) continue ;
/* Open ncm storage. */
rc = ncmOpenContentStorage ( ncm_storage , i ) ;
if ( R_FAILED ( rc ) )
{
/* If the SD card is mounted, but it isn't currently being used by HOS, 0x21005 will be returned, so we'll just filter this particular error and continue. */
/* This can occur when using the "Nintendo" directory from a different console, or when the "sdmc:/Nintendo/Contents/private" file is corrupted. */
LOGFILE ( " ncmOpenContentStorage failed for storage ID %u! (0x%08X). " , i , rc ) ;
if ( i = = NcmStorageId_SdCard & & rc = = 0x21005 ) continue ;
return false ;
}
}
return true ;
}
static void titleCloseNcmStorages ( void )
{
NcmContentStorage * ncm_storage = NULL ;
for ( u8 i = NcmStorageId_BuiltInSystem ; i < = NcmStorageId_SdCard ; i + + )
{
/* Retrieve ncm storage pointer. */
ncm_storage = titleGetNcmStorageByStorageId ( i ) ;
if ( ! ncm_storage ) continue ;
/* Check if the ncm storage handle has already been retrieved. */
if ( serviceIsActive ( & ( ncm_storage - > s ) ) ) ncmContentStorageClose ( ncm_storage ) ;
}
}
2020-07-25 14:50:42 -04:00
static bool titleOpenNcmDatabaseAndStorageFromGameCard ( void )
{
Result rc = 0 ;
NcmContentMetaDatabase * ncm_db = & g_ncmDbGameCard ;
NcmContentStorage * ncm_storage = & g_ncmStorageGameCard ;
/* Open ncm database. */
rc = ncmOpenContentMetaDatabase ( ncm_db , NcmStorageId_GameCard ) ;
if ( R_FAILED ( rc ) )
{
LOGFILE ( " ncmOpenContentMetaDatabase failed! (0x%08X). " , rc ) ;
goto end ;
}
/* Open ncm storage. */
rc = ncmOpenContentStorage ( ncm_storage , NcmStorageId_GameCard ) ;
if ( R_FAILED ( rc ) )
{
LOGFILE ( " ncmOpenContentStorage failed! (0x%08X). " , rc ) ;
goto end ;
}
end :
return R_SUCCEEDED ( rc ) ;
}
static void titleCloseNcmDatabaseAndStorageFromGameCard ( void )
{
NcmContentMetaDatabase * ncm_db = & g_ncmDbGameCard ;
NcmContentStorage * ncm_storage = & g_ncmStorageGameCard ;
/* Check if the ncm database handle has already been retrieved. */
if ( serviceIsActive ( & ( ncm_db - > s ) ) ) ncmContentMetaDatabaseClose ( ncm_db ) ;
/* Check if the ncm storage handle has already been retrieved. */
if ( serviceIsActive ( & ( ncm_storage - > s ) ) ) ncmContentStorageClose ( ncm_storage ) ;
}
2020-07-25 01:56:35 -04:00
static bool titleLoadTitleInfo ( void )
{
/* Return right away if title info has already been retrieved. */
if ( g_titleInfo | | g_titleInfoCount ) return true ;
g_titleInfoCount = 0 ;
for ( u8 i = NcmStorageId_BuiltInSystem ; i < = NcmStorageId_SdCard ; i + + )
{
2020-07-26 00:57:12 -04:00
/* Retrieve content meta keys from the current storage. */
if ( ! titleRetrieveContentMetaKeysFromDatabase ( i ) )
2020-07-25 01:56:35 -04:00
{
LOGFILE ( " Failed to retrieve content meta keys from storage ID %u! " , i ) ;
return false ;
}
}
return true ;
}
2020-07-26 00:57:12 -04:00
static bool titleRetrieveContentMetaKeysFromDatabase ( u8 storage_id )
2020-07-25 01:56:35 -04:00
{
2020-07-26 00:57:12 -04:00
NcmContentMetaDatabase * ncm_db = NULL ;
if ( ! ( ncm_db = titleGetNcmDatabaseByStorageId ( storage_id ) ) | | ! serviceIsActive ( & ( ncm_db - > s ) ) )
2020-07-25 01:56:35 -04:00
{
LOGFILE ( " Invalid parameters! " ) ;
return false ;
}
Result rc = 0 ;
u32 written = 0 , total = 0 ;
NcmContentMetaKey * meta_keys = NULL , * meta_keys_tmp = NULL ;
size_t meta_keys_size = sizeof ( NcmContentMetaKey ) ;
TitleInfo * tmp_title_info = NULL ;
bool success = false ;
/* Allocate memory for the ncm application content meta keys. */
meta_keys = calloc ( 1 , meta_keys_size ) ;
if ( ! meta_keys )
{
LOGFILE ( " Unable to allocate memory for the ncm application meta keys! " ) ;
goto end ;
}
/* Get a full list of all titles available in this storage. */
/* Meta type '0' means all title types will be retrieved. */
rc = ncmContentMetaDatabaseList ( ncm_db , ( s32 * ) & total , ( s32 * ) & written , meta_keys , 1 , 0 , 0 , 0 , - 1 , NcmContentInstallType_Full ) ;
if ( R_FAILED ( rc ) )
{
LOGFILE ( " ncmContentMetaDatabaseList failed! (0x%08X) (first entry). " , rc ) ;
goto end ;
}
/* Check if our application meta keys buffer was actually filled. */
/* If it wasn't, odds are there are no titles in this storage. */
if ( ! written | | ! total )
{
success = true ;
goto end ;
}
/* Check if we need to resize our application meta keys buffer. */
if ( total > written )
{
/* Update application meta keys buffer size. */
meta_keys_size * = total ;
/* Reallocate application meta keys buffer. */
meta_keys_tmp = realloc ( meta_keys , meta_keys_size ) ;
if ( ! meta_keys_tmp )
{
LOGFILE ( " Unable to reallocate application meta keys buffer! (%u entries). " , total ) ;
goto end ;
}
meta_keys = meta_keys_tmp ;
meta_keys_tmp = NULL ;
/* Issue call again. */
rc = ncmContentMetaDatabaseList ( ncm_db , ( s32 * ) & total , ( s32 * ) & written , meta_keys , ( s32 ) total , 0 , 0 , 0 , - 1 , NcmContentInstallType_Full ) ;
if ( R_FAILED ( rc ) )
{
LOGFILE ( " ncmContentMetaDatabaseList failed! (0x%08X) (%u %s). " , rc , total , total > 1 ? " entries " : " entry " ) ;
goto end ;
}
/* Safety check. */
if ( written ! = total )
{
LOGFILE ( " Application meta key count mismatch! (%u != %u). " , written , total ) ;
goto end ;
}
}
/* Reallocate title info buffer. */
/* If g_titleInfo == NULL, realloc() will essentially act as a malloc(). */
tmp_title_info = realloc ( g_titleInfo , ( g_titleInfoCount + total ) * sizeof ( TitleInfo ) ) ;
if ( ! tmp_title_info )
{
LOGFILE ( " Unable to reallocate title info buffer! (%u %s). " , g_titleInfoCount + total , ( g_titleInfoCount + total ) > 1 ? " entries " : " entry " ) ;
goto end ;
}
g_titleInfo = tmp_title_info ;
tmp_title_info = NULL ;
/* Clear new title info buffer area. */
memset ( g_titleInfo + g_titleInfoCount , 0 , total * sizeof ( TitleInfo ) ) ;
/* Fill new title info entries. */
for ( u32 i = 0 ; i < total ; i + + )
{
TitleInfo * cur_title_info = & ( g_titleInfo [ g_titleInfoCount + i ] ) ;
2020-07-26 00:57:12 -04:00
/* Fill information. */
2020-07-25 01:56:35 -04:00
cur_title_info - > storage_id = storage_id ;
2020-07-26 00:57:12 -04:00
memcpy ( & ( cur_title_info - > dot_version ) , & ( meta_keys [ i ] . version ) , sizeof ( u32 ) ) ;
2020-07-25 01:56:35 -04:00
memcpy ( & ( cur_title_info - > meta_key ) , & ( meta_keys [ i ] ) , sizeof ( NcmContentMetaKey ) ) ;
cur_title_info - > app_metadata = titleFindApplicationMetadataByTitleId ( meta_keys [ i ] . id ) ;
2020-07-26 00:57:12 -04:00
/* Retrieve content infos. */
if ( titleGetContentInfosFromTitle ( storage_id , & ( meta_keys [ i ] ) , & ( cur_title_info - > content_infos ) , & ( cur_title_info - > content_count ) ) )
{
/* Calculate title size. */
for ( u32 j = 0 ; j < cur_title_info - > content_count ; j + + )
{
u64 tmp_size = 0 ;
titleConvertNcmContentSizeToU64 ( cur_title_info - > content_infos [ j ] . size , & tmp_size ) ;
cur_title_info - > title_size + = tmp_size ;
}
}
/* Generate formatted title size string. */
utilsGenerateFormattedSizeString ( cur_title_info - > title_size , cur_title_info - > title_size_str , sizeof ( cur_title_info - > title_size_str ) ) ;
2020-07-25 01:56:35 -04:00
}
/* Update title info count. */
g_titleInfoCount + = total ;
success = true ;
end :
if ( meta_keys ) free ( meta_keys ) ;
return success ;
}
2020-07-26 00:57:12 -04:00
static bool titleGetContentInfosFromTitle ( u8 storage_id , const NcmContentMetaKey * meta_key , NcmContentInfo * * out_content_infos , u32 * out_content_count )
{
NcmContentMetaDatabase * ncm_db = NULL ;
if ( ! ( ncm_db = titleGetNcmDatabaseByStorageId ( storage_id ) ) | | ! serviceIsActive ( & ( ncm_db - > s ) ) | | ! meta_key | | ! out_content_infos | | ! out_content_count )
{
LOGFILE ( " Invalid parameters! " ) ;
return false ;
}
Result rc = 0 ;
NcmContentMetaHeader content_meta_header = { 0 } ;
u64 content_meta_header_read_size = 0 ;
NcmContentInfo * content_infos = NULL ;
u32 content_count = 0 , written = 0 ;
bool success = false ;
/* Retrieve content meta header. */
rc = ncmContentMetaDatabaseGet ( ncm_db , meta_key , & content_meta_header_read_size , & content_meta_header , sizeof ( NcmContentMetaHeader ) ) ;
if ( R_FAILED ( rc ) )
{
LOGFILE ( " ncmContentMetaDatabaseGet failed! (0x%08X). " , rc ) ;
goto end ;
}
if ( content_meta_header_read_size ! = sizeof ( NcmContentMetaHeader ) )
{
LOGFILE ( " Content meta header size mismatch! (0x%lX != 0x%lX). " , rc , content_meta_header_read_size , sizeof ( NcmContentMetaHeader ) ) ;
goto end ;
}
/* Get content count. */
content_count = ( u32 ) content_meta_header . content_count ;
if ( ! content_count )
{
LOGFILE ( " Content count is zero! " ) ;
goto end ;
}
/* Allocate memory for the content infos. */
content_infos = calloc ( content_count , sizeof ( NcmContentInfo ) ) ;
if ( ! content_infos )
{
LOGFILE ( " Unable to allocate memory for the content infos buffer! (%u content[s]). " , content_count ) ;
goto end ;
}
/* Retrieve content infos. */
rc = ncmContentMetaDatabaseListContentInfo ( ncm_db , ( s32 * ) & written , content_infos , ( s32 ) content_count , meta_key , 0 ) ;
if ( R_FAILED ( rc ) )
{
LOGFILE ( " ncmContentMetaDatabaseListContentInfo failed! (0x%08X). " , rc ) ;
goto end ;
}
if ( written ! = content_count )
{
LOGFILE ( " Content count mismatch! (%u != %u). " , written , content_count ) ;
goto end ;
}
/* Update output. */
* out_content_infos = content_infos ;
* out_content_count = content_count ;
success = true ;
end :
if ( ! success & & content_infos ) free ( content_infos ) ;
return success ;
}
2020-07-25 14:50:42 -04:00
static bool _titleRefreshGameCardTitleInfo ( bool lock )
{
if ( lock ) mutexLock ( & g_titleMutex ) ;
TitleApplicationMetadata * tmp_app_metadata = NULL ;
u32 orig_app_count = g_appMetadataCount , cur_app_count = g_appMetadataCount , gamecard_app_count = 0 , gamecard_metadata_count = 0 ;
bool status = false , success = false , cleanup = true ;
/* Retrieve current gamecard status. */
status = ( gamecardGetStatus ( ) = = GameCardStatus_InsertedAndInfoLoaded ) ;
if ( status = = g_titleGameCardAvailable | | ! status )
{
cleanup = ( status ! = g_titleGameCardAvailable ) ;
goto end ;
}
/* Open gamecard ncm database and storage handles. */
if ( ! titleOpenNcmDatabaseAndStorageFromGameCard ( ) )
{
LOGFILE ( " Failed to open gamecard ncm database and storage handles. " ) ;
goto end ;
}
/* Update start index for the gamecard title info entries. */
g_titleInfoGameCardStartIndex = g_titleInfoCount ;
/* Retrieve content meta keys from the gamecard ncm database. */
2020-07-26 00:57:12 -04:00
if ( ! titleRetrieveContentMetaKeysFromDatabase ( NcmStorageId_GameCard ) )
2020-07-25 14:50:42 -04:00
{
LOGFILE ( " Failed to retrieve content meta keys from gamecard! " ) ;
goto end ;
}
/* Update gamecard title info count. */
g_titleInfoGameCardCount = ( g_titleInfoCount - g_titleInfoGameCardStartIndex ) ;
if ( ! g_titleInfoGameCardCount )
{
LOGFILE ( " Empty content meta key count from gamecard! " ) ;
goto end ;
}
/* Retrieve gamecard application metadata. */
for ( u32 i = g_titleInfoGameCardStartIndex ; i < g_titleInfoCount ; i + + )
{
TitleInfo * cur_title_info = & ( g_titleInfo [ i ] ) ;
/* Skip current title if it's not an application. */
if ( cur_title_info - > meta_key . type ! = NcmContentMetaType_Application ) continue ;
gamecard_app_count + + ;
/* Check if we already have an application metadata entry for this title ID. */
if ( ( cur_title_info - > app_metadata = titleFindApplicationMetadataByTitleId ( cur_title_info - > meta_key . id ) ) ! = NULL )
{
gamecard_metadata_count + + ;
continue ;
}
/* Reallocate application metadata buffer (if needed). */
if ( cur_app_count < ( g_appMetadataCount + 1 ) )
{
tmp_app_metadata = realloc ( g_appMetadata , ( g_appMetadataCount + 1 ) * sizeof ( TitleApplicationMetadata ) ) ;
if ( ! tmp_app_metadata )
{
LOGFILE ( " Failed to reallocate application metadata buffer! (additional entry). " ) ;
goto end ;
}
g_appMetadata = tmp_app_metadata ;
tmp_app_metadata = NULL ;
cur_app_count + + ;
}
/* Retrieve application metadata. */
if ( ! titleRetrieveApplicationMetadataByTitleId ( cur_title_info - > meta_key . id , & ( g_appMetadata [ g_appMetadataCount ] ) ) ) continue ;
cur_title_info - > app_metadata = & ( g_appMetadata [ g_appMetadataCount ] ) ;
g_appMetadataCount + + ;
gamecard_metadata_count + + ;
}
/* Check gamecard application count. */
if ( ! gamecard_app_count )
{
LOGFILE ( " Gamecard application count is zero! " ) ;
goto end ;
}
/* Check retrieved application metadata count. */
if ( ! gamecard_metadata_count )
{
LOGFILE ( " Unable to retrieve application metadata from gamecard! (%u %s). " , gamecard_app_count , gamecard_app_count > 1 ? " entries " : " entry " ) ;
goto end ;
}
success = true ;
cleanup = false ;
end :
/* Update gamecard status. */
g_titleGameCardAvailable = status ;
/* Decrease application metadata buffer size if needed. */
if ( ( success & & g_appMetadataCount < cur_app_count ) | | ( ! success & & g_appMetadataCount > orig_app_count ) )
{
if ( ! success ) g_appMetadataCount = orig_app_count ;
tmp_app_metadata = realloc ( g_appMetadata , g_appMetadataCount * sizeof ( TitleApplicationMetadata ) ) ;
if ( tmp_app_metadata )
{
g_appMetadata = tmp_app_metadata ;
tmp_app_metadata = NULL ;
}
}
if ( cleanup )
{
titleRemoveGameCardTitleInfoEntries ( ) ;
titleCloseNcmDatabaseAndStorageFromGameCard ( ) ;
}
if ( lock ) mutexUnlock ( & g_titleMutex ) ;
return success ;
}
static void titleRemoveGameCardTitleInfoEntries ( void )
{
if ( ! g_titleInfo | | ! g_titleInfoCount | | ! g_titleInfoGameCardCount | | g_titleInfoGameCardCount > g_titleInfoCount | | \
g_titleInfoGameCardStartIndex ! = ( g_titleInfoCount - g_titleInfoGameCardCount ) ) return ;
if ( g_titleInfoGameCardCount = = g_titleInfoCount )
{
2020-07-26 00:57:12 -04:00
titleFreeTitleInfo ( ) ;
2020-07-25 14:50:42 -04:00
} else {
2020-07-26 00:57:12 -04:00
for ( u32 i = ( g_titleInfoCount - g_titleInfoGameCardCount ) ; i < g_titleInfoCount ; i + + )
{
if ( g_titleInfo [ i ] . content_infos ) free ( g_titleInfo [ i ] . content_infos ) ;
}
2020-07-25 14:50:42 -04:00
TitleInfo * tmp_title_info = realloc ( g_titleInfo , ( g_titleInfoCount - g_titleInfoGameCardCount ) * sizeof ( TitleInfo ) ) ;
if ( tmp_title_info )
{
g_titleInfo = tmp_title_info ;
tmp_title_info = NULL ;
}
2020-07-26 00:57:12 -04:00
g_titleInfoCount = ( g_titleInfoCount - g_titleInfoGameCardCount ) ;
g_titleInfoGameCardStartIndex = g_titleInfoGameCardCount = 0 ;
2020-07-25 14:50:42 -04:00
}
2020-07-25 01:56:35 -04:00
}