diff --git a/code_templates/nxdt_rw_poc.c b/code_templates/nxdt_rw_poc.c index b38e1614..a1f59897 100644 --- a/code_templates/nxdt_rw_poc.c +++ b/code_templates/nxdt_rw_poc.c @@ -1264,7 +1264,7 @@ int main(int argc, char *argv[]) break; default: /* Get TitleInfo element on demand. */ - title_info = titleGetInfoFromStorageByTitleId(NcmStorageId_BuiltInSystem, app_metadata->title_id); + title_info = titleGetTitleInfoEntryFromStorageByTitleId(NcmStorageId_BuiltInSystem, app_metadata->title_id); break; } diff --git a/include/core/title.h b/include/core/title.h index 7f7da37a..5f2a0d25 100644 --- a/include/core/title.h +++ b/include/core/title.h @@ -122,7 +122,7 @@ TitleGameCardApplicationMetadata *titleGetGameCardApplicationMetadataEntries(u32 /// Returns a pointer to a dynamically allocated TitleInfo element with a matching storage ID and title ID. Returns NULL if an error occurs. /// If NcmStorageId_Any is used, the first entry with a matching title ID is returned. /// Use titleFreeTitleInfo() to free the returned data. -TitleInfo *titleGetInfoFromStorageByTitleId(u8 storage_id, u64 title_id); +TitleInfo *titleGetTitleInfoEntryFromStorageByTitleId(u8 storage_id, u64 title_id); /// Frees a dynamically allocated TitleInfo element. void titleFreeTitleInfo(TitleInfo **info); @@ -152,7 +152,7 @@ TitleInfo **titleGetOrphanTitles(u32 *out_count); void titleFreeOrphanTitles(TitleInfo ***orphan_info); /// Checks if a gamecard status update has been detected by the background gamecard title info thread (e.g. after a new gamecard has been inserted, of after the current one has been taken out). -/// If this function returns true and functions such as titleGetInfoFromStorageByTitleId(), titleGetUserApplicationData() or titleGetInfoFromOrphanTitles() have been previously called: +/// If this function returns true and functions such as titleGetTitleInfoEntryFromStorageByTitleId(), titleGetUserApplicationData() or titleGetInfoFromOrphanTitles() have been previously called: /// 1. Their returned data must be freed. /// 2. They must be called again. bool titleIsGameCardInfoUpdated(void); diff --git a/source/core/bfttf.c b/source/core/bfttf.c index b9c4e220..fb17902e 100644 --- a/source/core/bfttf.c +++ b/source/core/bfttf.c @@ -91,7 +91,7 @@ bool bfttfInitialize(void) TitleInfo *title_info = NULL; /* Get title info. */ - if (!(title_info = titleGetInfoFromStorageByTitleId(NcmStorageId_BuiltInSystem, font_info->title_id))) + if (!(title_info = titleGetTitleInfoEntryFromStorageByTitleId(NcmStorageId_BuiltInSystem, font_info->title_id))) { LOG_MSG_ERROR("Failed to get title info for %016lX!", font_info->title_id); continue; diff --git a/source/core/nxdt_bfsar.c b/source/core/nxdt_bfsar.c index 14879b87..c7b826ac 100644 --- a/source/core/nxdt_bfsar.c +++ b/source/core/nxdt_bfsar.c @@ -91,7 +91,7 @@ bool bfsarInitialize(void) } /* Get title info. */ - if (!(title_info = titleGetInfoFromStorageByTitleId(NcmStorageId_BuiltInSystem, QLAUNCH_TID))) + if (!(title_info = titleGetTitleInfoEntryFromStorageByTitleId(NcmStorageId_BuiltInSystem, QLAUNCH_TID))) { LOG_MSG_ERROR("Failed to get title info for qlaunch!"); break; diff --git a/source/core/title.c b/source/core/title.c index 22f092de..185bad17 100644 --- a/source/core/title.c +++ b/source/core/title.c @@ -23,6 +23,7 @@ #include #include #include +#include #define NS_APPLICATION_RECORD_BLOCK_SIZE 1024 @@ -46,6 +47,12 @@ typedef struct { u32 title_count; } TitleStorage; +typedef struct { + NcaContext nca_ctx; + ContentMetaContext cnmt_ctx; + NcmContentMetaKey meta_key; +} TitleGameCardContentMetaContext; + /* Global variables. */ static Mutex g_titleMutex = 0; @@ -549,6 +556,7 @@ NX_INLINE bool titleInitializePersistentTitleStorages(void); NX_INLINE void titleCloseTitleStorages(void); static bool titleInitializeTitleStorage(u8 storage_id); +static bool titleInitializeGameCardTitleStorageByHashFileSystem(u8 hfs_partition_type); static void titleCloseTitleStorage(u8 storage_id); static bool titleReallocateTitleInfoFromStorage(TitleStorage *title_storage, u32 extra_title_count, bool free_entries); @@ -557,44 +565,63 @@ static void titleAddOrphanTitleInfoEntry(TitleInfo *orphan_title); static bool titleGenerateMetadataEntriesFromSystemTitles(void); static bool titleGenerateMetadataEntriesFromNsRecords(void); -static TitleApplicationMetadata *titleGenerateDummySystemMetadataEntry(u64 title_id); -static bool titleRetrieveUserApplicationMetadataByTitleId(u64 title_id, TitleApplicationMetadata *out); + +static TitleApplicationMetadata *titleGetSystemMetadataEntry(u64 title_id); +static TitleApplicationMetadata *titleGenerateUserMetadataEntryFromNs(u64 title_id); +static TitleApplicationMetadata *titleGenerateUserMetadataEntryFromControlNca(TitleInfo *title_info); + +static bool titleGetApplicationControlDataFromNs(u64 title_id, NsApplicationControlData *out_control_data, u64 *out_control_data_size); +static bool titleGetApplicationControlDataFromControlNca(TitleInfo *title_info, NsApplicationControlData *out_control_data, u64 *out_control_data_size); + +static TitleApplicationMetadata *titleInitializeUserMetadataEntryFromControlData(u64 title_id, const NsApplicationControlData *control_data, u64 control_data_size); static void titleGenerateFilteredApplicationMetadataPointerArray(bool is_system); -static void titleGenerateGameCardApplicationMetadataArray(void); +static bool titleIsUserApplicationContentAvailable(u64 app_id); NX_INLINE TitleApplicationMetadata *titleFindApplicationMetadataByTitleId(u64 title_id, bool is_system, u32 extra_app_count); NX_INLINE u64 titleGetApplicationIdByContentMetaKey(const NcmContentMetaKey *meta_key); static bool titleGenerateTitleInfoEntriesForTitleStorage(TitleStorage *title_storage); +static bool titleGenerateTitleInfoEntriesByHashFileSystemForGameCardTitleStorage(TitleStorage *title_storage, HashFileSystemContext *hfs_ctx); + +static TitleInfo *titleGenerateTitleInfoEntry(u8 storage_id, const NcmContentMetaKey *meta_key, NcmContentInfo *content_infos, u32 content_count, bool get_control_nca_metadata); +static bool titleInitializeTitleInfoApplicationMetadataFromControlNca(TitleInfo *title_info); + static bool titleGetMetaKeysFromContentDatabase(NcmContentMetaDatabase *ncm_db, NcmContentMetaKey **out_meta_keys, u32 *out_meta_key_count); -static bool titleGetContentInfosForMetaKey(NcmContentMetaDatabase *ncm_db, const NcmContentMetaKey *meta_key, NcmContentInfo **out_content_infos, u32 *out_content_count); +static bool titleGetContentInfosByMetaKey(NcmContentMetaDatabase *ncm_db, const NcmContentMetaKey *meta_key, NcmContentInfo **out_content_infos, u32 *out_content_count); + +static bool titleGetGameCardContentMetaContexts(HashFileSystemContext *hfs_ctx, TitleGameCardContentMetaContext **out_gc_meta_ctxs, u32 *out_gc_meta_ctx_count); +static void titleFreeGameCardContentMetaContexts(TitleGameCardContentMetaContext **gc_meta_ctxs, u32 gc_meta_ctx_count); + +static bool titleGetContentInfosByGameCardContentMetaContext(TitleGameCardContentMetaContext *gc_meta_ctx, HashFileSystemContext *hfs_ctx, NcmContentInfo **out_content_infos, u32 *out_content_count); static void titleUpdateTitleInfoLinkedLists(void); +static TitleInfo *_titleGetTitleInfoEntryFromStorageByTitleId(u8 storage_id, u64 title_id); + +static TitleInfo *titleDuplicateTitleInfoFull(TitleInfo *title_info, TitleInfo *previous, TitleInfo *next); +static TitleInfo *titleDuplicateTitleInfo(TitleInfo *title_info); + +static char *titleGetDisplayVersionString(TitleInfo *title_info); + static bool titleCreateGameCardInfoThread(void); static void titleDestroyGameCardInfoThread(void); static void titleGameCardInfoThreadFunc(void *arg); static bool titleRefreshGameCardTitleInfo(void); -NX_INLINE void titleFreeGameCardFileNames(void); +static void titleGenerateGameCardApplicationMetadataArray(void); + NX_INLINE void titleGenerateGameCardFileNames(void); +NX_INLINE void titleFreeGameCardFileNames(void); static char *_titleGenerateGameCardFileName(u8 naming_convention); -static bool titleIsUserApplicationContentAvailable(u64 app_id); -static TitleInfo *_titleGetInfoFromStorageByTitleId(u8 storage_id, u64 title_id); - -static TitleInfo *titleDuplicateTitleInfoFull(TitleInfo *title_info, TitleInfo *previous, TitleInfo *next); -static TitleInfo *titleDuplicateTitleInfo(TitleInfo *title_info); - -static int titleSystemTitleMetadataEntrySortFunction(const void *a, const void *b); -static int titleUserApplicationMetadataEntrySortFunction(const void *a, const void *b); -static int titleInfoEntrySortFunction(const void *a, const void *b); +static int titleSystemMetadataSortFunction(const void *a, const void *b); +static int titleUserMetadataSortFunction(const void *a, const void *b); +static int titleInfoSortFunction(const void *a, const void *b); static int titleGameCardApplicationMetadataSortFunction(const void *a, const void *b); - -static char *titleGetDisplayVersionString(TitleInfo *title_info); +static int titleGameCardContentMetaContextSortFunction(const void *a, const void *b); bool titleInitialize(void) { @@ -771,13 +798,13 @@ TitleGameCardApplicationMetadata *titleGetGameCardApplicationMetadataEntries(u32 return dup_gc_app_metadata; } -TitleInfo *titleGetInfoFromStorageByTitleId(u8 storage_id, u64 title_id) +TitleInfo *titleGetTitleInfoEntryFromStorageByTitleId(u8 storage_id, u64 title_id) { TitleInfo *ret = NULL; SCOPED_LOCK(&g_titleMutex) { - TitleInfo *title_info = (g_titleInterfaceInit ? _titleGetInfoFromStorageByTitleId(storage_id, title_id) : NULL); + TitleInfo *title_info = (g_titleInterfaceInit ? _titleGetTitleInfoEntryFromStorageByTitleId(storage_id, title_id) : NULL); if (title_info) { ret = titleDuplicateTitleInfoFull(title_info, NULL, NULL); @@ -848,11 +875,11 @@ bool titleGetUserApplicationData(u64 app_id, TitleUserApplicationData *out) } /* Get info for the first user application title. */ - app_info = _titleGetInfoFromStorageByTitleId(NcmStorageId_Any, app_id); + app_info = _titleGetTitleInfoEntryFromStorageByTitleId(NcmStorageId_Any, app_id); TITLE_ALLOCATE_USER_APP_DATA(app, "user application", break); /* Get info for the first patch title. */ - patch_info = _titleGetInfoFromStorageByTitleId(NcmStorageId_Any, titleGetPatchIdByApplicationId(app_id)); + patch_info = _titleGetTitleInfoEntryFromStorageByTitleId(NcmStorageId_Any, titleGetPatchIdByApplicationId(app_id)); TITLE_ALLOCATE_USER_APP_DATA(patch, "patch", break); /* Get info for the first add-on content and add-on content patch titles. */ @@ -939,7 +966,7 @@ TitleInfo *titleGetAddOnContentBaseOrPatchList(TitleInfo *title_info) bool error = false; /* Get info for the first add-on content (patch) title matching the lookup title ID. */ - aoc_info = _titleGetInfoFromStorageByTitleId(NcmStorageId_Any, lookup_tid); + aoc_info = _titleGetTitleInfoEntryFromStorageByTitleId(NcmStorageId_Any, lookup_tid); if (!aoc_info) break; /* Create our own custom linked list using entries that match our lookup title ID. */ @@ -1371,6 +1398,49 @@ static bool titleInitializeTitleStorage(u8 storage_id) return success; } +static bool titleInitializeGameCardTitleStorageByHashFileSystem(u8 hfs_partition_type) +{ + if (hfs_partition_type < HashFileSystemPartitionType_Root || hfs_partition_type >= HashFileSystemPartitionType_Count) + { + LOG_MSG_ERROR("Invalid parameters!"); + return false; + } + + /* Close title storage before proceeding. */ + titleCloseTitleStorage(NcmStorageId_GameCard); + + TitleStorage *title_storage = &(g_titleStorage[TITLE_STORAGE_INDEX(NcmStorageId_GameCard)]); + HashFileSystemContext hfs_ctx = {0}; + bool success = false; + + /* Set ncm storage ID. */ + title_storage->storage_id = NcmStorageId_GameCard; + + /* Get Hash FS context for the desired partition on the gamecard. */ + if (!gamecardGetHashFileSystemContext(hfs_partition_type, &hfs_ctx)) + { + LOG_MSG_ERROR("Failed to retrieve HFS context for the gamecard's %s partition.", hfsGetPartitionNameString(hfs_partition_type)); + goto end; + } + + /* Generate title info entries for this storage. */ + if (!titleGenerateTitleInfoEntriesByHashFileSystemForGameCardTitleStorage(title_storage, &hfs_ctx)) + { + LOG_MSG_ERROR("Failed to generate title info entries!"); + goto end; + } + + LOG_MSG_INFO("Loaded %u title info %s.", title_storage->title_count, (title_storage->title_count == 1 ? "entry" : "entries")); + + /* Update flag. */ + success = true; + +end: + hfsFreeContext(&hfs_ctx); + + return success; +} + static void titleCloseTitleStorage(u8 storage_id) { if (storage_id < NcmStorageId_GameCard || storage_id > NcmStorageId_SdCard) return; @@ -1507,7 +1577,7 @@ static void titleAddOrphanTitleInfoEntry(TitleInfo *orphan_title) g_orphanTitleInfo[g_orphanTitleInfoCount++] = orphan_title; /* Sort orphan title info entries by title ID, version and storage ID. */ - if (g_orphanTitleInfoCount > 1) qsort(g_orphanTitleInfo, g_orphanTitleInfoCount, sizeof(TitleInfo*), &titleInfoEntrySortFunction); + if (g_orphanTitleInfoCount > 1) qsort(g_orphanTitleInfo, g_orphanTitleInfoCount, sizeof(TitleInfo*), &titleInfoSortFunction); } static bool titleGenerateMetadataEntriesFromSystemTitles(void) @@ -1545,8 +1615,8 @@ static bool titleGenerateMetadataEntriesFromSystemTitles(void) /* Update application metadata count. */ g_systemMetadataCount += g_systemTitlesCount; - /* Sort metadata entries by title ID. */ - if (g_systemMetadataCount > 1) qsort(g_systemMetadata, g_systemMetadataCount, sizeof(TitleApplicationMetadata*), &titleSystemTitleMetadataEntrySortFunction); + /* Sort application metadata entries by title ID. */ + if (g_systemMetadataCount > 1) qsort(g_systemMetadata, g_systemMetadataCount, sizeof(TitleApplicationMetadata*), &titleSystemMetadataSortFunction); /* Update flag. */ success = true; @@ -1598,7 +1668,7 @@ static bool titleGenerateMetadataEntriesFromNsRecords(void) app_records_count += app_records_block_count; } while(app_records_block_count >= NS_APPLICATION_RECORD_BLOCK_SIZE); - /* Return right away if no records were retrieved. */ + /* Return right away if no records are available. */ if (!app_records_count) { success = true; @@ -1617,23 +1687,12 @@ static bool titleGenerateMetadataEntriesFromNsRecords(void) /* Retrieve application metadata for each NS application record. */ for(u32 i = 0; i < app_records_count; i++) { - TitleApplicationMetadata *cur_app_metadata = g_userMetadata[g_userMetadataCount + extra_app_count]; - if (!cur_app_metadata) - { - /* Allocate memory for a new application metadata entry. */ - cur_app_metadata = calloc(1, sizeof(TitleApplicationMetadata)); - if (!cur_app_metadata) - { - LOG_MSG_ERROR("Failed to allocate memory for application metadata entry #%u! (%u / %u).", extra_app_count, i + 1, app_records_count); - goto end; - } - - /* Set application metadata entry pointer. */ - g_userMetadata[g_userMetadataCount + extra_app_count] = cur_app_metadata; - } - /* Retrieve application metadata. */ - if (!titleRetrieveUserApplicationMetadataByTitleId(app_records[i].application_id, cur_app_metadata)) continue; + TitleApplicationMetadata *cur_app_metadata = titleGenerateUserMetadataEntryFromNs(app_records[i].application_id); + if (!cur_app_metadata) continue; + + /* Set application metadata entry pointer. */ + g_userMetadata[g_userMetadataCount + extra_app_count] = cur_app_metadata; /* Increase extra application metadata counter. */ extra_app_count++; @@ -1653,7 +1712,7 @@ static bool titleGenerateMetadataEntriesFromNsRecords(void) if (extra_app_count < app_records_count) titleReallocateApplicationMetadata(0, false, false); /* Sort application metadata entries by name. */ - if (g_userMetadataCount > 1) qsort(g_userMetadata, g_userMetadataCount, sizeof(TitleApplicationMetadata*), &titleUserApplicationMetadataEntrySortFunction); + if (g_userMetadataCount > 1) qsort(g_userMetadata, g_userMetadataCount, sizeof(TitleApplicationMetadata*), &titleUserMetadataSortFunction); /* Update flag. */ success = true; @@ -1667,7 +1726,7 @@ static bool titleGenerateMetadataEntriesFromNsRecords(void) return success; } -static TitleApplicationMetadata *titleGenerateDummySystemMetadataEntry(u64 title_id) +static TitleApplicationMetadata *titleGetSystemMetadataEntry(u64 title_id) { if (!title_id) { @@ -1675,8 +1734,29 @@ static TitleApplicationMetadata *titleGenerateDummySystemMetadataEntry(u64 title return NULL; } - TitleApplicationMetadata *cur_app_metadata = NULL; - bool free_entry = false; + TitleApplicationMetadata *app_metadata = NULL; + bool success = false; + + /* Make sure we have no application metadata entry for the requested title ID. */ + /* If we do, we'll just return a pointer to the existing entry. */ + app_metadata = titleFindApplicationMetadataByTitleId(title_id, true, 0); + if (app_metadata) + { + success = true; + goto end; + } + + /* Allocate memory for the current entry. */ + app_metadata = calloc(1, sizeof(TitleApplicationMetadata)); + if (!app_metadata) + { + LOG_MSG_ERROR("Failed to allocate memory for application metadata entry for %016lX!", title_id); + goto end; + } + + /* Fill information for our dummy entry. */ + app_metadata->title_id = title_id; + sprintf(app_metadata->lang_entry.name, "Unknown"); /* Reallocate application metadata pointer array. */ if (!titleReallocateApplicationMetadata(1, true, false)) @@ -1685,85 +1765,277 @@ static TitleApplicationMetadata *titleGenerateDummySystemMetadataEntry(u64 title goto end; } - free_entry = true; + /* Set application metadata entry pointer. */ + g_systemMetadata[g_systemMetadataCount++] = app_metadata; + + /* Sort application metadata entries by title ID. */ + if (g_systemMetadataCount > 1) qsort(g_systemMetadata, g_systemMetadataCount, sizeof(TitleApplicationMetadata*), &titleSystemMetadataSortFunction); - /* Allocate memory for the current entry. */ - cur_app_metadata = calloc(1, sizeof(TitleApplicationMetadata)); - if (!cur_app_metadata) + /* Update flag. */ + success = true; + +end: + if (!success && app_metadata) { - LOG_MSG_ERROR("Failed to allocate memory for application metadata %016lX!", title_id); - goto end; + free(app_metadata); + app_metadata = NULL; } - /* Fill information. */ - cur_app_metadata->title_id = title_id; - sprintf(cur_app_metadata->lang_entry.name, "Unknown"); + return app_metadata; +} - /* Set application metadata entry pointer. */ - g_systemMetadata[g_systemMetadataCount++] = cur_app_metadata; +static TitleApplicationMetadata *titleGenerateUserMetadataEntryFromNs(u64 title_id) +{ + if (!g_nsAppControlData || !title_id) + { + LOG_MSG_ERROR("Invalid parameters!"); + return NULL; + } + + u64 control_data_size = 0; + TitleApplicationMetadata *app_metadata = NULL; + + /* Retrieve application control data from ns. */ + if (!titleGetApplicationControlDataFromNs(title_id, g_nsAppControlData, &control_data_size)) + { + LOG_MSG_ERROR("Failed to retrieve application control data for %016lX!", title_id); + goto end; + } - /* Sort metadata entries by title ID. */ - if (g_systemMetadataCount > 1) qsort(g_systemMetadata, g_systemMetadataCount, sizeof(TitleApplicationMetadata*), &titleSystemTitleMetadataEntrySortFunction); + /* Initialize application metadata entry using the control data we just retrieved. */ + app_metadata = titleInitializeUserMetadataEntryFromControlData(title_id, g_nsAppControlData, control_data_size); + if (!app_metadata) LOG_MSG_ERROR("Failed to generate application metadata entry for %016lX!", title_id); end: - /* Free previously allocated application metadata pointer. Ignore return value. */ - if (!cur_app_metadata && free_entry) titleReallocateApplicationMetadata(0, true, false); + return app_metadata; +} + +static TitleApplicationMetadata *titleGenerateUserMetadataEntryFromControlNca(TitleInfo *title_info) +{ + if (!g_nsAppControlData || !title_info || !title_info->meta_key.id || title_info->app_metadata || \ + (title_info->meta_key.type != NcmContentMetaType_Application && title_info->meta_key.type != NcmContentMetaType_Patch)) + { + LOG_MSG_ERROR("Invalid parameters!"); + return NULL; + } + + u64 control_data_size = 0, title_id = title_info->meta_key.id, app_id = titleGetApplicationIdByContentMetaKey(&(title_info->meta_key)); + TitleApplicationMetadata *app_metadata = NULL; + + /* Retrieve application control data from Control NCA. */ + if (!titleGetApplicationControlDataFromControlNca(title_info, g_nsAppControlData, &control_data_size)) + { + LOG_MSG_ERROR("Failed to retrieve application control data for %016lX!", title_id); + goto end; + } - return cur_app_metadata; + /* Initialize application metadata entry using the control data we just retrieved. */ + app_metadata = titleInitializeUserMetadataEntryFromControlData(app_id, g_nsAppControlData, control_data_size); + if (!app_metadata) LOG_MSG_ERROR("Failed to generate application metadata entry for %016lX!", title_id); + +end: + return app_metadata; } -static bool titleRetrieveUserApplicationMetadataByTitleId(u64 title_id, TitleApplicationMetadata *out) +static bool titleGetApplicationControlDataFromNs(u64 title_id, NsApplicationControlData *out_control_data, u64 *out_control_data_size) { - if (!g_nsAppControlData || !title_id || !out) + if (!title_id || !out_control_data || !out_control_data_size) { LOG_MSG_ERROR("Invalid parameters!"); return false; } Result rc = 0; - u64 write_size = 0; - NacpLanguageEntry *lang_entry = NULL; - - u32 icon_size = 0; - u8 *icon = NULL; + u64 control_data_size = 0; - /* Retrieve ns application control data. */ - rc = nsGetApplicationControlData(NsApplicationControlSource_Storage, title_id, g_nsAppControlData, sizeof(NsApplicationControlData), &write_size); + /* Retrieve application control data from ns. */ + rc = nsGetApplicationControlData(NsApplicationControlSource_Storage, title_id, out_control_data, sizeof(NsApplicationControlData), &control_data_size); if (R_FAILED(rc)) { LOG_MSG_ERROR("nsGetApplicationControlData failed for title ID \"%016lX\"! (0x%X).", title_id, rc); return false; } - if (write_size < sizeof(NacpStruct)) + /* Sanity check. */ + if (control_data_size < sizeof(NacpStruct)) + { + LOG_MSG_ERROR("Retrieved application control data buffer for title ID \"%016lX\" is too small! (0x%lX).", title_id, control_data_size); + return false; + } + + /* Update output size. */ + *out_control_data_size = control_data_size; + + return true; +} + +static bool titleGetApplicationControlDataFromControlNca(TitleInfo *title_info, NsApplicationControlData *out_control_data, u64 *out_control_data_size) +{ + NcmContentInfo *nacp_content = NULL; + + if (!title_info || !title_info->meta_key.id || (title_info->meta_key.type != NcmContentMetaType_Application && title_info->meta_key.type != NcmContentMetaType_Patch) || \ + !(nacp_content = titleGetContentInfoByTypeAndIdOffset(title_info, NcmContentType_Control, 0)) || !out_control_data || !out_control_data_size) { - LOG_MSG_ERROR("Retrieved application control data buffer is too small! (0x%lX).", write_size); + LOG_MSG_ERROR("Invalid parameters!"); return false; } + u64 title_id = title_info->meta_key.id; + u8 storage_id = title_info->storage_id; + u8 hfs_partition_type = (storage_id == NcmStorageId_GameCard ? HashFileSystemPartitionType_Secure : HashFileSystemPartitionType_None); + + NcaContext *nca_ctx = NULL; + NacpContext nacp_ctx = {0}; + + Result rc = 0; + NacpLanguageEntry *lang_entry = NULL; + u8 lang_id = NacpLanguage_AmericanEnglish; /* Fallback value. */ + + NacpIconContext *icon_ctx = NULL; + + bool success = false; + + LOG_MSG_DEBUG("Retrieving application control data for %s %016lX in %s...", titleGetNcmContentMetaTypeName(title_info->meta_key.type), title_id, \ + titleGetNcmStorageIdName(title_info->storage_id)); + + /* Allocate memory for the NCA context. */ + nca_ctx = calloc(1, sizeof(NcaContext)); + if (!nca_ctx) + { + LOG_MSG_ERROR("Failed to allocate memory for NCA context!"); + goto end; + } + + /* Initialize NCA context. */ + if (!ncaInitializeContext(nca_ctx, storage_id, hfs_partition_type, &(title_info->meta_key), nacp_content, NULL)) + { + LOG_MSG_ERROR("Failed to initialize NCA context for Control NCA from %016lX!", title_id); + goto end; + } + + /* Initialize NACP context. */ + if (!nacpInitializeContext(&nacp_ctx, nca_ctx)) + { + LOG_MSG_ERROR("Failed to initialize NACP context for %016lX!", title_id); + goto end; + } + /* Get language entry. */ - rc = nacpGetLanguageEntry(&(g_nsAppControlData->nacp), &lang_entry); + rc = nacpGetLanguageEntry((NacpStruct*)nacp_ctx.data, &lang_entry); if (R_FAILED(rc)) { LOG_MSG_ERROR("nacpGetLanguageEntry failed! (0x%X).", rc); - return false; + goto end; + } + + /* Determine language ID from the selected entry. */ + for(u8 i = NacpLanguage_AmericanEnglish; i < NacpLanguage_Count; i++) + { + /* Don't proceed any further if no language entry was retrieved. */ + if (!lang_entry) break; + + NacpTitle *cur_title = &(nacp_ctx.data->title[i]); + if (cur_title == (NacpTitle*)lang_entry) + { + lang_id = i; + LOG_MSG_DEBUG("Selected language ID for %016lX: %u (%s).", title_id, lang_id, nacpGetLanguageString(lang_id)); + break; + } + } + + /* Find the right NACP icon for the selected language entry. */ + for(u8 i = 0; i < nacp_ctx.icon_count; i++) + { + icon_ctx = &(nacp_ctx.icon_ctx[i]); + if (icon_ctx->language == lang_id) break; + icon_ctx = NULL; + } + + /* Fallback to the first available icon if we couldn't find one for our language ID. */ + if (!icon_ctx && nacp_ctx.icon_ctx) icon_ctx = &(nacp_ctx.icon_ctx[0]); + + /* Sanity check. */ + if (icon_ctx && (!icon_ctx->icon_size || icon_ctx->icon_size > NACP_MAX_ICON_SIZE)) + { + LOG_MSG_ERROR("Invalid icon size! (0x%lX)", icon_ctx->icon_size); + goto end; + } + + /* Fill output. */ + memcpy(&(out_control_data->nacp), nacp_ctx.data, sizeof(NacpStruct)); + + if (icon_ctx) + { + size_t diff = (NACP_MAX_ICON_SIZE - icon_ctx->icon_size); + memcpy(out_control_data->icon, icon_ctx->icon_data, icon_ctx->icon_size); + if (diff) memset(out_control_data->icon + icon_ctx->icon_size, 0, diff); + } else { + memset(out_control_data->icon, 0, NACP_MAX_ICON_SIZE); + } + + *out_control_data_size = (sizeof(NacpStruct) + (icon_ctx ? icon_ctx->icon_size : 0)); + + /* Update flag. */ + success = true; + +end: + nacpFreeContext(&nacp_ctx); + + if (nca_ctx) free(nca_ctx); + + return success; +} + +static TitleApplicationMetadata *titleInitializeUserMetadataEntryFromControlData(u64 title_id, const NsApplicationControlData *control_data, u64 control_data_size) +{ + if (!title_id || !control_data || control_data_size < sizeof(NacpStruct)) + { + LOG_MSG_ERROR("Invalid parameters!"); + return NULL; + } + + Result rc = 0; + NacpLanguageEntry *lang_entry = NULL; + u32 icon_size = 0; + TitleApplicationMetadata *out = NULL; + bool success = false; + + /* Get language entry. */ + rc = nacpGetLanguageEntry((NacpStruct*)&(control_data->nacp), &lang_entry); + if (R_FAILED(rc)) + { + LOG_MSG_ERROR("nacpGetLanguageEntry failed! (0x%X).", rc); + goto end; + } + + /* Allocate memory for our application metadata entry. */ + out = calloc(1, sizeof(TitleApplicationMetadata)); + if (!out) + { + LOG_MSG_ERROR("Error allocating memory for application metadata entry for %016lX!", title_id); + goto end; } - /* Get icon. */ - icon_size = (u32)(write_size - sizeof(NacpStruct)); + /* Calculate icon size. */ + icon_size = (u32)(control_data_size - sizeof(NacpStruct)); if (icon_size) { - icon = malloc(icon_size); - if (!icon) + /* Allocate memory for our icon. */ + out->icon = malloc(icon_size); + if (!out->icon) { - LOG_MSG_ERROR("Error allocating memory for the icon buffer! (0x%X).", icon_size); - return false; + LOG_MSG_ERROR("Error allocating memory for the icon buffer! (0x%X, %016lX).", icon_size, title_id); + goto end; } - memcpy(icon, g_nsAppControlData->icon, icon_size); + /* Copy icon data. */ + memcpy(out->icon, control_data->icon, icon_size); + + /* Set icon size. */ + out->icon_size = icon_size; } - /* Copy data. */ + /* Fill the rest of the information. */ out->title_id = title_id; if (lang_entry) @@ -1775,13 +2047,20 @@ static bool titleRetrieveUserApplicationMetadataByTitleId(u64 title_id, TitleApp /* Yes, this can happen -- NACPs with empty language entries are a thing, somehow. */ sprintf(out->lang_entry.name, "Unknown"); sprintf(out->lang_entry.author, "Unknown"); - LOG_DATA_DEBUG(&(g_nsAppControlData->nacp), sizeof(NacpStruct), "NACP dump (ID %016lX):", title_id); + LOG_DATA_DEBUG(&(control_data->nacp), sizeof(NacpStruct), "NACP dump (ID %016lX):", title_id); } - out->icon_size = icon_size; - out->icon = icon; + /* Update flag. */ + success = true; - return true; +end: + if (!success && out) + { + free(out); + out = NULL; + } + + return out; } static void titleGenerateFilteredApplicationMetadataPointerArray(bool is_system) @@ -1826,7 +2105,7 @@ static void titleGenerateFilteredApplicationMetadataPointerArray(bool is_system) if (!cur_app_metadata) continue; /* Skip current metadata entry if content data for this title isn't available. */ - if ((is_system && !_titleGetInfoFromStorageByTitleId(NcmStorageId_BuiltInSystem, cur_app_metadata->title_id)) || \ + if ((is_system && !_titleGetTitleInfoEntryFromStorageByTitleId(NcmStorageId_BuiltInSystem, cur_app_metadata->title_id)) || \ (!is_system && !titleIsUserApplicationContentAvailable(cur_app_metadata->title_id))) continue; /* Reallocate filtered application metadata pointer array. */ @@ -1862,108 +2141,30 @@ static void titleGenerateFilteredApplicationMetadataPointerArray(bool is_system) } } -static void titleGenerateGameCardApplicationMetadataArray(void) +static bool titleIsUserApplicationContentAvailable(u64 app_id) { - TitleStorage *title_storage = &(g_titleStorage[TITLE_STORAGE_INDEX(NcmStorageId_GameCard)]); - TitleInfo **titles = title_storage->titles; - u32 title_count = title_storage->title_count; - TitleGameCardApplicationMetadata *tmp_gc_app_metadata = NULL; + if (!app_id) return false; - /* Free gamecard application metadata array. */ - if (g_titleGameCardApplicationMetadata) + for(u8 i = NcmStorageId_GameCard; i <= NcmStorageId_SdCard; i++) { - free(g_titleGameCardApplicationMetadata); - g_titleGameCardApplicationMetadata = NULL; - } + if (i == NcmStorageId_BuiltInSystem) continue; - g_titleGameCardApplicationMetadataCount = 0; + TitleStorage *title_storage = &(g_titleStorage[TITLE_STORAGE_INDEX(i)]); + if (!title_storage->titles || !*(title_storage->titles) || !title_storage->title_count) continue; - /* Make sure we actually have gamecard TitleInfo entries we can work with. */ - if (!titles || !title_count) - { - LOG_MSG_ERROR("No gamecard TitleInfo entries available!"); - return; - } + for(u32 j = 0; j < title_storage->title_count; j++) + { + TitleInfo *title_info = title_storage->titles[j]; + if (!title_info) continue; - /* Loop through our gamecard TitleInfo entries. */ - LOG_MSG_DEBUG("Retrieving gamecard application metadata (%u title[s])...", title_count); - - for(u32 i = 0; i < title_count; i++) - { - /* Skip current entry if it's not a user application. */ - TitleInfo *app_info = titles[i], *patch_info = NULL; - if (!app_info || app_info->meta_key.type != NcmContentMetaType_Application) continue; - - u32 app_version = app_info->meta_key.version; - u32 dlc_count = 0; - - /* 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 = 0; j < title_count; j++) - { - if (j == i) continue; - - TitleInfo *cur_title_info = titles[j]; - if (!cur_title_info || cur_title_info->meta_key.type != NcmContentMetaType_Patch || \ - !titleCheckIfPatchIdBelongsToApplicationId(app_info->meta_key.id, cur_title_info->meta_key.id) || cur_title_info->meta_key.version <= app_version) continue; - - patch_info = cur_title_info; - app_version = cur_title_info->meta_key.version; - } - - /* Count DLCs available for this application in the inserted gamecard. */ - for(u32 j = 0; j < title_count; j++) - { - if (j == i) continue; - - TitleInfo *cur_title_info = titles[j]; - if (!cur_title_info || cur_title_info->meta_key.type != NcmContentMetaType_AddOnContent || \ - !titleCheckIfAddOnContentIdBelongsToApplicationId(app_info->meta_key.id, cur_title_info->meta_key.id)) continue; - - dlc_count++; - } - - /* Reallocate application metadata pointer array. */ - tmp_gc_app_metadata = realloc(g_titleGameCardApplicationMetadata, (g_titleGameCardApplicationMetadataCount + 1) * sizeof(TitleGameCardApplicationMetadata)); - if (!tmp_gc_app_metadata) - { - LOG_MSG_ERROR("Failed to reallocate gamecard application metadata array!"); - - if (g_titleGameCardApplicationMetadata) free(g_titleGameCardApplicationMetadata); - g_titleGameCardApplicationMetadata = NULL; - g_titleGameCardApplicationMetadataCount = 0; - - return; - } - - g_titleGameCardApplicationMetadata = tmp_gc_app_metadata; - tmp_gc_app_metadata = NULL; - - /* Fill current entry and increase counter. */ - tmp_gc_app_metadata = &(g_titleGameCardApplicationMetadata[g_titleGameCardApplicationMetadataCount++]); - memset(tmp_gc_app_metadata, 0, sizeof(TitleGameCardApplicationMetadata)); - tmp_gc_app_metadata->app_metadata = app_info->app_metadata; - tmp_gc_app_metadata->has_patch = (patch_info != NULL); - tmp_gc_app_metadata->version.value = app_version; - tmp_gc_app_metadata->dlc_count = dlc_count; - - /* Try to retrieve the display version. */ - char *version_str = titleGetDisplayVersionString(patch_info ? patch_info : app_info); - if (version_str) - { - snprintf(tmp_gc_app_metadata->display_version, MAX_ELEMENTS(tmp_gc_app_metadata->display_version), "%s", version_str); - free(version_str); + if ((title_info->meta_key.type == NcmContentMetaType_Application && title_info->meta_key.id == app_id) || \ + (title_info->meta_key.type == NcmContentMetaType_Patch && titleCheckIfPatchIdBelongsToApplicationId(app_id, title_info->meta_key.id)) || \ + (title_info->meta_key.type == NcmContentMetaType_AddOnContent && titleCheckIfAddOnContentIdBelongsToApplicationId(app_id, title_info->meta_key.id)) || \ + (title_info->meta_key.type == NcmContentMetaType_DataPatch && titleCheckIfDataPatchIdBelongsToApplicationId(app_id, title_info->meta_key.id))) return true; } } - if (g_titleGameCardApplicationMetadata && g_titleGameCardApplicationMetadataCount) - { - /* Reorder title metadata entries by name. */ - if (g_titleGameCardApplicationMetadataCount > 1) qsort(g_titleGameCardApplicationMetadata, g_titleGameCardApplicationMetadataCount, sizeof(TitleGameCardApplicationMetadata), - &titleGameCardApplicationMetadataSortFunction); - } else { - LOG_MSG_ERROR("No gamecard content data found for user applications!"); - } + return false; } NX_INLINE TitleApplicationMetadata *titleFindApplicationMetadataByTitleId(u64 title_id, bool is_system, u32 extra_app_count) @@ -2020,75 +2221,54 @@ static bool titleGenerateTitleInfoEntriesForTitleStorage(TitleStorage *title_sto u8 storage_id = title_storage->storage_id; NcmContentMetaDatabase *ncm_db = &(title_storage->ncm_db); - u32 total = 0, extra_title_count = 0; + u32 meta_key_count = 0, extra_title_count = 0; NcmContentMetaKey *meta_keys = NULL; bool success = false, free_entries = false; /* Get content meta keys for this storage. */ - if (!titleGetMetaKeysFromContentDatabase(ncm_db, &meta_keys, &total)) goto end; + if (!titleGetMetaKeysFromContentDatabase(ncm_db, &meta_keys, &meta_key_count)) goto end; /* Check if we're dealing with an empty storage. */ - if (!total) + if (!meta_key_count) { success = true; goto end; } /* Reallocate pointer array in title storage. */ - if (!titleReallocateTitleInfoFromStorage(title_storage, total, false)) goto end; + if (!titleReallocateTitleInfoFromStorage(title_storage, meta_key_count, false)) goto end; free_entries = true; /* Fill new title info entries. */ - for(u32 i = 0; i < total; i++) + for(u32 i = 0; i < meta_key_count; i++) { - u64 tmp_size = 0; NcmContentMetaKey *cur_meta_key = &(meta_keys[i]); - TitleInfo *cur_title_info = title_storage->titles[title_storage->title_count + extra_title_count]; - if (!cur_title_info) - { - /* Allocate memory for a new entry. */ - cur_title_info = calloc(1, sizeof(TitleInfo)); - if (!cur_title_info) - { - LOG_MSG_ERROR("Failed to allocate memory for title info entry #%u! (%u / %u).", extra_title_count, i + 1, total); - goto end; - } + NcmContentInfo *content_infos = NULL; + u32 content_count = 0; - /* Set title info entry pointer. */ - title_storage->titles[title_storage->title_count + extra_title_count] = cur_title_info; - } + TitleInfo *title_info = NULL; /* Get content infos. */ - if (!titleGetContentInfosForMetaKey(ncm_db, cur_meta_key, &(cur_title_info->content_infos), &(cur_title_info->content_count))) + if (!titleGetContentInfosByMetaKey(ncm_db, cur_meta_key, &content_infos, &content_count)) { - LOG_MSG_ERROR("Failed to get content infos for title ID %016lX!", cur_meta_key->id); + LOG_MSG_ERROR("Failed to get content infos for %016lX!", cur_meta_key->id); continue; } - /* Calculate title size. */ - for(u32 j = 0; j < cur_title_info->content_count; j++) + /* Generate TitleInfo entry. */ + title_info = titleGenerateTitleInfoEntry(storage_id, cur_meta_key, content_infos, content_count, false); + if (!title_info) { - ncmContentInfoSizeToU64(&(cur_title_info->content_infos[j]), &tmp_size); - cur_title_info->size += tmp_size; + LOG_MSG_ERROR("Failed to generate TitleInfo entry for %016lX!", cur_meta_key->id); + free(content_infos); + continue; } - /* Fill information. */ - cur_title_info->storage_id = storage_id; - memcpy(&(cur_title_info->meta_key), cur_meta_key, sizeof(NcmContentMetaKey)); - cur_title_info->version.value = cur_title_info->meta_key.version; - utilsGenerateFormattedSizeString((double)cur_title_info->size, cur_title_info->size_str, sizeof(cur_title_info->size_str)); - - /* Retrieve application metadata. */ - u64 app_id = titleGetApplicationIdByContentMetaKey(&(cur_title_info->meta_key)); - cur_title_info->app_metadata = titleFindApplicationMetadataByTitleId(app_id, storage_id == NcmStorageId_BuiltInSystem, 0); - if (!cur_title_info->app_metadata && storage_id == NcmStorageId_BuiltInSystem) - { - /* Generate dummy system metadata entry if we have no hardcoded information for this system title. */ - cur_title_info->app_metadata = titleGenerateDummySystemMetadataEntry(cur_title_info->meta_key.id); - } + /* Set title info entry pointer. */ + title_storage->titles[title_storage->title_count + extra_title_count] = title_info; /* Increase extra title info counter. */ extra_title_count++; @@ -2097,7 +2277,7 @@ static bool titleGenerateTitleInfoEntriesForTitleStorage(TitleStorage *title_sto /* Check retrieved title info count. */ if (!extra_title_count) { - LOG_MSG_ERROR("Unable to generate title info entries! (%u element[s]).", total); + LOG_MSG_ERROR("Unable to generate title info entries! (%u element[s]).", meta_key_count); goto end; } @@ -2105,10 +2285,10 @@ static bool titleGenerateTitleInfoEntriesForTitleStorage(TitleStorage *title_sto title_storage->title_count += extra_title_count; /* Free extra allocated pointers if we didn't use them. */ - if (extra_title_count < total) titleReallocateTitleInfoFromStorage(title_storage, 0, false); + if (extra_title_count < meta_key_count) titleReallocateTitleInfoFromStorage(title_storage, 0, false); /* Sort title info entries by title ID, version and storage ID. */ - if (title_storage->title_count > 1) qsort(title_storage->titles, title_storage->title_count, sizeof(TitleInfo*), &titleInfoEntrySortFunction); + if (title_storage->title_count > 1) qsort(title_storage->titles, title_storage->title_count, sizeof(TitleInfo*), &titleInfoSortFunction); /* 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. */ @@ -2118,11 +2298,218 @@ static bool titleGenerateTitleInfoEntriesForTitleStorage(TitleStorage *title_sto success = true; end: + /* Free previously allocated title info pointers. Ignore return value. */ + if (!success && free_entries) titleReallocateTitleInfoFromStorage(title_storage, extra_title_count, true); + if (meta_keys) free(meta_keys); + return success; +} + +static bool titleGenerateTitleInfoEntriesByHashFileSystemForGameCardTitleStorage(TitleStorage *title_storage, HashFileSystemContext *hfs_ctx) +{ + if (!title_storage || title_storage->storage_id != NcmStorageId_GameCard || !hfsIsValidContext(hfs_ctx)) + { + LOG_MSG_ERROR("Invalid parameters!"); + return false; + } + + TitleGameCardContentMetaContext *gc_meta_ctxs = NULL; + u32 gc_meta_ctx_count = 0, extra_title_count = 0; + bool success = false, free_entries = false; + + /* Get gamecard Content Meta contexts. */ + if (!titleGetGameCardContentMetaContexts(hfs_ctx, &gc_meta_ctxs, &gc_meta_ctx_count)) + { + LOG_MSG_ERROR("Failed to retrieve gamecard Content Meta contexts! (%s partition).", hfsGetPartitionNameString(hfs_ctx->type)); + goto end; + } + + /* Check if we're dealing with an empty storage. */ + if (!gc_meta_ctx_count) + { + success = true; + goto end; + } + + /* Reallocate pointer array in title storage. */ + if (!titleReallocateTitleInfoFromStorage(title_storage, gc_meta_ctx_count, false)) goto end; + + free_entries = true; + + /* Fill new title info entries. */ + for(u32 i = 0; i < gc_meta_ctx_count; i++) + { + TitleGameCardContentMetaContext *cur_gc_meta_ctx = &(gc_meta_ctxs[i]); + NcmContentMetaKey *meta_key = &(cur_gc_meta_ctx->meta_key); + + NcmContentInfo *content_infos = NULL; + u32 content_count = 0; + + TitleInfo *title_info = NULL; + + /* Get content infos. */ + if (!titleGetContentInfosByGameCardContentMetaContext(cur_gc_meta_ctx, hfs_ctx, &content_infos, &content_count)) + { + LOG_MSG_ERROR("Failed to get content infos for %016lX! (%s partition).", meta_key->id, hfsGetPartitionNameString(hfs_ctx->type)); + continue; + } + + /* Generate TitleInfo entry. */ + title_info = titleGenerateTitleInfoEntry(NcmStorageId_GameCard, meta_key, content_infos, content_count, true); + if (!title_info) + { + LOG_MSG_ERROR("Failed to generate TitleInfo entry for %016lX! (%s partition).", meta_key->id, hfsGetPartitionNameString(hfs_ctx->type)); + free(content_infos); + continue; + } + + /* Set title info entry pointer. */ + title_storage->titles[title_storage->title_count + extra_title_count] = title_info; + + /* Increase extra title info counter. */ + extra_title_count++; + } + + /* Check retrieved title info count. */ + if (!extra_title_count) + { + LOG_MSG_ERROR("Unable to generate title info entries! (%u element[s]).", gc_meta_ctx_count); + goto end; + } + + /* Update title info count. */ + title_storage->title_count += extra_title_count; + + /* Free extra allocated pointers if we didn't use them. */ + if (extra_title_count < gc_meta_ctx_count) titleReallocateTitleInfoFromStorage(title_storage, 0, false); + + /* Sort title info entries by title ID, version and storage ID. */ + if (title_storage->title_count > 1) qsort(title_storage->titles, title_storage->title_count, sizeof(TitleInfo*), &titleInfoSortFunction); + + /* Update linked lists for user applications, patches and add-on contents. */ + titleUpdateTitleInfoLinkedLists(); + + /* Update flag. */ + success = true; + +end: /* Free previously allocated title info pointers. Ignore return value. */ if (!success && free_entries) titleReallocateTitleInfoFromStorage(title_storage, extra_title_count, true); + titleFreeGameCardContentMetaContexts(&gc_meta_ctxs, gc_meta_ctx_count); + + return success; +} + +static TitleInfo *titleGenerateTitleInfoEntry(u8 storage_id, const NcmContentMetaKey *meta_key, NcmContentInfo *content_infos, u32 content_count, bool get_control_nca_metadata) +{ + if (storage_id < NcmStorageId_GameCard || storage_id > NcmStorageId_SdCard || !meta_key || !meta_key->id || !content_infos || !content_count) + { + LOG_MSG_ERROR("Invalid parameters!"); + return NULL; + } + + TitleInfo *title_info = NULL; + u64 title_size = 0, content_size = 0; + + /* Allocate memory for a new entry. */ + title_info = calloc(1, sizeof(TitleInfo)); + if (!title_info) + { + LOG_MSG_ERROR("Failed to allocate memory for title info entry!"); + return NULL; + } + + /* Calculate title size. */ + for(u32 i = 0; i < content_count; i++) + { + ncmContentInfoSizeToU64(&(content_infos[i]), &content_size); + title_size += content_size; + } + + /* Fill information. */ + title_info->storage_id = storage_id; + memcpy(&(title_info->meta_key), meta_key, sizeof(NcmContentMetaKey)); + title_info->version.value = meta_key->version; + title_info->content_count = content_count; + title_info->content_infos = content_infos; + title_info->size = title_size; + utilsGenerateFormattedSizeString((double)title_size, title_info->size_str, sizeof(title_info->size_str)); + + /* Retrieve application metadata. */ + if (storage_id == NcmStorageId_BuiltInSystem) + { + /* If no application metadata entry is found, titleGetSystemMetadataEntry() will take care of generating a dummy one. */ + title_info->app_metadata = titleGetSystemMetadataEntry(meta_key->id); + } else { + /* Dig through what we have. */ + u64 app_id = titleGetApplicationIdByContentMetaKey(meta_key); + title_info->app_metadata = titleFindApplicationMetadataByTitleId(app_id, false, 0); + if (!title_info->app_metadata && get_control_nca_metadata && (meta_key->type == NcmContentMetaType_Application || meta_key->type == NcmContentMetaType_Patch)) + { + /* Manually retrieve application metadata from this title's Control NCA. */ + titleInitializeTitleInfoApplicationMetadataFromControlNca(title_info); + } + } + + return title_info; +} + +static bool titleInitializeTitleInfoApplicationMetadataFromControlNca(TitleInfo *title_info) +{ + if (!title_info || !title_info->meta_key.id || \ + (title_info->meta_key.type != NcmContentMetaType_Application && title_info->meta_key.type != NcmContentMetaType_Patch)) + { + LOG_MSG_ERROR("Invalid parameters!"); + return false; + } + + TitleApplicationMetadata *app_metadata = NULL; + bool success = false; + + /* Return immediately if an application metadata pointer was already set. */ + if (title_info->app_metadata) + { + success = true; + goto end; + } + + /* Get application metadata. */ + app_metadata = titleGenerateUserMetadataEntryFromControlNca(title_info); + if (!app_metadata) + { + LOG_MSG_ERROR("Failed to generate application metadata from Control NCA for %016lX!", title_info->meta_key.id); + goto end; + } + + /* Reallocate application metadata pointer array. */ + if (!titleReallocateApplicationMetadata(1, false, false)) + { + LOG_MSG_ERROR("Failed to reallocate application metadata pointer array for %016lX!", title_info->meta_key.id); + goto end; + } + + /* Set application metadata entry pointer. */ + g_userMetadata[g_userMetadataCount++] = app_metadata; + + /* Sort application metadata entries by name. */ + if (g_userMetadataCount > 1) qsort(g_userMetadata, g_userMetadataCount, sizeof(TitleApplicationMetadata*), &titleUserMetadataSortFunction); + + /* Update flag. */ + success = true; + +end: + if (app_metadata) + { + if (success) + { + title_info->app_metadata = app_metadata; + } else { + free(app_metadata); + } + } + return success; } @@ -2211,7 +2598,7 @@ static bool titleGetMetaKeysFromContentDatabase(NcmContentMetaDatabase *ncm_db, return success; } -static bool titleGetContentInfosForMetaKey(NcmContentMetaDatabase *ncm_db, const NcmContentMetaKey *meta_key, NcmContentInfo **out_content_infos, u32 *out_content_count) +static bool titleGetContentInfosByMetaKey(NcmContentMetaDatabase *ncm_db, const NcmContentMetaKey *meta_key, NcmContentInfo **out_content_infos, u32 *out_content_count) { if (!ncm_db || !serviceIsActive(&(ncm_db->s)) || !meta_key || !out_content_infos || !out_content_count) { @@ -2285,47 +2672,231 @@ static bool titleGetContentInfosForMetaKey(NcmContentMetaDatabase *ncm_db, const return success; } -static void titleUpdateTitleInfoLinkedLists(void) +static bool titleGetGameCardContentMetaContexts(HashFileSystemContext *hfs_ctx, TitleGameCardContentMetaContext **out_gc_meta_ctxs, u32 *out_gc_meta_ctx_count) { - /* Free orphan title info entries. */ - titleFreeOrphanTitleInfoEntries(); + u32 hfs_entry_count = 0; - /* Loop through all available title storages. */ - for(u8 i = NcmStorageId_GameCard; i <= NcmStorageId_SdCard; i++) + if (!hfsIsValidContext(hfs_ctx) || !(hfs_entry_count = hfsGetEntryCount(hfs_ctx)) || !out_gc_meta_ctxs || !out_gc_meta_ctx_count) { - /* Don't process system titles. */ - if (i == NcmStorageId_BuiltInSystem) continue; + LOG_MSG_ERROR("Invalid parameters!"); + return false; + } - TitleStorage *title_storage = &(g_titleStorage[TITLE_STORAGE_INDEX(i)]); - TitleInfo **titles = title_storage->titles; - u32 title_count = title_storage->title_count; + TitleGameCardContentMetaContext *gc_meta_ctxs = NULL, *gc_meta_ctxs_tmp = NULL; + u32 gc_meta_ctx_count = 0; - /* Don't proceed if the current storage holds no titles. */ - if (!titles || !title_count) continue; + bool success = false; - /* Process titles from the current storage. */ - for(u32 j = 0; j < title_count; j++) + /* Loop through all Hash FS file entries. */ + for(u32 i = 0; i < hfs_entry_count; i++) + { + HashFileSystemEntry *hfs_entry = NULL; + char *hfs_entry_name = NULL; + size_t meta_nca_filename_len = 0; + + /* Retrieve Hash FS file entry information. */ + if (!(hfs_entry = hfsGetEntryByIndex(hfs_ctx, i)) || !(hfs_entry_name = hfsGetEntryName(hfs_ctx, hfs_entry))) { - /* Get pointer to the current title info and reset its linked list pointers. */ - TitleInfo *child_info = titles[j]; - if (!child_info) continue; + LOG_MSG_ERROR("Failed to retrieve Hash FS file entry information for index %u (%s partition).", i, hfsGetPartitionNameString(hfs_ctx->type)); + goto end; + } - child_info->previous = child_info->next = NULL; + /* Skip non-Meta NCAs. */ + if ((meta_nca_filename_len = strlen(hfs_entry_name)) < NCA_HFS_META_NAME_LENGTH || \ + strcasecmp(hfs_entry_name + meta_nca_filename_len - 9, ".cnmt.nca") != 0) + { + LOG_MSG_DEBUG("Skipping non-Meta NCA \"%s\" (index %u, %s partition).", hfs_entry_name, i, hfsGetPartitionNameString(hfs_ctx->type)); + continue; + } - /* If we're dealing with a title that's not an user application, patch, add-on content or add-on content patch, flag it as orphan and proceed onto the next one. */ - if (child_info->meta_key.type < NcmContentMetaType_Application || (child_info->meta_key.type > NcmContentMetaType_AddOnContent && \ - child_info->meta_key.type != NcmContentMetaType_DataPatch)) - { - titleAddOrphanTitleInfoEntry(child_info); - continue; - } + /* Reallocate contexts buffer. */ + gc_meta_ctxs_tmp = realloc(gc_meta_ctxs, (gc_meta_ctx_count + 1) * sizeof(TitleGameCardContentMetaContext)); + if (!gc_meta_ctxs_tmp) + { + LOG_MSG_ERROR("Unable to reallocate gamecard Content Meta contexts buffer! (%u entries, index %u, %s partition).", \ + gc_meta_ctx_count + 1, i, hfsGetPartitionNameString(hfs_ctx->type)); + goto end; + } + + gc_meta_ctxs = gc_meta_ctxs_tmp; + + /* Clear current context and increase counter. */ + gc_meta_ctxs_tmp = &(gc_meta_ctxs[gc_meta_ctx_count++]); + memset(gc_meta_ctxs_tmp, 0, sizeof(TitleGameCardContentMetaContext)); + + /* Get pointers for NCA and Content Meta contexts. */ + NcaContext *nca_ctx = &(gc_meta_ctxs_tmp->nca_ctx); + ContentMetaContext *cnmt_ctx = &(gc_meta_ctxs_tmp->cnmt_ctx); + NcmContentMetaKey *meta_key = &(gc_meta_ctxs_tmp->meta_key); + + /* Initialize NCA context. */ + if (!ncaInitializeContextByHashFileSystemEntry(nca_ctx, hfs_ctx, hfs_entry, NULL)) + { + LOG_MSG_ERROR("Failed to initialize NCA context for \"%s\" (index %u, %s partition).", hfs_entry_name, i, hfsGetPartitionNameString(hfs_ctx->type)); + goto end; + } + + /* Initialize Content Meta context. */ + if (!cnmtInitializeContext(cnmt_ctx, nca_ctx)) + { + LOG_MSG_ERROR("Failed to initialize Content Meta context for \"%s\" (index %u, %s partition).", hfs_entry_name, i, hfsGetPartitionNameString(hfs_ctx->type)); + goto end; + } + + /* Manually fill content meta key using CNMT info. */ + meta_key->id = cnmt_ctx->packaged_header->title_id; + meta_key->version = cnmt_ctx->packaged_header->version.value; + meta_key->type = cnmt_ctx->packaged_header->content_meta_type; + meta_key->install_type = cnmt_ctx->packaged_header->content_install_type; + } + + if (gc_meta_ctx_count) + { + /* Sort gamecard Content Meta contexts in descendent order. */ + /* This is done to make sure control data from patches is processed first. */ + if (gc_meta_ctx_count > 1) qsort(gc_meta_ctxs, gc_meta_ctx_count, sizeof(TitleGameCardContentMetaContext), &titleGameCardContentMetaContextSortFunction); + + /* Update output. */ + *out_gc_meta_ctxs = gc_meta_ctxs; + *out_gc_meta_ctx_count = gc_meta_ctx_count; + } else { + LOG_MSG_INFO("No Meta NCAs available in gamecard %s partition.", hfsGetPartitionNameString(hfs_ctx->type)); + *out_gc_meta_ctx_count = 0; + } + + /* Update flag. */ + success = true; + +end: + if (!success && gc_meta_ctxs) titleFreeGameCardContentMetaContexts(&gc_meta_ctxs, gc_meta_ctx_count); + + return success; +} + +static void titleFreeGameCardContentMetaContexts(TitleGameCardContentMetaContext **gc_meta_ctxs, u32 gc_meta_ctx_count) +{ + TitleGameCardContentMetaContext *ptr = NULL; + if (!gc_meta_ctxs || !(ptr = *gc_meta_ctxs)) return; + + for(u32 i = 0; i < gc_meta_ctx_count; i++) cnmtFreeContext(&(ptr[i].cnmt_ctx)); + + free(ptr); + *gc_meta_ctxs = NULL; +} + +static bool titleGetContentInfosByGameCardContentMetaContext(TitleGameCardContentMetaContext *gc_meta_ctx, HashFileSystemContext *hfs_ctx, NcmContentInfo **out_content_infos, u32 *out_content_count) +{ + if (!gc_meta_ctx || !cnmtIsValidContext(&(gc_meta_ctx->cnmt_ctx)) || !hfsIsValidContext(hfs_ctx) || !gc_meta_ctx->cnmt_ctx.packaged_header->content_count || !out_content_infos || !out_content_count) + { + LOG_MSG_ERROR("Invalid parameters!"); + return false; + } + + NcmContentInfo *content_infos = NULL, *content_infos_tmp = NULL; + u32 content_count = (gc_meta_ctx->cnmt_ctx.packaged_header->content_count + 1), available_count = 0; + + /* Allocate memory for the content infos. */ + content_infos = calloc(content_count, sizeof(NcmContentInfo)); + if (!content_infos) + { + LOG_MSG_ERROR("Unable to allocate memory for the content infos buffer! (%u content[s]).", content_count); + return false; + } + + /* Loop through our NcmPackagedContentInfo entries. */ + for(u32 i = 0; i < content_count; i++) + { + if (i == 0) + { + /* Reserve the very first content info entry for our Meta NCA. */ + NcmContentInfo *cur_content_info = &(content_infos[0]); + + memcpy(&(cur_content_info->content_id), &(gc_meta_ctx->nca_ctx.content_id), sizeof(NcmContentId)); + ncmU64ToContentInfoSize(gc_meta_ctx->nca_ctx.content_size, cur_content_info); + cur_content_info->attr = 0; + cur_content_info->content_type = NcmContentType_Meta; + cur_content_info->id_offset = 0; + + //LOG_DATA_DEBUG(cur_content_info, sizeof(NcmContentInfo), "Forged Meta content record:"); + } else { + char nca_filename[0x30] = {0}; + NcmContentInfo *cur_content_info = &(gc_meta_ctx->cnmt_ctx.packaged_content_info[i - 1].info); + + /* Make sure this content exists on the inserted gamecard. */ + utilsGenerateHexString(nca_filename, sizeof(nca_filename), cur_content_info->content_id.c, sizeof(cur_content_info->content_id.c), false); + strcat(nca_filename, cur_content_info->content_type == NcmContentType_Meta ? ".cnmt.nca" : ".nca"); + + if (!hfsGetEntryByName(hfs_ctx, nca_filename)) + { + LOG_MSG_DEBUG("Unable to locate %s NCA \"%s\" in Hash FS by name (%s partition).", titleGetNcmContentTypeName(cur_content_info->content_type), nca_filename, \ + hfsGetPartitionNameString(hfs_ctx->type)); + continue; + } + + /* Copy content info data. */ + memcpy(&(content_infos[available_count]), cur_content_info, sizeof(NcmContentInfo)); + } + + /* Update available content count. */ + available_count++; + } + + if (available_count < content_count) + { + /* Reallocate output buffer, if needed. */ + content_infos_tmp = realloc(content_infos, available_count * sizeof(NcmContentInfo)); + if (content_infos_tmp) content_infos = content_infos_tmp; + content_infos_tmp = NULL; + } + + /* Update output. */ + *out_content_infos = content_infos; + *out_content_count = available_count; + + return true; +} + +static void titleUpdateTitleInfoLinkedLists(void) +{ + /* Free orphan title info entries. */ + titleFreeOrphanTitleInfoEntries(); + + /* Loop through all available title storages. */ + for(u8 i = NcmStorageId_GameCard; i <= NcmStorageId_SdCard; i++) + { + /* Don't process system titles. */ + if (i == NcmStorageId_BuiltInSystem) continue; + + TitleStorage *title_storage = &(g_titleStorage[TITLE_STORAGE_INDEX(i)]); + TitleInfo **titles = title_storage->titles; + u32 title_count = title_storage->title_count; + + /* Don't proceed if the current storage holds no titles. */ + if (!titles || !title_count) continue; + + /* Process titles from the current storage. */ + for(u32 j = 0; j < title_count; j++) + { + /* Get pointer to the current title info and reset its linked list pointers. */ + TitleInfo *child_info = titles[j]; + if (!child_info) continue; + + child_info->previous = child_info->next = NULL; + + /* If we're dealing with a title that's not an user application, patch, add-on content or add-on content patch, flag it as orphan and proceed onto the next one. */ + if (child_info->meta_key.type < NcmContentMetaType_Application || (child_info->meta_key.type > NcmContentMetaType_AddOnContent && \ + child_info->meta_key.type != NcmContentMetaType_DataPatch)) + { + titleAddOrphanTitleInfoEntry(child_info); + continue; + } if (child_info->meta_key.type != NcmContentMetaType_Application && !child_info->app_metadata) { /* We're dealing with a patch, an add-on content or an add-on content patch. */ /* We'll just retrieve a pointer to the first matching user application entry and use it to set a pointer to an application metadata entry. */ u64 app_id = titleGetApplicationIdByContentMetaKey(&(child_info->meta_key)); - TitleInfo *parent = _titleGetInfoFromStorageByTitleId(NcmStorageId_Any, app_id); + TitleInfo *parent = _titleGetTitleInfoEntryFromStorageByTitleId(NcmStorageId_Any, app_id); if (parent) { /* Set pointer to application metadata. */ @@ -2374,6 +2945,241 @@ static void titleUpdateTitleInfoLinkedLists(void) } } +static TitleInfo *_titleGetTitleInfoEntryFromStorageByTitleId(u8 storage_id, u64 title_id) +{ + if (storage_id < NcmStorageId_GameCard || storage_id > NcmStorageId_Any || !title_id) + { + LOG_MSG_ERROR("Invalid parameters!"); + return NULL; + } + + TitleInfo *out = NULL; + + u8 start_idx = (storage_id == NcmStorageId_Any ? TITLE_STORAGE_INDEX(NcmStorageId_GameCard) : TITLE_STORAGE_INDEX(storage_id)); + u8 max_val = (storage_id == NcmStorageId_Any ? TITLE_STORAGE_INDEX(NcmStorageId_SdCard) : start_idx); + + for(u8 i = start_idx; i <= max_val; i++) + { + TitleStorage *title_storage = &(g_titleStorage[i]); + if (!title_storage->titles || !*(title_storage->titles) || !title_storage->title_count) continue; + + for(u32 j = 0; j < title_storage->title_count; j++) + { + TitleInfo *title_info = title_storage->titles[j]; + if (title_info && title_info->meta_key.id == title_id) + { + out = title_info; + break; + } + } + + if (out) break; + } + + if (!out && storage_id != NcmStorageId_BuiltInSystem) LOG_MSG_DEBUG("Unable to find title info entry with ID \"%016lX\" in %s.", title_id, titleGetNcmStorageIdName(storage_id)); + + return out; +} + +static TitleInfo *titleDuplicateTitleInfoFull(TitleInfo *title_info, TitleInfo *previous, TitleInfo *next) +{ + if (!titleIsValidInfoBlock(title_info)) + { + LOG_MSG_ERROR("Invalid parameters!"); + return NULL; + } + + TitleInfo *title_info_dup = NULL, *tmp1 = NULL, *tmp2 = NULL; + bool dup_previous = false, dup_next = false, success = false; + + /* Duplicate TitleInfo object. */ + title_info_dup = titleDuplicateTitleInfo(title_info); + if (!title_info_dup) + { + LOG_MSG_ERROR("Failed to duplicate TitleInfo object!"); + return NULL; + } + +#define TITLE_DUPLICATE_LINKED_LIST(elem, prv, nxt) \ + if (title_info->elem) { \ + if (elem) { \ + title_info_dup->elem = elem; \ + } else { \ + title_info_dup->elem = titleDuplicateTitleInfoFull(title_info->elem, prv, nxt); \ + if (!title_info_dup->elem) goto end; \ + dup_##elem = true; \ + } \ + } + +#define TITLE_FREE_DUPLICATED_LINKED_LIST(elem) \ + if (dup_##elem) { \ + tmp1 = title_info_dup->elem; \ + while(tmp1) { \ + tmp2 = tmp1->elem; \ + tmp1->previous = tmp1->next = NULL; \ + titleFreeTitleInfo(&tmp1); \ + tmp1 = tmp2; \ + } \ + } + + /* Duplicate linked lists based on two different principles: */ + /* 1) Linked list pointers will only be populated if their corresponding pointer is also populated in the TitleInfo element to duplicate. */ + /* 2) Pointers passed into this function take precedence before actual data duplication. */ + TITLE_DUPLICATE_LINKED_LIST(previous, NULL, title_info_dup); + TITLE_DUPLICATE_LINKED_LIST(next, title_info_dup, NULL); + + /* Update flag. */ + success = true; + +end: + /* We can't directly use titleFreeTitleInfo() on title_info_dup because some or all of the linked list data may have been provided as function arguments. */ + /* So we'll take care of freeing data the old fashioned way. */ + if (!success && title_info_dup) + { + /* Free content infos pointer. */ + if (title_info_dup->content_infos) free(title_info_dup->content_infos); + + /* Free previous and next linked lists (if duplicated). */ + /* We need to take care of not freeing the linked lists right away, either because we may have already freed them, or because they may have been passed as arguments. */ + /* Furthermore, both the next pointer from the previous sibling and the previous pointer from the next sibling reference our current duplicated entry. */ + /* To avoid issues, we'll just clear all linked list pointers. */ + TITLE_FREE_DUPLICATED_LINKED_LIST(previous); + TITLE_FREE_DUPLICATED_LINKED_LIST(next); + + /* Free allocated buffer and update return pointer. */ + free(title_info_dup); + title_info_dup = NULL; + } + +#undef TITLE_DUPLICATE_LINKED_LIST + +#undef TITLE_FREE_DUPLICATED_LINKED_LIST + + return title_info_dup; +} + +static TitleInfo *titleDuplicateTitleInfo(TitleInfo *title_info) +{ + if (!titleIsValidInfoBlock(title_info)) + { + LOG_MSG_ERROR("Invalid parameters!"); + return NULL; + } + + TitleInfo *title_info_dup = NULL; + NcmContentInfo *content_infos_dup = NULL; + bool success = false; + + /* Allocate memory for the new TitleInfo element. */ + title_info_dup = calloc(1, sizeof(TitleInfo)); + if (!title_info_dup) + { + LOG_MSG_ERROR("Failed to allocate memory for TitleInfo duplicate!"); + return NULL; + } + + /* Copy TitleInfo data. */ + memcpy(title_info_dup, title_info, sizeof(TitleInfo)); + title_info_dup->previous = title_info_dup->next = NULL; + + /* Allocate memory for NcmContentInfo elements. */ + content_infos_dup = calloc(title_info->content_count, sizeof(NcmContentInfo)); + if (!content_infos_dup) + { + LOG_MSG_ERROR("Failed to allocate memory for NcmContentInfo duplicates!"); + goto end; + } + + /* Copy NcmContentInfo data. */ + memcpy(content_infos_dup, title_info->content_infos, title_info->content_count * sizeof(NcmContentInfo)); + + /* Update content infos pointer. */ + title_info_dup->content_infos = content_infos_dup; + + /* Update flag. */ + success = true; + +end: + if (!success) + { + if (content_infos_dup) free(content_infos_dup); + + if (title_info_dup) + { + free(title_info_dup); + title_info_dup = NULL; + } + } + + return title_info_dup; +} + +static char *titleGetDisplayVersionString(TitleInfo *title_info) +{ + NcmContentInfo *nacp_content = NULL; + + if (!title_info || (title_info->meta_key.type != NcmContentMetaType_Application && title_info->meta_key.type != NcmContentMetaType_Patch) || \ + !(nacp_content = titleGetContentInfoByTypeAndIdOffset(title_info, NcmContentType_Control, 0))) + { + LOG_MSG_ERROR("Invalid parameters!"); + return false; + } + + u64 title_id = title_info->meta_key.id; + u8 storage_id = title_info->storage_id, hfs_partition_type = (storage_id == NcmStorageId_GameCard ? HashFileSystemPartitionType_Secure : 0); + NcaContext *nca_ctx = NULL; + NacpContext nacp_ctx = {0}; + char display_version[0x11] = {0}, *str = NULL; + + LOG_MSG_DEBUG("Retrieving display version string for %s \"%s\" (%016lX) in %s...", titleGetNcmContentMetaTypeName(title_info->meta_key.type), \ + title_info->app_metadata->lang_entry.name, title_id, \ + titleGetNcmStorageIdName(title_info->storage_id)); + + /* Allocate memory for the NCA context. */ + nca_ctx = calloc(1, sizeof(NcaContext)); + if (!nca_ctx) + { + LOG_MSG_ERROR("Failed to allocate memory for NCA context!"); + goto end; + } + + /* Initialize NCA context. */ + if (!ncaInitializeContext(nca_ctx, storage_id, hfs_partition_type, &(title_info->meta_key), nacp_content, NULL)) + { + LOG_MSG_ERROR("Failed to initialize NCA context for Control NCA from %016lX!", title_id); + goto end; + } + + /* Initialize NACP context. */ + if (!nacpInitializeContext(&nacp_ctx, nca_ctx)) + { + LOG_MSG_ERROR("Failed to initialize NACP context for %016lX!", title_id); + goto end; + } + + /* Get trimmed version string. */ + snprintf(display_version, sizeof(display_version), "%s", nacp_ctx.data->display_version); + utilsTrimString(display_version); + + /* Check version string length. */ + if (!*display_version) + { + LOG_MSG_ERROR("Display version string from %016lX is empty!", title_id); + goto end; + } + + /* Duplicate version string. */ + str = strdup(display_version); + if (!str) LOG_MSG_ERROR("Failed to duplicate version string from %016lX!", title_id); + +end: + nacpFreeContext(&nacp_ctx); + + if (nca_ctx) free(nca_ctx); + + return str; +} + static bool titleCreateGameCardInfoThread(void) { if (!utilsCreateThread(&g_titleGameCardInfoThread, titleGameCardInfoThreadFunc, NULL, 1)) @@ -2443,7 +3249,7 @@ static bool titleRefreshGameCardTitleInfo(void) TitleStorage *title_storage = NULL; TitleInfo **titles = NULL; u32 title_count = 0, gamecard_app_count = 0, extra_app_count = 0; - bool status = false, success = false, cleanup = true, free_entries = false; + bool status = false, success = false, cleanup = true, free_entries = false, hfs_init = false; /* Retrieve current gamecard status. */ status = (gamecardGetStatus() == GameCardStatus_InsertedAndInfoLoaded); @@ -2456,8 +3262,12 @@ static bool titleRefreshGameCardTitleInfo(void) /* Initialize gamecard title storage. */ if (!titleInitializeTitleStorage(NcmStorageId_GameCard)) { - LOG_MSG_ERROR("Failed to initialize gamecard title storage!"); - goto end; + /* Try to initialize the gamecard title storage manually. */ + if (!(hfs_init = titleInitializeGameCardTitleStorageByHashFileSystem(HashFileSystemPartitionType_Secure))) + { + LOG_MSG_ERROR("Failed to initialize gamecard title storage!"); + goto end; + } } /* Get gamecard title storage info. */ @@ -2479,8 +3289,8 @@ static bool titleRefreshGameCardTitleInfo(void) if (cur_title_info && cur_title_info->meta_key.type == NcmContentMetaType_Application) gamecard_app_count++; } - /* Return immediately if, for some reason, there are no user applications. */ - if (!gamecard_app_count) + /* Return immediately if there are no user applications or if we initialized the gamecard storage using a Hash FS. */ + if (!gamecard_app_count || hfs_init) { success = true; cleanup = false; @@ -2496,7 +3306,7 @@ static bool titleRefreshGameCardTitleInfo(void) free_entries = true; - /* Retrieve application metadata for gamecard user applications. */ + /* Retrieve application metadata via ns for any new gamecard user applications. */ for(u32 i = 0; i < title_count; i++) { TitleInfo *cur_title_info = titles[i]; @@ -2506,27 +3316,12 @@ static bool titleRefreshGameCardTitleInfo(void) u64 app_id = titleGetApplicationIdByContentMetaKey(&(cur_title_info->meta_key)); if (cur_title_info->app_metadata != NULL || (cur_title_info->app_metadata = titleFindApplicationMetadataByTitleId(app_id, false, extra_app_count)) != NULL) continue; - /* Retrieve application metadata pointer. */ - TitleApplicationMetadata *cur_app_metadata = g_userMetadata[g_userMetadataCount + extra_app_count]; - if (!cur_app_metadata) - { - /* Allocate memory for a new application metadata entry. */ - cur_app_metadata = calloc(1, sizeof(TitleApplicationMetadata)); - if (!cur_app_metadata) - { - LOG_MSG_ERROR("Failed to allocate memory for application metadata entry #%u!", extra_app_count); - goto end; - } - - /* Set application metadata entry pointer. */ - g_userMetadata[g_userMetadataCount + extra_app_count] = cur_app_metadata; - } - /* Retrieve application metadata. */ - if (!titleRetrieveUserApplicationMetadataByTitleId(app_id, cur_app_metadata)) continue; + TitleApplicationMetadata *cur_app_metadata = titleGenerateUserMetadataEntryFromNs(app_id); + if (!cur_app_metadata) continue; - /* Update application metadata pointer in title info. */ - cur_title_info->app_metadata = cur_app_metadata; + /* Set application metadata entry pointer. */ + g_userMetadata[g_userMetadataCount + extra_app_count] = cur_app_metadata; /* Increase extra application metadata counter. */ extra_app_count++; @@ -2538,17 +3333,11 @@ static bool titleRefreshGameCardTitleInfo(void) g_userMetadataCount += extra_app_count; /* Sort application metadata entries by name. */ - if (g_userMetadataCount > 1) qsort(g_userMetadata, g_userMetadataCount, sizeof(TitleApplicationMetadata*), &titleUserApplicationMetadataEntrySortFunction); + if (g_userMetadataCount > 1) qsort(g_userMetadata, g_userMetadataCount, sizeof(TitleApplicationMetadata*), &titleUserMetadataSortFunction); /* Update linked lists for user applications, patches and add-on contents. */ /* This will take care of orphan titles we might now have application metadata for. */ titleUpdateTitleInfoLinkedLists(); - } else - if (g_userMetadata[g_userMetadataCount]) - { - /* Free leftover application metadata entry (if needed). */ - free(g_userMetadata[g_userMetadataCount]); - g_userMetadata[g_userMetadataCount] = NULL; } /* Free extra allocated pointers if we didn't use them. */ @@ -2577,15 +3366,106 @@ static bool titleRefreshGameCardTitleInfo(void) return success; } -NX_INLINE void titleFreeGameCardFileNames(void) +static void titleGenerateGameCardApplicationMetadataArray(void) { - for(u8 i = 0; i < TitleNamingConvention_Count; i++) + TitleStorage *title_storage = &(g_titleStorage[TITLE_STORAGE_INDEX(NcmStorageId_GameCard)]); + TitleInfo **titles = title_storage->titles; + u32 title_count = title_storage->title_count; + TitleGameCardApplicationMetadata *tmp_gc_app_metadata = NULL; + + /* Free gamecard application metadata array. */ + if (g_titleGameCardApplicationMetadata) { - if (g_titleGameCardFileNames[i]) + free(g_titleGameCardApplicationMetadata); + g_titleGameCardApplicationMetadata = NULL; + } + + g_titleGameCardApplicationMetadataCount = 0; + + /* Make sure we actually have gamecard TitleInfo entries we can work with. */ + if (!titles || !title_count) + { + LOG_MSG_ERROR("No gamecard TitleInfo entries available!"); + return; + } + + /* Loop through our gamecard TitleInfo entries. */ + LOG_MSG_DEBUG("Retrieving gamecard application metadata (%u title[s])...", title_count); + + for(u32 i = 0; i < title_count; i++) + { + /* Skip current entry if it's not a user application. */ + TitleInfo *app_info = titles[i], *patch_info = NULL; + if (!app_info || app_info->meta_key.type != NcmContentMetaType_Application) continue; + + u32 app_version = app_info->meta_key.version; + u32 dlc_count = 0; + + /* 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 = 0; j < title_count; j++) { - free(g_titleGameCardFileNames[i]); - g_titleGameCardFileNames[i] = NULL; + if (j == i) continue; + + TitleInfo *cur_title_info = titles[j]; + if (!cur_title_info || cur_title_info->meta_key.type != NcmContentMetaType_Patch || \ + !titleCheckIfPatchIdBelongsToApplicationId(app_info->meta_key.id, cur_title_info->meta_key.id) || cur_title_info->meta_key.version < app_version) continue; + + patch_info = cur_title_info; + app_version = cur_title_info->meta_key.version; + } + + /* Count DLCs available for this application in the inserted gamecard. */ + for(u32 j = 0; j < title_count; j++) + { + if (j == i) continue; + + TitleInfo *cur_title_info = titles[j]; + if (!cur_title_info || cur_title_info->meta_key.type != NcmContentMetaType_AddOnContent || \ + !titleCheckIfAddOnContentIdBelongsToApplicationId(app_info->meta_key.id, cur_title_info->meta_key.id)) continue; + + dlc_count++; } + + /* Reallocate application metadata pointer array. */ + tmp_gc_app_metadata = realloc(g_titleGameCardApplicationMetadata, (g_titleGameCardApplicationMetadataCount + 1) * sizeof(TitleGameCardApplicationMetadata)); + if (!tmp_gc_app_metadata) + { + LOG_MSG_ERROR("Failed to reallocate gamecard application metadata array!"); + + if (g_titleGameCardApplicationMetadata) free(g_titleGameCardApplicationMetadata); + g_titleGameCardApplicationMetadata = NULL; + g_titleGameCardApplicationMetadataCount = 0; + + return; + } + + g_titleGameCardApplicationMetadata = tmp_gc_app_metadata; + + /* Fill current entry and increase counter. */ + tmp_gc_app_metadata = &(g_titleGameCardApplicationMetadata[g_titleGameCardApplicationMetadataCount++]); + memset(tmp_gc_app_metadata, 0, sizeof(TitleGameCardApplicationMetadata)); + tmp_gc_app_metadata->app_metadata = app_info->app_metadata; + tmp_gc_app_metadata->has_patch = (patch_info != NULL); + tmp_gc_app_metadata->version.value = app_version; + tmp_gc_app_metadata->dlc_count = dlc_count; + + /* Try to retrieve the display version. */ + char *version_str = titleGetDisplayVersionString(patch_info ? patch_info : app_info); + if (version_str) + { + snprintf(tmp_gc_app_metadata->display_version, MAX_ELEMENTS(tmp_gc_app_metadata->display_version), "%s", version_str); + free(version_str); + } + } + + if (g_titleGameCardApplicationMetadata && g_titleGameCardApplicationMetadataCount) + { + /* Sort title metadata entries by name. */ + if (g_titleGameCardApplicationMetadataCount > 1) qsort(g_titleGameCardApplicationMetadata, g_titleGameCardApplicationMetadataCount, sizeof(TitleGameCardApplicationMetadata), + &titleGameCardApplicationMetadataSortFunction); + } else { + LOG_MSG_ERROR("No gamecard content data found for user applications!"); } } @@ -2595,7 +3475,19 @@ NX_INLINE void titleGenerateGameCardFileNames(void) if (g_titleGameCardAvailable) { - for(u8 i = 0; i < TitleNamingConvention_Count; i++) g_titleGameCardFileNames[i] = _titleGenerateGameCardFileName(i); + for(u8 i = 0; i < TitleNamingConvention_Count; i++) g_titleGameCardFileNames[i] = _titleGenerateGameCardFileName(i); + } +} + +NX_INLINE void titleFreeGameCardFileNames(void) +{ + for(u8 i = 0; i < TitleNamingConvention_Count; i++) + { + if (g_titleGameCardFileNames[i]) + { + free(g_titleGameCardFileNames[i]); + g_titleGameCardFileNames[i] = NULL; + } } } @@ -2698,202 +3590,7 @@ static char *_titleGenerateGameCardFileName(u8 naming_convention) return filename; } -static bool titleIsUserApplicationContentAvailable(u64 app_id) -{ - if (!app_id) return false; - - for(u8 i = NcmStorageId_GameCard; i <= NcmStorageId_SdCard; i++) - { - if (i == NcmStorageId_BuiltInSystem) continue; - - TitleStorage *title_storage = &(g_titleStorage[TITLE_STORAGE_INDEX(i)]); - if (!title_storage->titles || !*(title_storage->titles) || !title_storage->title_count) continue; - - for(u32 j = 0; j < title_storage->title_count; j++) - { - TitleInfo *title_info = title_storage->titles[j]; - if (!title_info) continue; - - if ((title_info->meta_key.type == NcmContentMetaType_Application && title_info->meta_key.id == app_id) || \ - (title_info->meta_key.type == NcmContentMetaType_Patch && titleCheckIfPatchIdBelongsToApplicationId(app_id, title_info->meta_key.id)) || \ - (title_info->meta_key.type == NcmContentMetaType_AddOnContent && titleCheckIfAddOnContentIdBelongsToApplicationId(app_id, title_info->meta_key.id)) || \ - (title_info->meta_key.type == NcmContentMetaType_DataPatch && titleCheckIfDataPatchIdBelongsToApplicationId(app_id, title_info->meta_key.id))) return true; - } - } - - return false; -} - -static TitleInfo *_titleGetInfoFromStorageByTitleId(u8 storage_id, u64 title_id) -{ - if (storage_id < NcmStorageId_GameCard || storage_id > NcmStorageId_Any || !title_id) - { - LOG_MSG_ERROR("Invalid parameters!"); - return NULL; - } - - TitleInfo *out = NULL; - - u8 start_idx = (storage_id == NcmStorageId_Any ? TITLE_STORAGE_INDEX(NcmStorageId_GameCard) : TITLE_STORAGE_INDEX(storage_id)); - u8 max_val = (storage_id == NcmStorageId_Any ? TITLE_STORAGE_INDEX(NcmStorageId_SdCard) : start_idx); - - for(u8 i = start_idx; i <= max_val; i++) - { - TitleStorage *title_storage = &(g_titleStorage[i]); - if (!title_storage->titles || !*(title_storage->titles) || !title_storage->title_count) continue; - - for(u32 j = 0; j < title_storage->title_count; j++) - { - TitleInfo *title_info = title_storage->titles[j]; - if (title_info && title_info->meta_key.id == title_id) - { - out = title_info; - break; - } - } - - if (out) break; - } - - if (!out && storage_id != NcmStorageId_BuiltInSystem) LOG_MSG_DEBUG("Unable to find title info entry with ID \"%016lX\" in %s.", title_id, titleGetNcmStorageIdName(storage_id)); - - return out; -} - -static TitleInfo *titleDuplicateTitleInfoFull(TitleInfo *title_info, TitleInfo *previous, TitleInfo *next) -{ - if (!titleIsValidInfoBlock(title_info)) - { - LOG_MSG_ERROR("Invalid parameters!"); - return NULL; - } - - TitleInfo *title_info_dup = NULL, *tmp1 = NULL, *tmp2 = NULL; - bool dup_previous = false, dup_next = false, success = false; - - /* Duplicate TitleInfo object. */ - title_info_dup = titleDuplicateTitleInfo(title_info); - if (!title_info_dup) - { - LOG_MSG_ERROR("Failed to duplicate TitleInfo object!"); - return NULL; - } - -#define TITLE_DUPLICATE_LINKED_LIST(elem, prv, nxt) \ - if (title_info->elem) { \ - if (elem) { \ - title_info_dup->elem = elem; \ - } else { \ - title_info_dup->elem = titleDuplicateTitleInfoFull(title_info->elem, prv, nxt); \ - if (!title_info_dup->elem) goto end; \ - dup_##elem = true; \ - } \ - } - -#define TITLE_FREE_DUPLICATED_LINKED_LIST(elem) \ - if (dup_##elem) { \ - tmp1 = title_info_dup->elem; \ - while(tmp1) { \ - tmp2 = tmp1->elem; \ - tmp1->previous = tmp1->next = NULL; \ - titleFreeTitleInfo(&tmp1); \ - tmp1 = tmp2; \ - } \ - } - - /* Duplicate linked lists based on two different principles: */ - /* 1) Linked list pointers will only be populated if their corresponding pointer is also populated in the TitleInfo element to duplicate. */ - /* 2) Pointers passed into this function take precedence before actual data duplication. */ - TITLE_DUPLICATE_LINKED_LIST(previous, NULL, title_info_dup); - TITLE_DUPLICATE_LINKED_LIST(next, title_info_dup, NULL); - - /* Update flag. */ - success = true; - -end: - /* We can't directly use titleFreeTitleInfo() on title_info_dup because some or all of the linked list data may have been provided as function arguments. */ - /* So we'll take care of freeing data the old fashioned way. */ - if (!success && title_info_dup) - { - /* Free content infos pointer. */ - if (title_info_dup->content_infos) free(title_info_dup->content_infos); - - /* Free previous and next linked lists (if duplicated). */ - /* We need to take care of not freeing the linked lists right away, either because we may have already freed them, or because they may have been passed as arguments. */ - /* Furthermore, both the next pointer from the previous sibling and the previous pointer from the next sibling reference our current duplicated entry. */ - /* To avoid issues, we'll just clear all linked list pointers. */ - TITLE_FREE_DUPLICATED_LINKED_LIST(previous); - TITLE_FREE_DUPLICATED_LINKED_LIST(next); - - /* Free allocated buffer and update return pointer. */ - free(title_info_dup); - title_info_dup = NULL; - } - -#undef TITLE_DUPLICATE_LINKED_LIST - -#undef TITLE_FREE_DUPLICATED_LINKED_LIST - - return title_info_dup; -} - -static TitleInfo *titleDuplicateTitleInfo(TitleInfo *title_info) -{ - if (!titleIsValidInfoBlock(title_info)) - { - LOG_MSG_ERROR("Invalid parameters!"); - return NULL; - } - - TitleInfo *title_info_dup = NULL; - NcmContentInfo *content_infos_dup = NULL; - bool success = false; - - /* Allocate memory for the new TitleInfo element. */ - title_info_dup = calloc(1, sizeof(TitleInfo)); - if (!title_info_dup) - { - LOG_MSG_ERROR("Failed to allocate memory for TitleInfo duplicate!"); - return NULL; - } - - /* Copy TitleInfo data. */ - memcpy(title_info_dup, title_info, sizeof(TitleInfo)); - title_info_dup->previous = title_info_dup->next = NULL; - - /* Allocate memory for NcmContentInfo elements. */ - content_infos_dup = calloc(title_info->content_count, sizeof(NcmContentInfo)); - if (!content_infos_dup) - { - LOG_MSG_ERROR("Failed to allocate memory for NcmContentInfo duplicates!"); - goto end; - } - - /* Copy NcmContentInfo data. */ - memcpy(content_infos_dup, title_info->content_infos, title_info->content_count * sizeof(NcmContentInfo)); - - /* Update content infos pointer. */ - title_info_dup->content_infos = content_infos_dup; - - /* Update flag. */ - success = true; - -end: - if (!success) - { - if (content_infos_dup) free(content_infos_dup); - - if (title_info_dup) - { - free(title_info_dup); - title_info_dup = NULL; - } - } - - return title_info_dup; -} - -static int titleSystemTitleMetadataEntrySortFunction(const void *a, const void *b) +static int titleSystemMetadataSortFunction(const void *a, const void *b) { const TitleApplicationMetadata *app_metadata_1 = *((const TitleApplicationMetadata**)a); const TitleApplicationMetadata *app_metadata_2 = *((const TitleApplicationMetadata**)b); @@ -2910,7 +3607,7 @@ static int titleSystemTitleMetadataEntrySortFunction(const void *a, const void * return 0; } -static int titleUserApplicationMetadataEntrySortFunction(const void *a, const void *b) +static int titleUserMetadataSortFunction(const void *a, const void *b) { const TitleApplicationMetadata *app_metadata_1 = *((const TitleApplicationMetadata**)a); const TitleApplicationMetadata *app_metadata_2 = *((const TitleApplicationMetadata**)b); @@ -2918,7 +3615,7 @@ static int titleUserApplicationMetadataEntrySortFunction(const void *a, const vo return strcasecmp(app_metadata_1->lang_entry.name, app_metadata_2->lang_entry.name); } -static int titleInfoEntrySortFunction(const void *a, const void *b) +static int titleInfoSortFunction(const void *a, const void *b) { const TitleInfo *title_info_1 = *((const TitleInfo**)a); const TitleInfo *title_info_2 = *((const TitleInfo**)b); @@ -2961,70 +3658,37 @@ static int titleGameCardApplicationMetadataSortFunction(const void *a, const voi return strcasecmp(gc_app_metadata_1->app_metadata->lang_entry.name, gc_app_metadata_2->app_metadata->lang_entry.name); } -static char *titleGetDisplayVersionString(TitleInfo *title_info) +static int titleGameCardContentMetaContextSortFunction(const void *a, const void *b) { - NcmContentInfo *nacp_content = NULL; - u8 storage_id = NcmStorageId_None, hfs_partition_type = HashFileSystemPartitionType_None; - NcaContext *nca_ctx = NULL; - NacpContext nacp_ctx = {0}; - char display_version[0x11] = {0}, *str = NULL; + const TitleGameCardContentMetaContext *gc_meta_ctx_1 = (const TitleGameCardContentMetaContext*)a; + const TitleGameCardContentMetaContext *gc_meta_ctx_2 = (const TitleGameCardContentMetaContext*)b; - if (!title_info || (title_info->meta_key.type != NcmContentMetaType_Application && title_info->meta_key.type != NcmContentMetaType_Patch) || \ - !(nacp_content = titleGetContentInfoByTypeAndIdOffset(title_info, NcmContentType_Control, 0))) + if (gc_meta_ctx_1->meta_key.type < gc_meta_ctx_2->meta_key.type) { - LOG_MSG_ERROR("Invalid parameters!"); - goto end; - } - - LOG_MSG_DEBUG("Retrieving display version string for %s \"%s\" (%016lX) in %s...", titleGetNcmContentMetaTypeName(title_info->meta_key.type), \ - title_info->app_metadata->lang_entry.name, title_info->meta_key.id, \ - titleGetNcmStorageIdName(title_info->storage_id)); - - /* Update parameters. */ - storage_id = title_info->storage_id; - if (storage_id == NcmStorageId_GameCard) hfs_partition_type = HashFileSystemPartitionType_Secure; - - /* Allocate memory for the NCA context. */ - nca_ctx = calloc(1, sizeof(NcaContext)); - if (!nca_ctx) + return 1; + } else + if (gc_meta_ctx_1->meta_key.type > gc_meta_ctx_2->meta_key.type) { - LOG_MSG_ERROR("Failed to allocate memory for NCA context!"); - goto end; + return -1; } - /* Initialize NCA context. */ - if (!ncaInitializeContext(nca_ctx, storage_id, hfs_partition_type, &(title_info->meta_key), nacp_content, NULL)) + if (gc_meta_ctx_1->meta_key.id < gc_meta_ctx_2->meta_key.id) { - LOG_MSG_ERROR("Failed to initialize NCA context for Control NCA from %016lX!", title_info->meta_key.id); - goto end; - } - - /* Initialize NACP context. */ - if (!nacpInitializeContext(&nacp_ctx, nca_ctx)) + return 1; + } else + if (gc_meta_ctx_1->meta_key.id > gc_meta_ctx_2->meta_key.id) { - LOG_MSG_ERROR("Failed to initialize NACP context for %016lX!", title_info->meta_key.id); - goto end; + return -1; } - /* Get trimmed version string. */ - snprintf(display_version, sizeof(display_version), "%s", nacp_ctx.data->display_version); - utilsTrimString(display_version); - - /* Check version string length. */ - if (!*display_version) + if (gc_meta_ctx_1->meta_key.version < gc_meta_ctx_2->meta_key.version) { - LOG_MSG_ERROR("Display version string from %016lX is empty!", title_info->meta_key.id); - goto end; + return 1; + } else + if (gc_meta_ctx_1->meta_key.version > gc_meta_ctx_2->meta_key.version) + { + return -1; } - /* Duplicate version string. */ - str = strdup(display_version); - if (!str) LOG_MSG_ERROR("Failed to duplicate version string from %016lX!", title_info->meta_key.id); - -end: - nacpFreeContext(&nacp_ctx); - - if (nca_ctx) free(nca_ctx); - - return str; + return 0; } diff --git a/source/views/titles_tab.cpp b/source/views/titles_tab.cpp index 8325f06e..66b63265 100644 --- a/source/views/titles_tab.cpp +++ b/source/views/titles_tab.cpp @@ -36,7 +36,7 @@ namespace nxdt::views user_ret = titleGetUserApplicationData(title_id, &(this->user_app_data)); } else { /* Get system title info. */ - this->system_title_info = titleGetInfoFromStorageByTitleId(NcmStorageId_BuiltInSystem, title_id); + this->system_title_info = titleGetTitleInfoEntryFromStorageByTitleId(NcmStorageId_BuiltInSystem, title_id); } /* Make sure we got title information. This should never get triggered. */