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

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().
This commit is contained in:
Pablo Curiel 2022-07-28 00:53:52 +02:00
parent 2f85394117
commit eee1b2a771
16 changed files with 304 additions and 164 deletions

View file

@ -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

View file

@ -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)

View file

@ -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.

View file

@ -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);

View file

@ -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:

@ -1 +1 @@
Subproject commit df32a7cc42f06e0de259a8f0807e3d1690ef31f3
Subproject commit 2dcb0c6f8370ae0c5de760bf612e6bf5b54ee837

@ -1 +1 @@
Subproject commit 4d109af9b4c34a685521ddc49103784585ecc1f4
Subproject commit 315c98ecd631656b0e5839ca5e6fc293908d06f8

View file

@ -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."

View file

@ -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})."
}

View file

@ -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;
}

View file

@ -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;

View file

@ -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) : \

View file

@ -21,6 +21,7 @@
#include <nxdt_utils.h>
#include <gamecard_tab.hpp>
#include <titles_tab.hpp>
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);
}
}

View file

@ -80,17 +80,31 @@ namespace nxdt::views
bool LayeredErrorFrame::UpdateFocusStackViewAtIndex(int index, brls::View *view)
{
std::vector<brls::View*> *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<int>(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);

View file

@ -23,7 +23,8 @@
#include <scope_guard.hpp>
#include <root_view.hpp>
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;

View file

@ -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);