1
0
Fork 0
mirror of https://github.com/DarkMatterCore/nxdumptool.git synced 2024-11-22 10:16:39 +00:00

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.
This commit is contained in:
Pablo Curiel 2024-05-05 21:42:32 +02:00
parent 6acdb38d11
commit 5cc387c9b6
17 changed files with 351 additions and 215 deletions

View file

@ -49,10 +49,11 @@ typedef struct {
/// Used to display gamecard-specific title information. /// Used to display gamecard-specific title information.
typedef struct { typedef struct {
TitleApplicationMetadata *app_metadata; ///< User application metadata. TitleApplicationMetadata *app_metadata; ///< User application metadata.
Version version; ///< Reflects the title version stored in the inserted gamecard. bool has_patch; ///< Set to true if a patch is also available in the inserted gamecard for this user application.
char display_version[32]; ///< Reflects the title display version stored in its NACP. 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. u32 dlc_count; ///< Reflects the number of DLCs available for this application in the inserted gamecard.
} TitleGameCardApplicationMetadataEntry; } TitleGameCardApplicationMetadata;
/// Generated using ncm calls. /// Generated using ncm calls.
/// User applications: the previous/next pointers reference other user applications with the same ID. /// 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(). /// The allocated buffer must be freed by the caller using free().
TitleApplicationMetadata **titleGetApplicationMetadataEntries(bool is_system, u32 *out_count); 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. /// Returns NULL if an error occurs.
/// The allocated buffer must be freed by the caller using free(). /// 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. /// 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. /// If NcmStorageId_Any is used, the first entry with a matching title ID is returned.

View file

@ -51,6 +51,12 @@ namespace nxdt::tasks
GameCardStatusTask(); GameCardStatusTask();
~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) ALWAYS_INLINE GameCardStatusEvent::Subscription RegisterListener(GameCardStatusEvent::Callback cb)
{ {
return this->gc_status_event.subscribe(cb); return this->gc_status_event.subscribe(cb);

View file

@ -58,7 +58,10 @@ namespace nxdt::tasks
StatusInfoTask(); StatusInfoTask();
~StatusInfoTask(); ~StatusInfoTask();
bool IsInternetConnectionAvailable(void); ALWAYS_INLINE bool IsInternetConnectionAvailable(void)
{
return this->status_info_data.connected;
}
ALWAYS_INLINE StatusInfoEvent::Subscription RegisterListener(StatusInfoEvent::Callback cb) ALWAYS_INLINE StatusInfoEvent::Subscription RegisterListener(StatusInfoEvent::Callback cb)
{ {

View file

@ -57,7 +57,10 @@ namespace nxdt::tasks
~TitleMetadataTask(); ~TitleMetadataTask();
/* Intentionally left here to let views retrieve title metadata on-demand. */ /* 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) ALWAYS_INLINE UserTitleEvent::Subscription RegisterListener(UserTitleEvent::Callback cb)
{ {

View file

@ -60,7 +60,10 @@ namespace nxdt::tasks
~UmsTask(); ~UmsTask();
/* Intentionally left here to let views retrieve UMS device info on-demand. */ /* 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) ALWAYS_INLINE UmsEvent::Subscription RegisterListener(UmsEvent::Callback cb)
{ {

View file

@ -50,7 +50,10 @@ namespace nxdt::tasks
~UsbHostTask(); ~UsbHostTask();
/* Intentionally left here to let views retrieve USB host connection speed on-demand. */ /* 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) ALWAYS_INLINE UsbHostEvent::Subscription RegisterListener(UsbHostEvent::Callback cb)
{ {

View file

@ -43,9 +43,7 @@ namespace nxdt::views
std::string raw_filename_full = ""; std::string raw_filename_full = "";
std::string raw_filename_id_only = ""; std::string raw_filename_id_only = "";
void ProcessGameCardStatus(GameCardStatus gc_status); void ProcessGameCardStatus(const GameCardStatus& gc_status);
void PopulateList(void); void PopulateList(void);
void AddApplicationMetadataItems(void); void AddApplicationMetadataItems(void);
@ -55,8 +53,6 @@ namespace nxdt::views
std::string GetFormattedSizeString(GameCardSizeFunc func); std::string GetFormattedSizeString(GameCardSizeFunc func);
std::string GetCardIdSetString(const FsGameCardIdSet& card_id_set); std::string GetCardIdSetString(const FsGameCardIdSet& card_id_set);
public: public:
GameCardTab(RootView *root_view); GameCardTab(RootView *root_view);
~GameCardTab(); ~GameCardTab();

View file

@ -90,6 +90,11 @@ namespace nxdt::views
return this->status_info_task->IsInternetConnectionAvailable(); 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) ALWAYS_INLINE const nxdt::tasks::TitleApplicationMetadataVector& GetApplicationMetadata(bool is_system)
{ {
return this->title_metadata_task->GetApplicationMetadata(is_system); return this->title_metadata_task->GetApplicationMetadata(is_system);

@ -1 +1 @@
Subproject commit 0846ff57b72a1bdd9fc86eee348258c0b52e0ece Subproject commit 1a69955c64f72a48342abce317a75998ffac3f9d

View file

@ -59,6 +59,12 @@ static NsApplicationControlData *g_nsAppControlData = NULL;
static TitleApplicationMetadata **g_systemMetadata = NULL, **g_userMetadata = NULL; static TitleApplicationMetadata **g_systemMetadata = NULL, **g_userMetadata = NULL;
static u32 g_systemMetadataCount = 0, g_userMetadataCount = 0; 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 TitleStorage g_titleStorage[TITLE_STORAGE_COUNT] = {0};
static TitleInfo **g_orphanTitleInfo = NULL; static TitleInfo **g_orphanTitleInfo = NULL;
@ -552,6 +558,9 @@ static bool titleGenerateMetadataEntriesFromNsRecords(void);
static TitleApplicationMetadata *titleGenerateDummySystemMetadataEntry(u64 title_id); static TitleApplicationMetadata *titleGenerateDummySystemMetadataEntry(u64 title_id);
static bool titleRetrieveUserApplicationMetadataByTitleId(u64 title_id, TitleApplicationMetadata *out); 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 TitleApplicationMetadata *titleFindApplicationMetadataByTitleId(u64 title_id, bool is_system, u32 extra_app_count);
NX_INLINE u64 titleGetApplicationIdByContentMetaKey(const NcmContentMetaKey *meta_key); 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 titleInfoEntrySortFunction(const void *a, const void *b);
static int titleGameCardApplicationMetadataSortFunction(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) bool titleInitialize(void)
{ {
@ -623,6 +632,12 @@ bool titleInitialize(void)
break; break;
} }
/* Generate filtered system application metadata pointer array. */
titleGenerateFilteredApplicationMetadataPointerArray(true);
/* Generate filtered user application metadata pointer array. */
titleGenerateFilteredApplicationMetadataPointerArray(false);
/* Create user-mode exit event. */ /* Create user-mode exit event. */
ueventCreate(&g_titleGameCardInfoThreadExitEvent, true); ueventCreate(&g_titleGameCardInfoThreadExitEvent, true);
@ -689,158 +704,65 @@ NcmContentStorage *titleGetNcmStorageByStorageId(u8 storage_id)
TitleApplicationMetadata **titleGetApplicationMetadataEntries(bool is_system, u32 *out_count) TitleApplicationMetadata **titleGetApplicationMetadataEntries(bool is_system, u32 *out_count)
{ {
u32 app_count = 0; TitleApplicationMetadata **dup_filtered_app_metadata = NULL;
TitleApplicationMetadata **app_metadata = NULL, **tmp_app_metadata = NULL;
SCOPED_LOCK(&g_titleMutex) 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!"); LOG_MSG_ERROR("Invalid parameters!");
break; break;
} }
TitleApplicationMetadata **cached_app_metadata = (is_system ? g_systemMetadata : g_userMetadata); /* Allocate memory for the pointer array. */
u32 cached_app_metadata_count = (is_system ? g_systemMetadataCount : g_userMetadataCount); dup_filtered_app_metadata = malloc(filtered_app_metadata_count * sizeof(TitleApplicationMetadata*));
bool error = false; if (!dup_filtered_app_metadata)
for(u32 i = 0; i < cached_app_metadata_count; i++)
{ {
TitleApplicationMetadata *cur_app_metadata = cached_app_metadata[i]; LOG_MSG_ERROR("Failed to allocate memory for pointer array duplicate!");
if (!cur_app_metadata) continue; break;
/* 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;
} }
if (error) break; /* Copy application metadata pointers. */
memcpy(dup_filtered_app_metadata, filtered_app_metadata, filtered_app_metadata_count * sizeof(TitleApplicationMetadata*));
/* Update output counter. */ /* Update output counter. */
*out_count = app_count; *out_count = filtered_app_metadata_count;
if (!app_metadata || !app_count) LOG_MSG_ERROR("No content data found for %s!", is_system ? "system titles" : "user applications");
} }
return app_metadata; return dup_filtered_app_metadata;
} }
TitleGameCardApplicationMetadataEntry *titleGetGameCardApplicationMetadataEntries(u32 *out_count) TitleGameCardApplicationMetadata *titleGetGameCardApplicationMetadataEntries(u32 *out_count)
{ {
u32 app_count = 0; TitleGameCardApplicationMetadata *dup_gc_app_metadata = NULL;
TitleGameCardApplicationMetadataEntry *gc_app_metadata = NULL, *tmp_gc_app_metadata = NULL;
SCOPED_LOCK(&g_titleMutex) SCOPED_LOCK(&g_titleMutex)
{ {
TitleStorage *title_storage = &(g_titleStorage[TITLE_STORAGE_INDEX(NcmStorageId_GameCard)]); if (!g_titleInterfaceInit || !g_titleGameCardAvailable || !g_titleGameCardApplicationMetadata || !g_titleGameCardApplicationMetadataCount || !out_count)
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)
{ {
LOG_MSG_ERROR("Invalid parameters!"); LOG_MSG_ERROR("Invalid parameters!");
break; break;
} }
/* Loop through our gamecard TitleInfo entries. */ /* Allocate memory for the output array. */
for(u32 i = 0; i < title_count; i++) dup_gc_app_metadata = malloc(g_titleGameCardApplicationMetadataCount * sizeof(TitleGameCardApplicationMetadata));
if (!dup_gc_app_metadata)
{ {
/* Skip current entry if it's not a user application. */ LOG_MSG_ERROR("Failed to allocate memory for output array!");
TitleInfo *app_info = titles[i], *patch_info = NULL; break;
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);
}
} }
if (error) break; /* Copy array data. */
memcpy(dup_gc_app_metadata, g_titleGameCardApplicationMetadata, g_titleGameCardApplicationMetadataCount * sizeof(TitleGameCardApplicationMetadata));
/* Update output counter. */ /* Update output counter. */
*out_count = app_count; *out_count = g_titleGameCardApplicationMetadataCount;
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!");
}
} }
return gc_app_metadata; return dup_gc_app_metadata;
} }
TitleInfo *titleGetInfoFromStorageByTitleId(u8 storage_id, u64 title_id) 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); 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. */ /* 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) if (version_str)
{ {
title_name_len = strlen(title_name); title_name_len = strlen(title_name);
@ -1192,13 +1114,9 @@ char *titleGenerateGameCardFileName(u8 naming_convention, u8 illegal_char_replac
SCOPED_LOCK(&g_titleMutex) 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}; GameCardHeader gc_header = {0};
size_t cur_filename_len = 0, app_name_len = 0;
char app_name[0x300] = {0}; char app_name[0x300] = {0};
size_t cur_filename_len = 0, app_name_len = 0;
bool error = false; bool error = false;
if (!g_titleInterfaceInit || !g_titleGameCardAvailable || naming_convention > TitleNamingConvention_IdAndVersionOnly || \ if (!g_titleInterfaceInit || !g_titleGameCardAvailable || naming_convention > TitleNamingConvention_IdAndVersionOnly || \
@ -1208,30 +1126,16 @@ char *titleGenerateGameCardFileName(u8 naming_convention, u8 illegal_char_replac
break; 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. */ /* 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; const TitleGameCardApplicationMetadata *cur_gc_app_metadata = &(g_titleGameCardApplicationMetadata[i]);
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;
}
/* Generate current user application name. */ /* Generate current user application name. */
*app_name = '\0'; *app_name = '\0';
@ -1240,31 +1144,29 @@ char *titleGenerateGameCardFileName(u8 naming_convention, u8 illegal_char_replac
{ {
if (cur_filename_len) strcat(app_name, " + "); 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); 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. */ /* Append 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 (cur_gc_app_metadata->has_patch && cur_gc_app_metadata->display_version[0])
if (version_str)
{ {
app_name_len = strlen(app_name); app_name_len = strlen(app_name);
snprintf(app_name + app_name_len, MAX_ELEMENTS(app_name) - app_name_len, "%s ", version_str); snprintf(app_name + app_name_len, MAX_ELEMENTS(app_name) - app_name_len, "%s ", cur_gc_app_metadata->display_version);
free(version_str);
} }
if (illegal_char_replace_type) utilsReplaceIllegalCharacters(app_name, illegal_char_replace_type == TitleFileNameIllegalCharReplaceType_KeepAsciiCharsOnly); if (illegal_char_replace_type) utilsReplaceIllegalCharacters(app_name, illegal_char_replace_type == TitleFileNameIllegalCharReplaceType_KeepAsciiCharsOnly);
} }
app_name_len = strlen(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);
} else } else
if (naming_convention == TitleNamingConvention_IdAndVersionOnly) if (naming_convention == TitleNamingConvention_IdAndVersionOnly)
{ {
if (cur_filename_len) strcat(app_name, "+"); if (cur_filename_len) strcat(app_name, "+");
app_name_len = strlen(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. */ /* Reallocate output buffer. */
@ -1330,6 +1232,7 @@ const char *titleGetNcmContentMetaTypeName(u8 content_meta_type)
NX_INLINE void titleFreeApplicationMetadata(void) NX_INLINE void titleFreeApplicationMetadata(void)
{ {
/* Free cached application metadata. */
for(u8 i = 0; i < 2; i++) for(u8 i = 0; i < 2; i++)
{ {
TitleApplicationMetadata **cached_app_metadata = (i == 0 ? g_systemMetadata : g_userMetadata); 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_systemMetadata = g_userMetadata = NULL;
g_systemMetadataCount = g_userMetadataCount = 0; 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) 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; 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) 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; 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; if (idx == 1) break;
/* Update gamecard title info. */ /* 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. */ /* 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) static int titleGameCardApplicationMetadataSortFunction(const void *a, const void *b)
{ {
const TitleGameCardApplicationMetadataEntry *gc_app_metadata_1 = (const TitleGameCardApplicationMetadataEntry*)a; const TitleGameCardApplicationMetadata *gc_app_metadata_1 = (const TitleGameCardApplicationMetadata*)a;
const TitleGameCardApplicationMetadataEntry *gc_app_metadata_2 = (const TitleGameCardApplicationMetadataEntry*)b; 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); 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; NcmContentInfo *nacp_content = NULL;
u8 storage_id = NcmStorageId_None, hfs_partition_type = HashFileSystemPartitionType_None; u8 storage_id = NcmStorageId_None, hfs_partition_type = HashFileSystemPartitionType_None;
@ -2807,12 +2918,17 @@ static char *titleGetPatchVersionString(TitleInfo *title_info)
NacpContext nacp_ctx = {0}; NacpContext nacp_ctx = {0};
char display_version[0x11] = {0}, *str = NULL; 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!"); LOG_MSG_ERROR("Invalid parameters!");
goto end; 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. */ /* Update parameters. */
storage_id = title_info->storage_id; storage_id = title_info->storage_id;
if (storage_id == NcmStorageId_GameCard) hfs_partition_type = HashFileSystemPartitionType_Secure; if (storage_id == NcmStorageId_GameCard) hfs_partition_type = HashFileSystemPartitionType_Secure;

View file

@ -28,9 +28,10 @@ namespace nxdt::tasks
GameCardStatusTask::GameCardStatusTask() : brls::RepeatingTask(REPEATING_TASK_INTERVAL) GameCardStatusTask::GameCardStatusTask() : brls::RepeatingTask(REPEATING_TASK_INTERVAL)
{ {
brls::RepeatingTask::start(); 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() GameCardStatusTask::~GameCardStatusTask()

View file

@ -34,11 +34,6 @@ namespace nxdt::tasks
LOG_MSG_DEBUG("Status info task stopped."); 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) void StatusInfoTask::run(retro_time_t current_time)
{ {
brls::RepeatingTask::run(current_time); brls::RepeatingTask::run(current_time);

View file

@ -27,6 +27,8 @@ namespace nxdt::tasks
{ {
TitleMetadataTask::TitleMetadataTask() : brls::RepeatingTask(REPEATING_TASK_INTERVAL) TitleMetadataTask::TitleMetadataTask() : brls::RepeatingTask(REPEATING_TASK_INTERVAL)
{ {
LOG_MSG_DEBUG("Title metadata task started.");
/* Get system metadata entries. */ /* Get system metadata entries. */
this->PopulateApplicationMetadataVector(true); this->PopulateApplicationMetadataVector(true);
@ -35,7 +37,6 @@ namespace nxdt::tasks
/* Start task. */ /* Start task. */
brls::RepeatingTask::start(); brls::RepeatingTask::start();
LOG_MSG_DEBUG("Title metadata task started.");
} }
TitleMetadataTask::~TitleMetadataTask() TitleMetadataTask::~TitleMetadataTask()
@ -53,20 +54,15 @@ namespace nxdt::tasks
if (titleIsGameCardInfoUpdated()) if (titleIsGameCardInfoUpdated())
{ {
LOG_MSG_DEBUG("Title info updated.");
//brls::Application::notify("tasks/notifications/user_titles"_i18n);
/* Update user metadata vector. */ /* Update user metadata vector. */
this->PopulateApplicationMetadataVector(false); this->PopulateApplicationMetadataVector(false);
/* Fire task event. */ /* Fire task event. */
this->user_title_event.fire(this->user_metadata); this->user_title_event.fire(this->user_metadata);
}
}
const TitleApplicationMetadataVector& TitleMetadataTask::GetApplicationMetadata(bool is_system) //brls::Application::notify("tasks/notifications/user_titles"_i18n);
{ LOG_MSG_DEBUG("Title info updated.");
return (is_system ? this->system_metadata : this->user_metadata); }
} }
void TitleMetadataTask::PopulateApplicationMetadataVector(bool is_system) void TitleMetadataTask::PopulateApplicationMetadataVector(bool is_system)

View file

@ -59,11 +59,6 @@ namespace nxdt::tasks
} }
} }
const UmsDeviceVector& UmsTask::GetUmsDevices(void)
{
return this->ums_devices_vector;
}
void UmsTask::PopulateUmsDeviceVector(void) void UmsTask::PopulateUmsDeviceVector(void)
{ {
/* Clear UMS device vector. */ /* Clear UMS device vector. */

View file

@ -53,9 +53,4 @@ namespace nxdt::tasks
this->usb_host_event.fire(this->cur_usb_host_speed); this->usb_host_event.fire(this->cur_usb_host_speed);
} }
} }
const UsbHostSpeed& UsbHostTask::GetUsbHostSpeed(void)
{
return this->cur_usb_host_speed;
}
} }

