mirror of
https://github.com/DarkMatterCore/nxdumptool.git
synced 2024-11-22 18:26:39 +00:00
CNMT AuthoringTool-like XML generation.
This commit is contained in:
parent
6e32829cf1
commit
98b7a309b3
13 changed files with 612 additions and 87 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -9,6 +9,7 @@ build
|
||||||
/*.tar.bz2
|
/*.tar.bz2
|
||||||
/code_templates/tmp/*
|
/code_templates/tmp/*
|
||||||
/source/main.c
|
/source/main.c
|
||||||
|
/*.log
|
||||||
|
|
||||||
# Clion files
|
# Clion files
|
||||||
.idea
|
.idea
|
||||||
|
|
2
build.sh
2
build.sh
|
@ -22,7 +22,7 @@ for f in ./code_templates/*.c; do
|
||||||
{
|
{
|
||||||
make clean
|
make clean
|
||||||
make -j 12
|
make -j 12
|
||||||
} &> /dev/null
|
}
|
||||||
|
|
||||||
mkdir ./code_templates/tmp/$filename
|
mkdir ./code_templates/tmp/$filename
|
||||||
cp ./nxdumptool-rewrite.nro ./code_templates/tmp/$filename/nxdumptool-rewrite.nro
|
cp ./nxdumptool-rewrite.nro ./code_templates/tmp/$filename/nxdumptool-rewrite.nro
|
||||||
|
|
257
code_templates/cnmt_xml_generator.c
Normal file
257
code_templates/cnmt_xml_generator.c
Normal 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;
|
||||||
|
}
|
|
@ -23,7 +23,6 @@
|
||||||
#include "title.h"
|
#include "title.h"
|
||||||
#include "pfs.h"
|
#include "pfs.h"
|
||||||
#include "romfs.h"
|
#include "romfs.h"
|
||||||
#include "cnmt.h"
|
|
||||||
|
|
||||||
#define BLOCK_SIZE 0x800000
|
#define BLOCK_SIZE 0x800000
|
||||||
#define OUTPATH "sdmc:/systitle_dumps"
|
#define OUTPATH "sdmc:/systitle_dumps"
|
||||||
|
@ -355,42 +354,6 @@ int main(int argc, char *argv[])
|
||||||
{
|
{
|
||||||
consolePrint("nca initialize ctx failed\n");
|
consolePrint("nca initialize ctx failed\n");
|
||||||
error = true;
|
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
|
} else
|
||||||
if (menu == 3)
|
if (menu == 3)
|
||||||
|
|
174
source/cnmt.c
174
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 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)
|
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 || \
|
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;
|
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. */
|
/* Save pointer to NCA context to the output CNMT context. */
|
||||||
out->nca_ctx = nca_ctx;
|
out->nca_ctx = nca_ctx;
|
||||||
|
|
||||||
|
@ -143,7 +149,7 @@ bool cnmtInitializeContext(ContentMetaContext *out, NcaContext *nca_ctx)
|
||||||
goto end;
|
goto end;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Save pointer to extended header */
|
/* Save pointer to extended header. */
|
||||||
if (out->packaged_header->extended_header_size)
|
if (out->packaged_header->extended_header_size)
|
||||||
{
|
{
|
||||||
out->extended_header = (out->raw_data + cur_offset);
|
out->extended_header = (out->raw_data + cur_offset);
|
||||||
|
@ -227,6 +233,128 @@ end:
|
||||||
return success;
|
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)
|
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)
|
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;
|
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;
|
||||||
|
}
|
||||||
|
|
|
@ -23,7 +23,6 @@
|
||||||
#ifndef __CNMT_H__
|
#ifndef __CNMT_H__
|
||||||
#define __CNMT_H__
|
#define __CNMT_H__
|
||||||
|
|
||||||
#include "nca.h"
|
|
||||||
#include "pfs.h"
|
#include "pfs.h"
|
||||||
|
|
||||||
#define CNMT_DIGEST_SIZE SHA256_HASH_SIZE
|
#define CNMT_DIGEST_SIZE SHA256_HASH_SIZE
|
||||||
|
@ -231,7 +230,8 @@ typedef struct {
|
||||||
///< Bear in mind that generating a patch modifies the NCA context.
|
///< 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.
|
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.
|
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'.
|
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'.
|
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'.
|
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.
|
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'.
|
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'.
|
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;
|
} ContentMetaContext;
|
||||||
|
|
||||||
/// Initializes a ContentMetaContext using a previously initialized NcaContext (which must belong to a Meta NCA).
|
/// Initializes a ContentMetaContext using a previously initialized NcaContext (which must belong to a Meta NCA).
|
||||||
bool cnmtInitializeContext(ContentMetaContext *out, NcaContext *nca_ctx);
|
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.
|
/// Helper inline functions.
|
||||||
|
|
||||||
NX_INLINE void cnmtFreeContext(ContentMetaContext *cnmt_ctx)
|
NX_INLINE void cnmtFreeContext(ContentMetaContext *cnmt_ctx)
|
||||||
|
@ -252,7 +260,34 @@ NX_INLINE void cnmtFreeContext(ContentMetaContext *cnmt_ctx)
|
||||||
pfsFreeContext(&(cnmt_ctx->pfs_ctx));
|
pfsFreeContext(&(cnmt_ctx->pfs_ctx));
|
||||||
pfsFreeEntryPatch(&(cnmt_ctx->nca_patch));
|
pfsFreeEntryPatch(&(cnmt_ctx->nca_patch));
|
||||||
if (cnmt_ctx->raw_data) free(cnmt_ctx->raw_data);
|
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));
|
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__ */
|
#endif /* __CNMT_H__ */
|
||||||
|
|
19
source/mem.c
19
source/mem.c
|
@ -22,11 +22,12 @@
|
||||||
#include "utils.h"
|
#include "utils.h"
|
||||||
#include "mem.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. */
|
/* Global variables. */
|
||||||
|
|
||||||
static char g_memLogBuf[512] = {0};
|
static char *g_memLogBuf = NULL;
|
||||||
|
static size_t g_memLogBufSize = 0;
|
||||||
static Mutex g_memMutex = 0;
|
static Mutex g_memMutex = 0;
|
||||||
|
|
||||||
/* Function prototypes. */
|
/* Function prototypes. */
|
||||||
|
@ -78,8 +79,6 @@ static bool memRetrieveProgramMemory(MemoryLocation *location, bool is_segment)
|
||||||
|
|
||||||
bool success = true;
|
bool success = true;
|
||||||
|
|
||||||
*g_memLogBuf = '\0';
|
|
||||||
|
|
||||||
/* Clear output MemoryLocation element. */
|
/* Clear output MemoryLocation element. */
|
||||||
memFreeMemoryLocation(location);
|
memFreeMemoryLocation(location);
|
||||||
|
|
||||||
|
@ -167,8 +166,7 @@ end:
|
||||||
|
|
||||||
if (success && (!location->data || !location->data_size))
|
if (success && (!location->data || !location->data_size))
|
||||||
{
|
{
|
||||||
MEMLOG("total size: 0x%lX", location->data_size);
|
MEMLOG("Unable to locate readable program memory pages for %016lX that match the required criteria!", location->program_id);
|
||||||
MEMLOG("Unable to locate readable program %016lX memory pages that match the required criteria!", location->program_id);
|
|
||||||
success = false;
|
success = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -177,6 +175,15 @@ end:
|
||||||
/* Write log buffer data. This will do nothing if the log buffer length is zero. */
|
/* Write log buffer data. This will do nothing if the log buffer length is zero. */
|
||||||
utilsWriteLogBufferToLogFile(g_memLogBuf);
|
utilsWriteLogBufferToLogFile(g_memLogBuf);
|
||||||
|
|
||||||
|
/* Free memory log buffer. */
|
||||||
|
if (g_memLogBuf)
|
||||||
|
{
|
||||||
|
free(g_memLogBuf);
|
||||||
|
g_memLogBuf = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
g_memLogBufSize = 0;
|
||||||
|
|
||||||
return success;
|
return success;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -90,8 +90,8 @@ typedef enum {
|
||||||
NcaKeyGeneration_700_801 = 8,
|
NcaKeyGeneration_700_801 = 8,
|
||||||
NcaKeyGeneration_810_811 = 9,
|
NcaKeyGeneration_810_811 = 9,
|
||||||
NcaKeyGeneration_900_901 = 10,
|
NcaKeyGeneration_900_901 = 10,
|
||||||
NcaKeyGeneration_910_1004 = 11,
|
NcaKeyGeneration_910_1020 = 11,
|
||||||
NcaKeyGeneration_Current = NcaKeyGeneration_910_1004
|
NcaKeyGeneration_Current = NcaKeyGeneration_910_1020
|
||||||
} NcaKeyGeneration;
|
} NcaKeyGeneration;
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
|
|
30
source/pfs.c
30
source/pfs.c
|
@ -38,7 +38,7 @@ bool pfsInitializeContext(PartitionFileSystemContext *out, NcaFsSectionContext *
|
||||||
u32 hash_region_count = 0;
|
u32 hash_region_count = 0;
|
||||||
NcaRegion *hash_region = NULL;
|
NcaRegion *hash_region = NULL;
|
||||||
|
|
||||||
/* Clear output partition FS context. */
|
/* Clear output Partition FS context. */
|
||||||
memset(out, 0, sizeof(PartitionFileSystemContext));
|
memset(out, 0, sizeof(PartitionFileSystemContext));
|
||||||
|
|
||||||
/* Fill context. */
|
/* Fill context. */
|
||||||
|
@ -59,38 +59,38 @@ bool pfsInitializeContext(PartitionFileSystemContext *out, NcaFsSectionContext *
|
||||||
/* Read partial PFS header. */
|
/* Read partial PFS header. */
|
||||||
if (!ncaReadFsSection(nca_fs_ctx, &pfs_header, sizeof(PartitionFileSystemHeader), out->offset))
|
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;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
magic = __builtin_bswap32(pfs_header.magic);
|
magic = __builtin_bswap32(pfs_header.magic);
|
||||||
if (magic != PFS0_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;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!pfs_header.entry_count || !pfs_header.name_table_size)
|
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;
|
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);
|
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));
|
out->header = calloc(out->header_size, sizeof(u8));
|
||||||
if (!out->header)
|
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;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Read full partition FS header. */
|
/* Read full Partition FS header. */
|
||||||
if (!ncaReadFsSection(nca_fs_ctx, out->header, out->header_size, out->offset))
|
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);
|
pfsFreeContext(out);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -113,7 +113,7 @@ bool pfsReadPartitionData(PartitionFileSystemContext *ctx, void *out, u64 read_s
|
||||||
/* Read partition data. */
|
/* Read partition data. */
|
||||||
if (!ncaReadFsSection(ctx->nca_fs_ctx, out, read_size, ctx->offset + offset))
|
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;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -132,7 +132,7 @@ bool pfsReadEntryData(PartitionFileSystemContext *ctx, PartitionFileSystemEntry
|
||||||
/* Read entry data. */
|
/* Read entry data. */
|
||||||
if (!pfsReadPartitionData(ctx, out, read_size, ctx->header_size + fs_entry->offset + offset))
|
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;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -156,7 +156,7 @@ bool pfsGetEntryIndexByName(PartitionFileSystemContext *ctx, const char *name, u
|
||||||
{
|
{
|
||||||
if (!(fs_entry = pfsGetEntryByIndex(ctx, i)))
|
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;
|
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;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -188,7 +188,7 @@ bool pfsGetTotalDataSize(PartitionFileSystemContext *ctx, u64 *out_size)
|
||||||
{
|
{
|
||||||
if (!(fs_entry = pfsGetEntryByIndex(ctx, i)))
|
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;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -213,7 +213,7 @@ bool pfsGenerateEntryPatch(PartitionFileSystemContext *ctx, PartitionFileSystemE
|
||||||
|
|
||||||
if (!ncaGenerateHierarchicalSha256Patch(ctx->nca_fs_ctx, data, data_size, partition_offset, out))
|
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;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
18
source/pfs.h
18
source/pfs.h
|
@ -50,25 +50,25 @@ typedef struct {
|
||||||
u8 *header; ///< PartitionFileSystemHeader + (PartitionFileSystemEntry * entry_count) + Name Table.
|
u8 *header; ///< PartitionFileSystemHeader + (PartitionFileSystemEntry * entry_count) + Name Table.
|
||||||
} PartitionFileSystemContext;
|
} PartitionFileSystemContext;
|
||||||
|
|
||||||
/// Initializes a partition FS context.
|
/// Initializes a Partition FS context.
|
||||||
bool pfsInitializeContext(PartitionFileSystemContext *out, NcaFsSectionContext *nca_fs_ctx);
|
bool pfsInitializeContext(PartitionFileSystemContext *out, NcaFsSectionContext *nca_fs_ctx);
|
||||||
|
|
||||||
/// Reads raw partition data using a partition FS context.
|
/// Reads raw partition data using a Partition FS context.
|
||||||
/// Input offset must be relative to the start of the partition FS.
|
/// Input offset must be relative to the start of the Partition FS.
|
||||||
bool pfsReadPartitionData(PartitionFileSystemContext *ctx, void *out, u64 read_size, u64 offset);
|
bool pfsReadPartitionData(PartitionFileSystemContext *ctx, void *out, u64 read_size, u64 offset);
|
||||||
|
|
||||||
/// Reads data from a previously retrieved PartitionFileSystemEntry using a partition FS context.
|
/// 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.
|
/// 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);
|
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);
|
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);
|
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.
|
/// 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.
|
/// Input offset must be relative to the start of the Partition FS entry data.
|
||||||
/// This function shares the same limitations as ncaGenerateHierarchicalSha256Patch().
|
/// This function shares the same limitations as ncaGenerateHierarchicalSha256Patch().
|
||||||
/// Use the pfsWriteEntryPatchToMemoryBuffer() wrapper to write patch data generated by this function.
|
/// 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);
|
bool pfsGenerateEntryPatch(PartitionFileSystemContext *ctx, PartitionFileSystemEntry *fs_entry, const void *data, u64 data_size, u64 data_offset, NcaHierarchicalSha256Patch *out);
|
||||||
|
|
|
@ -31,7 +31,7 @@
|
||||||
|
|
||||||
#define TITLE_DELTA_TYPE_VALUE (u64)0xC00
|
#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 {
|
typedef struct {
|
||||||
u32 TitleVersion_MinorRelstep : 8;
|
u32 TitleVersion_MinorRelstep : 8;
|
||||||
u32 TitleVersion_MajorRelstep : 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.
|
/// Returns NULL if an error occurs.
|
||||||
char *titleGenerateGameCardFileName(u8 name_convention, u8 illegal_char_replace_type);
|
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);
|
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);
|
const char *titleGetNcmContentMetaTypeName(u8 content_meta_type);
|
||||||
|
|
||||||
/// Miscellaneous functions.
|
/// Miscellaneous functions.
|
||||||
|
@ -230,4 +230,9 @@ NX_INLINE NcmContentInfo *titleGetContentInfoByTypeAndIdOffset(TitleInfo *info,
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
NX_INLINE u32 titleGetVersionInteger(TitleVersion *version)
|
||||||
|
{
|
||||||
|
return (version ? *((u32*)version) : 0);
|
||||||
|
}
|
||||||
|
|
||||||
#endif /* __TITLE_H__ */
|
#endif /* __TITLE_H__ */
|
||||||
|
|
103
source/utils.c
103
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, ...)
|
void utilsWriteMessageToLogFile(const char *func_name, const char *fmt, ...)
|
||||||
{
|
{
|
||||||
if (!func_name || !strlen(func_name) || !fmt || !strlen(fmt)) return;
|
if (!func_name || !strlen(func_name) || !fmt || !strlen(fmt)) return;
|
||||||
|
@ -346,26 +403,52 @@ end:
|
||||||
mutexUnlock(&g_logfileMutex);
|
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;
|
if (!dst || !dst_size || !func_name || !strlen(func_name) || !fmt || !strlen(fmt)) return;
|
||||||
|
|
||||||
va_list args;
|
va_list args;
|
||||||
|
va_start(args, fmt);
|
||||||
|
|
||||||
time_t now = time(NULL);
|
time_t now = time(NULL);
|
||||||
struct tm *ts = localtime(&now);
|
struct tm *ts = localtime(&now);
|
||||||
|
|
||||||
char msg[512] = {0};
|
int timestamp_len = 0, formatted_str_len = 0;
|
||||||
size_t msg_len = 0, dst_len = strlen(dst);
|
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);
|
if (dst_str_len > *dst_size)
|
||||||
msg_len = strlen(msg);
|
{
|
||||||
|
**dst = '\0';
|
||||||
|
dst_str_len = 0;
|
||||||
|
}
|
||||||
|
|
||||||
va_start(args, fmt);
|
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);
|
||||||
vsnprintf(msg + msg_len, sizeof(msg) - msg_len, fmt, args);
|
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);
|
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)
|
void utilsWriteLogBufferToLogFile(const char *src)
|
||||||
|
|
|
@ -86,8 +86,10 @@ u64 utilsHidKeysAllHeld(void);
|
||||||
|
|
||||||
void utilsWaitForButtonPress(u64 flag);
|
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 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 utilsWriteLogBufferToLogFile(const char *src);
|
||||||
void utilsLogFileMutexControl(bool lock);
|
void utilsLogFileMutexControl(bool lock);
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue