diff --git a/source/gamecard.c b/source/gamecard.c index aac95ba..3a34434 100644 --- a/source/gamecard.c +++ b/source/gamecard.c @@ -233,7 +233,7 @@ bool gamecardGetOffsetAndSizeFromHashFileSystemPartitionEntryByName(u8 hfs_parti mtx_lock(&g_gameCardSharedDataMutex); - if (!g_gameCardInserted || !g_gameCardInfoLoaded || !name || !*name || !out_offset || !out_size || !gamecardGetHashFileSystemPartitionIndexByType(hfs_partition_type, &hfs_partition_idx)) + if (!g_gameCardInserted || !g_gameCardInfoLoaded || !name || !*name || (!out_offset && !out_size) || !gamecardGetHashFileSystemPartitionIndexByType(hfs_partition_type, &hfs_partition_idx)) { LOGFILE("Invalid parameters!"); goto out; @@ -252,8 +252,8 @@ bool gamecardGetOffsetAndSizeFromHashFileSystemPartitionEntryByName(u8 hfs_parti if (!strncasecmp(entry_name, name, name_len)) { - *out_offset = (g_gameCardHfsPartitions[hfs_partition_idx].offset + g_gameCardHfsPartitions[hfs_partition_idx].header_size + fs_entry->offset); - *out_size = fs_entry->size; + if (out_offset) *out_offset = (g_gameCardHfsPartitions[hfs_partition_idx].offset + g_gameCardHfsPartitions[hfs_partition_idx].header_size + fs_entry->offset); + if (out_size) *out_size = fs_entry->size; ret = true; break; } diff --git a/source/main.c b/source/main.c index 4eb859f..805678c 100644 --- a/source/main.c +++ b/source/main.c @@ -23,7 +23,7 @@ #include "utils.h" #include "gamecard.h" -#include "tik.h" +#include "nca.h" #include "cert.h" int main(int argc, char *argv[]) @@ -147,7 +147,9 @@ int main(int argc, char *argv[]) if (gamecardRead(buf, (u64)0x400300, (u64)0x16F18100)) // force unaligned read that spans both storage areas { - printf("read succeeded\n"); + u32 crc = crc32Calculate(buf, (u64)0x400300); + + printf("read succeeded: %08X\n", crc); consoleUpdate(NULL); tmp_file = fopen("sdmc:/data.bin", "wb"); @@ -188,7 +190,7 @@ int main(int argc, char *argv[]) u64 cert_chain_size = 0; FsRightsId rights_id = { - .c = { 0x01, 0x00, 0x9a, 0xa0, 0x00, 0xfa, 0xa0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } // Sonic Mania + .c = { 0x01, 0x00, 0x82, 0x40, 0x0B, 0xCC, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08 } // Untitled Goose Game }; if (tikRetrieveTicketByRightsId(&tik, &rights_id, false)) @@ -209,7 +211,7 @@ int main(int argc, char *argv[]) consoleUpdate(NULL); - tikConvertPersonalizedTicketToCommonTicket(&tik); + /*tikConvertPersonalizedTicketToCommonTicket(&tik); printf("common tik generated\n"); consoleUpdate(NULL); @@ -225,7 +227,7 @@ int main(int argc, char *argv[]) printf("common tik not saved\n"); } - consoleUpdate(NULL); + consoleUpdate(NULL);*/ tik_common_blk = tikGetCommonBlockFromTicket(&tik); @@ -257,9 +259,73 @@ int main(int argc, char *argv[]) consoleUpdate(NULL); + NcaContext *nca_ctx = calloc(1, sizeof(NcaContext)); + if (nca_ctx) + { + printf("nca ctx buf succeeded\n"); + consoleUpdate(NULL); + + NcmContentStorage ncm_storage = {0}; + if (R_SUCCEEDED(ncmOpenContentStorage(&ncm_storage, NcmStorageId_SdCard))) + { + printf("ncm open storage succeeded\n"); + consoleUpdate(NULL); + + NcmContentId nca_id = { + .c = { 0x8E, 0xF9, 0x20, 0xD4, 0x5E, 0xE1, 0x9E, 0xD1, 0xD2, 0x04, 0xC4, 0xC8, 0x22, 0x50, 0x79, 0xE8 } // Untitled Goose Game + }; + + u8 nca_hash[SHA256_HASH_SIZE] = { + 0x8E, 0xF9, 0x20, 0xD4, 0x5E, 0xE1, 0x9E, 0xD1, 0xD2, 0x04, 0xC4, 0xC8, 0x22, 0x50, 0x79, 0xE8, + 0x8E, 0xF9, 0x20, 0xD4, 0x5E, 0xE1, 0x9E, 0xD1, 0xD2, 0x04, 0xC4, 0xC8, 0x22, 0x50, 0x79, 0xE8 + }; + + u8 nca_size[0x6] = { + 0x00, 0x40, 0xAD, 0x31, 0x00, 0x00 + }; + + if (ncaProcessContent(nca_ctx, &tik, NcmStorageId_SdCard, &ncm_storage, &nca_id, nca_hash, NcmContentType_Program, nca_size, 0, 0)) + { + printf("nca process succeeded\n"); + consoleUpdate(NULL); + + tmp_file = fopen("sdmc:/nca_ctx.bin", "wb"); + if (tmp_file) + { + fwrite(nca_ctx, 1, sizeof(NcaContext), tmp_file); + fclose(tmp_file); + tmp_file = NULL; + printf("nca ctx saved\n"); + } else { + printf("nca ctx not saved\n"); + } + } else { + printf("nca process failed\n"); + } + + consoleUpdate(NULL); + + ncmContentStorageClose(&ncm_storage); + } else { + printf("ncm open storage failed\n"); + } + + free(nca_ctx); + } else { + printf("nca ctx buf failed\n"); + } + + consoleUpdate(NULL); + + + while(true) + { + hidScanInput(); + if (utilsHidKeysAllDown() & KEY_A) break; + } + - utilsSleep(5); consoleExit(NULL); out: diff --git a/source/nca.c b/source/nca.c index 2014296..5e5f610 100644 --- a/source/nca.c +++ b/source/nca.c @@ -33,7 +33,9 @@ static const u8 g_nca0KeyAreaHash[SHA256_HASH_SIZE] = { /* Function prototypes. */ -static inline bool ncaCheckIfVersion0KeyAreaIsEncrypted(NcaContext *ctx); +static bool ncaCheckIfVersion0KeyAreaIsEncrypted(NcaContext *ctx); + +static void ncaInitializeAesCtrIv(u8 *out, const u8 *ctr, u64 offset); static void ncaUpdateAesCtrIv(u8 *ctr, u64 offset); static void ncaUpdateAesCtrExIv(u8 *ctr, u32 ctr_val, u64 offset); @@ -69,7 +71,7 @@ size_t aes128XtsNintendoCrypt(Aes128XtsContext *ctx, void *dst, const void *src, return i; } -bool ncaRead(NcaContext *ctx, void *out, u64 read_size, u64 offset) +bool ncaReadContent(NcaContext *ctx, void *out, u64 read_size, u64 offset) { if (!ctx || (ctx->storage_id != NcmStorageId_GameCard && !ctx->ncm_storage) || (ctx->storage_id == NcmStorageId_GameCard && !ctx->gamecard_offset) || !out || !read_size || \ offset >= ctx->size || (offset + read_size) > ctx->size) @@ -81,33 +83,23 @@ bool ncaRead(NcaContext *ctx, void *out, u64 read_size, u64 offset) Result rc = 0; bool ret = false; - if (ctx->storage_id == NcmStorageId_GameCard) + if (ctx->storage_id != NcmStorageId_GameCard) { - ret = gamecardRead(out, read_size, ctx->gamecard_offset + offset); - if (!ret) LOGFILE("Failed to read 0x%lX bytes block at offset 0x%lX from NCA \"%s\"! (gamecard)", read_size, offset, ctx->id_str); - } else { + /* Retrieve NCA data normally */ + /* This strips NAX0 crypto from SD card NCAs (not used on eMMC NCAs) */ rc = ncmContentStorageReadContentIdFile(ctx->ncm_storage, out, read_size, &(ctx->id), offset); ret = R_SUCCEEDED(rc); if (!ret) LOGFILE("Failed to read 0x%lX bytes block at offset 0x%lX from NCA \"%s\"! (0x%08X) (ncm)", read_size, offset, ctx->id_str, rc); + } else { + /* Retrieve NCA data using raw gamecard reads */ + /* Fixes NCA read issues with gamecards under HOS < 4.0.0 when using ncmContentStorageReadContentIdFile() */ + ret = gamecardRead(out, read_size, ctx->gamecard_offset + offset); + if (!ret) LOGFILE("Failed to read 0x%lX bytes block at offset 0x%lX from NCA \"%s\"! (gamecard)", read_size, offset, ctx->id_str); } return ret; } - - - - - - - - - - - - - - bool ncaDecryptKeyArea(NcaContext *ctx) { if (!ctx) @@ -202,20 +194,22 @@ bool ncaDecryptHeader(NcaContext *ctx) size_t crypt_res = 0; u64 fs_header_offset = 0; const u8 *header_key = NULL; + u8 tmp_hdr[NCA_HEADER_LENGTH] = {0}; Aes128XtsContext hdr_aes_ctx = {0}, nca0_fs_header_ctx = {0}; header_key = keysGetNcaHeaderKey(); aes128XtsContextCreate(&hdr_aes_ctx, header_key, header_key + 0x10, false); - crypt_res = aes128XtsNintendoCrypt(&hdr_aes_ctx, &(ctx->header), &(ctx->header), NCA_HEADER_LENGTH, 0, NCA_AES_XTS_SECTOR_SIZE, false); + crypt_res = aes128XtsNintendoCrypt(&hdr_aes_ctx, tmp_hdr, &(ctx->header), NCA_HEADER_LENGTH, 0, NCA_AES_XTS_SECTOR_SIZE, false); if (crypt_res != NCA_HEADER_LENGTH) { LOGFILE("Invalid output length for decrypted NCA header! (0x%X != 0x%lX)", NCA_HEADER_LENGTH, crypt_res); return false; } - magic = __builtin_bswap32(ctx->header.magic); + memcpy(&magic, tmp_hdr + 0x200, sizeof(u32)); + magic = __builtin_bswap32(magic); switch(magic) { @@ -252,7 +246,7 @@ bool ncaDecryptHeader(NcaContext *ctx) /* 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 key area from NCA0 header!"); + LOGFILE("Error decrypting NCA0 key area!"); return false; } @@ -264,7 +258,7 @@ bool ncaDecryptHeader(NcaContext *ctx) /* 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 (!ncaRead(ctx, &(ctx->header.fs_headers[i]), NCA_FS_HEADER_LENGTH, fs_header_offset)) + if (!ncaReadContent(ctx, &(ctx->header.fs_headers[i]), NCA_FS_HEADER_LENGTH, fs_header_offset)) { LOGFILE("Failed to read NCA0 FS section header #%u at offset 0x%lX!", i, fs_header_offset); return false; @@ -285,10 +279,6 @@ bool ncaDecryptHeader(NcaContext *ctx) return false; } - /* Fill additional context info */ - ctx->key_generation = ncaGetKeyGenerationValue(ctx); - ctx->rights_id_available = ncaCheckRightsIdAvailability(ctx); - return true; } @@ -302,8 +292,9 @@ bool ncaEncryptHeader(NcaContext *ctx) u32 i; size_t crypt_res = 0; + u64 fs_header_offset = 0; const u8 *header_key = NULL; - Aes128XtsContext hdr_aes_ctx = {0}; + Aes128XtsContext hdr_aes_ctx = {0}, nca0_fs_header_ctx = {0}; header_key = keysGetNcaHeaderKey(); @@ -342,7 +333,31 @@ bool ncaEncryptHeader(NcaContext *ctx) break; case NcaVersion_Nca0: - /* There's nothing else to do */ + 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 NCA0 header!"); + return false; + } + + /* 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, (fs_header_offset - 0x400) >> 9, \ + NCA_AES_XTS_SECTOR_SIZE, true); + if (crypt_res != NCA_FS_HEADER_LENGTH) + { + LOGFILE("Error decrypting NCA0 FS section header #%u!", i); + return false; + } + } + break; default: LOGFILE("Invalid NCA format version! (0x%02X)", ctx->format_version); @@ -352,6 +367,146 @@ bool ncaEncryptHeader(NcaContext *ctx) return true; } +bool ncaProcessContent(NcaContext *out, Ticket *tik, u8 storage_id, NcmContentStorage *ncm_storage, const NcmContentId *id, const u8 *hash, u8 type, const u8 *size, u8 id_offset, u8 hfs_partition_type) +{ + if (!out || !tik || (storage_id != NcmStorageId_GameCard && !ncm_storage) || !id || !hash || type > NcmContentType_DeltaFragment || !size || \ + (storage_id == NcmStorageId_GameCard && hfs_partition_type > GameCardHashFileSystemPartitionType_Secure)) + { + LOGFILE("Invalid parameters!"); + return false; + } + + /* Fill NCA context */ + out->storage_id = storage_id; + out->ncm_storage = (out->storage_id != NcmStorageId_GameCard ? ncm_storage : NULL); + + memcpy(&(out->id), id, sizeof(NcmContentId)); + utilsGenerateHexStringFromData(out->id_str, sizeof(out->id_str), out->id.c, sizeof(out->id.c)); + + memcpy(out->hash, hash, SHA256_HASH_SIZE); + utilsGenerateHexStringFromData(out->hash_str, sizeof(out->hash_str), out->hash, sizeof(out->hash)); + + out->type = type; + ncaConvertNcmContentSizeToU64(size, &(out->size)); + out->id_offset = id_offset; + + out->rights_id_available = out->dirty_header = false; + + if (out->storage_id == NcmStorageId_GameCard) + { + /* Retrieve gamecard NCA offset */ + char nca_filename[0x30] = {0}; + sprintf(nca_filename, "%s.%s", out->id_str, out->type == NcmContentType_Meta ? "cnmt.nca" : "nca"); + + if (!gamecardGetOffsetAndSizeFromHashFileSystemPartitionEntryByName(hfs_partition_type, nca_filename, &(out->gamecard_offset), NULL)) + { + LOGFILE("Error retrieving offset for \"%s\" entry in secure hash FS partition!", nca_filename); + return false; + } + } + + /* Read NCA header */ + if (!ncaReadContent(out, &(out->header), sizeof(NcaHeader), 0)) + { + LOGFILE("Failed to read NCA \"%s\" header!", out->id_str); + return false; + } + + /* Decrypt header */ + if (!ncaDecryptHeader(out)) + { + LOGFILE("Failed to decrypt NCA \"%s\" header!", out->id_str); + return false; + } + + if (out->header.content_size != out->size) + { + LOGFILE("Content size mismatch for NCA \"%s\"! (0x%lX != 0x%lX)", out->header.content_size, out->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 */ + /* This will return true if it has already been retrieved */ + if (!tikRetrieveTicketByRightsId(tik, &(out->header.rights_id), out->storage_id == NcmStorageId_GameCard)) + { + LOGFILE("Error retrieving ticket for NCA \"%s\"!", out->id_str); + return false; + } + + /* Copy decrypted titlekey */ + memcpy(out->titlekey, tik->dec_titlekey, 0x10); + } else { + /* Decrypt key area */ + if (out->format_version != NcaVersion_Nca0 && !ncaDecryptKeyArea(out)) + { + LOGFILE("Error decrypting NCA key area!"); + return false; + } + } + + /* Parse sections */ + for(u8 i = 0; i < NCA_FS_HEADER_COUNT; i++) + { + if (!out->header.fs_entries[i].enable_entry) continue; + + /* Fill section context */ + out->fs_contexts[i].nca_ctx = out; + out->fs_contexts[i].section_num = i; + out->fs_contexts[i].offset = NCA_FS_ENTRY_BLOCK_OFFSET(out->header.fs_entries[i].start_block_offset); + out->fs_contexts[i].size = (NCA_FS_ENTRY_BLOCK_OFFSET(out->header.fs_entries[i].end_block_offset) - out->fs_contexts[i].offset); + out->fs_contexts[i].section_type = NcaSectionType_Invalid; /* Placeholder */ + out->fs_contexts[i].encryption_type = (out->format_version == NcaVersion_Nca0 ? NcaEncryptionType_Nca0 : out->header.fs_headers[i].encryption_type); + out->fs_contexts[i].header = &(out->header.fs_headers[i]); + out->fs_contexts[i].use_xts = false; + + /* Determine FS section type */ + 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 = NcaSectionType_PartitionFs; + } else + 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 ? NcaSectionType_PatchRomFs : NcaSectionType_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) + { + out->fs_contexts[i].section_type = NcaSectionType_Nca0RomFs; + } + + if (out->fs_contexts[i].section_type == NcaSectionType_Invalid || out->fs_contexts[i].encryption_type <= NcaEncryptionType_None) continue; + + /* Initialize section CTR */ + ncaInitializeAesCtrIv(out->fs_contexts[i].ctr, out->fs_contexts[i].header->section_ctr, out->fs_contexts[i].offset); + + /* Initialize AES context */ + if (out->rights_id_available) + { + /* AES-128-CTR */ + 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) + { + /* AES-128-CTR */ + 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 || out->fs_contexts[i].encryption_type == NcaEncryptionType_Nca0) + { + /* AES-128-XTS */ + aes128XtsContextCreate(&(out->fs_contexts[i].xts_ctx), out->decrypted_keys[0].key, out->decrypted_keys[1].key, false); + out->fs_contexts[i].use_xts = true; + } + } + } + + return true; +} + @@ -366,7 +521,7 @@ bool ncaEncryptHeader(NcaContext *ctx) -static inline bool ncaCheckIfVersion0KeyAreaIsEncrypted(NcaContext *ctx) +static bool ncaCheckIfVersion0KeyAreaIsEncrypted(NcaContext *ctx) { if (!ctx || ctx->format_version != NcaVersion_Nca0) return false; @@ -378,6 +533,20 @@ static inline bool ncaCheckIfVersion0KeyAreaIsEncrypted(NcaContext *ctx) return true; } +static void ncaInitializeAesCtrIv(u8 *out, const u8 *ctr, u64 offset) +{ + if (!out || !ctr) return; + + offset >>= 4; + + for(u8 i = 0; i < 8; i++) + { + out[i] = ctr[0x8 - i - 1]; + out[0x10 - i - 1] = (u8)(offset & 0xFF); + offset >>= 8; + } +} + static void ncaUpdateAesCtrIv(u8 *ctr, u64 offset) { if (!ctr) return; diff --git a/source/nca.h b/source/nca.h index 7212874..7e1a964 100644 --- a/source/nca.h +++ b/source/nca.h @@ -20,6 +20,7 @@ #define __NCA_H__ #include +#include "tik.h" #define NCA_HEADER_LENGTH 0x400 #define NCA_FS_HEADER_LENGTH 0x200 @@ -35,18 +36,12 @@ #define NCA_BKTR_MAGIC 0x424B5452 /* "BKTR" */ #define NCA_FS_ENTRY_BLOCK_SIZE 0x200 -#define NCA_FS_ENTRY_BLOCK_OFFSET(x) ((x) * NCA_FS_ENTRY_BLOCK_SIZE) +#define NCA_FS_ENTRY_BLOCK_OFFSET(x) ((u64)(x) * NCA_FS_ENTRY_BLOCK_SIZE) #define NCA_AES_XTS_SECTOR_SIZE 0x200 #define NCA_IVFC_BLOCK_SIZE(x) (1 << (x)) -typedef enum { - NcaVersion_Nca0 = 0, - NcaVersion_Nca2 = 1, - NcaVersion_Nca3 = 2 -} NcaVersion; - typedef enum { NcaDistributionType_Download = 0, NcaDistributionType_GameCard = 1 @@ -118,7 +113,8 @@ typedef enum { NcaEncryptionType_None = 1, NcaEncryptionType_AesXts = 2, NcaEncryptionType_AesCtr = 3, - NcaEncryptionType_AesCtrEx = 4 + NcaEncryptionType_AesCtrEx = 4, + NcaEncryptionType_Nca0 = 5 ///< Only used to represent NCA0 FS section crypto - not actually used as a possible value for this field. } NcaEncryptionType; typedef struct { @@ -126,7 +122,7 @@ typedef struct { u64 size; } NcaHierarchicalSha256LayerInfo; -/// Used for NcaFsType_PartitionFs and NCA0 RomFS. +/// Used for NcaFsType_PartitionFs and NCA0 NcaFsType_RomFsRomFS. typedef struct { u8 master_hash[SHA256_HASH_SIZE]; u32 hash_block_size; @@ -157,7 +153,7 @@ typedef struct { typedef struct { union { struct { - ///< Used if hash_type == NcaHashType_HierarchicalSha256 (NcaFsType_PartitionFs). + ///< Used if hash_type == NcaHashType_HierarchicalSha256 (NcaFsType_PartitionFs and NCA0 NcaFsType_RomFs). NcaHierarchicalSha256 hierarchical_sha256; u8 reserved_1[0xB0]; }; @@ -241,26 +237,32 @@ typedef struct { NcaFsHeader fs_headers[4]; /// NCA FS section headers. } NcaHeader; +typedef enum { + NcaVersion_Nca0 = 0, + NcaVersion_Nca2 = 1, + NcaVersion_Nca3 = 2 +} NcaVersion; - - - - - - - +typedef enum { + NcaSectionType_PartitionFs = 0, ///< NcaFsType_PartitionFs + NcaHashType_HierarchicalSha256. + NcaSectionType_RomFs = 1, ///< NcaFsType_RomFs + NcaHashType_HierarchicalIntegrity. + NcaSectionType_PatchRomFs = 2, ///< NcaFsType_RomFs + NcaHashType_HierarchicalIntegrity + NcaEncryptionType_AesCtrEx. + NcaSectionType_Nca0RomFs = 3, ///< NcaFsType_RomFs + NcaHashType_HierarchicalSha256 + NcaVersion_Nca0. + NcaSectionType_Invalid = 4 +} NcaSectionType; typedef struct { + void *nca_ctx; ///< NcaContext. Used to perform NCA reads. + u8 section_num; u64 offset; u64 size; - u32 section_num; - NcaFsHeader *fs_header; - - - - - - u8 ctr; + u8 section_type; ///< NcaSectionType. + u8 encryption_type; ///< NcaEncryptionType. + NcaFsHeader *header; + bool use_xts; + Aes128CtrContext ctr_ctx; + Aes128XtsContext xts_ctx; + u8 ctr[0x10]; ///< Used to update the AES context IV based on the desired offset. } NcaFsContext; typedef struct { @@ -277,18 +279,24 @@ typedef struct { u8 key_generation; ///< NcaKeyGenerationOld / NcaKeyGeneration. Retrieved from the decrypted header. u8 id_offset; ///< Retrieved from NcmContentInfo. bool rights_id_available; - NcaHeader header; bool dirty_header; - NcaKey decrypted_keys[4]; + NcaHeader header; NcaFsContext fs_contexts[4]; + NcaKey decrypted_keys[4]; + u8 titlekey[0x10]; } NcaContext; -/// Reads raw encrypted data from a NCA using a NCA context with the 'storage_id', 'id', 'id_str' and 'size' elements filled beforehand. -/// If 'storage_id' == NcmStorageId_GameCard, the 'gamecard_offset' element should hold a value greater than zero. +/// Reads raw encrypted data from a NCA using an input NCA context. +/// 'storage_id', 'id', 'id_str' and 'size' elements must have been filled beforehand (e.g. using ncaProcessContent()). /// If 'storage_id' != NcmStorageId_GameCard, the 'ncm_storage' element should point to a valid NcmContentStorage instance. -bool ncaRead(NcaContext *ctx, void *out, u64 read_size, u64 offset); - +/// If 'storage_id' == NcmStorageId_GameCard, the 'gamecard_offset' element should hold a value greater than zero. +bool ncaReadContent(NcaContext *ctx, void *out, u64 read_size, u64 offset); +/// Generates a valid NCA context. +/// 'hash', 'type', 'size' and 'id_offset' elements can be retrieved from a NcmContentInfo object. +/// If the NCA holds a populated Rights ID field, and if the Ticket object pointed to by 'tik' hasn't been filled, the ticket and titlekey will be retrieved. +/// 'hfs_partition_type' is only necessary if 'storage_id' == NcmStorageId_GameCard. +bool ncaProcessContent(NcaContext *out, Ticket *tik, u8 storage_id, NcmContentStorage *ncm_storage, const NcmContentId *id, const u8 *hash, u8 type, const u8 *size, u8 id_offset, u8 hfs_partition_type); diff --git a/source/tik.c b/source/tik.c index 849a28b..06bfa71 100644 --- a/source/tik.c +++ b/source/tik.c @@ -63,7 +63,7 @@ static bool tikRetrieveTicketFromEsSaveDataByRightsId(Ticket *dst, const FsRight static TikCommonBlock *tikGetCommonBlockFromMemoryBuffer(void *data); -static bool tikGetTitleKeyFromTicketCommonBlock(void *dst, const TikCommonBlock *tik_common_blk); +static bool tikGetTitleKekEncryptedTitleKeyFromTicket(Ticket *tik); static bool tikGetTitleKekDecryptedTitleKey(void *dst, const void *src, u8 key_generation); static bool tikGetTitleKeyTypeFromRightsId(const FsRightsId *id, u8 *out); @@ -76,24 +76,27 @@ static bool tikTestKeyPairFromEticketDeviceKey(const void *e, const void *d, con bool tikRetrieveTicketByRightsId(Ticket *dst, const FsRightsId *id, bool use_gamecard) { - bool tik_retrieved = false; - TikCommonBlock *tik_common_blk = NULL; + if (!dst || !id) + { + LOGFILE("Invalid parameters!"); + return false; + } - tik_retrieved = (use_gamecard ? tikRetrieveTicketFromGameCardByRightsId(dst, id) : tikRetrieveTicketFromEsSaveDataByRightsId(dst, id)); + /* Check if this ticket has already been retrieved */ + if (dst->type > TikType_None && dst->type <= TikType_SigEcsda240 && dst->size >= TIK_MIN_SIZE && dst->size <= TIK_MAX_SIZE) + { + TikCommonBlock *tik_common_blk = tikGetCommonBlockFromTicket(dst); + if (tik_common_blk && !memcmp(tik_common_blk->rights_id.c, id->c, 0x10)) return true; + } + + bool tik_retrieved = (use_gamecard ? tikRetrieveTicketFromGameCardByRightsId(dst, id) : tikRetrieveTicketFromEsSaveDataByRightsId(dst, id)); if (!tik_retrieved) { LOGFILE("Unable to retrieve ticket data!"); return false; } - tik_common_blk = tikGetCommonBlockFromTicket(dst); - if (!tik_common_blk) - { - LOGFILE("Unable to retrieve common block from ticket!"); - return false; - } - - if (!tikGetTitleKeyFromTicketCommonBlock(dst->enc_titlekey, tik_common_blk)) + if (!tikGetTitleKekEncryptedTitleKeyFromTicket(dst)) { LOGFILE("Unable to retrieve titlekey from ticket!"); return false; @@ -101,7 +104,7 @@ bool tikRetrieveTicketByRightsId(Ticket *dst, const FsRightsId *id, bool use_gam /* Even though tickets do have a proper key_generation field, we'll just retrieve it from the rights_id field */ /* Old custom tools used to wipe the key_generation field or save it to a different offset */ - if (!tikGetTitleKekDecryptedTitleKey(dst->dec_titlekey, dst->enc_titlekey, tik_common_blk->rights_id.c[0xF])) + if (!tikGetTitleKekDecryptedTitleKey(dst->dec_titlekey, dst->enc_titlekey, id->c[0xF])) { LOGFILE("Unable to perform titlekek decryption!"); return false; @@ -371,9 +374,9 @@ static TikCommonBlock *tikGetCommonBlockFromMemoryBuffer(void *data) return tik_common_blk; } -static bool tikGetTitleKeyFromTicketCommonBlock(void *dst, const TikCommonBlock *tik_common_blk) +static bool tikGetTitleKekEncryptedTitleKeyFromTicket(Ticket *tik) { - if (!dst || !tik_common_blk) + if (!tik) { LOGFILE("Invalid parameters!"); return false; @@ -381,13 +384,21 @@ static bool tikGetTitleKeyFromTicketCommonBlock(void *dst, const TikCommonBlock size_t out_keydata_size = 0; u8 out_keydata[0x100] = {0}; + TikCommonBlock *tik_common_blk = NULL; tikEticketDeviceKeyData *eticket_devkey = NULL; + tik_common_blk = tikGetCommonBlockFromTicket(tik); + if (!tik_common_blk) + { + LOGFILE("Unable to retrieve common block from ticket!"); + return false; + } + switch(tik_common_blk->titlekey_type) { case TikTitleKeyType_Common: /* No titlekek crypto used */ - memcpy(dst, tik_common_blk->titlekey_block, 0x10); + memcpy(tik->enc_titlekey, tik_common_blk->titlekey_block, 0x10); break; case TikTitleKeyType_Personalized: /* Retrieve eTicket device key */ @@ -408,7 +419,7 @@ static bool tikGetTitleKeyFromTicketCommonBlock(void *dst, const TikCommonBlock } /* Copy decrypted titlekey */ - memcpy(dst, out_keydata, 0x10); + memcpy(tik->enc_titlekey, out_keydata, 0x10); break; default: