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

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.
This commit is contained in:
Pablo Curiel 2021-07-21 11:04:18 -04:00
parent a2a401d36d
commit 64ab1705bc
15 changed files with 600 additions and 60 deletions

View file

@ -84,15 +84,14 @@ CFLAGS += -DGIT_BRANCH=\"${GIT_BRANCH}\" -DGIT_COMMIT=\"${GIT_COMMIT}\" -DGIT_R
CFLAGS += -DBOREALIS_RESOURCES="\"${BOREALIS_RESOURCES}\"" CFLAGS += -DBOREALIS_RESOURCES="\"${BOREALIS_RESOURCES}\""
CFLAGS += `aarch64-none-elf-pkg-config zlib --cflags` CFLAGS += `aarch64-none-elf-pkg-config zlib --cflags`
CFLAGS += `aarch64-none-elf-pkg-config libxml-2.0 --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 CXXFLAGS := $(CFLAGS) -std=c++20 -Wno-volatile -Wno-unused-parameter
ASFLAGS := -g -gdwarf-4 $(ARCH) ASFLAGS := -g -gdwarf-4 $(ARCH)
LDFLAGS := -specs=$(DEVKITPRO)/libnx/switch.specs -g -gdwarf-4 $(ARCH) -Wl,-Map,$(notdir $*.map) 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 := -lcurl -lmbedtls -lmbedx509 -lmbedcrypto -lxml2 -ljson-c -lz -lusbhsfs -lntfs-3g -llwext4 -lnx
#LIBS += -ljson-c
#--------------------------------------------------------------------------------- #---------------------------------------------------------------------------------
# list of directories containing libraries, this must be the top level containing # list of directories containing libraries, this must be the top level containing

63
include/core/config.h Normal file
View file

@ -0,0 +1,63 @@
/*
* config.h
*
* Copyright (c) 2020-2021, DarkMatterCore <pabloacurielz@gmail.com>.
*
* 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 <https://www.gnu.org/licenses/>.
*/
#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__ */

View file

@ -56,6 +56,9 @@
/* File-based logger. */ /* File-based logger. */
#include "nxdt_log.h" #include "nxdt_log.h"
/* Configuration handler. */
#include "config.h"
/* USB Mass Storage support. */ /* USB Mass Storage support. */
#include "ums.h" #include "ums.h"

View file

@ -116,6 +116,10 @@ void utilsGenerateFormattedSizeString(u64 size, char *dst, size_t dst_size);
/// Returns false if there's an error. /// Returns false if there's an error.
bool utilsGetFileSystemStatsByPath(const char *path, u64 *out_total, u64 *out_free); 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. /// Returns true if a file exists.
bool utilsCheckIfFileExists(const char *path); bool utilsCheckIfFileExists(const char *path);

View file

@ -65,9 +65,36 @@
#define FAT32_FILESIZE_LIMIT (u64)0xFFFFFFFF /* 4 GiB - 1 (4294967295 bytes). */ #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 BIS_SYSTEM_PARTITION_MOUNT_NAME "sys:"
#define GITHUB_REPOSITORY_URL "https://github.com/DarkMatterCore/nxdumptool" #define GITHUB_REPOSITORY_URL "https://github.com/DarkMatterCore/nxdumptool"
@ -80,4 +107,6 @@
#define DISCORD_SERVER_URL "https://discord.gg/SCbbcQx" #define DISCORD_SERVER_URL "https://discord.gg/SCbbcQx"
#define LOCKPICK_RCM_URL "https://github.com/shchmue/Lockpick_RCM"
#endif /* __DEFINES_H__ */ #endif /* __DEFINES_H__ */

29
romfs/default_config.json Normal file
View file

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

View file

@ -300,7 +300,7 @@ static bool _certRetrieveCertificateChainBySignatureIssuer(CertificateChain *dst
} }
u32 i = 0; u32 i = 0;
char issuer_copy[0x40] = {0}; char issuer_copy[0x40] = {0}, *pch = NULL, *state = NULL;
bool success = true; bool success = true;
dst->count = certGetCertificateCountInSignatureIssuer(issuer); dst->count = certGetCertificateCountInSignatureIssuer(issuer);
@ -317,11 +317,11 @@ static bool _certRetrieveCertificateChainBySignatureIssuer(CertificateChain *dst
return false; 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. */ /* 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) while(pch != NULL)
{ {
if (!_certRetrieveCertificateByName(&(dst->certs[i]), pch)) if (!_certRetrieveCertificateByName(&(dst->certs[i]), pch))
@ -332,7 +332,7 @@ static bool _certRetrieveCertificateChainBySignatureIssuer(CertificateChain *dst
} }
i++; i++;
pch = strtok(NULL, "-"); pch = strtok_r(NULL, "-", &state);
} }
if (!success) certFreeCertificateChain(dst); if (!success) certFreeCertificateChain(dst);
@ -345,17 +345,17 @@ static u32 certGetCertificateCountInSignatureIssuer(const char *issuer)
if (!issuer || !*issuer) return 0; if (!issuer || !*issuer) return 0;
u32 count = 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. */ /* 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) while(pch != NULL)
{ {
count++; count++;
pch = strtok(NULL, "-"); pch = strtok_r(NULL, "-", &state);
} }
return count; return count;

372
source/core/config.c Normal file
View file

@ -0,0 +1,372 @@
/*
* config.c
*
* Copyright (c) 2020-2021, DarkMatterCore <pabloacurielz@gmail.com>.
*
* 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 <https://www.gnu.org/licenses/>.
*/
#include "nxdt_utils.h"
#include "config.h"
#include "title.h"
#include <json-c/json.h>
#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);
}

View file

@ -27,8 +27,6 @@
#include "nca.h" #include "nca.h"
#include "rsa.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 #define ETICKET_RSA_DEVICE_KEY_PUBLIC_EXPONENT 0x10001
/* Type definitions. */ /* Type definitions. */

View file

@ -21,10 +21,6 @@
#include "nxdt_utils.h" #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. */ /* Global variables. */
static Mutex g_logMutex = 0; static Mutex g_logMutex = 0;
@ -37,9 +33,7 @@ static s64 g_logFileOffset = 0;
static char *g_logBuffer = NULL; static char *g_logBuffer = NULL;
static size_t g_logBufferLength = 0; 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_logStrFormat = "[%d-%02d-%02d %02d:%02d:%02d.%09lu] %s -> ";
static const char *g_logLineBreak = "\r\n";
/* Function prototypes. */ /* Function prototypes. */
@ -121,7 +115,7 @@ __attribute__((format(printf, 4, 5))) void logWriteFormattedStringToBuffer(char
/* Generate formatted string. */ /* 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); 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); vsprintf(dst_ptr + dst_str_len + (size_t)str1_len, fmt, args);
strcat(dst_ptr, g_logLineBreak); strcat(dst_ptr, CRLF);
end: end:
va_end(args); va_end(args);
@ -141,7 +135,7 @@ __attribute__((format(printf, 4, 5))) void logWriteBinaryDataToLogFile(const voi
/* Generate hex string representation. */ /* Generate hex string representation. */
utilsGenerateHexStringFromData(data_str, data_str_size, data, data_size, true); utilsGenerateHexStringFromData(data_str, data_str_size, data, data_size, true);
strcat(data_str, g_logLineBreak); strcat(data_str, CRLF);
SCOPED_LOCK(&g_logMutex) 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. */ /* 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); 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); 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; g_logBufferLength += log_str_len;
} else { } else {
/* Flush log buffer. */ /* Flush log buffer. */
@ -336,7 +330,7 @@ static void _logWriteFormattedStringToLogFile(bool save, const char *func_name,
/* Generate formatted string. */ /* 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); 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); 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. */ /* Write formatted string data until it no longer exceeds the log buffer size. */
while(log_str_len >= LOG_BUF_SIZE) while(log_str_len >= LOG_BUF_SIZE)
@ -432,8 +426,8 @@ static bool logOpenLogFile(void)
/* Write UTF-8 BOM right away (if needed). */ /* Write UTF-8 BOM right away (if needed). */
if (!g_logFileOffset) if (!g_logFileOffset)
{ {
size_t utf8_bom_len = strlen(g_utf8Bom); size_t utf8_bom_len = strlen(UTF8_BOM);
fsFileWrite(&g_logFile, g_logFileOffset, g_utf8Bom, utf8_bom_len, FsWriteOption_Flush); fsFileWrite(&g_logFile, g_logFileOffset, UTF8_BOM, utf8_bom_len, FsWriteOption_Flush);
g_logFileOffset += (s64)utf8_bom_len; g_logFileOffset += (s64)utf8_bom_len;
} }
} else { } else {

View file

@ -65,6 +65,20 @@ static const u32 g_sizeSuffixesCount = MAX_ELEMENTS(g_sizeSuffixes);
static const char g_illegalFileSystemChars[] = "\\/:*?\"<>|"; static const char g_illegalFileSystemChars[] = "\\/:*?\"<>|";
static const size_t g_illegalFileSystemCharsLength = (MAX_ELEMENTS(g_illegalFileSystemChars) - 1); 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. */ /* Function prototypes. */
static void _utilsGetLaunchPath(int program_argc, const char **program_argv); 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) bool utilsInitializeResources(const int program_argc, const char **program_argv)
{ {
Result rc = 0; Result rc = 0;
bool ret = false, flag = false; bool ret = false;
SCOPED_LOCK(&g_resourcesMutex) SCOPED_LOCK(&g_resourcesMutex)
{ {
@ -129,6 +143,9 @@ bool utilsInitializeResources(const int program_argc, const char **program_argv)
g_programAppletType = appletGetAppletType(); g_programAppletType = appletGetAppletType();
LOG_MSG("Running under %s mode.", _utilsAppletModeCheck() ? "applet" : "title override"); LOG_MSG("Running under %s mode.", _utilsAppletModeCheck() ? "applet" : "title override");
/* Create output directories (SD card only). */
utilsCreateOutputDirectories(NULL);
/* Initialize USB interface. */ /* Initialize USB interface. */
if (!usbInitialize()) break; if (!usbInitialize()) break;
@ -160,22 +177,34 @@ bool utilsInitializeResources(const int program_argc, const char **program_argv)
/* Mount eMMC BIS System partition. */ /* Mount eMMC BIS System partition. */
if (!utilsMountEmmcBisSystemPartitionStorage()) break; if (!utilsMountEmmcBisSystemPartitionStorage()) break;
/* Enable video recording. */ /* Mount application RomFS. */
rc = appletIsGamePlayRecordingSupported(&flag); rc = romfsInit();
if (R_SUCCEEDED(rc) && flag) appletInitializeGamePlayRecording(); if (R_FAILED(rc))
{
LOG_MSG("Failed to mount RomFS container!");
break;
}
/* Disable screen dimming and auto sleep. */ /* Load configuration. */
/* TODO: only use this function right before starting a dump procedure - make sure to handle power button presses as well. */ if (!configInitialize()) break;
appletSetMediaPlaybackState(true);
/* Overclock system. */ /* Overclock system. */
utilsOverclockSystem(true); utilsOverclockSystem(configGetBoolean("overclock"));
/* Setup an applet hook to change the hardware clocks after a system mode change (docked <-> undocked). */ /* Setup an applet hook to change the hardware clocks after a system mode change (docked <-> undocked). */
appletHook(&g_systemOverclockCookie, utilsOverclockSystemAppletHook, NULL); appletHook(&g_systemOverclockCookie, utilsOverclockSystemAppletHook, NULL);
/* Mount application RomFS. */ /* Enable video recording if we're running under title override mode. */
romfsInit(); 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. */ /* Redirect stdout and stderr over network to nxlink. */
rc = socketInitializeDefault(); rc = socketInitializeDefault();
@ -203,8 +232,12 @@ void utilsCloseResources(void)
socketExit(); socketExit();
/* Unmount application RomFS. */ /* Enable screen dimming and auto sleep. */
romfsExit(); /* 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. */ /* Unset our overclock applet hook. */
appletUnhook(&g_systemOverclockCookie); appletUnhook(&g_systemOverclockCookie);
@ -212,11 +245,11 @@ void utilsCloseResources(void)
/* Restore hardware clocks. */ /* Restore hardware clocks. */
utilsOverclockSystem(false); utilsOverclockSystem(false);
/* Enable screen dimming and auto sleep. */ /* Close configuration interface. */
appletSetMediaPlaybackState(false); configExit();
/* Unblock HOME button presses. */ /* Unmount application RomFS. */
utilsChangeHomeButtonBlockStatus(false); romfsExit();
/* Unmount eMMC BIS System partition. */ /* Unmount eMMC BIS System partition. */
utilsUnmountEmmcBisSystemPartitionStorage(); utilsUnmountEmmcBisSystemPartitionStorage();
@ -574,6 +607,24 @@ bool utilsGetFileSystemStatsByPath(const char *path, u64 *out_total, u64 *out_fr
return true; 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) bool utilsCheckIfFileExists(const char *path)
{ {
if (!path || !*path) return false; if (!path || !*path) return false;
@ -844,8 +895,7 @@ static void utilsOverclockSystemAppletHook(AppletHookType hook, void *param)
if (hook != AppletHookType_OnOperationMode && hook != AppletHookType_OnPerformanceMode) return; if (hook != AppletHookType_OnOperationMode && hook != AppletHookType_OnPerformanceMode) return;
/* TODO: read config here to actually know the value to use with utilsOverclockSystem. */ utilsOverclockSystem(configGetBoolean("overclock"));
utilsOverclockSystem(true);
} }
static void utilsPrintConsoleError(void) static void utilsPrintConsoleError(void)

View file

@ -276,7 +276,7 @@ bool romfsGetDirectoryDataSize(RomFileSystemContext *ctx, RomFileSystemDirectory
RomFileSystemDirectoryEntry *romfsGetDirectoryEntryByPath(RomFileSystemContext *ctx, const char *path) RomFileSystemDirectoryEntry *romfsGetDirectoryEntryByPath(RomFileSystemContext *ctx, const char *path)
{ {
size_t path_len = 0; size_t path_len = 0;
char *path_dup = NULL, *pch = NULL; char *path_dup = NULL, *pch = NULL, *state = NULL;
RomFileSystemDirectoryEntry *dir_entry = 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))) 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. */ /* Check if the root directory was requested. */
if (path_len == 1) return dir_entry; 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))) if (!(path_dup = strdup(path)))
{ {
LOG_MSG("Unable to duplicate input path! (\"%s\").", path); LOG_MSG("Unable to duplicate input path! (\"%s\").", path);
return NULL; return NULL;
} }
pch = strtok(path_dup, "/"); pch = strtok_r(path_dup, "/", &state);
if (!pch) if (!pch)
{ {
LOG_MSG("Failed to tokenize input path! (\"%s\").", path); LOG_MSG("Failed to tokenize input path! (\"%s\").", path);
@ -311,7 +311,7 @@ RomFileSystemDirectoryEntry *romfsGetDirectoryEntryByPath(RomFileSystemContext *
break; break;
} }
pch = strtok(NULL, "/"); pch = strtok_r(NULL, "/", &state);
} }
end: end:

View file

@ -1434,7 +1434,7 @@ static bool titleGenerateMetadataEntriesFromSystemTitles(void)
/* Fill information. */ /* Fill information. */
const TitleSystemEntry *system_title = &(g_systemTitles[extra_app_count]); const TitleSystemEntry *system_title = &(g_systemTitles[extra_app_count]);
cur_app_metadata->title_id = system_title->title_id; 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. */ /* Set application metadata entry pointer. */
g_systemMetadata[g_systemMetadataCount + extra_app_count] = cur_app_metadata; g_systemMetadata[g_systemMetadataCount + extra_app_count] = cur_app_metadata;

View file

@ -66,11 +66,7 @@ namespace nxdt::views
is_system(is_system) is_system(is_system)
{ {
/* Set sublabel. */ /* Set sublabel. */
if (!this->is_system) if (!this->is_system) this->setSubLabel(std::string(app_metadata->lang_entry.author));
{
this->setSubLabel(std::string(app_metadata->lang_entry.author));
this->setHeight(brls::Application::getStyle()->List.Item.heightWithSubLabel);
}
/* Set thumbnail (if needed). */ /* Set thumbnail (if needed). */
if (app_metadata->icon && app_metadata->icon_size) this->setThumbnail(app_metadata->icon, app_metadata->icon_size); 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) void TitlesTab::PopulateList(const nxdt::tasks::TitleApplicationMetadataVector* app_metadata)
{ {
if (!app_metadata) return;
/* Populate variables. */ /* 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(); bool update_focused_view = this->IsListItemFocused();
int focus_stack_index = this->GetFocusStackViewIndex(); int focus_stack_index = this->GetFocusStackViewIndex();

View file

@ -18,6 +18,11 @@ todo:
bktr: functions to display filelist (wrappers for romfs functions tbh) 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: config load/save using json
others: dump verification via nswdb / no-intro others: dump verification via nswdb / no-intro
others: update application feature others: update application feature