1
0
Fork 0
mirror of https://github.com/DarkMatterCore/nxdumptool.git synced 2024-12-23 00:52:10 +00:00

CNMT AuthoringTool-like XML generation.

This commit is contained in:
Pablo Curiel 2020-10-02 05:53:58 -04:00
parent 6e32829cf1
commit 98b7a309b3
13 changed files with 612 additions and 87 deletions

1
.gitignore vendored
View file

@ -9,6 +9,7 @@ build
/*.tar.bz2
/code_templates/tmp/*
/source/main.c
/*.log
# Clion files
.idea

View file

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

View file

@ -0,0 +1,257 @@
/*
* main.c
*
* Copyright (c) 2020, 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 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 <http://www.gnu.org/licenses/>.
*/
#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;
}

View file

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

View file

@ -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, \
"<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" \
"<ContentMeta>\n" \
" <Type>%s</Type>\n" \
" <Id>0x%016lx</Id>\n" \
" <Version>%u</Version>\n" \
" <RequiredDownloadSystemVersion>%u</RequiredDownloadSystemVersion>\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, \
" <Content>\n" \
" <Type>%s</Type>\n" \
" <Id>%s</Id>\n" \
" <Size>%lu</Size>\n" \
" <Hash>%s</Hash>\n" \
" <KeyGeneration>%u</KeyGeneration>\n" \
" <IdOffset>%u</IdOffset>\n" \
" </Content>\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, \
" <Digest>%s</Digest>\n" \
" <KeyGenerationMin>%u</KeyGenerationMin>\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</%s>\n" \
" <%s>0x%016lx</%s>\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, \
" <RequiredApplicationVersion>%u</RequiredApplicationVersion>\n", \
cnmtGetVersionInteger((ContentMetaVersion*)(cnmt_ctx->extended_header + sizeof(u64) + sizeof(u32))))) goto end;
}
if (!(success = utilsAppendFormattedStringToBuffer(&xml_buf, &xml_buf_size, "</ContentMeta>"))) 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;
}

View file

@ -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__ */

View file

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

View file

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

View file

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

View file

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

View file

@ -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__ */

View file

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

View file

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