1
0
Fork 0
mirror of https://github.com/DarkMatterCore/nxdumptool.git synced 2024-11-26 04:02:11 +00:00

nca: add ncaInitializeContextByHashFileSystemEntry

Other changes include:

* nca: move common logic from ncaInitializeContext() into ncaInitializeContextCommon().
This commit is contained in:
Pablo Curiel 2024-08-11 20:15:45 +02:00
parent 5a40167a13
commit a06a511ce7
3 changed files with 123 additions and 50 deletions

View file

@ -24,6 +24,7 @@
#ifndef __NCA_H__ #ifndef __NCA_H__
#define __NCA_H__ #define __NCA_H__
#include "hfs.h"
#include "tik.h" #include "tik.h"
#ifdef __cplusplus #ifdef __cplusplus
@ -33,9 +34,9 @@ extern "C" {
#define NCA_FS_HEADER_COUNT 4 #define NCA_FS_HEADER_COUNT 4
#define NCA_FULL_HEADER_LENGTH (sizeof(NcaHeader) + (sizeof(NcaFsHeader) * NCA_FS_HEADER_COUNT)) #define NCA_FULL_HEADER_LENGTH (sizeof(NcaHeader) + (sizeof(NcaFsHeader) * NCA_FS_HEADER_COUNT))
#define NCA_NCA0_MAGIC 0x4E434130 /* "NCA0". */ #define NCA_NCA0_MAGIC 0x4E434130 /* "NCA0". */
#define NCA_NCA2_MAGIC 0x4E434132 /* "NCA2". */ #define NCA_NCA2_MAGIC 0x4E434132 /* "NCA2". */
#define NCA_NCA3_MAGIC 0x4E434133 /* "NCA3". */ #define NCA_NCA3_MAGIC 0x4E434133 /* "NCA3". */
#define NCA_KEY_AREA_KEY_COUNT 0x10 #define NCA_KEY_AREA_KEY_COUNT 0x10
#define NCA_KEY_AREA_SIZE (NCA_KEY_AREA_KEY_COUNT * AES_128_KEY_SIZE) #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_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_MAX_LEVEL_COUNT 7
#define NCA_IVFC_LEVEL_COUNT (NCA_IVFC_MAX_LEVEL_COUNT - 1) #define NCA_IVFC_LEVEL_COUNT (NCA_IVFC_MAX_LEVEL_COUNT - 1)
#define NCA_IVFC_BLOCK_SIZE(x) (1U << (x)) #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_BKTR_VERSION 1
#define NCA_FS_SECTOR_SIZE 0x200 #define NCA_FS_SECTOR_SIZE 0x200
@ -58,7 +59,11 @@ extern "C" {
#define NCA_AES_XTS_SECTOR_SIZE 0x200 #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 { typedef enum {
NcaDistributionType_Download = 0, 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()). /// 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); 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(). /// 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. /// Input offset must be relative to the start of the NCA content file.
bool ncaReadContentFile(NcaContext *ctx, void *out, u64 read_size, u64 offset); bool ncaReadContentFile(NcaContext *ctx, void *out, u64 read_size, u64 offset);

View file

@ -61,15 +61,15 @@ typedef struct {
/// Add-on contents: the previous/next pointers reference sibling add-on contents. /// 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. /// 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 { typedef struct _TitleInfo {
u8 storage_id; ///< NcmStorageId. u8 storage_id; ///< NcmStorageId.
NcmContentMetaKey meta_key; ///< Used with ncm calls. NcmContentMetaKey meta_key; ///< Used with ncm calls.
Version version; ///< Holds the same value from meta_key.version. Version version; ///< Holds the same value from meta_key.version.
u32 content_count; ///< Content info count. u32 content_count; ///< Content info count.
NcmContentInfo *content_infos; ///< Content info entries from this title. NcmContentInfo *content_infos; ///< Content info entries from this title.
u64 size; ///< Total title size. u64 size; ///< Total title size.
char size_str[32]; ///< Total title size string. char size_str[32]; ///< Total title size string.
TitleApplicationMetadata *app_metadata; ///< User application metadata. TitleApplicationMetadata *app_metadata; ///< User application metadata.
struct _TitleInfo *previous, *next; ///< Linked lists. struct _TitleInfo *previous, *next; ///< Linked lists.
} TitleInfo; } TitleInfo;
/// Used to deal with user applications stored in the eMMC, SD card and/or gamecard. /// Used to deal with user applications stored in the eMMC, SD card and/or gamecard.

