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:
parent
5a40167a13
commit
a06a511ce7
3 changed files with 123 additions and 50 deletions
|
@ -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);
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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;
|
||||||
|
|
Loading…
Reference in a new issue