1
0
Fork 0
mirror of https://github.com/DarkMatterCore/nxdumptool.git synced 2024-10-30 07:21:46 +00:00
nxdumptool/source/core/system_update.c
Pablo Curiel 160236c4de system_update: add menu option.
Makes it possible to dump a system update. The SystemVersion file is retrieved to get additional information about the system update.
2024-10-27 12:23:46 +01:00

497 lines
16 KiB
C

/*
* system_update.c
*
* Copyright (c) 2020-2024, DarkMatterCore <pabloacurielz@gmail.com>.
*
* This file is part of nxdumptool (https://github.com/DarkMatterCore/nxdumptool).
*
* nxdumptool is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* nxdumptool is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#include <core/nxdt_utils.h>
#include <core/system_update.h>
#include <core/cnmt.h>
#include <core/romfs.h>
#define SYSTEM_VERSION_FILE_PATH "/file"
/* Global variables. */
static Mutex g_systemUpdateMutex = 0;
static bool g_systemUpdateInterfaceInit = false;
static TitleInfo *g_systemUpdateTitleInfo = NULL;
static NcaContext *g_systemUpdateNcaContext = NULL;
static ContentMetaContext g_systemUpdateCnmtContext = {0};
/* Function prototypes. */
static bool _systemUpdateInitializeDumpContext(SystemUpdateDumpContext *ctx);
static bool systemUpdateProcessContentMetaInfo(SystemUpdateDumpContext *ctx, const NcmContentMetaInfo *content_meta_info);
static bool systemUpdateProcessContentRecords(SystemUpdateDumpContext *ctx, TitleInfo *title_info);
static int systemUpdateNcaContextSortFunction(const void *a, const void *b);
static bool systemUpdateGetSystemVersionFileData(SystemUpdateDumpContext *ctx);
NX_INLINE NcaContext *systemUpdateGetCurrentNcaContextFromDumpContext(SystemUpdateDumpContext *ctx);
bool systemUpdateInitialize(void)
{
bool ret = false;
SCOPED_LOCK(&g_systemUpdateMutex)
{
ret = g_systemUpdateInterfaceInit;
if (ret) break;
/* Get title info. */
if (!(g_systemUpdateTitleInfo = titleGetTitleInfoEntryFromStorageByTitleId(NcmStorageId_BuiltInSystem, SYSTEM_UPDATE_TID)))
{
LOG_MSG_ERROR("Failed to get title info for SystemUpdate!");
break;
}
/* Allocate memory for the SystemUpdate NCA context. */
g_systemUpdateNcaContext = calloc(1, sizeof(NcaContext));
if (!g_systemUpdateNcaContext)
{
LOG_MSG_ERROR("Failed to allocate memory for SystemUpdate NCA context!");
goto end;
}
/* Initialize NCA context. */
/* Don't allow invalid NCA signatures. */
if (!ncaInitializeContext(g_systemUpdateNcaContext, g_systemUpdateTitleInfo->storage_id, 0, &(g_systemUpdateTitleInfo->meta_key), \
titleGetContentInfoByTypeAndIdOffset(g_systemUpdateTitleInfo, NcmContentType_Meta, 0), NULL) || !g_systemUpdateNcaContext->valid_main_signature)
{
LOG_MSG_ERROR("Failed to initialize SystemUpdate Meta NCA context!");
goto end;
}
/* Initialize Content Meta context. */
ret = g_systemUpdateInterfaceInit = (cnmtInitializeContext(&g_systemUpdateCnmtContext, g_systemUpdateNcaContext) && \
g_systemUpdateCnmtContext.packaged_header->content_meta_count && g_systemUpdateCnmtContext.content_meta_info);
if (ret) break;
LOG_MSG_ERROR("Failed to initialize Content Meta context for SystemUpdate Meta NCA \"%s\"!", g_systemUpdateNcaContext->content_id_str);
end:
cnmtFreeContext(&g_systemUpdateCnmtContext);
if (g_systemUpdateNcaContext)
{
free(g_systemUpdateNcaContext);
g_systemUpdateNcaContext = NULL;
}
titleFreeTitleInfo(&g_systemUpdateTitleInfo);
}
return ret;
}
void systemUpdateExit(void)
{
SCOPED_LOCK(&g_systemUpdateMutex)
{
/* Free Content Meta context. */
cnmtFreeContext(&g_systemUpdateCnmtContext);
/* Free NCA context. */
if (g_systemUpdateNcaContext)
{
free(g_systemUpdateNcaContext);
g_systemUpdateNcaContext = NULL;
}
/* Free TitleInfo entry. */
titleFreeTitleInfo(&g_systemUpdateTitleInfo);
/* Update flag. */
g_systemUpdateInterfaceInit = false;
}
}
bool systemUpdateInitializeDumpContext(SystemUpdateDumpContext *ctx)
{
bool ret = false;
SCOPED_LOCK(&g_systemUpdateMutex) ret = _systemUpdateInitializeDumpContext(ctx);
return ret;
}
bool systemUpdateGetCurrentContentFileSizeFromDumpContext(SystemUpdateDumpContext *ctx, u64 *out_size)
{
NcaContext *nca_ctx = systemUpdateGetCurrentNcaContextFromDumpContext(ctx);
if (!nca_ctx || !out_size)
{
LOG_MSG_ERROR("Invalid parameters!");
return false;
}
*out_size = nca_ctx->content_size;
return true;
}
char *systemUpdateGetCurrentContentFileNameFromDumpContext(SystemUpdateDumpContext *ctx)
{
NcaContext *nca_ctx = systemUpdateGetCurrentNcaContextFromDumpContext(ctx);
if (!nca_ctx)
{
LOG_MSG_ERROR("Invalid parameters!");
return NULL;
}
bool is_meta_nca = (nca_ctx->content_type == NcmContentType_Meta);
size_t nca_filename_size = ((is_meta_nca ? NCA_HFS_META_NAME_LENGTH : NCA_HFS_REGULAR_NAME_LENGTH) + 1);
char *nca_filename = calloc(nca_filename_size, sizeof(char));
if (nca_filename)
{
snprintf(nca_filename, nca_filename_size, "%s%s", nca_ctx->content_id_str, is_meta_nca ? ".cnmt.nca" : ".nca");
} else {
LOG_MSG_ERROR("Failed to allocate 0x%lX-byte long filename for %s NCA \"%s\"! (title %016lX).", nca_filename_size, titleGetNcmContentTypeName(nca_ctx->content_type), \
nca_ctx->content_id_str, nca_ctx->title_id);
}
return nca_filename;
}
bool systemUpdateReadCurrentContentFileFromDumpContext(SystemUpdateDumpContext *ctx, void *out, u64 read_size)
{
NcaContext *nca_ctx = NULL;
if (!(nca_ctx = systemUpdateGetCurrentNcaContextFromDumpContext(ctx)) || !out || !read_size || (ctx->cur_content_offset + read_size) > nca_ctx->content_size)
{
LOG_MSG_ERROR("Invalid parameters!");
return false;
}
u8 nca_hash[SHA256_HASH_SIZE] = {0};
bool success = false;
/* Read NCA data. */
if (!ncaReadContentFile(nca_ctx, out, read_size, ctx->cur_content_offset))
{
LOG_MSG_ERROR("Failed to read %s NCA \"%s\"! (title %016lX).", titleGetNcmContentTypeName(nca_ctx->content_type), \
nca_ctx->content_id_str, nca_ctx->title_id);
goto end;
}
/* (Re-)initialize SHA-256 hash context, if needed. */
if (!ctx->cur_content_offset) sha256ContextCreate(&(ctx->sha256_ctx));
/* Update SHA-256 hash context. */
sha256ContextUpdate(&(ctx->sha256_ctx), out, read_size);
/* Update system update context. */
ctx->cur_size += read_size;
ctx->cur_content_offset += read_size;
/* Check if we have finished reading this content. */
if (ctx->cur_content_offset >= nca_ctx->content_size)
{
/* Verify SHA-256 hash for this content. */
sha256ContextGetHash(&(ctx->sha256_ctx), nca_hash);
if (memcmp(nca_hash, nca_ctx->content_id.c, sizeof(nca_ctx->content_id.c)) != 0)
{
LOG_MSG_ERROR("SHA-256 checksum mismatch for %s NCA \"%s\"! (title %016lX).", titleGetNcmContentTypeName(nca_ctx->content_type), \
nca_ctx->content_id_str, nca_ctx->title_id);
goto end;
}
/* Update system update context. */
ctx->content_idx++;
ctx->cur_content_offset = 0;
}
/* Update return value. */
success = true;
end:
return success;
}
static bool _systemUpdateInitializeDumpContext(SystemUpdateDumpContext *ctx)
{
if (!g_systemUpdateInterfaceInit || !ctx)
{
LOG_MSG_ERROR("Invalid parameters!");
return false;
}
bool success = false;
/* Free output context beforehand. */
systemUpdateFreeDumpContext(ctx);
/* Loop through all of our content meta info records. */
for(u16 i = 0; i < g_systemUpdateCnmtContext.packaged_header->content_meta_count; i++)
{
NcmContentMetaInfo *cur_meta_info = &(g_systemUpdateCnmtContext.content_meta_info[i]);
/* Process current content meta info record. */
if (!systemUpdateProcessContentMetaInfo(ctx, cur_meta_info))
{
LOG_MSG_ERROR("Failed to process content meta info for title %016lX!", cur_meta_info->id);
goto end;
}
}
/* Manually add SystemUpdate content records. */
/* The SystemUpdate CNMT doesn't reference itself. */
if (!systemUpdateProcessContentRecords(ctx, g_systemUpdateTitleInfo))
{
LOG_MSG_ERROR("Failed to process SystemUpdate content records!");
goto end;
}
/* Sort NCA contexts. */
if (ctx->content_count > 1) qsort(ctx->nca_ctxs, ctx->content_count, sizeof(NcaContext*), &systemUpdateNcaContextSortFunction);
/* Retrieve system version file data. */
success = systemUpdateGetSystemVersionFileData(ctx);
if (!success) LOG_MSG_ERROR("Failed to retrieve SystemVersion file data!");
end:
/* Free output context, if needed. */
if (!success) systemUpdateFreeDumpContext(ctx);
return success;
}
static bool systemUpdateProcessContentMetaInfo(SystemUpdateDumpContext *ctx, const NcmContentMetaInfo *content_meta_info)
{
if (!ctx || !content_meta_info)
{
LOG_MSG_ERROR("Invalid parameters!");
return false;
}
TitleInfo *title_info = NULL;
bool success = false;
/* Get TitleInfo entry. */
title_info = titleGetTitleInfoEntryFromStorageByTitleId(NcmStorageId_BuiltInSystem, content_meta_info->id);
if (!title_info)
{
LOG_MSG_ERROR("Failed to get TitleInfo entry for ID %016lX!", content_meta_info->id);
goto end;
}
/* Check for matching version values. */
if (title_info->version.value != content_meta_info->version)
{
LOG_MSG_ERROR("Version mismatch for title %016lX! (got v%u, expected v%u).", content_meta_info->id, title_info->version.value, \
content_meta_info->version);
goto end;
}
/* Process content records. */
success = systemUpdateProcessContentRecords(ctx, title_info);
if (!success) LOG_MSG_ERROR("Failed to process content records for title %016lX!", content_meta_info->id);
end:
titleFreeTitleInfo(&title_info);
return success;
}
static bool systemUpdateProcessContentRecords(SystemUpdateDumpContext *ctx, TitleInfo *title_info)
{
if (!ctx || !titleIsValidInfoBlock(title_info))
{
LOG_MSG_ERROR("Invalid parameters!");
return false;
}
NcaContext **nca_ctxs_tmp = NULL;
const u32 nca_ctxs_count = (ctx->content_count + title_info->content_count);
bool success = false;
/* Reallocate NCA context pointer array. */
nca_ctxs_tmp = realloc(ctx->nca_ctxs, nca_ctxs_count * sizeof(NcaContext*));
if (!nca_ctxs_tmp)
{
LOG_MSG_ERROR("Failed to reallocate NCA context pointer array for title %016lX! (%u %s).", title_info->meta_key.id, nca_ctxs_count, \
nca_ctxs_count > 1 ? "entries" : "entry");
goto end;
}
memset(&(nca_ctxs_tmp[ctx->content_count]), 0, title_info->content_count * sizeof(NcaContext*));
ctx->nca_ctxs = nca_ctxs_tmp;
/* Loop through all of the content records for the current title. */
for(u32 i = 0; i < title_info->content_count; i++)
{
NcmContentInfo *cur_content_info = &(title_info->content_infos[i]);
NcaContext **cur_nca_ctx = &(ctx->nca_ctxs[ctx->content_count + i]);
/* Allocate memory for the current NCA context. */
*cur_nca_ctx = calloc(1, sizeof(NcaContext));
if (!*cur_nca_ctx)
{
LOG_MSG_ERROR("Failed to allocate memory for NCA context! (title %016lX, content #%u).", title_info->meta_key.id, i);
goto end;
}
/* Initialize current NCA context. */
if (!ncaInitializeContext(*cur_nca_ctx, title_info->storage_id, 0, &(title_info->meta_key), cur_content_info, NULL) || !(*cur_nca_ctx)->valid_main_signature)
{
LOG_MSG_ERROR("Failed to initialize NCA context! (title %016lX, content #%u).", title_info->meta_key.id, i);
goto end;
}
}
/* Update context. */
ctx->total_size += title_info->size;
ctx->content_count += title_info->content_count;
/* Update return value. */
success = true;
end:
/* Free previously allocated NCA contexts if we ran into a problem. */
if (!success && nca_ctxs_tmp)
{
for(u32 i = 0; i < title_info->content_count; i++)
{
if (!ctx->nca_ctxs[ctx->content_count + i]) continue;
free(ctx->nca_ctxs[ctx->content_count + i]);
ctx->nca_ctxs[ctx->content_count + i] = NULL;
}
}
return success;
}
static int systemUpdateNcaContextSortFunction(const void *a, const void *b)
{
const NcaContext *nca_ctx_1 = *((const NcaContext**)a);
const NcaContext *nca_ctx_2 = *((const NcaContext**)b);
if (nca_ctx_1->title_id < nca_ctx_2->title_id)
{
return -1;
} else
if (nca_ctx_1->title_id > nca_ctx_2->title_id)
{
return 1;
}
if (nca_ctx_1->title_version.value < nca_ctx_2->title_version.value)
{
return -1;
} else
if (nca_ctx_1->title_version.value > nca_ctx_2->title_version.value)
{
return 1;
}
if (nca_ctx_1->content_type < nca_ctx_2->content_type)
{
return -1;
} else
if (nca_ctx_1->content_type > nca_ctx_2->content_type)
{
return 1;
}
if (nca_ctx_1->id_offset < nca_ctx_2->id_offset)
{
return -1;
} else
if (nca_ctx_1->id_offset > nca_ctx_2->id_offset)
{
return 1;
}
return 0;
}
static bool systemUpdateGetSystemVersionFileData(SystemUpdateDumpContext *ctx)
{
if (!systemUpdateIsValidDumpContext(ctx))
{
LOG_MSG_ERROR("Invalid parameters!");
return false;
}
NcaContext *nca_ctx = NULL;
RomFileSystemContext romfs_ctx = {0};
RomFileSystemFileEntry *romfs_file_entry = NULL;
bool success = false;
/* Loop through our NCA contexts until we find the Data NCA for the SystemVersion title. */
for(u32 i = 0; i < ctx->content_count; i++)
{
nca_ctx = ctx->nca_ctxs[i];
if (nca_ctx && nca_ctx->title_id == SYSTEM_VERSION_TID && nca_ctx->content_type == NcmContentType_Data) break;
nca_ctx = NULL;
}
if (!nca_ctx)
{
LOG_MSG_ERROR("Unable to find Data NCA for SystemVersion title!");
goto end;
}
LOG_MSG_DEBUG("Found Data NCA \"%s\" for SystemVersion title.", nca_ctx->content_id_str);
/* Initialize RomFS context. */
if (!romfsInitializeContext(&romfs_ctx, &(nca_ctx->fs_ctx[0]), NULL))
{
LOG_MSG_ERROR("Failed to initialize RomFS context for SystemVersion Data NCA!");
goto end;
}
/* Get RomFS file entry. */
if (!(romfs_file_entry = romfsGetFileEntryByPath(&romfs_ctx, SYSTEM_VERSION_FILE_PATH)))
{
LOG_MSG_ERROR("Failed to retrieve RomFS file entry for SystemVersion Data NCA!");
goto end;
}
/* Validate file size. */
if (romfs_file_entry->size != sizeof(ctx->version_file))
{
LOG_MSG_ERROR("Invalid RomFS file entry size in SystemVersion Data NCA! Got 0x%lX, expected 0x%lX.", romfs_file_entry->size, sizeof(ctx->version_file));
goto end;
}
/* Read SystemVersion file data. */
success = romfsReadFileEntryData(&romfs_ctx, romfs_file_entry, &(ctx->version_file), sizeof(ctx->version_file), 0);
if (!success)
{
LOG_MSG_ERROR("Failed to read SystemVersion file data!");
goto end;
}
LOG_DATA_DEBUG(&(ctx->version_file), sizeof(ctx->version_file), "SystemVersion file data:");
end:
romfsFreeContext(&romfs_ctx);
return success;
}
NX_INLINE NcaContext *systemUpdateGetCurrentNcaContextFromDumpContext(SystemUpdateDumpContext *ctx)
{
return ((systemUpdateIsValidDumpContext(ctx) && !systemUpdateIsDumpContextFinished(ctx)) ? ctx->nca_ctxs[ctx->content_idx] : NULL);
}