diff --git a/.gitignore b/.gitignore index c878aa4..5388b81 100644 --- a/.gitignore +++ b/.gitignore @@ -9,6 +9,7 @@ build /*.tar.bz2 /code_templates/tmp/* /source/main.c +/*.log # Clion files .idea diff --git a/build.sh b/build.sh index 3f5cc8c..7c273d1 100644 --- a/build.sh +++ b/build.sh @@ -22,7 +22,7 @@ for f in ./code_templates/*.c; do { make clean make -j 12 - } &> /dev/null + } mkdir ./code_templates/tmp/$filename cp ./nxdumptool-rewrite.nro ./code_templates/tmp/$filename/nxdumptool-rewrite.nro diff --git a/code_templates/cnmt_xml_generator.c b/code_templates/cnmt_xml_generator.c new file mode 100644 index 0000000..e9d3d8f --- /dev/null +++ b/code_templates/cnmt_xml_generator.c @@ -0,0 +1,257 @@ +/* + * main.c + * + * Copyright (c) 2020, 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 and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * nxdumptool is distributed in the hope 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 "utils.h" +#include "gamecard.h" +#include "title.h" +#include "cnmt.h" + +static void consolePrint(const char *text, ...) +{ + va_list v; + va_start(v, text); + vfprintf(stdout, text, v); + va_end(v); + consoleUpdate(NULL); +} + +int main(int argc, char *argv[]) +{ + (void)argc; + (void)argv; + + int ret = 0; + + LOGFILE(APP_TITLE " starting."); + + consoleInit(NULL); + + consolePrint("initializing...\n"); + + if (!utilsInitializeResources()) + { + ret = -1; + goto out; + } + + u32 app_count = 0; + TitleApplicationMetadata **app_metadata = NULL; + TitleUserApplicationData user_app_data = {0}; + + u32 selected_idx = 0, page_size = 30, scroll = 0; + bool exit_prompt = true; + + NcaContext *nca_ctx = NULL; + Ticket tik = {0}; + ContentMetaContext cnmt_ctx = {0}; + + app_metadata = titleGetApplicationMetadataEntries(false, &app_count); + if (!app_metadata || !app_count) + { + consolePrint("app metadata failed\n"); + goto out2; + } + + consolePrint("app metadata succeeded\n"); + + utilsSleep(1); + + while(true) + { + consoleClear(); + printf("select an user application to generate a cnmt xml for.\npress b to exit.\n\n"); + printf("title: %u / %u\n\n", selected_idx + 1, app_count); + + for(u32 i = scroll; i < app_count; i++) + { + if (i >= (scroll + page_size)) break; + printf("%s%016lX - %s\n", i == selected_idx ? " -> " : " ", app_metadata[i]->title_id, app_metadata[i]->lang_entry.name); + } + + printf("\n"); + + consoleUpdate(NULL); + + u64 btn_down = 0, btn_held = 0; + while(true) + { + hidScanInput(); + btn_down = utilsHidKeysAllDown(); + btn_held = utilsHidKeysAllHeld(); + if (btn_down || btn_held) break; + + if (titleIsGameCardInfoUpdated()) + { + free(app_metadata); + + app_metadata = titleGetApplicationMetadataEntries(false, &app_count); + if (!app_metadata) + { + consolePrint("\napp metadata failed\n"); + goto out2; + } + + selected_idx = scroll = 0; + break; + } + } + + if (btn_down & KEY_A) + { + if (!titleGetUserApplicationData(app_metadata[selected_idx]->title_id, &user_app_data) || !user_app_data.app_info) + { + consolePrint("\nthe selected title doesn't have available base content.\n"); + utilsSleep(3); + continue; + } + + break; + } else + if ((btn_down & KEY_DDOWN) || (btn_held & (KEY_LSTICK_DOWN | KEY_RSTICK_DOWN))) + { + selected_idx++; + + if (selected_idx >= app_count) + { + if (btn_down & KEY_DDOWN) + { + selected_idx = scroll = 0; + } else { + selected_idx = (app_count - 1); + } + } else + if (selected_idx >= (scroll + (page_size / 2)) && app_count > (scroll + page_size)) + { + scroll++; + } + } else + if ((btn_down & KEY_DUP) || (btn_held & (KEY_LSTICK_UP | KEY_RSTICK_UP))) + { + selected_idx--; + + if (selected_idx == UINT32_MAX) + { + if (btn_down & KEY_DUP) + { + selected_idx = (app_count - 1); + scroll = (app_count >= page_size ? (app_count - page_size) : 0); + } else { + selected_idx = 0; + } + } else + if (selected_idx < (scroll + (page_size / 2)) && scroll > 0) + { + scroll--; + } + } else + if (btn_down & KEY_B) + { + exit_prompt = false; + goto out2; + } + + if (btn_held & (KEY_LSTICK_DOWN | KEY_RSTICK_DOWN | KEY_LSTICK_UP | KEY_RSTICK_UP)) svcSleepThread(50000000); // 50 ms + } + + consoleClear(); + consolePrint("selected title:\n%s (%016lX)\n\n", app_metadata[selected_idx]->lang_entry.name, app_metadata[selected_idx]->title_id); + + nca_ctx = calloc(user_app_data.app_info->content_count, sizeof(NcaContext)); + if (!nca_ctx) + { + consolePrint("nca ctx calloc failed\n"); + goto out2; + } + + consolePrint("nca ctx calloc succeeded\n"); + + for(u32 i = 0, j = 0; i < user_app_data.app_info->content_count; i++) + { + if (user_app_data.app_info->content_infos[i].content_type == NcmContentType_Meta) continue; + + if (!ncaInitializeContext(&(nca_ctx[j]), user_app_data.app_info->storage_id, (user_app_data.app_info->storage_id == NcmStorageId_GameCard ? GameCardHashFileSystemPartitionType_Secure : 0), \ + &(user_app_data.app_info->content_infos[i]), &tik)) + { + consolePrint("%s nca initialize ctx failed\n", titleGetNcmContentTypeName(user_app_data.app_info->content_infos[i].content_type)); + goto out2; + } + + consolePrint("%s nca initialize ctx succeeded\n", titleGetNcmContentTypeName(user_app_data.app_info->content_infos[i].content_type)); + j++; + } + + u32 meta_idx = (user_app_data.app_info->content_count - 1); + + if (!ncaInitializeContext(&(nca_ctx[meta_idx]), user_app_data.app_info->storage_id, (user_app_data.app_info->storage_id == NcmStorageId_GameCard ? GameCardHashFileSystemPartitionType_Secure : 0), \ + titleGetContentInfoByTypeAndIdOffset(user_app_data.app_info, NcmContentType_Meta, 0), &tik)) + { + consolePrint("Meta nca initialize ctx failed\n"); + goto out2; + } + + consolePrint("Meta nca initialize ctx succeeded\n"); + + if (!cnmtInitializeContext(&cnmt_ctx, &(nca_ctx[meta_idx]))) + { + consolePrint("cnmt initialize ctx failed\n"); + goto out2; + } + + consolePrint("cnmt initialize ctx succeeded\n"); + + if (cnmtGenerateAuthoringToolXml(&cnmt_ctx, nca_ctx, user_app_data.app_info->content_count)) + { + consolePrint("cnmt xml succeeded\n"); + + FILE *xml_fd = NULL; + char path[FS_MAX_PATH] = {0}; + + sprintf(path, "sdmc:/%s.cnmt.xml", nca_ctx[meta_idx].content_id_str); + + xml_fd = fopen(path, "wb"); + if (xml_fd) + { + fwrite(cnmt_ctx.authoring_tool_xml, 1, cnmt_ctx.authoring_tool_xml_size, xml_fd); + fclose(xml_fd); + } + } else { + consolePrint("cnmt initialize ctx failed\n"); + } + +out2: + if (exit_prompt) + { + consolePrint("press any button to exit\n"); + utilsWaitForButtonPress(KEY_NONE); + } + + cnmtFreeContext(&cnmt_ctx); + + if (nca_ctx) free(nca_ctx); + + if (app_metadata) free(app_metadata); + +out: + utilsCloseResources(); + + consoleExit(NULL); + + return ret; +} diff --git a/code_templates/system_title_dumper.c b/code_templates/system_title_dumper.c index 06439ac..5d96641 100644 --- a/code_templates/system_title_dumper.c +++ b/code_templates/system_title_dumper.c @@ -23,7 +23,6 @@ #include "title.h" #include "pfs.h" #include "romfs.h" -#include "cnmt.h" #define BLOCK_SIZE 0x800000 #define OUTPATH "sdmc:/systitle_dumps" @@ -355,42 +354,6 @@ int main(int argc, char *argv[]) { consolePrint("nca initialize ctx failed\n"); error = true; - } else { - if (nca_ctx->content_type == NcmContentType_Meta) - { - ContentMetaContext cnmt_ctx = {0}; - FILE *cnmt_fd = NULL; - size_t path_len = 0; - - snprintf(path, sizeof(path), OUTPATH "/%016lX - %s/%s (%s)", cur_title_info->meta_key.id, cur_title_info->app_metadata->lang_entry.name, nca_ctx->content_id_str, \ - titleGetNcmContentTypeName(nca_ctx->content_type)); - utilsCreateDirectoryTree(path, true); - path_len = strlen(path); - - if (cnmtInitializeContext(&cnmt_ctx, nca_ctx)) - { - snprintf(path + path_len, sizeof(path) - path_len, "/%s", cnmt_ctx.cnmt_filename); - cnmt_fd = fopen(path, "wb"); - if (cnmt_fd) - { - fwrite(cnmt_ctx.raw_data, 1, cnmt_ctx.raw_data_size, cnmt_fd); - fclose(cnmt_fd); - cnmt_fd = NULL; - } - - path[path_len] = '\0'; - snprintf(path + path_len, sizeof(path) - path_len, "/%s.ctx", cnmt_ctx.cnmt_filename); - cnmt_fd = fopen(path, "wb"); - if (cnmt_fd) - { - fwrite(&cnmt_ctx, 1, sizeof(ContentMetaContext), cnmt_fd); - fclose(cnmt_fd); - cnmt_fd = NULL; - } - - cnmtFreeContext(&cnmt_ctx); - } - } } } else if (menu == 3) diff --git a/source/cnmt.c b/source/cnmt.c index 514c806..a9b5828 100644 --- a/source/cnmt.c +++ b/source/cnmt.c @@ -28,6 +28,9 @@ static bool cnmtGetContentMetaTypeAndTitleIdFromFileName(const char *cnmt_filename, size_t cnmt_filename_len, u8 *out_content_meta_type, u64 *out_title_id); +static const char *cnmtGetRequiredTitleVersionString(u8 content_meta_type); +static const char *cnmtGetRequiredTitleTypeString(u8 content_meta_type); + bool cnmtInitializeContext(ContentMetaContext *out, NcaContext *nca_ctx) { if (!out || !nca_ctx || !strlen(nca_ctx->content_id_str) || nca_ctx->content_type != NcmContentType_Meta || nca_ctx->content_size < NCA_FULL_HEADER_LENGTH || \ @@ -111,6 +114,9 @@ bool cnmtInitializeContext(ContentMetaContext *out, NcaContext *nca_ctx) goto end; } + /* Calculate SHA-256 checksum for the whole raw CNMT. */ + sha256CalculateHash(out->raw_data_hash, out->raw_data, out->raw_data_size); + /* Save pointer to NCA context to the output CNMT context. */ out->nca_ctx = nca_ctx; @@ -143,7 +149,7 @@ bool cnmtInitializeContext(ContentMetaContext *out, NcaContext *nca_ctx) goto end; } - /* Save pointer to extended header */ + /* Save pointer to extended header. */ if (out->packaged_header->extended_header_size) { out->extended_header = (out->raw_data + cur_offset); @@ -227,6 +233,128 @@ end: return success; } +bool cnmtGenerateAuthoringToolXml(ContentMetaContext *cnmt_ctx, NcaContext *nca_ctx, u32 nca_ctx_count) +{ + if (!cnmtIsValidContext(cnmt_ctx) || !nca_ctx || nca_ctx_count != ((u32)cnmt_ctx->packaged_header->content_count + 1)) + { + LOGFILE("Invalid parameters!"); + return false; + } + + u16 i, j; + char *xml_buf = NULL; + u64 xml_buf_size = 0; + char digest_str[0x41] = {0}; + bool success = false, invalid_nca = false; + + /* Free AuthoringTool-like XML data if needed. */ + if (cnmt_ctx->authoring_tool_xml) free(cnmt_ctx->authoring_tool_xml); + cnmt_ctx->authoring_tool_xml = NULL; + cnmt_ctx->authoring_tool_xml_size = 0; + + if (!utilsAppendFormattedStringToBuffer(&xml_buf, &xml_buf_size, \ + "\n" \ + "\n" \ + " %s\n" \ + " 0x%016lx\n" \ + " %u\n" \ + " %u\n", \ + titleGetNcmContentMetaTypeName(cnmt_ctx->packaged_header->content_meta_type), \ + cnmt_ctx->packaged_header->title_id, \ + cnmtGetVersionInteger(&(cnmt_ctx->packaged_header->version)), \ + cnmtGetVersionInteger(&(cnmt_ctx->packaged_header->required_download_system_version)))) goto end; + + for(i = 0; i < nca_ctx_count; i++) + { + /* Check if this NCA is really referenced by our CNMT. */ + if (nca_ctx[i].content_type != NcmContentType_Meta) + { + /* Non-Meta NCAs: check if their content IDs are part of the packaged content info entries from the CNMT. */ + for(j = 0; j < cnmt_ctx->packaged_header->content_count; j++) + { + if (!memcmp(cnmt_ctx->packaged_content_info[j].info.content_id.c, nca_ctx[i].content_id.c, 0x10)) break; + } + + invalid_nca = (j >= cnmt_ctx->packaged_header->content_count); + } else { + /* Meta NCAs: quick and dirty pointer comparison because why not. */ + invalid_nca = (cnmt_ctx->nca_ctx != &(nca_ctx[i])); + } + + if (invalid_nca) + { + LOGFILE("NCA \"%s\" isn't referenced by this CNMT!", nca_ctx[i].content_id_str); + goto end; + } + + if (!utilsAppendFormattedStringToBuffer(&xml_buf, &xml_buf_size, \ + " \n" \ + " %s\n" \ + " %s\n" \ + " %lu\n" \ + " %s\n" \ + " %u\n" \ + " %u\n" \ + " \n", \ + titleGetNcmContentTypeName(nca_ctx[i].content_type), \ + nca_ctx[i].content_id_str, \ + nca_ctx[i].content_size, \ + nca_ctx[i].hash_str, \ + nca_ctx[i].key_generation, \ + nca_ctx[i].id_offset)) goto end; + } + + utilsGenerateHexStringFromData(digest_str, sizeof(digest_str), cnmt_ctx->digest, CNMT_DIGEST_SIZE); + + if (!utilsAppendFormattedStringToBuffer(&xml_buf, &xml_buf_size, \ + " %s\n" \ + " %u\n", \ + digest_str, \ + cnmt_ctx->nca_ctx->key_generation)) goto end; + + if (cnmt_ctx->packaged_header->content_meta_type == NcmContentMetaType_Application || cnmt_ctx->packaged_header->content_meta_type == NcmContentMetaType_Patch || \ + cnmt_ctx->packaged_header->content_meta_type == NcmContentMetaType_AddOnContent) + { + u32 required_title_version = cnmtGetVersionInteger((ContentMetaVersion*)(cnmt_ctx->extended_header + sizeof(u64))); + const char *required_title_version_str = cnmtGetRequiredTitleVersionString(cnmt_ctx->packaged_header->content_meta_type); + + u64 required_title_id = *((u64*)cnmt_ctx->extended_header); + const char *required_title_type_str = cnmtGetRequiredTitleTypeString(cnmt_ctx->packaged_header->content_meta_type); + + if (!utilsAppendFormattedStringToBuffer(&xml_buf, &xml_buf_size, \ + " <%s>%u\n" \ + " <%s>0x%016lx\n", \ + required_title_version_str, \ + required_title_version, \ + required_title_version_str, \ + required_title_type_str, \ + required_title_id, \ + required_title_type_str)) goto end; + } + + if (cnmt_ctx->packaged_header->content_meta_type == NcmContentMetaType_Application) + { + if (!utilsAppendFormattedStringToBuffer(&xml_buf, &xml_buf_size, \ + " %u\n", \ + cnmtGetVersionInteger((ContentMetaVersion*)(cnmt_ctx->extended_header + sizeof(u64) + sizeof(u32))))) goto end; + } + + if (!(success = utilsAppendFormattedStringToBuffer(&xml_buf, &xml_buf_size, ""))) goto end; + + /* Update CNMT context. */ + cnmt_ctx->authoring_tool_xml = xml_buf; + cnmt_ctx->authoring_tool_xml_size = strlen(xml_buf); + +end: + if (!success) + { + if (xml_buf) free(xml_buf); + LOGFILE("Failed to generate CNMT AuthoringTool XML!"); + } + + return success; +} + static bool cnmtGetContentMetaTypeAndTitleIdFromFileName(const char *cnmt_filename, size_t cnmt_filename_len, u8 *out_content_meta_type, u64 *out_title_id) { if (!cnmt_filename || cnmt_filename_len < CNMT_MINIMUM_FILENAME_LENGTH || !out_content_meta_type || !out_title_id) @@ -277,3 +405,47 @@ static bool cnmtGetContentMetaTypeAndTitleIdFromFileName(const char *cnmt_filena return true; } + +static const char *cnmtGetRequiredTitleVersionString(u8 content_meta_type) +{ + const char *str = NULL; + + switch(content_meta_type) + { + case NcmContentMetaType_Application: + case NcmContentMetaType_Patch: + str = "RequiredSystemVersion"; + break; + case NcmContentMetaType_AddOnContent: + str = "RequiredApplicationVersion"; + break; + default: + str = "Unknown"; + break; + } + + return str; +} + +static const char *cnmtGetRequiredTitleTypeString(u8 content_meta_type) +{ + const char *str = NULL; + + switch(content_meta_type) + { + case NcmContentMetaType_Application: + str = "PatchId"; + break; + case NcmContentMetaType_Patch: + str = "OriginalId"; + break; + case NcmContentMetaType_AddOnContent: + str = "ApplicationId"; + break; + default: + str = "Unknown"; + break; + } + + return str; +} diff --git a/source/cnmt.h b/source/cnmt.h index 64888c6..b4c6db4 100644 --- a/source/cnmt.h +++ b/source/cnmt.h @@ -23,7 +23,6 @@ #ifndef __CNMT_H__ #define __CNMT_H__ -#include "nca.h" #include "pfs.h" #define CNMT_DIGEST_SIZE SHA256_HASH_SIZE @@ -231,7 +230,8 @@ typedef struct { ///< Bear in mind that generating a patch modifies the NCA context. char *cnmt_filename; ///< Pointer to the CNMT filename in the Meta NCA FS section #0. u8 *raw_data; ///< Pointer to a dynamically allocated buffer that holds the raw CNMT. - u64 raw_data_size; ///< Raw CNMT size. + u64 raw_data_size; ///< Raw CNMT size. Kept here for convenience - this is part of 'pfs_entry'. + u8 raw_data_hash[SHA256_HASH_SIZE]; ///< SHA-256 checksum calculated over the whole raw CNMT. Used to determine if NcaHierarchicalSha256Patch generation is truly needed. ContentMetaPackagedHeader *packaged_header; ///< Pointer to the ContentMetaPackagedHeader within 'raw_data'. u8 *extended_header; ///< Pointer to the extended header within 'raw_data', if available. May be casted to other types. Its size is stored in 'packaged_header'. NcmPackagedContentInfo *packaged_content_info; ///< Pointer to the NcmPackagedContentInfo entries within 'raw_data'. The content count is stored in 'packaged_header'. @@ -239,11 +239,19 @@ typedef struct { u8 *extended_data; ///< Pointer to the extended data block within 'raw_data', if available. u32 extended_data_size; ///< Size of the extended data block within 'raw_data', if available. Kept here for convenience - this is part of the header in 'extended_data'. u8 *digest; ///< Pointer to the digest within 'raw_data'. + char *authoring_tool_xml; ///< Pointer to a dynamically allocated, NULL-terminated buffer that holds AuthoringTool-like XML data. + ///< This is always NULL unless cnmtGenerateAuthoringToolXml() is used on this ContentMetaContext. + u64 authoring_tool_xml_size; ///< Size for the AuthoringTool-like XML. This is essentially the same as using strlen() on 'authoring_tool_xml'. + ///< This is always 0 unless cnmtGenerateAuthoringToolXml() is used on this ContentMetaContext. } ContentMetaContext; /// Initializes a ContentMetaContext using a previously initialized NcaContext (which must belong to a Meta NCA). bool cnmtInitializeContext(ContentMetaContext *out, NcaContext *nca_ctx); +/// Generates an AuthoringTool-like XML using information from a previously initialized ContentMetaContext, as well as a pointer to 'nca_ctx_count' NcaContext with content information. +/// If the function succeeds, XML data and size will get saved to the 'authoring_tool_xml' and 'authoring_tool_xml_size' members from the ContentMetaContext. +bool cnmtGenerateAuthoringToolXml(ContentMetaContext *cnmt_ctx, NcaContext *nca_ctx, u32 nca_ctx_count); + /// Helper inline functions. NX_INLINE void cnmtFreeContext(ContentMetaContext *cnmt_ctx) @@ -252,7 +260,34 @@ NX_INLINE void cnmtFreeContext(ContentMetaContext *cnmt_ctx) pfsFreeContext(&(cnmt_ctx->pfs_ctx)); pfsFreeEntryPatch(&(cnmt_ctx->nca_patch)); if (cnmt_ctx->raw_data) free(cnmt_ctx->raw_data); + if (cnmt_ctx->authoring_tool_xml) free(cnmt_ctx->authoring_tool_xml); memset(cnmt_ctx, 0, sizeof(ContentMetaContext)); } +NX_INLINE bool cnmtIsValidContext(ContentMetaContext *cnmt_ctx) +{ + return (cnmt_ctx && cnmt_ctx->nca_ctx && cnmt_ctx->pfs_entry && cnmt_ctx->cnmt_filename && cnmt_ctx->raw_data && cnmt_ctx->raw_data_size && cnmt_ctx->packaged_header && \ + ((cnmt_ctx->packaged_header->extended_header_size && cnmt_ctx->extended_header) || (!cnmt_ctx->packaged_header->extended_header_size && !cnmt_ctx->extended_header)) && \ + cnmt_ctx->packaged_content_info && ((cnmt_ctx->packaged_header->content_meta_count && cnmt_ctx->content_meta_info) || (!cnmt_ctx->packaged_header->content_meta_count && \ + !cnmt_ctx->content_meta_info)) && ((cnmt_ctx->extended_data_size && cnmt_ctx->extended_data) || (!cnmt_ctx->extended_data_size && !cnmt_ctx->extended_data)) && cnmt_ctx->digest); +} + +NX_INLINE bool cnmtIsNcaPatchRequired(ContentMetaContext *cnmt_ctx) +{ + if (!cnmtIsValidContext(cnmt_ctx) || cnmt_ctx->nca_patch.hash_region_count) return false; + u8 tmp_hash[SHA256_HASH_SIZE] = {0}; + sha256CalculateHash(tmp_hash, cnmt_ctx->raw_data, cnmt_ctx->raw_data_size); + return (memcmp(tmp_hash, cnmt_ctx->raw_data_hash, SHA256_HASH_SIZE) != 0); +} + +NX_INLINE bool cnmtGenerateNcaPatch(ContentMetaContext *cnmt_ctx) +{ + return (cnmtIsValidContext(cnmt_ctx) ? pfsGenerateEntryPatch(&(cnmt_ctx->pfs_ctx), cnmt_ctx->pfs_entry, cnmt_ctx->raw_data, cnmt_ctx->raw_data_size, 0, &(cnmt_ctx->nca_patch)) : false); +} + +NX_INLINE u32 cnmtGetVersionInteger(ContentMetaVersion *version) +{ + return (version ? *((u32*)version) : 0); +} + #endif /* __CNMT_H__ */ diff --git a/source/mem.c b/source/mem.c index d4143a8..c075760 100644 --- a/source/mem.c +++ b/source/mem.c @@ -22,11 +22,12 @@ #include "utils.h" #include "mem.h" -#define MEMLOG(fmt, ...) LOGBUF(g_memLogBuf, sizeof(g_memLogBuf), fmt, ##__VA_ARGS__) +#define MEMLOG(fmt, ...) LOGBUF(&g_memLogBuf, &g_memLogBufSize, fmt, ##__VA_ARGS__) /* Global variables. */ -static char g_memLogBuf[512] = {0}; +static char *g_memLogBuf = NULL; +static size_t g_memLogBufSize = 0; static Mutex g_memMutex = 0; /* Function prototypes. */ @@ -78,8 +79,6 @@ static bool memRetrieveProgramMemory(MemoryLocation *location, bool is_segment) bool success = true; - *g_memLogBuf = '\0'; - /* Clear output MemoryLocation element. */ memFreeMemoryLocation(location); @@ -167,8 +166,7 @@ end: if (success && (!location->data || !location->data_size)) { - MEMLOG("total size: 0x%lX", location->data_size); - MEMLOG("Unable to locate readable program %016lX memory pages that match the required criteria!", location->program_id); + MEMLOG("Unable to locate readable program memory pages for %016lX that match the required criteria!", location->program_id); success = false; } @@ -177,6 +175,15 @@ end: /* Write log buffer data. This will do nothing if the log buffer length is zero. */ utilsWriteLogBufferToLogFile(g_memLogBuf); + /* Free memory log buffer. */ + if (g_memLogBuf) + { + free(g_memLogBuf); + g_memLogBuf = NULL; + } + + g_memLogBufSize = 0; + return success; } diff --git a/source/nca.h b/source/nca.h index 10e86c5..a32d240 100644 --- a/source/nca.h +++ b/source/nca.h @@ -90,8 +90,8 @@ typedef enum { NcaKeyGeneration_700_801 = 8, NcaKeyGeneration_810_811 = 9, NcaKeyGeneration_900_901 = 10, - NcaKeyGeneration_910_1004 = 11, - NcaKeyGeneration_Current = NcaKeyGeneration_910_1004 + NcaKeyGeneration_910_1020 = 11, + NcaKeyGeneration_Current = NcaKeyGeneration_910_1020 } NcaKeyGeneration; typedef struct { diff --git a/source/pfs.c b/source/pfs.c index 9be1633..15a4b0f 100644 --- a/source/pfs.c +++ b/source/pfs.c @@ -38,7 +38,7 @@ bool pfsInitializeContext(PartitionFileSystemContext *out, NcaFsSectionContext * u32 hash_region_count = 0; NcaRegion *hash_region = NULL; - /* Clear output partition FS context. */ + /* Clear output Partition FS context. */ memset(out, 0, sizeof(PartitionFileSystemContext)); /* Fill context. */ @@ -59,38 +59,38 @@ bool pfsInitializeContext(PartitionFileSystemContext *out, NcaFsSectionContext * /* Read partial PFS header. */ if (!ncaReadFsSection(nca_fs_ctx, &pfs_header, sizeof(PartitionFileSystemHeader), out->offset)) { - LOGFILE("Failed to read partial partition FS header!"); + LOGFILE("Failed to read partial Partition FS header!"); return false; } magic = __builtin_bswap32(pfs_header.magic); if (magic != PFS0_MAGIC) { - LOGFILE("Invalid partition FS magic word! (0x%08X).", magic); + LOGFILE("Invalid Partition FS magic word! (0x%08X).", magic); return false; } if (!pfs_header.entry_count || !pfs_header.name_table_size) { - LOGFILE("Invalid partition FS entry count / name table size!"); + LOGFILE("Invalid Partition FS entry count / name table size!"); return false; } - /* Calculate full partition FS header size. */ + /* Calculate full Partition FS header size. */ out->header_size = (sizeof(PartitionFileSystemHeader) + (pfs_header.entry_count * sizeof(PartitionFileSystemEntry)) + pfs_header.name_table_size); - /* Allocate memory for the full partition FS header. */ + /* Allocate memory for the full Partition FS header. */ out->header = calloc(out->header_size, sizeof(u8)); if (!out->header) { - LOGFILE("Unable to allocate 0x%lX bytes buffer for the full partition FS header!", out->header_size); + LOGFILE("Unable to allocate 0x%lX bytes buffer for the full Partition FS header!", out->header_size); return false; } - /* Read full partition FS header. */ + /* Read full Partition FS header. */ if (!ncaReadFsSection(nca_fs_ctx, out->header, out->header_size, out->offset)) { - LOGFILE("Failed to read full partition FS header!"); + LOGFILE("Failed to read full Partition FS header!"); pfsFreeContext(out); return false; } @@ -113,7 +113,7 @@ bool pfsReadPartitionData(PartitionFileSystemContext *ctx, void *out, u64 read_s /* Read partition data. */ if (!ncaReadFsSection(ctx->nca_fs_ctx, out, read_size, ctx->offset + offset)) { - LOGFILE("Failed to read partition FS data!"); + LOGFILE("Failed to read Partition FS data!"); return false; } @@ -132,7 +132,7 @@ bool pfsReadEntryData(PartitionFileSystemContext *ctx, PartitionFileSystemEntry /* Read entry data. */ if (!pfsReadPartitionData(ctx, out, read_size, ctx->header_size + fs_entry->offset + offset)) { - LOGFILE("Failed to read partition FS entry data!"); + LOGFILE("Failed to read Partition FS entry data!"); return false; } @@ -156,7 +156,7 @@ bool pfsGetEntryIndexByName(PartitionFileSystemContext *ctx, const char *name, u { if (!(fs_entry = pfsGetEntryByIndex(ctx, i))) { - LOGFILE("Failed to retrieve partition FS entry #%u!", i); + LOGFILE("Failed to retrieve Partition FS entry #%u!", i); return false; } @@ -167,7 +167,7 @@ bool pfsGetEntryIndexByName(PartitionFileSystemContext *ctx, const char *name, u } } - //LOGFILE("Unable to find partition FS entry \"%s\"!", name); + //LOGFILE("Unable to find Partition FS entry \"%s\"!", name); return false; } @@ -188,7 +188,7 @@ bool pfsGetTotalDataSize(PartitionFileSystemContext *ctx, u64 *out_size) { if (!(fs_entry = pfsGetEntryByIndex(ctx, i))) { - LOGFILE("Failed to retrieve partition FS entry #%u!", i); + LOGFILE("Failed to retrieve Partition FS entry #%u!", i); return false; } @@ -213,7 +213,7 @@ bool pfsGenerateEntryPatch(PartitionFileSystemContext *ctx, PartitionFileSystemE if (!ncaGenerateHierarchicalSha256Patch(ctx->nca_fs_ctx, data, data_size, partition_offset, out)) { - LOGFILE("Failed to generate 0x%lX bytes HierarchicalSha256 patch at offset 0x%lX for partition FS entry!", data_size, partition_offset); + LOGFILE("Failed to generate 0x%lX bytes HierarchicalSha256 patch at offset 0x%lX for Partition FS entry!", data_size, partition_offset); return false; } diff --git a/source/pfs.h b/source/pfs.h index df2d9ab..8525a7d 100644 --- a/source/pfs.h +++ b/source/pfs.h @@ -50,25 +50,25 @@ typedef struct { u8 *header; ///< PartitionFileSystemHeader + (PartitionFileSystemEntry * entry_count) + Name Table. } PartitionFileSystemContext; -/// Initializes a partition FS context. +/// Initializes a Partition FS context. bool pfsInitializeContext(PartitionFileSystemContext *out, NcaFsSectionContext *nca_fs_ctx); -/// Reads raw partition data using a partition FS context. -/// Input offset must be relative to the start of the partition FS. +/// Reads raw partition data using a Partition FS context. +/// Input offset must be relative to the start of the Partition FS. bool pfsReadPartitionData(PartitionFileSystemContext *ctx, void *out, u64 read_size, u64 offset); -/// Reads data from a previously retrieved PartitionFileSystemEntry using a partition FS context. -/// Input offset must be relative to the start of the partition FS entry. +/// Reads data from a previously retrieved PartitionFileSystemEntry using a Partition FS context. +/// Input offset must be relative to the start of the Partition FS entry. bool pfsReadEntryData(PartitionFileSystemContext *ctx, PartitionFileSystemEntry *fs_entry, void *out, u64 read_size, u64 offset); -/// Retrieves a partition FS entry index by its name. +/// Retrieves a Partition FS entry index by its name. bool pfsGetEntryIndexByName(PartitionFileSystemContext *ctx, const char *name, u32 *out_idx); -/// Calculates the extracted partition FS size. +/// Calculates the extracted Partition FS size. bool pfsGetTotalDataSize(PartitionFileSystemContext *ctx, u64 *out_size); -/// Generates HierarchicalSha256 FS section patch data using a partition FS context + entry, which can be used to seamlessly replace NCA data. -/// Input offset must be relative to the start of the partition FS entry data. +/// Generates HierarchicalSha256 FS section patch data using a Partition FS context + entry, which can be used to seamlessly replace NCA data. +/// Input offset must be relative to the start of the Partition FS entry data. /// This function shares the same limitations as ncaGenerateHierarchicalSha256Patch(). /// Use the pfsWriteEntryPatchToMemoryBuffer() wrapper to write patch data generated by this function. bool pfsGenerateEntryPatch(PartitionFileSystemContext *ctx, PartitionFileSystemEntry *fs_entry, const void *data, u64 data_size, u64 data_offset, NcaHierarchicalSha256Patch *out); diff --git a/source/title.h b/source/title.h index 469236e..ee9112e 100644 --- a/source/title.h +++ b/source/title.h @@ -31,7 +31,7 @@ #define TITLE_DELTA_TYPE_VALUE (u64)0xC00 -/// Used to display version numbers in dot notation (major.minor.micro-major_relstep.minor_relstep). +/// Used to display version numbers in dot notation: "{Major}.{Minor}.{Micro}-{MajorRelstep}.{MinorRelstep}". typedef struct { u32 TitleVersion_MinorRelstep : 8; u32 TitleVersion_MajorRelstep : 8; @@ -130,10 +130,10 @@ char *titleGenerateFileName(const TitleInfo *title_info, u8 name_convention, u8 /// Returns NULL if an error occurs. char *titleGenerateGameCardFileName(u8 name_convention, u8 illegal_char_replace_type); -/// Returns a pointer to a string holding the name of the provided ncm content type. +/// Returns a pointer to a string holding the name of the provided NcmContentType value. const char *titleGetNcmContentTypeName(u8 content_type); -/// Returns a pointer to a string holding the name of the provided ncm content meta type. +/// Returns a pointer to a string holding the name of the provided NcmContentMetaType value. const char *titleGetNcmContentMetaTypeName(u8 content_meta_type); /// Miscellaneous functions. @@ -230,4 +230,9 @@ NX_INLINE NcmContentInfo *titleGetContentInfoByTypeAndIdOffset(TitleInfo *info, return NULL; } +NX_INLINE u32 titleGetVersionInteger(TitleVersion *version) +{ + return (version ? *((u32*)version) : 0); +} + #endif /* __TITLE_H__ */ diff --git a/source/utils.c b/source/utils.c index ce4c17f..67ed8a5 100644 --- a/source/utils.c +++ b/source/utils.c @@ -317,6 +317,63 @@ void utilsWaitForButtonPress(u64 flag) } } +bool utilsAppendFormattedStringToBuffer(char **dst, size_t *dst_size, const char *fmt, ...) +{ + if (!dst || !dst_size || !fmt || !strlen(fmt)) + { + LOGFILE("Invalid parameters!"); + return false; + } + + va_list args; + va_start(args, fmt); + + int formatted_str_len = 0; + size_t required_dst_size = 0, dst_str_len = (*dst ? strlen(*dst) : 0); + char *realloc_dst = NULL; + + bool success = false; + + if (dst_str_len > *dst_size) + { + **dst = '\0'; + dst_str_len = 0; + } + + formatted_str_len = vsnprintf(NULL, 0, fmt, args); + if (formatted_str_len <= 0) + { + LOGFILE("Failed to retrieve formatted string length!"); + goto end; + } + + required_dst_size = (dst_str_len + (size_t)formatted_str_len + 1); + if (required_dst_size > *dst_size) + { + realloc_dst = realloc(*dst, required_dst_size); + if (!realloc_dst) + { + LOGFILE("Failed to reallocate destination buffer!"); + goto end; + } + + *dst = realloc_dst; + realloc_dst = NULL; + + memset(*dst + dst_str_len, 0, (size_t)formatted_str_len + 1); + + *dst_size = required_dst_size; + } + + vsprintf(*dst + dst_str_len, fmt, args); + success = true; + +end: + va_end(args); + + return success; +} + void utilsWriteMessageToLogFile(const char *func_name, const char *fmt, ...) { if (!func_name || !strlen(func_name) || !fmt || !strlen(fmt)) return; @@ -346,26 +403,52 @@ end: mutexUnlock(&g_logfileMutex); } -void utilsWriteMessageToLogBuffer(char *dst, size_t dst_size, const char *func_name, const char *fmt, ...) +void utilsWriteMessageToLogBuffer(char **dst, size_t *dst_size, const char *func_name, const char *fmt, ...) { if (!dst || !dst_size || !func_name || !strlen(func_name) || !fmt || !strlen(fmt)) return; va_list args; + va_start(args, fmt); + time_t now = time(NULL); struct tm *ts = localtime(&now); - char msg[512] = {0}; - size_t msg_len = 0, dst_len = strlen(dst); + int timestamp_len = 0, formatted_str_len = 0; + size_t required_dst_size = 0, dst_str_len = (*dst ? strlen(*dst) : 0); + char *realloc_dst = NULL; - snprintf(msg, sizeof(msg), "%d-%02d-%02d %02d:%02d:%02d -> %s: ", ts->tm_year + 1900, ts->tm_mon + 1, ts->tm_mday, ts->tm_hour, ts->tm_min, ts->tm_sec, func_name); - msg_len = strlen(msg); + if (dst_str_len > *dst_size) + { + **dst = '\0'; + dst_str_len = 0; + } - va_start(args, fmt); - vsnprintf(msg + msg_len, sizeof(msg) - msg_len, fmt, args); + timestamp_len = snprintf(NULL, 0, "%d-%02d-%02d %02d:%02d:%02d -> %s: ", ts->tm_year + 1900, ts->tm_mon + 1, ts->tm_mday, ts->tm_hour, ts->tm_min, ts->tm_sec, func_name); + if (timestamp_len <= 0) goto end; + + formatted_str_len = vsnprintf(NULL, 0, fmt, args); + if (formatted_str_len <= 0) goto end; + + required_dst_size = (dst_str_len + (size_t)timestamp_len + (size_t)formatted_str_len + 3); + if (required_dst_size > *dst_size) + { + realloc_dst = realloc(*dst, required_dst_size); + if (!realloc_dst) goto end; + + *dst = realloc_dst; + realloc_dst = NULL; + + memset(*dst + dst_str_len, 0, (size_t)timestamp_len + (size_t)formatted_str_len + 3); + + *dst_size = required_dst_size; + } + + sprintf(*dst + dst_str_len, "%d-%02d-%02d %02d:%02d:%02d -> %s: ", ts->tm_year + 1900, ts->tm_mon + 1, ts->tm_mday, ts->tm_hour, ts->tm_min, ts->tm_sec, func_name); + vsprintf(*dst + dst_str_len + (size_t)timestamp_len, fmt, args); + sprintf(*dst + dst_str_len + (size_t)timestamp_len + (size_t)formatted_str_len, "\r\n"); + +end: va_end(args); - msg_len = strlen(msg); - - if ((dst_size - dst_len) > (msg_len + 2)) snprintf(dst + dst_len, dst_size - dst_len, "%s\r\n", msg); } void utilsWriteLogBufferToLogFile(const char *src) diff --git a/source/utils.h b/source/utils.h index 33e46e6..f6de759 100644 --- a/source/utils.h +++ b/source/utils.h @@ -86,8 +86,10 @@ u64 utilsHidKeysAllHeld(void); void utilsWaitForButtonPress(u64 flag); +bool utilsAppendFormattedStringToBuffer(char **dst, size_t *dst_size, const char *fmt, ...); + void utilsWriteMessageToLogFile(const char *func_name, const char *fmt, ...); -void utilsWriteMessageToLogBuffer(char *dst, size_t dst_size, const char *func_name, const char *fmt, ...); +void utilsWriteMessageToLogBuffer(char **dst, size_t *dst_size, const char *func_name, const char *fmt, ...); void utilsWriteLogBufferToLogFile(const char *src); void utilsLogFileMutexControl(bool lock);