mirror of
https://github.com/DarkMatterCore/nxdumptool.git
synced 2024-11-22 18:26:39 +00:00
CNMT parsing.
This commit is contained in:
parent
d0f9a0b248
commit
6e32829cf1
4 changed files with 576 additions and 0 deletions
2
.gitignore
vendored
2
.gitignore
vendored
|
@ -7,6 +7,8 @@ build
|
||||||
/*.pfs0
|
/*.pfs0
|
||||||
/*.lst
|
/*.lst
|
||||||
/*.tar.bz2
|
/*.tar.bz2
|
||||||
|
/code_templates/tmp/*
|
||||||
|
/source/main.c
|
||||||
|
|
||||||
# Clion files
|
# Clion files
|
||||||
.idea
|
.idea
|
||||||
|
|
|
@ -23,6 +23,7 @@
|
||||||
#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"
|
||||||
|
@ -354,6 +355,42 @@ 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)
|
||||||
|
|
279
source/cnmt.c
Normal file
279
source/cnmt.c
Normal file
|
@ -0,0 +1,279 @@
|
||||||
|
/*
|
||||||
|
* cnmt.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 "cnmt.h"
|
||||||
|
#include "title.h"
|
||||||
|
|
||||||
|
#define CNMT_MINIMUM_FILENAME_LENGTH 23 /* Content Meta Type + "_" + Title ID + ".cnmt". */
|
||||||
|
|
||||||
|
/* Function prototypes. */
|
||||||
|
|
||||||
|
static bool cnmtGetContentMetaTypeAndTitleIdFromFileName(const char *cnmt_filename, size_t cnmt_filename_len, u8 *out_content_meta_type, u64 *out_title_id);
|
||||||
|
|
||||||
|
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 || \
|
||||||
|
(nca_ctx->storage_id != NcmStorageId_GameCard && !nca_ctx->ncm_storage) || (nca_ctx->storage_id == NcmStorageId_GameCard && !nca_ctx->gamecard_offset) || \
|
||||||
|
nca_ctx->header.content_type != NcaContentType_Meta || !out)
|
||||||
|
{
|
||||||
|
LOGFILE("Invalid parameters!");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
u32 i = 0, pfs_entry_count = 0;
|
||||||
|
size_t cnmt_filename_len = 0;
|
||||||
|
|
||||||
|
u8 content_meta_type = 0;
|
||||||
|
u64 title_id = 0, cur_offset = 0;
|
||||||
|
|
||||||
|
bool success = false, invalid_ext_header_size = false, invalid_ext_data_size = false;
|
||||||
|
|
||||||
|
/* Free output context beforehand. */
|
||||||
|
cnmtFreeContext(out);
|
||||||
|
|
||||||
|
/* Initialize Partition FS context. */
|
||||||
|
if (!pfsInitializeContext(&(out->pfs_ctx), &(nca_ctx->fs_contexts[0])))
|
||||||
|
{
|
||||||
|
LOGFILE("Failed to initialize Partition FS context!");
|
||||||
|
goto end;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Get Partition FS entry count. */
|
||||||
|
if (!(pfs_entry_count = pfsGetEntryCount(&(out->pfs_ctx))))
|
||||||
|
{
|
||||||
|
LOGFILE("Partition FS has no file entries!");
|
||||||
|
goto end;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Look for the '.cnmt' file entry index. */
|
||||||
|
for(i = 0; i < pfs_entry_count; i++)
|
||||||
|
{
|
||||||
|
if ((out->cnmt_filename = pfsGetEntryNameByIndex(&(out->pfs_ctx), i)) && (cnmt_filename_len = strlen(out->cnmt_filename)) >= CNMT_MINIMUM_FILENAME_LENGTH && \
|
||||||
|
!strncasecmp(out->cnmt_filename + cnmt_filename_len - 5, ".cnmt", 5)) break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (i >= pfs_entry_count)
|
||||||
|
{
|
||||||
|
LOGFILE("'.cnmt' entry unavailable in Partition FS!");
|
||||||
|
goto end;
|
||||||
|
}
|
||||||
|
|
||||||
|
//LOGFILE("Found '.cnmt' entry \"%s\" in Meta NCA \"%s\".", out->cnmt_filename, nca_ctx->content_id_str);
|
||||||
|
|
||||||
|
/* Retrieve content meta type and title ID from the '.cnmt' filename. */
|
||||||
|
if (!cnmtGetContentMetaTypeAndTitleIdFromFileName(out->cnmt_filename, cnmt_filename_len, &content_meta_type, &title_id)) goto end;
|
||||||
|
|
||||||
|
/* Get '.cnmt' file entry. */
|
||||||
|
if (!(out->pfs_entry = pfsGetEntryByIndex(&(out->pfs_ctx), i)))
|
||||||
|
{
|
||||||
|
LOGFILE("Failed to get '.cnmt' entry from Partition FS!");
|
||||||
|
goto end;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Check raw CNMT size. */
|
||||||
|
if (!out->pfs_entry->size)
|
||||||
|
{
|
||||||
|
LOGFILE("Invalid raw CNMT size!");
|
||||||
|
goto end;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Allocate memory for the raw CNMT data. */
|
||||||
|
out->raw_data_size = out->pfs_entry->size;
|
||||||
|
out->raw_data = malloc(out->raw_data_size);
|
||||||
|
if (!out->raw_data)
|
||||||
|
{
|
||||||
|
LOGFILE("Failed to allocate memory for the raw CNMT data!");
|
||||||
|
goto end;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Read raw CNMT data into memory buffer. */
|
||||||
|
if (!pfsReadEntryData(&(out->pfs_ctx), out->pfs_entry, out->raw_data, out->raw_data_size, 0))
|
||||||
|
{
|
||||||
|
LOGFILE("Failed to read raw CNMT data!");
|
||||||
|
goto end;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Save pointer to NCA context to the output CNMT context. */
|
||||||
|
out->nca_ctx = nca_ctx;
|
||||||
|
|
||||||
|
/* Verify packaged header. */
|
||||||
|
out->packaged_header = (ContentMetaPackagedHeader*)out->raw_data;
|
||||||
|
cur_offset += sizeof(ContentMetaPackagedHeader);
|
||||||
|
|
||||||
|
if (out->packaged_header->title_id != title_id)
|
||||||
|
{
|
||||||
|
LOGFILE("CNMT title ID mismatch! (%016lX != %016lX).", out->packaged_header->title_id, title_id);
|
||||||
|
goto end;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (out->packaged_header->content_meta_type != content_meta_type)
|
||||||
|
{
|
||||||
|
LOGFILE("CNMT content meta type mismatch! (0x%02X != 0x%02X).", out->packaged_header->content_meta_type, content_meta_type);
|
||||||
|
goto end;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!out->packaged_header->content_count)
|
||||||
|
{
|
||||||
|
LOGFILE("Invalid content count!");
|
||||||
|
goto end;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((out->packaged_header->content_meta_type == NcmContentMetaType_SystemUpdate && !out->packaged_header->content_meta_count) || \
|
||||||
|
(out->packaged_header->content_meta_type != NcmContentMetaType_SystemUpdate && out->packaged_header->content_meta_count))
|
||||||
|
{
|
||||||
|
LOGFILE("Invalid content meta count!");
|
||||||
|
goto end;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Save pointer to extended header */
|
||||||
|
if (out->packaged_header->extended_header_size)
|
||||||
|
{
|
||||||
|
out->extended_header = (out->raw_data + cur_offset);
|
||||||
|
cur_offset += out->packaged_header->extended_header_size;
|
||||||
|
|
||||||
|
switch(out->packaged_header->content_meta_type)
|
||||||
|
{
|
||||||
|
case NcmContentMetaType_SystemUpdate:
|
||||||
|
invalid_ext_header_size = (out->packaged_header->extended_header_size != (u16)sizeof(ContentMetaSystemUpdateMetaExtendedHeader));
|
||||||
|
out->extended_data_size = (!invalid_ext_header_size ? ((ContentMetaSystemUpdateMetaExtendedHeader*)out->extended_header)->extended_data_size : 0);
|
||||||
|
invalid_ext_data_size = (out->extended_data_size <= sizeof(ContentMetaSystemUpdateMetaExtendedDataHeader));
|
||||||
|
break;
|
||||||
|
case NcmContentMetaType_Application:
|
||||||
|
invalid_ext_header_size = (out->packaged_header->extended_header_size != (u16)sizeof(ContentMetaApplicationMetaExtendedHeader));
|
||||||
|
break;
|
||||||
|
case NcmContentMetaType_Patch:
|
||||||
|
invalid_ext_header_size = (out->packaged_header->extended_header_size != (u16)sizeof(ContentMetaPatchMetaExtendedHeader));
|
||||||
|
out->extended_data_size = (!invalid_ext_header_size ? ((ContentMetaPatchMetaExtendedHeader*)out->extended_header)->extended_data_size : 0);
|
||||||
|
invalid_ext_data_size = (out->extended_data_size <= sizeof(ContentMetaPatchMetaExtendedDataHeader));
|
||||||
|
break;
|
||||||
|
case NcmContentMetaType_AddOnContent:
|
||||||
|
invalid_ext_header_size = (out->packaged_header->extended_header_size != (u16)sizeof(ContentMetaAddOnContentMetaExtendedHeader));
|
||||||
|
break;
|
||||||
|
case NcmContentMetaType_Delta:
|
||||||
|
invalid_ext_header_size = (out->packaged_header->extended_header_size != (u16)sizeof(ContentMetaDeltaMetaExtendedHeader));
|
||||||
|
out->extended_data_size = (!invalid_ext_header_size ? ((ContentMetaDeltaMetaExtendedHeader*)out->extended_header)->extended_data_size : 0);
|
||||||
|
invalid_ext_data_size = (out->extended_data_size <= sizeof(ContentMetaDeltaMetaExtendedDataHeader));
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
invalid_ext_header_size = (out->packaged_header->extended_header_size > 0);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (invalid_ext_header_size)
|
||||||
|
{
|
||||||
|
LOGFILE("Invalid extended header size!");
|
||||||
|
goto end;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (invalid_ext_data_size)
|
||||||
|
{
|
||||||
|
LOGFILE("Invalid extended data size!");
|
||||||
|
goto end;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Save pointer to packaged content infos. */
|
||||||
|
out->packaged_content_info = (NcmPackagedContentInfo*)(out->raw_data + cur_offset);
|
||||||
|
cur_offset += (out->packaged_header->content_count * sizeof(NcmPackagedContentInfo));
|
||||||
|
|
||||||
|
/* Save pointer to content meta infos. */
|
||||||
|
if (out->packaged_header->content_meta_count)
|
||||||
|
{
|
||||||
|
out->content_meta_info = (NcmContentMetaInfo*)(out->raw_data + cur_offset);
|
||||||
|
cur_offset += (out->packaged_header->content_meta_count * sizeof(NcmContentMetaInfo));
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Save pointer to the extended data block. */
|
||||||
|
if (out->extended_data_size)
|
||||||
|
{
|
||||||
|
out->extended_data = (out->raw_data + cur_offset);
|
||||||
|
cur_offset += out->extended_data_size;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Save pointer to digest. */
|
||||||
|
out->digest = (out->raw_data + cur_offset);
|
||||||
|
cur_offset += CNMT_DIGEST_SIZE;
|
||||||
|
|
||||||
|
/* Safety check: verify raw CNMT size. */
|
||||||
|
if (cur_offset != out->raw_data_size)
|
||||||
|
{
|
||||||
|
LOGFILE("Raw CNMT size mismatch! (0x%X != 0x%X).", cur_offset, out->raw_data_size);
|
||||||
|
goto end;
|
||||||
|
}
|
||||||
|
|
||||||
|
success = true;
|
||||||
|
|
||||||
|
end:
|
||||||
|
if (!success) cnmtFreeContext(out);
|
||||||
|
|
||||||
|
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)
|
||||||
|
{
|
||||||
|
LOGFILE("Invalid parameters!");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
u8 i = 0;
|
||||||
|
const char *pch1 = NULL, *pch2 = NULL;
|
||||||
|
size_t content_meta_type_str_len = 0;
|
||||||
|
|
||||||
|
pch1 = (const char*)strstr(cnmt_filename, "_");
|
||||||
|
pch2 = (cnmt_filename + cnmt_filename_len - 5);
|
||||||
|
|
||||||
|
if (!pch1 || !(content_meta_type_str_len = (pch1 - cnmt_filename)) || (pch2 - ++pch1) != 16)
|
||||||
|
{
|
||||||
|
LOGFILE("Invalid '.cnmt' filename in Partition FS!");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
for(i = NcmContentMetaType_SystemProgram; i <= NcmContentMetaType_Delta; i++)
|
||||||
|
{
|
||||||
|
if (i > NcmContentMetaType_BootImagePackageSafe && i < NcmContentMetaType_Application)
|
||||||
|
{
|
||||||
|
i = (NcmContentMetaType_Application - 1);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!strncasecmp(cnmt_filename, titleGetNcmContentMetaTypeName(i), content_meta_type_str_len))
|
||||||
|
{
|
||||||
|
*out_content_meta_type = i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (i > NcmContentMetaType_Delta)
|
||||||
|
{
|
||||||
|
LOGFILE("Invalid content meta type \"%.*s\" in '.cnmt' filename!", (int)content_meta_type_str_len, cnmt_filename);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!(*out_title_id = strtoull(pch1, NULL, 16)))
|
||||||
|
{
|
||||||
|
LOGFILE("Invalid title ID in '.cnmt' filename!");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
258
source/cnmt.h
Normal file
258
source/cnmt.h
Normal file
|
@ -0,0 +1,258 @@
|
||||||
|
/*
|
||||||
|
* cnmt.h
|
||||||
|
*
|
||||||
|
* 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/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#ifndef __CNMT_H__
|
||||||
|
#define __CNMT_H__
|
||||||
|
|
||||||
|
#include "nca.h"
|
||||||
|
#include "pfs.h"
|
||||||
|
|
||||||
|
#define CNMT_DIGEST_SIZE SHA256_HASH_SIZE
|
||||||
|
|
||||||
|
/// Used to display version numbers in dot notation: "{Major}.{Minor}.{Micro}-{MajorRelstep}.{MinorRelstep}".
|
||||||
|
typedef struct {
|
||||||
|
u32 ContentMetaVersion_MinorRelstep : 8;
|
||||||
|
u32 ContentMetaVersion_MajorRelstep : 8;
|
||||||
|
u32 ContentMetaVersion_Micro : 4;
|
||||||
|
u32 ContentMetaVersion_Minor : 6;
|
||||||
|
u32 ContentMetaVersion_Major : 6;
|
||||||
|
} ContentMetaVersion;
|
||||||
|
|
||||||
|
/// Equivalent to NcmContentMetaAttribute.
|
||||||
|
typedef enum {
|
||||||
|
ContentMetaAttribute_IncludesExFatDriver = BIT(0),
|
||||||
|
ContentMetaAttribute_Rebootless = BIT(1),
|
||||||
|
ContentMetaAttribute_Compacted = BIT(2)
|
||||||
|
} ContentMetaAttribute;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
u8 ContentMetaInstallState_Committed : 1;
|
||||||
|
u8 ContentMetaInstallState_Reserved : 7;
|
||||||
|
} ContentMetaInstallState;
|
||||||
|
|
||||||
|
/// Extended variation of NcmContentMetaHeader. This is essentially the start of every CNMT file.
|
||||||
|
/// Depending on the content meta type value, this header may be followed by an additional extended header for that specific content meta type.
|
||||||
|
/// NcmPackagedContentInfo and/or NcmContentMetaInfo entries may follow afterwards.
|
||||||
|
/// If the extended data size field from the extended header (if available) is non-zero, this data is saved after the content info entries.
|
||||||
|
/// Finally, a 0x20 byte long digest is appended to the EOF.
|
||||||
|
typedef struct {
|
||||||
|
u64 title_id;
|
||||||
|
ContentMetaVersion version;
|
||||||
|
u8 content_meta_type; ///< NcmContentMetaType.
|
||||||
|
u8 reserved_1;
|
||||||
|
u16 extended_header_size; ///< Must match the size from the extended header struct for this content meta type (SystemUpdate, Application, Patch, AddOnContent, Delta).
|
||||||
|
u16 content_count; ///< Determines how many NcmPackagedContentInfo entries are available after the extended header.
|
||||||
|
u16 content_meta_count; ///< Determines how many NcmContentMetaInfo entries are available after the NcmPackagedContentInfo entries. Only used for SystemUpdate.
|
||||||
|
u8 content_meta_attribute; ///< ContentMetaAttribute.
|
||||||
|
u8 storage_id; ///< NcmStorageId.
|
||||||
|
u8 content_install_type; ///< NcmContentInstallType.
|
||||||
|
ContentMetaInstallState install_state;
|
||||||
|
ContentMetaVersion required_download_system_version;
|
||||||
|
u8 reserved_2[0x4];
|
||||||
|
} ContentMetaPackagedHeader;
|
||||||
|
|
||||||
|
/// Extended header for the SystemUpdate title.
|
||||||
|
/// Equivalent to NcmSystemUpdateMetaExtendedHeader.
|
||||||
|
typedef struct {
|
||||||
|
u32 extended_data_size;
|
||||||
|
} ContentMetaSystemUpdateMetaExtendedHeader;
|
||||||
|
|
||||||
|
/// Extended header for Application titles.
|
||||||
|
/// Equivalent to NcmApplicationMetaExtendedHeader, but using ContentMetaVersion structs.
|
||||||
|
typedef struct {
|
||||||
|
u64 patch_id;
|
||||||
|
ContentMetaVersion required_system_version;
|
||||||
|
ContentMetaVersion required_application_version;
|
||||||
|
} ContentMetaApplicationMetaExtendedHeader;
|
||||||
|
|
||||||
|
/// Extended header for Patch titles.
|
||||||
|
/// Equivalent to NcmPatchMetaExtendedHeader, but using a ContentMetaVersion struct.
|
||||||
|
typedef struct {
|
||||||
|
u64 application_id;
|
||||||
|
ContentMetaVersion required_system_version;
|
||||||
|
u32 extended_data_size;
|
||||||
|
u8 reserved[0x8];
|
||||||
|
} ContentMetaPatchMetaExtendedHeader;
|
||||||
|
|
||||||
|
/// Extended header for AddOnContent titles.
|
||||||
|
/// Equivalent to NcmAddOnContentMetaExtendedHeader, but using a ContentMetaVersion struct.
|
||||||
|
typedef struct {
|
||||||
|
u64 application_id;
|
||||||
|
ContentMetaVersion required_application_version;
|
||||||
|
u8 reserved[0x4];
|
||||||
|
} ContentMetaAddOnContentMetaExtendedHeader;
|
||||||
|
|
||||||
|
/// Extended header for Delta titles.
|
||||||
|
typedef struct {
|
||||||
|
u64 application_id;
|
||||||
|
u32 extended_data_size;
|
||||||
|
u8 reserved[0x4];
|
||||||
|
} ContentMetaDeltaMetaExtendedHeader;
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
ContentMetaFirmwareVariationVersion_Invalid = 0,
|
||||||
|
ContentMetaFirmwareVariationVersion_V1 = 1,
|
||||||
|
ContentMetaFirmwareVariationVersion_V2 = 2,
|
||||||
|
ContentMetaFirmwareVariationVersion_Unknown = 3
|
||||||
|
} ContentMetaFirmwareVariationVersion;
|
||||||
|
|
||||||
|
/// Header for the extended data region in the SystemUpdate title, pointed to by the extended header.
|
||||||
|
/// If version is ContentMetaFirmwareVariationVersion_V1, this is followed by 'variation_count' ContentMetaFirmwareVariationInfoV1 entries.
|
||||||
|
/// Otherwise, if version is ContentMetaFirmwareVariationVersion_V2, this is followed by:
|
||||||
|
/// * 'variation_count' firmware variation IDs.
|
||||||
|
/// * 'variation_count' ContentMetaFirmwareVariationInfoV2 entries.
|
||||||
|
/// * (Optionally) A variable number of NcmContentMetaInfo entries, which is the sum of all 'meta_count' values from ContentMetaFirmwareVariationInfoV2 entries where 'refer_to_base' is set to false.
|
||||||
|
typedef struct {
|
||||||
|
u32 version; ///< ContentMetaFirmwareVariationVersion.
|
||||||
|
u32 variation_count; ///< Determines how many firmware variation entries are available after this header.
|
||||||
|
} ContentMetaSystemUpdateMetaExtendedDataHeader;
|
||||||
|
|
||||||
|
/// Used if the firmware variation version matches ContentMetaFirmwareVariationVersion_V1.
|
||||||
|
typedef struct {
|
||||||
|
u32 firmware_variation_id;
|
||||||
|
u8 reserved[0x1C];
|
||||||
|
} ContentMetaFirmwareVariationInfoV1;
|
||||||
|
|
||||||
|
/// Used if the firmware variation version matches ContentMetaFirmwareVariationVersion_V2.
|
||||||
|
typedef struct {
|
||||||
|
bool refer_to_base;
|
||||||
|
u8 reserved_1[0x3];
|
||||||
|
u32 meta_count;
|
||||||
|
u8 reserved_2[0x18];
|
||||||
|
} ContentMetaFirmwareVariationInfoV2;
|
||||||
|
|
||||||
|
/// Header for the extended data region in Patch titles, pointed to by the extended header.
|
||||||
|
/// This is followed by:
|
||||||
|
/// * 'history_count' ContentMetaPatchHistoryHeader entries.
|
||||||
|
/// * 'delta_history_count' ContentMetaPatchDeltaHistory entries.
|
||||||
|
/// * 'delta_count' ContentMetaPatchDeltaHeader entries.
|
||||||
|
/// * 'fragment_set_count' ContentMetaFragmentSet entries.
|
||||||
|
/// * 'history_content_count' NcmContentInfo entries.
|
||||||
|
/// * 'delta_content_count' NcmPackagedContentInfo entries.
|
||||||
|
/// * A variable number of ContentMetaFragmentIndicator entries, which is the sum of all 'fragment_count' values from ContentMetaFragmentSet entries.
|
||||||
|
typedef struct {
|
||||||
|
u32 history_count;
|
||||||
|
u32 delta_history_count;
|
||||||
|
u32 delta_count;
|
||||||
|
u32 fragment_set_count;
|
||||||
|
u32 history_content_count;
|
||||||
|
u32 delta_content_count;
|
||||||
|
u8 reserved[0x4];
|
||||||
|
} ContentMetaPatchMetaExtendedDataHeader;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
NcmContentMetaKey content_meta_key;
|
||||||
|
u8 digest[CNMT_DIGEST_SIZE];
|
||||||
|
u16 content_info_count;
|
||||||
|
u8 reserved[0x6];
|
||||||
|
} ContentMetaPatchHistoryHeader;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
u64 source_patch_id;
|
||||||
|
u64 destination_patch_id;
|
||||||
|
ContentMetaVersion source_version;
|
||||||
|
ContentMetaVersion destination_version;
|
||||||
|
u64 download_size;
|
||||||
|
u8 reserved[0x8];
|
||||||
|
} ContentMetaPatchDeltaHistory;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
u64 source_patch_id;
|
||||||
|
u64 destination_patch_id;
|
||||||
|
ContentMetaVersion source_version;
|
||||||
|
ContentMetaVersion destination_version;
|
||||||
|
u16 fragment_set_count;
|
||||||
|
u8 reserved_1[0x6];
|
||||||
|
u16 content_info_count;
|
||||||
|
u8 reserved_2[0x6];
|
||||||
|
} ContentMetaPatchDeltaHeader;
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
ContentMetaUpdateType_ApplyAsDelta = 0,
|
||||||
|
ContentMetaUpdateType_Overwrite = 1,
|
||||||
|
ContentMetaUpdateType_Create = 2
|
||||||
|
} ContentMetaUpdateType;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
NcmContentId source_content_id;
|
||||||
|
NcmContentId destination_content_id;
|
||||||
|
u32 source_size_low;
|
||||||
|
u16 source_size_high;
|
||||||
|
u32 destination_size_low;
|
||||||
|
u16 destination_size_high;
|
||||||
|
u16 fragment_count;
|
||||||
|
NcmContentType fragment_target_content_type;
|
||||||
|
u8 update_type; ///< ContentMetaUpdateType.
|
||||||
|
u8 reserved[0x4];
|
||||||
|
} ContentMetaFragmentSet;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
u16 content_info_index;
|
||||||
|
u16 fragment_index;
|
||||||
|
} ContentMetaFragmentIndicator;
|
||||||
|
|
||||||
|
/// Header for the extended data region in Delta titles, pointed to by the extended header.
|
||||||
|
/// This is followed by:
|
||||||
|
/// * 'fragment_set_count' ContentMetaFragmentSet entries.
|
||||||
|
/// * A variable number of ContentMetaFragmentIndicator entries, which is the sum of all 'fragment_count' values from ContentMetaFragmentSet entries.
|
||||||
|
typedef struct {
|
||||||
|
u64 source_patch_id;
|
||||||
|
u64 destination_patch_id;
|
||||||
|
ContentMetaVersion source_version;
|
||||||
|
ContentMetaVersion destination_version;
|
||||||
|
u16 fragment_set_count;
|
||||||
|
u8 reserved[0x6];
|
||||||
|
} ContentMetaDeltaMetaExtendedDataHeader;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
NcaContext *nca_ctx; ///< Pointer to the NCA context for the Meta NCA from which CNMT data is retrieved.
|
||||||
|
PartitionFileSystemContext pfs_ctx; ///< PartitionFileSystemContext for the Meta NCA FS section #0, which is where the CNMT is stored.
|
||||||
|
PartitionFileSystemEntry *pfs_entry; ///< PartitionFileSystemEntry for the CNMT in the Meta NCA FS section #0. Used to generate a NcaHierarchicalSha256Patch if needed.
|
||||||
|
NcaHierarchicalSha256Patch nca_patch; ///< NcaHierarchicalSha256Patch generated if CNMT modifications are needed. Used to seamlessly replace Meta NCA data while writing it.
|
||||||
|
///< 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.
|
||||||
|
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'.
|
||||||
|
NcmContentMetaInfo *content_meta_info; ///< Pointer to the NcmContentMetaInfo entries within 'raw_data', if available. The content meta count is stored in 'packaged_header'.
|
||||||
|
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'.
|
||||||
|
} ContentMetaContext;
|
||||||
|
|
||||||
|
/// Initializes a ContentMetaContext using a previously initialized NcaContext (which must belong to a Meta NCA).
|
||||||
|
bool cnmtInitializeContext(ContentMetaContext *out, NcaContext *nca_ctx);
|
||||||
|
|
||||||
|
/// Helper inline functions.
|
||||||
|
|
||||||
|
NX_INLINE void cnmtFreeContext(ContentMetaContext *cnmt_ctx)
|
||||||
|
{
|
||||||
|
if (!cnmt_ctx) return;
|
||||||
|
pfsFreeContext(&(cnmt_ctx->pfs_ctx));
|
||||||
|
pfsFreeEntryPatch(&(cnmt_ctx->nca_patch));
|
||||||
|
if (cnmt_ctx->raw_data) free(cnmt_ctx->raw_data);
|
||||||
|
memset(cnmt_ctx, 0, sizeof(ContentMetaContext));
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif /* __CNMT_H__ */
|
Loading…
Reference in a new issue