mirror of
https://github.com/DarkMatterCore/nxdumptool.git
synced 2024-11-22 10:16: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
|
||||
/code_templates/tmp/*
|
||||
/source/main.c
|
||||
/*.log
|
||||
|
||||
# Clion files
|
||||
.idea
|
||||
|
|
2
build.sh
2
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
|
||||
|
|
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 "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)
|
||||
|
|
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 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;
|
||||
}
|
||||
|
|
|
@ -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__ */
|
||||
|
|
19
source/mem.c
19
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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
|
30
source/pfs.c
30
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;
|
||||
}
|
||||
|
||||
|
|
18
source/pfs.h
18
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);
|
||||
|
|
|
@ -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__ */
|
||||
|
|
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, ...)
|
||||
{
|
||||
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)
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
Loading…
Reference in a new issue