View file

@ -127,6 +127,8 @@ static const u8 g_nca0KeyAreaHash[SHA256_HASH_SIZE] = {
/* Function prototypes. */ /* Function prototypes. */
static bool ncaInitializeContextCommon(NcaContext *out, u8 storage_id, NcmContentStorage *ncm_storage, Ticket *tik);
NX_INLINE bool ncaIsFsInfoEntryValid(NcaFsInfo *fs_info); NX_INLINE bool ncaIsFsInfoEntryValid(NcaFsInfo *fs_info);
static bool ncaReadDecryptedHeader(NcaContext *ctx); 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) 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; NcmContentStorage *ncm_storage = NULL;
u8 valid_fs_section_cnt = 0;
if (!out || (storage_id != NcmStorageId_GameCard && !(ncm_storage = titleGetNcmStorageByStorageId(storage_id))) || \ 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)) || \ (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)); memset(out, 0, sizeof(NcaContext));
/* Fill NCA context. */ /* 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_id = meta_key->id;
out->title_version.value = meta_key->version; out->title_version.value = meta_key->version;
out->title_type = meta_key->type; out->title_type = meta_key->type;
memcpy(&(out->content_id), &(content_info->content_id), sizeof(NcmContentId)); 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)); ncmContentInfoSizeToU64(content_info, &(out->content_size));
utilsGenerateFormattedSizeString((double)out->content_size, out->content_size_str, sizeof(out->content_size_str)); 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) if (out->content_size < NCA_FULL_HEADER_LENGTH)
{ {
LOG_MSG_ERROR("Invalid size for NCA \"%s\"!", out->content_id_str); 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. */ return ncaInitializeContextCommon(out, storage_id, ncm_storage, tik);
if (!ncaReadDecryptedHeader(out)) }
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; return false;
} }
if (out->rights_id_available) const char *hfs_entry_name = NULL;
{ size_t hfs_entry_name_len = 0;
Ticket tmp_tik = {0};
Ticket *usable_tik = (tik ? tik : &tmp_tik);
/* Retrieve ticket. */ /* Clear output NCA context. */
/* This will return true if it has already been retrieved. */ memset(out, 0, sizeof(NcaContext));
if (tikRetrieveTicketByRightsId(usable_tik, &(out->header.rights_id), out->key_generation, out->storage_id == NcmStorageId_GameCard))
{ /* Get Hash FS entry name. */
/* Copy decrypted titlekey. */ hfs_entry_name = hfsGetEntryName(hfs_ctx, hfs_entry);
memcpy(out->titlekey, usable_tik->dec_titlekey, sizeof(usable_tik->dec_titlekey)); hfs_entry_name_len = (hfs_entry_name ? strlen(hfs_entry_name) : 0);
out->titlekey_retrieved = true;
} else { if (!hfs_entry_name || (hfs_entry_name_len != NCA_HFS_REGULAR_NAME_LENGTH && hfs_entry_name_len != NCA_HFS_META_NAME_LENGTH) || \
/* We must proceed even if we have no ticket. The user may just want to copy a raw NCA. */ strcmp(hfs_entry_name + hfs_entry_name_len - 4, ".nca") != 0)
LOG_MSG_ERROR("Error retrieving ticket for NCA \"%s\"!", out->content_id_str); {
} LOG_MSG_ERROR("Invalid HFS entry name!");
return false;
} }
/* Parse NCA FS sections. */ /* Fill NCA context. */
for(u8 i = 0; i < NCA_FS_HEADER_COUNT; i++) 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. */ LOG_MSG_ERROR("Error retrieving offset for \"%s\" entry in %s hash FS partition!", hfs_entry_name, hfsGetPartitionNameString(hfs_ctx->type));
if (ncaInitializeFsSectionContext(out, i)) valid_fs_section_cnt++; 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 ncaInitializeContextCommon(out, NcmStorageId_GameCard, NULL, tik);
return (valid_fs_section_cnt > 0);
} }
bool ncaReadContentFile(NcaContext *ctx, void *out, u64 read_size, u64 offset) bool ncaReadContentFile(NcaContext *ctx, void *out, u64 read_size, u64 offset)
@ -574,6 +578,61 @@ const char *ncaGetFsSectionTypeName(NcaFsSectionContext *ctx)
return str; 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) NX_INLINE bool ncaIsFsInfoEntryValid(NcaFsInfo *fs_info)
{ {
if (!fs_info) return false; if (!fs_info) return false;