From 4d1b3660c3d81ae9b403d939e6aad6917c0b862b Mon Sep 17 00:00:00 2001 From: Pablo Curiel Date: Wed, 23 Jun 2021 14:27:06 -0400 Subject: [PATCH] Cherry-pick improvements from the rewrite-yoga branch. --- Makefile | 18 ++- code_templates/nsp_dumper_stor.c | 2 +- code_templates/nsp_dumper_usb.c | 2 +- include/core/nxdt_bfsar.h | 44 +++++++ include/defines.h | 1 + include/root_view.hpp | 12 +- include/tasks.hpp | 70 +++++------ romfs/i18n/en-US/root_view.json | 6 + source/core/gamecard.c | 2 +- source/core/nxdt_bfsar.c | 204 +++++++++++++++++++++++++++++++ source/core/nxdt_utils.c | 18 ++- source/core/title.c | 67 +++++----- source/root_view.cpp | 68 +++++++---- source/tasks.cpp | 168 ++++++++++--------------- 14 files changed, 460 insertions(+), 222 deletions(-) create mode 100644 include/core/nxdt_bfsar.h create mode 100644 source/core/nxdt_bfsar.c diff --git a/Makefile b/Makefile index f361805..baf4410 100644 --- a/Makefile +++ b/Makefile @@ -40,6 +40,11 @@ include $(DEVKITPRO)/libnx/switch_rules GIT_BRANCH := $(shell git rev-parse --abbrev-ref HEAD) GIT_COMMIT := $(shell git rev-parse --short HEAD) +GIT_REV := ${GIT_BRANCH}-${GIT_COMMIT} + +ifneq (, $(strip $(shell git status --porcelain 2>/dev/null))) +GIT_REV := $(GIT_REV)-dirty +endif VERSION_MAJOR := 2 VERSION_MINOR := 0 @@ -49,6 +54,7 @@ APP_TITLE := nxdumptool APP_AUTHOR := DarkMatterCore APP_VERSION := ${VERSION_MAJOR}.${VERSION_MINOR}.${VERSION_MICRO} +# TODO: remove this after the PoC builds are no longer needed. ifneq ($(origin BUILD_TYPE),undefined) APP_TITLE := ${BUILD_TYPE} endif @@ -61,11 +67,11 @@ ICON := romfs/icon/${APP_TITLE}.jpg INCLUDES := include include/core include/fatfs ROMFS := romfs -USBHSFS_PATH := $(TOPDIR)/libs/libusbhsfs - BOREALIS_PATH := libs/borealis BOREALIS_RESOURCES := romfs:/ +USBHSFS_PATH := $(TOPDIR)/libs/libusbhsfs + #--------------------------------------------------------------------------------- # options for code generation #--------------------------------------------------------------------------------- @@ -74,12 +80,11 @@ ARCH := -march=armv8-a+crc+crypto -mtune=cortex-a57 -mtp=soft -fPIE 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}\" +CFLAGS += -DGIT_BRANCH=\"${GIT_BRANCH}\" -DGIT_COMMIT=\"${GIT_COMMIT}\" -DGIT_REV=\"${GIT_REV}\" CFLAGS += -DBOREALIS_RESOURCES="\"${BOREALIS_RESOURCES}\"" CFLAGS += `aarch64-none-elf-pkg-config zlib --cflags` CFLAGS += `aarch64-none-elf-pkg-config libxml-2.0 --cflags` -CFLAGS += `aarch64-none-elf-pkg-config json-c --cflags` -CFLAGS += `aarch64-none-elf-pkg-config libturbojpeg --cflags` +#CFLAGS += `aarch64-none-elf-pkg-config json-c --cflags` CXXFLAGS := $(CFLAGS) -std=c++20 -O2 -Wno-volatile -Wno-unused-parameter @@ -87,6 +92,7 @@ ASFLAGS := -g $(ARCH) LDFLAGS := -specs=$(DEVKITPRO)/libnx/switch.specs -g $(ARCH) -Wl,-Map,$(notdir $*.map) LIBS := -lcurl -lmbedtls -lmbedx509 -lmbedcrypto -lxml2 -lz -lusbhsfs -lntfs-3g -llwext4 -lnx +#LIBS += -ljson-c #--------------------------------------------------------------------------------- # list of directories containing libraries, this must be the top level containing @@ -183,7 +189,7 @@ ifneq ($(ROMFS),) export NROFLAGS += --romfsdir=$(CURDIR)/$(ROMFS) endif -.PHONY: $(BUILD) clean clean_all all +.PHONY: $(BUILD) all clean clean_all #--------------------------------------------------------------------------------- all: $(BUILD) diff --git a/code_templates/nsp_dumper_stor.c b/code_templates/nsp_dumper_stor.c index c7be27e..3542434 100644 --- a/code_templates/nsp_dumper_stor.c +++ b/code_templates/nsp_dumper_stor.c @@ -264,7 +264,7 @@ static void nspDump(TitleInfo *title_info, u64 free_space) consolePrint("%s #%u initialize nca ctx succeeded\n", titleGetNcmContentTypeName(content_info->content_type), content_info->id_offset); // don't go any further with this nca if we can't access its fs data because it's pointless - // to do: add preload warning + // TODO: add preload warning if (cur_nca_ctx->rights_id_available && !cur_nca_ctx->titlekey_retrieved) { j++; diff --git a/code_templates/nsp_dumper_usb.c b/code_templates/nsp_dumper_usb.c index 5057fc2..cb7fbb5 100644 --- a/code_templates/nsp_dumper_usb.c +++ b/code_templates/nsp_dumper_usb.c @@ -254,7 +254,7 @@ static void dump_thread_func(void *arg) consolePrint("%s #%u initialize nca ctx succeeded\n", titleGetNcmContentTypeName(content_info->content_type), content_info->id_offset); // don't go any further with this nca if we can't access its fs data because it's pointless - // to do: add preload warning + // TODO: add preload warning if (cur_nca_ctx->rights_id_available && !cur_nca_ctx->titlekey_retrieved) { j++; diff --git a/include/core/nxdt_bfsar.h b/include/core/nxdt_bfsar.h new file mode 100644 index 0000000..593db52 --- /dev/null +++ b/include/core/nxdt_bfsar.h @@ -0,0 +1,44 @@ +/* + * nxdt_bfsar.h + * + * Copyright (c) 2020-2021, DarkMatterCore . + * + * This file is part of nxdumptool (https://github.com/DarkMatterCore/nxdumptool). + * + * nxdumptool is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * nxdumptool is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#pragma once + +#ifndef __NXDT_BFSAR_H__ +#define __NXDT_BFSAR_H__ + +#ifdef __cplusplus +extern "C" { +#endif + +/// Initializes the BFSAR interface. +bool bfsarInitialize(void); + +/// Closes the BFSAR interface. +void bfsarExit(void); + +/// Returns a pointer to the BFSAR file path. +const char *bfsarGetFilePath(void); + +#ifdef __cplusplus +} +#endif + +#endif /* __NXDT_BFSAR_H__ */ diff --git a/include/defines.h b/include/defines.h index c54a5be..3965e58 100644 --- a/include/defines.h +++ b/include/defines.h @@ -61,6 +61,7 @@ #define SPL_SYSMODULE_TID (u64)0x0100000000000028 #define ES_SYSMODULE_TID (u64)0x0100000000000033 #define SYSTEM_UPDATE_TID (u64)0x0100000000000816 +#define QLAUNCH_TID (u64)0x0100000000001000 #define FAT32_FILESIZE_LIMIT (u64)0xFFFFFFFF /* 4 GiB - 1 (4294967295 bytes). */ diff --git a/include/root_view.hpp b/include/root_view.hpp index edd965a..77e6353 100644 --- a/include/root_view.hpp +++ b/include/root_view.hpp @@ -33,18 +33,18 @@ namespace nxdt::views private: bool applet_mode = false; + brls::Label *applet_mode_lbl = nullptr; + brls::Label *time_lbl = nullptr; + brls::Label *battery_icon = nullptr, *battery_percentage = nullptr; + brls::Label *connection_icon = nullptr, *connection_status_lbl = nullptr; + nxdt::tasks::StatusInfoTask *status_info_task = nullptr; nxdt::tasks::GameCardTask *gc_status_task = nullptr; nxdt::tasks::TitleTask *title_task = nullptr; nxdt::tasks::UmsTask *ums_task = nullptr; nxdt::tasks::UsbHostTask *usb_host_task = nullptr; - nxdt::tasks::VoidEvent::Subscription status_info_task_sub; - - brls::Label *applet_mode_lbl = nullptr; - brls::Label *time_lbl = nullptr; - brls::Label *battery_icon = nullptr, *battery_percentage = nullptr; - brls::Label *connection_icon = nullptr, *connection_status_lbl = nullptr; + nxdt::tasks::StatusInfoEvent::Subscription status_info_task_sub; protected: void draw(NVGcontext* vg, int x, int y, unsigned width, unsigned height, brls::Style* style, brls::FrameContext* ctx) override; diff --git a/include/tasks.hpp b/include/tasks.hpp index 85f3a47..23503e5 100644 --- a/include/tasks.hpp +++ b/include/tasks.hpp @@ -33,32 +33,35 @@ namespace nxdt::tasks { - /* Custom event types used by the tasks defined below. */ - typedef brls::Event GameCardStatusEvent; - typedef brls::VoidEvent VoidEvent; - typedef brls::Event BooleanEvent; + /* Used to hold status info data. */ + typedef struct { + struct tm *timeinfo; + u32 charge_percentage; + PsmChargerType charger_type; + NifmInternetConnectionType connection_type; + char *ip_addr; + } StatusInfoData; - /* Custom vector type used to hold pointers to application metadata entries. */ + /* Used to hold pointers to application metadata entries. */ typedef std::vector TitleApplicationMetadataVector; - /* Custom vector type used to hold UMS devices. */ + /* Used to hold UMS devices. */ typedef std::vector UmsDeviceVector; + /* Custom event types. */ + typedef brls::Event StatusInfoEvent; + typedef brls::Event GameCardStatusEvent; + typedef brls::Event TitleEvent; + typedef brls::Event UmsEvent; + typedef brls::Event UsbHostEvent; + /* Status info task. */ + /* Its event returns a pointer to a StatusInfoData struct. */ class StatusInfoTask: public brls::RepeatingTask { private: - VoidEvent status_info_event; - - std::string cur_time = ""; - - u32 charge_percentage = 0; - PsmChargerType charger_type = PsmChargerType_Unconnected; - - NifmInternetConnectionType connection_type = (NifmInternetConnectionType)0; - u32 signal_strength = 0; - NifmInternetConnectionStatus connection_status = (NifmInternetConnectionStatus)0; - char *ip_addr = NULL; + StatusInfoEvent status_info_event; + StatusInfoData status_info_data = {0}; protected: void run(retro_time_t current_time) override; @@ -67,27 +70,23 @@ namespace nxdt::tasks StatusInfoTask(void); ~StatusInfoTask(void); - std::string GetCurrentTimeString(void); - void GetBatteryStats(u32 *out_charge_percentage, PsmChargerType *out_charger_type); - void GetNetworkStats(NifmInternetConnectionType *out_connection_type, u32 *out_signal_strength, NifmInternetConnectionStatus *out_connection_status, char **out_ip_addr); - - ALWAYS_INLINE VoidEvent::Subscription RegisterListener(VoidEvent::Callback cb) + ALWAYS_INLINE StatusInfoEvent::Subscription RegisterListener(StatusInfoEvent::Callback cb) { return this->status_info_event.subscribe(cb); } - ALWAYS_INLINE void UnregisterListener(VoidEvent::Subscription subscription) + ALWAYS_INLINE void UnregisterListener(StatusInfoEvent::Subscription subscription) { this->status_info_event.unsubscribe(subscription); } }; /* Gamecard task. */ + /* Its event returns a GameCardStatus value. */ class GameCardTask: public brls::RepeatingTask { private: GameCardStatusEvent gc_status_event; - GameCardStatus cur_gc_status = GameCardStatus_NotInserted; GameCardStatus prev_gc_status = GameCardStatus_NotInserted; @@ -110,10 +109,11 @@ namespace nxdt::tasks }; /* Title task. */ + /* Its event returns a pointer to a TitleApplicationMetadataVector with metadata for user titles (system titles don't change at runtime). */ class TitleTask: public brls::RepeatingTask { private: - VoidEvent title_event; + TitleEvent title_event; TitleApplicationMetadataVector system_metadata; TitleApplicationMetadataVector user_metadata; @@ -127,24 +127,26 @@ namespace nxdt::tasks TitleTask(void); ~TitleTask(void); + /* Intentionally left here to let system titles views retrieve metadata. */ TitleApplicationMetadataVector* GetApplicationMetadata(bool is_system); - ALWAYS_INLINE VoidEvent::Subscription RegisterListener(VoidEvent::Callback cb) + ALWAYS_INLINE TitleEvent::Subscription RegisterListener(TitleEvent::Callback cb) { return this->title_event.subscribe(cb); } - ALWAYS_INLINE void UnregisterListener(VoidEvent::Subscription subscription) + ALWAYS_INLINE void UnregisterListener(TitleEvent::Subscription subscription) { this->title_event.unsubscribe(subscription); } }; /* USB Mass Storage task. */ + /* Its event returns a pointer to a UmsDeviceVector. */ class UmsTask: public brls::RepeatingTask { private: - VoidEvent ums_event; + UmsEvent ums_event; UmsDeviceVector ums_devices; @@ -157,14 +159,12 @@ namespace nxdt::tasks UmsTask(void); ~UmsTask(void); - UmsDeviceVector* GetUmsDevices(void); - - ALWAYS_INLINE VoidEvent::Subscription RegisterListener(VoidEvent::Callback cb) + ALWAYS_INLINE UmsEvent::Subscription RegisterListener(UmsEvent::Callback cb) { return this->ums_event.subscribe(cb); } - ALWAYS_INLINE void UnregisterListener(VoidEvent::Subscription subscription) + ALWAYS_INLINE void UnregisterListener(UmsEvent::Subscription subscription) { this->ums_event.unsubscribe(subscription); } @@ -174,7 +174,7 @@ namespace nxdt::tasks class UsbHostTask: public brls::RepeatingTask { private: - BooleanEvent usb_host_event; + UsbHostEvent usb_host_event; bool cur_usb_host_status = false; bool prev_usb_host_status = false; @@ -186,12 +186,12 @@ namespace nxdt::tasks UsbHostTask(void); ~UsbHostTask(void); - ALWAYS_INLINE BooleanEvent::Subscription RegisterListener(BooleanEvent::Callback cb) + ALWAYS_INLINE UsbHostEvent::Subscription RegisterListener(UsbHostEvent::Callback cb) { return this->usb_host_event.subscribe(cb); } - ALWAYS_INLINE void UnregisterListener(BooleanEvent::Subscription subscription) + ALWAYS_INLINE void UnregisterListener(UsbHostEvent::Subscription subscription) { this->usb_host_event.unsubscribe(subscription); } diff --git a/romfs/i18n/en-US/root_view.json b/romfs/i18n/en-US/root_view.json index e99bba0..8960912 100644 --- a/romfs/i18n/en-US/root_view.json +++ b/romfs/i18n/en-US/root_view.json @@ -1,7 +1,13 @@ { + "__comment__": "Comments about how specific fields work use keys that follow the '__{field}_comment__' format. These don't have to be replicated in your translation files.", + "applet_mode": "\uE8B2 Applet Mode \uE8B2", + "time_format": "12", + "__time_format_comment__": "Use 12 for a 12-hour clock, or 24 for a 24-hour clock", + "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)", "not_connected": "Not connected", diff --git a/source/core/gamecard.c b/source/core/gamecard.c index 97d8d17..4f5ae4a 100644 --- a/source/core/gamecard.c +++ b/source/core/gamecard.c @@ -961,7 +961,7 @@ static bool gamecardGetHandleAndStorage(u32 partition) NX_INLINE void gamecardCloseHandle(void) { - /* TO DO: find a way to properly close a gamecard handle. */ + /* TODO: find a way to properly close a gamecard handle. */ if (!g_gameCardHandle.value) return; svcCloseHandle(g_gameCardHandle.value); g_gameCardHandle.value = 0; diff --git a/source/core/nxdt_bfsar.c b/source/core/nxdt_bfsar.c new file mode 100644 index 0000000..934f5a7 --- /dev/null +++ b/source/core/nxdt_bfsar.c @@ -0,0 +1,204 @@ +/* + * nxdt_bfsar.c + * + * Copyright (c) 2020-2021, DarkMatterCore . + * + * This file is part of nxdumptool (https://github.com/DarkMatterCore/nxdumptool). + * + * nxdumptool is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * nxdumptool is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "nxdt_utils.h" +#include "nxdt_bfsar.h" +#include "romfs.h" +#include "title.h" + +#define BFSAR_FILENAME "qlaunch.bfsar" +#define BFSAR_ROMFS_PATH "/sound/" BFSAR_FILENAME + +/* Global variables. */ + +static Mutex g_bfsarMutex = 0; +static bool g_bfsarInterfaceInit = false; + +static char g_bfsarPath[FS_MAX_PATH] = {0}; + +bool bfsarInitialize(void) +{ + bool use_root = true; + const char *launch_path = utilsGetLaunchPath(); + char *ptr1 = NULL, *ptr2 = NULL; + + TitleInfo *title_info = NULL; + + NcaContext *nca_ctx = NULL; + + RomFileSystemContext romfs_ctx = {0}; + RomFileSystemFileEntry *romfs_file_entry = NULL; + + FILE *bfsar_file = NULL; + u8 *bfsar_data = NULL; + size_t bfsar_size = 0, wr = 0; + + bool ret = false; + + SCOPED_LOCK(&g_bfsarMutex) + { + ret = g_bfsarInterfaceInit; + if (ret) break; + + /* Generate BFSAR file path. */ + if (launch_path) + { + ptr1 = strchr(launch_path, '/'); + ptr2 = strrchr(launch_path, '/'); + + if (ptr1 && ptr2 && ptr1 != ptr2) + { + /* Create BFSAR file in the current working directory. */ + snprintf(g_bfsarPath, sizeof(g_bfsarPath), "%.*s" BFSAR_FILENAME, (int)((ptr2 - ptr1) + 1), ptr1); + use_root = false; + } + } + + /* Create BFSAR file in the SD card root directory. */ + if (use_root) sprintf(g_bfsarPath, "/" BFSAR_FILENAME); + + LOG_MSG("BFSAR path: \"%s\".", g_bfsarPath); + + /* Check if the BFSAR file is already available and not empty. */ + bfsar_file = fopen(g_bfsarPath, "rb"); + if (bfsar_file) + { + fseek(bfsar_file, 0, SEEK_END); + bfsar_size = ftell(bfsar_file); + if (bfsar_size) + { + ret = g_bfsarInterfaceInit = true; + break; + } + } + + /* Get title info. */ + if (!(title_info = titleGetInfoFromStorageByTitleId(NcmStorageId_BuiltInSystem, QLAUNCH_TID))) + { + LOG_MSG("Failed to get title info for qlaunch!"); + break; + } + + /* Allocate memory for a temporary NCA context. */ + nca_ctx = calloc(1, sizeof(NcaContext)); + if (!nca_ctx) + { + LOG_MSG("Failed to allocate memory for temporary NCA context!"); + break; + } + + /* Initialize NCA context. */ + if (!ncaInitializeContext(nca_ctx, NcmStorageId_BuiltInSystem, 0, titleGetContentInfoByTypeAndIdOffset(title_info, NcmContentType_Program, 0), NULL)) + { + LOG_MSG("Failed to initialize qlaunch Program NCA context!"); + break; + } + + /* Initialize RomFS context. */ + if (!romfsInitializeContext(&romfs_ctx, &(nca_ctx->fs_ctx[1]))) + { + LOG_MSG("Failed to initialize RomFS context for qlaunch Program NCA!"); + break; + } + + /* Get RomFS file entry. */ + if (!(romfs_file_entry = romfsGetFileEntryByPath(&romfs_ctx, BFSAR_ROMFS_PATH))) + { + LOG_MSG("Failed to retrieve RomFS file entry for \"" BFSAR_ROMFS_PATH "\"!"); + break; + } + + /* Check file size. */ + bfsar_size = romfs_file_entry->size; + if (!bfsar_size) + { + LOG_MSG("File size for qlaunch's \"" BFSAR_ROMFS_PATH "\" is zero!"); + break; + } + + /* Allocate memory for BFSAR data. */ + if (!(bfsar_data = malloc(bfsar_size))) + { + LOG_MSG("Failed to allocate 0x%lX bytes for qlaunch's \"" BFSAR_ROMFS_PATH "\"!", bfsar_size); + break; + } + + /* Read BFSAR data. */ + if (!romfsReadFileEntryData(&romfs_ctx, romfs_file_entry, bfsar_data, bfsar_size, 0)) + { + LOG_MSG("Failed to read 0x%lX bytes long \"" BFSAR_ROMFS_PATH "\" from qlaunch!", bfsar_size); + break; + } + + /* Create BFSAR file. */ + bfsar_file = fopen(g_bfsarPath, "wb"); + if (!bfsar_file) + { + LOG_MSG("Failed to open \"%s\" for writing!", g_bfsarPath); + break; + } + + /* Write BFSAR data. */ + wr = fwrite(bfsar_data, 1, bfsar_size, bfsar_file); + if (wr != bfsar_size) + { + LOG_MSG("Failed to write 0x%lX bytes block to \"%s\"!", bfsar_size, g_bfsarPath); + break; + } + + /* Update flags. */ + ret = g_bfsarInterfaceInit = true; + } + + if (bfsar_file) fclose(bfsar_file); + + if (bfsar_data) free(bfsar_data); + + romfsFreeContext(&romfs_ctx); + + if (nca_ctx) free(nca_ctx); + + titleFreeTitleInfo(&title_info); + + return ret; +} + +void bfsarExit(void) +{ + SCOPED_LOCK(&g_bfsarMutex) + { + /* Clear BFSAR file path. */ + *g_bfsarPath = '\0'; + g_bfsarInterfaceInit = false; + } +} + +const char *bfsarGetFilePath(void) +{ + const char *ret = NULL; + + SCOPED_TRY_LOCK(&g_bfsarMutex) + { + if (g_bfsarInterfaceInit) ret = (const char*)g_bfsarPath; + } + + return ret; +} diff --git a/source/core/nxdt_utils.c b/source/core/nxdt_utils.c index 9024f20..322d470 100644 --- a/source/core/nxdt_utils.c +++ b/source/core/nxdt_utils.c @@ -29,6 +29,7 @@ #include "usb.h" #include "title.h" #include "bfttf.h" +#include "nxdt_bfsar.h" #include "fatfs/ff.h" /* Reference: https://docs.microsoft.com/en-us/windows/win32/fileio/filesystem-functionality-comparison#limits. */ @@ -86,7 +87,7 @@ static size_t utilsGetUtf8CodepointCount(const char *str, size_t str_size, size_ bool utilsInitializeResources(const int program_argc, const char **program_argv) { Result rc = 0; - bool ret = false; + bool ret = false, flag = false; SCOPED_LOCK(&g_resourcesMutex) { @@ -105,7 +106,7 @@ bool utilsInitializeResources(const int program_argc, const char **program_argv) /* Create logfile. */ logWriteStringToLogFile("________________________________________________________________\r\n"); - LOG_MSG(APP_TITLE " v%u.%u.%u starting (" GIT_COMMIT "). Built on " __DATE__ " - " __TIME__ ".", VERSION_MAJOR, VERSION_MINOR, VERSION_MICRO); + LOG_MSG(APP_TITLE " v%u.%u.%u starting (" GIT_REV "). Built on " __DATE__ " - " __TIME__ ".", VERSION_MAJOR, VERSION_MINOR, VERSION_MICRO); if (g_appLaunchPath) LOG_MSG("Launch path: \"%s\".", g_appLaunchPath); /* Log Horizon OS version. */ @@ -153,10 +154,18 @@ bool utilsInitializeResources(const int program_argc, const char **program_argv) /* Initialize BFTTF interface. */ if (!bfttfInitialize()) break; + /* Initialize BFSAR interface. */ + //if (!bfsarInitialize()) break; + /* Mount eMMC BIS System partition. */ if (!utilsMountEmmcBisSystemPartitionStorage()) break; + /* Enable video recording. */ + rc = appletIsGamePlayRecordingSupported(&flag); + if (R_SUCCEEDED(rc) && flag) appletInitializeGamePlayRecording(); + /* Disable screen dimming and auto sleep. */ + /* TODO: only use this function right before starting a dump procedure - make sure to handle power button presses as well. */ appletSetMediaPlaybackState(true); /* Overclock system. */ @@ -212,6 +221,9 @@ void utilsCloseResources(void) /* Unmount eMMC BIS System partition. */ utilsUnmountEmmcBisSystemPartitionStorage(); + /* Deinitialize BFSAR interface. */ + bfsarExit(); + /* Deinitialize BFTTF interface. */ bfttfExit(); @@ -832,7 +844,7 @@ static void utilsOverclockSystemAppletHook(AppletHookType hook, void *param) if (hook != AppletHookType_OnOperationMode && hook != AppletHookType_OnPerformanceMode) return; - /* TO DO: read config here to actually know the value to use with utilsOverclockSystem. */ + /* TODO: read config here to actually know the value to use with utilsOverclockSystem. */ utilsOverclockSystem(true); } diff --git a/source/core/title.c b/source/core/title.c index 411e118..8683533 100644 --- a/source/core/title.c +++ b/source/core/title.c @@ -972,17 +972,21 @@ char *titleGenerateGameCardFileName(u8 name_convention, u8 illegal_char_replace_ TitleInfo **titles = title_storage->titles; u32 title_count = title_storage->title_count; - if (!g_titleInterfaceInit || !g_titleGameCardAvailable || !titles || !title_count || name_convention > TitleFileNameConvention_IdAndVersionOnly || \ + GameCardHeader gc_header = {0}; + size_t cur_filename_len = 0; + char app_name[0x400] = {0}; + bool error = false; + + if (!g_titleInterfaceInit || !g_titleGameCardAvailable || name_convention > TitleFileNameConvention_IdAndVersionOnly || \ (name_convention == TitleFileNameConvention_Full && illegal_char_replace_type > TitleFileNameIllegalCharReplaceType_KeepAsciiCharsOnly)) { LOG_MSG("Invalid parameters!"); break; } - GameCardHeader gc_header = {0}; - size_t cur_filename_len = 0; - char app_name[0x400] = {0}; - bool error = false; + /* Check if the gamecard title storage is empty. */ + /* This is especially true for Kiosk / Quest gamecards. */ + if (!titles || !title_count) goto fallback; for(u32 i = 0; i < title_count; i++) { @@ -1058,12 +1062,12 @@ char *titleGenerateGameCardFileName(u8 name_convention, u8 illegal_char_replace_ cur_filename_len += app_name_len; } +fallback: if (!filename && !error) { LOG_MSG("Error: the inserted gamecard doesn't hold any user applications!"); /* Fallback string if no applications can be found. */ - /* This function is guaranteed to fail with Kiosk / Quest gamecards, so that's why this is needed. */ sprintf(app_name, "gamecard"); if (gamecardGetHeader(&gc_header)) @@ -1074,6 +1078,7 @@ char *titleGenerateGameCardFileName(u8 name_convention, u8 illegal_char_replace_ } filename = strdup(app_name); + if (!filename) LOG_MSG("Failed to duplicate fallback filename!"); } } @@ -1093,41 +1098,29 @@ const char *titleGetNcmContentMetaTypeName(u8 content_meta_type) NX_INLINE void titleFreeApplicationMetadata(void) { - if (g_systemMetadata) + for(u8 i = 0; i < 2; i++) { - for(u32 i = 0; i < g_systemMetadataCount; i++) - { - TitleApplicationMetadata *cur_app_metadata = g_systemMetadata[i]; - if (cur_app_metadata) - { - if (cur_app_metadata->icon) free(cur_app_metadata->icon); - free(cur_app_metadata); - } - } + TitleApplicationMetadata **cached_app_metadata = (i == 0 ? g_systemMetadata : g_userMetadata); + u32 cached_app_metadata_count = (i == 0 ? g_systemMetadataCount : g_userMetadataCount); - free(g_systemMetadata); - g_systemMetadata = NULL; + if (cached_app_metadata) + { + for(u32 j = 0; j < cached_app_metadata_count; j++) + { + TitleApplicationMetadata *cur_app_metadata = cached_app_metadata[j]; + if (cur_app_metadata) + { + if (cur_app_metadata->icon) free(cur_app_metadata->icon); + free(cur_app_metadata); + } + } + + free(cached_app_metadata); + } } - g_systemMetadataCount = 0; - - if (g_userMetadata) - { - for(u32 i = 0; i < g_userMetadataCount; i++) - { - TitleApplicationMetadata *cur_app_metadata = g_userMetadata[i]; - if (cur_app_metadata) - { - if (cur_app_metadata->icon) free(cur_app_metadata->icon); - free(cur_app_metadata); - } - } - - free(g_userMetadata); - g_userMetadata = NULL; - } - - g_userMetadataCount = 0; + g_systemMetadata = g_userMetadata = NULL; + g_systemMetadataCount = g_userMetadataCount = 0; } static bool titleReallocateApplicationMetadata(u32 extra_app_count, bool is_system, bool free_entries) diff --git a/source/root_view.cpp b/source/root_view.cpp index 11d164b..6a67418 100644 --- a/source/root_view.cpp +++ b/source/root_view.cpp @@ -27,12 +27,18 @@ //#include #include -using namespace brls::i18n::literals; /* For _i18n. */ +namespace i18n = brls::i18n; /* For getStr(). */ +using namespace i18n::literals; /* For _i18n. */ namespace nxdt::views { RootView::RootView(void) : brls::TabFrame() { + /* Set UI properties. */ + this->setTitle(APP_TITLE); + this->setIcon(BOREALIS_ASSET("icon/" APP_TITLE ".jpg")); + this->setFooterText("v" APP_VERSION); + /* Check if we're running under applet mode. */ this->applet_mode = utilsAppletModeCheck(); @@ -65,11 +71,6 @@ namespace nxdt::views this->ums_task = new nxdt::tasks::UmsTask(); this->usb_host_task = new nxdt::tasks::UsbHostTask(); - /* Set UI properties. */ - this->setTitle(APP_TITLE); - this->setIcon(BOREALIS_ASSET("icon/" APP_TITLE ".jpg")); - this->setFooterText("v" APP_VERSION); - /* Add tabs. */ this->addTab("root_view/tabs/gamecard"_i18n, new GameCardTab(this->gc_status_task)); this->addSeparator(); @@ -81,20 +82,34 @@ namespace nxdt::views this->addTab("root_view/tabs/about"_i18n, new AboutTab()); /* Subscribe to status info event. */ - this->status_info_task_sub = this->status_info_task->RegisterListener([this](void) { - u32 charge_percentage = 0; - PsmChargerType charger_type = PsmChargerType_Unconnected; - - NifmInternetConnectionType connection_type = (NifmInternetConnectionType)0; - u32 signal_strength = 0; - NifmInternetConnectionStatus connection_status = (NifmInternetConnectionStatus)0; - char *ip_addr = NULL; - + this->status_info_task_sub = this->status_info_task->RegisterListener([this](const nxdt::tasks::StatusInfoData *status_info_data) { /* Update time label. */ - this->time_lbl->setText(this->status_info_task->GetCurrentTimeString()); + bool is_am = true; + struct tm *timeinfo = status_info_data->timeinfo; + + timeinfo->tm_mon++; + timeinfo->tm_year += 1900; + + if ("root_view/time_format"_i18n.compare("12") == 0) + { + /* Adjust time for 12-hour clock. */ + if (timeinfo->tm_hour > 12) + { + timeinfo->tm_hour -= 12; + is_am = false; + } else + if (!timeinfo->tm_hour) + { + timeinfo->tm_hour = 12; + } + } + + this->time_lbl->setText(i18n::getStr("root_view/date"_i18n, timeinfo->tm_year, timeinfo->tm_mon, timeinfo->tm_mday, timeinfo->tm_hour, timeinfo->tm_min, timeinfo->tm_sec, \ + is_am ? "AM" : "PM")); /* Update battery labels. */ - this->status_info_task->GetBatteryStats(&charge_percentage, &charger_type); + u32 charge_percentage = status_info_data->charge_percentage; + PsmChargerType charger_type = status_info_data->charger_type; this->battery_icon->setText(charger_type != PsmChargerType_Unconnected ? "\uE1A3" : (charge_percentage <= 15 ? "\uE19C" : "\uE1A4")); this->battery_icon->setColor(charger_type != PsmChargerType_Unconnected ? nvgRGB(0, 255, 0) : (charge_percentage <= 15 ? nvgRGB(255, 0, 0) : brls::Application::getTheme()->textColor)); @@ -102,7 +117,8 @@ namespace nxdt::views this->battery_percentage->setText(fmt::format("{}%", charge_percentage)); /* Update network label. */ - this->status_info_task->GetNetworkStats(&connection_type, &signal_strength, &connection_status, &ip_addr); + NifmInternetConnectionType connection_type = status_info_data->connection_type; + char *ip_addr = status_info_data->ip_addr; this->connection_icon->setText(!connection_type ? "\uE195" : (connection_type == NifmInternetConnectionType_WiFi ? "\uE63E" : "\uE8BE")); this->connection_status_lbl->setText(ip_addr ? std::string(ip_addr) : "root_view/not_connected"_i18n); @@ -117,6 +133,12 @@ namespace nxdt::views /* Unregister status info task listener. */ this->status_info_task->UnregisterListener(this->status_info_task_sub); + /* Stop background tasks. */ + this->gc_status_task->stop(); + this->title_task->stop(); + this->ums_task->stop(); + this->usb_host_task->stop(); + /* Destroy labels. */ delete this->applet_mode_lbl; delete this->time_lbl; @@ -124,16 +146,12 @@ namespace nxdt::views delete this->battery_percentage; delete this->connection_icon; delete this->connection_status_lbl; - - /* Stop background tasks. */ - this->gc_status_task->stop(); - this->title_task->stop(); - this->ums_task->stop(); - this->usb_host_task->stop(); } void RootView::draw(NVGcontext* vg, int x, int y, unsigned width, unsigned height, brls::Style* style, brls::FrameContext* ctx) { + brls::AppletFrame::draw(vg, x, y, width, height, style, ctx); + if (this->applet_mode) this->applet_mode_lbl->frame(ctx); this->time_lbl->frame(ctx); @@ -143,8 +161,6 @@ namespace nxdt::views this->connection_icon->frame(ctx); this->connection_status_lbl->frame(ctx); - - brls::AppletFrame::draw(vg, x, y, width, height, style, ctx); } void RootView::layout(NVGcontext* vg, brls::Style* style, brls::FontStash* stash) diff --git a/source/tasks.cpp b/source/tasks.cpp index 5205468..26bb4cb 100644 --- a/source/tasks.cpp +++ b/source/tasks.cpp @@ -23,16 +23,13 @@ #include #include -#define NXDT_TASK_INTERVAL 100 /* 100 ms. */ - -namespace i18n = brls::i18n; /* For getStr(). */ -using namespace i18n::literals; /* For _i18n. */ +#define NXDT_TASK_INTERVAL 250 /* 250 ms. */ namespace nxdt::tasks { /* Status info task. */ - StatusInfoTask::StatusInfoTask(void) : brls::RepeatingTask(1000) + StatusInfoTask::StatusInfoTask(void) : brls::RepeatingTask(NXDT_TASK_INTERVAL) { brls::RepeatingTask::start(); brls::Logger::debug("Status info task started."); @@ -40,9 +37,6 @@ namespace nxdt::tasks StatusInfoTask::~StatusInfoTask(void) { - /* Clear current time string. */ - this->cur_time.clear(); - brls::Logger::debug("Status info task stopped."); } @@ -50,72 +44,37 @@ namespace nxdt::tasks { brls::RepeatingTask::run(current_time); + StatusInfoData *status_info_data = &(this->status_info_data); + /* Get current time. */ - bool is_am = true; time_t unix_time = time(NULL); - struct tm *timeinfo = localtime(&unix_time); - - if (timeinfo->tm_hour > 12) - { - timeinfo->tm_hour -= 12; - is_am = false; - } else - if (!timeinfo->tm_hour) - { - timeinfo->tm_hour = 12; - } - - timeinfo->tm_mon++; - timeinfo->tm_year += 1900; - - this->cur_time.clear(); - this->cur_time = i18n::getStr("root_view/date"_i18n, timeinfo->tm_year, timeinfo->tm_mon, timeinfo->tm_mday, timeinfo->tm_hour, timeinfo->tm_min, timeinfo->tm_sec, is_am ? "AM" : "PM"); + status_info_data->timeinfo = localtime(&unix_time); /* Get battery stats. */ - psmGetBatteryChargePercentage(&(this->charge_percentage)); - psmGetChargerType(&(this->charger_type)); + psmGetBatteryChargePercentage(&(status_info_data->charge_percentage)); + psmGetChargerType(&(status_info_data->charger_type)); /* Get network connection status. */ - Result rc = nifmGetInternetConnectionStatus(&(this->connection_type), &(this->signal_strength), &(this->connection_status)); + u32 signal_strength = 0; + NifmInternetConnectionStatus connection_status = (NifmInternetConnectionStatus)0; + + Result rc = nifmGetInternetConnectionStatus(&(status_info_data->connection_type), &signal_strength, &connection_status); if (R_SUCCEEDED(rc)) { - if (this->connection_type && this->connection_status == NifmInternetConnectionStatus_Connected) + if (status_info_data->connection_type && connection_status == NifmInternetConnectionStatus_Connected) { struct in_addr addr = { .s_addr = 0 }; nifmGetCurrentIpAddress(&(addr.s_addr)); - this->ip_addr = inet_ntoa(addr); + status_info_data->ip_addr = inet_ntoa(addr); } else { - this->ip_addr = NULL; + status_info_data->ip_addr = NULL; } } else { - this->connection_type = (NifmInternetConnectionType)0; - this->signal_strength = 0; - this->connection_status = (NifmInternetConnectionStatus)0; - this->ip_addr = NULL; + status_info_data->connection_type = (NifmInternetConnectionType)0; + status_info_data->ip_addr = NULL; } - this->status_info_event.fire(); - } - - std::string StatusInfoTask::GetCurrentTimeString(void) - { - return this->cur_time; - } - - void StatusInfoTask::GetBatteryStats(u32 *out_charge_percentage, PsmChargerType *out_charger_type) - { - if (!out_charge_percentage || !out_charger_type) return; - *out_charge_percentage = this->charge_percentage; - *out_charger_type = this->charger_type; - } - - void StatusInfoTask::GetNetworkStats(NifmInternetConnectionType *out_connection_type, u32 *out_signal_strength, NifmInternetConnectionStatus *out_connection_status, char **out_ip_addr) - { - if (!out_connection_type || !out_signal_strength || !out_connection_status || !out_ip_addr) return; - *out_connection_type = this->connection_type; - *out_signal_strength = this->signal_strength; - *out_connection_status = this->connection_status; - *out_ip_addr = this->ip_addr; + this->status_info_event.fire(status_info_data); } /* Gamecard task. */ @@ -168,28 +127,6 @@ namespace nxdt::tasks brls::Logger::debug("Title task stopped."); } - void TitleTask::PopulateApplicationMetadataVector(bool is_system) - { - TitleApplicationMetadata **app_metadata = NULL; - u32 app_metadata_count = 0; - - /* Get pointer to output vector. */ - TitleApplicationMetadataVector *vector = (is_system ? &(this->system_metadata) : &(this->user_metadata)); - if (vector->size()) vector->clear(); - - /* Get application metadata entries. */ - app_metadata = titleGetApplicationMetadataEntries(is_system, &app_metadata_count); - if (!app_metadata) return; - - /* Fill output vector. */ - for(u32 i = 0; i < app_metadata_count; i++) vector->push_back(app_metadata[i]); - - /* Free application metadata array. */ - free(app_metadata); - - brls::Logger::debug("Retrieved {} {} metadata {}.", app_metadata_count, is_system ? "system" : "user", app_metadata_count == 1 ? "entry" : "entries"); - } - void TitleTask::run(retro_time_t current_time) { brls::RepeatingTask::run(current_time); @@ -200,7 +137,7 @@ namespace nxdt::tasks this->PopulateApplicationMetadataVector(false); /* Fire task event. */ - this->title_event.fire(); + this->title_event.fire(&(this->user_metadata)); brls::Logger::debug("Title info updated."); } } @@ -210,6 +147,29 @@ namespace nxdt::tasks return (is_system ? &(this->system_metadata) : &(this->user_metadata)); } + void TitleTask::PopulateApplicationMetadataVector(bool is_system) + { + TitleApplicationMetadata **app_metadata = NULL; + u32 app_metadata_count = 0; + + /* Get pointer to output vector. */ + TitleApplicationMetadataVector *vector = (is_system ? &(this->system_metadata) : &(this->user_metadata)); + vector->clear(); + + /* Get application metadata entries. */ + app_metadata = titleGetApplicationMetadataEntries(is_system, &app_metadata_count); + if (app_metadata) + { + /* Fill output vector. */ + for(u32 i = 0; i < app_metadata_count; i++) vector->push_back(app_metadata[i]); + + /* Free application metadata array. */ + free(app_metadata); + } + + brls::Logger::debug("Retrieved {} {} metadata {}.", app_metadata_count, is_system ? "system" : "user", app_metadata_count == 1 ? "entry" : "entries"); + } + /* USB Mass Storage task. */ UmsTask::UmsTask(void) : brls::RepeatingTask(NXDT_TASK_INTERVAL) @@ -226,27 +186,6 @@ namespace nxdt::tasks brls::Logger::debug("UMS task stopped."); } - void UmsTask::PopulateUmsDeviceVector(void) - { - UsbHsFsDevice *ums_devices = NULL; - u32 ums_device_count = 0; - - /* Clear UMS device vector (if needed). */ - if (this->ums_devices.size()) this->ums_devices.clear(); - - /* Get UMS devices. */ - ums_devices = umsGetDevices(&ums_device_count); - if (!ums_devices) return; - - /* Fill UMS device vector. */ - for(u32 i = 0; i < ums_device_count; i++) this->ums_devices.push_back(ums_devices[i]); - - /* Free UMS devices array. */ - free(ums_devices); - - brls::Logger::debug("Retrieved info for {} UMS {}.", ums_device_count, ums_device_count == 1 ? "device" : "devices"); - } - void UmsTask::run(retro_time_t current_time) { brls::RepeatingTask::run(current_time); @@ -257,14 +196,31 @@ namespace nxdt::tasks this->PopulateUmsDeviceVector(); /* Fire task event. */ - this->ums_event.fire(); + this->ums_event.fire(&(this->ums_devices)); brls::Logger::debug("UMS device info updated."); } } - UmsDeviceVector* UmsTask::GetUmsDevices(void) + void UmsTask::PopulateUmsDeviceVector(void) { - return &(this->ums_devices); + UsbHsFsDevice *ums_devices = NULL; + u32 ums_device_count = 0; + + /* Clear UMS device vector. */ + this->ums_devices.clear(); + + /* Get UMS devices. */ + ums_devices = umsGetDevices(&ums_device_count); + if (ums_devices) + { + /* Fill UMS device vector. */ + for(u32 i = 0; i < ums_device_count; i++) this->ums_devices.push_back(ums_devices[i]); + + /* Free UMS devices array. */ + free(ums_devices); + } + + brls::Logger::debug("Retrieved info for {} UMS {}.", ums_device_count, ums_device_count == 1 ? "device" : "devices"); } /* USB host device connection task. */ @@ -287,8 +243,8 @@ namespace nxdt::tasks this->cur_usb_host_status = usbIsReady(); if (this->cur_usb_host_status != this->prev_usb_host_status) { - this->usb_host_event.fire(this->cur_usb_host_status); this->prev_usb_host_status = this->cur_usb_host_status; + this->usb_host_event.fire(this->cur_usb_host_status); brls::Logger::debug("USB host status change triggered: {}.", this->cur_usb_host_status); } }