From 64ab1705bcf9ac0978f83d10eec92aa082f6307f Mon Sep 17 00:00:00 2001 From: Pablo Curiel Date: Wed, 21 Jul 2021 11:04:18 -0400 Subject: [PATCH] Implement JSON configuration handler. * Thread-safe. * Provides getter/setter functions for the data types used by nxdumptool's configuration. * Each setter function writes the modified JSON configuration back to the SD card. * Configuration is validated on interface initialization. If validation fails, a default JSON template is loaded from the application's RomFS and written back to the SD card. Other changes: * Implement directory creation. * Moved more preprocessor definitions to defines.h. * Replaced strtok() calls throughout the code with strtok_r() to guarantee thread-safety. --- Makefile | 5 +- include/core/config.h | 63 ++++++ include/core/nxdt_includes.h | 3 + include/core/nxdt_utils.h | 4 + include/defines.h | 33 +++- romfs/default_config.json | 29 +++ source/core/cert.c | 20 +- source/core/config.c | 372 +++++++++++++++++++++++++++++++++++ source/core/keys.c | 2 - source/core/nxdt_log.c | 18 +- source/core/nxdt_utils.c | 86 ++++++-- source/core/romfs.c | 8 +- source/core/title.c | 2 +- source/titles_tab.cpp | 10 +- todo.txt | 5 + 15 files changed, 600 insertions(+), 60 deletions(-) create mode 100644 include/core/config.h create mode 100644 romfs/default_config.json create mode 100644 source/core/config.c diff --git a/Makefile b/Makefile index e26afb0..c938779 100644 --- a/Makefile +++ b/Makefile @@ -84,15 +84,14 @@ CFLAGS += -DGIT_BRANCH=\"${GIT_BRANCH}\" -DGIT_COMMIT=\"${GIT_COMMIT}\" -DGIT_R 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 json-c --cflags` 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) -LIBS := -lcurl -lmbedtls -lmbedx509 -lmbedcrypto -lxml2 -lz -lusbhsfs -lntfs-3g -llwext4 -lnx -#LIBS += -ljson-c +LIBS := -lcurl -lmbedtls -lmbedx509 -lmbedcrypto -lxml2 -ljson-c -lz -lusbhsfs -lntfs-3g -llwext4 -lnx #--------------------------------------------------------------------------------- # list of directories containing libraries, this must be the top level containing diff --git a/include/core/config.h b/include/core/config.h new file mode 100644 index 0000000..b97e6db --- /dev/null +++ b/include/core/config.h @@ -0,0 +1,63 @@ +/* + * config.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 __CONFIG_H__ +#define __CONFIG_H__ + +#ifdef __cplusplus +extern "C" { +#endif + +typedef enum { + ConfigDumpDestination_SdCard = 0, + ConfigDumpDestination_UsbHost = 1, + ConfigDumpDestination_Count = 2 +} ConfigDumpDestination; + +typedef enum { + ConfigChecksumLookupMethod_None = 0, + ConfigChecksumLookupMethod_NSWDB = 1, + ConfigChecksumLookupMethod_NoIntro = 2, + ConfigChecksumLookupMethod_Count = 3 +} ConfigChecksumLookupMethod; + +/// Initializes the configuration interface. +bool configInitialize(void); + +/// Closes the configuration interface. +void configExit(void); + +/// Getters and setters for various data types. +/// Path elements must be separated using forward slashes. + +bool configGetBoolean(const char *path); +void configSetBoolean(const char *path, bool value); + +int configGetInteger(const char *path); +void configSetInteger(const char *path, int value); + +#ifdef __cplusplus +} +#endif + +#endif /* __CONFIG_H__ */ diff --git a/include/core/nxdt_includes.h b/include/core/nxdt_includes.h index ba88ba0..40c7bcd 100644 --- a/include/core/nxdt_includes.h +++ b/include/core/nxdt_includes.h @@ -56,6 +56,9 @@ /* File-based logger. */ #include "nxdt_log.h" +/* Configuration handler. */ +#include "config.h" + /* USB Mass Storage support. */ #include "ums.h" diff --git a/include/core/nxdt_utils.h b/include/core/nxdt_utils.h index 84114ac..c9d366e 100644 --- a/include/core/nxdt_utils.h +++ b/include/core/nxdt_utils.h @@ -116,6 +116,10 @@ void utilsGenerateFormattedSizeString(u64 size, char *dst, size_t dst_size); /// Returns false if there's an error. bool utilsGetFileSystemStatsByPath(const char *path, u64 *out_total, u64 *out_free); +/// Creates output directories in the specified device. +/// If 'device' is NULL, output directories will be created on the SD card. +void utilsCreateOutputDirectories(const char *device); + /// Returns true if a file exists. bool utilsCheckIfFileExists(const char *path); diff --git a/include/defines.h b/include/defines.h index 3965e58..427c32e 100644 --- a/include/defines.h +++ b/include/defines.h @@ -65,9 +65,36 @@ #define FAT32_FILESIZE_LIMIT (u64)0xFFFFFFFF /* 4 GiB - 1 (4294967295 bytes). */ -/* Other defines. */ +#define UTF8_BOM "\xEF\xBB\xBF" +#define CRLF "\r\n" + +#define HBMENU_BASE_PATH "/switch/" +#define APP_BASE_PATH HBMENU_BASE_PATH APP_TITLE "/" + +#define GAMECARD_PATH APP_BASE_PATH "Gamecard/" +#define CERT_PATH APP_BASE_PATH "Certificate/" +#define HFS_PATH APP_BASE_PATH "HFS/" +#define NSP_PATH APP_BASE_PATH "NSP/" +#define TICKET_PATH APP_BASE_PATH "Ticket/" +#define NCA_PATH APP_BASE_PATH "NCA/" +#define NCA_FS_PATH APP_BASE_PATH "NCA FS/" + +#define CONFIG_PATH "sdmc:" APP_BASE_PATH "config.json" +#define DEFAULT_CONFIG_PATH "romfs:/default_config.json" + +#define NRO_NAME APP_TITLE ".nro" +#define NRO_PATH APP_BASE_PATH NRO_NAME + +#define NSWDB_XML_PATH APP_BASE_PATH "NSWreleases.xml" + +#define KEYS_FILE_PATH "sdmc:" HBMENU_BASE_PATH "prod.keys" /* Location used by Lockpick_RCM. */ + +#define LOG_FILE_NAME APP_TITLE ".log" +#define LOG_BUF_SIZE 0x400000 /* 4 MiB. */ +#define LOG_FORCE_FLUSH 0 /* Forces a log buffer flush each time the logfile is written to. */ + +#define NXLINK_TIMEOUT 2000 -#define APP_BASE_PATH "sdmc:/switch/" APP_TITLE "/" #define BIS_SYSTEM_PARTITION_MOUNT_NAME "sys:" #define GITHUB_REPOSITORY_URL "https://github.com/DarkMatterCore/nxdumptool" @@ -80,4 +107,6 @@ #define DISCORD_SERVER_URL "https://discord.gg/SCbbcQx" +#define LOCKPICK_RCM_URL "https://github.com/shchmue/Lockpick_RCM" + #endif /* __DEFINES_H__ */ diff --git a/romfs/default_config.json b/romfs/default_config.json new file mode 100644 index 0000000..a55b1dc --- /dev/null +++ b/romfs/default_config.json @@ -0,0 +1,29 @@ +{ + "overclock": true, + "name_convention": 0, + "dump_destination": 0, + "gamecard": { + "append_key_area": false, + "keep_certificate": false, + "trim_dump": false, + "calculate_checksum": true, + "checksum_lookup_method": 1 + }, + "nsp": { + "set_download_distribution": false, + "remove_console_data": true, + "remove_titlekey_crypto": false, + "replace_acid_key_sig": false, + "disable_linked_account_requirement": false, + "enable_screenshots": false, + "enable_video_capture": false, + "disable_hdcp": false, + "lookup_checksum": true + }, + "ticket": { + "remove_console_data": true + }, + "nca_fs": { + "use_layeredfs_dir": false + } +} diff --git a/source/core/cert.c b/source/core/cert.c index 8fe4b71..0da9041 100644 --- a/source/core/cert.c +++ b/source/core/cert.c @@ -300,7 +300,7 @@ static bool _certRetrieveCertificateChainBySignatureIssuer(CertificateChain *dst } u32 i = 0; - char issuer_copy[0x40] = {0}; + char issuer_copy[0x40] = {0}, *pch = NULL, *state = NULL; bool success = true; dst->count = certGetCertificateCountInSignatureIssuer(issuer); @@ -317,11 +317,11 @@ static bool _certRetrieveCertificateChainBySignatureIssuer(CertificateChain *dst return false; } - /* Copy string to avoid problems with strtok(). */ + /* Copy string to avoid problems with strtok_r(). */ /* The "Root-" parent from the issuer string is skipped. */ - snprintf(issuer_copy, 0x40, "%s", issuer + 5); + snprintf(issuer_copy, sizeof(issuer_copy), "%s", issuer + 5); - char *pch = strtok(issuer_copy, "-"); + pch = strtok_r(issuer_copy, "-", &state); while(pch != NULL) { if (!_certRetrieveCertificateByName(&(dst->certs[i]), pch)) @@ -332,7 +332,7 @@ static bool _certRetrieveCertificateChainBySignatureIssuer(CertificateChain *dst } i++; - pch = strtok(NULL, "-"); + pch = strtok_r(NULL, "-", &state); } if (!success) certFreeCertificateChain(dst); @@ -345,17 +345,17 @@ static u32 certGetCertificateCountInSignatureIssuer(const char *issuer) if (!issuer || !*issuer) return 0; u32 count = 0; - char issuer_copy[0x40] = {0}; + char issuer_copy[0x40] = {0}, *pch = NULL, *state = NULL; - /* Copy string to avoid problems with strtok(). */ + /* Copy string to avoid problems with strtok_r(). */ /* The "Root-" parent from the issuer string is skipped. */ - snprintf(issuer_copy, 0x40, issuer + 5); + snprintf(issuer_copy, sizeof(issuer_copy), "%s", issuer + 5); - char *pch = strtok(issuer_copy, "-"); + pch = strtok_r(issuer_copy, "-", &state); while(pch != NULL) { count++; - pch = strtok(NULL, "-"); + pch = strtok_r(NULL, "-", &state); } return count; diff --git a/source/core/config.c b/source/core/config.c new file mode 100644 index 0000000..2058ee1 --- /dev/null +++ b/source/core/config.c @@ -0,0 +1,372 @@ +/* + * config.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 "config.h" +#include "title.h" + +#include + +#define JSON_VALIDATE_FIELD(type, name, ...) \ +if (!strcmp(key, #name)) { \ + if (name##_found || !configValidateJson##type(val, ##__VA_ARGS__)) goto end; \ + name##_found = true; \ + continue; \ +} + +#define JSON_VALIDATE_OBJECT(type, name) \ +if (!strcmp(key, #name)) { \ + if (name##_found || !configValidateJson##type##Object(val)) goto end; \ + name##_found = true; \ + continue; \ +} + +#define JSON_GETTER(functype, vartype, jsontype, ...) \ +vartype configGet##functype(const char *path) { \ + vartype ret = (vartype)0; \ + SCOPED_LOCK(&g_configMutex) { \ + struct json_object *obj = configGetJsonObjectByPath(g_configJson, path); \ + if (!obj || !configValidateJson##functype(obj, ##__VA_ARGS__)) break; \ + ret = (vartype)json_object_get_##jsontype(obj); \ + } \ + return ret; \ +} + +#define JSON_SETTER(functype, vartype, jsontype, ...) \ +void configSet##functype(const char *path, vartype value) { \ + SCOPED_LOCK(&g_configMutex) { \ + struct json_object *obj = configGetJsonObjectByPath(g_configJson, path); \ + if (!obj || !configValidateJson##functype(obj, ##__VA_ARGS__)) break; \ + if (json_object_set_##jsontype(obj, value)) { \ + configWriteConfigJson(); \ + } else { \ + LOG_MSG("Failed to update \"%s\"!", path); \ + } \ + } \ +} + +/* Global variables. */ + +static Mutex g_configMutex = 0; +static bool g_configInterfaceInit = false; + +static struct json_object *g_configJson = NULL; + +/* Function prototypes. */ + +static bool configParseConfigJson(void); +static void configWriteConfigJson(void); +static void configFreeConfigJson(void); + +static struct json_object *configGetJsonObjectByPath(const struct json_object *obj, const char *path); + +static bool configValidateJsonRootObject(const struct json_object *obj); +static bool configValidateJsonGameCardObject(const struct json_object *obj); +static bool configValidateJsonNspObject(const struct json_object *obj); +static bool configValidateJsonTicketObject(const struct json_object *obj); +static bool configValidateJsonNcaFsObject(const struct json_object *obj); + +NX_INLINE bool configValidateJsonBoolean(const struct json_object *obj); +NX_INLINE bool configValidateJsonInteger(const struct json_object *obj, int lower_boundary, int upper_boundary); +NX_INLINE bool configValidateJsonObject(const struct json_object *obj); + +static void configLogJsonError(void); + +bool configInitialize(void) +{ + bool ret = false; + + SCOPED_LOCK(&g_configMutex) + { + ret = g_configInterfaceInit; + if (ret) break; + + /* Parse JSON config. */ + if (!configParseConfigJson()) + { + LOG_MSG("Failed to parse JSON configuration!"); + break; + } + + /* Update flags. */ + ret = g_configInterfaceInit = true; + } + + return ret; +} + +void configExit(void) +{ + SCOPED_LOCK(&g_configMutex) + { + /* Free JSON object. */ + /* We don't need to write it back to the SD card - setter functions do that on their own. */ + configFreeConfigJson(); + + /* Update flag. */ + g_configInterfaceInit = false; + } +} + +JSON_GETTER(Boolean, bool, boolean); +JSON_SETTER(Boolean, bool, boolean); + +JSON_GETTER(Integer, int, int, INT32_MIN, INT32_MAX); +JSON_SETTER(Integer, int, int, INT32_MIN, INT32_MAX); + +static bool configParseConfigJson(void) +{ + bool use_default_config = true, ret = false; + + /* Read config JSON. */ + g_configJson = json_object_from_file(CONFIG_PATH); + if (!g_configJson) + { + configLogJsonError(); + goto end; + } + + /* Validate configuration. */ + ret = configValidateJsonRootObject(g_configJson); + use_default_config = !ret; + +end: + if (use_default_config) + { + /* Free config JSON. */ + configFreeConfigJson(); + + /* Read default config JSON. */ + g_configJson = json_object_from_file(DEFAULT_CONFIG_PATH); + if (g_configJson) + { + configWriteConfigJson(); + ret = true; + } else { + configLogJsonError(); + } + } + + return ret; +} + +static void configWriteConfigJson(void) +{ + if (!g_configJson) return; + if (json_object_to_file_ext(CONFIG_PATH, g_configJson, JSON_C_TO_STRING_PRETTY) != 0) configLogJsonError(); +} + +static void configFreeConfigJson(void) +{ + if (!g_configJson) return; + json_object_put(g_configJson); + g_configJson = NULL; +} + +static struct json_object *configGetJsonObjectByPath(const struct json_object *obj, const char *path) +{ + const struct json_object *parent_obj = obj; + struct json_object *child_obj = NULL; + char *path_dup = NULL, *pch = NULL, *state = NULL; + + if (!configValidateJsonObject(obj) || !path || !*path) + { + LOG_MSG("Invalid parameters!"); + return NULL; + } + + /* Duplicate path to avoid problems with strtok_r(). */ + if (!(path_dup = strdup(path))) + { + LOG_MSG("Unable to duplicate input path! (\"%s\").", path); + return NULL; + } + + pch = strtok_r(path_dup, "/", &state); + if (!pch) + { + LOG_MSG("Failed to tokenize input path! (\"%s\").", path); + goto end; + } + + while(pch) + { + if (!json_object_object_get_ex(parent_obj, pch, &child_obj)) + { + LOG_MSG("Failed to retrieve JSON object by key for \"%s\"! (\"%s\").", pch, path); + break; + } + + pch = strtok_r(NULL, "/", &state); + if (pch) + { + parent_obj = child_obj; + child_obj = NULL; + } + } + +end: + if (path_dup) free(path_dup); + + return child_obj; +} + +static bool configValidateJsonRootObject(const struct json_object *obj) +{ + bool ret = false, overclock_found = false, name_convention_found = false, dump_destination_found = false, gamecard_found = false; + bool nsp_found = false, ticket_found = false, nca_fs_found = false; + + if (!configValidateJsonObject(obj)) goto end; + + json_object_object_foreach(obj, key, val) + { + JSON_VALIDATE_FIELD(Boolean, overclock); + JSON_VALIDATE_FIELD(Integer, name_convention, TitleFileNameConvention_Full, TitleFileNameConvention_IdAndVersionOnly); + JSON_VALIDATE_FIELD(Integer, dump_destination, ConfigDumpDestination_SdCard, ConfigDumpDestination_UsbHost); + JSON_VALIDATE_OBJECT(GameCard, gamecard); + JSON_VALIDATE_OBJECT(Nsp, nsp); + JSON_VALIDATE_OBJECT(Ticket, ticket); + JSON_VALIDATE_OBJECT(NcaFs, nca_fs); + goto end; + } + + ret = (overclock_found && name_convention_found && dump_destination_found && gamecard_found && nsp_found && ticket_found && nca_fs_found); + +end: + return ret; +} + +static bool configValidateJsonGameCardObject(const struct json_object *obj) +{ + bool ret = false, append_key_area_found = false, keep_certificate_found = false, trim_dump_found = false, calculate_checksum_found = false, checksum_lookup_method_found = false; + + if (!configValidateJsonObject(obj)) goto end; + + json_object_object_foreach(obj, key, val) + { + JSON_VALIDATE_FIELD(Boolean, append_key_area); + JSON_VALIDATE_FIELD(Boolean, keep_certificate); + JSON_VALIDATE_FIELD(Boolean, trim_dump); + JSON_VALIDATE_FIELD(Boolean, calculate_checksum); + JSON_VALIDATE_FIELD(Integer, checksum_lookup_method, ConfigChecksumLookupMethod_None, ConfigChecksumLookupMethod_NoIntro); + goto end; + } + + ret = (append_key_area_found && keep_certificate_found && trim_dump_found && calculate_checksum_found && checksum_lookup_method_found); + +end: + return ret; +} + +static bool configValidateJsonNspObject(const struct json_object *obj) +{ + bool ret = false, set_download_distribution_found = false, remove_console_data_found = false, remove_titlekey_crypto_found = false, replace_acid_key_sig_found = false; + bool disable_linked_account_requirement_found = false, enable_screenshots_found = false, enable_video_capture_found = false, disable_hdcp_found = false, lookup_checksum_found = false; + + if (!configValidateJsonObject(obj)) goto end; + + json_object_object_foreach(obj, key, val) + { + JSON_VALIDATE_FIELD(Boolean, set_download_distribution); + JSON_VALIDATE_FIELD(Boolean, remove_console_data); + JSON_VALIDATE_FIELD(Boolean, remove_titlekey_crypto); + JSON_VALIDATE_FIELD(Boolean, replace_acid_key_sig); + JSON_VALIDATE_FIELD(Boolean, disable_linked_account_requirement); + JSON_VALIDATE_FIELD(Boolean, enable_screenshots); + JSON_VALIDATE_FIELD(Boolean, enable_video_capture); + JSON_VALIDATE_FIELD(Boolean, disable_hdcp); + JSON_VALIDATE_FIELD(Boolean, lookup_checksum); + goto end; + } + + ret = (set_download_distribution_found && remove_console_data_found && remove_titlekey_crypto_found && replace_acid_key_sig_found && disable_linked_account_requirement_found && \ + enable_screenshots_found && enable_video_capture_found && disable_hdcp_found && lookup_checksum_found); + +end: + return ret; +} + +static bool configValidateJsonTicketObject(const struct json_object *obj) +{ + bool ret = false, remove_console_data_found = false; + + if (!configValidateJsonObject(obj)) goto end; + + json_object_object_foreach(obj, key, val) + { + JSON_VALIDATE_FIELD(Boolean, remove_console_data); + goto end; + } + + ret = remove_console_data_found; + +end: + return ret; +} + +static bool configValidateJsonNcaFsObject(const struct json_object *obj) +{ + bool ret = false, use_layeredfs_dir_found = false; + + if (!configValidateJsonObject(obj)) goto end; + + json_object_object_foreach(obj, key, val) + { + JSON_VALIDATE_FIELD(Boolean, use_layeredfs_dir); + goto end; + } + + ret = use_layeredfs_dir_found; + +end: + return ret; +} + +NX_INLINE bool configValidateJsonBoolean(const struct json_object *obj) +{ + return (obj != NULL && json_object_is_type(obj, json_type_boolean)); +} + +NX_INLINE bool configValidateJsonInteger(const struct json_object *obj, int lower_boundary, int upper_boundary) +{ + if (!obj || !json_object_is_type(obj, json_type_int) || lower_boundary > upper_boundary) return false; + int val = json_object_get_int(obj); + return (val >= lower_boundary && val <= upper_boundary); +} + +NX_INLINE bool configValidateJsonObject(const struct json_object *obj) +{ + return (obj != NULL && json_object_is_type(obj, json_type_object) && json_object_object_length(obj) > 0); +} + +static void configLogJsonError(void) +{ + size_t str_len = 0; + char *str = (char*)json_util_get_last_err(); /* Drop the const. */ + if (!str || !(str_len = strlen(str))) return; + + /* Remove line breaks. */ + if (str[str_len - 1] == '\n') str[--str_len] = '\0'; + if (str[str_len - 1] == '\r') str[--str_len] = '\0'; + + /* Log error message. */ + LOG_MSG("%s", str); +} diff --git a/source/core/keys.c b/source/core/keys.c index 1500423..bf8abb3 100644 --- a/source/core/keys.c +++ b/source/core/keys.c @@ -27,8 +27,6 @@ #include "nca.h" #include "rsa.h" -#define KEYS_FILE_PATH "sdmc:/switch/prod.keys" /* Location used by Lockpick_RCM. */ - #define ETICKET_RSA_DEVICE_KEY_PUBLIC_EXPONENT 0x10001 /* Type definitions. */ diff --git a/source/core/nxdt_log.c b/source/core/nxdt_log.c index 0a9768b..e7f837f 100644 --- a/source/core/nxdt_log.c +++ b/source/core/nxdt_log.c @@ -21,10 +21,6 @@ #include "nxdt_utils.h" -#define LOG_FILE_NAME APP_TITLE ".log" -#define LOG_BUF_SIZE 0x400000 /* 4 MiB. */ -#define LOG_FORCE_FLUSH 0 /* Forces a log buffer flush each time the logfile is written to. */ - /* Global variables. */ static Mutex g_logMutex = 0; @@ -37,9 +33,7 @@ static s64 g_logFileOffset = 0; static char *g_logBuffer = NULL; static size_t g_logBufferLength = 0; -static const char *g_utf8Bom = "\xEF\xBB\xBF"; static const char *g_logStrFormat = "[%d-%02d-%02d %02d:%02d:%02d.%09lu] %s -> "; -static const char *g_logLineBreak = "\r\n"; /* Function prototypes. */ @@ -121,7 +115,7 @@ __attribute__((format(printf, 4, 5))) void logWriteFormattedStringToBuffer(char /* Generate formatted string. */ sprintf(dst_ptr + dst_str_len, g_logStrFormat, ts->tm_year, ts->tm_mon, ts->tm_mday, ts->tm_hour, ts->tm_min, ts->tm_sec, now.tv_nsec, func_name); vsprintf(dst_ptr + dst_str_len + (size_t)str1_len, fmt, args); - strcat(dst_ptr, g_logLineBreak); + strcat(dst_ptr, CRLF); end: va_end(args); @@ -141,7 +135,7 @@ __attribute__((format(printf, 4, 5))) void logWriteBinaryDataToLogFile(const voi /* Generate hex string representation. */ utilsGenerateHexStringFromData(data_str, data_str_size, data, data_size, true); - strcat(data_str, g_logLineBreak); + strcat(data_str, CRLF); SCOPED_LOCK(&g_logMutex) { @@ -322,7 +316,7 @@ static void _logWriteFormattedStringToLogFile(bool save, const char *func_name, /* Nice and easy string formatting using the log buffer. */ sprintf(g_logBuffer + g_logBufferLength, g_logStrFormat, ts->tm_year, ts->tm_mon, ts->tm_mday, ts->tm_hour, ts->tm_min, ts->tm_sec, now.tv_nsec, func_name); vsprintf(g_logBuffer + g_logBufferLength + (size_t)str1_len, fmt, args); - strcat(g_logBuffer, g_logLineBreak); + strcat(g_logBuffer, CRLF); g_logBufferLength += log_str_len; } else { /* Flush log buffer. */ @@ -336,7 +330,7 @@ static void _logWriteFormattedStringToLogFile(bool save, const char *func_name, /* Generate formatted string. */ sprintf(tmp_str, g_logStrFormat, ts->tm_year, ts->tm_mon, ts->tm_mday, ts->tm_hour, ts->tm_min, ts->tm_sec, now.tv_nsec, func_name); vsprintf(tmp_str + (size_t)str1_len, fmt, args); - strcat(tmp_str, g_logLineBreak); + strcat(tmp_str, CRLF); /* Write formatted string data until it no longer exceeds the log buffer size. */ while(log_str_len >= LOG_BUF_SIZE) @@ -432,8 +426,8 @@ static bool logOpenLogFile(void) /* Write UTF-8 BOM right away (if needed). */ if (!g_logFileOffset) { - size_t utf8_bom_len = strlen(g_utf8Bom); - fsFileWrite(&g_logFile, g_logFileOffset, g_utf8Bom, utf8_bom_len, FsWriteOption_Flush); + size_t utf8_bom_len = strlen(UTF8_BOM); + fsFileWrite(&g_logFile, g_logFileOffset, UTF8_BOM, utf8_bom_len, FsWriteOption_Flush); g_logFileOffset += (s64)utf8_bom_len; } } else { diff --git a/source/core/nxdt_utils.c b/source/core/nxdt_utils.c index 322d470..9f98f87 100644 --- a/source/core/nxdt_utils.c +++ b/source/core/nxdt_utils.c @@ -65,6 +65,20 @@ static const u32 g_sizeSuffixesCount = MAX_ELEMENTS(g_sizeSuffixes); static const char g_illegalFileSystemChars[] = "\\/:*?\"<>|"; static const size_t g_illegalFileSystemCharsLength = (MAX_ELEMENTS(g_illegalFileSystemChars) - 1); +static const char *g_outputDirs[] = { + HBMENU_BASE_PATH, + APP_BASE_PATH, + GAMECARD_PATH, + CERT_PATH, + HFS_PATH, + NSP_PATH, + TICKET_PATH, + NCA_PATH, + NCA_FS_PATH +}; + +static const size_t g_outputDirsCount = MAX_ELEMENTS(g_outputDirs); + /* Function prototypes. */ static void _utilsGetLaunchPath(int program_argc, const char **program_argv); @@ -87,7 +101,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, flag = false; + bool ret = false; SCOPED_LOCK(&g_resourcesMutex) { @@ -129,6 +143,9 @@ bool utilsInitializeResources(const int program_argc, const char **program_argv) g_programAppletType = appletGetAppletType(); LOG_MSG("Running under %s mode.", _utilsAppletModeCheck() ? "applet" : "title override"); + /* Create output directories (SD card only). */ + utilsCreateOutputDirectories(NULL); + /* Initialize USB interface. */ if (!usbInitialize()) break; @@ -160,22 +177,34 @@ bool utilsInitializeResources(const int program_argc, const char **program_argv) /* Mount eMMC BIS System partition. */ if (!utilsMountEmmcBisSystemPartitionStorage()) break; - /* Enable video recording. */ - rc = appletIsGamePlayRecordingSupported(&flag); - if (R_SUCCEEDED(rc) && flag) appletInitializeGamePlayRecording(); + /* Mount application RomFS. */ + rc = romfsInit(); + if (R_FAILED(rc)) + { + LOG_MSG("Failed to mount RomFS container!"); + break; + } - /* 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); + /* Load configuration. */ + if (!configInitialize()) break; /* Overclock system. */ - utilsOverclockSystem(true); + utilsOverclockSystem(configGetBoolean("overclock")); /* Setup an applet hook to change the hardware clocks after a system mode change (docked <-> undocked). */ appletHook(&g_systemOverclockCookie, utilsOverclockSystemAppletHook, NULL); - /* Mount application RomFS. */ - romfsInit(); + /* Enable video recording if we're running under title override mode. */ + if (!_utilsAppletModeCheck()) + { + bool flag = false; + rc = appletIsGamePlayRecordingSupported(&flag); + if (R_SUCCEEDED(rc) && flag) appletInitializeGamePlayRecording(); + } + + /* Disable screen dimming and auto sleep. */ + /* TODO: only use this function while dealing with a dump process - make sure to handle power button presses as well. */ + appletSetMediaPlaybackState(true); /* Redirect stdout and stderr over network to nxlink. */ rc = socketInitializeDefault(); @@ -203,8 +232,12 @@ void utilsCloseResources(void) socketExit(); - /* Unmount application RomFS. */ - romfsExit(); + /* Enable screen dimming and auto sleep. */ + /* TODO: only use this function while dealing with a dump process - make sure to handle power button presses as well. */ + appletSetMediaPlaybackState(false); + + /* Unblock HOME button presses. */ + utilsChangeHomeButtonBlockStatus(false); /* Unset our overclock applet hook. */ appletUnhook(&g_systemOverclockCookie); @@ -212,11 +245,11 @@ void utilsCloseResources(void) /* Restore hardware clocks. */ utilsOverclockSystem(false); - /* Enable screen dimming and auto sleep. */ - appletSetMediaPlaybackState(false); + /* Close configuration interface. */ + configExit(); - /* Unblock HOME button presses. */ - utilsChangeHomeButtonBlockStatus(false); + /* Unmount application RomFS. */ + romfsExit(); /* Unmount eMMC BIS System partition. */ utilsUnmountEmmcBisSystemPartitionStorage(); @@ -574,6 +607,24 @@ bool utilsGetFileSystemStatsByPath(const char *path, u64 *out_total, u64 *out_fr return true; } +void utilsCreateOutputDirectories(const char *device) +{ + size_t device_len = 0; + char path[FS_MAX_PATH] = {0}; + + if (device && (!(device_len = strlen(device)) || device[device_len - 1] != ':')) + { + LOG_MSG("Invalid parameters!"); + return; + } + + for(size_t i = 0; i < g_outputDirsCount; i++) + { + sprintf(path, "%s%s", (device ? device : "sdmc:"), g_outputDirs[i]); + mkdir(path, 0744); + } +} + bool utilsCheckIfFileExists(const char *path) { if (!path || !*path) return false; @@ -844,8 +895,7 @@ static void utilsOverclockSystemAppletHook(AppletHookType hook, void *param) if (hook != AppletHookType_OnOperationMode && hook != AppletHookType_OnPerformanceMode) return; - /* TODO: read config here to actually know the value to use with utilsOverclockSystem. */ - utilsOverclockSystem(true); + utilsOverclockSystem(configGetBoolean("overclock")); } static void utilsPrintConsoleError(void) diff --git a/source/core/romfs.c b/source/core/romfs.c index aee97a4..8fb7f69 100644 --- a/source/core/romfs.c +++ b/source/core/romfs.c @@ -276,7 +276,7 @@ bool romfsGetDirectoryDataSize(RomFileSystemContext *ctx, RomFileSystemDirectory RomFileSystemDirectoryEntry *romfsGetDirectoryEntryByPath(RomFileSystemContext *ctx, const char *path) { size_t path_len = 0; - char *path_dup = NULL, *pch = NULL; + char *path_dup = NULL, *pch = NULL, *state = NULL; RomFileSystemDirectoryEntry *dir_entry = NULL; if (!ctx || !ctx->dir_table || !ctx->dir_table_size || !path || *path != '/' || !(path_len = strlen(path)) || !(dir_entry = romfsGetDirectoryEntryByOffset(ctx, 0))) @@ -288,14 +288,14 @@ RomFileSystemDirectoryEntry *romfsGetDirectoryEntryByPath(RomFileSystemContext * /* Check if the root directory was requested. */ if (path_len == 1) return dir_entry; - /* Duplicate path to avoid problems with strtok(). */ + /* Duplicate path to avoid problems with strtok_r(). */ if (!(path_dup = strdup(path))) { LOG_MSG("Unable to duplicate input path! (\"%s\").", path); return NULL; } - pch = strtok(path_dup, "/"); + pch = strtok_r(path_dup, "/", &state); if (!pch) { LOG_MSG("Failed to tokenize input path! (\"%s\").", path); @@ -311,7 +311,7 @@ RomFileSystemDirectoryEntry *romfsGetDirectoryEntryByPath(RomFileSystemContext * break; } - pch = strtok(NULL, "/"); + pch = strtok_r(NULL, "/", &state); } end: diff --git a/source/core/title.c b/source/core/title.c index aba6ccc..1d76ba8 100644 --- a/source/core/title.c +++ b/source/core/title.c @@ -1434,7 +1434,7 @@ static bool titleGenerateMetadataEntriesFromSystemTitles(void) /* Fill information. */ const TitleSystemEntry *system_title = &(g_systemTitles[extra_app_count]); cur_app_metadata->title_id = system_title->title_id; - sprintf(cur_app_metadata->lang_entry.name, system_title->name); + sprintf(cur_app_metadata->lang_entry.name, "%s", system_title->name); /* Set application metadata entry pointer. */ g_systemMetadata[g_systemMetadataCount + extra_app_count] = cur_app_metadata; diff --git a/source/titles_tab.cpp b/source/titles_tab.cpp index effdb0a..89f745c 100644 --- a/source/titles_tab.cpp +++ b/source/titles_tab.cpp @@ -66,11 +66,7 @@ namespace nxdt::views is_system(is_system) { /* Set sublabel. */ - if (!this->is_system) - { - this->setSubLabel(std::string(app_metadata->lang_entry.author)); - this->setHeight(brls::Application::getStyle()->List.Item.heightWithSubLabel); - } + if (!this->is_system) this->setSubLabel(std::string(app_metadata->lang_entry.author)); /* Set thumbnail (if needed). */ if (app_metadata->icon && app_metadata->icon_size) this->setThumbnail(app_metadata->icon, app_metadata->icon_size); @@ -102,10 +98,8 @@ namespace nxdt::views void TitlesTab::PopulateList(const nxdt::tasks::TitleApplicationMetadataVector* app_metadata) { - if (!app_metadata) return; - /* Populate variables. */ - size_t app_metadata_count = app_metadata->size(); + size_t app_metadata_count = (app_metadata ? app_metadata->size() : 0); bool update_focused_view = this->IsListItemFocused(); int focus_stack_index = this->GetFocusStackViewIndex(); diff --git a/todo.txt b/todo.txt index e1d78ad..d8f9c39 100644 --- a/todo.txt +++ b/todo.txt @@ -18,6 +18,11 @@ todo: bktr: functions to display filelist (wrappers for romfs functions tbh) + usb: change buffer size? + usb: change chunk size? + usb: improve abi (make it rest-like?) + usb: improve cancel mechanism + others: config load/save using json others: dump verification via nswdb / no-intro others: update application feature