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

Bunch of changes.

* Updated NCA structs (including NcaSparseInfo).
* Changed the way NCA header + NCA FS header decryption is handled.
* Changed the way the NCA encrypted key area is handled.
* Unified hierarchical patch generation functions.
* Updated PFS, RomFS and BKTR functions accordingly to reflect NCA handling changes.
* Logfile path is now relative.
* Gamecard initial data lookup code now uses the initial data hash from the gamecard header (a tad bit slower, but way more failproof).
This commit is contained in:
Pablo Curiel 2020-07-22 04:03:28 -04:00
parent cddf57363c
commit 90e0f057bc
13 changed files with 738 additions and 794 deletions

View file

@ -37,14 +37,14 @@ bool bktrInitializeContext(BktrContext *out, NcaFsSectionContext *base_nca_fs_ct
{
NcaContext *base_nca_ctx = NULL, *update_nca_ctx = NULL;
if (!out || !base_nca_fs_ctx || !base_nca_fs_ctx->enabled || !(base_nca_ctx = (NcaContext*)base_nca_fs_ctx->nca_ctx) || !base_nca_fs_ctx->header || \
base_nca_fs_ctx->section_type != NcaFsSectionType_RomFs || base_nca_fs_ctx->encryption_type == NcaEncryptionType_AesCtrEx || !update_nca_fs_ctx || !update_nca_fs_ctx->enabled || \
!update_nca_fs_ctx->header || !(update_nca_ctx = (NcaContext*)update_nca_fs_ctx->nca_ctx) || update_nca_fs_ctx->section_type != NcaFsSectionType_PatchRomFs || \
update_nca_fs_ctx->encryption_type != NcaEncryptionType_AesCtrEx || base_nca_ctx->header.program_id != update_nca_ctx->header.program_id || \
base_nca_ctx->header.content_type != update_nca_ctx->header.content_type || __builtin_bswap32(update_nca_fs_ctx->header->patch_info.indirect_header.magic) != NCA_BKTR_MAGIC || \
__builtin_bswap32(update_nca_fs_ctx->header->patch_info.aes_ctr_ex_header.magic) != NCA_BKTR_MAGIC || \
(update_nca_fs_ctx->header->patch_info.indirect_offset + update_nca_fs_ctx->header->patch_info.indirect_size) != update_nca_fs_ctx->header->patch_info.aes_ctr_ex_offset || \
(update_nca_fs_ctx->header->patch_info.aes_ctr_ex_offset + update_nca_fs_ctx->header->patch_info.aes_ctr_ex_size) != update_nca_fs_ctx->section_size)
if (!out || !base_nca_fs_ctx || !base_nca_fs_ctx->enabled || !(base_nca_ctx = (NcaContext*)base_nca_fs_ctx->nca_ctx) || base_nca_fs_ctx->section_type != NcaFsSectionType_RomFs || \
base_nca_fs_ctx->encryption_type == NcaEncryptionType_AesCtrEx || !update_nca_fs_ctx || !update_nca_fs_ctx->enabled || !(update_nca_ctx = (NcaContext*)update_nca_fs_ctx->nca_ctx) || \
update_nca_fs_ctx->section_type != NcaFsSectionType_PatchRomFs || update_nca_fs_ctx->encryption_type != NcaEncryptionType_AesCtrEx || \
base_nca_ctx->header.program_id != update_nca_ctx->header.program_id || base_nca_ctx->header.content_type != update_nca_ctx->header.content_type || \
__builtin_bswap32(update_nca_fs_ctx->header.patch_info.indirect_bucket.header.magic) != NCA_BKTR_MAGIC || \
__builtin_bswap32(update_nca_fs_ctx->header.patch_info.aes_ctr_ex_bucket.header.magic) != NCA_BKTR_MAGIC || \
(update_nca_fs_ctx->header.patch_info.indirect_bucket.offset + update_nca_fs_ctx->header.patch_info.indirect_bucket.size) != update_nca_fs_ctx->header.patch_info.aes_ctr_ex_bucket.offset || \
(update_nca_fs_ctx->header.patch_info.aes_ctr_ex_bucket.offset + update_nca_fs_ctx->header.patch_info.aes_ctr_ex_bucket.size) != update_nca_fs_ctx->section_size)
{
LOGFILE("Invalid parameters!");
return false;
@ -59,10 +59,10 @@ bool bktrInitializeContext(BktrContext *out, NcaFsSectionContext *base_nca_fs_ct
/* Fill context. */
bool success = false;
NcaPatchInfo *patch_info = &(update_nca_fs_ctx->header->patch_info);
NcaPatchInfo *patch_info = &(update_nca_fs_ctx->header.patch_info);
/* Allocate space for an extra (fake) indirect storage entry, to simplify our logic. */
out->indirect_block = calloc(1, patch_info->indirect_size + ((0x3FF0 / sizeof(u64)) * sizeof(BktrIndirectStorageEntry)));
out->indirect_block = calloc(1, patch_info->indirect_bucket.size + ((0x3FF0 / sizeof(u64)) * sizeof(BktrIndirectStorageEntry)));
if (!out->indirect_block)
{
LOGFILE("Unable to allocate memory for the BKTR Indirect Storage Block!");
@ -70,14 +70,14 @@ bool bktrInitializeContext(BktrContext *out, NcaFsSectionContext *base_nca_fs_ct
}
/* Read indirect storage block data. */
if (!ncaReadFsSection(update_nca_fs_ctx, out->indirect_block, patch_info->indirect_size, patch_info->indirect_offset))
if (!ncaReadFsSection(update_nca_fs_ctx, out->indirect_block, patch_info->indirect_bucket.size, patch_info->indirect_bucket.offset))
{
LOGFILE("Failed to read BKTR Indirect Storage Block data!");
goto end;
}
/* Allocate space for an extra (fake) AesCtrEx storage entry, to simplify our logic. */
out->aes_ctr_ex_block = calloc(1, patch_info->aes_ctr_ex_size + (((0x3FF0 / sizeof(u64)) + 1) * sizeof(BktrAesCtrExStorageEntry)));
out->aes_ctr_ex_block = calloc(1, patch_info->aes_ctr_ex_bucket.size + (((0x3FF0 / sizeof(u64)) + 1) * sizeof(BktrAesCtrExStorageEntry)));
if (!out->aes_ctr_ex_block)
{
LOGFILE("Unable to allocate memory for the BKTR AesCtrEx Storage Block!");
@ -85,13 +85,13 @@ bool bktrInitializeContext(BktrContext *out, NcaFsSectionContext *base_nca_fs_ct
}
/* Read AesCtrEx storage block data. */
if (!ncaReadFsSection(update_nca_fs_ctx, out->aes_ctr_ex_block, patch_info->aes_ctr_ex_size, patch_info->aes_ctr_ex_offset))
if (!ncaReadFsSection(update_nca_fs_ctx, out->aes_ctr_ex_block, patch_info->aes_ctr_ex_bucket.size, patch_info->aes_ctr_ex_bucket.offset))
{
LOGFILE("Failed to read BKTR AesCtrEx Storage Block data!");
goto end;
}
if (out->aes_ctr_ex_block->physical_size != patch_info->aes_ctr_ex_offset)
if (out->aes_ctr_ex_block->physical_size != patch_info->aes_ctr_ex_bucket.offset)
{
LOGFILE("Invalid BKTR AesCtrEx Storage Block size!");
goto end;
@ -129,16 +129,16 @@ bool bktrInitializeContext(BktrContext *out, NcaFsSectionContext *base_nca_fs_ct
BktrIndirectStorageBucket *last_indirect_bucket = bktrGetIndirectStorageBucket(out->indirect_block, out->indirect_block->bucket_count - 1);
BktrAesCtrExStorageBucket *last_aes_ctr_ex_bucket = bktrGetAesCtrExStorageBucket(out->aes_ctr_ex_block, out->aes_ctr_ex_block->bucket_count - 1);
last_indirect_bucket->indirect_storage_entries[last_indirect_bucket->entry_count].virtual_offset = out->indirect_block->virtual_size;
last_aes_ctr_ex_bucket->aes_ctr_ex_storage_entries[last_aes_ctr_ex_bucket->entry_count].offset = patch_info->indirect_offset;
last_aes_ctr_ex_bucket->aes_ctr_ex_storage_entries[last_aes_ctr_ex_bucket->entry_count].generation = update_nca_fs_ctx->header->generation;
last_aes_ctr_ex_bucket->aes_ctr_ex_storage_entries[last_aes_ctr_ex_bucket->entry_count].offset = patch_info->indirect_bucket.offset;
last_aes_ctr_ex_bucket->aes_ctr_ex_storage_entries[last_aes_ctr_ex_bucket->entry_count].generation = update_nca_fs_ctx->header.aes_ctr_upper_iv.generation;
last_aes_ctr_ex_bucket->aes_ctr_ex_storage_entries[last_aes_ctr_ex_bucket->entry_count + 1].offset = update_nca_fs_ctx->section_size;
last_aes_ctr_ex_bucket->aes_ctr_ex_storage_entries[last_aes_ctr_ex_bucket->entry_count + 1].generation = 0;
/* Initialize update NCA RomFS context. */
/* Don't verify offsets from Patch RomFS sections, because they reflect the full, patched RomFS image. */
out->patch_romfs_ctx.nca_fs_ctx = update_nca_fs_ctx;
out->patch_romfs_ctx.offset = out->offset = update_nca_fs_ctx->header->hash_info.hierarchical_integrity.hash_target_layer_info.offset;
out->patch_romfs_ctx.size = out->size = update_nca_fs_ctx->header->hash_info.hierarchical_integrity.hash_target_layer_info.size;
out->patch_romfs_ctx.offset = out->offset = update_nca_fs_ctx->header.hash_data.integrity_meta_info.info_level_hash.level_information[NCA_IVFC_LEVEL_COUNT - 1].offset;
out->patch_romfs_ctx.size = out->size = update_nca_fs_ctx->header.hash_data.integrity_meta_info.info_level_hash.level_information[NCA_IVFC_LEVEL_COUNT - 1].size;
/* Read update NCA RomFS header. */
if (!bktrPhysicalSectionRead(out, &(out->patch_romfs_ctx.header), sizeof(RomFileSystemHeader), out->patch_romfs_ctx.offset))

View file

@ -23,6 +23,8 @@
#ifndef __FS_EXT_H__
#define __FS_EXT_H__
#define GAMECARD_CERT_MAGIC 0x43455254 /* "CERT". */
/// Located at offset 0x7000 in the gamecard image.
typedef struct {
u8 signature[0x100]; ///< RSA-2048 PKCS #1 signature over the rest of the data.

View file

@ -42,6 +42,8 @@
/* Type definitions. */
/// Only kept for documentation purposes, not really used.
/// A copy of the gamecard header without the RSA-2048 signature and a plaintext GameCardHeaderEncryptedArea precedes this struct in FS program memory.
typedef struct {
u32 memory_interface_mode;
u32 asic_status;
@ -106,6 +108,8 @@ static u64 g_gameCardCapacity = 0;
static u8 *g_gameCardHfsRootHeader = NULL; /// GameCardHashFileSystemHeader + (entry_count * GameCardHashFileSystemEntry) + Name Table.
static GameCardHashFileSystemPartitionInfo *g_gameCardHfsPartitions = NULL;
static GameCardKeyArea g_gameCardKeyArea = {0};
static MemoryLocation g_fsProgramMemory = {
.program_id = FS_SYSMODULE_TID,
.mask = 0,
@ -113,9 +117,6 @@ static MemoryLocation g_fsProgramMemory = {
.data_size = 0
};
static GameCardSecurityInformation g_gameCardSecurityInfo = {0};
static GameCardKeyArea g_gameCardKeyArea = {0};
/* Function prototypes. */
static bool gamecardCreateDetectionThread(void);
@ -127,7 +128,7 @@ NX_INLINE bool gamecardIsInserted(void);
static void gamecardLoadInfo(void);
static void gamecardFreeInfo(void);
static bool gamecardReadSecurityInformation(void);
static bool gamecardReadInitialData(void);
static bool gamecardGetHandleAndStorage(u32 partition);
NX_INLINE void gamecardCloseHandle(void);
@ -713,13 +714,13 @@ static void gamecardLoadInfo(void)
}
}
/* Read full FS program memory to retrieve the GameCardSecurityInformation data, which holds the gamecard initial data area. */
/* This must be performed while the gamecard is in secure mode, which is already taken care of in the gamecardReadStorageArea() calls from the last iteration in the previous for() loop. */
/* GameCardSecurityInformation data is returned by Lotus command "ChangeToSecureMode" (0xF), and kept in FS program memory only after the gamecard secure area has been both mounted and read from. */
/* Under some circumstances, the gamecard initial data is located *after* the GameCardSecurityInformation area (offset 0x600), instead of its common location at offset 0x400. */
if (!gamecardReadSecurityInformation())
/* Read full FS program memory to retrieve the GameCardInitialData block, which is part of the GameCardKeyArea block. */
/* In FS program memory, this is stored as part of the GameCardSecurityInformation struct, which is returned by Lotus command "ChangeToSecureMode" (0xF). */
/* This means it is only available *after* the gamecard secure area has been both mounted and read from, which has already been taken care of in the last iteration from the previous for() loop. */
/* The GameCardSecurityInformation struct is only kept for documentation purposes. It isn't used at all to retrieve the GameCardInitialData block. */
if (!gamecardReadInitialData())
{
LOGFILE("Failed to read gamecard security information area from FS program memory!");
LOGFILE("Failed to read gamecard initial data area from FS program memory!");
goto end;
}
@ -731,10 +732,8 @@ end:
static void gamecardFreeInfo(void)
{
memset(&g_gameCardHeader, 0, sizeof(GameCardHeader));
memset(&g_gameCardSecurityInfo, 0, sizeof(GameCardSecurityInformation));
memset(&g_gameCardKeyArea, 0, sizeof(GameCardKeyArea));
memset(&g_gameCardHeader, 0, sizeof(GameCardHeader));
g_gameCardStorageNormalAreaSize = 0;
g_gameCardStorageSecureAreaSize = 0;
@ -768,9 +767,10 @@ static void gamecardFreeInfo(void)
g_gameCardInfoLoaded = false;
}
static bool gamecardReadSecurityInformation(void)
static bool gamecardReadInitialData(void)
{
bool found = false;
u8 tmp_hash[SHA256_HASH_SIZE] = {0};
/* Retrieve full FS program memory dump. */
if (!memRetrieveFullProgramMemory(&g_fsProgramMemory))
@ -779,35 +779,20 @@ static bool gamecardReadSecurityInformation(void)
return false;
}
/* Look for the gamecard header in the FS memory dump. */
/* Look for the initial data block in the FS memory dump using the package ID and the initial data hash from the gamecard header. */
for(u64 offset = 0; offset < g_fsProgramMemory.data_size; offset++)
{
if (memcmp(&(g_gameCardHeader.magic), g_fsProgramMemory.data + offset, 0x90) != 0) continue;
if (memcmp(g_fsProgramMemory.data + offset, &(g_gameCardHeader.package_id), sizeof(g_gameCardHeader.package_id)) != 0) continue;
/* Found the gamecard header. Let's read the GameCardSecurityInformation element. */
offset += 0x100;
memcpy(&g_gameCardSecurityInfo, g_fsProgramMemory.data + offset, sizeof(GameCardSecurityInformation));
sha256CalculateHash(tmp_hash, g_fsProgramMemory.data + offset, sizeof(GameCardInitialData));
/* Check the key_source / package_id value. */
if (g_gameCardSecurityInfo.initial_data.package_id == g_gameCardHeader.package_id)
if (!memcmp(tmp_hash, g_gameCardHeader.initial_data_hash, SHA256_HASH_SIZE))
{
/* Jackpot. */
memcpy(&(g_gameCardKeyArea.initial_data), g_fsProgramMemory.data + offset, sizeof(GameCardInitialData));
found = true;
} else {
/* Copy the sector right after the GameCardSecurityInformation element from the memory dump, since it may hold the gamecard initial data. */
offset += sizeof(GameCardSecurityInformation);
memcpy(&(g_gameCardSecurityInfo.initial_data), g_fsProgramMemory.data + offset, sizeof(GameCardInitialData));
found = (g_gameCardSecurityInfo.initial_data.package_id == g_gameCardHeader.package_id);
}
break;
}
if (found)
{
memcpy(&(g_gameCardKeyArea.initial_data), &(g_gameCardSecurityInfo.initial_data), sizeof(GameCardInitialData));
} else {
LOGFILE("Failed to locate gamecard initial data area!");
}
/* Free FS memory dump. */
@ -824,29 +809,29 @@ static bool gamecardGetHandleAndStorage(u32 partition)
return false;
}
Result rc1 = 0, rc2 = 0;
Result rc = 0;
/* 10 tries. */
for(u8 i = 0; i < 10; i++)
{
/* 100 ms wait in case there was an error in the previous loop. */
if (R_FAILED(rc1) || R_FAILED(rc2)) svcSleepThread(100000000);
if (R_FAILED(rc)) svcSleepThread(100000000);
/* First, let's try to retrieve a gamecard handle. */
/* This can return 0x140A02 if the "nogc" patch is enabled by the running CFW. */
rc1 = fsDeviceOperatorGetGameCardHandle(&g_deviceOperator, &g_gameCardHandle);
if (R_FAILED(rc1))
rc = fsDeviceOperatorGetGameCardHandle(&g_deviceOperator, &g_gameCardHandle);
if (R_FAILED(rc))
{
LOGFILE("fsDeviceOperatorGetGameCardHandle failed on try #%u! (0x%08X).", i + 1, rc1);
LOGFILE("fsDeviceOperatorGetGameCardHandle failed on try #%u! (0x%08X).", i + 1, rc);
continue;
}
/* If the previous call succeeded, let's try to open the desired gamecard storage area. */
rc2 = fsOpenGameCardStorage(&g_gameCardStorage, &g_gameCardHandle, partition);
if (R_FAILED(rc2))
rc = fsOpenGameCardStorage(&g_gameCardStorage, &g_gameCardHandle, partition);
if (R_FAILED(rc))
{
gamecardCloseHandle(); /* Close invalid gamecard handle. */
LOGFILE("fsOpenGameCardStorage failed to open %s storage area on try #%u! (0x%08X).", GAMECARD_STORAGE_AREA_NAME(partition + 1), i + 1, rc2);
LOGFILE("fsOpenGameCardStorage failed to open %s storage area on try #%u! (0x%08X).", GAMECARD_STORAGE_AREA_NAME(partition + 1), i + 1, rc);
continue;
}
@ -854,7 +839,7 @@ static bool gamecardGetHandleAndStorage(u32 partition)
break;
}
return (R_SUCCEEDED(rc1) && R_SUCCEEDED(rc2));
return R_SUCCEEDED(rc);
}
NX_INLINE void gamecardCloseHandle(void)

View file

@ -26,7 +26,6 @@
#include "fs_ext.h"
#define GAMECARD_HEAD_MAGIC 0x48454144 /* "HEAD". */
#define GAMECARD_CERT_MAGIC 0x43455254 /* "CERT". */
#define GAMECARD_MEDIA_UNIT_SIZE 0x200
#define GAMECARD_MEDIA_UNIT_OFFSET(x) ((u64)(x) * GAMECARD_MEDIA_UNIT_SIZE)
@ -37,18 +36,18 @@
((x) == GameCardHashFileSystemPartitionType_Logo ? "logo" : ((x) == GameCardHashFileSystemPartitionType_Normal ? "normal" : \
((x) == GameCardHashFileSystemPartitionType_Secure ? "secure" : ((x) == GameCardHashFileSystemPartitionType_Boot ? "boot" : "unknown"))))))
/// Encrypted using AES-128-ECB with the common titlekek generator key (stored in the .rodata segment from the Lotus firmware).
typedef struct {
u64 package_id; ///< Matches package_id from GameCardHeader.
u8 reserved[0x8]; ///< Just zeroes.
} GameCardKeySource;
/// Plaintext area. Dumped from FS program memory.
typedef struct {
union {
u8 key_source[0x10]; ///< Encrypted using AES-128-ECB with the common titlekek generator key (stored in the .rodata segment from the Lotus firmware).
struct {
u64 package_id; ///< Matches package_id from GameCardHeader.
u64 padding; ///< Just zeroes.
};
};
GameCardKeySource key_source;
u8 encrypted_titlekey[0x10]; ///< Encrypted using AES-128-CCM with the decrypted key_source and the nonce from this section.
u8 mac[0x10]; ///< Used to verify the validity of the decrypted titlekey.
u8 nonce[0xC]; ///< Used as the IV to decrypt the key_source using AES-128-CCM.
u8 nonce[0xC]; ///< Used as the IV to decrypt encrypted_titlekey using AES-128-CCM.
u8 reserved[0x1C4];
} GameCardInitialData;
@ -81,7 +80,7 @@ typedef enum {
typedef struct {
u8 kek_index : 4; ///< GameCardKekIndex.
u8 titlekey_dec_index : 4;
} GameCardKeyFlags;
} GameCardKeyIndex;
typedef enum {
GameCardRomSize_1GiB = 0xFA,
@ -101,20 +100,20 @@ typedef enum {
} GameCardFlags;
typedef enum {
GameCardSelSec_ForT1 = 0,
GameCardSelSec_ForT2 = 1
GameCardSelSec_ForT1 = 1,
GameCardSelSec_ForT2 = 2
} GameCardSelSec;
typedef enum {
GameCardFwVersion_Dev = 0,
GameCardFwVersion_Prod = 1,
GameCardFwVersion_Since400NUP = 2
GameCardFwVersion_ForDev = 0,
GameCardFwVersion_Before400NUP = 1, ///< cup_version < 268435456 (4.0.0-0.0) in GameCardHeaderEncryptedArea.
GameCardFwVersion_Since400NUP = 2 ///< cup_version >= 268435456 (4.0.0-0.0) in GameCardHeaderEncryptedArea.
} GameCardFwVersion;
typedef enum {
GameCardAccCtrl_25MHz = 0xA10011,
GameCardAccCtrl_50MHz = 0xA10010
} GameCardAccCtrl;
GameCardAccCtrl1_25MHz = 0xA10011,
GameCardAccCtrl1_50MHz = 0xA10010 ///< GameCardRomSize_8GiB or greater.
} GameCardAccCtrl1;
typedef enum {
GameCardCompatibilityType_Normal = 0,
@ -129,27 +128,27 @@ typedef struct {
} GameCardFwMode;
typedef struct {
u32 GameCardUppVersion_MinorRelstep : 8;
u32 GameCardUppVersion_MajorRelstep : 8;
u32 GameCardUppVersion_Micro : 4;
u32 GameCardUppVersion_Minor : 6;
u32 GameCardUppVersion_Major : 6;
} GameCardUppVersion;
u32 GameCardCupVersion_MinorRelstep : 8;
u32 GameCardCupVersion_MajorRelstep : 8;
u32 GameCardCupVersion_Micro : 4;
u32 GameCardCupVersion_Minor : 6;
u32 GameCardCupVersion_Major : 6;
} GameCardCupVersion;
/// Encrypted using AES-128-CBC with the `xci_header_key` (which can't dumped through current methods) and the IV from `GameCardHeader`.
typedef struct {
u64 fw_version; ///< GameCardFwVersion.
u32 acc_ctrl; ///< GameCardAccCtrl.
u32 acc_ctrl_1; ///< GameCardAccCtrl1.
u32 wait_1_time_read; ///< Always 0x1388.
u32 wait_2_time_read; ///< Always 0.
u32 wait_1_time_write; ///< Always 0.
u32 wait_2_time_write; ///< Always 0.
GameCardFwMode fw_mode;
GameCardUppVersion upp_version;
GameCardCupVersion cup_version;
u8 compatibility_type; ///< GameCardCompatibilityType.
u8 reserved_1[0x3];
u64 upp_hash;
u64 upp_id; ///< Must match GAMECARD_UPDATE_TID.
u64 cup_hash;
u64 cup_id; ///< Must match GAMECARD_UPDATE_TID.
u8 reserved_2[0x38];
} GameCardHeaderEncryptedArea;
@ -159,9 +158,9 @@ typedef struct {
u32 magic; ///< "HEAD".
u32 secure_area_start_address; ///< Expressed in GAMECARD_MEDIA_UNIT_SIZE blocks.
u32 backup_area_start_address; ///< Always 0xFFFFFFFF.
GameCardKeyFlags key_flags;
GameCardKeyIndex key_index;
u8 rom_size; ///< GameCardRomSize.
u8 header_version;
u8 header_version; ///< Always 0.
u8 flags; ///< GameCardFlags.
u64 package_id;
u32 valid_data_end_address; ///< Expressed in GAMECARD_MEDIA_UNIT_SIZE blocks.
@ -172,8 +171,8 @@ typedef struct {
u8 partition_fs_header_hash[SHA256_HASH_SIZE];
u8 initial_data_hash[SHA256_HASH_SIZE];
u32 sel_sec; ///< GameCardSelSec.
u32 sel_t1_key_index;
u32 sel_key_index;
u32 sel_t1_key; ///< Always 2.
u32 sel_key; ///> Always 0.
u32 normal_area_end_address; ///< Expressed in GAMECARD_MEDIA_UNIT_SIZE blocks.
GameCardHeaderEncryptedArea encrypted_area;
} GameCardHeader;

View file

@ -200,7 +200,7 @@ int main(int argc, char *argv[])
int ret = 0;
LOGFILE("nxdumptool starting.");
LOGFILE(APP_TITLE " starting.");
consoleInit(NULL);
@ -216,7 +216,7 @@ int main(int argc, char *argv[])
u8 *buf = NULL;
u64 base_tid = (u64)0x01006F8002326000; // ACNH 0x01006F8002326000 | Smash 0x01006A800016E000 | Dark Souls 0x01004AB00A260000 | BotW 0x01007EF00011E000
u64 base_tid = (u64)0x01006A800016E000; // ACNH 0x01006F8002326000 | Smash 0x01006A800016E000 | Dark Souls 0x01004AB00A260000 | BotW 0x01007EF00011E000
u64 update_tid = (base_tid | 0x800);
Ticket base_tik = {0}, update_tik = {0};

File diff suppressed because it is too large Load diff

View file

@ -25,29 +25,28 @@
#include "tik.h"
#define NCA_HEADER_LENGTH 0x400
#define NCA_FS_HEADER_LENGTH 0x200
#define NCA_FS_HEADER_COUNT 4
#define NCA_FULL_HEADER_LENGTH (NCA_HEADER_LENGTH + (NCA_FS_HEADER_LENGTH * 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_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_HIERARCHICAL_SHA256_LAYER_COUNT 2
#define NCA_USED_KEY_AREA_SIZE sizeof(NcaDecryptedKeyArea) /* Four keys, 0x40 bytes. */
#define NCA_IVFC_MAGIC 0x49564643 /* "IVFC" */
#define NCA_IVFC_LAYER_COUNT 7
#define NCA_IVFC_HASH_DATA_LAYER_COUNT 5
#define NCA_IVFC_BLOCK_SIZE(x) (1 << (x))
#define NCA_HIERARCHICAL_SHA256_MAX_REGION_COUNT 5
#define NCA_BKTR_MAGIC 0x424B5452 /* "BKTR" */
#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_FS_ENTRY_BLOCK_SIZE 0x200
#define NCA_FS_ENTRY_BLOCK_OFFSET(x) ((u64)(x) * NCA_FS_ENTRY_BLOCK_SIZE)
#define NCA_BKTR_MAGIC 0x424B5452 /* "BKTR". */
#define NCA_FS_SECTOR_SIZE 0x200
#define NCA_FS_SECTOR_OFFSET(x) ((u64)(x) * NCA_FS_SECTOR_SIZE)
#define NCA_AES_XTS_SECTOR_SIZE 0x200
#define NCA_NCA0_FS_HEADER_AES_XTS_SECTOR(x) (((x) - NCA_HEADER_LENGTH) >> 9)
typedef enum {
NcaDistributionType_Download = 0,
@ -96,19 +95,49 @@ typedef enum {
} NcaKeyGeneration;
typedef struct {
u32 start_block_offset; ///< Expressed in NCA_FS_ENTRY_BLOCK_SIZE blocks.
u32 end_block_offset; ///< Expressed in NCA_FS_ENTRY_BLOCK_SIZE blocks.
u8 enable_entry;
u8 reserved[0x7];
} NcaFsEntry;
u32 start_sector; ///< Expressed in NCA_FS_SECTOR_SIZE sectors.
u32 end_sector; ///< Expressed in NCA_FS_SECTOR_SIZE sectors.
u32 hash_sector;
u8 reserved[0x4];
} NcaFsInfo;
typedef struct {
u8 hash[SHA256_HASH_SIZE];
} NcaFsHash;
} NcaFsHeaderHash;
/// Encrypted NCA key area used to hold NCA FS section encryption keys. Zeroed out if the NCA uses titlekey crypto.
/// Only the first 4 key entries are encrypted.
/// If a particular key entry is unused, it is zeroed out before this area is encrypted.
typedef struct {
u8 key[0x10];
} NcaKey;
u8 aes_xts_1[AES_128_KEY_SIZE]; ///< AES-128-XTS key 0 used for NCA FS sections with NcaEncryptionType_AesXts crypto.
u8 aes_xts_2[AES_128_KEY_SIZE]; ///< AES-128-XTS key 1 used for NCA FS sections with NcaEncryptionType_AesXts crypto.
u8 aes_ctr[AES_128_KEY_SIZE]; ///< AES-128-CTR key used for NCA FS sections with NcaEncryptionType_AesCtr crypto.
u8 aes_ctr_ex[AES_128_KEY_SIZE]; ///< AES-128-CTR key used for NCA FS sections with NcaEncryptionType_AesCtrEx crypto.
u8 aes_ctr_hw[AES_128_KEY_SIZE]; ///< Unused AES-128-CTR key.
u8 reserved[0xB0];
} NcaEncryptedKeyArea;
/// First 0x400 bytes from every NCA.
typedef struct {
u8 main_signature[0x100]; ///< RSA-PSS signature over header with fixed key.
u8 acid_signature[0x100]; ///< RSA-PSS signature over header with key in NPDM.
u32 magic; ///< "NCA0" / "NCA2" / "NCA3".
u8 distribution_type; ///< NcaDistributionType.
u8 content_type; ///< NcaContentType.
u8 key_generation_old; ///< NcaKeyGenerationOld.
u8 kaek_index; ///< NcaKeyAreaEncryptionKeyIndex.
u64 content_size;
u64 program_id;
u32 content_index;
NcaSdkAddOnVersion sdk_addon_version;
u8 key_generation; ///< NcaKeyGeneration.
u8 main_signature_key_generation;
u8 reserved_1[0xE];
FsRightsId rights_id; ///< Used for titlekey crypto.
NcaFsInfo fs_info[NCA_FS_HEADER_COUNT]; ///< Start and end sectors for each NCA FS section.
NcaFsHeaderHash fs_header_hash[NCA_FS_HEADER_COUNT]; ///< SHA-256 hashes calculated over each NCA FS section header.
NcaEncryptedKeyArea encrypted_key_area;
} NcaHeader;
typedef enum {
NcaFsType_RomFs = 0,
@ -133,121 +162,111 @@ typedef enum {
typedef struct {
u64 offset;
u64 size;
} NcaHierarchicalSha256LayerInfo;
} NcaRegion;
/// Used for NcaFsType_PartitionFs and NCA0 NcaFsType_RomFsRomFS.
/// Used by NcaFsType_PartitionFs and NCA0 NcaFsType_RomFs.
typedef struct {
u8 master_hash[SHA256_HASH_SIZE];
u32 hash_block_size;
u32 layer_count;
NcaHierarchicalSha256LayerInfo hash_data_layer_info;
NcaHierarchicalSha256LayerInfo hash_target_layer_info;
} NcaHierarchicalSha256;
u32 hash_region_count;
NcaRegion hash_region[NCA_HIERARCHICAL_SHA256_MAX_REGION_COUNT];
} NcaHierarchicalSha256Data;
typedef struct {
u64 offset;
u64 size;
u32 block_size; ///< Use NCA_IVFC_BLOCK_SIZE to calculate the actual block size using this value.
u32 block_order; ///< Use NCA_IVFC_BLOCK_SIZE to calculate the actual block size using this value.
u8 reserved[0x4];
} NcaHierarchicalIntegrityLayerInfo;
} NcaHierarchicalIntegrityVerificationLevelInformation;
/// Used for NcaFsType_RomFs.
typedef struct {
u8 value[0x20];
} NcaSignatureSalt;
#pragma pack(push, 1)
typedef struct {
u32 max_level_count; ///< Always NCA_IVFC_MAX_LEVEL_COUNT.
NcaHierarchicalIntegrityVerificationLevelInformation level_information[NCA_IVFC_LEVEL_COUNT];
NcaSignatureSalt signature_salt;
} NcaInfoLevelHash;
#pragma pack(pop)
/// Used by NcaFsType_RomFs.
typedef struct {
u32 magic; ///< "IVFC".
u32 version;
u32 master_hash_size;
u32 layer_count;
NcaHierarchicalIntegrityLayerInfo hash_data_layer_info[NCA_IVFC_HASH_DATA_LAYER_COUNT];
NcaHierarchicalIntegrityLayerInfo hash_target_layer_info;
u8 signature_salt[0x20];
u8 master_hash[0x20];
} NcaHierarchicalIntegrity;
u32 master_hash_size; ///< Always SHA256_HASH_SIZE.
NcaInfoLevelHash info_level_hash;
u8 master_hash[SHA256_HASH_SIZE];
} NcaIntegrityMetaInfo;
typedef struct {
union {
struct {
///< Used if hash_type == NcaHashType_HierarchicalSha256 (NcaFsType_PartitionFs and NCA0 NcaFsType_RomFs).
NcaHierarchicalSha256 hierarchical_sha256;
u8 reserved_1[0xB0];
NcaHierarchicalSha256Data hierarchical_sha256_data;
u8 reserved_1[0x80];
};
struct {
///< Used if hash_type == NcaHashType_HierarchicalIntegrity (NcaFsType_RomFs).
NcaHierarchicalIntegrity hierarchical_integrity;
NcaIntegrityMetaInfo integrity_meta_info;
u8 reserved_2[0x18];
};
};
} NcaHashInfo;
} NcaHashData;
typedef struct {
u32 magic; ///< "BKTR".
u32 bucket_count;
u32 version; ///< offset_count / node_count ?
u32 entry_count;
u8 reserved[0x4];
} NcaBucketTreeHeader;
typedef struct {
u64 offset;
u64 size;
NcaBucketTreeHeader header;
} NcaBucketInfo;
/// Only used for NcaEncryptionType_AesCtrEx (PatchRomFs).
typedef struct {
u64 indirect_offset;
u64 indirect_size;
NcaBucketTreeHeader indirect_header;
u64 aes_ctr_ex_offset;
u64 aes_ctr_ex_size;
NcaBucketTreeHeader aes_ctr_ex_header;
NcaBucketInfo indirect_bucket;
NcaBucketInfo aes_ctr_ex_bucket;
} NcaPatchInfo;
/// Format unknown.
typedef struct {
u8 unknown[0x30];
union {
u8 value[0x8];
struct {
u32 generation;
u32 secure_value;
};
};
} NcaAesCtrUpperIv;
/// Used in NCAs with sparse storage.
typedef struct {
NcaBucketInfo sparse_bucket;
u64 physical_offset;
u16 generation;
u8 reserved[0x6];
} NcaSparseInfo;
/// Four NCA FS headers are placed right after the 0x400 byte long NCA header in NCA2 and NCA3.
/// NCA0 place the FS headers at the start sector from the NcaFsInfo entries.
typedef struct {
u16 version;
u8 fs_type; ///< NcaFsType.
u8 hash_type; ///< NcaHashType.
u8 encryption_type; ///< NcaEncryptionType.
u8 reserved_1[0x3];
NcaHashInfo hash_info;
NcaHashData hash_data;
NcaPatchInfo patch_info;
union {
u8 section_ctr[0x8];
struct {
u32 generation;
u32 secure_value;
};
};
NcaAesCtrUpperIv aes_ctr_upper_iv;
NcaSparseInfo sparse_info;
u8 reserved_2[0x88];
} NcaFsHeader;
typedef struct {
u8 main_signature[0x100]; ///< RSA-PSS signature over header with fixed key.
u8 acid_signature[0x100]; ///< RSA-PSS signature over header with key in NPDM.
u32 magic; ///< "NCA0" / "NCA2" / "NCA3".
u8 distribution_type; ///< NcaDistributionType.
u8 content_type; ///< NcaContentType.
u8 key_generation_old; ///< NcaKeyGenerationOld.
u8 kaek_index; ///< NcaKeyAreaEncryptionKeyIndex.
u64 content_size;
u64 program_id;
u32 content_index;
NcaSdkAddOnVersion sdk_addon_version;
u8 key_generation; ///< NcaKeyGeneration.
u8 main_signature_key_generation;
u8 reserved_1[0xE];
FsRightsId rights_id; ///< Used for titlekey crypto.
NcaFsEntry fs_entries[4]; ///< Start and end offsets for each NCA FS section.
NcaFsHash fs_hashes[4]; ///< SHA-256 hashes calculated over each NCA FS section header.
NcaKey encrypted_keys[4]; ///< Only the encrypted key at index #2 is used. The other three are zero filled before the key area is encrypted.
u8 reserved_2[0xC0];
NcaFsHeader fs_headers[4]; /// NCA FS section headers.
} NcaHeader;
typedef enum {
NcaVersion_Nca0 = 0,
NcaVersion_Nca2 = 1,
NcaVersion_Nca3 = 2
} NcaVersion;
typedef enum {
NcaFsSectionType_PartitionFs = 0, ///< NcaFsType_PartitionFs + NcaHashType_HierarchicalSha256.
NcaFsSectionType_RomFs = 1, ///< NcaFsType_RomFs + NcaHashType_HierarchicalIntegrity.
@ -259,25 +278,38 @@ typedef enum {
typedef struct {
bool enabled;
void *nca_ctx; ///< NcaContext. Used to perform NCA reads.
NcaFsHeader header; ///< NCA FS section header.
u8 section_num;
u64 section_offset;
u64 section_size;
u8 section_type; ///< NcaFsSectionType.
u8 encryption_type; ///< NcaEncryptionType.
NcaFsHeader *header;
u8 ctr[0x10]; ///< Used to update the AES CTR context IV based on the desired offset.
u8 ctr[AES_BLOCK_SIZE]; ///< Used to update the AES CTR context IV based on the desired offset.
Aes128CtrContext ctr_ctx;
Aes128XtsContext xts_decrypt_ctx;
Aes128XtsContext xts_encrypt_ctx;
} NcaFsSectionContext;
typedef enum {
NcaVersion_Nca0 = 0,
NcaVersion_Nca2 = 2,
NcaVersion_Nca3 = 3
} NcaVersion;
typedef struct {
u8 aes_xts_1[AES_128_KEY_SIZE]; ///< AES-128-XTS key 0 used for NCA FS sections with NcaEncryptionType_AesXts crypto.
u8 aes_xts_2[AES_128_KEY_SIZE]; ///< AES-128-XTS key 1 used for NCA FS sections with NcaEncryptionType_AesXts crypto.
u8 aes_ctr[AES_128_KEY_SIZE]; ///< AES-128-CTR key used for NCA FS sections with NcaEncryptionType_AesCtr crypto.
u8 aes_ctr_ex[AES_128_KEY_SIZE]; ///< AES-128-CTR key used for NCA FS sections with NcaEncryptionType_AesCtrEx crypto.
} NcaDecryptedKeyArea;
typedef struct {
u8 storage_id; ///< NcmStorageId.
NcmContentStorage *ncm_storage; ///< Pointer to a NcmContentStorage instance. Used to read NCA data.
u64 gamecard_offset; ///< Used to read NCA data from a gamecard using a FsStorage instance when storage_id == NcmStorageId_GameCard.
NcmContentId content_id; ///< Also used to read NCA data.
char content_id_str[0x21];
u8 hash[0x20]; ///< Manually calculated (if needed).
u8 hash[SHA256_HASH_SIZE]; ///< Manually calculated (if needed).
char hash_str[0x41];
u8 format_version; ///< NcaVersion.
u8 content_type; ///< NcmContentType. Retrieved from NcmContentInfo.
@ -286,27 +318,26 @@ typedef struct {
u8 id_offset; ///< Retrieved from NcmContentInfo.
bool rights_id_available;
bool titlekey_retrieved;
u8 titlekey[0x10];
u8 titlekey[AES_128_KEY_SIZE]; ///< Decrypted titlekey from the ticket.
bool dirty_header;
NcaHeader header;
NcaFsSectionContext fs_contexts[4];
NcaKey decrypted_keys[4];
NcaHeader header; ///< NCA header.
NcaFsSectionContext fs_contexts[NCA_FS_HEADER_COUNT];
NcaDecryptedKeyArea decrypted_key_area;
} NcaContext;
typedef struct {
u64 offset; ///< New layer data offset (relative to the start of the NCA content file).
u64 size; ///< New layer data size.
u8 *data; ///< New layer data.
} NcaHashInfoLayerPatch;
u64 offset; ///< New data offset (relative to the start of the NCA content file).
u64 size; ///< New data size.
u8 *data; ///< New data.
} NcaHashDataPatch;
typedef struct {
NcaHashInfoLayerPatch hash_data_layer_patch;
NcaHashInfoLayerPatch hash_target_layer_patch;
u32 hash_region_count;
NcaHashDataPatch hash_region_patch[NCA_HIERARCHICAL_SHA256_MAX_REGION_COUNT];
} NcaHierarchicalSha256Patch;
typedef struct {
NcaHashInfoLayerPatch hash_data_layer_patch[NCA_IVFC_HASH_DATA_LAYER_COUNT];
NcaHashInfoLayerPatch hash_target_layer_patch;
NcaHashDataPatch hash_level_patch[NCA_IVFC_LEVEL_COUNT];
} NcaHierarchicalIntegrityPatch;
/// Functions to control the internal heap buffer used by NCA FS section crypto operations.
@ -318,7 +349,7 @@ void ncaFreeCryptoBuffer(void);
/// If 'storage_id' != NcmStorageId_GameCard, the 'ncm_storage' argument must point to a valid NcmContentStorage instance, previously opened using the same NcmStorageId value.
/// If 'storage_id' == NcmStorageId_GameCard, the 'hfs_partition_type' argument must be a valid GameCardHashFileSystemPartitionType value.
/// If the NCA holds a populated Rights ID field, and if the Ticket element pointed to by 'tik' hasn't been filled, ticket data will be retrieved.
/// If ticket data can't be retrieved, the context will still be initialized, but anything that involves working with plaintext 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, NcmContentStorage *ncm_storage, u8 hfs_partition_type, const NcmContentInfo *content_info, Ticket *tik);
/// Reads raw encrypted data from a NCA using an input context, previously initialized by ncaInitializeContext().
@ -341,24 +372,28 @@ bool ncaReadAesCtrExStorageFromBktrSection(NcaFsSectionContext *ctx, void *out,
/// This function isn't compatible with Patch RomFS sections.
void *ncaGenerateEncryptedFsSectionBlock(NcaFsSectionContext *ctx, const void *data, u64 data_size, u64 data_offset, u64 *out_block_size, u64 *out_block_offset);
/// Generates HierarchicalSha256 FS section patch data, which can be used to replace NCA data in content dumping operations.
/// Input offset must be relative to the start of the HierarchicalSha256 hash target layer (actual underlying FS).
/// Bear in mind that this function recalculates both the NcaHashInfo block master hash and the NCA FS header hash from the NCA header, and enables the 'dirty_header' flag from the NCA context.
/// Generates HierarchicalSha256 FS section patch data, which can be used to seamlessly replace NCA data.
/// Input offset must be relative to the start of the last HierarchicalSha256 hash region (actual underlying FS).
/// Bear in mind that this function recalculates both the NcaHashData block master hash and the NCA FS header hash from the NCA header, and enables the 'dirty_header' flag from the NCA context.
/// As such, this function is not designed to generate more than one patch per HierarchicalSha256 FS section.
bool ncaGenerateHierarchicalSha256Patch(NcaFsSectionContext *ctx, const void *data, u64 data_size, u64 data_offset, NcaHierarchicalSha256Patch *out);
/// Cleanups a previously generated NcaHierarchicalSha256Patch.
NX_INLINE void ncaFreeHierarchicalSha256Patch(NcaHierarchicalSha256Patch *patch)
{
if (!patch) return;
if (patch->hash_data_layer_patch.data) free(patch->hash_data_layer_patch.data);
if (patch->hash_target_layer_patch.data) free(patch->hash_target_layer_patch.data);
if (!patch || !patch->hash_region_count || patch->hash_region_count > NCA_HIERARCHICAL_SHA256_MAX_REGION_COUNT) return;
for(u8 i = 0; i < patch->hash_region_count; i++)
{
if (patch->hash_region_patch[i].data) free(patch->hash_region_patch[i].data);
}
memset(patch, 0, sizeof(NcaHierarchicalSha256Patch));
}
/// Generates HierarchicalIntegrity FS section patch data, which can be used to replace NCA data in content dumping operations.
/// Input offset must be relative to the start of the HierarchicalIntegrity hash target layer (actual underlying FS).
/// Bear in mind that this function recalculates both the NcaHashInfo block master hash and the NCA FS header hash from the NCA header, and enables the 'dirty_header' flag from the NCA context.
/// Generates HierarchicalIntegrity FS section patch data, which can be used to seamlessly replace NCA data.
/// Input offset must be relative to the start of the last HierarchicalIntegrity hash level (actual underlying FS).
/// Bear in mind that this function recalculates both the NcaHashData block master hash and the NCA FS header hash from the NCA header, and enables the 'dirty_header' flag from the NCA context.
/// As such, this function is not designed to generate more than one patch per HierarchicalIntegrity FS section.
bool ncaGenerateHierarchicalIntegrityPatch(NcaFsSectionContext *ctx, const void *data, u64 data_size, u64 data_offset, NcaHierarchicalIntegrityPatch *out);
@ -367,15 +402,22 @@ NX_INLINE void ncaFreeHierarchicalIntegrityPatch(NcaHierarchicalIntegrityPatch *
{
if (!patch) return;
for(u8 i = 0; i < (NCA_IVFC_HASH_DATA_LAYER_COUNT + 1); i++)
for(u8 i = 0; i < NCA_IVFC_LEVEL_COUNT; i++)
{
NcaHashInfoLayerPatch *layer_patch = (i < NCA_IVFC_HASH_DATA_LAYER_COUNT ? &(patch->hash_data_layer_patch[i]) : &(patch->hash_target_layer_patch));
if (layer_patch->data) free(layer_patch->data);
if (patch->hash_level_patch[i].data) free(patch->hash_level_patch[i].data);
}
memset(patch, 0, sizeof(NcaHierarchicalIntegrityPatch));
}
/// Removes titlekey crypto dependency from a NCA context by wiping the Rights ID from the underlying NCA header copy and copying the decrypted titlekey to the NCA key area.
void ncaRemoveTitlekeyCrypto(NcaContext *ctx);
@ -384,8 +426,6 @@ NX_INLINE void ncaFreeHierarchicalIntegrityPatch(NcaHierarchicalIntegrityPatch *
bool ncaEncryptKeyArea(NcaContext *nca_ctx);
bool ncaEncryptHeader(NcaContext *ctx);
@ -412,35 +452,30 @@ NX_INLINE void ncaSetDownloadDistributionType(NcaContext *ctx)
ctx->dirty_header = true;
}
NX_INLINE void ncaWipeRightsId(NcaContext *ctx)
NX_INLINE bool ncaValidateHierarchicalSha256Offsets(NcaHierarchicalSha256Data *hierarchical_sha256_data, u64 section_size)
{
if (!ctx || !ctx->rights_id_available) return;
memset(&(ctx->header.rights_id), 0, sizeof(FsRightsId));
ctx->dirty_header = true;
}
if (!hierarchical_sha256_data || !section_size || !hierarchical_sha256_data->hash_block_size || !hierarchical_sha256_data->hash_region_count || \
hierarchical_sha256_data->hash_region_count > NCA_HIERARCHICAL_SHA256_MAX_REGION_COUNT) return false;
NX_INLINE bool ncaValidateHierarchicalSha256Offsets(NcaHierarchicalSha256 *hierarchical_sha256, u64 section_size)
{
if (!hierarchical_sha256 || !section_size || !hierarchical_sha256->hash_block_size || hierarchical_sha256->layer_count != NCA_HIERARCHICAL_SHA256_LAYER_COUNT) return false;
for(u8 i = 0; i < NCA_HIERARCHICAL_SHA256_LAYER_COUNT; i++)
for(u8 i = 0; i < hierarchical_sha256_data->hash_region_count; i++)
{
NcaHierarchicalSha256LayerInfo *layer_info = (i == 0 ? &(hierarchical_sha256->hash_data_layer_info) : &(hierarchical_sha256->hash_target_layer_info));
if (layer_info->offset >= section_size || !layer_info->size || (layer_info->offset + layer_info->size) > section_size) return false;
if (hierarchical_sha256_data->hash_region[i].offset >= section_size || !hierarchical_sha256_data->hash_region[i].size || \
(hierarchical_sha256_data->hash_region[i].offset + hierarchical_sha256_data->hash_region[i].size) > section_size) return false;
}
return true;
}
NX_INLINE bool ncaValidateHierarchicalIntegrityOffsets(NcaHierarchicalIntegrity *hierarchical_integrity, u64 section_size)
NX_INLINE bool ncaValidateHierarchicalIntegrityOffsets(NcaIntegrityMetaInfo *integrity_meta_info, u64 section_size)
{
if (!hierarchical_integrity || !section_size || __builtin_bswap32(hierarchical_integrity->magic) != NCA_IVFC_MAGIC || !hierarchical_integrity->master_hash_size || \
hierarchical_integrity->layer_count != NCA_IVFC_LAYER_COUNT) return false;
if (!integrity_meta_info || !section_size || __builtin_bswap32(integrity_meta_info->magic) != NCA_IVFC_MAGIC || integrity_meta_info->master_hash_size != SHA256_HASH_SIZE || \
integrity_meta_info->info_level_hash.max_level_count != NCA_IVFC_MAX_LEVEL_COUNT) return false;
for(u8 i = 0; i < (NCA_IVFC_HASH_DATA_LAYER_COUNT + 1); i++)
for(u8 i = 0; i < NCA_IVFC_LEVEL_COUNT; i++)
{
NcaHierarchicalIntegrityLayerInfo *layer_info = (i < NCA_IVFC_HASH_DATA_LAYER_COUNT ? &(hierarchical_integrity->hash_data_layer_info[i]) : &(hierarchical_integrity->hash_target_layer_info));
if (layer_info->offset >= section_size || !layer_info->size || !layer_info->block_size || (layer_info->offset + layer_info->size) > section_size) return false;
if (integrity_meta_info->info_level_hash.level_information[i].offset >= section_size || !integrity_meta_info->info_level_hash.level_information[i].size || \
!integrity_meta_info->info_level_hash.level_information[i].block_order || \
(integrity_meta_info->info_level_hash.level_information[i].offset + integrity_meta_info->info_level_hash.level_information[i].size) > section_size) return false;
}
return true;

View file

@ -23,31 +23,38 @@
bool pfsInitializeContext(PartitionFileSystemContext *out, NcaFsSectionContext *nca_fs_ctx)
{
if (!out || !nca_fs_ctx || !nca_fs_ctx->enabled || nca_fs_ctx->section_type != NcaFsSectionType_PartitionFs || !nca_fs_ctx->header || nca_fs_ctx->header->fs_type != NcaFsType_PartitionFs || \
nca_fs_ctx->header->hash_type != NcaHashType_HierarchicalSha256)
if (!out || !nca_fs_ctx || !nca_fs_ctx->enabled || nca_fs_ctx->section_type != NcaFsSectionType_PartitionFs || nca_fs_ctx->header.fs_type != NcaFsType_PartitionFs || \
nca_fs_ctx->header.hash_type != NcaHashType_HierarchicalSha256)
{
LOGFILE("Invalid parameters!");
return false;
}
u32 magic = 0;
PartitionFileSystemHeader pfs_header = {0};
PartitionFileSystemEntry *main_npdm_entry = NULL;
u32 hash_region_count = 0;
NcaRegion *hash_region = NULL;
/* Clear output partition FS context. */
memset(out, 0, sizeof(PartitionFileSystemContext));
/* Fill context. */
out->nca_fs_ctx = nca_fs_ctx;
if (!ncaValidateHierarchicalSha256Offsets(&(nca_fs_ctx->header->hash_info.hierarchical_sha256), nca_fs_ctx->section_size))
if (!ncaValidateHierarchicalSha256Offsets(&(nca_fs_ctx->header.hash_data.hierarchical_sha256_data), nca_fs_ctx->section_size))
{
LOGFILE("Invalid HierarchicalSha256 block!");
return false;
}
out->offset = nca_fs_ctx->header->hash_info.hierarchical_sha256.hash_target_layer_info.offset;
out->size = nca_fs_ctx->header->hash_info.hierarchical_sha256.hash_target_layer_info.size;
hash_region_count = nca_fs_ctx->header.hash_data.hierarchical_sha256_data.hash_region_count;
hash_region = &(nca_fs_ctx->header.hash_data.hierarchical_sha256_data.hash_region[hash_region_count - 1]);
out->offset = hash_region->offset;
out->size = hash_region->size;
/* Read partial PFS header. */
if (!ncaReadFsSection(nca_fs_ctx, &pfs_header, sizeof(PartitionFileSystemHeader), out->offset))

View file

@ -31,14 +31,18 @@ bool romfsInitializeContext(RomFileSystemContext *out, NcaFsSectionContext *nca_
NcaContext *nca_ctx = NULL;
u64 dir_table_offset = 0, file_table_offset = 0;
if (!out || !nca_fs_ctx || !nca_fs_ctx->enabled || !nca_fs_ctx->header || !(nca_ctx = (NcaContext*)nca_fs_ctx->nca_ctx) || (nca_ctx->format_version == NcaVersion_Nca0 && \
(nca_fs_ctx->section_type != NcaFsSectionType_Nca0RomFs || nca_fs_ctx->header->hash_type != NcaHashType_HierarchicalSha256)) || (nca_ctx->format_version != NcaVersion_Nca0 && \
(nca_fs_ctx->section_type != NcaFsSectionType_RomFs || nca_fs_ctx->header->hash_type != NcaHashType_HierarchicalIntegrity)))
if (!out || !nca_fs_ctx || !nca_fs_ctx->enabled || !(nca_ctx = (NcaContext*)nca_fs_ctx->nca_ctx) || (nca_ctx->format_version == NcaVersion_Nca0 && \
(nca_fs_ctx->section_type != NcaFsSectionType_Nca0RomFs || nca_fs_ctx->header.hash_type != NcaHashType_HierarchicalSha256)) || (nca_ctx->format_version != NcaVersion_Nca0 && \
(nca_fs_ctx->section_type != NcaFsSectionType_RomFs || nca_fs_ctx->header.hash_type != NcaHashType_HierarchicalIntegrity)))
{
LOGFILE("Invalid parameters!");
return false;
}
u32 layer_count = 0;
NcaRegion *hash_region = NULL;
NcaHierarchicalIntegrityVerificationLevelInformation *level_information = NULL;
/* Clear output RomFS context. */
memset(out, 0, sizeof(RomFileSystemContext));
@ -47,23 +51,29 @@ bool romfsInitializeContext(RomFileSystemContext *out, NcaFsSectionContext *nca_
if (nca_fs_ctx->section_type == NcaFsSectionType_Nca0RomFs)
{
if (!ncaValidateHierarchicalSha256Offsets(&(nca_fs_ctx->header->hash_info.hierarchical_sha256), nca_fs_ctx->section_size))
if (!ncaValidateHierarchicalSha256Offsets(&(nca_fs_ctx->header.hash_data.hierarchical_sha256_data), nca_fs_ctx->section_size))
{
LOGFILE("Invalid HierarchicalSha256 block!");
return false;
}
out->offset = nca_fs_ctx->header->hash_info.hierarchical_sha256.hash_target_layer_info.offset;
out->size = nca_fs_ctx->header->hash_info.hierarchical_sha256.hash_target_layer_info.size;
layer_count = nca_fs_ctx->header.hash_data.hierarchical_sha256_data.hash_region_count;
hash_region = &(nca_fs_ctx->header.hash_data.hierarchical_sha256_data.hash_region[layer_count - 1]);
out->offset = hash_region->offset;
out->size = hash_region->size;
} else {
if (!ncaValidateHierarchicalIntegrityOffsets(&(nca_fs_ctx->header->hash_info.hierarchical_integrity), nca_fs_ctx->section_size))
if (!ncaValidateHierarchicalIntegrityOffsets(&(nca_fs_ctx->header.hash_data.integrity_meta_info), nca_fs_ctx->section_size))
{
LOGFILE("Invalid HierarchicalIntegrity block!");
return false;
}
out->offset = nca_fs_ctx->header->hash_info.hierarchical_integrity.hash_target_layer_info.offset;
out->size = nca_fs_ctx->header->hash_info.hierarchical_integrity.hash_target_layer_info.size;
layer_count = NCA_IVFC_LEVEL_COUNT;
level_information = &(nca_fs_ctx->header.hash_data.integrity_meta_info.info_level_hash.level_information[layer_count - 1]);
out->offset = level_information->offset;
out->size = level_information->size;
}
/* Read RomFS header. */

View file

@ -156,7 +156,7 @@ bool romfsGeneratePathFromDirectoryEntry(RomFileSystemContext *ctx, RomFileSyste
/// Generates a path string from a RomFS file entry.
bool romfsGeneratePathFromFileEntry(RomFileSystemContext *ctx, RomFileSystemFileEntry *file_entry, char *out_path, size_t out_path_size, u8 illegal_char_replace_type);
/// Generates HierarchicalSha256 (NCA0) / HierarchicalIntegrity (NCA2/NCA3) FS section patch data using a RomFS context + file entry, which can be used to replace NCA data in content dumping operations.
/// Generates HierarchicalSha256 (NCA0) / HierarchicalIntegrity (NCA2/NCA3) FS section patch data using a RomFS context + file entry, which can be used to seamlessly replace NCA data.
/// Input offset must be relative to the start of the RomFS file entry data.
/// This function shares the same limitations as ncaGenerateHierarchicalSha256Patch() / ncaGenerateHierarchicalIntegrityPatch().
bool romfsGenerateFileEntryPatch(RomFileSystemContext *ctx, RomFileSystemFileEntry *file_entry, const void *data, u64 data_size, u64 data_offset, RomFileSystemFileEntryPatch *out);

View file

@ -29,7 +29,7 @@
#include "usb.h"
#include "fatfs/ff.h"
#define LOGFILE_PATH "./nxdumptool.log"
#define LOGFILE_PATH "./" APP_TITLE ".log"
/* Global variables. */

View file

@ -37,7 +37,7 @@
#include <stdatomic.h>
#include <switch.h>
#define APP_BASE_PATH "sdmc:/switch/nxdumptool/"
#define APP_BASE_PATH "sdmc:/switch/" APP_TITLE "/"
#define MEMBER_SIZE(type, member) sizeof(((type*)NULL)->member)

View file

@ -7,6 +7,7 @@ todo:
tik: use dumped tickets when the original ones can't be found in the ES savefile?
nca: function to write encrypted nca headers / nca fs headers (don't forget nca0 please)
nca: function to write hashdata patches
pfs0: filelist generation methods
pfs0: full header aligned to 0x20 (nsp)
@ -31,17 +32,3 @@ todo:
fclose(content_info);
}
Result txIsFat32(bool *mode) {
Result rc = serviceDispatch(&g_tx, 137);
if (rc == 0xa08) {
*mode = false;
return 0;
} else if (rc == 0) {
*mode = true;
}
return rc;
}