View file

@ -43,7 +43,7 @@ namespace nxdt::views
this->list->setMarginBottom(20); this->list->setMarginBottom(20);
/* Subscribe to the gamecard status event. */ /* 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. */ /* Process gamecard status. */
this->ProcessGameCardStatus(gc_status); this->ProcessGameCardStatus(gc_status);
}); });
@ -58,8 +58,13 @@ namespace nxdt::views
this->root_view->UnregisterGameCardStatusTaskListener(this->gc_status_task_sub); 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. */ /* Switch to the error layer if gamecard info hasn't been loaded. */
if (gc_status < GameCardStatus_InsertedAndInfoLoaded) this->SwitchLayerView(true); if (gc_status < GameCardStatus_InsertedAndInfoLoaded) this->SwitchLayerView(true);
@ -90,6 +95,9 @@ namespace nxdt::views
/* Update internal gamecard status. */ /* Update internal gamecard status. */
this->gc_status = gc_status; this->gc_status = gc_status;
/* Unlock user inputs. */
brls::Application::unblockInputs();
} }
void GameCardTab::PopulateList(void) void GameCardTab::PopulateList(void)
@ -155,7 +163,7 @@ namespace nxdt::views
void GameCardTab::AddApplicationMetadataItems(void) void GameCardTab::AddApplicationMetadataItems(void)
{ {
TitleGameCardApplicationMetadataEntry *gc_app_metadata = nullptr; TitleGameCardApplicationMetadata *gc_app_metadata = nullptr;
u32 gc_app_metadata_count = 0; u32 gc_app_metadata_count = 0;
/* Retrieve gamecard application metadata. */ /* Retrieve gamecard application metadata. */
@ -176,7 +184,7 @@ namespace nxdt::views
/* Add gamecard application metadata items. */ /* Add gamecard application metadata items. */
for(u32 i = 0; i < gc_app_metadata_count; i++) 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. */ /* Create item. */
TitlesTabItem *title = new TitlesTabItem(cur_gc_app_metadata->app_metadata, false, false); TitlesTabItem *title = new TitlesTabItem(cur_gc_app_metadata->app_metadata, false, false);

View file

@ -102,6 +102,9 @@ namespace nxdt::views
void TitlesTab::PopulateList(const nxdt::tasks::TitleApplicationMetadataVector& app_metadata) void TitlesTab::PopulateList(const nxdt::tasks::TitleApplicationMetadataVector& app_metadata)
{ {
/* Block user inputs. */
brls::Application::blockInputs();
/* Populate variables. */ /* Populate variables. */
size_t app_metadata_count = app_metadata.size(); size_t app_metadata_count = app_metadata.size();
bool update_focused_view = this->IsListItemFocused(); bool update_focused_view = this->IsListItemFocused();
@ -114,8 +117,12 @@ namespace nxdt::views
this->list->clear(); this->list->clear();
this->list->invalidate(true); this->list->invalidate(true);
/* Return immediately if we have no user application metadata. */ /* Return immediately if we have no application metadata. */
if (!app_metadata_count) return; if (!app_metadata_count)
{
brls::Application::unblockInputs();
return;
}
/* Populate list. */ /* Populate list. */
for(const TitleApplicationMetadata *cur_app_metadata : app_metadata) for(const TitleApplicationMetadata *cur_app_metadata : app_metadata)
@ -126,14 +133,14 @@ namespace nxdt::views
/* Register click event. */ /* Register click event. */
item->getClickEvent()->subscribe([](brls::View *view) { item->getClickEvent()->subscribe([](brls::View *view) {
TitlesTabItem *item = static_cast<TitlesTabItem*>(view); TitlesTabItem *item = static_cast<TitlesTabItem*>(view);
const TitleApplicationMetadata *app_metadata = item->GetApplicationMetadata(); const TitleApplicationMetadata *item_app_metadata = item->GetApplicationMetadata();
bool is_system = item->IsSystemTitle(); bool is_system = item->IsSystemTitle();
/* Create popup. */ /* Create popup. */
TitlesTabPopup *popup = nullptr; TitlesTabPopup *popup = nullptr;
try { try {
popup = new TitlesTabPopup(app_metadata, is_system); popup = new TitlesTabPopup(item_app_metadata, is_system);
} catch(const std::string& msg) { } catch(const std::string& msg) {
LOG_MSG_DEBUG("%s", msg.c_str()); LOG_MSG_DEBUG("%s", msg.c_str());
if (popup) delete popup; if (popup) delete popup;
@ -141,14 +148,14 @@ namespace nxdt::views
} }
/* Display popup. */ /* Display popup. */
std::string name = std::string(app_metadata->lang_entry.name); std::string name = std::string(item_app_metadata->lang_entry.name);
std::string tid = fmt::format("{:016X}", app_metadata->title_id); std::string tid = fmt::format("{:016X}", item_app_metadata->title_id);
std::string sub_left = (!is_system ? std::string(app_metadata->lang_entry.author) : tid); std::string sub_left = (!is_system ? std::string(item_app_metadata->lang_entry.author) : tid);
std::string sub_right = (!is_system ? 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 { } else {
brls::PopupFrame::open(name, popup, sub_left, sub_right); brls::PopupFrame::open(name, popup, sub_left, sub_right);
} }
@ -164,5 +171,8 @@ namespace nxdt::views
/* Switch to the list. */ /* Switch to the list. */
this->list->invalidate(true); this->list->invalidate(true);
this->SwitchLayerView(false, update_focused_view, focus_stack_index < 0); this->SwitchLayerView(false, update_focused_view, focus_stack_index < 0);
/* Unblock user inputs. */
brls::Application::unblockInputs();
} }
} }