diff --git a/source/bktr.c b/source/bktr.c index 533c1ad..f20a8a2 100644 --- a/source/bktr.c +++ b/source/bktr.c @@ -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)) diff --git a/source/fs_ext.h b/source/fs_ext.h index 595dcd7..6128e2f 100644 --- a/source/fs_ext.h +++ b/source/fs_ext.h @@ -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. diff --git a/source/gamecard.c b/source/gamecard.c index dd0f29d..55b03b1 100644 --- a/source/gamecard.c +++ b/source/gamecard.c @@ -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; } - - 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) diff --git a/source/gamecard.h b/source/gamecard.h index d91c6e6..eeb095f 100644 --- a/source/gamecard.h +++ b/source/gamecard.h @@ -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; diff --git a/source/main.c b/source/main.c index aecd933..3a9e833 100644 --- a/source/main.c +++ b/source/main.c @@ -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}; diff --git a/source/nca.c b/source/nca.c index 67c7319..e364cb7 100644 --- a/source/nca.c +++ b/source/nca.c @@ -39,9 +39,14 @@ static const u8 g_nca0KeyAreaHash[SHA256_HASH_SIZE] = { /* Function prototypes. */ +NX_INLINE bool ncaIsFsInfoEntryValid(NcaFsInfo *fs_info); + static bool ncaDecryptHeader(NcaContext *ctx); static bool ncaDecryptKeyArea(NcaContext *ctx); +static bool ncaEncryptHeader(NcaContext *ctx); +static bool ncaEncryptKeyArea(NcaContext *ctx); + NX_INLINE bool ncaIsVersion0KeyAreaEncrypted(NcaContext *ctx); NX_INLINE u8 ncaGetKeyGenerationValue(NcaContext *ctx); NX_INLINE bool ncaCheckRightsIdAvailability(NcaContext *ctx); @@ -53,6 +58,7 @@ NX_INLINE void ncaUpdateAesCtrExIv(u8 *ctr, u32 ctr_val, u64 offset); static bool _ncaReadFsSection(NcaFsSectionContext *ctx, void *out, u64 read_size, u64 offset, bool lock); static bool _ncaReadAesCtrExStorageFromBktrSection(NcaFsSectionContext *ctx, void *out, u64 read_size, u64 offset, u32 ctr_val, bool lock); +static bool ncaGenerateHashDataPatch(NcaFsSectionContext *ctx, const void *data, u64 data_size, u64 data_offset, void *out, bool is_integrity_patch); static void *_ncaGenerateEncryptedFsSectionBlock(NcaFsSectionContext *ctx, const void *data, u64 data_size, u64 data_offset, u64 *out_block_size, u64 *out_block_offset, bool lock); bool ncaAllocateCryptoBuffer(void) @@ -75,118 +81,6 @@ void ncaFreeCryptoBuffer(void) mutexUnlock(&g_ncaCryptoBufferMutex); } -bool ncaEncryptKeyArea(NcaContext *ctx) -{ - if (!ctx) - { - LOGFILE("Invalid NCA context!"); - return false; - } - - u8 key_count; - const u8 *kaek = NULL; - Aes128Context key_area_ctx = {0}; - - /* Check if we're dealing with a NCA0 with a plain text key area. */ - if (ncaIsVersion0KeyAreaEncrypted(ctx)) - { - memcpy(ctx->header.encrypted_keys, ctx->decrypted_keys, 0x40); - return true; - } - - kaek = keysGetKeyAreaEncryptionKey(ctx->key_generation, ctx->header.kaek_index); - if (!kaek) - { - LOGFILE("Unable to retrieve KAEK for key generation 0x%02X and KAEK index 0x%02X!", ctx->key_generation, ctx->header.kaek_index); - return false; - } - - key_count = (ctx->format_version == NcaVersion_Nca0 ? 2 : 4); - - aes128ContextCreate(&key_area_ctx, kaek, true); - for(u8 i = 0; i < key_count; i++) aes128EncryptBlock(&key_area_ctx, ctx->header.encrypted_keys[i].key, ctx->decrypted_keys[i].key); - - return true; -} - -bool ncaEncryptHeader(NcaContext *ctx) -{ - if (!ctx || !strlen(ctx->content_id_str)) - { - LOGFILE("Invalid NCA context!"); - return false; - } - - u32 i; - size_t crypt_res = 0; - u64 fs_header_offset = 0; - const u8 *header_key = NULL; - Aes128XtsContext hdr_aes_ctx = {0}, nca0_fs_header_ctx = {0}; - - header_key = keysGetNcaHeaderKey(); - - aes128XtsContextCreate(&hdr_aes_ctx, header_key, header_key + 0x10, true); - - crypt_res = aes128XtsNintendoCrypt(&hdr_aes_ctx, &(ctx->header), &(ctx->header), NCA_HEADER_LENGTH, 0, NCA_AES_XTS_SECTOR_SIZE, true); - if (crypt_res != NCA_HEADER_LENGTH) - { - LOGFILE("Error encrypting partial NCA \"%s\" header!", ctx->content_id_str); - return false; - } - - switch(ctx->format_version) - { - case NcaVersion_Nca3: - crypt_res = aes128XtsNintendoCrypt(&hdr_aes_ctx, ctx->header.fs_headers, ctx->header.fs_headers, NCA_FULL_HEADER_LENGTH - NCA_HEADER_LENGTH, 2, NCA_AES_XTS_SECTOR_SIZE, true); - if (crypt_res != (NCA_FULL_HEADER_LENGTH - NCA_HEADER_LENGTH)) - { - LOGFILE("Error encrypting NCA3 \"%s\" FS section headers!", ctx->content_id_str); - return false; - } - - break; - case NcaVersion_Nca2: - for(i = 0; i < NCA_FS_HEADER_COUNT; i++) - { - if (!ctx->header.fs_entries[i].enable_entry) continue; - - crypt_res = aes128XtsNintendoCrypt(&hdr_aes_ctx, &(ctx->header.fs_headers[i]), &(ctx->header.fs_headers[i]), NCA_FS_HEADER_LENGTH, 0, NCA_AES_XTS_SECTOR_SIZE, true); - if (crypt_res != NCA_FS_HEADER_LENGTH) - { - LOGFILE("Error encrypting NCA2 \"%s\" FS section header #%u!", ctx->content_id_str, i); - return false; - } - } - - break; - case NcaVersion_Nca0: - /* NCA0 FS section headers will be encrypted in-place, but they need to be written to their proper offsets. */ - aes128XtsContextCreate(&nca0_fs_header_ctx, ctx->decrypted_keys[0].key, ctx->decrypted_keys[1].key, true); - - for(i = 0; i < NCA_FS_HEADER_COUNT; i++) - { - if (!ctx->header.fs_entries[i].enable_entry) continue; - - fs_header_offset = NCA_FS_ENTRY_BLOCK_OFFSET(ctx->header.fs_entries[i].start_block_offset); - - crypt_res = aes128XtsNintendoCrypt(&nca0_fs_header_ctx, &(ctx->header.fs_headers[i]), &(ctx->header.fs_headers[i]), NCA_FS_HEADER_LENGTH, \ - NCA_NCA0_FS_HEADER_AES_XTS_SECTOR(fs_header_offset), NCA_AES_XTS_SECTOR_SIZE, true); - if (crypt_res != NCA_FS_HEADER_LENGTH) - { - LOGFILE("Error decrypting NCA0 \"%s\" FS section header #%u!", ctx->content_id_str, i); - return false; - } - } - - break; - default: - LOGFILE("Invalid NCA \"%s\" format version! (0x%02X)", ctx->content_id_str, ctx->format_version); - return false; - } - - return true; -} - bool ncaInitializeContext(NcaContext *out, u8 storage_id, NcmContentStorage *ncm_storage, u8 hfs_partition_type, const NcmContentInfo *content_info, Ticket *tik) { if (!out || !tik || (storage_id != NcmStorageId_GameCard && !ncm_storage) || (storage_id == NcmStorageId_GameCard && hfs_partition_type > GameCardHashFileSystemPartitionType_Secure) || \ @@ -243,16 +137,6 @@ bool ncaInitializeContext(NcaContext *out, u8 storage_id, NcmContentStorage *ncm return false; } - if (out->header.content_size != out->content_size) - { - LOGFILE("Content size mismatch for NCA \"%s\"! (0x%lX != 0x%lX)", out->content_id_str, out->header.content_size, out->content_size); - return false; - } - - /* Fill additional NCA context info. */ - out->key_generation = ncaGetKeyGenerationValue(out); - out->rights_id_available = ncaCheckRightsIdAvailability(out); - if (out->rights_id_available) { /* Retrieve ticket. */ @@ -265,34 +149,23 @@ bool ncaInitializeContext(NcaContext *out, u8 storage_id, NcmContentStorage *ncm } else { LOGFILE("Error retrieving ticket for NCA \"%s\"!", out->content_id_str); } - } else { - /* Decrypt key area. */ - if (out->format_version != NcaVersion_Nca0 && !ncaDecryptKeyArea(out)) - { - LOGFILE("Error decrypting NCA key area!"); - return false; - } } - /* Return right away if the NCA uses titlekey crypto and the titlekey couldn't be retrieved. */ - if (out->rights_id_available && !out->titlekey_retrieved) return true; - /* Parse sections. */ for(u8 i = 0; i < NCA_FS_HEADER_COUNT; i++) { - /* Skip NCA section if it's not enabled in the FS entries. */ - if (!out->header.fs_entries[i].enable_entry) continue; + /* Don't proceed if this NCA FS section isn't populated. */ + if (!ncaIsFsInfoEntryValid(&(out->header.fs_info[i]))) continue; /* Fill section context. */ out->fs_contexts[i].nca_ctx = out; out->fs_contexts[i].section_num = i; - out->fs_contexts[i].section_offset = NCA_FS_ENTRY_BLOCK_OFFSET(out->header.fs_entries[i].start_block_offset); - out->fs_contexts[i].section_size = (NCA_FS_ENTRY_BLOCK_OFFSET(out->header.fs_entries[i].end_block_offset) - out->fs_contexts[i].section_offset); + out->fs_contexts[i].section_offset = NCA_FS_SECTOR_OFFSET(out->header.fs_info[i].start_sector); + out->fs_contexts[i].section_size = (NCA_FS_SECTOR_OFFSET(out->header.fs_info[i].end_sector) - out->fs_contexts[i].section_offset); out->fs_contexts[i].section_type = NcaFsSectionType_Invalid; /* Placeholder. */ - out->fs_contexts[i].header = &(out->header.fs_headers[i]); /* Determine encryption type. */ - out->fs_contexts[i].encryption_type = (out->format_version == NcaVersion_Nca0 ? NcaEncryptionType_AesXts : out->header.fs_headers[i].encryption_type); + out->fs_contexts[i].encryption_type = (out->format_version == NcaVersion_Nca0 ? NcaEncryptionType_AesXts : out->fs_contexts[i].header.encryption_type); if (out->fs_contexts[i].encryption_type == NcaEncryptionType_Auto) { switch(out->fs_contexts[i].section_num) @@ -317,15 +190,15 @@ bool ncaInitializeContext(NcaContext *out, u8 storage_id, NcmContentStorage *ncm } /* Determine FS section type. */ - if (out->fs_contexts[i].header->fs_type == NcaFsType_PartitionFs && out->fs_contexts[i].header->hash_type == NcaHashType_HierarchicalSha256) + if (out->fs_contexts[i].header.fs_type == NcaFsType_PartitionFs && out->fs_contexts[i].header.hash_type == NcaHashType_HierarchicalSha256) { out->fs_contexts[i].section_type = NcaFsSectionType_PartitionFs; } else - if (out->fs_contexts[i].header->fs_type == NcaFsType_RomFs && out->fs_contexts[i].header->hash_type == NcaHashType_HierarchicalIntegrity) + if (out->fs_contexts[i].header.fs_type == NcaFsType_RomFs && out->fs_contexts[i].header.hash_type == NcaHashType_HierarchicalIntegrity) { out->fs_contexts[i].section_type = (out->fs_contexts[i].encryption_type == NcaEncryptionType_AesCtrEx ? NcaFsSectionType_PatchRomFs : NcaFsSectionType_RomFs); } else - if (out->fs_contexts[i].header->fs_type == NcaFsType_RomFs && out->fs_contexts[i].header->hash_type == NcaHashType_HierarchicalSha256 && out->format_version == NcaVersion_Nca0) + if (out->fs_contexts[i].header.fs_type == NcaFsType_RomFs && out->fs_contexts[i].header.hash_type == NcaHashType_HierarchicalSha256 && out->format_version == NcaVersion_Nca0) { out->fs_contexts[i].section_type = NcaFsSectionType_Nca0RomFs; } @@ -337,26 +210,32 @@ bool ncaInitializeContext(NcaContext *out, u8 storage_id, NcmContentStorage *ncm continue; } - /* Initialize crypto related fields. */ - if (out->fs_contexts[i].encryption_type > NcaEncryptionType_None && out->fs_contexts[i].encryption_type <= NcaEncryptionType_AesCtrEx) + /* Initialize crypto data. */ + if ((!out->rights_id_available || (out->rights_id_available && out->titlekey_retrieved)) && out->fs_contexts[i].encryption_type > NcaEncryptionType_None && \ + out->fs_contexts[i].encryption_type <= NcaEncryptionType_AesCtrEx) { /* Initialize section CTR. */ - ncaInitializeAesCtrIv(out->fs_contexts[i].ctr, out->fs_contexts[i].header->section_ctr, out->fs_contexts[i].section_offset); + ncaInitializeAesCtrIv(out->fs_contexts[i].ctr, out->fs_contexts[i].header.aes_ctr_upper_iv.value, out->fs_contexts[i].section_offset); /* Initialize AES context. */ if (out->rights_id_available) { + /* AES-128-CTR is always used for FS crypto in NCAs with a rights ID. */ aes128CtrContextCreate(&(out->fs_contexts[i].ctr_ctx), out->titlekey, out->fs_contexts[i].ctr); } else { - if (out->fs_contexts[i].encryption_type == NcaEncryptionType_AesCtr || out->fs_contexts[i].encryption_type == NcaEncryptionType_AesCtrEx) - { - aes128CtrContextCreate(&(out->fs_contexts[i].ctr_ctx), out->decrypted_keys[2].key, out->fs_contexts[i].ctr); - } else if (out->fs_contexts[i].encryption_type == NcaEncryptionType_AesXts) { /* We need to create two different contexts: one for decryption and another one for encryption. */ - aes128XtsContextCreate(&(out->fs_contexts[i].xts_decrypt_ctx), out->decrypted_keys[0].key, out->decrypted_keys[1].key, false); - aes128XtsContextCreate(&(out->fs_contexts[i].xts_encrypt_ctx), out->decrypted_keys[0].key, out->decrypted_keys[1].key, true); + aes128XtsContextCreate(&(out->fs_contexts[i].xts_decrypt_ctx), out->decrypted_key_area.aes_xts_1, out->decrypted_key_area.aes_xts_2, false); + aes128XtsContextCreate(&(out->fs_contexts[i].xts_encrypt_ctx), out->decrypted_key_area.aes_xts_1, out->decrypted_key_area.aes_xts_2, true); + } else + if (out->fs_contexts[i].encryption_type == NcaEncryptionType_AesCtr) + { + aes128CtrContextCreate(&(out->fs_contexts[i].ctr_ctx), out->decrypted_key_area.aes_ctr, out->fs_contexts[i].ctr); + } else + if (out->fs_contexts[i].encryption_type == NcaEncryptionType_AesCtrEx) + { + aes128CtrContextCreate(&(out->fs_contexts[i].ctr_ctx), out->decrypted_key_area.aes_ctr_ex, out->fs_contexts[i].ctr); } } } @@ -414,288 +293,41 @@ void *ncaGenerateEncryptedFsSectionBlock(NcaFsSectionContext *ctx, const void *d bool ncaGenerateHierarchicalSha256Patch(NcaFsSectionContext *ctx, const void *data, u64 data_size, u64 data_offset, NcaHierarchicalSha256Patch *out) { - mutexLock(&g_ncaCryptoBufferMutex); - - NcaContext *nca_ctx = NULL; - - u64 hash_block_size = 0; - u64 hash_data_layer_offset = 0, hash_data_layer_size = 0; - u64 hash_target_layer_offset = 0, hash_target_layer_size = 0; - u8 *hash_data_layer = NULL, *hash_target_block = NULL; - - bool success = false; - - if (!ctx || !ctx->enabled || !(nca_ctx = (NcaContext*)ctx->nca_ctx) || !ctx->header || ctx->header->hash_type != NcaHashType_HierarchicalSha256 || \ - ctx->header->encryption_type == NcaEncryptionType_AesCtrEx || !data || !data_size || !(hash_block_size = ctx->header->hash_info.hierarchical_sha256.hash_block_size) || \ - !(hash_data_layer_size = ctx->header->hash_info.hierarchical_sha256.hash_data_layer_info.size) || \ - !(hash_target_layer_size = ctx->header->hash_info.hierarchical_sha256.hash_target_layer_info.size) || data_offset >= hash_target_layer_size || \ - (data_offset + data_size) > hash_target_layer_size || !out) - { - LOGFILE("Invalid parameters!"); - goto end; - } - - /* Calculate required offsets and sizes. */ - hash_data_layer_offset = ctx->header->hash_info.hierarchical_sha256.hash_data_layer_info.offset; - hash_target_layer_offset = ctx->header->hash_info.hierarchical_sha256.hash_target_layer_info.offset; - - u64 hash_data_start_offset = ((data_offset / hash_block_size) * SHA256_HASH_SIZE); - u64 hash_data_end_offset = (((data_offset + data_size) / hash_block_size) * SHA256_HASH_SIZE); - u64 hash_data_size = (hash_data_end_offset != hash_data_start_offset ? (hash_data_end_offset - hash_data_start_offset) : SHA256_HASH_SIZE); - - u64 hash_target_start_offset = (hash_target_layer_offset + ALIGN_DOWN(data_offset, hash_block_size)); - u64 hash_target_end_offset = (hash_target_layer_offset + ALIGN_UP(data_offset + data_size, hash_block_size)); - if (hash_target_end_offset > (hash_target_layer_offset + hash_target_layer_size)) hash_target_end_offset = (hash_target_layer_offset + hash_target_layer_size); - u64 hash_target_size = (hash_target_end_offset - hash_target_start_offset); - - u64 hash_target_data_offset = (data_offset - ALIGN_DOWN(data_offset, hash_block_size)); - - /* Allocate memory for the full hash data layer. */ - hash_data_layer = malloc(hash_data_layer_size); - if (!hash_data_layer) - { - LOGFILE("Unable to allocate 0x%lX bytes buffer for the full HierarchicalSha256 hash data layer!", hash_data_layer_size); - goto end; - } - - /* Read full hash data layer. */ - if (!_ncaReadFsSection(ctx, hash_data_layer, hash_data_layer_size, hash_data_layer_offset, false)) - { - LOGFILE("Failed to read full HierarchicalSha256 hash data layer!"); - goto end; - } - - /* Allocate memory for the modified hash target layer block. */ - hash_target_block = malloc(hash_target_size); - if (!hash_target_block) - { - LOGFILE("Unable to allocate 0x%lX bytes buffer for the modified HierarchicalSha256 hash target layer block!", hash_target_size); - goto end; - } - - /* Read hash target layer block. */ - if (!_ncaReadFsSection(ctx, hash_target_block, hash_target_size, hash_target_start_offset, false)) - { - LOGFILE("Failed to read HierarchicalSha256 hash target layer block!"); - goto end; - } - - /* Replace data. */ - memcpy(hash_target_block + hash_target_data_offset, data, data_size); - - /* Recalculate hashes. */ - for(u64 i = 0, j = 0; i < hash_target_size; i += hash_block_size, j++) - { - if (hash_block_size > (hash_target_size - i)) hash_block_size = (hash_target_size - i); - sha256CalculateHash(hash_data_layer + hash_data_start_offset + (j * SHA256_HASH_SIZE), hash_target_block + i, hash_block_size); - } - - /* Reencrypt modified hash data layer block. */ - out->hash_data_layer_patch.data = _ncaGenerateEncryptedFsSectionBlock(ctx, hash_data_layer + hash_data_start_offset, hash_data_size, hash_data_layer_offset + hash_data_start_offset, \ - &(out->hash_data_layer_patch.size), &(out->hash_data_layer_patch.offset), false); - if (!out->hash_data_layer_patch.data) - { - LOGFILE("Failed to generate encrypted HierarchicalSha256 hash data layer block!"); - goto end; - } - - /* Reencrypt hash target layer block. */ - out->hash_target_layer_patch.data = _ncaGenerateEncryptedFsSectionBlock(ctx, hash_target_block + hash_target_data_offset, data_size, hash_target_layer_offset + data_offset, \ - &(out->hash_target_layer_patch.size), &(out->hash_target_layer_patch.offset), false); - if (!out->hash_target_layer_patch.data) - { - LOGFILE("Failed to generate encrypted HierarchicalSha256 hash target layer block!"); - goto end; - } - - /* Recalculate master hash from hash info block. */ - sha256CalculateHash(ctx->header->hash_info.hierarchical_sha256.master_hash, hash_data_layer, hash_data_layer_size); - - /* Recalculate FS header hash. */ - sha256CalculateHash(nca_ctx->header.fs_hashes[ctx->section_num].hash, ctx->header, sizeof(NcaFsHeader)); - - /* Enable the 'dirty_header' flag. */ - nca_ctx->dirty_header = true; - - success = true; - -end: - if (hash_target_block) free(hash_target_block); - - if (hash_data_layer) free(hash_data_layer); - - if (!success) ncaFreeHierarchicalSha256Patch(out); - - mutexUnlock(&g_ncaCryptoBufferMutex); - - return success; + return ncaGenerateHashDataPatch(ctx, data, data_size, data_offset, out, false); } bool ncaGenerateHierarchicalIntegrityPatch(NcaFsSectionContext *ctx, const void *data, u64 data_size, u64 data_offset, NcaHierarchicalIntegrityPatch *out) { - mutexLock(&g_ncaCryptoBufferMutex); + return ncaGenerateHashDataPatch(ctx, data, data_size, data_offset, out, true); +} + +void ncaRemoveTitlekeyCrypto(NcaContext *ctx) +{ + if (!ctx || !ctx->rights_id_available || !ctx->titlekey_retrieved) return; - NcaContext *nca_ctx = NULL; - bool success = false; - - u8 *cur_data = NULL; - u64 cur_data_offset = data_offset; - u64 cur_data_size = data_size; - - u8 *hash_data_block = NULL, *hash_target_block = NULL; - - if (!ctx || !ctx->enabled || !(nca_ctx = (NcaContext*)ctx->nca_ctx) || !ctx->header || ctx->header->hash_type != NcaHashType_HierarchicalIntegrity || \ - ctx->header->encryption_type == NcaEncryptionType_AesCtrEx || !data || !data_size || !out || data_offset >= ctx->header->hash_info.hierarchical_integrity.hash_target_layer_info.size || \ - (data_offset + data_size) > ctx->header->hash_info.hierarchical_integrity.hash_target_layer_info.size) + /* Copy decrypted titlekey to the decrypted NCA key area. */ + /* This will be reecrypted at a later stage. */ + for(u8 i = 0; i < NCA_FS_HEADER_COUNT; i++) { - LOGFILE("Invalid parameters!"); - goto end; + /* AES-128-XTS is not used in FS sections from NCAs with titlekey crypto. */ + if (!ctx->fs_contexts[i].enabled || (ctx->fs_contexts[i].encryption_type != NcaEncryptionType_AesCtr && ctx->fs_contexts[i].encryption_type != NcaEncryptionType_AesCtrEx)) continue; + u8 *key_ptr = (ctx->fs_contexts[i].encryption_type == NcaEncryptionType_AesCtr ? ctx->decrypted_key_area.aes_ctr : ctx->decrypted_key_area.aes_ctr_ex); + memcpy(key_ptr, ctx->titlekey, AES_128_KEY_SIZE); } - /* Process each IVFC layer. */ - for(u8 i = (NCA_IVFC_HASH_DATA_LAYER_COUNT + 1); i > 0; i--) - { - NcaHierarchicalIntegrityLayerInfo *cur_layer_info = (i > NCA_IVFC_HASH_DATA_LAYER_COUNT ? &(ctx->header->hash_info.hierarchical_integrity.hash_target_layer_info) : \ - &(ctx->header->hash_info.hierarchical_integrity.hash_data_layer_info[i - 1])); - - NcaHierarchicalIntegrityLayerInfo *parent_layer_info = (i > 1 ? &(ctx->header->hash_info.hierarchical_integrity.hash_data_layer_info[i - 2]) : NULL); - - NcaHashInfoLayerPatch *cur_layer_patch = (i > NCA_IVFC_HASH_DATA_LAYER_COUNT ? &(out->hash_target_layer_patch) : &(out->hash_data_layer_patch[i - 1])); - - if (!cur_layer_info->size || !cur_layer_info->block_size || (parent_layer_info && (!parent_layer_info->size || !parent_layer_info->block_size))) - { - LOGFILE("Invalid HierarchicalIntegrity parent/child layer!"); - goto end; - } - - /* Calculate required offsets and sizes. */ - u64 hash_block_size = NCA_IVFC_BLOCK_SIZE(cur_layer_info->block_size); - - u64 hash_data_layer_offset = 0; - u64 hash_data_start_offset = 0, hash_data_end_offset = 0, hash_data_size = 0; - - u64 hash_target_layer_offset = cur_layer_info->offset, hash_target_layer_size = cur_layer_info->size; - u64 hash_target_start_offset = 0, hash_target_end_offset = 0, hash_target_size = 0, hash_target_data_offset = 0; - - if (parent_layer_info) - { - /* HierarchicalIntegrity layer from L1 to L5. */ - hash_data_layer_offset = parent_layer_info->offset; - - hash_data_start_offset = ((cur_data_offset / hash_block_size) * SHA256_HASH_SIZE); - hash_data_end_offset = (((cur_data_offset + cur_data_size) / hash_block_size) * SHA256_HASH_SIZE); - hash_data_size = (hash_data_end_offset != hash_data_start_offset ? (hash_data_end_offset - hash_data_start_offset) : SHA256_HASH_SIZE); - - hash_target_start_offset = (hash_target_layer_offset + ALIGN_DOWN(cur_data_offset, hash_block_size)); - hash_target_end_offset = (hash_target_layer_offset + ALIGN_UP(cur_data_offset + cur_data_size, hash_block_size)); - hash_target_size = (hash_target_end_offset - hash_target_start_offset); - } else { - /* HierarchicalIntegrity master layer. */ - /* The master hash is calculated over the whole layer and saved to the NCA FS header. */ - hash_target_start_offset = hash_target_layer_offset; - hash_target_end_offset = (hash_target_layer_offset + hash_target_layer_size); - hash_target_size = hash_target_layer_size; - } - - hash_target_data_offset = (cur_data_offset - ALIGN_DOWN(cur_data_offset, hash_block_size)); - - /* Allocate memory for our hash target layer block. */ - hash_target_block = calloc(hash_target_size, sizeof(u8)); - if (!hash_target_block) - { - LOGFILE("Unable to allocate 0x%lX bytes for the HierarchicalIntegrity hash target layer block!"); - goto end; - } - - /* Adjust hash target layer end offset and size if needed to avoid read errors. */ - if (hash_target_end_offset > (hash_target_layer_offset + hash_target_layer_size)) - { - hash_target_end_offset = (hash_target_layer_offset + hash_target_layer_size); - hash_target_size = (hash_target_end_offset - hash_target_start_offset); - } - - /* Read hash target layer block. */ - if (!_ncaReadFsSection(ctx, hash_target_block, hash_target_size, hash_target_start_offset, false)) - { - LOGFILE("Failed to read HierarchicalIntegrity hash target layer block!"); - goto end; - } - - /* Replace hash target layer block data. */ - memcpy(hash_target_block + hash_target_data_offset, (i > NCA_IVFC_HASH_DATA_LAYER_COUNT ? data : cur_data), cur_data_size); - - if (parent_layer_info) - { - /* Allocate memory for our hash data layer block. */ - hash_data_block = calloc(hash_data_size, sizeof(u8)); - if (!hash_data_block) - { - LOGFILE("Unable to allocate 0x%lX bytes for the HierarchicalIntegrity hash data layer block!"); - goto end; - } - - /* Read hash target layer block. */ - if (!_ncaReadFsSection(ctx, hash_data_block, hash_data_size, hash_data_layer_offset + hash_data_start_offset, false)) - { - LOGFILE("Failed to read HierarchicalIntegrity hash data layer block!"); - goto end; - } - - /* Recalculate hashes. */ - /* Size isn't truncated for blocks smaller than the hash block size, unlike HierarchicalSha256, so we just keep using the same hash block size throughout the loop. */ - /* For these specific cases, the rest of the block should be filled with zeroes (already taken care of by using calloc()). */ - for(u64 i = 0, j = 0; i < hash_target_size; i += hash_block_size, j++) sha256CalculateHash(hash_data_block + (j * SHA256_HASH_SIZE), hash_target_block + i, hash_block_size); - } else { - /* Recalculate master hash from hash info block. */ - sha256CalculateHash(ctx->header->hash_info.hierarchical_integrity.master_hash, hash_target_block, hash_target_size); - } - - /* Reencrypt hash target layer block. */ - cur_layer_patch->data = _ncaGenerateEncryptedFsSectionBlock(ctx, hash_target_block + hash_target_data_offset, cur_data_size, hash_target_layer_offset + cur_data_offset, \ - &(cur_layer_patch->size), &(cur_layer_patch->offset), false); - if (!cur_layer_patch->data) - { - LOGFILE("Failed to generate encrypted HierarchicalIntegrity hash target layer block!"); - goto end; - } - - /* Free hash target layer block. */ - free(hash_target_block); - hash_target_block = NULL; - - if (parent_layer_info) - { - /* Free previous layer data if necessary. */ - if (cur_data) free(cur_data); - - /* Prepare data for the next target layer. */ - cur_data = hash_data_block; - cur_data_offset = hash_data_start_offset; - cur_data_size = hash_data_size; - hash_data_block = NULL; - } - } + /* Wipe Rights ID. */ + memset(&(ctx->header.rights_id), 0, sizeof(FsRightsId)); - /* Recalculate FS header hash. */ - sha256CalculateHash(nca_ctx->header.fs_hashes[ctx->section_num].hash, ctx->header, sizeof(NcaFsHeader)); - - /* Enable the 'dirty_header' flag. */ - nca_ctx->dirty_header = true; - - success = true; - -end: - if (hash_data_block) free(hash_data_block); - - if (hash_target_block) free(hash_target_block); - - if (cur_data) free(cur_data); - - if (!success) ncaFreeHierarchicalIntegrityPatch(out); - - mutexUnlock(&g_ncaCryptoBufferMutex); - - return success; + /* Update context flags. */ + ctx->rights_id_available = false; + ctx->dirty_header = true; +} + +NX_INLINE bool ncaIsFsInfoEntryValid(NcaFsInfo *fs_info) +{ + if (!fs_info) return false; + NcaFsInfo tmp_fs_info = {0}; + return (memcmp(&tmp_fs_info, fs_info, sizeof(NcaFsInfo)) != 0); } static bool ncaDecryptHeader(NcaContext *ctx) @@ -706,91 +338,67 @@ static bool ncaDecryptHeader(NcaContext *ctx) return false; } - u32 i, magic = 0; + u32 magic = 0; size_t crypt_res = 0; - u64 fs_header_offset = 0; - const u8 *header_key = NULL; + const u8 *header_key = keysGetNcaHeaderKey(); Aes128XtsContext hdr_aes_ctx = {0}, nca0_fs_header_ctx = {0}; - header_key = keysGetNcaHeaderKey(); + /* Prepare NCA header AES-128-XTS context. */ + aes128XtsContextCreate(&hdr_aes_ctx, header_key, header_key + AES_128_KEY_SIZE, false); - aes128XtsContextCreate(&hdr_aes_ctx, header_key, header_key + 0x10, false); + /* Decrypt NCA header. */ + crypt_res = aes128XtsNintendoCrypt(&hdr_aes_ctx, &(ctx->header), &(ctx->header), sizeof(NcaHeader), 0, NCA_AES_XTS_SECTOR_SIZE, false); + magic = __builtin_bswap32(ctx->header.magic); - crypt_res = aes128XtsNintendoCrypt(&hdr_aes_ctx, &(ctx->header), &(ctx->header), NCA_HEADER_LENGTH, 0, NCA_AES_XTS_SECTOR_SIZE, false); - if (crypt_res != NCA_HEADER_LENGTH) + if (crypt_res != sizeof(NcaHeader) || (magic != NCA_NCA3_MAGIC && magic != NCA_NCA2_MAGIC && magic != NCA_NCA0_MAGIC) || ctx->header.content_size != ctx->content_size) { - LOGFILE("Error decrypting partial NCA \"%s\" header!", ctx->content_id_str); + LOGFILE("Error decrypting NCA \"%s\" header!", ctx->content_id_str); return false; } - magic = __builtin_bswap32(ctx->header.magic); + /* Fill additional NCA context info. */ + ctx->format_version = (magic == NCA_NCA3_MAGIC ? NcaVersion_Nca3 : (magic == NCA_NCA2_MAGIC ? NcaVersion_Nca2 : NcaVersion_Nca0)); + ctx->key_generation = ncaGetKeyGenerationValue(ctx); + ctx->rights_id_available = ncaCheckRightsIdAvailability(ctx); - switch(magic) + /* Decrypt NCA key area (if needed). */ + if (!ctx->rights_id_available && !ncaDecryptKeyArea(ctx)) { - case NCA_NCA3_MAGIC: - ctx->format_version = NcaVersion_Nca3; - - crypt_res = aes128XtsNintendoCrypt(&hdr_aes_ctx, ctx->header.fs_headers, ctx->header.fs_headers, NCA_FULL_HEADER_LENGTH - NCA_HEADER_LENGTH, 2, NCA_AES_XTS_SECTOR_SIZE, false); - if (crypt_res != (NCA_FULL_HEADER_LENGTH - NCA_HEADER_LENGTH)) - { - LOGFILE("Error decrypting NCA3 \"%s\" FS section headers!", ctx->content_id_str); - return false; - } - - break; - case NCA_NCA2_MAGIC: - ctx->format_version = NcaVersion_Nca2; - - for(i = 0; i < NCA_FS_HEADER_COUNT; i++) - { - if (!ctx->header.fs_entries[i].enable_entry) continue; - - crypt_res = aes128XtsNintendoCrypt(&hdr_aes_ctx, &(ctx->header.fs_headers[i]), &(ctx->header.fs_headers[i]), NCA_FS_HEADER_LENGTH, 0, NCA_AES_XTS_SECTOR_SIZE, false); - if (crypt_res != NCA_FS_HEADER_LENGTH) - { - LOGFILE("Error decrypting NCA2 \"%s\" FS section header #%u!", ctx->content_id_str, i); - return false; - } - } - - break; - case NCA_NCA0_MAGIC: - ctx->format_version = NcaVersion_Nca0; - - /* We first need to decrypt the key area from the NCA0 header in order to access its FS section headers. */ - if (!ncaDecryptKeyArea(ctx)) - { - LOGFILE("Error decrypting NCA0 \"%s\" key area!", ctx->content_id_str); - return false; - } - - aes128XtsContextCreate(&nca0_fs_header_ctx, ctx->decrypted_keys[0].key, ctx->decrypted_keys[1].key, false); - - for(i = 0; i < NCA_FS_HEADER_COUNT; i++) - { - if (!ctx->header.fs_entries[i].enable_entry) continue; - - /* FS headers are not part of NCA0 headers. */ - fs_header_offset = NCA_FS_ENTRY_BLOCK_OFFSET(ctx->header.fs_entries[i].start_block_offset); - if (!ncaReadContentFile(ctx, &(ctx->header.fs_headers[i]), NCA_FS_HEADER_LENGTH, fs_header_offset)) - { - LOGFILE("Failed to read NCA0 \"%s\" FS section header #%u at offset 0x%lX!", ctx->content_id_str, i, fs_header_offset); - return false; - } - - crypt_res = aes128XtsNintendoCrypt(&nca0_fs_header_ctx, &(ctx->header.fs_headers[i]), &(ctx->header.fs_headers[i]), NCA_FS_HEADER_LENGTH, \ - NCA_NCA0_FS_HEADER_AES_XTS_SECTOR(fs_header_offset), NCA_AES_XTS_SECTOR_SIZE, false); - if (crypt_res != NCA_FS_HEADER_LENGTH) - { - LOGFILE("Error decrypting NCA0 \"%s\" FS section header #%u!", ctx->content_id_str, i); - return false; - } - } - - break; - default: - LOGFILE("Invalid NCA \"%s\" magic word! Wrong header key? (0x%08X).", ctx->content_id_str, magic); + LOGFILE("Error decrypting NCA \"%s\" key area!", ctx->content_id_str); + return false; + } + + /* Prepare NCA0 FS header AES-128-XTS context (if needed). */ + if (ctx->format_version == NcaVersion_Nca0) aes128XtsContextCreate(&nca0_fs_header_ctx, ctx->decrypted_key_area.aes_xts_1, ctx->decrypted_key_area.aes_xts_2, false); + + /* Read decrypted NCA FS section headers. */ + /* Both NCA2 and NCA3 place the NCA FS section headers right after the NCA header. However, NCA0 places them at the start sector from each NCA FS section. */ + for(u8 i = 0; i < NCA_FS_HEADER_COUNT; i++) + { + /* Don't proceed if this NCA FS section isn't populated. */ + if (ctx->format_version != NcaVersion_Nca3 && !ncaIsFsInfoEntryValid(&(ctx->header.fs_info[i]))) continue; + + /* Read NCA FS section header. */ + u64 fs_header_offset = (ctx->format_version != NcaVersion_Nca0 ? (sizeof(NcaHeader) + (i * sizeof(NcaFsHeader))) : NCA_FS_SECTOR_OFFSET(ctx->header.fs_info[i].start_sector)); + if (!ncaReadContentFile(ctx, &(ctx->fs_contexts[i].header), sizeof(NcaFsHeader), fs_header_offset)) + { + LOGFILE("Failed to read NCA%u \"%s\" FS section header #%u at offset 0x%lX!", ctx->format_version, ctx->content_id_str, i, fs_header_offset); return false; + } + + /* The AES-XTS sector number for each NCA FS header varies depending on the NCA format version. */ + /* NCA3 uses sector number 0 for the NCA header, then increases it with each new sector (e.g. making the first NCA FS section header use sector number 2, and so on). */ + /* NCA2 uses sector number 0 for each NCA FS section header. */ + /* NCA0 uses sector number 0 for the NCA header, then uses sector number 0 for the rest of the data and increases it with each new sector. */ + Aes128XtsContext *aes_xts_ctx = (ctx->format_version != NcaVersion_Nca0 ? &hdr_aes_ctx : &nca0_fs_header_ctx); + u64 sector = (ctx->format_version == NcaVersion_Nca3 ? (2U + i) : (ctx->format_version == NcaVersion_Nca2 ? 0 : (ctx->header.fs_info[i].start_sector - 2))); + + crypt_res = aes128XtsNintendoCrypt(aes_xts_ctx, &(ctx->fs_contexts[i].header), &(ctx->fs_contexts[i].header), sizeof(NcaFsHeader), sector, NCA_AES_XTS_SECTOR_SIZE, false); + if (crypt_res != sizeof(NcaFsHeader)) + { + LOGFILE("Error decrypting NCA%u \"%s\" FS section header #%u!", ctx->format_version, ctx->content_id_str, i); + return false; + } } return true; @@ -806,12 +414,12 @@ static bool ncaDecryptKeyArea(NcaContext *ctx) Result rc = 0; const u8 *kek_src = NULL; - u8 key_count, tmp_kek[0x10] = {0}; + u8 key_count = 0, tmp_kek[AES_128_KEY_SIZE] = {0}; /* Check if we're dealing with a NCA0 with a plain text key area. */ if (ncaIsVersion0KeyAreaEncrypted(ctx)) { - memcpy(ctx->decrypted_keys, ctx->header.encrypted_keys, 0x40); + memcpy(&(ctx->decrypted_key_area), &(ctx->header.encrypted_key_area), NCA_USED_KEY_AREA_SIZE); return true; } @@ -833,10 +441,10 @@ static bool ncaDecryptKeyArea(NcaContext *ctx) for(u8 i = 0; i < key_count; i++) { - rc = splCryptoGenerateAesKey(tmp_kek, ctx->header.encrypted_keys[i].key, ctx->decrypted_keys[i].key); + rc = splCryptoGenerateAesKey(tmp_kek, (u8*)&(ctx->header.encrypted_key_area) + (i * AES_128_KEY_SIZE), (u8*)&(ctx->decrypted_key_area) + (i * AES_128_KEY_SIZE)); if (R_FAILED(rc)) { - LOGFILE("splCryptoGenerateAesKey failed! (0x%08X).", rc); + LOGFILE("splCryptoGenerateAesKey failed to decrypt NCA key area entry #%u! (0x%08X).", i, rc); return false; } } @@ -844,12 +452,103 @@ static bool ncaDecryptKeyArea(NcaContext *ctx) return true; } +static bool ncaEncryptHeader(NcaContext *ctx) +{ + if (!ctx || !strlen(ctx->content_id_str)) + { + LOGFILE("Invalid NCA context!"); + return false; + } + + size_t crypt_res = 0; + const u8 *header_key = keysGetNcaHeaderKey(); + Aes128XtsContext hdr_aes_ctx = {0}, nca0_fs_header_ctx = {0}; + + /* Encrypt NCA key area. */ + if (!ncaEncryptKeyArea(ctx)) + { + LOGFILE("Error encrypting NCA \"%s\" key area!", ctx->content_id_str); + return false; + } + + /* Prepare AES-128-XTS contexts. */ + aes128XtsContextCreate(&hdr_aes_ctx, header_key, header_key + AES_128_KEY_SIZE, true); + if (ctx->format_version == NcaVersion_Nca0) aes128XtsContextCreate(&nca0_fs_header_ctx, ctx->decrypted_key_area.aes_xts_1, ctx->decrypted_key_area.aes_xts_2, true); + + /* Encrypt NCA header. */ + crypt_res = aes128XtsNintendoCrypt(&hdr_aes_ctx, &(ctx->header), &(ctx->header), sizeof(NcaHeader), 0, NCA_AES_XTS_SECTOR_SIZE, true); + if (crypt_res != sizeof(NcaHeader)) + { + LOGFILE("Error encrypting NCA \"%s\" header!", ctx->content_id_str); + return false; + } + + /* Encrypt NCA FS section headers. */ + /* Both NCA2 and NCA3 place the NCA FS section headers right after the NCA header. However, NCA0 places them at the start sector from each NCA FS section. */ + /* NCA0 FS section headers will be encrypted in-place, but they need to be written to their proper offsets. */ + for(u8 i = 0; i < NCA_FS_HEADER_COUNT; i++) + { + /* Don't proceed if this NCA FS section isn't populated. */ + if (ctx->format_version != NcaVersion_Nca3 && !ncaIsFsInfoEntryValid(&(ctx->header.fs_info[i]))) continue; + + /* The AES-XTS sector number for each NCA FS header varies depending on the NCA format version. */ + /* NCA3 uses sector number 0 for the NCA header, then increases it with each new sector (e.g. making the first NCA FS section header use sector number 2, and so on). */ + /* NCA2 uses sector number 0 for each NCA FS section header. */ + /* NCA0 uses sector number 0 for the NCA header, then uses sector number 0 for the rest of the data and increases it with each new sector. */ + Aes128XtsContext *aes_xts_ctx = (ctx->format_version != NcaVersion_Nca0 ? &hdr_aes_ctx : &nca0_fs_header_ctx); + u64 sector = (ctx->format_version == NcaVersion_Nca3 ? (2U + i) : (ctx->format_version == NcaVersion_Nca2 ? 0 : (ctx->header.fs_info[i].start_sector - 2))); + + crypt_res = aes128XtsNintendoCrypt(aes_xts_ctx, &(ctx->fs_contexts[i].header), &(ctx->fs_contexts[i].header), sizeof(NcaFsHeader), sector, NCA_AES_XTS_SECTOR_SIZE, true); + if (crypt_res != sizeof(NcaFsHeader)) + { + LOGFILE("Error encrypting NCA%u \"%s\" FS section header #%u!", ctx->format_version, ctx->content_id_str, i); + return false; + } + } + + return true; +} + +static bool ncaEncryptKeyArea(NcaContext *ctx) +{ + if (!ctx) + { + LOGFILE("Invalid NCA context!"); + return false; + } + + u8 key_count = 0; + const u8 *kaek = NULL; + Aes128Context key_area_ctx = {0}; + + /* Check if we're dealing with a NCA0 with a plaintext key area. */ + if (ncaIsVersion0KeyAreaEncrypted(ctx)) + { + memcpy(&(ctx->header.encrypted_key_area), &(ctx->decrypted_key_area), NCA_USED_KEY_AREA_SIZE); + return true; + } + + kaek = keysGetKeyAreaEncryptionKey(ctx->key_generation, ctx->header.kaek_index); + if (!kaek) + { + LOGFILE("Unable to retrieve KAEK for key generation 0x%02X and KAEK index 0x%02X!", ctx->key_generation, ctx->header.kaek_index); + return false; + } + + key_count = (ctx->format_version == NcaVersion_Nca0 ? 2 : 4); + + aes128ContextCreate(&key_area_ctx, kaek, true); + for(u8 i = 0; i < key_count; i++) aes128EncryptBlock(&key_area_ctx, (u8*)&(ctx->header.encrypted_key_area) + (i * AES_128_KEY_SIZE), (u8*)&(ctx->decrypted_key_area) + (i * AES_128_KEY_SIZE)); + + return true; +} + NX_INLINE bool ncaIsVersion0KeyAreaEncrypted(NcaContext *ctx) { if (!ctx || ctx->format_version != NcaVersion_Nca0) return false; u8 nca0_key_area_hash[SHA256_HASH_SIZE] = {0}; - sha256CalculateHash(nca0_key_area_hash, ctx->header.encrypted_keys, 0x40); + sha256CalculateHash(nca0_key_area_hash, &(ctx->header.encrypted_key_area), NCA_USED_KEY_AREA_SIZE); if (!memcmp(nca0_key_area_hash, g_nca0KeyAreaHash, SHA256_HASH_SIZE)) return false; return true; @@ -932,7 +631,7 @@ static bool _ncaReadFsSection(NcaFsSectionContext *ctx, void *out, u64 read_size bool ret = false; if (!g_ncaCryptoBuffer || !ctx || !ctx->enabled || !ctx->nca_ctx || ctx->section_num >= NCA_FS_HEADER_COUNT || ctx->section_offset < NCA_FULL_HEADER_LENGTH || \ - ctx->section_type >= NcaFsSectionType_Invalid || ctx->encryption_type == NcaEncryptionType_Auto || ctx->encryption_type > NcaEncryptionType_AesCtrEx || !ctx->header || !out || !read_size || \ + ctx->section_type >= NcaFsSectionType_Invalid || ctx->encryption_type == NcaEncryptionType_Auto || ctx->encryption_type > NcaEncryptionType_AesCtrEx || !out || !read_size || \ offset >= ctx->section_size || (offset + read_size) > ctx->section_size) { LOGFILE("Invalid NCA FS section header parameters!"); @@ -949,7 +648,8 @@ static bool _ncaReadFsSection(NcaFsSectionContext *ctx, void *out, u64 read_size u64 data_start_offset = 0, chunk_size = 0, out_chunk_size = 0; if (!strlen(nca_ctx->content_id_str) || (nca_ctx->storage_id != NcmStorageId_GameCard && !nca_ctx->ncm_storage) || (nca_ctx->storage_id == NcmStorageId_GameCard && !nca_ctx->gamecard_offset) || \ - content_offset >= nca_ctx->content_size || (content_offset + read_size) > nca_ctx->content_size) + (nca_ctx->format_version != NcaVersion_Nca0 && nca_ctx->format_version != NcaVersion_Nca2 && nca_ctx->format_version != NcaVersion_Nca3) || content_offset >= nca_ctx->content_size || \ + (content_offset + read_size) > nca_ctx->content_size) { LOGFILE("Invalid NCA header parameters!"); goto end; @@ -977,12 +677,13 @@ static bool _ncaReadFsSection(NcaFsSectionContext *ctx, void *out, u64 read_size /* Decrypt data. */ if (ctx->encryption_type == NcaEncryptionType_AesXts) { - sector_num = ((ctx->encryption_type == NcaEncryptionType_AesXts ? offset : (content_offset - NCA_HEADER_LENGTH)) / NCA_AES_XTS_SECTOR_SIZE); + sector_num = ((nca_ctx->format_version != NcaVersion_Nca0 ? offset : (content_offset - sizeof(NcaHeader))) / NCA_AES_XTS_SECTOR_SIZE); crypt_res = aes128XtsNintendoCrypt(&(ctx->xts_decrypt_ctx), out, out, read_size, sector_num, NCA_AES_XTS_SECTOR_SIZE, false); if (crypt_res != read_size) { - LOGFILE("Failed to AES-XTS decrypt 0x%lX bytes data block at offset 0x%lX from NCA \"%s\" FS section #%u! (aligned).", read_size, content_offset, nca_ctx->content_id_str, ctx->section_num); + LOGFILE("Failed to AES-XTS decrypt 0x%lX bytes data block at offset 0x%lX from NCA \"%s\" FS section #%u! (aligned).", read_size, content_offset, nca_ctx->content_id_str, \ + ctx->section_num); goto end; } } else @@ -1009,19 +710,21 @@ static bool _ncaReadFsSection(NcaFsSectionContext *ctx, void *out, u64 read_size /* Read data. */ if (!ncaReadContentFile(nca_ctx, g_ncaCryptoBuffer, chunk_size, block_start_offset)) { - LOGFILE("Failed to read 0x%lX bytes encrypted data block at offset 0x%lX from NCA \"%s\" FS section #%u! (unaligned).", chunk_size, block_start_offset, nca_ctx->content_id_str, ctx->section_num); + LOGFILE("Failed to read 0x%lX bytes encrypted data block at offset 0x%lX from NCA \"%s\" FS section #%u! (unaligned).", chunk_size, block_start_offset, nca_ctx->content_id_str, \ + ctx->section_num); goto end; } /* Decrypt data. */ if (ctx->encryption_type == NcaEncryptionType_AesXts) { - sector_num = ((ctx->encryption_type == NcaEncryptionType_AesXts ? offset : (content_offset - NCA_HEADER_LENGTH)) / NCA_AES_XTS_SECTOR_SIZE); + sector_num = ((nca_ctx->format_version != NcaVersion_Nca0 ? offset : (content_offset - sizeof(NcaHeader))) / NCA_AES_XTS_SECTOR_SIZE); crypt_res = aes128XtsNintendoCrypt(&(ctx->xts_decrypt_ctx), g_ncaCryptoBuffer, g_ncaCryptoBuffer, chunk_size, sector_num, NCA_AES_XTS_SECTOR_SIZE, false); if (crypt_res != chunk_size) { - LOGFILE("Failed to AES-XTS decrypt 0x%lX bytes data block at offset 0x%lX from NCA \"%s\" FS section #%u! (unaligned).", chunk_size, block_start_offset, nca_ctx->content_id_str, ctx->section_num); + LOGFILE("Failed to AES-XTS decrypt 0x%lX bytes data block at offset 0x%lX from NCA \"%s\" FS section #%u! (unaligned).", chunk_size, block_start_offset, nca_ctx->content_id_str, \ + ctx->section_num); goto end; } } else @@ -1050,7 +753,7 @@ static bool _ncaReadAesCtrExStorageFromBktrSection(NcaFsSectionContext *ctx, voi bool ret = false; if (!g_ncaCryptoBuffer || !ctx || !ctx->enabled || !ctx->nca_ctx || ctx->section_num >= NCA_FS_HEADER_COUNT || ctx->section_offset < NCA_FULL_HEADER_LENGTH || \ - ctx->section_type != NcaFsSectionType_PatchRomFs || ctx->encryption_type != NcaEncryptionType_AesCtrEx || !ctx->header || !out || !read_size || offset >= ctx->section_size || \ + ctx->section_type != NcaFsSectionType_PatchRomFs || ctx->encryption_type != NcaEncryptionType_AesCtrEx || !out || !read_size || offset >= ctx->section_size || \ (offset + read_size) > ctx->section_size) { LOGFILE("Invalid NCA FS section header parameters!"); @@ -1101,7 +804,8 @@ static bool _ncaReadAesCtrExStorageFromBktrSection(NcaFsSectionContext *ctx, voi /* Read data. */ if (!ncaReadContentFile(nca_ctx, g_ncaCryptoBuffer, chunk_size, block_start_offset)) { - LOGFILE("Failed to read 0x%lX bytes encrypted data block at offset 0x%lX from NCA \"%s\" FS section #%u! (unaligned).", chunk_size, block_start_offset, nca_ctx->content_id_str, ctx->section_num); + LOGFILE("Failed to read 0x%lX bytes encrypted data block at offset 0x%lX from NCA \"%s\" FS section #%u! (unaligned).", chunk_size, block_start_offset, nca_ctx->content_id_str, \ + ctx->section_num); goto end; } @@ -1121,6 +825,220 @@ end: return ret; } +/* In this function, the term "layer" is used as a generic way to refer to both HierarchicalSha256 hash regions and HierarchicalIntegrity verification levels. */ +static bool ncaGenerateHashDataPatch(NcaFsSectionContext *ctx, const void *data, u64 data_size, u64 data_offset, void *out, bool is_integrity_patch) +{ + mutexLock(&g_ncaCryptoBufferMutex); + + NcaContext *nca_ctx = NULL; + NcaHierarchicalSha256Patch *hierarchical_sha256_patch = (!is_integrity_patch ? ((NcaHierarchicalSha256Patch*)out) : NULL); + NcaHierarchicalIntegrityPatch *hierarchical_integrity_patch = (is_integrity_patch ? ((NcaHierarchicalIntegrityPatch*)out) : NULL); + + u8 *cur_data = NULL; + u64 cur_data_offset = data_offset; + u64 cur_data_size = data_size; + + u32 layer_count = 0; + u8 *parent_layer_block = NULL, *cur_layer_block = NULL; + u64 last_layer_size = 0; + + bool success = false; + + if (!ctx || !ctx->enabled || !(nca_ctx = (NcaContext*)ctx->nca_ctx) || (!is_integrity_patch && (ctx->header.hash_type != NcaHashType_HierarchicalSha256 || \ + !ctx->header.hash_data.hierarchical_sha256_data.hash_block_size || !(layer_count = ctx->header.hash_data.hierarchical_sha256_data.hash_region_count) || \ + layer_count > NCA_HIERARCHICAL_SHA256_MAX_REGION_COUNT || !(last_layer_size = ctx->header.hash_data.hierarchical_sha256_data.hash_region[layer_count - 1].size))) || \ + (is_integrity_patch && (ctx->header.hash_type != NcaHashType_HierarchicalIntegrity || \ + !(layer_count = (ctx->header.hash_data.integrity_meta_info.info_level_hash.max_level_count - 1)) || layer_count != NCA_IVFC_LEVEL_COUNT || \ + !(last_layer_size = ctx->header.hash_data.integrity_meta_info.info_level_hash.level_information[NCA_IVFC_LEVEL_COUNT - 1].size))) || !data || !data_size || \ + data_offset >= last_layer_size || (data_offset + data_size) > last_layer_size || !out) + { + LOGFILE("Invalid parameters!"); + goto end; + } + + /* Clear output patch. */ + memset(out, 0, !is_integrity_patch ? sizeof(NcaHierarchicalSha256Patch) : sizeof(NcaHierarchicalIntegrityPatch)); + + /* Process each layer. */ + for(u32 i = layer_count; i > 0; i--) + { + u64 hash_block_size = 0; + + u64 cur_layer_offset = 0, cur_layer_size = 0; + u64 cur_layer_read_start_offset = 0, cur_layer_read_end_offset = 0, cur_layer_read_size = 0, cur_layer_read_patch_offset = 0; + + u64 parent_layer_offset = 0, parent_layer_size = 0; + u64 parent_layer_read_start_offset = 0, parent_layer_read_end_offset = 0, parent_layer_read_size = 0; + + NcaHashDataPatch *cur_layer_patch = NULL; + + /* Retrieve current layer properties. */ + hash_block_size = (!is_integrity_patch ? ctx->header.hash_data.hierarchical_sha256_data.hash_block_size : \ + NCA_IVFC_BLOCK_SIZE(ctx->header.hash_data.integrity_meta_info.info_level_hash.level_information[i - 1].block_order)); + + cur_layer_offset = (!is_integrity_patch ? ctx->header.hash_data.hierarchical_sha256_data.hash_region[i - 1].offset : \ + ctx->header.hash_data.integrity_meta_info.info_level_hash.level_information[i - 1].offset); + + cur_layer_size = (!is_integrity_patch ? ctx->header.hash_data.hierarchical_sha256_data.hash_region[i - 1].size : \ + ctx->header.hash_data.integrity_meta_info.info_level_hash.level_information[i - 1].size); + + /* Retrieve parent layer properties. */ + /* If this is the master layer, then no properties are retrieved, since it is verified by the master hash from the HashData block in the NCA FS section header. */ + if (i > 1) + { + parent_layer_offset = (!is_integrity_patch ? ctx->header.hash_data.hierarchical_sha256_data.hash_region[i - 2].offset : \ + ctx->header.hash_data.integrity_meta_info.info_level_hash.level_information[i - 2].offset); + + parent_layer_size = (!is_integrity_patch ? ctx->header.hash_data.hierarchical_sha256_data.hash_region[i - 2].size : \ + ctx->header.hash_data.integrity_meta_info.info_level_hash.level_information[i - 2].size); + } + + /* Validate layer properties. */ + if (hash_block_size <= 1 || cur_layer_offset >= ctx->section_size || !cur_layer_size || (cur_layer_offset + cur_layer_size) > ctx->section_size || \ + (i > 1 && (parent_layer_offset >= ctx->section_size || !parent_layer_size || (parent_layer_offset + parent_layer_size) > ctx->section_size))) + { + LOGFILE("Invalid hierarchical parent/child layer!"); + goto end; + } + + /* Retrieve pointer to the current layer patch. */ + cur_layer_patch = (!is_integrity_patch ? &(hierarchical_sha256_patch->hash_region_patch[i - 1]) : &(hierarchical_integrity_patch->hash_level_patch[i - 1])); + + /* Calculate required offsets and sizes. */ + if (i > 1) + { + /* HierarchicalSha256 hash region with index 1 through 4, or HierarchicalIntegrity verification level with index 1 through 5. */ + parent_layer_read_start_offset = ((cur_data_offset / hash_block_size) * SHA256_HASH_SIZE); + parent_layer_read_end_offset = (((cur_data_offset + cur_data_size) / hash_block_size) * SHA256_HASH_SIZE); + parent_layer_read_size = (parent_layer_read_end_offset != parent_layer_read_start_offset ? (parent_layer_read_end_offset - parent_layer_read_start_offset) : SHA256_HASH_SIZE); + + cur_layer_read_start_offset = (cur_layer_offset + ALIGN_DOWN(cur_data_offset, hash_block_size)); + cur_layer_read_end_offset = (cur_layer_offset + ALIGN_UP(cur_data_offset + cur_data_size, hash_block_size)); + cur_layer_read_size = (cur_layer_read_end_offset - cur_layer_read_start_offset); + } else { + /* HierarchicalSha256 master hash region, or HierarchicalIntegrity master verification level. Both with index 0. */ + /* The master hash is calculated over the whole layer and saved to the HashData block from the NCA FS section header. */ + cur_layer_read_start_offset = cur_layer_offset; + cur_layer_read_end_offset = (cur_layer_offset + cur_layer_size); + cur_layer_read_size = cur_layer_size; + } + + cur_layer_read_patch_offset = (cur_data_offset - ALIGN_DOWN(cur_data_offset, hash_block_size)); + + /* Allocate memory for our current layer block. */ + cur_layer_block = calloc(cur_layer_read_size, sizeof(u8)); + if (!cur_layer_block) + { + LOGFILE("Unable to allocate 0x%lX bytes for hierarchical layer #%u data block! (current).", cur_layer_read_size, i - 1); + goto end; + } + + /* Adjust current layer read size to avoid read errors (if needed). */ + if (cur_layer_read_end_offset > (cur_layer_offset + cur_layer_size)) + { + cur_layer_read_end_offset = (cur_layer_offset + cur_layer_size); + cur_layer_read_size = (cur_layer_read_end_offset - cur_layer_read_start_offset); + } + + /* Read current layer block. */ + if (!_ncaReadFsSection(ctx, cur_layer_block, cur_layer_read_size, cur_layer_read_start_offset, false)) + { + LOGFILE("Failed to read 0x%lX bytes long hierarchical layer #%u data block from offset 0x%lX! (current).", cur_layer_read_size, i - 1, cur_layer_read_start_offset); + goto end; + } + + /* Replace current layer block data. */ + memcpy(cur_layer_block + cur_layer_read_patch_offset, (i == layer_count ? data : cur_data), cur_data_size); + + /* Recalculate hashes. */ + if (i > 1) + { + /* Allocate memory for our parent layer block. */ + parent_layer_block = calloc(parent_layer_read_size, sizeof(u8)); + if (!parent_layer_block) + { + LOGFILE("Unable to allocate 0x%lX bytes for hierarchical layer #%u data block! (parent).", parent_layer_read_size, i - 2); + goto end; + } + + /* Read parent layer block. */ + if (!_ncaReadFsSection(ctx, parent_layer_block, parent_layer_read_size, parent_layer_offset + parent_layer_read_start_offset, false)) + { + LOGFILE("Failed to read 0x%lX bytes long hierarchical layer #%u data block from offset 0x%lX! (parent).", parent_layer_read_size, i - 2, parent_layer_read_start_offset); + goto end; + } + + /* HierarchicalSha256: size is truncated for blocks smaller than the hash block size. */ + /* HierarchicalIntegrity: size *isn't* truncated for blocks smaller than the hash block size, so we just keep using the same hash block size throughout the loop. */ + /* For these specific cases, the rest of the block should be filled with zeroes (already taken care of by using calloc()). */ + for(u64 j = 0, k = 0; j < cur_layer_read_size; j += hash_block_size, k++) + { + if (!is_integrity_patch && hash_block_size > (cur_layer_read_size - j)) hash_block_size = (cur_layer_read_size - j); + sha256CalculateHash(parent_layer_block + (k * SHA256_HASH_SIZE), cur_layer_block + j, hash_block_size); + } + } else { + /* Recalculate master hash from the HashData area. */ + u8 *master_hash = (!is_integrity_patch ? ctx->header.hash_data.hierarchical_sha256_data.master_hash : ctx->header.hash_data.integrity_meta_info.master_hash); + sha256CalculateHash(master_hash, cur_layer_block, cur_layer_read_size); + } + + /* Reencrypt current layer block. */ + cur_layer_patch->data = _ncaGenerateEncryptedFsSectionBlock(ctx, cur_layer_block + cur_layer_read_patch_offset, cur_data_size, cur_layer_offset + cur_data_offset, \ + &(cur_layer_patch->size), &(cur_layer_patch->offset), false); + if (!cur_layer_patch->data) + { + LOGFILE("Failed to generate encrypted 0x%lX bytes long hierarchical layer #%u data block!", cur_data_size, i - 1); + goto end; + } + + /* Free current layer block. */ + free(cur_layer_block); + cur_layer_block = NULL; + + if (i > 1) + { + /* Free previous layer block (if needed). */ + if (cur_data) free(cur_data); + + /* Prepare data for the next layer. */ + cur_data = parent_layer_block; + cur_data_offset = parent_layer_read_start_offset; + cur_data_size = parent_layer_read_size; + parent_layer_block = NULL; + } + } + + /* Recalculate FS header hash. */ + sha256CalculateHash(nca_ctx->header.fs_header_hash[ctx->section_num].hash, &(ctx->header), sizeof(NcaFsHeader)); + + /* Enable the 'dirty_header' flag. */ + nca_ctx->dirty_header = true; + + /* Set hash region count (if needed). */ + if (!is_integrity_patch) hierarchical_sha256_patch->hash_region_count = layer_count; + + success = true; + +end: + if (cur_layer_block) free(cur_layer_block); + + if (parent_layer_block) free(parent_layer_block); + + if (!success && out) + { + if (!is_integrity_patch) + { + ncaFreeHierarchicalSha256Patch(hierarchical_sha256_patch); + } else { + ncaFreeHierarchicalIntegrityPatch(hierarchical_integrity_patch); + } + } + + mutexUnlock(&g_ncaCryptoBufferMutex); + + return success; +} + static void *_ncaGenerateEncryptedFsSectionBlock(NcaFsSectionContext *ctx, const void *data, u64 data_size, u64 data_offset, u64 *out_block_size, u64 *out_block_offset, bool lock) { if (lock) mutexLock(&g_ncaCryptoBufferMutex); @@ -1129,7 +1047,7 @@ static void *_ncaGenerateEncryptedFsSectionBlock(NcaFsSectionContext *ctx, const bool success = false; if (!g_ncaCryptoBuffer || !ctx || !ctx->enabled || !ctx->nca_ctx || ctx->section_num >= NCA_FS_HEADER_COUNT || ctx->section_offset < NCA_FULL_HEADER_LENGTH || \ - ctx->section_type >= NcaFsSectionType_Invalid || ctx->encryption_type == NcaEncryptionType_Auto || ctx->encryption_type >= NcaEncryptionType_AesCtrEx || !ctx->header || !data || !data_size || \ + ctx->section_type >= NcaFsSectionType_Invalid || ctx->encryption_type == NcaEncryptionType_Auto || ctx->encryption_type >= NcaEncryptionType_AesCtrEx || !data || !data_size || \ data_offset >= ctx->section_size || (data_offset + data_size) > ctx->section_size || !out_block_size || !out_block_offset) { LOGFILE("Invalid NCA FS section header parameters!"); @@ -1146,7 +1064,8 @@ static void *_ncaGenerateEncryptedFsSectionBlock(NcaFsSectionContext *ctx, const u64 plain_chunk_offset = 0; if (!strlen(nca_ctx->content_id_str) || (nca_ctx->storage_id != NcmStorageId_GameCard && !nca_ctx->ncm_storage) || (nca_ctx->storage_id == NcmStorageId_GameCard && !nca_ctx->gamecard_offset) || \ - content_offset >= nca_ctx->content_size || (content_offset + data_size) > nca_ctx->content_size) + (nca_ctx->format_version != NcaVersion_Nca0 && nca_ctx->format_version != NcaVersion_Nca2 && nca_ctx->format_version != NcaVersion_Nca3) || content_offset >= nca_ctx->content_size || \ + (content_offset + data_size) > nca_ctx->content_size) { LOGFILE("Invalid NCA header parameters!"); goto end; @@ -1171,7 +1090,7 @@ static void *_ncaGenerateEncryptedFsSectionBlock(NcaFsSectionContext *ctx, const /* Encrypt data. */ if (ctx->encryption_type == NcaEncryptionType_AesXts) { - sector_num = ((ctx->encryption_type == NcaEncryptionType_AesXts ? data_offset : (content_offset - NCA_HEADER_LENGTH)) / NCA_AES_XTS_SECTOR_SIZE); + sector_num = ((nca_ctx->format_version != NcaVersion_Nca0 ? data_offset : (content_offset - sizeof(NcaHeader))) / NCA_AES_XTS_SECTOR_SIZE); crypt_res = aes128XtsNintendoCrypt(&(ctx->xts_encrypt_ctx), out, out, data_size, sector_num, NCA_AES_XTS_SECTOR_SIZE, true); if (crypt_res != data_size) @@ -1223,7 +1142,7 @@ static void *_ncaGenerateEncryptedFsSectionBlock(NcaFsSectionContext *ctx, const /* Reencrypt data. */ if (ctx->encryption_type == NcaEncryptionType_AesXts) { - sector_num = ((ctx->encryption_type == NcaEncryptionType_AesXts ? block_start_offset : (content_offset - NCA_HEADER_LENGTH)) / NCA_AES_XTS_SECTOR_SIZE); + sector_num = ((nca_ctx->format_version != NcaVersion_Nca0 ? block_start_offset : (content_offset - sizeof(NcaHeader))) / NCA_AES_XTS_SECTOR_SIZE); crypt_res = aes128XtsNintendoCrypt(&(ctx->xts_encrypt_ctx), out, out, block_size, sector_num, NCA_AES_XTS_SECTOR_SIZE, true); if (crypt_res != block_size) diff --git a/source/nca.h b/source/nca.h index 92b62f8..e76121f 100644 --- a/source/nca.h +++ b/source/nca.h @@ -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_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_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_AES_XTS_SECTOR_SIZE 0x200 -#define NCA_NCA0_FS_HEADER_AES_XTS_SECTOR(x) (((x) - NCA_HEADER_LENGTH) >> 9) +#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 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 { - u32 magic; ///< "IVFC". + 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]; -} NcaSparseInfo; - -typedef struct { - u16 version; - u8 fs_type; ///< NcaFsType. - u8 hash_type; ///< NcaHashType. - u8 encryption_type; ///< NcaEncryptionType. - u8 reserved_1[0x3]; - NcaHashInfo hash_info; - NcaPatchInfo patch_info; union { - u8 section_ctr[0x8]; + 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]; + NcaHashData hash_data; + NcaPatchInfo patch_info; + 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,54 +278,66 @@ 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 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. + 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. - u64 content_size; ///< Retrieved from NcmContentInfo. - u8 key_generation; ///< NcaKeyGenerationOld / NcaKeyGeneration. Retrieved from the decrypted header. - u8 id_offset; ///< Retrieved from NcmContentInfo. + u8 format_version; ///< NcaVersion. + u8 content_type; ///< NcmContentType. Retrieved from NcmContentInfo. + u64 content_size; ///< Retrieved from NcmContentInfo. + u8 key_generation; ///< NcaKeyGenerationOld / NcaKeyGeneration. Retrieved from the decrypted header. + 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; -} - -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; + 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; - 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; diff --git a/source/pfs.c b/source/pfs.c index 4c7a5e3..7eafcdd 100644 --- a/source/pfs.c +++ b/source/pfs.c @@ -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)) diff --git a/source/romfs.c b/source/romfs.c index 2f4b4bb..83422e6 100644 --- a/source/romfs.c +++ b/source/romfs.c @@ -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. */ diff --git a/source/romfs.h b/source/romfs.h index 728ffe9..1b57424 100644 --- a/source/romfs.h +++ b/source/romfs.h @@ -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); diff --git a/source/utils.c b/source/utils.c index 6282b2e..b357b1c 100644 --- a/source/utils.c +++ b/source/utils.c @@ -29,7 +29,7 @@ #include "usb.h" #include "fatfs/ff.h" -#define LOGFILE_PATH "./nxdumptool.log" +#define LOGFILE_PATH "./" APP_TITLE ".log" /* Global variables. */ diff --git a/source/utils.h b/source/utils.h index 02ff350..6945816 100644 --- a/source/utils.h +++ b/source/utils.h @@ -37,7 +37,7 @@ #include #include -#define APP_BASE_PATH "sdmc:/switch/nxdumptool/" +#define APP_BASE_PATH "sdmc:/switch/" APP_TITLE "/" #define MEMBER_SIZE(type, member) sizeof(((type*)NULL)->member) diff --git a/todo.txt b/todo.txt index fa79aac..8148c5e 100644 --- a/todo.txt +++ b/todo.txt @@ -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) @@ -30,18 +31,4 @@ todo: fwrite(titleContentInfos, 1, titleContentInfoCnt * sizeof(NcmContentInfo), content_info); 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; - } \ No newline at end of file + \ No newline at end of file