From eee1b2a7718eaf8b1cd408dd65a06f20f792a663 Mon Sep 17 00:00:00 2001 From: Pablo Curiel Date: Thu, 28 Jul 2022 00:53:52 +0200 Subject: [PATCH] More changes. * Update libusbhsfs. * Update borealis. * nsp_dumper: force free size retrieval after dumping an NSP. * title: add titleGetGameCardApplicationMetadataEntries(). * Makefile: remove -gdwarf-4 flag. * nxdt_utils: treat NT_MAX_FILENAME_LENGTH as bytes instead of codepoints, add "TiB" to the array of supported size suffixes. * GameCardTab: add ProcessGameCardStatus() and PopulateList(), manage list updates in the same fashion as TitlesTab, display message about how to mitigate launch errors after exiting the application, display available applications in the inserted gamecard, display message about how to perform individual operations on the gamecard titles. * main: add a try/catch block to intercept any possible exceptions thrown while the application is running + use brls::Application::crash() to gracefully exit afterwards. Temporarily disable Applet Mode support. * exception_handler: use LOG_LEVEL_ERROR. * LayeredErrorFrame: add GetListFirstFocusableChild(). --- Makefile | 6 +- code_templates/nsp_dumper.c | 1 + include/core/title.h | 4 + include/gamecard_tab.hpp | 18 +-- include/layered_error_frame.hpp | 2 + libs/borealis | 2 +- libs/libusbhsfs | 2 +- romfs/i18n/en-US/gamecard_tab.json | 23 ++- romfs/i18n/en-US/generic.json | 3 + source/core/nxdt_utils.c | 29 ++-- source/core/title.c | 52 ++++++ source/exception_handler.cpp | 10 +- source/gamecard_tab.cpp | 248 ++++++++++++++++++----------- source/layered_error_frame.cpp | 24 ++- source/main.cpp | 40 +++-- source/titles_tab.cpp | 4 +- 16 files changed, 304 insertions(+), 164 deletions(-) diff --git a/Makefile b/Makefile index 912cc0d..b444261 100644 --- a/Makefile +++ b/Makefile @@ -84,7 +84,7 @@ USBHSFS_PATH := $(ROOTDIR)/libs/libusbhsfs #--------------------------------------------------------------------------------- ARCH := -march=armv8-a+crc+crypto -mtune=cortex-a57 -mtp=soft -fPIE -CFLAGS := -g -gdwarf-4 -Wall -Werror -O2 -ffunction-sections $(ARCH) $(DEFINES) $(INCLUDE) -D__SWITCH__ +CFLAGS := -g -Wall -Werror -O2 -ffunction-sections $(ARCH) $(DEFINES) $(INCLUDE) -D__SWITCH__ CFLAGS += -DVERSION_MAJOR=${VERSION_MAJOR} -DVERSION_MINOR=${VERSION_MINOR} -DVERSION_MICRO=${VERSION_MICRO} CFLAGS += -DAPP_TITLE=\"${APP_TITLE}\" -DAPP_AUTHOR=\"${APP_AUTHOR}\" -DAPP_VERSION=\"${APP_VERSION}\" CFLAGS += -DGIT_BRANCH=\"${GIT_BRANCH}\" -DGIT_COMMIT=\"${GIT_COMMIT}\" -DGIT_REV=\"${GIT_REV}\" @@ -92,8 +92,8 @@ CFLAGS += -DBUILD_TIMESTAMP="\"${BUILD_TIMESTAMP}\"" -DBOREALIS_RESOURCES="\"${ CXXFLAGS := $(CFLAGS) -std=c++20 -Wno-volatile -Wno-unused-parameter -ASFLAGS := -g -gdwarf-4 $(ARCH) -LDFLAGS := -specs=$(DEVKITPRO)/libnx/switch.specs -g -gdwarf-4 $(ARCH) -Wl,-Map,$(notdir $*.map) +ASFLAGS := -g $(ARCH) +LDFLAGS := -specs=$(DEVKITPRO)/libnx/switch.specs -g $(ARCH) -Wl,-Map,$(notdir $*.map) LIBS := -lcurl -lmbedtls -lmbedx509 -lmbedcrypto -lxml2 -ljson-c -lz -lusbhsfs -lntfs-3g -llwext4 -lnx diff --git a/code_templates/nsp_dumper.c b/code_templates/nsp_dumper.c index b37868a..75f54a7 100644 --- a/code_templates/nsp_dumper.c +++ b/code_templates/nsp_dumper.c @@ -1356,6 +1356,7 @@ int main(int argc, char *argv[]) utilsSetLongRunningProcessState(true); nspDump(title_info); utilsSetLongRunningProcessState(false); + device_retrieved_size = false; } if (error || menu >= 3) diff --git a/include/core/title.h b/include/core/title.h index 25b8d8a..5c46724 100644 --- a/include/core/title.h +++ b/include/core/title.h @@ -98,6 +98,10 @@ NcmContentStorage *titleGetNcmStorageByStorageId(u8 storage_id); /// The allocated buffer must be freed by the calling function using free(). TitleApplicationMetadata **titleGetApplicationMetadataEntries(bool is_system, u32 *out_count); +/// Returns a pointer to a dynamically allocated array of pointers to TitleApplicationMetadata entries with matching gamecard user titles, as well as their count. Returns NULL if an error occurs. +/// The allocated buffer must be freed by the calling function using free(). +TitleApplicationMetadata **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. /// Use titleFreeTitleInfo() to free the returned data. diff --git a/include/gamecard_tab.hpp b/include/gamecard_tab.hpp index cbda430..5f54ddd 100644 --- a/include/gamecard_tab.hpp +++ b/include/gamecard_tab.hpp @@ -40,23 +40,9 @@ namespace nxdt::views nxdt::tasks::GameCardStatusEvent::Subscription gc_status_task_sub; GameCardStatus gc_status = GameCardStatus_NotInserted; - FocusableTable *properties_table = nullptr; - brls::TableRow *capacity = nullptr; - brls::TableRow *total_size = nullptr; - brls::TableRow *trimmed_size = nullptr; - brls::TableRow *update_version = nullptr; - brls::TableRow *lafw_version = nullptr; - brls::TableRow *sdk_version = nullptr; - brls::TableRow *compatibility_type = nullptr; - - brls::ListItem *dump_card_image = nullptr; - brls::ListItem *dump_certificate = nullptr; - brls::ListItem *dump_header = nullptr; - brls::ListItem *dump_decrypted_cardinfo = nullptr; - brls::ListItem *dump_initial_data = nullptr; - brls::ListItem *dump_hfs_partitions = nullptr; - + void ProcessGameCardStatus(GameCardStatus gc_status); std::string GetFormattedSizeString(GameCardSizeFunc func); + void PopulateList(void); public: GameCardTab(RootView *root_view); diff --git a/include/layered_error_frame.hpp b/include/layered_error_frame.hpp index eda73ba..00873ca 100644 --- a/include/layered_error_frame.hpp +++ b/include/layered_error_frame.hpp @@ -43,6 +43,8 @@ namespace nxdt::views int GetFocusStackViewIndex(void); bool UpdateFocusStackViewAtIndex(int index, brls::View *view); + brls::View *GetListFirstFocusableChild(void); + void SwitchLayerView(bool use_error_frame, bool update_focused_view = false, bool update_focus_stack = true); public: diff --git a/libs/borealis b/libs/borealis index df32a7c..2dcb0c6 160000 --- a/libs/borealis +++ b/libs/borealis @@ -1 +1 @@ -Subproject commit df32a7cc42f06e0de259a8f0807e3d1690ef31f3 +Subproject commit 2dcb0c6f8370ae0c5de760bf612e6bf5b54ee837 diff --git a/libs/libusbhsfs b/libs/libusbhsfs index 4d109af..315c98e 160000 --- a/libs/libusbhsfs +++ b/libs/libusbhsfs @@ -1 +1 @@ -Subproject commit 4d109af9b4c34a685521ddc49103784585ecc1f4 +Subproject commit 315c98ecd631656b0e5839ca5e6fc293908d06f8 diff --git a/romfs/i18n/en-US/gamecard_tab.json b/romfs/i18n/en-US/gamecard_tab.json index 8d6c0a4..e7b27bf 100644 --- a/romfs/i18n/en-US/gamecard_tab.json +++ b/romfs/i18n/en-US/gamecard_tab.json @@ -6,8 +6,15 @@ "lafw_update_required": "A gamecard has been inserted, but a Lotus ASIC firmware update is required.\nUpdate your console using the inserted gamecard and try again.", "info_not_loaded": "A gamecard has been inserted, but an unexpected I/O error occurred.\nPlease report this issue at \"{0}\"." }, - + "list": { + "launch_error_info": "Please take out the gamecard and reinsert it into the console after exiting nxdumptool to mitigate launch errors.", + + "user_titles": { + "header": "Applications available on the inserted gamecard", + "info": "To perform operations on the user titles available on this gamecard, please go to the \"{0}\" menu." + }, + "properties_table": { "header": "Gamecard properties", "capacity": "Capacity", @@ -19,34 +26,34 @@ "sdk_version": "SDK version", "compatibility_type": "Compatibility type" }, - + "dump_options": "Dump options", - + "dump_card_image": { "label": "Dump gamecard image (XCI)", "description": "Generates a raw gamecard image. This is the option most people will want to use." }, - + "dump_certificate": { "label": "Dump gamecard certificate", "description": "The gamecard certificate is used to unequivocally identify each individual gamecard." }, - + "dump_header": { "label": "Dump gamecard header", "description": "The gamecard header holds information such as the location of the root HFS partition and the gamecard capacity.\nOnly useful for developers, preservationists and advanced users." }, - + "dump_decrypted_cardinfo": { "label": "Dump decrypted CardInfo area", "description": "The CardInfo area holds information such as the bundled system update version and the Lotus ASIC firmware version required by the gamecard.\nThis area is part of the gamecard header, but it's always encrypted.\nOnly useful for developers, preservationists and advanced users." }, - + "dump_initial_data": { "label": "Dump InitialData area", "description": "The InitialData area holds cryptographic information used by the Lotus ASIC to communicate with the gamecard.\nIt can't be dumped through normal means - it's not part of the storage areas from gamecard images.\nOnly useful for developers, preservationists and advanced users." }, - + "dump_hfs_partitions": { "label": "Dump Hash File System (HFS) partitions", "description": "Dumps data from the HFS partitions within the gamecard storage areas, in both raw and extracted forms." diff --git a/romfs/i18n/en-US/generic.json b/romfs/i18n/en-US/generic.json index 624642f..d75f546 100644 --- a/romfs/i18n/en-US/generic.json +++ b/romfs/i18n/en-US/generic.json @@ -11,6 +11,9 @@ "date": "{1:02d}/{2:02d}/{0} {3:02d}:{4:02d}:{5:02d} {6}", "__date_comment__": "{0} = Year, {1} = Month, {2} = Day, {3} = Hour, {4} = Minute, {5} = Second, {6} = AM/PM (if time_format is set to 12)", + "exception_caught": "Exception caught! ({}).", + "unknown_exception": "unknown", + "libnx_abort": "Fatal error triggered in libnx!\nError code: 0x{:08X}.", "exception_triggered": "Fatal exception triggered!\nReason: {} (0x{:X})." } diff --git a/source/core/nxdt_utils.c b/source/core/nxdt_utils.c index 953c486..06fce19 100644 --- a/source/core/nxdt_utils.c +++ b/source/core/nxdt_utils.c @@ -33,6 +33,7 @@ #include "fatfs/ff.h" /* Reference: https://docs.microsoft.com/en-us/windows/win32/fileio/filesystem-functionality-comparison#limits. */ +/* Actually expressed in bytes, not codepoints. */ #define NT_MAX_FILENAME_LENGTH 255 /* Type definitions. */ @@ -67,7 +68,7 @@ static AppletHookCookie g_systemOverclockCookie = {0}; static bool g_longRunningProcess = false; -static const char *g_sizeSuffixes[] = { "B", "KiB", "MiB", "GiB" }; +static const char *g_sizeSuffixes[] = { "B", "KiB", "MiB", "GiB", "TiB" }; static const u32 g_sizeSuffixesCount = MAX_ELEMENTS(g_sizeSuffixes); static const char g_illegalFileSystemChars[] = "\\/:*?\"<>|"; @@ -107,7 +108,7 @@ static void utilsOverclockSystemAppletHook(AppletHookType hook, void *param); static void utilsChangeHomeButtonBlockStatus(bool block); -static size_t utilsGetUtf8CodepointCount(const char *str, size_t str_size, size_t cp_limit, size_t *last_cp_pos); +static size_t utilsGetUtf8StringLimit(const char *str, size_t str_size, size_t byte_limit); bool utilsInitializeResources(const int program_argc, const char **program_argv) { @@ -804,11 +805,10 @@ char *utilsGeneratePath(const char *prefix, const char *filename, const char *ex /* Get current path element size. */ size_t element_size = (ptr2 ? (size_t)(ptr2 - ptr1) : (path_len - (size_t)(ptr1 - path))); - /* Get UTF-8 codepoint count. */ - /* Use NT_MAX_FILENAME_LENGTH as the codepoint count limit. */ - size_t last_cp_pos = 0; - size_t cp_count = utilsGetUtf8CodepointCount(ptr1, element_size, NT_MAX_FILENAME_LENGTH, &last_cp_pos); - if (cp_count > NT_MAX_FILENAME_LENGTH) + /* Get UTF-8 string limit. */ + /* Use NT_MAX_FILENAME_LENGTH as the byte count limit. */ + size_t last_cp_pos = utilsGetUtf8StringLimit(ptr1, element_size, NT_MAX_FILENAME_LENGTH); + if (last_cp_pos < element_size) { if (ptr2) { @@ -1154,25 +1154,26 @@ NX_INLINE void utilsCloseFileDescriptor(int *fd) *fd = -1; } -static size_t utilsGetUtf8CodepointCount(const char *str, size_t str_size, size_t cp_limit, size_t *last_cp_pos) +static size_t utilsGetUtf8StringLimit(const char *str, size_t str_size, size_t byte_limit) { - if (!str || !*str || !str_size || (!cp_limit && last_cp_pos) || (cp_limit && !last_cp_pos)) return 0; + if (!str || !*str || !str_size || !byte_limit) return 0; + + if (byte_limit > str_size) return str_size; u32 code = 0; ssize_t units = 0; - size_t cur_pos = 0, cp_count = 0; + size_t cur_pos = 0, last_cp_pos = 0; const u8 *str_u8 = (const u8*)str; - while(cur_pos < str_size) + while(cur_pos < str_size && cur_pos < byte_limit) { units = decode_utf8(&code, str_u8 + cur_pos); size_t new_pos = (cur_pos + (size_t)units); if (units < 0 || !code || new_pos > str_size) break; - cp_count++; cur_pos = new_pos; - if (cp_limit && last_cp_pos && cp_count < cp_limit) *last_cp_pos = cur_pos; + if (cur_pos < byte_limit) last_cp_pos = cur_pos; } - return cp_count; + return last_cp_pos; } diff --git a/source/core/title.c b/source/core/title.c index acfc33c..a670a80 100644 --- a/source/core/title.c +++ b/source/core/title.c @@ -690,6 +690,58 @@ TitleApplicationMetadata **titleGetApplicationMetadataEntries(bool is_system, u3 return app_metadata; } +TitleApplicationMetadata **titleGetGameCardApplicationMetadataEntries(u32 *out_count) +{ + u32 app_count = 0; + TitleApplicationMetadata **app_metadata = NULL, **tmp_app_metadata = NULL; + + SCOPED_LOCK(&g_titleMutex) + { + if (!g_titleInterfaceInit || !g_userMetadata || !g_userMetadataCount || !g_titleGameCardAvailable || !out_count) + { + LOG_MSG_ERROR("Invalid parameters!"); + break; + } + + bool error = false; + + for(u32 i = 0; i < g_userMetadataCount; i++) + { + TitleApplicationMetadata *cur_app_metadata = g_userMetadata[i]; + if (!cur_app_metadata) continue; + + /* Skip current metadata entry if content data for this title isn't available on the inserted gamecard. */ + if (!_titleGetInfoFromStorageByTitleId(NcmStorageId_GameCard, 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; + + /* Update output counter. */ + *out_count = app_count; + + if (!app_metadata || !app_count) LOG_MSG_ERROR("No gamecard content data found for user applications!"); + } + + return app_metadata; +} + TitleInfo *titleGetInfoFromStorageByTitleId(u8 storage_id, u64 title_id) { TitleInfo *ret = NULL; diff --git a/source/exception_handler.cpp b/source/exception_handler.cpp index 34dd050..9772edb 100644 --- a/source/exception_handler.cpp +++ b/source/exception_handler.cpp @@ -61,7 +61,7 @@ namespace nxdt::utils { if (R_FAILED(rc)) LOG_MSG_ERROR("svcQueryMemory failed! (0x%X).", rc); } -#if LOG_LEVEL == LOG_LEVEL_DEBUG +#if LOG_LEVEL <= LOG_LEVEL_ERROR static bool UnwindStack(u64 *out_stack_trace, u32 *out_stack_trace_size, size_t max_stack_trace_size, u64 cur_fp) { if (!out_stack_trace || !out_stack_trace_size || !max_stack_trace_size || !cur_fp) @@ -90,7 +90,7 @@ namespace nxdt::utils { return (*out_stack_trace_size > 0); } -#endif /* LOG_LEVEL == LOG_LEVEL_DEBUG */ +#endif /* LOG_LEVEL <= LOG_LEVEL_ERROR */ static void NORETURN AbortProgramExecution(std::string str) { @@ -164,7 +164,7 @@ extern "C" { break; } -#if LOG_LEVEL == LOG_LEVEL_DEBUG +#if LOG_LEVEL <= LOG_LEVEL_ERROR char *exception_str = NULL; size_t exception_str_size = 0; @@ -172,7 +172,7 @@ extern "C" { u64 stack_trace[STACK_TRACE_SIZE] = {0}; /* Log exception type. */ - LOG_MSG_DEBUG("*** Exception Triggered ***"); + LOG_MSG_ERROR("*** Exception Triggered ***"); EH_ADD_FMT_STR("Type: %s (0x%X)\r\n", error_desc_str.c_str(), ctx->error_desc); @@ -220,7 +220,7 @@ extern "C" { /* Free exception info string. */ if (exception_str) free(exception_str); -#endif /* LOG_LEVEL == LOG_LEVEL_DEBUG */ +#endif /* LOG_LEVEL <= LOG_LEVEL_ERROR */ /* Abort program execution. */ crash_str = (g_borealisInitialized ? i18n::getStr("generic/exception_triggered"_i18n, error_desc_str, ctx->error_desc) : \ diff --git a/source/gamecard_tab.cpp b/source/gamecard_tab.cpp index 27d4bef..6e98820 100644 --- a/source/gamecard_tab.cpp +++ b/source/gamecard_tab.cpp @@ -21,6 +21,7 @@ #include #include +#include namespace i18n = brls::i18n; /* For getStr(). */ using namespace i18n::literals; /* For _i18n. */ @@ -33,104 +34,14 @@ namespace nxdt::views this->list->setSpacing(this->list->getSpacing() / 2); this->list->setMarginBottom(20); - /* Gamecard properties table. */ - this->list->addView(new brls::Header("gamecard_tab/list/properties_table/header"_i18n)); - - this->properties_table = new FocusableTable(); - this->capacity = this->properties_table->addRow(brls::TableRowType::BODY, "gamecard_tab/list/properties_table/capacity"_i18n); - this->total_size = this->properties_table->addRow(brls::TableRowType::BODY, "gamecard_tab/list/properties_table/total_size"_i18n); - this->trimmed_size = this->properties_table->addRow(brls::TableRowType::BODY, "gamecard_tab/list/properties_table/trimmed_size"_i18n); - this->update_version = this->properties_table->addRow(brls::TableRowType::BODY, "gamecard_tab/list/properties_table/update_version"_i18n); - this->lafw_version = this->properties_table->addRow(brls::TableRowType::BODY, "gamecard_tab/list/properties_table/lafw_version"_i18n); - this->sdk_version = this->properties_table->addRow(brls::TableRowType::BODY, "gamecard_tab/list/properties_table/sdk_version"_i18n); - this->compatibility_type = this->properties_table->addRow(brls::TableRowType::BODY, "gamecard_tab/list/properties_table/compatibility_type"_i18n); - this->list->addView(this->properties_table); - - /* ListItem elements. */ - this->list->addView(new brls::Header("gamecard_tab/list/dump_options"_i18n)); - - this->dump_card_image = new brls::ListItem("gamecard_tab/list/dump_card_image/label"_i18n, "gamecard_tab/list/dump_card_image/description"_i18n); - - /*this->dump_card_image->getClickEvent()->subscribe([](brls::View *view) { - - });*/ - - this->list->addView(this->dump_card_image); - - this->dump_certificate = new brls::ListItem("gamecard_tab/list/dump_certificate/label"_i18n, "gamecard_tab/list/dump_certificate/description"_i18n); - this->list->addView(this->dump_certificate); - - this->dump_header = new brls::ListItem("gamecard_tab/list/dump_header/label"_i18n, "gamecard_tab/list/dump_header/description"_i18n); - this->list->addView(this->dump_header); - - this->dump_decrypted_cardinfo = new brls::ListItem("gamecard_tab/list/dump_decrypted_cardinfo/label"_i18n, "gamecard_tab/list/dump_decrypted_cardinfo/description"_i18n); - this->list->addView(this->dump_decrypted_cardinfo); - - this->dump_initial_data = new brls::ListItem("gamecard_tab/list/dump_initial_data/label"_i18n, "gamecard_tab/list/dump_initial_data/description"_i18n); - this->list->addView(this->dump_initial_data); - - this->dump_hfs_partitions = new brls::ListItem("gamecard_tab/list/dump_hfs_partitions/label"_i18n, "gamecard_tab/list/dump_hfs_partitions/description"_i18n); - this->list->addView(this->dump_hfs_partitions); - /* Subscribe to gamecard status event. */ this->gc_status_task_sub = this->root_view->RegisterGameCardTaskListener([this](GameCardStatus gc_status) { - /* Switch to the error layer if gamecard info hasn't been loaded. */ - if (gc_status < GameCardStatus_InsertedAndInfoLoaded) this->SwitchLayerView(true); - - switch(gc_status) - { - case GameCardStatus_NotInserted: - this->error_frame->SetMessage("gamecard_tab/error_frame/not_inserted"_i18n); - break; - case GameCardStatus_Processing: - this->error_frame->SetMessage("gamecard_tab/error_frame/processing"_i18n); - break; - case GameCardStatus_NoGameCardPatchEnabled: - this->error_frame->SetMessage("gamecard_tab/error_frame/nogc_enabled"_i18n); - break; - case GameCardStatus_LotusAsicFirmwareUpdateRequired: - this->error_frame->SetMessage("gamecard_tab/error_frame/lafw_update_required"_i18n); - break; - case GameCardStatus_InsertedAndInfoNotLoaded: - this->error_frame->SetMessage(i18n::getStr("gamecard_tab/error_frame/info_not_loaded"_i18n, GITHUB_NEW_ISSUE_URL)); - break; - case GameCardStatus_InsertedAndInfoLoaded: - { - /* Fill properties table. */ - GameCardInfo card_info = {0}; - gamecardGetDecryptedCardInfoArea(&card_info); - - this->capacity->setValue(this->GetFormattedSizeString(&gamecardGetRomCapacity)); - this->total_size->setValue(this->GetFormattedSizeString(&gamecardGetTotalSize)); - this->trimmed_size->setValue(this->GetFormattedSizeString(&gamecardGetTrimmedSize)); - - const Version *upp_version = &(card_info.upp_version); - this->update_version->setValue(fmt::format("{}.{}.{}-{}.{} (v{})", upp_version->system_version.major, upp_version->system_version.minor, upp_version->system_version.micro, \ - upp_version->system_version.major_relstep, upp_version->system_version.minor_relstep, upp_version->value)); - - u64 fw_version = card_info.fw_version; - this->lafw_version->setValue(fmt::format("{} ({})", fw_version, fw_version >= GameCardFwVersion_Count ? "generic/unknown"_i18n : gamecardGetRequiredHosVersionString(fw_version))); - - const SdkAddOnVersion *fw_mode = &(card_info.fw_mode); - this->sdk_version->setValue(fmt::format("{}.{}.{}-{} (v{})", fw_mode->major, fw_mode->minor, fw_mode->micro, fw_mode->relstep, fw_mode->value)); - - u8 compatibility_type = card_info.compatibility_type; - this->compatibility_type->setValue(fmt::format("{} ({})", \ - compatibility_type >= GameCardCompatibilityType_Count ? "generic/unknown"_i18n : gamecardGetCompatibilityTypeString(compatibility_type), \ - compatibility_type)); - - /* Switch to the list view. */ - this->SwitchLayerView(false); - - break; - } - default: - break; - } - - /* Update internal gamecard status. */ - this->gc_status = gc_status; + /* Process gamecard status. */ + this->ProcessGameCardStatus(gc_status); }); + + /* Process gamecard status. */ + this->ProcessGameCardStatus(GameCardStatus_NotInserted); } GameCardTab::~GameCardTab(void) @@ -139,6 +50,40 @@ namespace nxdt::views this->root_view->UnregisterGameCardTaskListener(this->gc_status_task_sub); } + void GameCardTab::ProcessGameCardStatus(GameCardStatus gc_status) + { + /* Switch to the error layer if gamecard info hasn't been loaded. */ + if (gc_status < GameCardStatus_InsertedAndInfoLoaded) this->SwitchLayerView(true); + + switch(gc_status) + { + case GameCardStatus_NotInserted: + this->error_frame->SetMessage("gamecard_tab/error_frame/not_inserted"_i18n); + break; + case GameCardStatus_Processing: + this->error_frame->SetMessage("gamecard_tab/error_frame/processing"_i18n); + break; + case GameCardStatus_NoGameCardPatchEnabled: + this->error_frame->SetMessage("gamecard_tab/error_frame/nogc_enabled"_i18n); + break; + case GameCardStatus_LotusAsicFirmwareUpdateRequired: + this->error_frame->SetMessage("gamecard_tab/error_frame/lafw_update_required"_i18n); + break; + case GameCardStatus_InsertedAndInfoNotLoaded: + this->error_frame->SetMessage(i18n::getStr("gamecard_tab/error_frame/info_not_loaded"_i18n, GITHUB_NEW_ISSUE_URL)); + break; + case GameCardStatus_InsertedAndInfoLoaded: + /* Update list and switch to it. */ + this->PopulateList(); + break; + default: + break; + } + + /* Update internal gamecard status. */ + this->gc_status = gc_status; + } + std::string GameCardTab::GetFormattedSizeString(GameCardSizeFunc func) { u64 size = 0; @@ -149,4 +94,117 @@ namespace nxdt::views return std::string(strbuf); } + + void GameCardTab::PopulateList(void) + { + TitleApplicationMetadata **app_metadata = NULL; + u32 app_metadata_count = 0; + GameCardInfo card_info = {0}; + + bool update_focused_view = this->IsListItemFocused(); + int focus_stack_index = this->GetFocusStackViewIndex(); + + /* Clear list. */ + this->list->clear(); + this->list->invalidate(true); + + /* Information about how to handle HOS launch errors. */ + /* TODO: remove this after we find a way to fix this issue. */ + FocusableLabel *launch_error_info = new FocusableLabel(brls::LabelStyle::DESCRIPTION, "gamecard_tab/list/launch_error_info"_i18n, true); + launch_error_info->setHorizontalAlign(NVG_ALIGN_CENTER); + this->list->addView(launch_error_info); + + /* Retrieve gamecard application metadata. */ + app_metadata = titleGetGameCardApplicationMetadataEntries(&app_metadata_count); + if (app_metadata) + { + /* Display the applications that are part of the inserted gamecard. */ + this->list->addView(new brls::Header("gamecard_tab/list/user_titles/header"_i18n)); + + /* Populate list. */ + for(u32 i = 0; i < app_metadata_count; i++) + { + TitlesTabItem *title = new TitlesTabItem(app_metadata[i], false); + title->unregisterAction(brls::Key::A); + this->list->addView(title); + } + + /* Information about how to handle user titles. */ + brls::Label *user_titles_info = new brls::Label(brls::LabelStyle::DESCRIPTION, i18n::getStr("gamecard_tab/list/user_titles/info"_i18n, \ + "root_view/tabs/user_titles"_i18n), true); + user_titles_info->setHorizontalAlign(NVG_ALIGN_CENTER); + this->list->addView(user_titles_info); + + /* Free application metadata array. */ + free(app_metadata); + } + + /* Populate gamecard properties table. */ + this->list->addView(new brls::Header("gamecard_tab/list/properties_table/header"_i18n)); + + FocusableTable *properties_table = new FocusableTable(); + brls::TableRow *capacity = properties_table->addRow(brls::TableRowType::BODY, "gamecard_tab/list/properties_table/capacity"_i18n); + brls::TableRow *total_size = properties_table->addRow(brls::TableRowType::BODY, "gamecard_tab/list/properties_table/total_size"_i18n); + brls::TableRow *trimmed_size = properties_table->addRow(brls::TableRowType::BODY, "gamecard_tab/list/properties_table/trimmed_size"_i18n); + brls::TableRow *update_version = properties_table->addRow(brls::TableRowType::BODY, "gamecard_tab/list/properties_table/update_version"_i18n); + brls::TableRow *lafw_version = properties_table->addRow(brls::TableRowType::BODY, "gamecard_tab/list/properties_table/lafw_version"_i18n); + brls::TableRow *sdk_version = properties_table->addRow(brls::TableRowType::BODY, "gamecard_tab/list/properties_table/sdk_version"_i18n); + brls::TableRow *compatibility_type = properties_table->addRow(brls::TableRowType::BODY, "gamecard_tab/list/properties_table/compatibility_type"_i18n); + + capacity->setValue(this->GetFormattedSizeString(&gamecardGetRomCapacity)); + total_size->setValue(this->GetFormattedSizeString(&gamecardGetTotalSize)); + trimmed_size->setValue(this->GetFormattedSizeString(&gamecardGetTrimmedSize)); + + gamecardGetDecryptedCardInfoArea(&card_info); + + const Version *upp_version = &(card_info.upp_version); + update_version->setValue(fmt::format("{}.{}.{}-{}.{} (v{})", upp_version->system_version.major, upp_version->system_version.minor, upp_version->system_version.micro, \ + upp_version->system_version.major_relstep, upp_version->system_version.minor_relstep, upp_version->value)); + + u64 fw_version = card_info.fw_version; + lafw_version->setValue(fmt::format("{} ({})", fw_version, fw_version >= GameCardFwVersion_Count ? "generic/unknown"_i18n : gamecardGetRequiredHosVersionString(fw_version))); + + const SdkAddOnVersion *fw_mode = &(card_info.fw_mode); + sdk_version->setValue(fmt::format("{}.{}.{}-{} (v{})", fw_mode->major, fw_mode->minor, fw_mode->micro, fw_mode->relstep, fw_mode->value)); + + u8 compat_type = card_info.compatibility_type; + compatibility_type->setValue(fmt::format("{} ({})", \ + compat_type >= GameCardCompatibilityType_Count ? "generic/unknown"_i18n : gamecardGetCompatibilityTypeString(compat_type), \ + compat_type)); + + this->list->addView(properties_table); + + /* ListItem elements. */ + this->list->addView(new brls::Header("gamecard_tab/list/dump_options"_i18n)); + + brls::ListItem *dump_card_image = new brls::ListItem("gamecard_tab/list/dump_card_image/label"_i18n, "gamecard_tab/list/dump_card_image/description"_i18n); + + /*dump_card_image->getClickEvent()->subscribe([](brls::View *view) { + + });*/ + + this->list->addView(dump_card_image); + + brls::ListItem *dump_certificate = new brls::ListItem("gamecard_tab/list/dump_certificate/label"_i18n, "gamecard_tab/list/dump_certificate/description"_i18n); + this->list->addView(dump_certificate); + + brls::ListItem *dump_header = new brls::ListItem("gamecard_tab/list/dump_header/label"_i18n, "gamecard_tab/list/dump_header/description"_i18n); + this->list->addView(dump_header); + + brls::ListItem *dump_decrypted_cardinfo = new brls::ListItem("gamecard_tab/list/dump_decrypted_cardinfo/label"_i18n, "gamecard_tab/list/dump_decrypted_cardinfo/description"_i18n); + this->list->addView(dump_decrypted_cardinfo); + + brls::ListItem *dump_initial_data = new brls::ListItem("gamecard_tab/list/dump_initial_data/label"_i18n, "gamecard_tab/list/dump_initial_data/description"_i18n); + this->list->addView(dump_initial_data); + + brls::ListItem *dump_hfs_partitions = new brls::ListItem("gamecard_tab/list/dump_hfs_partitions/label"_i18n, "gamecard_tab/list/dump_hfs_partitions/description"_i18n); + this->list->addView(dump_hfs_partitions); + + /* Update focus stack, if needed. */ + if (focus_stack_index > -1) this->UpdateFocusStackViewAtIndex(focus_stack_index, this->GetListFirstFocusableChild()); + + /* Switch to the list view. */ + this->list->invalidate(true); + this->SwitchLayerView(false, update_focused_view, focus_stack_index < 0); + } } diff --git a/source/layered_error_frame.cpp b/source/layered_error_frame.cpp index 0bc3841..b6dfbe3 100644 --- a/source/layered_error_frame.cpp +++ b/source/layered_error_frame.cpp @@ -80,17 +80,31 @@ namespace nxdt::views bool LayeredErrorFrame::UpdateFocusStackViewAtIndex(int index, brls::View *view) { std::vector *focus_stack = brls::Application::getFocusStack(); - if (!focus_stack || index < 0) return false; + if (!focus_stack || index < 0 || !view) return false; size_t focus_stack_size = focus_stack->size(); if (index >= static_cast(focus_stack_size)) return false; focus_stack->at(index) = view; - LOG_MSG_DEBUG("Focus stack updated"); + LOG_MSG_DEBUG("Focus stack updated."); return true; } + brls::View *LayeredErrorFrame::GetListFirstFocusableChild(void) + { + size_t cur_list_count = this->list->getViewsCount(); + if (!cur_list_count) return nullptr; + + for(size_t i = 0; i < cur_list_count; i++) + { + brls::View *cur_child = this->list->getChild(i); + if (cur_child && !cur_child->isHidden() && cur_child->getDefaultFocus() != nullptr) return cur_child; + } + + return nullptr; + } + void LayeredErrorFrame::SwitchLayerView(bool use_error_frame, bool update_focused_view, bool update_focus_stack) { int cur_index = this->getLayerIndex(); @@ -104,8 +118,10 @@ namespace nxdt::views if (cur_list_count) { - /* Get pointer to the first list item. */ - first_child = this->list->getChild(0); + /* Get pointer to the first focusable/unhidden list item. */ + /* Fallback to the sidebar item if there's none. */ + first_child = this->GetListFirstFocusableChild(); + if (!first_child) first_child = this->sidebar_item; /* Update focus stack information, if needed. */ if (update_focus_stack && focus_stack_index > -1) focus_stack_updated = this->UpdateFocusStackViewAtIndex(focus_stack_index, use_error_frame ? this->sidebar_item : first_child); diff --git a/source/main.cpp b/source/main.cpp index b58970e..143d268 100644 --- a/source/main.cpp +++ b/source/main.cpp @@ -23,7 +23,8 @@ #include #include -using namespace brls::i18n::literals; /* For _i18n. */ +namespace i18n = brls::i18n; /* For getStr(). */ +using namespace i18n::literals; /* For _i18n. */ bool g_borealisInitialized = false; @@ -45,21 +46,30 @@ int main(int argc, char *argv[]) if (!brls::Application::init(APP_TITLE)) return EXIT_FAILURE; g_borealisInitialized = true; - /* Check if we're running under applet mode. */ - if (utilsAppletModeCheck()) - { - /* Push crash frame with the applet mode warning. */ - brls::Application::pushView(new brls::CrashFrame("generic/applet_mode_warning"_i18n, [](brls::View *view) { - /* Swap crash frame with root view whenever the crash frame button is clicked. */ - brls::Application::swapView(new nxdt::views::RootView()); - })); - } else { - /* Push root view. */ - brls::Application::pushView(new nxdt::views::RootView()); - } + try { + /* Check if we're running under applet mode. */ + if (utilsAppletModeCheck()) + { + /* Push crash frame with the applet mode warning. */ + brls::Application::pushView(new brls::CrashFrame("generic/applet_mode_warning"_i18n, [](brls::View *view) { + /* Swap crash frame with root view whenever the crash frame button is clicked. */ + //brls::Application::swapView(new nxdt::views::RootView()); + /* TODO: restore original behavior after fixing the applet mode issues. */ + brls::Application::quit(); + })); + } else { + /* Push root view. */ + brls::Application::pushView(new nxdt::views::RootView()); + } - /* Run the application. */ - while(brls::Application::mainLoop()); + /* Run the application. */ + while(brls::Application::mainLoop()); + } catch (...) { + std::exception_ptr p = std::current_exception(); + LOG_MSG_ERROR("Exception caught! (%s).", p ? p.__cxa_exception_type()->name() : "unknown"); + brls::Application::crash(i18n::getStr("generic/exception_caught"_i18n, p ? p.__cxa_exception_type()->name() : "generic/unknown_exception"_i18n)); + while(brls::Application::mainLoop()); + } /* Exit. */ return EXIT_SUCCESS; diff --git a/source/titles_tab.cpp b/source/titles_tab.cpp index 30f5d01..bebe221 100644 --- a/source/titles_tab.cpp +++ b/source/titles_tab.cpp @@ -131,7 +131,7 @@ namespace nxdt::views try { popup = new TitlesTabPopup(app_metadata, is_system); } catch(const std::string& msg) { - LOG_MSG_DEBUG(msg.c_str()); + LOG_MSG_DEBUG("%s", msg.c_str()); if (popup) delete popup; return; } @@ -155,7 +155,7 @@ namespace nxdt::views } /* Update focus stack, if needed. */ - if (focus_stack_index > -1) this->UpdateFocusStackViewAtIndex(focus_stack_index, this->list->getChild(0)); + if (focus_stack_index > -1) this->UpdateFocusStackViewAtIndex(focus_stack_index, this->GetListFirstFocusableChild()); /* Switch to the list. */ this->list->invalidate(true);