From a06a511ce7909316647f750e87197c393b38a0ad Mon Sep 17 00:00:00 2001 From: Pablo Curiel Date: Sun, 11 Aug 2024 20:15:45 +0200 Subject: [PATCH] nca: add ncaInitializeContextByHashFileSystemEntry Other changes include: * nca: move common logic from ncaInitializeContext() into ncaInitializeContextCommon(). --- include/core/nca.h | 26 +++++++-- include/core/title.h | 18 +++--- source/core/nca.c | 129 +++++++++++++++++++++++++++++++------------ 3 files changed, 123 insertions(+), 50 deletions(-) diff --git a/include/core/nca.h b/include/core/nca.h index e050bd3..75f61ab 100644 --- a/include/core/nca.h +++ b/include/core/nca.h @@ -24,6 +24,7 @@ #ifndef __NCA_H__ #define __NCA_H__ +#include "hfs.h" #include "tik.h" #ifdef __cplusplus @@ -33,9 +34,9 @@ extern "C" { #define NCA_FS_HEADER_COUNT 4 #define NCA_FULL_HEADER_LENGTH (sizeof(NcaHeader) + (sizeof(NcaFsHeader) * NCA_FS_HEADER_COUNT)) -#define NCA_NCA0_MAGIC 0x4E434130 /* "NCA0". */ -#define NCA_NCA2_MAGIC 0x4E434132 /* "NCA2". */ -#define NCA_NCA3_MAGIC 0x4E434133 /* "NCA3". */ +#define NCA_NCA0_MAGIC 0x4E434130 /* "NCA0". */ +#define NCA_NCA2_MAGIC 0x4E434132 /* "NCA2". */ +#define NCA_NCA3_MAGIC 0x4E434133 /* "NCA3". */ #define NCA_KEY_AREA_KEY_COUNT 0x10 #define NCA_KEY_AREA_SIZE (NCA_KEY_AREA_KEY_COUNT * AES_128_KEY_SIZE) @@ -45,12 +46,12 @@ extern "C" { #define NCA_HIERARCHICAL_SHA256_MAX_REGION_COUNT 5 -#define NCA_IVFC_MAGIC 0x49564643 /* "IVFC". */ +#define NCA_IVFC_MAGIC 0x49564643 /* "IVFC". */ #define NCA_IVFC_MAX_LEVEL_COUNT 7 #define NCA_IVFC_LEVEL_COUNT (NCA_IVFC_MAX_LEVEL_COUNT - 1) #define NCA_IVFC_BLOCK_SIZE(x) (1U << (x)) -#define NCA_BKTR_MAGIC 0x424B5452 /* "BKTR". */ +#define NCA_BKTR_MAGIC 0x424B5452 /* "BKTR". */ #define NCA_BKTR_VERSION 1 #define NCA_FS_SECTOR_SIZE 0x200 @@ -58,7 +59,11 @@ extern "C" { #define NCA_AES_XTS_SECTOR_SIZE 0x200 -#define NCA_SIGNATURE_AREA_SIZE 0x200 /* Signature is calculated starting at the NCA header magic word. */ +#define NCA_SIGNATURE_AREA_SIZE 0x200 /* Signature is calculated starting at the NCA header magic word. */ + +#define NCA_CONTENT_ID_STR_LENGTH 0x20 /* Content ID. */ +#define NCA_HFS_REGULAR_NAME_LENGTH (NCA_CONTENT_ID_STR_LENGTH + 4) /* Content ID + ".nca". */ +#define NCA_HFS_META_NAME_LENGTH (NCA_CONTENT_ID_STR_LENGTH + 9) /* Content ID + ".cnmt.nca". */ typedef enum { NcaDistributionType_Download = 0, @@ -503,6 +508,15 @@ void ncaFreeCryptoBuffer(void); /// If ticket data can't be retrieved, the context will still be initialized, but anything that involves working with encrypted NCA FS section blocks won't be possible (e.g. ncaReadFsSection()). bool ncaInitializeContext(NcaContext *out, u8 storage_id, u8 hfs_partition_type, const NcmContentMetaKey *meta_key, const NcmContentInfo *content_info, Ticket *tik); +/// Initializes a NCA context using a Hash FS context and a Hash FS file entry. +/// If the NCA holds a populated Rights ID field, ticket data will need to be retrieved. +/// If the 'tik' argument points to a valid Ticket element, it will either be updated (if it's empty) or used to read ticket data that has already been retrieved. +/// If the 'tik' argument is NULL, the function will just retrieve the necessary ticket data on its own. +/// If ticket data can't be retrieved, the context will still be initialized, but anything that involves working with encrypted NCA FS section blocks won't be possible (e.g. ncaReadFsSection()). +/// Since this function doesn't take in NcmContentMetaKey nor NcmContentInfo arguments, information such as title ID, title version, title type, content type and ID offset won't be available +/// in the returned NCA context. +bool ncaInitializeContextByHashFileSystemEntry(NcaContext *out, HashFileSystemContext *hfs_ctx, HashFileSystemEntry *hfs_entry, Ticket *tik); + /// Reads raw encrypted data from a NCA using an input context, previously initialized by ncaInitializeContext(). /// Input offset must be relative to the start of the NCA content file. bool ncaReadContentFile(NcaContext *ctx, void *out, u64 read_size, u64 offset); diff --git a/include/core/title.h b/include/core/title.h index 3abdcf1..7f7da37 100644 --- a/include/core/title.h +++ b/include/core/title.h @@ -61,15 +61,15 @@ typedef struct { /// Add-on contents: the previous/next pointers reference sibling add-on contents. /// Add-on content patches: the previous/next pointers reference other patches with the same ID and/or other patches for sibling add-on contents. typedef struct _TitleInfo { - u8 storage_id; ///< NcmStorageId. - NcmContentMetaKey meta_key; ///< Used with ncm calls. - Version version; ///< Holds the same value from meta_key.version. - u32 content_count; ///< Content info count. - NcmContentInfo *content_infos; ///< Content info entries from this title. - u64 size; ///< Total title size. - char size_str[32]; ///< Total title size string. - TitleApplicationMetadata *app_metadata; ///< User application metadata. - struct _TitleInfo *previous, *next; ///< Linked lists. + u8 storage_id; ///< NcmStorageId. + NcmContentMetaKey meta_key; ///< Used with ncm calls. + Version version; ///< Holds the same value from meta_key.version. + u32 content_count; ///< Content info count. + NcmContentInfo *content_infos; ///< Content info entries from this title. + u64 size; ///< Total title size. + char size_str[32]; ///< Total title size string. + TitleApplicationMetadata *app_metadata; ///< User application metadata. + struct _TitleInfo *previous, *next; ///< Linked lists. } TitleInfo; /// Used to deal with user applications stored in the eMMC, SD card and/or gamecard. diff --git a/source/core/nca.c b/source/core/nca.c index c253c74..b8e232c 100644 --- a/source/core/nca.c +++ b/source/core/nca.c @@ -127,6 +127,8 @@ static const u8 g_nca0KeyAreaHash[SHA256_HASH_SIZE] = { /* Function prototypes. */ +static bool ncaInitializeContextCommon(NcaContext *out, u8 storage_id, NcmContentStorage *ncm_storage, Ticket *tik); + NX_INLINE bool ncaIsFsInfoEntryValid(NcaFsInfo *fs_info); static bool ncaReadDecryptedHeader(NcaContext *ctx); @@ -178,7 +180,6 @@ void ncaFreeCryptoBuffer(void) bool ncaInitializeContext(NcaContext *out, u8 storage_id, u8 hfs_partition_type, const NcmContentMetaKey *meta_key, const NcmContentInfo *content_info, Ticket *tik) { NcmContentStorage *ncm_storage = NULL; - u8 valid_fs_section_cnt = 0; if (!out || (storage_id != NcmStorageId_GameCard && !(ncm_storage = titleGetNcmStorageByStorageId(storage_id))) || \ (storage_id == NcmStorageId_GameCard && (hfs_partition_type < HashFileSystemPartitionType_Root || hfs_partition_type >= HashFileSystemPartitionType_Count)) || \ @@ -192,24 +193,18 @@ bool ncaInitializeContext(NcaContext *out, u8 storage_id, u8 hfs_partition_type, memset(out, 0, sizeof(NcaContext)); /* Fill NCA context. */ - out->storage_id = storage_id; - out->ncm_storage = (out->storage_id != NcmStorageId_GameCard ? ncm_storage : NULL); - out->title_id = meta_key->id; out->title_version.value = meta_key->version; out->title_type = meta_key->type; memcpy(&(out->content_id), &(content_info->content_id), sizeof(NcmContentId)); - utilsGenerateHexString(out->content_id_str, sizeof(out->content_id_str), out->content_id.c, sizeof(out->content_id.c), false); - - utilsGenerateHexString(out->hash_str, sizeof(out->hash_str), out->hash, sizeof(out->hash), false); /* Placeholder, needs to be manually calculated. */ - - out->content_type = content_info->content_type; - out->id_offset = content_info->id_offset; ncmContentInfoSizeToU64(content_info, &(out->content_size)); utilsGenerateFormattedSizeString((double)out->content_size, out->content_size_str, sizeof(out->content_size_str)); + out->content_type = content_info->content_type; + out->id_offset = content_info->id_offset; + if (out->content_size < NCA_FULL_HEADER_LENGTH) { LOG_MSG_ERROR("Invalid size for NCA \"%s\"!", out->content_id_str); @@ -230,41 +225,50 @@ bool ncaInitializeContext(NcaContext *out, u8 storage_id, u8 hfs_partition_type, } } - /* Read decrypted NCA header and NCA FS section headers. */ - if (!ncaReadDecryptedHeader(out)) + return ncaInitializeContextCommon(out, storage_id, ncm_storage, tik); +} + +bool ncaInitializeContextByHashFileSystemEntry(NcaContext *out, HashFileSystemContext *hfs_ctx, HashFileSystemEntry *hfs_entry, Ticket *tik) +{ + if (!out || !hfsIsValidContext(hfs_ctx) || !hfs_entry || hfs_entry->size < NCA_FULL_HEADER_LENGTH) { - LOG_MSG_ERROR("Failed to read decrypted NCA \"%s\" header!", out->content_id_str); + LOG_MSG_ERROR("Invalid parameters!"); return false; } - if (out->rights_id_available) - { - Ticket tmp_tik = {0}; - Ticket *usable_tik = (tik ? tik : &tmp_tik); + const char *hfs_entry_name = NULL; + size_t hfs_entry_name_len = 0; - /* Retrieve ticket. */ - /* This will return true if it has already been retrieved. */ - if (tikRetrieveTicketByRightsId(usable_tik, &(out->header.rights_id), out->key_generation, out->storage_id == NcmStorageId_GameCard)) - { - /* Copy decrypted titlekey. */ - memcpy(out->titlekey, usable_tik->dec_titlekey, sizeof(usable_tik->dec_titlekey)); - out->titlekey_retrieved = true; - } else { - /* We must proceed even if we have no ticket. The user may just want to copy a raw NCA. */ - LOG_MSG_ERROR("Error retrieving ticket for NCA \"%s\"!", out->content_id_str); - } + /* Clear output NCA context. */ + memset(out, 0, sizeof(NcaContext)); + + /* Get Hash FS entry name. */ + hfs_entry_name = hfsGetEntryName(hfs_ctx, hfs_entry); + hfs_entry_name_len = (hfs_entry_name ? strlen(hfs_entry_name) : 0); + + if (!hfs_entry_name || (hfs_entry_name_len != NCA_HFS_REGULAR_NAME_LENGTH && hfs_entry_name_len != NCA_HFS_META_NAME_LENGTH) || \ + strcmp(hfs_entry_name + hfs_entry_name_len - 4, ".nca") != 0) + { + LOG_MSG_ERROR("Invalid HFS entry name!"); + return false; } - /* Parse NCA FS sections. */ - for(u8 i = 0; i < NCA_FS_HEADER_COUNT; i++) + /* Fill NCA context. */ + utilsParseHexString(&(out->content_id), sizeof(out->content_id), hfs_entry_name, NCA_CONTENT_ID_STR_LENGTH); + + out->content_size = hfs_entry->size; + utilsGenerateFormattedSizeString((double)out->content_size, out->content_size_str, sizeof(out->content_size_str)); + + if (hfs_entry_name_len == NCA_HFS_META_NAME_LENGTH) out->content_type = NcmContentType_Meta; /* Set Meta as the content type if we know it. */ + + /* Retrieve gamecard NCA offset. */ + if (!gamecardGetHashFileSystemEntryInfoByName(hfs_ctx->type, hfs_entry_name, &(out->gamecard_offset), NULL)) { - /* Increase valid NCA FS section count if the FS section is valid. */ - if (ncaInitializeFsSectionContext(out, i)) valid_fs_section_cnt++; + LOG_MSG_ERROR("Error retrieving offset for \"%s\" entry in %s hash FS partition!", hfs_entry_name, hfsGetPartitionNameString(hfs_ctx->type)); + return false; } - if (!valid_fs_section_cnt) LOG_MSG_ERROR("Unable to identify any valid FS sections in NCA \"%s\"!", out->content_id_str); - - return (valid_fs_section_cnt > 0); + return ncaInitializeContextCommon(out, NcmStorageId_GameCard, NULL, tik); } bool ncaReadContentFile(NcaContext *ctx, void *out, u64 read_size, u64 offset) @@ -574,6 +578,61 @@ const char *ncaGetFsSectionTypeName(NcaFsSectionContext *ctx) return str; } +static bool ncaInitializeContextCommon(NcaContext *out, u8 storage_id, NcmContentStorage *ncm_storage, Ticket *tik) +{ + if (!out || out->content_size < NCA_FULL_HEADER_LENGTH) + { + LOG_MSG_ERROR("Invalid parameters!"); + return false; + } + + u8 valid_fs_section_cnt = 0; + + /* Fill NCA context. */ + out->storage_id = storage_id; + out->ncm_storage = (out->storage_id != NcmStorageId_GameCard ? ncm_storage : NULL); + + utilsGenerateHexString(out->content_id_str, sizeof(out->content_id_str), out->content_id.c, sizeof(out->content_id.c), false); + + utilsGenerateHexString(out->hash_str, sizeof(out->hash_str), out->hash, sizeof(out->hash), false); /* Placeholder, needs to be manually calculated. */ + + /* Read decrypted NCA header and NCA FS section headers. */ + if (!ncaReadDecryptedHeader(out)) + { + LOG_MSG_ERROR("Failed to read decrypted NCA \"%s\" header!", out->content_id_str); + return false; + } + + if (out->rights_id_available) + { + Ticket tmp_tik = {0}; + Ticket *usable_tik = (tik ? tik : &tmp_tik); + + /* Retrieve ticket. */ + /* This will return true if it has already been retrieved. */ + if (tikRetrieveTicketByRightsId(usable_tik, &(out->header.rights_id), out->key_generation, out->storage_id == NcmStorageId_GameCard)) + { + /* Copy decrypted titlekey. */ + memcpy(out->titlekey, usable_tik->dec_titlekey, sizeof(usable_tik->dec_titlekey)); + out->titlekey_retrieved = true; + } else { + /* We must proceed even if we have no ticket. The user may just want to copy a raw NCA. */ + LOG_MSG_ERROR("Error retrieving ticket for NCA \"%s\"!", out->content_id_str); + } + } + + /* Parse NCA FS sections. */ + for(u8 i = 0; i < NCA_FS_HEADER_COUNT; i++) + { + /* Increase valid NCA FS section count if the FS section is valid. */ + if (ncaInitializeFsSectionContext(out, i)) valid_fs_section_cnt++; + } + + if (!valid_fs_section_cnt) LOG_MSG_ERROR("Unable to identify any valid FS sections in NCA \"%s\"!", out->content_id_str); + + return (valid_fs_section_cnt > 0); +} + NX_INLINE bool ncaIsFsInfoEntryValid(NcaFsInfo *fs_info) { if (!fs_info) return false;