mirror of
https://github.com/DarkMatterCore/nxdumptool.git
synced 2024-12-23 09:02:06 +00:00
NCA process done.
This commit is contained in:
parent
cf8ab4d4ac
commit
ccf36f4963
5 changed files with 342 additions and 88 deletions
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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:
|
||||
|
|
231
source/nca.c
231
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;
|
||||
|
|
70
source/nca.h
70
source/nca.h
|
@ -20,6 +20,7 @@
|
|||
#define __NCA_H__
|
||||
|
||||
#include <switch.h>
|
||||
#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);
|
||||
|
||||
|
||||
|
||||
|
|
45
source/tik.c
45
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:
|
||||
|
|
Loading…
Reference in a new issue