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

Bunch of changes.

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

View file

@ -37,14 +37,14 @@ bool bktrInitializeContext(BktrContext *out, NcaFsSectionContext *base_nca_fs_ct
{ {
NcaContext *base_nca_ctx = NULL, *update_nca_ctx = NULL; 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 || \ 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->section_type != NcaFsSectionType_RomFs || base_nca_fs_ctx->encryption_type == NcaEncryptionType_AesCtrEx || !update_nca_fs_ctx || !update_nca_fs_ctx->enabled || \ 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->header || !(update_nca_ctx = (NcaContext*)update_nca_fs_ctx->nca_ctx) || update_nca_fs_ctx->section_type != NcaFsSectionType_PatchRomFs || \ update_nca_fs_ctx->section_type != NcaFsSectionType_PatchRomFs || update_nca_fs_ctx->encryption_type != NcaEncryptionType_AesCtrEx || \
update_nca_fs_ctx->encryption_type != NcaEncryptionType_AesCtrEx || base_nca_ctx->header.program_id != update_nca_ctx->header.program_id || \ base_nca_ctx->header.program_id != update_nca_ctx->header.program_id || base_nca_ctx->header.content_type != update_nca_ctx->header.content_type || \
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.indirect_bucket.header.magic) != NCA_BKTR_MAGIC || \
__builtin_bswap32(update_nca_fs_ctx->header->patch_info.aes_ctr_ex_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_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.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_offset + update_nca_fs_ctx->header->patch_info.aes_ctr_ex_size) != update_nca_fs_ctx->section_size) (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!"); LOGFILE("Invalid parameters!");
return false; return false;
@ -59,10 +59,10 @@ bool bktrInitializeContext(BktrContext *out, NcaFsSectionContext *base_nca_fs_ct
/* Fill context. */ /* Fill context. */
bool success = false; 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. */ /* 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) if (!out->indirect_block)
{ {
LOGFILE("Unable to allocate memory for the BKTR Indirect Storage 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. */ /* 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!"); LOGFILE("Failed to read BKTR Indirect Storage Block data!");
goto end; goto end;
} }
/* Allocate space for an extra (fake) AesCtrEx storage entry, to simplify our logic. */ /* 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) if (!out->aes_ctr_ex_block)
{ {
LOGFILE("Unable to allocate memory for the BKTR AesCtrEx Storage 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. */ /* 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!"); LOGFILE("Failed to read BKTR AesCtrEx Storage Block data!");
goto end; 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!"); LOGFILE("Invalid BKTR AesCtrEx Storage Block size!");
goto end; 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); 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); 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_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].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->generation; 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].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; 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. */ /* Initialize update NCA RomFS context. */
/* Don't verify offsets from Patch RomFS sections, because they reflect the full, patched RomFS image. */ /* 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.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.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_info.hierarchical_integrity.hash_target_layer_info.size; 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. */ /* Read update NCA RomFS header. */
if (!bktrPhysicalSectionRead(out, &(out->patch_romfs_ctx.header), sizeof(RomFileSystemHeader), out->patch_romfs_ctx.offset)) if (!bktrPhysicalSectionRead(out, &(out->patch_romfs_ctx.header), sizeof(RomFileSystemHeader), out->patch_romfs_ctx.offset))

View file

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

View file

@ -42,6 +42,8 @@
/* Type definitions. */ /* 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 { typedef struct {
u32 memory_interface_mode; u32 memory_interface_mode;
u32 asic_status; u32 asic_status;
@ -106,6 +108,8 @@ static u64 g_gameCardCapacity = 0;
static u8 *g_gameCardHfsRootHeader = NULL; /// GameCardHashFileSystemHeader + (entry_count * GameCardHashFileSystemEntry) + Name Table. static u8 *g_gameCardHfsRootHeader = NULL; /// GameCardHashFileSystemHeader + (entry_count * GameCardHashFileSystemEntry) + Name Table.
static GameCardHashFileSystemPartitionInfo *g_gameCardHfsPartitions = NULL; static GameCardHashFileSystemPartitionInfo *g_gameCardHfsPartitions = NULL;
static GameCardKeyArea g_gameCardKeyArea = {0};
static MemoryLocation g_fsProgramMemory = { static MemoryLocation g_fsProgramMemory = {
.program_id = FS_SYSMODULE_TID, .program_id = FS_SYSMODULE_TID,
.mask = 0, .mask = 0,
@ -113,9 +117,6 @@ static MemoryLocation g_fsProgramMemory = {
.data_size = 0 .data_size = 0
}; };
static GameCardSecurityInformation g_gameCardSecurityInfo = {0};
static GameCardKeyArea g_gameCardKeyArea = {0};
/* Function prototypes. */ /* Function prototypes. */
static bool gamecardCreateDetectionThread(void); static bool gamecardCreateDetectionThread(void);
@ -127,7 +128,7 @@ NX_INLINE bool gamecardIsInserted(void);
static void gamecardLoadInfo(void); static void gamecardLoadInfo(void);
static void gamecardFreeInfo(void); static void gamecardFreeInfo(void);
static bool gamecardReadSecurityInformation(void); static bool gamecardReadInitialData(void);
static bool gamecardGetHandleAndStorage(u32 partition); static bool gamecardGetHandleAndStorage(u32 partition);
NX_INLINE void gamecardCloseHandle(void); 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. */ /* Read full FS program memory to retrieve the GameCardInitialData block, which is part of the GameCardKeyArea block. */
/* 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. */ /* In FS program memory, this is stored as part of the GameCardSecurityInformation struct, which is returned by Lotus command "ChangeToSecureMode" (0xF). */
/* 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. */ /* 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. */
/* Under some circumstances, the gamecard initial data is located *after* the GameCardSecurityInformation area (offset 0x600), instead of its common location at offset 0x400. */ /* The GameCardSecurityInformation struct is only kept for documentation purposes. It isn't used at all to retrieve the GameCardInitialData block. */
if (!gamecardReadSecurityInformation()) 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; goto end;
} }
@ -731,10 +732,8 @@ end:
static void gamecardFreeInfo(void) static void gamecardFreeInfo(void)
{ {
memset(&g_gameCardHeader, 0, sizeof(GameCardHeader));
memset(&g_gameCardSecurityInfo, 0, sizeof(GameCardSecurityInformation));
memset(&g_gameCardKeyArea, 0, sizeof(GameCardKeyArea)); memset(&g_gameCardKeyArea, 0, sizeof(GameCardKeyArea));
memset(&g_gameCardHeader, 0, sizeof(GameCardHeader));
g_gameCardStorageNormalAreaSize = 0; g_gameCardStorageNormalAreaSize = 0;
g_gameCardStorageSecureAreaSize = 0; g_gameCardStorageSecureAreaSize = 0;
@ -768,9 +767,10 @@ static void gamecardFreeInfo(void)
g_gameCardInfoLoaded = false; g_gameCardInfoLoaded = false;
} }
static bool gamecardReadSecurityInformation(void) static bool gamecardReadInitialData(void)
{ {
bool found = false; bool found = false;
u8 tmp_hash[SHA256_HASH_SIZE] = {0};
/* Retrieve full FS program memory dump. */ /* Retrieve full FS program memory dump. */
if (!memRetrieveFullProgramMemory(&g_fsProgramMemory)) if (!memRetrieveFullProgramMemory(&g_fsProgramMemory))
@ -779,35 +779,20 @@ static bool gamecardReadSecurityInformation(void)
return false; 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++) 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. */ sha256CalculateHash(tmp_hash, g_fsProgramMemory.data + offset, sizeof(GameCardInitialData));
offset += 0x100;
memcpy(&g_gameCardSecurityInfo, g_fsProgramMemory.data + offset, sizeof(GameCardSecurityInformation));
/* Check the key_source / package_id value. */ if (!memcmp(tmp_hash, g_gameCardHeader.initial_data_hash, SHA256_HASH_SIZE))
if (g_gameCardSecurityInfo.initial_data.package_id == g_gameCardHeader.package_id)
{ {
/* Jackpot. */ /* Jackpot. */
memcpy(&(g_gameCardKeyArea.initial_data), g_fsProgramMemory.data + offset, sizeof(GameCardInitialData));
found = true; found = true;
} else { break;
/* Copy the sector right after the GameCardSecurityInformation element from the memory dump, since it may hold the gamecard initial data. */
offset += sizeof(GameCardSecurityInformation);
memcpy(&(g_gameCardSecurityInfo.initial_data), g_fsProgramMemory.data + offset, sizeof(GameCardInitialData));
found = (g_gameCardSecurityInfo.initial_data.package_id == g_gameCardHeader.package_id);
} }
break;
}
if (found)
{
memcpy(&(g_gameCardKeyArea.initial_data), &(g_gameCardSecurityInfo.initial_data), sizeof(GameCardInitialData));
} else {
LOGFILE("Failed to locate gamecard initial data area!");
} }
/* Free FS memory dump. */ /* Free FS memory dump. */
@ -824,29 +809,29 @@ static bool gamecardGetHandleAndStorage(u32 partition)
return false; return false;
} }
Result rc1 = 0, rc2 = 0; Result rc = 0;
/* 10 tries. */ /* 10 tries. */
for(u8 i = 0; i < 10; i++) for(u8 i = 0; i < 10; i++)
{ {
/* 100 ms wait in case there was an error in the previous loop. */ /* 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. */ /* First, let's try to retrieve a gamecard handle. */
/* This can return 0x140A02 if the "nogc" patch is enabled by the running CFW. */ /* This can return 0x140A02 if the "nogc" patch is enabled by the running CFW. */
rc1 = fsDeviceOperatorGetGameCardHandle(&g_deviceOperator, &g_gameCardHandle); rc = fsDeviceOperatorGetGameCardHandle(&g_deviceOperator, &g_gameCardHandle);
if (R_FAILED(rc1)) 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; continue;
} }
/* If the previous call succeeded, let's try to open the desired gamecard storage area. */ /* If the previous call succeeded, let's try to open the desired gamecard storage area. */
rc2 = fsOpenGameCardStorage(&g_gameCardStorage, &g_gameCardHandle, partition); rc = fsOpenGameCardStorage(&g_gameCardStorage, &g_gameCardHandle, partition);
if (R_FAILED(rc2)) if (R_FAILED(rc))
{ {
gamecardCloseHandle(); /* Close invalid gamecard handle. */ 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; continue;
} }
@ -854,7 +839,7 @@ static bool gamecardGetHandleAndStorage(u32 partition)
break; break;
} }
return (R_SUCCEEDED(rc1) && R_SUCCEEDED(rc2)); return R_SUCCEEDED(rc);
} }
NX_INLINE void gamecardCloseHandle(void) NX_INLINE void gamecardCloseHandle(void)

