From 5cc387c9b654c4f940aa1621727b3bd1fc5368a1 Mon Sep 17 00:00:00 2001 From: Pablo Curiel Date: Sun, 5 May 2024 21:42:32 +0200 Subject: [PATCH] title: migrate application metadata filtering logic to background thread titleGetApplicationMetadataEntries() and titleGetGameCardApplicationMetadataEntries() will now return dynamically allocated copies of internal pre-filtered / pre-processed arrays, which are generated using the background gamecard thread. This results in less overhead for any potential calls to these functions. Other changes include: * title: rename TitleGameCardApplicationMetadataEntry -> TitleGameCardApplicationMetadata. * title: add `has_patch` field to TitleGameCardApplicationMetadata struct. * title: declare internal TitleApplicationMetadata arrays to hold pre-filtered application metadata. * title: declare internal TitleGameCardApplicationMetadata array to hold pre-processed gamecard application metadata. * title: move filtering logic from titleGetApplicationMetadataEntries() to a new function: titleGenerateFilteredApplicationMetadataPointerArray(). * title: move processing logic from titleGetGameCardApplicationMetadataEntries() to a new function: titleGenerateGameCardApplicationMetadataArray(). * title: rename titleGetPatchVersionString() -> titleGetDisplayVersionString(). * title: add extra debug log messages to some functions. * title: update titleFreeApplicationMetadata() to also free the new internal metadata arrays. * title: update background thread logic in titleGameCardInfoThreadFunc() to also regenerate the pre-filtered application metadata and gamecard application metadata arrays right after a successful call to titleRefreshGameCardTitleInfo(). * title: update titleGetDisplayVersionString() to also support base application titles. * title: simplify string generation logic in titleGenerateGameCardFileName() by using the cached gamecard application metadata array. * GameCardStatusTask: add GetGameCardStatus() method. * GameCardTab: fix callback argument type in class constructor. * GameCardTab: update ProcessGameCardStatus() to block user inputs while processing the new gamecard status. * RootView: add GetGameCardStatus() method. * StatusInfoTask: turn IsInternetConnectionAvailable() into an inline method. * TitleMetadataTask: turn GetApplicationMetadata() into an inline method. * TitleMetadataTask: move debug log messages around. * TitlesTab: update PopulateList() to block user inputs while updating the titles list. * UmsTask: turn GetUmsDevices() into an inline method. * UsbHostTask: turn GetUsbHostSpeed() into an inline method. --- include/core/title.h | 11 +- include/tasks/gamecard_status_task.hpp | 6 + include/tasks/status_info_task.hpp | 5 +- include/tasks/title_metadata_task.hpp | 5 +- include/tasks/ums_task.hpp | 5 +- include/tasks/usb_host_task.hpp | 5 +- include/views/gamecard_tab.hpp | 6 +- include/views/root_view.hpp | 5 + libs/borealis | 2 +- source/core/title.c | 438 ++++++++++++++++--------- source/tasks/gamecard_status_task.cpp | 5 +- source/tasks/status_info_task.cpp | 5 - source/tasks/title_metadata_task.cpp | 14 +- source/tasks/ums_task.cpp | 5 - source/tasks/usb_host_task.cpp | 5 - source/views/gamecard_tab.cpp | 16 +- source/views/titles_tab.cpp | 28 +- 17 files changed, 351 insertions(+), 215 deletions(-) diff --git a/include/core/title.h b/include/core/title.h index 4faccc2..3abdcf1 100644 --- a/include/core/title.h +++ b/include/core/title.h @@ -49,10 +49,11 @@ typedef struct { /// Used to display gamecard-specific title information. typedef struct { TitleApplicationMetadata *app_metadata; ///< User application metadata. - Version version; ///< Reflects the title version stored in the inserted gamecard. - char display_version[32]; ///< Reflects the title display version stored in its NACP. + bool has_patch; ///< Set to true if a patch is also available in the inserted gamecard for this user application. + Version version; ///< Reflects the title version stored in the inserted gamecard, either from a base application or a patch. + char display_version[32]; ///< Reflects the title display version from the NACP belonging to either a base application or a patch. u32 dlc_count; ///< Reflects the number of DLCs available for this application in the inserted gamecard. -} TitleGameCardApplicationMetadataEntry; +} TitleGameCardApplicationMetadata; /// Generated using ncm calls. /// User applications: the previous/next pointers reference other user applications with the same ID. @@ -113,10 +114,10 @@ NcmContentStorage *titleGetNcmStorageByStorageId(u8 storage_id); /// The allocated buffer must be freed by the caller using free(). TitleApplicationMetadata **titleGetApplicationMetadataEntries(bool is_system, u32 *out_count); -/// Returns a pointer to a dynamically allocated array of TitleGameCardApplicationMetadataEntry elements generated from gamecard user titles, as well as their count. +/// Returns a pointer to a dynamically allocated array of TitleGameCardApplicationMetadata elements generated from gamecard user titles, as well as their count. /// Returns NULL if an error occurs. /// The allocated buffer must be freed by the caller using free(). -TitleGameCardApplicationMetadataEntry *titleGetGameCardApplicationMetadataEntries(u32 *out_count); +TitleGameCardApplicationMetadata *titleGetGameCardApplicationMetadataEntries(u32 *out_count); /// 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. diff --git a/include/tasks/gamecard_status_task.hpp b/include/tasks/gamecard_status_task.hpp index c7ee3c5..fef198a 100644 --- a/include/tasks/gamecard_status_task.hpp +++ b/include/tasks/gamecard_status_task.hpp @@ -51,6 +51,12 @@ namespace nxdt::tasks GameCardStatusTask(); ~GameCardStatusTask(); + /* Intentionally left here to let views retrieve title metadata on-demand. */ + ALWAYS_INLINE const GameCardStatus& GetGameCardStatus(void) + { + return this->cur_gc_status; + } + ALWAYS_INLINE GameCardStatusEvent::Subscription RegisterListener(GameCardStatusEvent::Callback cb) { return this->gc_status_event.subscribe(cb); diff --git a/include/tasks/status_info_task.hpp b/include/tasks/status_info_task.hpp index 0c9ec8e..10ca64f 100644 --- a/include/tasks/status_info_task.hpp +++ b/include/tasks/status_info_task.hpp @@ -58,7 +58,10 @@ namespace nxdt::tasks StatusInfoTask(); ~StatusInfoTask(); - bool IsInternetConnectionAvailable(void); + ALWAYS_INLINE bool IsInternetConnectionAvailable(void) + { + return this->status_info_data.connected; + } ALWAYS_INLINE StatusInfoEvent::Subscription RegisterListener(StatusInfoEvent::Callback cb) { diff --git a/include/tasks/title_metadata_task.hpp b/include/tasks/title_metadata_task.hpp index 2c15cd7..14f4cc2 100644 --- a/include/tasks/title_metadata_task.hpp +++ b/include/tasks/title_metadata_task.hpp @@ -57,7 +57,10 @@ namespace nxdt::tasks ~TitleMetadataTask(); /* Intentionally left here to let views retrieve title metadata on-demand. */ - const TitleApplicationMetadataVector& GetApplicationMetadata(bool is_system); + ALWAYS_INLINE const TitleApplicationMetadataVector& GetApplicationMetadata(bool is_system) + { + return (is_system ? this->system_metadata : this->user_metadata); + } ALWAYS_INLINE UserTitleEvent::Subscription RegisterListener(UserTitleEvent::Callback cb) { diff --git a/include/tasks/ums_task.hpp b/include/tasks/ums_task.hpp index d23db5a..408f4e7 100644 --- a/include/tasks/ums_task.hpp +++ b/include/tasks/ums_task.hpp @@ -60,7 +60,10 @@ namespace nxdt::tasks ~UmsTask(); /* Intentionally left here to let views retrieve UMS device info on-demand. */ - const UmsDeviceVector& GetUmsDevices(void); + ALWAYS_INLINE const UmsDeviceVector& GetUmsDevices(void) + { + return this->ums_devices_vector; + } ALWAYS_INLINE UmsEvent::Subscription RegisterListener(UmsEvent::Callback cb) { diff --git a/include/tasks/usb_host_task.hpp b/include/tasks/usb_host_task.hpp index b849ad8..e46c0fc 100644 --- a/include/tasks/usb_host_task.hpp +++ b/include/tasks/usb_host_task.hpp @@ -50,7 +50,10 @@ namespace nxdt::tasks ~UsbHostTask(); /* Intentionally left here to let views retrieve USB host connection speed on-demand. */ - const UsbHostSpeed& GetUsbHostSpeed(void); + ALWAYS_INLINE const UsbHostSpeed& GetUsbHostSpeed(void) + { + return this->cur_usb_host_speed; + } ALWAYS_INLINE UsbHostEvent::Subscription RegisterListener(UsbHostEvent::Callback cb) { diff --git a/include/views/gamecard_tab.hpp b/include/views/gamecard_tab.hpp index cc7212f..52e0ce4 100644 --- a/include/views/gamecard_tab.hpp +++ b/include/views/gamecard_tab.hpp @@ -43,9 +43,7 @@ namespace nxdt::views std::string raw_filename_full = ""; std::string raw_filename_id_only = ""; - void ProcessGameCardStatus(GameCardStatus gc_status); - - + void ProcessGameCardStatus(const GameCardStatus& gc_status); void PopulateList(void); void AddApplicationMetadataItems(void); @@ -55,8 +53,6 @@ namespace nxdt::views std::string GetFormattedSizeString(GameCardSizeFunc func); std::string GetCardIdSetString(const FsGameCardIdSet& card_id_set); - - public: GameCardTab(RootView *root_view); ~GameCardTab(); diff --git a/include/views/root_view.hpp b/include/views/root_view.hpp index 6642625..03c1e0a 100644 --- a/include/views/root_view.hpp +++ b/include/views/root_view.hpp @@ -90,6 +90,11 @@ namespace nxdt::views return this->status_info_task->IsInternetConnectionAvailable(); } + ALWAYS_INLINE const GameCardStatus& GetGameCardStatus(void) + { + return this->gc_status_task->GetGameCardStatus(); + } + ALWAYS_INLINE const nxdt::tasks::TitleApplicationMetadataVector& GetApplicationMetadata(bool is_system) { return this->title_metadata_task->GetApplicationMetadata(is_system); diff --git a/libs/borealis b/libs/borealis index 0846ff5..1a69955 160000 --- a/libs/borealis +++ b/libs/borealis @@ -1 +1 @@ -Subproject commit 0846ff57b72a1bdd9fc86eee348258c0b52e0ece +Subproject commit 1a69955c64f72a48342abce317a75998ffac3f9d diff --git a/source/core/title.c b/source/core/title.c index 11c4744..ce60017 100644 --- a/source/core/title.c +++ b/source/core/title.c @@ -59,6 +59,12 @@ static NsApplicationControlData *g_nsAppControlData = NULL; static TitleApplicationMetadata **g_systemMetadata = NULL, **g_userMetadata = NULL; static u32 g_systemMetadataCount = 0, g_userMetadataCount = 0; +static TitleApplicationMetadata **g_filteredSystemMetadata = NULL, **g_filteredUserMetadata = NULL; +static u32 g_filteredSystemMetadataCount = 0, g_filteredUserMetadataCount = 0; + +static TitleGameCardApplicationMetadata *g_titleGameCardApplicationMetadata = NULL; +static u32 g_titleGameCardApplicationMetadataCount = 0; + static TitleStorage g_titleStorage[TITLE_STORAGE_COUNT] = {0}; static TitleInfo **g_orphanTitleInfo = NULL; @@ -552,6 +558,9 @@ static bool titleGenerateMetadataEntriesFromNsRecords(void); static TitleApplicationMetadata *titleGenerateDummySystemMetadataEntry(u64 title_id); static bool titleRetrieveUserApplicationMetadataByTitleId(u64 title_id, TitleApplicationMetadata *out); +static void titleGenerateFilteredApplicationMetadataPointerArray(bool is_system); +static void titleGenerateGameCardApplicationMetadataArray(void); + NX_INLINE TitleApplicationMetadata *titleFindApplicationMetadataByTitleId(u64 title_id, bool is_system, u32 extra_app_count); NX_INLINE u64 titleGetApplicationIdByContentMetaKey(const NcmContentMetaKey *meta_key); @@ -579,7 +588,7 @@ static int titleUserApplicationMetadataEntrySortFunction(const void *a, const vo static int titleInfoEntrySortFunction(const void *a, const void *b); static int titleGameCardApplicationMetadataSortFunction(const void *a, const void *b); -static char *titleGetPatchVersionString(TitleInfo *title_info); +static char *titleGetDisplayVersionString(TitleInfo *title_info); bool titleInitialize(void) { @@ -623,6 +632,12 @@ bool titleInitialize(void) break; } + /* Generate filtered system application metadata pointer array. */ + titleGenerateFilteredApplicationMetadataPointerArray(true); + + /* Generate filtered user application metadata pointer array. */ + titleGenerateFilteredApplicationMetadataPointerArray(false); + /* Create user-mode exit event. */ ueventCreate(&g_titleGameCardInfoThreadExitEvent, true); @@ -689,158 +704,65 @@ NcmContentStorage *titleGetNcmStorageByStorageId(u8 storage_id) TitleApplicationMetadata **titleGetApplicationMetadataEntries(bool is_system, u32 *out_count) { - u32 app_count = 0; - TitleApplicationMetadata **app_metadata = NULL, **tmp_app_metadata = NULL; + TitleApplicationMetadata **dup_filtered_app_metadata = NULL; SCOPED_LOCK(&g_titleMutex) { - if (!g_titleInterfaceInit || (is_system && (!g_systemMetadata || !g_systemMetadataCount)) || (!is_system && (!g_userMetadata || !g_userMetadataCount)) || !out_count) + TitleApplicationMetadata **filtered_app_metadata = (is_system ? g_filteredSystemMetadata : g_filteredUserMetadata); + u32 filtered_app_metadata_count = (is_system ? g_filteredSystemMetadataCount : g_filteredUserMetadataCount); + + if (!g_titleInterfaceInit || !filtered_app_metadata || !filtered_app_metadata_count || !out_count) { LOG_MSG_ERROR("Invalid parameters!"); break; } - TitleApplicationMetadata **cached_app_metadata = (is_system ? g_systemMetadata : g_userMetadata); - u32 cached_app_metadata_count = (is_system ? g_systemMetadataCount : g_userMetadataCount); - bool error = false; - - for(u32 i = 0; i < cached_app_metadata_count; i++) + /* Allocate memory for the pointer array. */ + dup_filtered_app_metadata = malloc(filtered_app_metadata_count * sizeof(TitleApplicationMetadata*)); + if (!dup_filtered_app_metadata) { - TitleApplicationMetadata *cur_app_metadata = cached_app_metadata[i]; - 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)) || \ - (!is_system && !titleIsUserApplicationContentAvailable(cur_app_metadata->title_id))) continue; - - /* Reallocate application metadata pointer array. */ - tmp_app_metadata = realloc(app_metadata, (app_count + 1) * sizeof(TitleApplicationMetadata*)); - if (!tmp_app_metadata) - { - LOG_MSG_ERROR("Failed to reallocate application metadata pointer array!"); - if (app_metadata) free(app_metadata); - app_metadata = NULL; - error = true; - break; - } - - app_metadata = tmp_app_metadata; - tmp_app_metadata = NULL; - - /* Set current pointer and increase counter. */ - app_metadata[app_count++] = cur_app_metadata; + LOG_MSG_ERROR("Failed to allocate memory for pointer array duplicate!"); + break; } - if (error) break; + /* Copy application metadata pointers. */ + memcpy(dup_filtered_app_metadata, filtered_app_metadata, filtered_app_metadata_count * sizeof(TitleApplicationMetadata*)); /* Update output counter. */ - *out_count = app_count; - - if (!app_metadata || !app_count) LOG_MSG_ERROR("No content data found for %s!", is_system ? "system titles" : "user applications"); + *out_count = filtered_app_metadata_count; } - return app_metadata; + return dup_filtered_app_metadata; } -TitleGameCardApplicationMetadataEntry *titleGetGameCardApplicationMetadataEntries(u32 *out_count) +TitleGameCardApplicationMetadata *titleGetGameCardApplicationMetadataEntries(u32 *out_count) { - u32 app_count = 0; - TitleGameCardApplicationMetadataEntry *gc_app_metadata = NULL, *tmp_gc_app_metadata = NULL; + TitleGameCardApplicationMetadata *dup_gc_app_metadata = NULL; SCOPED_LOCK(&g_titleMutex) { - TitleStorage *title_storage = &(g_titleStorage[TITLE_STORAGE_INDEX(NcmStorageId_GameCard)]); - TitleInfo **titles = title_storage->titles; - u32 title_count = title_storage->title_count; - bool error = false; - - if (!g_titleInterfaceInit || !g_titleGameCardAvailable || !out_count || !titles || !title_count) + if (!g_titleInterfaceInit || !g_titleGameCardAvailable || !g_titleGameCardApplicationMetadata || !g_titleGameCardApplicationMetadataCount || !out_count) { LOG_MSG_ERROR("Invalid parameters!"); break; } - /* Loop through our gamecard TitleInfo entries. */ - for(u32 i = 0; i < title_count; i++) + /* Allocate memory for the output array. */ + dup_gc_app_metadata = malloc(g_titleGameCardApplicationMetadataCount * sizeof(TitleGameCardApplicationMetadata)); + if (!dup_gc_app_metadata) { - /* 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(gc_app_metadata, (app_count + 1) * sizeof(TitleGameCardApplicationMetadataEntry)); - if (!tmp_gc_app_metadata) - { - LOG_MSG_ERROR("Failed to reallocate application metadata pointer array!"); - if (gc_app_metadata) free(gc_app_metadata); - gc_app_metadata = NULL; - error = true; - break; - } - - gc_app_metadata = tmp_gc_app_metadata; - tmp_gc_app_metadata = NULL; - - /* Fill current entry and increase counter. */ - tmp_gc_app_metadata = &(gc_app_metadata[app_count++]); - memset(tmp_gc_app_metadata, 0, sizeof(TitleGameCardApplicationMetadataEntry)); - tmp_gc_app_metadata->app_metadata = app_info->app_metadata; - 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 = titleGetPatchVersionString(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); - } + LOG_MSG_ERROR("Failed to allocate memory for output array!"); + break; } - if (error) break; + /* Copy array data. */ + memcpy(dup_gc_app_metadata, g_titleGameCardApplicationMetadata, g_titleGameCardApplicationMetadataCount * sizeof(TitleGameCardApplicationMetadata)); /* Update output counter. */ - *out_count = app_count; - - if (gc_app_metadata && app_count) - { - /* Reorder title metadata entries by name. */ - if (app_count > 1) qsort(gc_app_metadata, app_count, sizeof(TitleGameCardApplicationMetadataEntry), &titleGameCardApplicationMetadataSortFunction); - } else { - LOG_MSG_ERROR("No gamecard content data found for user applications!"); - } + *out_count = g_titleGameCardApplicationMetadataCount; } - return gc_app_metadata; + return dup_gc_app_metadata; } TitleInfo *titleGetInfoFromStorageByTitleId(u8 storage_id, u64 title_id) @@ -1159,7 +1081,7 @@ char *titleGenerateFileName(TitleInfo *title_info, u8 naming_convention, u8 ille snprintf(title_name, MAX_ELEMENTS(title_name), "%s ", title_info->app_metadata->lang_entry.name); /* Retrieve display version string if we're dealing with a Patch. */ - char *version_str = (title_info->meta_key.type == NcmContentMetaType_Patch ? titleGetPatchVersionString(title_info) : NULL); + char *version_str = (title_info->meta_key.type == NcmContentMetaType_Patch ? titleGetDisplayVersionString(title_info) : NULL); if (version_str) { title_name_len = strlen(title_name); @@ -1192,13 +1114,9 @@ char *titleGenerateGameCardFileName(u8 naming_convention, u8 illegal_char_replac SCOPED_LOCK(&g_titleMutex) { - TitleStorage *title_storage = &(g_titleStorage[TITLE_STORAGE_INDEX(NcmStorageId_GameCard)]); - TitleInfo **titles = title_storage->titles; - u32 title_count = title_storage->title_count; - GameCardHeader gc_header = {0}; - size_t cur_filename_len = 0, app_name_len = 0; char app_name[0x300] = {0}; + size_t cur_filename_len = 0, app_name_len = 0; bool error = false; if (!g_titleInterfaceInit || !g_titleGameCardAvailable || naming_convention > TitleNamingConvention_IdAndVersionOnly || \ @@ -1208,30 +1126,16 @@ char *titleGenerateGameCardFileName(u8 naming_convention, u8 illegal_char_replac break; } - /* Check if the gamecard title storage is empty. */ + LOG_MSG_DEBUG("Generating %s gamecard filename...", naming_convention == TitleNamingConvention_Full ? "full" : "ID and version"); + + /* Check if we don't have any gamecard application metadata records we can work with. */ /* This is especially true for Kiosk / Quest gamecards. */ - if (!titles || !title_count) goto fallback; + if (!g_titleGameCardApplicationMetadata || !g_titleGameCardApplicationMetadataCount) goto fallback; - for(u32 i = 0; i < title_count; i++) + /* Loop through our gamecard application metadata entries. */ + for(u32 i = 0; i < g_titleGameCardApplicationMetadataCount; i++) { - 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; - - /* 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; - } + const TitleGameCardApplicationMetadata *cur_gc_app_metadata = &(g_titleGameCardApplicationMetadata[i]); /* Generate current user application name. */ *app_name = '\0'; @@ -1240,31 +1144,29 @@ char *titleGenerateGameCardFileName(u8 naming_convention, u8 illegal_char_replac { if (cur_filename_len) strcat(app_name, " + "); - if (app_info->app_metadata && *(app_info->app_metadata->lang_entry.name)) + if (cur_gc_app_metadata->app_metadata && cur_gc_app_metadata->app_metadata->lang_entry.name[0]) { app_name_len = strlen(app_name); - snprintf(app_name + app_name_len, MAX_ELEMENTS(app_name) - app_name_len, "%s ", app_info->app_metadata->lang_entry.name); + snprintf(app_name + app_name_len, MAX_ELEMENTS(app_name) - app_name_len, "%s ", cur_gc_app_metadata->app_metadata->lang_entry.name); - /* Retrieve display version string if the inserted gamecard holds a patch for the current user application. */ - char *version_str = (patch_info ? titleGetPatchVersionString(patch_info) : NULL); - if (version_str) + /* Append display version string if the inserted gamecard holds a patch for the current user application. */ + if (cur_gc_app_metadata->has_patch && cur_gc_app_metadata->display_version[0]) { app_name_len = strlen(app_name); - snprintf(app_name + app_name_len, MAX_ELEMENTS(app_name) - app_name_len, "%s ", version_str); - free(version_str); + snprintf(app_name + app_name_len, MAX_ELEMENTS(app_name) - app_name_len, "%s ", cur_gc_app_metadata->display_version); } if (illegal_char_replace_type) utilsReplaceIllegalCharacters(app_name, illegal_char_replace_type == TitleFileNameIllegalCharReplaceType_KeepAsciiCharsOnly); } app_name_len = strlen(app_name); - snprintf(app_name + app_name_len, MAX_ELEMENTS(app_name) - app_name_len, "[%016lX][v%u]", app_info->meta_key.id, app_version); + snprintf(app_name + app_name_len, MAX_ELEMENTS(app_name) - app_name_len, "[%016lX][v%u]", cur_gc_app_metadata->app_metadata->title_id, cur_gc_app_metadata->version.value); } else if (naming_convention == TitleNamingConvention_IdAndVersionOnly) { if (cur_filename_len) strcat(app_name, "+"); app_name_len = strlen(app_name); - snprintf(app_name + app_name_len, MAX_ELEMENTS(app_name) - app_name_len, "%016lX_v%u", app_info->meta_key.id, app_version); + snprintf(app_name + app_name_len, MAX_ELEMENTS(app_name) - app_name_len, "%016lX_v%u", cur_gc_app_metadata->app_metadata->title_id, cur_gc_app_metadata->version.value); } /* Reallocate output buffer. */ @@ -1330,6 +1232,7 @@ const char *titleGetNcmContentMetaTypeName(u8 content_meta_type) NX_INLINE void titleFreeApplicationMetadata(void) { + /* Free cached application metadata. */ for(u8 i = 0; i < 2; i++) { TitleApplicationMetadata **cached_app_metadata = (i == 0 ? g_systemMetadata : g_userMetadata); @@ -1353,6 +1256,20 @@ NX_INLINE void titleFreeApplicationMetadata(void) g_systemMetadata = g_userMetadata = NULL; g_systemMetadataCount = g_userMetadataCount = 0; + + /* Free filtered application metadata. */ + if (g_filteredSystemMetadata) free(g_filteredSystemMetadata); + + if (g_filteredUserMetadata) free(g_filteredUserMetadata); + + g_filteredSystemMetadata = g_filteredUserMetadata = NULL; + g_filteredSystemMetadataCount = g_filteredUserMetadataCount = 0; + + /* Free gamecard application metadata. */ + if (g_titleGameCardApplicationMetadata) free(g_titleGameCardApplicationMetadata); + + g_titleGameCardApplicationMetadata = NULL; + g_titleGameCardApplicationMetadataCount = 0; } static bool titleReallocateApplicationMetadata(u32 extra_app_count, bool is_system, bool free_entries) @@ -1940,6 +1857,188 @@ static bool titleRetrieveUserApplicationMetadataByTitleId(u64 title_id, TitleApp return true; } +static void titleGenerateFilteredApplicationMetadataPointerArray(bool is_system) +{ + TitleApplicationMetadata **filtered_app_metadata = NULL, **tmp_filtered_app_metadata = NULL; + u32 filtered_app_metadata_count = 0; + + TitleApplicationMetadata **cached_app_metadata = (is_system ? g_systemMetadata : g_userMetadata); + u32 cached_app_metadata_count = (is_system ? g_systemMetadataCount : g_userMetadataCount); + + /* Reset the right pointer and counter based on the input flag. */ + if (is_system) + { + if (g_filteredSystemMetadata) + { + free(g_filteredSystemMetadata); + g_filteredSystemMetadata = NULL; + } + + g_filteredSystemMetadataCount = 0; + } else { + if (g_filteredUserMetadata) + { + free(g_filteredUserMetadata); + g_filteredUserMetadata = NULL; + } + + g_filteredUserMetadataCount = 0; + } + + /* Make sure we actually have cached application metadata entries we can work with. */ + if (!cached_app_metadata || !cached_app_metadata_count) + { + LOG_MSG_ERROR("Cached %s application metadata array is empty!", is_system ? "system" : "user"); + return; + } + + /* Loop through our cached application metadata entries. */ + for(u32 i = 0; i < cached_app_metadata_count; i++) + { + TitleApplicationMetadata *cur_app_metadata = cached_app_metadata[i]; + 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)) || \ + (!is_system && !titleIsUserApplicationContentAvailable(cur_app_metadata->title_id))) continue; + + /* Reallocate filtered application metadata pointer array. */ + tmp_filtered_app_metadata = realloc(filtered_app_metadata, (filtered_app_metadata_count + 1) * sizeof(TitleApplicationMetadata*)); + if (!tmp_filtered_app_metadata) + { + LOG_MSG_ERROR("Failed to reallocate filtered application metadata pointer array!"); + if (filtered_app_metadata) free(filtered_app_metadata); + return; + } + + filtered_app_metadata = tmp_filtered_app_metadata; + tmp_filtered_app_metadata = NULL; + + /* Set current pointer and increase counter. */ + filtered_app_metadata[filtered_app_metadata_count++] = cur_app_metadata; + } + + if (!filtered_app_metadata || !filtered_app_metadata_count) + { + LOG_MSG_ERROR("No content data found for %s!", is_system ? "system titles" : "user applications"); + return; + } + + /* Update the right pointer and counter based on the input flag. */ + if (is_system) + { + g_filteredSystemMetadata = filtered_app_metadata; + g_filteredSystemMetadataCount = filtered_app_metadata_count; + } else { + g_filteredUserMetadata = filtered_app_metadata; + g_filteredUserMetadataCount = filtered_app_metadata_count; + } +} + +static void titleGenerateGameCardApplicationMetadataArray(void) +{ + 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) + { + 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++) + { + 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 (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!"); + } +} + NX_INLINE TitleApplicationMetadata *titleFindApplicationMetadataByTitleId(u64 title_id, bool is_system, u32 extra_app_count) { if (!title_id || (is_system && (!g_systemMetadata || !g_systemMetadataCount)) || (!is_system && (!g_userMetadata || !g_userMetadataCount))) return NULL; @@ -2388,7 +2487,19 @@ static void titleGameCardInfoThreadFunc(void *arg) if (idx == 1) break; /* Update gamecard title info. */ - SCOPED_LOCK(&g_titleMutex) g_titleGameCardInfoUpdated = titleRefreshGameCardTitleInfo(); + SCOPED_LOCK(&g_titleMutex) + { + g_titleGameCardInfoUpdated = titleRefreshGameCardTitleInfo(); + + if (g_titleGameCardInfoUpdated) + { + /* Generate filtered user application metadata pointer array. */ + titleGenerateFilteredApplicationMetadataPointerArray(false); + + /* Generate gamecard application metadata array. */ + titleGenerateGameCardApplicationMetadataArray(); + } + } } /* Update gamecard flags. */ @@ -2793,13 +2904,13 @@ static int titleInfoEntrySortFunction(const void *a, const void *b) static int titleGameCardApplicationMetadataSortFunction(const void *a, const void *b) { - const TitleGameCardApplicationMetadataEntry *gc_app_metadata_1 = (const TitleGameCardApplicationMetadataEntry*)a; - const TitleGameCardApplicationMetadataEntry *gc_app_metadata_2 = (const TitleGameCardApplicationMetadataEntry*)b; + const TitleGameCardApplicationMetadata *gc_app_metadata_1 = (const TitleGameCardApplicationMetadata*)a; + const TitleGameCardApplicationMetadata *gc_app_metadata_2 = (const TitleGameCardApplicationMetadata*)b; return strcasecmp(gc_app_metadata_1->app_metadata->lang_entry.name, gc_app_metadata_2->app_metadata->lang_entry.name); } -static char *titleGetPatchVersionString(TitleInfo *title_info) +static char *titleGetDisplayVersionString(TitleInfo *title_info) { NcmContentInfo *nacp_content = NULL; u8 storage_id = NcmStorageId_None, hfs_partition_type = HashFileSystemPartitionType_None; @@ -2807,12 +2918,17 @@ static char *titleGetPatchVersionString(TitleInfo *title_info) NacpContext nacp_ctx = {0}; char display_version[0x11] = {0}, *str = NULL; - if (!title_info || title_info->meta_key.type != NcmContentMetaType_Patch || !(nacp_content = titleGetContentInfoByTypeAndIdOffset(title_info, NcmContentType_Control, 0))) + 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!"); 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; diff --git a/source/tasks/gamecard_status_task.cpp b/source/tasks/gamecard_status_task.cpp index 1145767..b6ba5c6 100644 --- a/source/tasks/gamecard_status_task.cpp +++ b/source/tasks/gamecard_status_task.cpp @@ -28,9 +28,10 @@ namespace nxdt::tasks GameCardStatusTask::GameCardStatusTask() : brls::RepeatingTask(REPEATING_TASK_INTERVAL) { brls::RepeatingTask::start(); - LOG_MSG_DEBUG("Gamecard task started."); - this->first_notification = (gamecardGetStatus() >= GameCardStatus_Processing); + this->first_notification = (gamecardGetStatus() != GameCardStatus_NotInserted); + + LOG_MSG_DEBUG("Gamecard task started with first_notification = %u.", this->first_notification); } GameCardStatusTask::~GameCardStatusTask() diff --git a/source/tasks/status_info_task.cpp b/source/tasks/status_info_task.cpp index edd1cc5..7069391 100644 --- a/source/tasks/status_info_task.cpp +++ b/source/tasks/status_info_task.cpp @@ -34,11 +34,6 @@ namespace nxdt::tasks LOG_MSG_DEBUG("Status info task stopped."); } - bool StatusInfoTask::IsInternetConnectionAvailable(void) - { - return this->status_info_data.connected; - } - void StatusInfoTask::run(retro_time_t current_time) { brls::RepeatingTask::run(current_time); diff --git a/source/tasks/title_metadata_task.cpp b/source/tasks/title_metadata_task.cpp index 3df6f46..86b9c27 100644 --- a/source/tasks/title_metadata_task.cpp +++ b/source/tasks/title_metadata_task.cpp @@ -27,6 +27,8 @@ namespace nxdt::tasks { TitleMetadataTask::TitleMetadataTask() : brls::RepeatingTask(REPEATING_TASK_INTERVAL) { + LOG_MSG_DEBUG("Title metadata task started."); + /* Get system metadata entries. */ this->PopulateApplicationMetadataVector(true); @@ -35,7 +37,6 @@ namespace nxdt::tasks /* Start task. */ brls::RepeatingTask::start(); - LOG_MSG_DEBUG("Title metadata task started."); } TitleMetadataTask::~TitleMetadataTask() @@ -53,20 +54,15 @@ namespace nxdt::tasks if (titleIsGameCardInfoUpdated()) { - LOG_MSG_DEBUG("Title info updated."); - //brls::Application::notify("tasks/notifications/user_titles"_i18n); - /* Update user metadata vector. */ this->PopulateApplicationMetadataVector(false); /* Fire task event. */ this->user_title_event.fire(this->user_metadata); - } - } - const TitleApplicationMetadataVector& TitleMetadataTask::GetApplicationMetadata(bool is_system) - { - return (is_system ? this->system_metadata : this->user_metadata); + //brls::Application::notify("tasks/notifications/user_titles"_i18n); + LOG_MSG_DEBUG("Title info updated."); + } } void TitleMetadataTask::PopulateApplicationMetadataVector(bool is_system) diff --git a/source/tasks/ums_task.cpp b/source/tasks/ums_task.cpp index 29a69c5..b9bfae6 100644 --- a/source/tasks/ums_task.cpp +++ b/source/tasks/ums_task.cpp @@ -59,11 +59,6 @@ namespace nxdt::tasks } } - const UmsDeviceVector& UmsTask::GetUmsDevices(void) - { - return this->ums_devices_vector; - } - void UmsTask::PopulateUmsDeviceVector(void) { /* Clear UMS device vector. */ diff --git a/source/tasks/usb_host_task.cpp b/source/tasks/usb_host_task.cpp index 19f9a7b..7365c9a 100644 --- a/source/tasks/usb_host_task.cpp +++ b/source/tasks/usb_host_task.cpp @@ -53,9 +53,4 @@ namespace nxdt::tasks this->usb_host_event.fire(this->cur_usb_host_speed); } } - - const UsbHostSpeed& UsbHostTask::GetUsbHostSpeed(void) - { - return this->cur_usb_host_speed; - } } diff --git a/source/views/gamecard_tab.cpp b/source/views/gamecard_tab.cpp index e1c13e6..ca455ae 100644 --- a/source/views/gamecard_tab.cpp +++ b/source/views/gamecard_tab.cpp @@ -43,7 +43,7 @@ namespace nxdt::views this->list->setMarginBottom(20); /* Subscribe to the gamecard status event. */ - this->gc_status_task_sub = this->root_view->RegisterGameCardStatusTaskListener([this](GameCardStatus gc_status) { + this->gc_status_task_sub = this->root_view->RegisterGameCardStatusTaskListener([this](const GameCardStatus& gc_status) { /* Process gamecard status. */ this->ProcessGameCardStatus(gc_status); }); @@ -58,8 +58,13 @@ namespace nxdt::views this->root_view->UnregisterGameCardStatusTaskListener(this->gc_status_task_sub); } - void GameCardTab::ProcessGameCardStatus(GameCardStatus gc_status) + void GameCardTab::ProcessGameCardStatus(const GameCardStatus& gc_status) { + LOG_MSG_DEBUG("Processing gamecard status: %u.", gc_status); + + /* Block user inputs. */ + brls::Application::blockInputs(); + /* Switch to the error layer if gamecard info hasn't been loaded. */ if (gc_status < GameCardStatus_InsertedAndInfoLoaded) this->SwitchLayerView(true); @@ -90,6 +95,9 @@ namespace nxdt::views /* Update internal gamecard status. */ this->gc_status = gc_status; + + /* Unlock user inputs. */ + brls::Application::unblockInputs(); } void GameCardTab::PopulateList(void) @@ -155,7 +163,7 @@ namespace nxdt::views void GameCardTab::AddApplicationMetadataItems(void) { - TitleGameCardApplicationMetadataEntry *gc_app_metadata = nullptr; + TitleGameCardApplicationMetadata *gc_app_metadata = nullptr; u32 gc_app_metadata_count = 0; /* Retrieve gamecard application metadata. */ @@ -176,7 +184,7 @@ namespace nxdt::views /* Add gamecard application metadata items. */ for(u32 i = 0; i < gc_app_metadata_count; i++) { - TitleGameCardApplicationMetadataEntry *cur_gc_app_metadata = &(gc_app_metadata[i]); + TitleGameCardApplicationMetadata *cur_gc_app_metadata = &(gc_app_metadata[i]); /* Create item. */ TitlesTabItem *title = new TitlesTabItem(cur_gc_app_metadata->app_metadata, false, false); diff --git a/source/views/titles_tab.cpp b/source/views/titles_tab.cpp index cd0deb3..93d2cc3 100644 --- a/source/views/titles_tab.cpp +++ b/source/views/titles_tab.cpp @@ -102,6 +102,9 @@ namespace nxdt::views void TitlesTab::PopulateList(const nxdt::tasks::TitleApplicationMetadataVector& app_metadata) { + /* Block user inputs. */ + brls::Application::blockInputs(); + /* Populate variables. */ size_t app_metadata_count = app_metadata.size(); bool update_focused_view = this->IsListItemFocused(); @@ -114,8 +117,12 @@ namespace nxdt::views this->list->clear(); this->list->invalidate(true); - /* Return immediately if we have no user application metadata. */ - if (!app_metadata_count) return; + /* Return immediately if we have no application metadata. */ + if (!app_metadata_count) + { + brls::Application::unblockInputs(); + return; + } /* Populate list. */ for(const TitleApplicationMetadata *cur_app_metadata : app_metadata) @@ -126,14 +133,14 @@ namespace nxdt::views /* Register click event. */ item->getClickEvent()->subscribe([](brls::View *view) { TitlesTabItem *item = static_cast(view); - const TitleApplicationMetadata *app_metadata = item->GetApplicationMetadata(); + const TitleApplicationMetadata *item_app_metadata = item->GetApplicationMetadata(); bool is_system = item->IsSystemTitle(); /* Create popup. */ TitlesTabPopup *popup = nullptr; try { - popup = new TitlesTabPopup(app_metadata, is_system); + popup = new TitlesTabPopup(item_app_metadata, is_system); } catch(const std::string& msg) { LOG_MSG_DEBUG("%s", msg.c_str()); if (popup) delete popup; @@ -141,14 +148,14 @@ namespace nxdt::views } /* Display popup. */ - std::string name = std::string(app_metadata->lang_entry.name); - std::string tid = fmt::format("{:016X}", app_metadata->title_id); - std::string sub_left = (!is_system ? std::string(app_metadata->lang_entry.author) : tid); + std::string name = std::string(item_app_metadata->lang_entry.name); + std::string tid = fmt::format("{:016X}", item_app_metadata->title_id); + std::string sub_left = (!is_system ? std::string(item_app_metadata->lang_entry.author) : tid); std::string sub_right = (!is_system ? tid : ""); - if (app_metadata->icon && app_metadata->icon_size) + if (item_app_metadata->icon && item_app_metadata->icon_size) { - brls::PopupFrame::open(name, app_metadata->icon, app_metadata->icon_size, popup, sub_left, sub_right); + brls::PopupFrame::open(name, item_app_metadata->icon, item_app_metadata->icon_size, popup, sub_left, sub_right); } else { brls::PopupFrame::open(name, popup, sub_left, sub_right); } @@ -164,5 +171,8 @@ namespace nxdt::views /* Switch to the list. */ this->list->invalidate(true); this->SwitchLayerView(false, update_focused_view, focus_stack_index < 0); + + /* Unblock user inputs. */ + brls::Application::unblockInputs(); } }