2020-07-25 06:56:35 +01:00
/*
* title . c
*
2020-12-23 17:48:57 +00:00
* Copyright ( c ) 2020 - 2021 , DarkMatterCore < pabloacurielz @ gmail . com > .
2020-07-25 06:56:35 +01:00
*
* 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 09:00:54 +01:00
/* Type definitions. */
typedef struct {
u64 title_id ;
char name [ 32 ] ;
} SystemTitleName ;
2020-07-25 06:56:35 +01:00
/* Global variables. */
static Mutex g_titleMutex = 0 ;
2020-08-18 06:04:13 +01:00
static Thread g_titleGameCardInfoThread = { 0 } ;
2020-10-27 21:23:19 +00:00
static UEvent g_titleGameCardInfoThreadExitEvent = { 0 } , * g_titleGameCardStatusChangeUserEvent = NULL , g_titleGameCardUpdateInfoUserEvent = { 0 } ;
static CondVar g_gameCardCondVar = 0 ;
2020-07-30 22:43:50 +01:00
static bool g_titleInterfaceInit = false , g_titleGameCardInfoThreadCreated = false , g_titleGameCardAvailable = false , g_titleGameCardInfoUpdated = false ;
2020-07-25 06:56:35 +01:00
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-30 23:37:45 +01:00
static u32 g_titleInfoCount = 0 , g_titleInfoGameCardStartIndex = 0 , g_titleInfoGameCardCount = 0 , g_titleInfoOrphanCount = 0 ;
2020-07-25 06:56:35 +01:00
2020-07-29 22:02:21 +01:00
static const char * g_titleNcmContentTypeNames [ ] = {
[ NcmContentType_Meta ] = " Meta " ,
[ NcmContentType_Program ] = " Program " ,
[ NcmContentType_Data ] = " Data " ,
[ NcmContentType_Control ] = " Control " ,
[ NcmContentType_HtmlDocument ] = " HtmlDocument " ,
[ NcmContentType_LegalInformation ] = " LegalInformation " ,
2020-10-03 18:09:29 +01:00
[ NcmContentType_DeltaFragment ] = " DeltaFragment "
2020-07-29 22:02:21 +01:00
} ;
2020-08-14 03:31:02 +01:00
static const char * g_titleNcmContentMetaTypeNames [ ] = {
[ NcmContentMetaType_Unknown ] = " Unknown " ,
[ NcmContentMetaType_SystemProgram ] = " SystemProgram " ,
[ NcmContentMetaType_SystemData ] = " SystemData " ,
[ NcmContentMetaType_SystemUpdate ] = " SystemUpdate " ,
[ NcmContentMetaType_BootImagePackage ] = " BootImagePackage " ,
[ NcmContentMetaType_BootImagePackageSafe ] = " BootImagePackageSafe " ,
[ NcmContentMetaType_Application - 0x7A ] = " Application " ,
[ NcmContentMetaType_Patch - 0x7A ] = " Patch " ,
[ NcmContentMetaType_AddOnContent - 0x7A ] = " AddOnContent " ,
[ NcmContentMetaType_Delta - 0x7A ] = " Delta "
} ;
2020-10-22 05:38:14 +01:00
static const char * g_filenameTypeStrings [ ] = {
[ NcmContentMetaType_Application - 0x80 ] = " BASE " ,
[ NcmContentMetaType_Patch - 0x80 ] = " UPD " ,
[ NcmContentMetaType_AddOnContent - 0x80 ] = " DLC " ,
[ NcmContentMetaType_Delta - 0x80 ] = " DELTA "
} ;
2020-07-26 09:00:54 +01: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 " } ,
2020-12-04 06:38:44 +00:00
{ 0x010000000000001B , " capmtp " } ,
2020-07-26 09:00:54 +01:00
{ 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 " } ,
2020-12-04 06:38:44 +00:00
{ 0x010000000000B240 , " htc.stub " } ,
2020-07-26 09:00:54 +01:00
{ 0x010000000000C600 , " BdkSample01 " } ,
{ 0x010000000000C601 , " BdkSample02 " } ,
{ 0x010000000000C602 , " BdkSample03 " } ,
{ 0x010000000000C603 , " BdkSample04 " } ,
{ 0x010000000000D609 , " dmnt.gen2 " } ,
2020-12-04 06:38:44 +00:00
{ 0x010000000000D623 , " DevServer " } ,
2020-07-26 09:00:54 +01:00
/* 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 06:56:35 +01:00
/* Function prototypes. */
2020-07-26 05:57:12 +01:00
NX_INLINE void titleFreeApplicationMetadata ( void ) ;
NX_INLINE void titleFreeTitleInfo ( void ) ;
NX_INLINE TitleApplicationMetadata * titleFindApplicationMetadataByTitleId ( u64 title_id ) ;
2020-07-28 04:32:08 +01:00
static bool titleGenerateMetadataEntriesFromSystemTitles ( void ) ;
static bool titleGenerateMetadataEntriesFromNsRecords ( void ) ;
2020-07-25 06:56:35 +01: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 19:50:42 +01:00
static bool titleOpenNcmDatabaseAndStorageFromGameCard ( void ) ;
static void titleCloseNcmDatabaseAndStorageFromGameCard ( void ) ;
2020-07-30 22:43:50 +01:00
static bool titleLoadPersistentStorageTitleInfo ( void ) ;
2021-02-22 21:30:47 +00:00
static bool titleGenerateTitleInfoFromStorage ( u8 storage_id ) ;
2020-07-26 05:57:12 +01:00
static bool titleGetContentInfosFromTitle ( u8 storage_id , const NcmContentMetaKey * meta_key , NcmContentInfo * * out_content_infos , u32 * out_content_count ) ;
2021-02-22 21:30:47 +00:00
static void titleUpdateTitleInfoLinkedLists ( void ) ;
2020-07-25 06:56:35 +01:00
2020-07-30 22:43:50 +01:00
static bool titleCreateGameCardInfoThread ( void ) ;
static void titleDestroyGameCardInfoThread ( void ) ;
2020-08-18 06:04:13 +01:00
static void titleGameCardInfoThreadFunc ( void * arg ) ;
2020-07-30 22:43:50 +01:00
static bool titleRefreshGameCardTitleInfo ( void ) ;
2020-07-25 19:50:42 +01:00
static void titleRemoveGameCardTitleInfoEntries ( void ) ;
2020-07-28 04:32:08 +01:00
static bool titleIsUserApplicationContentAvailable ( u64 app_id ) ;
static TitleInfo * _titleGetInfoFromStorageByTitleId ( u8 storage_id , u64 title_id , bool lock ) ;
2020-08-21 01:18:05 +01:00
static int titleSystemTitleMetadataEntrySortFunction ( const void * a , const void * b ) ;
static int titleUserApplicationMetadataEntrySortFunction ( const void * a , const void * b ) ;
static int titleOrphanTitleInfoSortFunction ( const void * a , const void * b ) ;
2020-07-28 04:32:08 +01:00
2020-07-25 06:56:35 +01: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 05:57:12 +01:00
/* This will be used each time we need to retrieve the metadata from an application. */
2020-07-25 06:56:35 +01:00
g_nsAppControlData = calloc ( 1 , sizeof ( NsApplicationControlData ) ) ;
if ( ! g_nsAppControlData )
{
LOGFILE ( " Failed to allocate memory for the ns application control data! " ) ;
goto end ;
}
2020-07-28 04:32:08 +01:00
/* Generate application metadata entries from hardcoded system titles, since we can't retrieve their names via ns. */
if ( ! titleGenerateMetadataEntriesFromSystemTitles ( ) )
2020-07-25 06:56:35 +01:00
{
2020-07-28 04:32:08 +01:00
LOGFILE ( " Failed to generate application metadata from hardcoded system titles! " ) ;
2020-07-25 06:56:35 +01:00
goto end ;
}
2020-07-28 04:32:08 +01:00
/* Generate application metadata entries 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 ( ! titleGenerateMetadataEntriesFromNsRecords ( ) )
2020-07-26 09:00:54 +01:00
{
2020-07-28 04:32:08 +01:00
LOGFILE ( " Failed to generate application metadata from ns records! " ) ;
2020-07-26 09:00:54 +01:00
goto end ;
}
2020-07-25 06:56:35 +01: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. */
2020-07-30 22:43:50 +01:00
if ( ! titleLoadPersistentStorageTitleInfo ( ) )
2020-07-25 06:56:35 +01:00
{
2020-07-30 22:43:50 +01:00
LOGFILE ( " Failed to load persistent storage title info! " ) ;
2020-07-25 06:56:35 +01:00
goto end ;
}
2020-11-28 06:38:53 +00:00
/* Create user-mode exit event. */
2020-07-30 22:43:50 +01:00
ueventCreate ( & g_titleGameCardInfoThreadExitEvent , true ) ;
2020-07-25 06:56:35 +01:00
2020-07-30 22:43:50 +01:00
/* Retrieve gamecard status change user event. */
g_titleGameCardStatusChangeUserEvent = gamecardGetStatusChangeUserEvent ( ) ;
if ( ! g_titleGameCardStatusChangeUserEvent )
{
LOGFILE ( " Failed to retrieve gamecard status change user event! " ) ;
goto end ;
}
2020-07-25 06:56:35 +01:00
2020-11-28 06:38:53 +00:00
/* Create user-mode gamecard update info event. */
2020-10-27 21:23:19 +00:00
ueventCreate ( & g_titleGameCardUpdateInfoUserEvent , true ) ;
2020-07-30 22:43:50 +01:00
/* Create gamecard title info thread. */
if ( ! ( g_titleGameCardInfoThreadCreated = titleCreateGameCardInfoThread ( ) ) ) goto end ;
2020-07-25 06:56:35 +01:00
ret = g_titleInterfaceInit = true ;
end :
mutexUnlock ( & g_titleMutex ) ;
return ret ;
}
void titleExit ( void )
{
mutexLock ( & g_titleMutex ) ;
2020-07-30 22:43:50 +01:00
/* Destroy gamecard detection thread. */
if ( g_titleGameCardInfoThreadCreated )
{
titleDestroyGameCardInfoThread ( ) ;
g_titleGameCardInfoThreadCreated = false ;
}
2020-07-26 05:57:12 +01:00
/* Free title info. */
titleFreeTitleInfo ( ) ;
2020-07-25 19:50:42 +01:00
2020-07-30 22:43:50 +01:00
/* Close gamecard ncm database and storage (if needed). */
2020-07-25 19:50:42 +01:00
titleCloseNcmDatabaseAndStorageFromGameCard ( ) ;
2020-07-25 06:56:35 +01: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 05:57:12 +01:00
titleFreeApplicationMetadata ( ) ;
2020-07-25 06:56:35 +01: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-28 04:32:08 +01:00
TitleApplicationMetadata * * titleGetApplicationMetadataEntries ( bool is_system , u32 * out_count )
{
mutexLock ( & g_titleMutex ) ;
u32 start_idx = ( is_system ? 0 : g_systemTitlesCount ) ;
u32 max_val = ( is_system ? g_systemTitlesCount : g_appMetadataCount ) ;
u32 app_count = 0 ;
TitleApplicationMetadata * * app_metadata = NULL , * * tmp_app_metadata = NULL ;
2021-02-12 20:35:23 +00:00
if ( ! g_titleInterfaceInit | | ! g_appMetadata | | ( is_system & & g_appMetadataCount < g_systemTitlesCount ) | | ( ! is_system & & g_appMetadataCount = = g_systemTitlesCount ) | | ! out_count )
2020-07-28 04:32:08 +01:00
{
LOGFILE ( " Invalid parameters! " ) ;
goto end ;
}
for ( u32 i = start_idx ; i < max_val ; i + + )
{
2020-10-14 19:58:33 +01:00
TitleApplicationMetadata * cur_app_metadata = & ( g_appMetadata [ i ] ) ;
2020-07-28 04:32:08 +01:00
/* Skip current metadata entry if content data for this title isn't available. */
2020-10-14 19:58:33 +01:00
if ( ( is_system & & ! _titleGetInfoFromStorageByTitleId ( NcmStorageId_BuiltInSystem , cur_app_metadata - > title_id , false ) ) | | \
( ! is_system & & ! titleIsUserApplicationContentAvailable ( cur_app_metadata - > title_id ) ) ) continue ;
2020-07-28 04:32:08 +01:00
/* Reallocate pointer buffer. */
tmp_app_metadata = realloc ( app_metadata , ( app_count + 1 ) * sizeof ( TitleApplicationMetadata * ) ) ;
if ( ! tmp_app_metadata )
{
LOGFILE ( " Failed to reallocate application metadata pointer buffer! " ) ;
if ( app_metadata ) free ( app_metadata ) ;
app_metadata = NULL ;
goto end ;
}
app_metadata = tmp_app_metadata ;
tmp_app_metadata = NULL ;
/* Set current pointer and increase counter. */
2020-10-14 19:58:33 +01:00
app_metadata [ app_count + + ] = cur_app_metadata ;
2020-07-28 04:32:08 +01:00
}
2020-12-24 08:49:38 +00:00
/* Update output counter. */
* out_count = app_count ;
if ( ! app_metadata | | ! app_count ) LOGFILE ( " No content data found for %s! " , is_system ? " system titles " : " user applications " ) ;
2020-07-28 04:32:08 +01:00
end :
mutexUnlock ( & g_titleMutex ) ;
return app_metadata ;
}
2020-07-26 05:57:12 +01:00
TitleInfo * titleGetInfoFromStorageByTitleId ( u8 storage_id , u64 title_id )
2020-07-28 04:32:08 +01:00
{
return _titleGetInfoFromStorageByTitleId ( storage_id , title_id , true ) ;
}
bool titleGetUserApplicationData ( u64 app_id , TitleUserApplicationData * out )
2020-07-26 05:57:12 +01:00
{
mutexLock ( & g_titleMutex ) ;
2020-07-28 04:32:08 +01:00
bool success = false ;
2020-07-26 05:57:12 +01:00
2021-02-12 20:35:23 +00:00
if ( ! g_titleInterfaceInit | | ! out )
2020-07-26 05:57:12 +01:00
{
LOGFILE ( " Invalid parameters! " ) ;
goto end ;
}
2020-07-28 04:32:08 +01:00
/* Clear output. */
memset ( out , 0 , sizeof ( TitleUserApplicationData ) ) ;
2020-08-13 19:12:33 +01:00
/* Get first user application title info. */
2020-07-28 04:32:08 +01:00
out - > app_info = _titleGetInfoFromStorageByTitleId ( NcmStorageId_Any , app_id , false ) ;
/* Get first patch title info. */
out - > patch_info = _titleGetInfoFromStorageByTitleId ( NcmStorageId_Any , titleGetPatchIdByApplicationId ( app_id ) , false ) ;
/* Get first add-on content title info. */
2020-07-26 05:57:12 +01:00
for ( u32 i = 0 ; i < g_titleInfoCount ; i + + )
{
2020-10-14 19:58:33 +01:00
TitleInfo * title_info = & ( g_titleInfo [ i ] ) ;
if ( title_info - > meta_key . type = = NcmContentMetaType_AddOnContent & & titleCheckIfAddOnContentIdBelongsToApplicationId ( app_id , title_info - > meta_key . id ) )
2020-07-26 05:57:12 +01:00
{
2020-10-14 19:58:33 +01:00
out - > aoc_info = title_info ;
2020-07-26 05:57:12 +01:00
break ;
}
}
2020-07-28 04:32:08 +01:00
/* Check retrieved title info. */
success = ( out - > app_info | | out - > patch_info | | out - > aoc_info ) ;
if ( ! success )
{
LOGFILE ( " Failed to retrieve user application data for ID \" %016lX \" ! " , app_id ) ;
goto end ;
}
2020-07-26 05:57:12 +01:00
end :
mutexUnlock ( & g_titleMutex ) ;
2020-07-28 04:32:08 +01:00
return success ;
2020-07-26 05:57:12 +01:00
}
2020-07-25 06:56:35 +01:00
2020-07-30 23:37:45 +01:00
bool titleAreOrphanTitlesAvailable ( void )
{
mutexLock ( & g_titleMutex ) ;
2021-02-12 20:35:23 +00:00
bool ret = ( g_titleInterfaceInit & & g_titleInfoOrphanCount > 0 ) ;
2020-07-30 23:37:45 +01:00
mutexUnlock ( & g_titleMutex ) ;
return ret ;
}
TitleInfo * * titleGetInfoFromOrphanTitles ( u32 * out_count )
{
mutexLock ( & g_titleMutex ) ;
TitleInfo * * orphan_info = NULL ;
2021-02-12 20:35:23 +00:00
if ( ! g_titleInterfaceInit | | ! g_titleInfo | | ! g_titleInfoCount | | ! g_titleInfoOrphanCount | | ! out_count )
2020-07-30 23:37:45 +01:00
{
LOGFILE ( " Invalid parameters! " ) ;
goto end ;
}
/* Allocate orphan title info buffer. */
orphan_info = calloc ( g_titleInfoOrphanCount , sizeof ( TitleInfo * ) ) ;
if ( ! orphan_info )
{
LOGFILE ( " Failed to allocate memory for orphan title info buffer! " ) ;
goto end ;
}
/* Get pointers to orphan title info entries. */
for ( u32 i = 0 , j = 0 ; i < g_titleInfoCount & & j < g_titleInfoOrphanCount ; i + + )
{
2020-10-14 19:58:33 +01:00
TitleInfo * title_info = & ( g_titleInfo [ i ] ) ;
2021-03-02 05:22:10 +00:00
if ( title_info - > meta_key . type ! = NcmContentMetaType_Application & & ! title_info - > app_metadata ) orphan_info [ j + + ] = title_info ;
2020-07-30 23:37:45 +01:00
}
2020-08-21 01:18:05 +01:00
/* Sort orphan title info entries by title ID. */
if ( g_titleInfoOrphanCount > 1 ) qsort ( orphan_info , g_titleInfoOrphanCount , sizeof ( TitleInfo * ) , & titleOrphanTitleInfoSortFunction ) ;
/* Update output counter. */
2020-07-30 23:37:45 +01:00
* out_count = g_titleInfoOrphanCount ;
end :
mutexUnlock ( & g_titleMutex ) ;
return orphan_info ;
}
2020-07-30 22:43:50 +01:00
bool titleIsGameCardInfoUpdated ( void )
{
mutexLock ( & g_titleMutex ) ;
2020-10-27 21:23:19 +00:00
/* Check if the gamecard thread detected a gamecard status change. */
2021-02-12 20:35:23 +00:00
bool ret = ( g_titleInterfaceInit & & g_titleGameCardInfoThreadCreated & & g_titleGameCardInfoUpdated ) ;
2020-10-27 21:23:19 +00:00
if ( ! ret ) goto end ;
/* Signal the gamecard update info user event. */
ueventSignal ( & g_titleGameCardUpdateInfoUserEvent ) ;
2021-02-20 18:52:07 +00:00
/* Wait for the gamecard thread to wake us up. */
2020-10-27 21:23:19 +00:00
condvarWait ( & g_gameCardCondVar , & g_titleMutex ) ;
/* Update output value and gamecard info updated flag (if needed). */
ret = g_titleGameCardInfoUpdated ;
if ( ret ) g_titleGameCardInfoUpdated = false ;
end :
2020-07-30 22:43:50 +01:00
mutexUnlock ( & g_titleMutex ) ;
return ret ;
}
2020-08-14 03:31:02 +01:00
char * titleGenerateFileName ( const TitleInfo * title_info , u8 name_convention , u8 illegal_char_replace_type )
{
mutexLock ( & g_titleMutex ) ;
char * filename = NULL ;
char title_name [ 0x400 ] = { 0 } ;
2020-10-22 05:38:14 +01:00
if ( ! title_info | | title_info - > meta_key . type < NcmContentMetaType_Application | | title_info - > meta_key . type > NcmContentMetaType_Delta | | name_convention > TitleFileNameConvention_IdAndVersionOnly | | \
2020-08-14 03:31:02 +01:00
( name_convention = = TitleFileNameConvention_Full & & illegal_char_replace_type > TitleFileNameIllegalCharReplaceType_KeepAsciiCharsOnly ) )
{
LOGFILE ( " Invalid parameters! " ) ;
goto end ;
}
2020-10-22 05:38:14 +01:00
u8 type = ( title_info - > meta_key . type - 0x80 ) ;
2020-08-14 03:31:02 +01:00
/* Generate filename for this title. */
if ( name_convention = = TitleFileNameConvention_Full )
{
2021-01-31 09:16:05 +00:00
if ( title_info - > app_metadata & & * ( title_info - > app_metadata - > lang_entry . name ) )
2020-08-14 03:31:02 +01:00
{
2021-01-31 09:16:05 +00:00
sprintf ( title_name , " %s " , title_info - > app_metadata - > lang_entry . name ) ;
2020-08-14 03:31:02 +01:00
if ( illegal_char_replace_type ) utilsReplaceIllegalCharacters ( title_name , illegal_char_replace_type = = TitleFileNameIllegalCharReplaceType_KeepAsciiCharsOnly ) ;
}
2020-10-22 05:38:14 +01:00
sprintf ( title_name + strlen ( title_name ) , " [%016lX][v%u][%s] " , title_info - > meta_key . id , title_info - > meta_key . version , g_filenameTypeStrings [ type ] ) ;
2020-08-14 03:31:02 +01:00
} else
if ( name_convention = = TitleFileNameConvention_IdAndVersionOnly )
{
2020-10-22 05:38:14 +01:00
sprintf ( title_name , " %016lX_v%u_%s " , title_info - > meta_key . id , title_info - > meta_key . version , g_filenameTypeStrings [ type ] ) ;
2020-08-14 03:31:02 +01:00
}
/* Duplicate generated filename. */
filename = strdup ( title_name ) ;
if ( ! filename ) LOGFILE ( " Failed to duplicate generated filename! " ) ;
end :
mutexUnlock ( & g_titleMutex ) ;
return filename ;
}
char * titleGenerateGameCardFileName ( u8 name_convention , u8 illegal_char_replace_type )
{
mutexLock ( & g_titleMutex ) ;
size_t cur_filename_len = 0 ;
char * filename = NULL , * tmp_filename = NULL ;
char app_name [ 0x400 ] = { 0 } ;
2021-02-12 20:35:23 +00:00
if ( ! g_titleInterfaceInit | | ! g_titleGameCardAvailable | | ! g_titleInfo | | ! g_titleInfoCount | | ! g_titleInfoGameCardCount | | g_titleInfoGameCardCount > g_titleInfoCount | | \
2020-08-14 03:31:02 +01:00
g_titleInfoGameCardStartIndex ! = ( g_titleInfoCount - g_titleInfoGameCardCount ) | | name_convention > TitleFileNameConvention_IdAndVersionOnly | | \
( name_convention = = TitleFileNameConvention_Full & & illegal_char_replace_type > TitleFileNameIllegalCharReplaceType_KeepAsciiCharsOnly ) )
{
LOGFILE ( " Invalid parameters! " ) ;
goto end ;
}
for ( u32 i = g_titleInfoGameCardStartIndex ; i < g_titleInfoCount ; i + + )
{
TitleInfo * app_info = & ( g_titleInfo [ i ] ) ;
u32 app_version = app_info - > meta_key . version ;
if ( app_info - > meta_key . type ! = NcmContentMetaType_Application ) continue ;
/* Check if the inserted gamecard holds any bundled patches for the current user application. */
/* If so, we'll use the highest patch version available as part of the filename. */
for ( u32 j = g_titleInfoGameCardStartIndex ; j < g_titleInfoCount ; j + + )
{
if ( j = = i ) continue ;
TitleInfo * patch_info = & ( g_titleInfo [ j ] ) ;
if ( patch_info - > meta_key . type ! = NcmContentMetaType_Patch | | ! titleCheckIfPatchIdBelongsToApplicationId ( app_info - > meta_key . id , patch_info - > meta_key . id ) | | \
patch_info - > meta_key . version < = app_version ) continue ;
app_version = patch_info - > meta_key . version ;
}
/* Generate current user application name. */
* app_name = ' \0 ' ;
if ( name_convention = = TitleFileNameConvention_Full )
{
if ( cur_filename_len ) strcat ( app_name , " + " ) ;
2020-10-15 01:06:53 +01:00
if ( app_info - > app_metadata & & * ( app_info - > app_metadata - > lang_entry . name ) )
2020-08-14 03:31:02 +01:00
{
sprintf ( app_name + strlen ( app_name ) , " %s " , app_info - > app_metadata - > lang_entry . name ) ;
if ( illegal_char_replace_type ) utilsReplaceIllegalCharacters ( app_name , illegal_char_replace_type = = TitleFileNameIllegalCharReplaceType_KeepAsciiCharsOnly ) ;
}
sprintf ( app_name + strlen ( app_name ) , " [%016lX][v%u] " , app_info - > meta_key . id , app_version ) ;
} else
if ( name_convention = = TitleFileNameConvention_IdAndVersionOnly )
{
if ( cur_filename_len ) strcat ( app_name , " + " ) ;
sprintf ( app_name + strlen ( app_name ) , " %016lX_v%u " , app_info - > meta_key . id , app_version ) ;
}
/* Reallocate output buffer. */
size_t app_name_len = strlen ( app_name ) ;
tmp_filename = realloc ( filename , ( cur_filename_len + app_name_len + 1 ) * sizeof ( char ) ) ;
if ( ! tmp_filename )
{
LOGFILE ( " Failed to reallocate filename buffer! " ) ;
if ( filename ) free ( filename ) ;
filename = NULL ;
goto end ;
}
filename = tmp_filename ;
tmp_filename = NULL ;
/* Concatenate current user application name. */
filename [ cur_filename_len ] = ' \0 ' ;
strcat ( filename , app_name ) ;
cur_filename_len + = app_name_len ;
}
if ( ! filename ) LOGFILE ( " Error: the inserted gamecard doesn't hold any user applications! " ) ;
end :
mutexUnlock ( & g_titleMutex ) ;
/* Fallback string if any errors occur. */
/* This function is guaranteed to fail with Kiosk / Quest gamecards, so that's why this is needed. */
if ( ! filename ) filename = strdup ( " gamecard " ) ;
return filename ;
}
2020-07-29 22:02:21 +01:00
const char * titleGetNcmContentTypeName ( u8 content_type )
{
2020-10-03 18:09:29 +01:00
return ( content_type < = NcmContentType_DeltaFragment ? g_titleNcmContentTypeNames [ content_type ] : NULL ) ;
2020-07-29 22:02:21 +01:00
}
2020-07-25 06:56:35 +01:00
2020-08-14 03:31:02 +01:00
const char * titleGetNcmContentMetaTypeName ( u8 content_meta_type )
{
2020-10-04 11:40:56 +01:00
if ( ( content_meta_type > NcmContentMetaType_BootImagePackageSafe & & content_meta_type < NcmContentMetaType_Application ) | | content_meta_type > NcmContentMetaType_Delta ) return NULL ;
return ( content_meta_type < = NcmContentMetaType_BootImagePackageSafe ? g_titleNcmContentMetaTypeNames [ content_meta_type ] : g_titleNcmContentMetaTypeNames [ content_meta_type - 0x7A ] ) ;
2020-08-14 03:31:02 +01:00
}
2020-07-26 05:57:12 +01:00
NX_INLINE void titleFreeApplicationMetadata ( void )
{
if ( g_appMetadata )
{
2020-07-26 09:00:54 +01:00
for ( u32 i = 0 ; i < g_appMetadataCount ; i + + )
{
if ( g_appMetadata [ i ] . icon ) free ( g_appMetadata [ i ] . icon ) ;
}
2020-07-26 05:57:12 +01:00
free ( g_appMetadata ) ;
g_appMetadata = NULL ;
}
g_appMetadataCount = 0 ;
}
2020-07-25 06:56:35 +01:00
2020-07-26 05:57:12 +01: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 ;
}
2020-07-30 23:37:45 +01:00
g_titleInfoCount = g_titleInfoGameCardStartIndex = g_titleInfoGameCardCount = g_titleInfoOrphanCount = 0 ;
2020-07-26 05:57:12 +01:00
}
2020-07-25 06:56:35 +01:00
2020-07-26 05:57:12 +01: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 06:56:35 +01:00
2020-07-28 04:32:08 +01:00
static bool titleGenerateMetadataEntriesFromSystemTitles ( void )
{
TitleApplicationMetadata * tmp_app_metadata = NULL ;
/* Reallocate application metadata buffer. */
/* If g_appMetadata == NULL, realloc() will essentially act as a malloc(). */
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 = NULL ;
/* 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 + + )
{
2020-10-14 19:58:33 +01:00
TitleApplicationMetadata * app_metadata = & ( g_appMetadata [ g_appMetadataCount + i ] ) ;
const SystemTitleName * system_title = & ( g_systemTitles [ i ] ) ;
app_metadata - > title_id = system_title - > title_id ;
sprintf ( app_metadata - > lang_entry . name , system_title - > name ) ;
2020-07-28 04:32:08 +01:00
}
2020-08-21 01:18:05 +01:00
/* Sort metadata entries by title ID. */
qsort ( g_appMetadata + g_appMetadataCount , g_systemTitlesCount , sizeof ( TitleApplicationMetadata ) , & titleSystemTitleMetadataEntrySortFunction ) ;
2020-07-28 04:32:08 +01:00
/* Update application metadata count. */
g_appMetadataCount + = g_systemTitlesCount ;
return true ;
}
static bool titleGenerateMetadataEntriesFromNsRecords ( void )
2020-07-25 06:56:35 +01:00
{
Result rc = 0 ;
NsApplicationRecord * app_records = NULL ;
u32 app_records_count = 0 ;
2020-08-21 01:18:05 +01:00
u32 cur_app_count = g_appMetadataCount , new_app_count = 0 ;
2020-07-28 04:32:08 +01:00
TitleApplicationMetadata * tmp_app_metadata = NULL ;
2020-07-25 06:56:35 +01:00
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 ;
}
2020-07-28 04:32:08 +01:00
/* Reallocate application metadata buffer. */
tmp_app_metadata = realloc ( g_appMetadata , ( g_appMetadataCount + app_records_count ) * sizeof ( TitleApplicationMetadata ) ) ;
if ( ! tmp_app_metadata )
2020-07-25 06:56:35 +01:00
{
2020-07-28 04:32:08 +01:00
LOGFILE ( " Failed to reallocate application metadata buffer! (%u %s). " , g_appMetadataCount + app_records_count , ( g_appMetadataCount + app_records_count ) > 1 ? " entries " : " entry " ) ;
2020-07-25 06:56:35 +01:00
goto end ;
}
2020-07-28 04:32:08 +01:00
g_appMetadata = tmp_app_metadata ;
tmp_app_metadata = NULL ;
2020-07-25 06:56:35 +01:00
/* Retrieve application metadata for each ns application record. */
for ( u32 i = 0 ; i < app_records_count ; i + + )
{
2020-08-21 01:18:05 +01:00
if ( ! titleRetrieveApplicationMetadataByTitleId ( app_records [ i ] . application_id , & ( g_appMetadata [ g_appMetadataCount + new_app_count ] ) ) ) continue ;
new_app_count + + ;
2020-07-25 06:56:35 +01:00
}
/* Check retrieved application metadata count. */
2020-08-21 01:18:05 +01:00
if ( ! new_app_count )
2020-07-25 06:56:35 +01:00
{
LOGFILE ( " Unable to retrieve application metadata from ns application records! (%u %s). " , app_records_count , app_records_count > 1 ? " entries " : " entry " ) ;
goto end ;
}
2020-08-21 01:18:05 +01:00
/* Sort application metadata entries by name. */
if ( new_app_count > 1 ) qsort ( g_appMetadata + g_appMetadataCount , new_app_count , sizeof ( TitleApplicationMetadata ) , & titleUserApplicationMetadataEntrySortFunction ) ;
/* Update application metadata count. */
g_appMetadataCount + = new_app_count ;
success = true ;
end :
if ( app_records )
2020-07-25 06:56:35 +01:00
{
2020-08-21 01:18:05 +01:00
/* Decrease application metadata buffer size if needed. */
if ( app_records_count & & g_appMetadataCount < ( cur_app_count + app_records_count ) )
2020-07-25 06:56:35 +01:00
{
2020-08-21 01:18:05 +01:00
TitleApplicationMetadata * tmp_app_metadata = realloc ( g_appMetadata , g_appMetadataCount * sizeof ( TitleApplicationMetadata ) ) ;
if ( tmp_app_metadata )
{
g_appMetadata = tmp_app_metadata ;
tmp_app_metadata = NULL ;
} else {
LOGFILE ( " Failed to reallocate application metadata buffer! (%u %s). " , g_appMetadataCount , g_appMetadataCount > 1 ? " entries " : " entry " ) ;
}
2020-07-25 06:56:35 +01:00
}
2020-08-21 01:18:05 +01:00
free ( app_records ) ;
2020-07-25 06:56:35 +01:00
}
return success ;
}
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 09:00:54 +01:00
u32 icon_size = 0 ;
u8 * icon = NULL ;
2020-07-25 06:56:35 +01: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 09:00:54 +01: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 06:56:35 +01: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 09:00:54 +01:00
out - > icon_size = icon_size ;
out - > icon = icon ;
2020-07-25 06:56:35 +01: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 19:50:42 +01: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-30 22:43:50 +01:00
static bool titleLoadPersistentStorageTitleInfo ( void )
2020-07-25 06:56:35 +01:00
{
/* Return right away if title info has already been retrieved. */
if ( g_titleInfo | | g_titleInfoCount ) return true ;
for ( u8 i = NcmStorageId_BuiltInSystem ; i < = NcmStorageId_SdCard ; i + + )
{
2021-02-22 21:30:47 +00:00
/* Generate title info from the current storage. */
if ( ! titleGenerateTitleInfoFromStorage ( i ) )
2020-07-25 06:56:35 +01:00
{
2021-02-22 21:30:47 +00:00
LOGFILE ( " Failed to generate title info from storage ID %u! " , i ) ;
2020-07-25 06:56:35 +01:00
return false ;
}
}
return true ;
}
2021-02-22 21:30:47 +00:00
static bool titleGenerateTitleInfoFromStorage ( u8 storage_id )
2020-07-25 06:56:35 +01:00
{
2020-07-26 05:57:12 +01:00
NcmContentMetaDatabase * ncm_db = NULL ;
if ( ! ( ncm_db = titleGetNcmDatabaseByStorageId ( storage_id ) ) | | ! serviceIsActive ( & ( ncm_db - > s ) ) )
2020-07-25 06:56:35 +01: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. */
2020-07-29 22:02:21 +01:00
rc = ncmContentMetaDatabaseList ( ncm_db , ( s32 * ) & total , ( s32 * ) & written , meta_keys , 1 , 0 , 0 , 0 , UINT64_MAX , NcmContentInstallType_Full ) ;
2020-07-25 06:56:35 +01:00
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. */
2020-07-29 22:02:21 +01:00
rc = ncmContentMetaDatabaseList ( ncm_db , ( s32 * ) & total , ( s32 * ) & written , meta_keys , ( s32 ) total , 0 , 0 , 0 , UINT64_MAX , NcmContentInstallType_Full ) ;
2020-07-25 06:56:35 +01:00
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 05:57:12 +01:00
/* Fill information. */
2020-07-25 06:56:35 +01:00
cur_title_info - > storage_id = storage_id ;
memcpy ( & ( cur_title_info - > meta_key ) , & ( meta_keys [ i ] ) , sizeof ( NcmContentMetaKey ) ) ;
2020-10-14 19:58:33 +01:00
cur_title_info - > version . value = cur_title_info - > meta_key . version ;
2020-10-06 16:41:26 +01:00
2021-01-31 09:16:05 +00:00
/* Retrieve application metadata. */
u64 app_id = ( cur_title_info - > meta_key . type < = NcmContentMetaType_Application ? cur_title_info - > meta_key . id : \
( cur_title_info - > meta_key . type = = NcmContentMetaType_Patch ? titleGetApplicationIdByPatchId ( cur_title_info - > meta_key . id ) : \
( cur_title_info - > meta_key . type = = NcmContentMetaType_AddOnContent ? titleGetApplicationIdByAddOnContentId ( cur_title_info - > meta_key . id ) : \
titleGetApplicationIdByDeltaId ( cur_title_info - > meta_key . id ) ) ) ) ;
cur_title_info - > app_metadata = titleFindApplicationMetadataByTitleId ( app_id ) ;
2020-07-26 05:57:12 +01:00
/* Retrieve content infos. */
2020-10-14 19:58:33 +01:00
if ( titleGetContentInfosFromTitle ( storage_id , & ( cur_title_info - > meta_key ) , & ( cur_title_info - > content_infos ) , & ( cur_title_info - > content_count ) ) )
2020-07-26 05:57:12 +01:00
{
/* Calculate title size. */
2020-07-28 04:32:08 +01:00
u64 tmp_size = 0 ;
2020-07-26 05:57:12 +01:00
for ( u32 j = 0 ; j < cur_title_info - > content_count ; j + + )
{
titleConvertNcmContentSizeToU64 ( cur_title_info - > content_infos [ j ] . size , & tmp_size ) ;
2020-10-21 05:27:48 +01:00
cur_title_info - > size + = tmp_size ;
2020-07-26 05:57:12 +01:00
}
}
/* Generate formatted title size string. */
2020-10-21 05:27:48 +01:00
utilsGenerateFormattedSizeString ( cur_title_info - > size , cur_title_info - > size_str , sizeof ( cur_title_info - > size_str ) ) ;
2020-07-25 06:56:35 +01:00
}
/* Update title info count. */
g_titleInfoCount + = total ;
2021-03-02 05:22:10 +00:00
/* Update linked lists for user applications, patches and add-on contents. */
/* This will also keep track of orphan titles - titles with no available application metadata. */
titleUpdateTitleInfoLinkedLists ( ) ;
2020-10-27 21:23:19 +00:00
2021-02-22 21:30:47 +00:00
/* Update flag. */
success = true ;
2020-07-28 04:32:08 +01:00
2020-07-25 06:56:35 +01:00
end :
if ( meta_keys ) free ( meta_keys ) ;
return success ;
}
2020-07-26 05:57:12 +01: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 ;
}
2021-02-22 21:30:47 +00:00
static void titleUpdateTitleInfoLinkedLists ( void )
{
/* Reset orphan title count. */
g_titleInfoOrphanCount = 0 ;
/* Loop through all available titles. */
for ( u32 i = 0 ; i < g_titleInfoCount ; i + + )
{
/* Get pointer to the current title info and reset its linked list pointers. */
TitleInfo * child_info = & ( g_titleInfo [ i ] ) ;
child_info - > parent = child_info - > previous = child_info - > next = NULL ;
2021-03-02 05:22:10 +00:00
if ( child_info - > meta_key . type < NcmContentMetaType_Application )
{
/* We're dealing with a system title. */
/* Increase orphan title count if we have no application metadata. Immediately proceed onto the next loop iteration. */
/* We don't generate linked lists for orphan titles nor system titles. */
if ( ! child_info - > app_metadata ) g_titleInfoOrphanCount + + ;
continue ;
} else
2021-02-22 21:30:47 +00:00
if ( child_info - > meta_key . type = = NcmContentMetaType_Patch | | child_info - > meta_key . type = = NcmContentMetaType_AddOnContent )
{
2021-03-02 05:22:10 +00:00
/* We're dealing with a patch or an add-on content. */
2021-02-22 21:30:47 +00:00
/* Retrieve pointer to the first parent user application entry for patches and add-on contents. */
/* Since gamecard title info entries are always appended to the end of the buffer, this guarantees we will first retrieve an eMMC / SD card entry (if available). */
for ( u32 j = 0 ; j < g_titleInfoCount ; j + + )
{
TitleInfo * parent_info = & ( g_titleInfo [ j ] ) ;
if ( parent_info - > meta_key . type = = NcmContentMetaType_Application & & \
( ( child_info - > meta_key . type = = NcmContentMetaType_Patch & & titleCheckIfPatchIdBelongsToApplicationId ( parent_info - > meta_key . id , child_info - > meta_key . id ) ) | | \
( child_info - > meta_key . type = = NcmContentMetaType_AddOnContent & & titleCheckIfAddOnContentIdBelongsToApplicationId ( parent_info - > meta_key . id , child_info - > meta_key . id ) ) ) )
{
child_info - > parent = parent_info ;
if ( ! child_info - > app_metadata ) child_info - > app_metadata = parent_info - > app_metadata ;
break ;
}
}
2021-03-02 05:22:10 +00:00
/* If we have no application metadata, increase orphan title count and proceed onto the next loop iteration. */
/* We don't generate linked lists for orphan titles. */
2021-02-22 21:30:47 +00:00
if ( ! child_info - > app_metadata )
{
g_titleInfoOrphanCount + + ;
continue ;
}
}
/* Locate previous user application, patch or add-on content entry. */
/* If it's found, we will update both its next pointer and the previous pointer from the current entry. */
for ( u32 j = i ; j > 0 ; j - - )
{
TitleInfo * previous_info = & ( g_titleInfo [ j - 1 ] ) ;
if ( previous_info - > meta_key . type = = child_info - > meta_key . type & & ( ( ( child_info - > meta_key . type = = NcmContentMetaType_Application | | child_info - > meta_key . type = = NcmContentMetaType_Patch ) & & \
previous_info - > meta_key . id = = child_info - > meta_key . id ) | | ( child_info - > meta_key . type = = NcmContentMetaType_AddOnContent & & \
titleCheckIfAddOnContentIdsAreSiblings ( previous_info - > meta_key . id , child_info - > meta_key . id ) ) ) )
{
previous_info - > next = child_info ;
child_info - > previous = previous_info ;
break ;
}
}
}
}
2020-07-30 22:43:50 +01:00
static bool titleCreateGameCardInfoThread ( void )
2020-07-25 19:50:42 +01:00
{
2020-08-18 06:04:13 +01:00
if ( ! utilsCreateThread ( & g_titleGameCardInfoThread , titleGameCardInfoThreadFunc , NULL , 1 ) )
2020-07-30 22:43:50 +01:00
{
LOGFILE ( " Failed to create gamecard title info thread! " ) ;
return false ;
}
2020-07-25 19:50:42 +01:00
2020-07-30 22:43:50 +01:00
return true ;
}
static void titleDestroyGameCardInfoThread ( void )
{
/* Signal the exit event to terminate the gamecard title info thread. */
ueventSignal ( & g_titleGameCardInfoThreadExitEvent ) ;
/* Wait for the gamecard title info thread to exit. */
2020-08-18 06:04:13 +01:00
utilsJoinThread ( & g_titleGameCardInfoThread ) ;
2020-07-30 22:43:50 +01:00
}
2020-08-18 06:04:13 +01:00
static void titleGameCardInfoThreadFunc ( void * arg )
2020-07-30 22:43:50 +01:00
{
( void ) arg ;
Result rc = 0 ;
int idx = 0 ;
2020-10-27 21:23:19 +00:00
bool first_run = true ;
2020-07-30 22:43:50 +01:00
Waiter gamecard_status_event_waiter = waiterForUEvent ( g_titleGameCardStatusChangeUserEvent ) ;
Waiter exit_event_waiter = waiterForUEvent ( & g_titleGameCardInfoThreadExitEvent ) ;
2020-10-27 21:23:19 +00:00
Waiter update_info_waiter = waiterForUEvent ( & g_titleGameCardUpdateInfoUserEvent ) ;
2020-07-30 22:43:50 +01:00
while ( true )
{
/* Wait until an event is triggered. */
rc = waitMulti ( & idx , - 1 , gamecard_status_event_waiter , exit_event_waiter ) ;
if ( R_FAILED ( rc ) ) continue ;
/* Exit event triggered. */
if ( idx = = 1 ) break ;
2020-10-27 21:23:19 +00:00
if ( ! first_run )
{
/* Update gamecard info updated flag. */
mutexLock ( & g_titleMutex ) ;
g_titleGameCardInfoUpdated = true ;
mutexUnlock ( & g_titleMutex ) ;
/* Wait until another function signals us (titleIsGameCardInfoUpdated() or titleExit()). */
rc = waitMulti ( & idx , - 1 , update_info_waiter , exit_event_waiter ) ;
if ( R_FAILED ( rc ) )
{
mutexLock ( & g_titleMutex ) ;
g_titleGameCardInfoUpdated = false ;
mutexUnlock ( & g_titleMutex ) ;
continue ;
}
/* Exit event triggered. */
if ( idx = = 1 ) break ;
}
2020-07-30 22:43:50 +01:00
/* Update gamecard title info. */
mutexLock ( & g_titleMutex ) ;
2020-10-27 21:23:19 +00:00
g_titleGameCardInfoUpdated = ( titleRefreshGameCardTitleInfo ( ) & & ! first_run ) ;
2020-07-30 22:43:50 +01:00
mutexUnlock ( & g_titleMutex ) ;
2020-10-27 21:23:19 +00:00
if ( first_run )
{
/* Disable first run flag. */
first_run = false ;
} else {
/* Wake up titleIsGameCardInfoUpdated(). */
condvarWakeAll ( & g_gameCardCondVar ) ;
}
2020-07-30 22:43:50 +01:00
}
/* Update gamecard flags. */
g_titleGameCardAvailable = g_titleGameCardInfoUpdated = false ;
2020-08-18 06:04:13 +01:00
threadExit ( ) ;
2020-07-30 22:43:50 +01:00
}
static bool titleRefreshGameCardTitleInfo ( void )
{
2020-07-25 19:50:42 +01:00
TitleApplicationMetadata * tmp_app_metadata = NULL ;
2021-02-22 21:30:47 +00:00
u32 orig_app_count = g_appMetadataCount , cur_app_count = g_appMetadataCount , gamecard_metadata_count = 0 ;
2020-07-25 19:50:42 +01:00
bool status = false , success = false , cleanup = true ;
/* Retrieve current gamecard status. */
status = ( gamecardGetStatus ( ) = = GameCardStatus_InsertedAndInfoLoaded ) ;
if ( status = = g_titleGameCardAvailable | | ! status )
{
2020-07-28 04:32:08 +01:00
success = cleanup = ( status ! = g_titleGameCardAvailable ) ;
2020-07-25 19:50:42 +01:00
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 ;
2021-02-22 21:30:47 +00:00
/* Generate gamecard title info. */
if ( ! titleGenerateTitleInfoFromStorage ( NcmStorageId_GameCard ) )
2020-07-25 19:50:42 +01:00
{
2021-02-22 21:30:47 +00:00
LOGFILE ( " Failed to generate gamecard title info! " ) ;
2020-07-25 19:50:42 +01:00
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 ] ) ;
2021-01-31 09:16:05 +00:00
u64 app_id = ( cur_title_info - > meta_key . type < = NcmContentMetaType_Application ? cur_title_info - > meta_key . id : \
( cur_title_info - > meta_key . type = = NcmContentMetaType_Patch ? titleGetApplicationIdByPatchId ( cur_title_info - > meta_key . id ) : \
( cur_title_info - > meta_key . type = = NcmContentMetaType_AddOnContent ? titleGetApplicationIdByAddOnContentId ( cur_title_info - > meta_key . id ) : \
titleGetApplicationIdByDeltaId ( cur_title_info - > meta_key . id ) ) ) ) ;
/* Do not proceed if application metadata has already been retrieved, or if we can successfully retrieve it. */
2021-02-22 21:30:47 +00:00
if ( cur_title_info - > app_metadata ! = NULL | | ( cur_title_info - > app_metadata = titleFindApplicationMetadataByTitleId ( app_id ) ) ! = NULL ) continue ;
2020-07-25 19:50:42 +01:00
/* 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. */
2021-01-31 09:16:05 +00:00
if ( ! titleRetrieveApplicationMetadataByTitleId ( app_id , & ( g_appMetadata [ g_appMetadataCount ] ) ) ) continue ;
2020-07-25 19:50:42 +01:00
2020-08-21 01:18:05 +01:00
cur_title_info - > app_metadata = & ( g_appMetadata [ g_appMetadataCount + + ] ) ;
2020-07-25 19:50:42 +01:00
gamecard_metadata_count + + ;
}
2021-02-22 21:30:47 +00:00
/* Check if we retrieved new application metadata that was previously unavailable. */
if ( gamecard_metadata_count )
2020-07-25 19:50:42 +01:00
{
2021-02-22 21:30:47 +00:00
/* Sort application metadata entries by name. */
qsort ( g_appMetadata + g_systemTitlesCount , gamecard_metadata_count , sizeof ( TitleApplicationMetadata ) , & titleUserApplicationMetadataEntrySortFunction ) ;
/* Check if the orphan title count is non-zero. */
if ( g_titleInfoOrphanCount )
{
/* Reset orphan title count. */
g_titleInfoOrphanCount = 0 ;
/* Try to update the application metadata pointer in orphan entries, hopefully reducing the orphan title count in the process. */
for ( u32 i = 0 ; i < g_titleInfoCount ; i + + )
{
TitleInfo * title_info = & ( g_titleInfo [ i ] ) ;
if ( ( title_info - > meta_key . type ! = NcmContentMetaType_Patch & & title_info - > meta_key . type ! = NcmContentMetaType_AddOnContent ) | | title_info - > app_metadata ) continue ;
u64 app_id = ( title_info - > meta_key . type = = NcmContentMetaType_Patch ? titleGetApplicationIdByPatchId ( title_info - > meta_key . id ) : \
titleGetApplicationIdByAddOnContentId ( title_info - > meta_key . id ) ) ;
if ( ! ( title_info - > app_metadata = titleFindApplicationMetadataByTitleId ( app_id ) ) ) g_titleInfoOrphanCount + + ;
}
}
2020-07-25 19:50:42 +01:00
}
success = true ;
cleanup = false ;
end :
/* Update gamecard status. */
g_titleGameCardAvailable = status ;
2020-07-30 22:43:50 +01:00
/* Decrease application metadata buffer size (if needed). */
2020-07-25 19:50:42 +01:00
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 ;
}
}
2020-07-30 22:43:50 +01:00
/* Remove gamecard title info entries and close its ncm database and storage handles (if needed). */
2020-07-25 19:50:42 +01:00
if ( cleanup )
{
titleRemoveGameCardTitleInfoEntries ( ) ;
titleCloseNcmDatabaseAndStorageFromGameCard ( ) ;
}
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-28 04:32:08 +01:00
/* Free all title info entries. */
2020-07-26 05:57:12 +01:00
titleFreeTitleInfo ( ) ;
2020-07-25 19:50:42 +01:00
} else {
2020-07-28 04:32:08 +01:00
/* Free content infos from gamecard title info entries. */
2020-08-14 03:31:02 +01:00
for ( u32 i = g_titleInfoGameCardStartIndex ; i < g_titleInfoCount ; i + + )
2020-07-26 05:57:12 +01:00
{
2020-08-13 19:20:53 +01:00
TitleInfo * cur_title_info = & ( g_titleInfo [ i ] ) ;
if ( cur_title_info - > content_infos ) free ( cur_title_info - > content_infos ) ;
2020-07-26 05:57:12 +01:00
}
2020-07-28 04:32:08 +01:00
/* Reallocate title info buffer. */
2020-08-14 03:31:02 +01:00
TitleInfo * tmp_title_info = realloc ( g_titleInfo , g_titleInfoGameCardStartIndex * sizeof ( TitleInfo ) ) ;
2020-07-25 19:50:42 +01:00
if ( tmp_title_info )
{
g_titleInfo = tmp_title_info ;
tmp_title_info = NULL ;
}
2020-07-26 05:57:12 +01:00
2020-07-28 04:32:08 +01:00
/* Update counters. */
2020-08-14 03:31:02 +01:00
g_titleInfoCount = g_titleInfoGameCardStartIndex ;
2020-07-26 05:57:12 +01:00
g_titleInfoGameCardStartIndex = g_titleInfoGameCardCount = 0 ;
2021-02-22 21:30:47 +00:00
/* Update linked lists for user applications, patches and add-on contents. */
titleUpdateTitleInfoLinkedLists ( ) ;
2020-07-25 19:50:42 +01:00
}
2020-07-25 06:56:35 +01:00
}
2020-07-28 04:32:08 +01:00
static bool titleIsUserApplicationContentAvailable ( u64 app_id )
{
if ( ! g_titleInfo | | ! g_titleInfoCount | | ! app_id ) return false ;
for ( u32 i = 0 ; i < g_titleInfoCount ; i + + )
{
2020-08-14 03:31:02 +01:00
TitleInfo * cur_title_info = & ( g_titleInfo [ i ] ) ;
if ( ( cur_title_info - > meta_key . type = = NcmContentMetaType_Application & & cur_title_info - > meta_key . id = = app_id ) | | \
( cur_title_info - > meta_key . type = = NcmContentMetaType_Patch & & titleCheckIfPatchIdBelongsToApplicationId ( app_id , cur_title_info - > meta_key . id ) ) | | \
( cur_title_info - > meta_key . type = = NcmContentMetaType_AddOnContent & & titleCheckIfAddOnContentIdBelongsToApplicationId ( app_id , cur_title_info - > meta_key . id ) ) ) return true ;
2020-07-28 04:32:08 +01:00
}
return false ;
}
static TitleInfo * _titleGetInfoFromStorageByTitleId ( u8 storage_id , u64 title_id , bool lock )
{
if ( lock ) mutexLock ( & g_titleMutex ) ;
TitleInfo * info = NULL ;
2021-02-12 20:35:23 +00:00
if ( ! g_titleInterfaceInit | | ! g_titleInfo | | ! g_titleInfoCount | | storage_id < NcmStorageId_GameCard | | storage_id > NcmStorageId_Any | | ( storage_id = = NcmStorageId_GameCard & & \
( ! g_titleInfoGameCardCount | | g_titleInfoGameCardCount > g_titleInfoCount | | g_titleInfoGameCardStartIndex ! = ( g_titleInfoCount - g_titleInfoGameCardCount ) ) ) | | ! title_id )
2020-07-28 04:32:08 +01:00
{
LOGFILE ( " Invalid parameters! " ) ;
goto end ;
}
/* Speed up gamecard lookups. */
u32 start_idx = ( storage_id = = NcmStorageId_GameCard ? g_titleInfoGameCardStartIndex : 0 ) ;
2020-10-27 21:23:19 +00:00
u32 max_val = ( ( storage_id = = NcmStorageId_GameCard | | storage_id = = NcmStorageId_Any ) ? g_titleInfoCount : ( g_titleInfoGameCardCount ? g_titleInfoGameCardStartIndex : g_titleInfoCount ) ) ;
2020-07-28 04:32:08 +01:00
for ( u32 i = start_idx ; i < max_val ; i + + )
{
2020-10-14 19:58:33 +01:00
TitleInfo * title_info = & ( g_titleInfo [ i ] ) ;
if ( title_info - > meta_key . id = = title_id & & ( storage_id = = NcmStorageId_Any | | ( storage_id ! = NcmStorageId_Any & & title_info - > storage_id = = storage_id ) ) )
2020-07-28 04:32:08 +01:00
{
2020-10-14 19:58:33 +01:00
info = title_info ;
2020-07-28 04:32:08 +01:00
break ;
}
}
2020-10-27 21:23:19 +00:00
if ( ! info & & lock ) LOGFILE ( " Unable to find TitleInfo entry with ID \" %016lX \" ! (storage ID %u). " , title_id , storage_id ) ;
2020-07-28 04:32:08 +01:00
end :
if ( lock ) mutexUnlock ( & g_titleMutex ) ;
return info ;
}
2020-08-21 01:18:05 +01:00
static int titleSystemTitleMetadataEntrySortFunction ( const void * a , const void * b )
2020-07-28 04:32:08 +01:00
{
2020-08-21 01:18:05 +01:00
const TitleApplicationMetadata * app_metadata_1 = ( const TitleApplicationMetadata * ) a ;
const TitleApplicationMetadata * app_metadata_2 = ( const TitleApplicationMetadata * ) b ;
2020-07-28 04:32:08 +01:00
if ( app_metadata_1 - > title_id < app_metadata_2 - > title_id )
{
return - 1 ;
} else
if ( app_metadata_1 - > title_id > app_metadata_2 - > title_id )
{
return 1 ;
}
return 0 ;
}
2020-07-28 04:38:45 +01:00
2020-08-21 01:18:05 +01:00
static int titleUserApplicationMetadataEntrySortFunction ( const void * a , const void * b )
2020-07-28 04:38:45 +01:00
{
2020-08-21 01:18:05 +01:00
const TitleApplicationMetadata * app_metadata_1 = ( const TitleApplicationMetadata * ) a ;
const TitleApplicationMetadata * app_metadata_2 = ( const TitleApplicationMetadata * ) b ;
2020-07-28 04:38:45 +01:00
return strcasecmp ( app_metadata_1 - > lang_entry . name , app_metadata_2 - > lang_entry . name ) ;
}
2020-08-21 01:18:05 +01:00
static int titleOrphanTitleInfoSortFunction ( const void * a , const void * b )
{
const TitleInfo * title_info_1 = * ( ( const TitleInfo * * ) a ) ;
const TitleInfo * title_info_2 = * ( ( const TitleInfo * * ) b ) ;
if ( title_info_1 - > meta_key . id < title_info_2 - > meta_key . id )
{
return - 1 ;
} else
if ( title_info_1 - > meta_key . id > title_info_2 - > meta_key . id )
{
return 1 ;
}
return 0 ;
}