View file

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

View file

@ -200,7 +200,7 @@ int main(int argc, char *argv[])
int ret = 0; int ret = 0;
LOGFILE("nxdumptool starting."); LOGFILE(APP_TITLE " starting.");
consoleInit(NULL); consoleInit(NULL);
@ -216,7 +216,7 @@ int main(int argc, char *argv[])
u8 *buf = NULL; 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); u64 update_tid = (base_tid | 0x800);
Ticket base_tik = {0}, update_tik = {0}; Ticket base_tik = {0}, update_tik = {0};

File diff suppressed because it is too large Load diff

View file

@ -25,29 +25,28 @@
#include "tik.h" #include "tik.h"
#define NCA_HEADER_LENGTH 0x400 #define NCA_FS_HEADER_COUNT 4
#define NCA_FS_HEADER_LENGTH 0x200 #define NCA_FULL_HEADER_LENGTH (sizeof(NcaHeader) + (sizeof(NcaFsHeader) * NCA_FS_HEADER_COUNT))
#define NCA_FS_HEADER_COUNT 4
#define NCA_FULL_HEADER_LENGTH (NCA_HEADER_LENGTH + (NCA_FS_HEADER_LENGTH * NCA_FS_HEADER_COUNT))
#define NCA_NCA0_MAGIC 0x4E434130 /* "NCA0" */ #define NCA_NCA0_MAGIC 0x4E434130 /* "NCA0". */
#define NCA_NCA2_MAGIC 0x4E434132 /* "NCA2" */ #define NCA_NCA2_MAGIC 0x4E434132 /* "NCA2". */
#define NCA_NCA3_MAGIC 0x4E434133 /* "NCA3" */ #define NCA_NCA3_MAGIC 0x4E434133 /* "NCA3". */
#define NCA_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_HIERARCHICAL_SHA256_MAX_REGION_COUNT 5
#define NCA_IVFC_LAYER_COUNT 7
#define NCA_IVFC_HASH_DATA_LAYER_COUNT 5
#define NCA_IVFC_BLOCK_SIZE(x) (1 << (x))
#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_BKTR_MAGIC 0x424B5452 /* "BKTR". */
#define NCA_FS_ENTRY_BLOCK_OFFSET(x) ((u64)(x) * NCA_FS_ENTRY_BLOCK_SIZE)
#define NCA_AES_XTS_SECTOR_SIZE 0x200 #define NCA_FS_SECTOR_SIZE 0x200
#define NCA_NCA0_FS_HEADER_AES_XTS_SECTOR(x) (((x) - NCA_HEADER_LENGTH) >> 9) #define NCA_FS_SECTOR_OFFSET(x) ((u64)(x) * NCA_FS_SECTOR_SIZE)
#define NCA_AES_XTS_SECTOR_SIZE 0x200
typedef enum { typedef enum {
NcaDistributionType_Download = 0, NcaDistributionType_Download = 0,
@ -96,19 +95,49 @@ typedef enum {
} NcaKeyGeneration; } NcaKeyGeneration;
typedef struct { typedef struct {
u32 start_block_offset; ///< Expressed in NCA_FS_ENTRY_BLOCK_SIZE blocks. u32 start_sector; ///< Expressed in NCA_FS_SECTOR_SIZE sectors.
u32 end_block_offset; ///< Expressed in NCA_FS_ENTRY_BLOCK_SIZE blocks. u32 end_sector; ///< Expressed in NCA_FS_SECTOR_SIZE sectors.
u8 enable_entry; u32 hash_sector;
u8 reserved[0x7]; u8 reserved[0x4];
} NcaFsEntry; } NcaFsInfo;
typedef struct { typedef struct {
u8 hash[SHA256_HASH_SIZE]; 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 { typedef struct {
u8 key[0x10]; u8 aes_xts_1[AES_128_KEY_SIZE]; ///< AES-128-XTS key 0 used for NCA FS sections with NcaEncryptionType_AesXts crypto.
} NcaKey; 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 { typedef enum {
NcaFsType_RomFs = 0, NcaFsType_RomFs = 0,
@ -133,121 +162,111 @@ typedef enum {
typedef struct { typedef struct {
u64 offset; u64 offset;
u64 size; u64 size;
} NcaHierarchicalSha256LayerInfo; } NcaRegion;
/// Used for NcaFsType_PartitionFs and NCA0 NcaFsType_RomFsRomFS. /// Used by NcaFsType_PartitionFs and NCA0 NcaFsType_RomFs.
typedef struct { typedef struct {
u8 master_hash[SHA256_HASH_SIZE]; u8 master_hash[SHA256_HASH_SIZE];
u32 hash_block_size; u32 hash_block_size;
u32 layer_count; u32 hash_region_count;
NcaHierarchicalSha256LayerInfo hash_data_layer_info; NcaRegion hash_region[NCA_HIERARCHICAL_SHA256_MAX_REGION_COUNT];
NcaHierarchicalSha256LayerInfo hash_target_layer_info; } NcaHierarchicalSha256Data;
} NcaHierarchicalSha256;
typedef struct { typedef struct {
u64 offset; u64 offset;
u64 size; 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]; u8 reserved[0x4];
} NcaHierarchicalIntegrityLayerInfo; } NcaHierarchicalIntegrityVerificationLevelInformation;
/// Used for NcaFsType_RomFs.
typedef struct { 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 version;
u32 master_hash_size; u32 master_hash_size; ///< Always SHA256_HASH_SIZE.
u32 layer_count; NcaInfoLevelHash info_level_hash;
NcaHierarchicalIntegrityLayerInfo hash_data_layer_info[NCA_IVFC_HASH_DATA_LAYER_COUNT]; u8 master_hash[SHA256_HASH_SIZE];
NcaHierarchicalIntegrityLayerInfo hash_target_layer_info; } NcaIntegrityMetaInfo;
u8 signature_salt[0x20];
u8 master_hash[0x20];
} NcaHierarchicalIntegrity;
typedef struct { typedef struct {
union { union {
struct { struct {
///< Used if hash_type == NcaHashType_HierarchicalSha256 (NcaFsType_PartitionFs and NCA0 NcaFsType_RomFs). ///< Used if hash_type == NcaHashType_HierarchicalSha256 (NcaFsType_PartitionFs and NCA0 NcaFsType_RomFs).
NcaHierarchicalSha256 hierarchical_sha256; NcaHierarchicalSha256Data hierarchical_sha256_data;
u8 reserved_1[0xB0]; u8 reserved_1[0x80];
}; };
struct { struct {
///< Used if hash_type == NcaHashType_HierarchicalIntegrity (NcaFsType_RomFs). ///< Used if hash_type == NcaHashType_HierarchicalIntegrity (NcaFsType_RomFs).
NcaHierarchicalIntegrity hierarchical_integrity; NcaIntegrityMetaInfo integrity_meta_info;
u8 reserved_2[0x18]; u8 reserved_2[0x18];
}; };
}; };
} NcaHashInfo; } NcaHashData;
typedef struct { typedef struct {
u32 magic; ///< "BKTR". u32 magic; ///< "BKTR".
u32 bucket_count; u32 version; ///< offset_count / node_count ?
u32 entry_count; u32 entry_count;
u8 reserved[0x4]; u8 reserved[0x4];
} NcaBucketTreeHeader; } NcaBucketTreeHeader;
typedef struct {
u64 offset;
u64 size;
NcaBucketTreeHeader header;
} NcaBucketInfo;
/// Only used for NcaEncryptionType_AesCtrEx (PatchRomFs). /// Only used for NcaEncryptionType_AesCtrEx (PatchRomFs).
typedef struct { typedef struct {
u64 indirect_offset; NcaBucketInfo indirect_bucket;
u64 indirect_size; NcaBucketInfo aes_ctr_ex_bucket;
NcaBucketTreeHeader indirect_header;
u64 aes_ctr_ex_offset;
u64 aes_ctr_ex_size;
NcaBucketTreeHeader aes_ctr_ex_header;
} NcaPatchInfo; } NcaPatchInfo;
/// Format unknown.
typedef struct { 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 { union {
u8 section_ctr[0x8]; u8 value[0x8];
struct { struct {
u32 generation; u32 generation;
u32 secure_value; 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; NcaSparseInfo sparse_info;
u8 reserved_2[0x88]; u8 reserved_2[0x88];
} NcaFsHeader; } 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 { typedef enum {
NcaFsSectionType_PartitionFs = 0, ///< NcaFsType_PartitionFs + NcaHashType_HierarchicalSha256. NcaFsSectionType_PartitionFs = 0, ///< NcaFsType_PartitionFs + NcaHashType_HierarchicalSha256.
NcaFsSectionType_RomFs = 1, ///< NcaFsType_RomFs + NcaHashType_HierarchicalIntegrity. NcaFsSectionType_RomFs = 1, ///< NcaFsType_RomFs + NcaHashType_HierarchicalIntegrity.
@ -259,54 +278,66 @@ typedef enum {
typedef struct { typedef struct {
bool enabled; bool enabled;
void *nca_ctx; ///< NcaContext. Used to perform NCA reads. void *nca_ctx; ///< NcaContext. Used to perform NCA reads.
NcaFsHeader header; ///< NCA FS section header.
u8 section_num; u8 section_num;
u64 section_offset; u64 section_offset;
u64 section_size; u64 section_size;
u8 section_type; ///< NcaFsSectionType. u8 section_type; ///< NcaFsSectionType.
u8 encryption_type; ///< NcaEncryptionType. u8 encryption_type; ///< NcaEncryptionType.
NcaFsHeader *header; u8 ctr[AES_BLOCK_SIZE]; ///< Used to update the AES CTR context IV based on the desired offset.
u8 ctr[0x10]; ///< Used to update the AES CTR context IV based on the desired offset.
Aes128CtrContext ctr_ctx; Aes128CtrContext ctr_ctx;
Aes128XtsContext xts_decrypt_ctx; Aes128XtsContext xts_decrypt_ctx;
Aes128XtsContext xts_encrypt_ctx; Aes128XtsContext xts_encrypt_ctx;
} NcaFsSectionContext; } NcaFsSectionContext;
typedef enum {
NcaVersion_Nca0 = 0,
NcaVersion_Nca2 = 2,
NcaVersion_Nca3 = 3
} NcaVersion;
typedef struct { typedef struct {
u8 storage_id; ///< NcmStorageId. u8 aes_xts_1[AES_128_KEY_SIZE]; ///< AES-128-XTS key 0 used for NCA FS sections with NcaEncryptionType_AesXts crypto.
NcmContentStorage *ncm_storage; ///< Pointer to a NcmContentStorage instance. Used to read NCA data. u8 aes_xts_2[AES_128_KEY_SIZE]; ///< AES-128-XTS key 1 used for NCA FS sections with NcaEncryptionType_AesXts crypto.
u64 gamecard_offset; ///< Used to read NCA data from a gamecard using a FsStorage instance when storage_id == NcmStorageId_GameCard. u8 aes_ctr[AES_128_KEY_SIZE]; ///< AES-128-CTR key used for NCA FS sections with NcaEncryptionType_AesCtr crypto.
NcmContentId content_id; ///< Also used to read NCA data. 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]; 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]; char hash_str[0x41];
u8 format_version; ///< NcaVersion. u8 format_version; ///< NcaVersion.
u8 content_type; ///< NcmContentType. Retrieved from NcmContentInfo. u8 content_type; ///< NcmContentType. Retrieved from NcmContentInfo.
u64 content_size; ///< Retrieved from NcmContentInfo. u64 content_size; ///< Retrieved from NcmContentInfo.
u8 key_generation; ///< NcaKeyGenerationOld / NcaKeyGeneration. Retrieved from the decrypted header. u8 key_generation; ///< NcaKeyGenerationOld / NcaKeyGeneration. Retrieved from the decrypted header.
u8 id_offset; ///< Retrieved from NcmContentInfo. u8 id_offset; ///< Retrieved from NcmContentInfo.
bool rights_id_available; bool rights_id_available;
bool titlekey_retrieved; bool titlekey_retrieved;
u8 titlekey[0x10]; u8 titlekey[AES_128_KEY_SIZE]; ///< Decrypted titlekey from the ticket.
bool dirty_header; bool dirty_header;
NcaHeader header; NcaHeader header; ///< NCA header.
NcaFsSectionContext fs_contexts[4]; NcaFsSectionContext fs_contexts[NCA_FS_HEADER_COUNT];
NcaKey decrypted_keys[4]; NcaDecryptedKeyArea decrypted_key_area;
} NcaContext; } NcaContext;
typedef struct { typedef struct {
u64 offset; ///< New layer data offset (relative to the start of the NCA content file). u64 offset; ///< New data offset (relative to the start of the NCA content file).
u64 size; ///< New layer data size. u64 size; ///< New data size.
u8 *data; ///< New layer data. u8 *data; ///< New data.
} NcaHashInfoLayerPatch; } NcaHashDataPatch;
typedef struct { typedef struct {
NcaHashInfoLayerPatch hash_data_layer_patch; u32 hash_region_count;
NcaHashInfoLayerPatch hash_target_layer_patch; NcaHashDataPatch hash_region_patch[NCA_HIERARCHICAL_SHA256_MAX_REGION_COUNT];
} NcaHierarchicalSha256Patch; } NcaHierarchicalSha256Patch;
typedef struct { typedef struct {
NcaHashInfoLayerPatch hash_data_layer_patch[NCA_IVFC_HASH_DATA_LAYER_COUNT]; NcaHashDataPatch hash_level_patch[NCA_IVFC_LEVEL_COUNT];
NcaHashInfoLayerPatch hash_target_layer_patch;
} NcaHierarchicalIntegrityPatch; } NcaHierarchicalIntegrityPatch;
/// Functions to control the internal heap buffer used by NCA FS section crypto operations. /// 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 '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 '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 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); 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(). /// 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. /// 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); 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. /// 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 HierarchicalSha256 hash target layer (actual underlying FS). /// 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 NcaHashInfo block master hash and the NCA FS header hash from the NCA header, and enables the 'dirty_header' flag from the NCA context. /// 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. /// 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); bool ncaGenerateHierarchicalSha256Patch(NcaFsSectionContext *ctx, const void *data, u64 data_size, u64 data_offset, NcaHierarchicalSha256Patch *out);
/// Cleanups a previously generated NcaHierarchicalSha256Patch. /// Cleanups a previously generated NcaHierarchicalSha256Patch.
NX_INLINE void ncaFreeHierarchicalSha256Patch(NcaHierarchicalSha256Patch *patch) NX_INLINE void ncaFreeHierarchicalSha256Patch(NcaHierarchicalSha256Patch *patch)
{ {
if (!patch) return; if (!patch || !patch->hash_region_count || patch->hash_region_count > NCA_HIERARCHICAL_SHA256_MAX_REGION_COUNT) 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); 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)); memset(patch, 0, sizeof(NcaHierarchicalSha256Patch));
} }
/// Generates HierarchicalIntegrity FS section patch data, which can be used to replace NCA data in content dumping operations. /// 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 HierarchicalIntegrity hash target layer (actual underlying FS). /// 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 NcaHashInfo block master hash and the NCA FS header hash from the NCA header, and enables the 'dirty_header' flag from the NCA context. /// 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. /// 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); 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; 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 (patch->hash_level_patch[i].data) free(patch->hash_level_patch[i].data);
if (layer_patch->data) free(layer_patch->data);
} }
memset(patch, 0, sizeof(NcaHierarchicalIntegrityPatch)); 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; 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; if (!hierarchical_sha256_data || !section_size || !hierarchical_sha256_data->hash_block_size || !hierarchical_sha256_data->hash_region_count || \
memset(&(ctx->header.rights_id), 0, sizeof(FsRightsId)); hierarchical_sha256_data->hash_region_count > NCA_HIERARCHICAL_SHA256_MAX_REGION_COUNT) return false;
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;
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 (hierarchical_sha256_data->hash_region[i].offset >= section_size || !hierarchical_sha256_data->hash_region[i].size || \
if (layer_info->offset >= section_size || !layer_info->size || (layer_info->offset + layer_info->size) > section_size) return false; (hierarchical_sha256_data->hash_region[i].offset + hierarchical_sha256_data->hash_region[i].size) > section_size) return false;
} }
return true; 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 || \ if (!integrity_meta_info || !section_size || __builtin_bswap32(integrity_meta_info->magic) != NCA_IVFC_MAGIC || integrity_meta_info->master_hash_size != SHA256_HASH_SIZE || \
hierarchical_integrity->layer_count != NCA_IVFC_LAYER_COUNT) return false; 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 (integrity_meta_info->info_level_hash.level_information[i].offset >= section_size || !integrity_meta_info->info_level_hash.level_information[i].size || \
if (layer_info->offset >= section_size || !layer_info->size || !layer_info->block_size || (layer_info->offset + layer_info->size) > section_size) return false; !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; return true;

View file

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

View file

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

View file

@ -156,7 +156,7 @@ bool romfsGeneratePathFromDirectoryEntry(RomFileSystemContext *ctx, RomFileSyste
/// Generates a path string from a RomFS file entry. /// 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); 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. /// Input offset must be relative to the start of the RomFS file entry data.
/// This function shares the same limitations as ncaGenerateHierarchicalSha256Patch() / ncaGenerateHierarchicalIntegrityPatch(). /// 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); bool romfsGenerateFileEntryPatch(RomFileSystemContext *ctx, RomFileSystemFileEntry *file_entry, const void *data, u64 data_size, u64 data_offset, RomFileSystemFileEntryPatch *out);

View file

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

View file

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

View file

@ -7,6 +7,7 @@ todo:
tik: use dumped tickets when the original ones can't be found in the ES savefile? 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 encrypted nca headers / nca fs headers (don't forget nca0 please)
nca: function to write hashdata patches
pfs0: filelist generation methods pfs0: filelist generation methods
pfs0: full header aligned to 0x20 (nsp) pfs0: full header aligned to 0x20 (nsp)
@ -30,18 +31,4 @@ todo:
fwrite(titleContentInfos, 1, titleContentInfoCnt * sizeof(NcmContentInfo), content_info); fwrite(titleContentInfos, 1, titleContentInfoCnt * sizeof(NcmContentInfo), content_info);
fclose(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;
}