mirror of
https://github.com/DarkMatterCore/nxdumptool.git
synced 2024-11-22 10:16:39 +00:00
NCA read finished.
This commit is contained in:
parent
0e5683b880
commit
76550adab8
6 changed files with 549 additions and 331 deletions
|
@ -744,7 +744,7 @@ static bool gamecardReadStorageArea(void *out, u64 read_size, u64 offset, bool l
|
|||
offset >= (g_gameCardStorageNormalAreaSize + g_gameCardStorageSecureAreaSize) || (offset + read_size) > (g_gameCardStorageNormalAreaSize + g_gameCardStorageSecureAreaSize))
|
||||
{
|
||||
LOGFILE("Invalid parameters!");
|
||||
goto out;
|
||||
goto exit;
|
||||
}
|
||||
|
||||
Result rc = 0;
|
||||
|
@ -757,7 +757,7 @@ static bool gamecardReadStorageArea(void *out, u64 read_size, u64 offset, bool l
|
|||
/* Calculate normal storage area size difference */
|
||||
u64 diff_size = (g_gameCardStorageNormalAreaSize - offset);
|
||||
|
||||
if (!gamecardReadStorageArea(out_u8, diff_size, offset, false)) goto out;
|
||||
if (!gamecardReadStorageArea(out_u8, diff_size, offset, false)) goto exit;
|
||||
|
||||
/* Adjust variables to read right from the start of the secure storage area */
|
||||
read_size -= diff_size;
|
||||
|
@ -771,7 +771,7 @@ static bool gamecardReadStorageArea(void *out, u64 read_size, u64 offset, bool l
|
|||
if (!gamecardOpenStorageArea(area))
|
||||
{
|
||||
LOGFILE("Failed to open %s storage area!", GAMECARD_STORAGE_AREA_NAME(area));
|
||||
goto out;
|
||||
goto exit;
|
||||
}
|
||||
|
||||
/* Calculate appropiate storage area offset and retrieve the right storage area pointer */
|
||||
|
@ -784,7 +784,7 @@ static bool gamecardReadStorageArea(void *out, u64 read_size, u64 offset, bool l
|
|||
if (R_FAILED(rc))
|
||||
{
|
||||
LOGFILE("fsStorageRead failed to read 0x%lX bytes at offset 0x%lX from %s storage area! (0x%08X) (aligned)", read_size, base_offset, GAMECARD_STORAGE_AREA_NAME(area), rc);
|
||||
goto out;
|
||||
goto exit;
|
||||
}
|
||||
|
||||
success = true;
|
||||
|
@ -802,7 +802,7 @@ static bool gamecardReadStorageArea(void *out, u64 read_size, u64 offset, bool l
|
|||
if (R_FAILED(rc))
|
||||
{
|
||||
LOGFILE("fsStorageRead failed to read 0x%lX bytes at offset 0x%lX from %s storage area! (0x%08X) (unaligned)", chunk_size, block_start_offset, GAMECARD_STORAGE_AREA_NAME(area), rc);
|
||||
goto out;
|
||||
goto exit;
|
||||
}
|
||||
|
||||
memcpy(out_u8, g_gameCardReadBuf + data_start_offset, out_chunk_size);
|
||||
|
@ -810,7 +810,7 @@ static bool gamecardReadStorageArea(void *out, u64 read_size, u64 offset, bool l
|
|||
success = (block_size > GAMECARD_READ_BUFFER_SIZE ? gamecardReadStorageArea(out_u8 + out_chunk_size, read_size - out_chunk_size, base_offset + out_chunk_size, false) : true);
|
||||
}
|
||||
|
||||
out:
|
||||
exit:
|
||||
if (lock) mtx_unlock(&g_gameCardSharedDataMutex);
|
||||
|
||||
return success;
|
||||
|
|
|
@ -23,6 +23,8 @@
|
|||
#include "nca.h"
|
||||
#include "utils.h"
|
||||
|
||||
#define KEYS_FILE_PATH "sdmc:/switch/prod.keys" /* Location used by Lockpick_RCM */
|
||||
|
||||
#define FS_SYSMODULE_TID (u64)0x0100000000000000
|
||||
|
||||
#define SEGMENT_TEXT BIT(0)
|
||||
|
|
|
@ -21,8 +21,6 @@
|
|||
|
||||
#include <switch/types.h>
|
||||
|
||||
#define KEYS_FILE_PATH "sdmc:/switch/prod.keys" /* Location used by Lockpick_RCM */
|
||||
|
||||
bool keysLoadNcaKeyset(void);
|
||||
|
||||
const u8 *keysGetNcaHeaderKey(void);
|
||||
|
|
|
@ -26,6 +26,8 @@
|
|||
#include "nca.h"
|
||||
#include "cert.h"
|
||||
|
||||
#include <dirent.h>
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
(void)argc;
|
||||
|
@ -70,12 +72,16 @@ int main(int argc, char *argv[])
|
|||
u32 update_version = 0;
|
||||
u64 nca_offset = 0, nca_size = 0;
|
||||
|
||||
|
||||
mkdir("sdmc:/nxdt_test", 0744);
|
||||
|
||||
|
||||
if (gamecardGetHeader(&header))
|
||||
{
|
||||
printf("header success\n");
|
||||
consoleUpdate(NULL);
|
||||
|
||||
tmp_file = fopen("sdmc:/header.bin", "wb");
|
||||
tmp_file = fopen("sdmc:/nxdt_test/header.bin", "wb");
|
||||
if (tmp_file)
|
||||
{
|
||||
fwrite(&header, 1, sizeof(GameCardHeader), tmp_file);
|
||||
|
@ -114,7 +120,7 @@ int main(int argc, char *argv[])
|
|||
printf("gamecard cert success\n");
|
||||
consoleUpdate(NULL);
|
||||
|
||||
tmp_file = fopen("sdmc:/cert.bin", "wb");
|
||||
tmp_file = fopen("sdmc:/nxdt_test/cert.bin", "wb");
|
||||
if (tmp_file)
|
||||
{
|
||||
fwrite(&cert, 1, sizeof(FsGameCardCertificate), tmp_file);
|
||||
|
@ -152,7 +158,7 @@ int main(int argc, char *argv[])
|
|||
printf("read succeeded: %08X\n", crc);
|
||||
consoleUpdate(NULL);
|
||||
|
||||
tmp_file = fopen("sdmc:/data.bin", "wb");
|
||||
tmp_file = fopen("sdmc:/nxdt_test/data.bin", "wb");
|
||||
if (tmp_file)
|
||||
{
|
||||
fwrite(buf, 1, (u64)0x400300, tmp_file);
|
||||
|
@ -196,7 +202,7 @@ int main(int argc, char *argv[])
|
|||
printf("tik succeeded\n");
|
||||
consoleUpdate(NULL);
|
||||
|
||||
tmp_file = fopen("sdmc:/tik.bin", "wb");
|
||||
tmp_file = fopen("sdmc:/nxdt_test/tik.bin", "wb");
|
||||
if (tmp_file)
|
||||
{
|
||||
fwrite(&tik, 1, sizeof(Ticket), tmp_file);
|
||||
|
@ -214,7 +220,7 @@ int main(int argc, char *argv[])
|
|||
printf("common tik generated\n");
|
||||
consoleUpdate(NULL);
|
||||
|
||||
tmp_file = fopen("sdmc:/common_tik.bin", "wb");
|
||||
tmp_file = fopen("sdmc:/nxdt_test/common_tik.bin", "wb");
|
||||
if (tmp_file)
|
||||
{
|
||||
fwrite(&tik, 1, sizeof(Ticket), tmp_file);
|
||||
|
@ -237,7 +243,7 @@ int main(int argc, char *argv[])
|
|||
printf("cert chain succeeded | size: 0x%lX\n", cert_chain_size);
|
||||
consoleUpdate(NULL);
|
||||
|
||||
tmp_file = fopen("sdmc:/chain.bin", "wb");
|
||||
tmp_file = fopen("sdmc:/nxdt_test/chain.bin", "wb");
|
||||
if (tmp_file)
|
||||
{
|
||||
fwrite(cert_chain, 1, cert_chain_size, tmp_file);
|
||||
|
@ -287,12 +293,12 @@ int main(int argc, char *argv[])
|
|||
}
|
||||
};
|
||||
|
||||
if (ncaInitializeContext(nca_ctx, &tik, NcmStorageId_SdCard, &ncm_storage, 0, &content_info))
|
||||
if (ncaInitializeContext(nca_ctx, NcmStorageId_SdCard, &ncm_storage, 0, &content_info, &tik))
|
||||
{
|
||||
printf("nca initialize ctx succeeded\n");
|
||||
consoleUpdate(NULL);
|
||||
|
||||
tmp_file = fopen("sdmc:/nca_ctx.bin", "wb");
|
||||
tmp_file = fopen("sdmc:/nxdt_test/nca_ctx.bin", "wb");
|
||||
if (tmp_file)
|
||||
{
|
||||
fwrite(nca_ctx, 1, sizeof(NcaContext), tmp_file);
|
||||
|
@ -305,10 +311,10 @@ int main(int argc, char *argv[])
|
|||
|
||||
consoleUpdate(NULL);
|
||||
|
||||
tmp_file = fopen("sdmc:/section0.bin", "wb");
|
||||
tmp_file = fopen("sdmc:/nxdt_test/section0.bin", "wb");
|
||||
if (tmp_file)
|
||||
{
|
||||
printf("nca section0 created\n");
|
||||
printf("nca section0 created: 0x%lX\n", nca_ctx->fs_contexts[0].section_size);
|
||||
consoleUpdate(NULL);
|
||||
|
||||
u64 curpos = 0;
|
||||
|
@ -322,15 +328,56 @@ int main(int argc, char *argv[])
|
|||
if (!ncaReadFsSection(&(nca_ctx->fs_contexts[0]), buf, blksize, curpos))
|
||||
{
|
||||
printf("nca read section failed\n");
|
||||
consoleUpdate(NULL);
|
||||
break;
|
||||
}
|
||||
|
||||
fwrite(buf, 1, blksize, tmp_file);
|
||||
|
||||
if (curpos == 0)
|
||||
{
|
||||
u8 cryptobuf[0x1E0] = {0};
|
||||
u64 block_size = 0, block_offset = 0;
|
||||
FILE *blktest = NULL;
|
||||
|
||||
u8 *block_data = ncaGenerateEncryptedFsSectionBlock(&(nca_ctx->fs_contexts[0]), buf + 0x809C, 0x1CE, 0x809C, &block_size, &block_offset);
|
||||
if (block_data)
|
||||
{
|
||||
printf("nca generate encrypted block success\n");
|
||||
consoleUpdate(NULL);
|
||||
|
||||
blktest = fopen("sdmc:/nxdt_test/blktest.bin", "wb");
|
||||
if (blktest)
|
||||
{
|
||||
fwrite(block_data, 1, block_size, blktest);
|
||||
fclose(blktest);
|
||||
blktest = NULL;
|
||||
}
|
||||
|
||||
free(block_data);
|
||||
}
|
||||
|
||||
if (ncaReadContentFile(nca_ctx, cryptobuf, 0x1E0, nca_ctx->fs_contexts[0].section_offset + 0x8090))
|
||||
{
|
||||
printf("nca read encrypted block success\n");
|
||||
consoleUpdate(NULL);
|
||||
|
||||
blktest = fopen("sdmc:/nxdt_test/crytobuf.bin", "wb");
|
||||
if (blktest)
|
||||
{
|
||||
fwrite(cryptobuf, 1, 0x1E0, blktest);
|
||||
fclose(blktest);
|
||||
blktest = NULL;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (curpos >= total) printf("nca read section success\n");
|
||||
|
||||
consoleUpdate(NULL);
|
||||
if (curpos >= total)
|
||||
{
|
||||
printf("nca read section success\n");
|
||||
consoleUpdate(NULL);
|
||||
}
|
||||
|
||||
fclose(tmp_file);
|
||||
tmp_file = NULL;
|
||||
|
|
720
source/nca.c
720
source/nca.c
|
@ -29,6 +29,7 @@
|
|||
/* Global variables. */
|
||||
|
||||
static u8 *g_ncaCryptoBuffer = NULL;
|
||||
static Mutex g_ncaCryptoBufferMutex = 0;
|
||||
|
||||
static const u8 g_nca0KeyAreaHash[SHA256_HASH_SIZE] = {
|
||||
0x9A, 0xBB, 0xD2, 0x11, 0x86, 0x00, 0x21, 0x9D, 0x7A, 0xDC, 0x5B, 0x43, 0x95, 0xF8, 0x4E, 0xFD,
|
||||
|
@ -37,104 +38,39 @@ static const u8 g_nca0KeyAreaHash[SHA256_HASH_SIZE] = {
|
|||
|
||||
/* Function prototypes. */
|
||||
|
||||
static bool ncaCheckIfVersion0KeyAreaIsEncrypted(NcaContext *ctx);
|
||||
static size_t aes128XtsNintendoCrypt(Aes128XtsContext *ctx, void *dst, const void *src, size_t size, u64 sector, size_t sector_size, bool encrypt); /* Not used anywhere else */
|
||||
|
||||
static bool ncaDecryptHeader(NcaContext *ctx);
|
||||
static bool ncaDecryptKeyArea(NcaContext *ctx);
|
||||
|
||||
static inline bool ncaCheckIfVersion0KeyAreaIsEncrypted(NcaContext *ctx);
|
||||
static inline u8 ncaGetKeyGenerationValue(NcaContext *ctx);
|
||||
static inline bool ncaCheckRightsIdAvailability(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);
|
||||
static inline void ncaInitializeAesCtrIv(u8 *out, const u8 *ctr, u64 offset);
|
||||
static inline void ncaUpdateAesCtrIv(u8 *ctr, u64 offset);
|
||||
static inline void ncaUpdateAesCtrExIv(u8 *ctr, u32 ctr_val, u64 offset);
|
||||
|
||||
static bool _ncaReadFsSection(NcaFsSectionContext *ctx, void *out, u64 read_size, u64 offset, bool lock);
|
||||
|
||||
bool ncaAllocateCryptoBuffer(void)
|
||||
{
|
||||
if (g_ncaCryptoBuffer) return true;
|
||||
|
||||
g_ncaCryptoBuffer = malloc(NCA_CRYPTO_BUFFER_SIZE);
|
||||
|
||||
return (g_ncaCryptoBuffer != NULL);
|
||||
mutexLock(&g_ncaCryptoBufferMutex);
|
||||
if (!g_ncaCryptoBuffer) g_ncaCryptoBuffer = malloc(NCA_CRYPTO_BUFFER_SIZE);
|
||||
bool ret = (g_ncaCryptoBuffer != NULL);
|
||||
mutexUnlock(&g_ncaCryptoBufferMutex);
|
||||
return ret;
|
||||
}
|
||||
|
||||
void ncaFreeCryptoBuffer(void)
|
||||
{
|
||||
mutexLock(&g_ncaCryptoBufferMutex);
|
||||
if (g_ncaCryptoBuffer)
|
||||
{
|
||||
free(g_ncaCryptoBuffer);
|
||||
g_ncaCryptoBuffer = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
size_t aes128XtsNintendoCrypt(Aes128XtsContext *ctx, void *dst, const void *src, size_t size, u64 sector, size_t sector_size, bool encrypt)
|
||||
{
|
||||
if (!ctx || !dst || !src || !size || !sector_size || (size % sector_size) != 0)
|
||||
{
|
||||
LOGFILE("Invalid parameters!");
|
||||
return 0;
|
||||
}
|
||||
|
||||
size_t i, crypt_res = 0;
|
||||
u64 cur_sector = sector;
|
||||
|
||||
u8 *dst_u8 = (u8*)dst;
|
||||
const u8 *src_u8 = (const u8*)src;
|
||||
|
||||
for(i = 0; i < size; i += sector_size, cur_sector++)
|
||||
{
|
||||
/* We have to force a sector reset on each new sector to actually enable Nintendo AES-XTS cipher tweak */
|
||||
aes128XtsContextResetSector(ctx, cur_sector, true);
|
||||
crypt_res = (encrypt ? aes128XtsEncrypt(ctx, dst_u8 + i, src_u8 + i, sector_size) : aes128XtsDecrypt(ctx, dst_u8 + i, src_u8 + i, sector_size));
|
||||
if (crypt_res != sector_size) break;
|
||||
}
|
||||
|
||||
return i;
|
||||
}
|
||||
|
||||
bool ncaDecryptKeyArea(NcaContext *ctx)
|
||||
{
|
||||
if (!ctx)
|
||||
{
|
||||
LOGFILE("Invalid NCA context!");
|
||||
return false;
|
||||
}
|
||||
|
||||
Result rc = 0;
|
||||
const u8 *kek_src = NULL;
|
||||
u8 key_count, tmp_kek[0x10] = {0};
|
||||
|
||||
/* Check if we're dealing with a NCA0 with a plain text key area */
|
||||
if (ctx->format_version == NcaVersion_Nca0 && !ncaCheckIfVersion0KeyAreaIsEncrypted(ctx))
|
||||
{
|
||||
memcpy(ctx->decrypted_keys, ctx->header.encrypted_keys, 0x40);
|
||||
return true;
|
||||
}
|
||||
|
||||
kek_src = keysGetKeyAreaEncryptionKeySource(ctx->header.kaek_index);
|
||||
if (!kek_src)
|
||||
{
|
||||
LOGFILE("Unable to retrieve KAEK source for index 0x%02X!", ctx->header.kaek_index);
|
||||
return false;
|
||||
}
|
||||
|
||||
rc = splCryptoGenerateAesKek(kek_src, ctx->key_generation, 0, tmp_kek);
|
||||
if (R_FAILED(rc))
|
||||
{
|
||||
LOGFILE("splCryptoGenerateAesKek failed! (0x%08X)", rc);
|
||||
return false;
|
||||
}
|
||||
|
||||
key_count = (ctx->format_version == NcaVersion_Nca0 ? 2 : 4);
|
||||
|
||||
for(u8 i = 0; i < key_count; i++)
|
||||
{
|
||||
rc = splCryptoGenerateAesKey(tmp_kek, ctx->header.encrypted_keys[i].key, ctx->decrypted_keys[i].key);
|
||||
if (R_FAILED(rc))
|
||||
{
|
||||
LOGFILE("splCryptoGenerateAesKey failed! (0x%08X)", rc);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
mutexUnlock(&g_ncaCryptoBufferMutex);
|
||||
}
|
||||
|
||||
bool ncaEncryptKeyArea(NcaContext *ctx)
|
||||
|
@ -171,104 +107,6 @@ bool ncaEncryptKeyArea(NcaContext *ctx)
|
|||
return true;
|
||||
}
|
||||
|
||||
bool ncaDecryptHeader(NcaContext *ctx)
|
||||
{
|
||||
if (!ctx || !strlen(ctx->content_id_str))
|
||||
{
|
||||
LOGFILE("Invalid NCA context!");
|
||||
return false;
|
||||
}
|
||||
|
||||
u32 i, magic = 0;
|
||||
size_t crypt_res = 0;
|
||||
u64 fs_header_offset = 0;
|
||||
const u8 *header_key = NULL;
|
||||
Aes128XtsContext hdr_aes_ctx = {0}, nca0_fs_header_ctx = {0};
|
||||
|
||||
header_key = keysGetNcaHeaderKey();
|
||||
|
||||
aes128XtsContextCreate(&hdr_aes_ctx, header_key, header_key + 0x10, false);
|
||||
|
||||
crypt_res = aes128XtsNintendoCrypt(&hdr_aes_ctx, &(ctx->header), &(ctx->header), NCA_HEADER_LENGTH, 0, NCA_AES_XTS_SECTOR_SIZE, false);
|
||||
if (crypt_res != NCA_HEADER_LENGTH)
|
||||
{
|
||||
LOGFILE("Error decrypting partial NCA \"%s\" header!", ctx->content_id_str);
|
||||
return false;
|
||||
}
|
||||
|
||||
magic = __builtin_bswap32(ctx->header.magic);
|
||||
|
||||
switch(magic)
|
||||
{
|
||||
case NCA_NCA3_MAGIC:
|
||||
ctx->format_version = NcaVersion_Nca3;
|
||||
|
||||
crypt_res = aes128XtsNintendoCrypt(&hdr_aes_ctx, ctx->header.fs_headers, ctx->header.fs_headers, NCA_FULL_HEADER_LENGTH - NCA_HEADER_LENGTH, 2, NCA_AES_XTS_SECTOR_SIZE, false);
|
||||
if (crypt_res != (NCA_FULL_HEADER_LENGTH - NCA_HEADER_LENGTH))
|
||||
{
|
||||
LOGFILE("Error decrypting NCA3 \"%s\" FS section headers!", ctx->content_id_str);
|
||||
return false;
|
||||
}
|
||||
|
||||
break;
|
||||
case NCA_NCA2_MAGIC:
|
||||
ctx->format_version = NcaVersion_Nca2;
|
||||
|
||||
for(i = 0; i < NCA_FS_HEADER_COUNT; i++)
|
||||
{
|
||||
if (!ctx->header.fs_entries[i].enable_entry) continue;
|
||||
|
||||
crypt_res = aes128XtsNintendoCrypt(&hdr_aes_ctx, &(ctx->header.fs_headers[i]), &(ctx->header.fs_headers[i]), NCA_FS_HEADER_LENGTH, 0, NCA_AES_XTS_SECTOR_SIZE, false);
|
||||
if (crypt_res != NCA_FS_HEADER_LENGTH)
|
||||
{
|
||||
LOGFILE("Error decrypting NCA2 \"%s\" FS section header #%u!", ctx->content_id_str, i);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
case NCA_NCA0_MAGIC:
|
||||
ctx->format_version = NcaVersion_Nca0;
|
||||
|
||||
/* We first need to decrypt the key area from the NCA0 header in order to access its FS section headers */
|
||||
if (!ncaDecryptKeyArea(ctx))
|
||||
{
|
||||
LOGFILE("Error decrypting NCA0 \"%s\" key area!", ctx->content_id_str);
|
||||
return false;
|
||||
}
|
||||
|
||||
aes128XtsContextCreate(&nca0_fs_header_ctx, ctx->decrypted_keys[0].key, ctx->decrypted_keys[1].key, false);
|
||||
|
||||
for(i = 0; i < NCA_FS_HEADER_COUNT; i++)
|
||||
{
|
||||
if (!ctx->header.fs_entries[i].enable_entry) continue;
|
||||
|
||||
/* FS headers are not part of NCA0 headers */
|
||||
fs_header_offset = NCA_FS_ENTRY_BLOCK_OFFSET(ctx->header.fs_entries[i].start_block_offset);
|
||||
if (!ncaReadContent(ctx, &(ctx->header.fs_headers[i]), NCA_FS_HEADER_LENGTH, fs_header_offset))
|
||||
{
|
||||
LOGFILE("Failed to read NCA0 \"%s\" FS section header #%u at offset 0x%lX!", ctx->content_id_str, i, fs_header_offset);
|
||||
return false;
|
||||
}
|
||||
|
||||
crypt_res = aes128XtsNintendoCrypt(&nca0_fs_header_ctx, &(ctx->header.fs_headers[i]), &(ctx->header.fs_headers[i]), NCA_FS_HEADER_LENGTH, \
|
||||
NCA_NCA0_FS_HEADER_AES_XTS_SECTOR(fs_header_offset), NCA_AES_XTS_SECTOR_SIZE, false);
|
||||
if (crypt_res != NCA_FS_HEADER_LENGTH)
|
||||
{
|
||||
LOGFILE("Error decrypting NCA0 \"%s\" FS section header #%u!", ctx->content_id_str, i);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
default:
|
||||
LOGFILE("Invalid NCA \"%s\" magic word! Wrong header key? (0x%08X)", ctx->content_id_str, magic);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ncaEncryptHeader(NcaContext *ctx)
|
||||
{
|
||||
if (!ctx || !strlen(ctx->content_id_str))
|
||||
|
@ -347,7 +185,7 @@ bool ncaEncryptHeader(NcaContext *ctx)
|
|||
return true;
|
||||
}
|
||||
|
||||
bool ncaInitializeContext(NcaContext *out, Ticket *tik, u8 storage_id, NcmContentStorage *ncm_storage, u8 hfs_partition_type, const NcmPackagedContentInfo *content_info)
|
||||
bool ncaInitializeContext(NcaContext *out, u8 storage_id, NcmContentStorage *ncm_storage, u8 hfs_partition_type, const NcmPackagedContentInfo *content_info, Ticket *tik)
|
||||
{
|
||||
if (!out || !tik || (storage_id != NcmStorageId_GameCard && !ncm_storage) || (storage_id == NcmStorageId_GameCard && hfs_partition_type > GameCardHashFileSystemPartitionType_Secure) || \
|
||||
!content_info || content_info->info.content_type > NcmContentType_DeltaFragment)
|
||||
|
@ -392,7 +230,7 @@ bool ncaInitializeContext(NcaContext *out, Ticket *tik, u8 storage_id, NcmConten
|
|||
}
|
||||
|
||||
/* Read NCA header */
|
||||
if (!ncaReadContent(out, &(out->header), sizeof(NcaHeader), 0))
|
||||
if (!ncaReadContentFile(out, &(out->header), sizeof(NcaHeader), 0))
|
||||
{
|
||||
LOGFILE("Failed to read NCA \"%s\" header!", out->content_id_str);
|
||||
return false;
|
||||
|
@ -446,9 +284,14 @@ bool ncaInitializeContext(NcaContext *out, Ticket *tik, u8 storage_id, NcmConten
|
|||
out->fs_contexts[i].section_num = i;
|
||||
out->fs_contexts[i].section_offset = NCA_FS_ENTRY_BLOCK_OFFSET(out->header.fs_entries[i].start_block_offset);
|
||||
out->fs_contexts[i].section_size = (NCA_FS_ENTRY_BLOCK_OFFSET(out->header.fs_entries[i].end_block_offset) - out->fs_contexts[i].section_offset);
|
||||
out->fs_contexts[i].section_type = NcaSectionType_Invalid; /* Placeholder */
|
||||
out->fs_contexts[i].section_type = NcaFsSectionType_Invalid; /* Placeholder */
|
||||
out->fs_contexts[i].header = &(out->header.fs_headers[i]);
|
||||
|
||||
memset(out->fs_contexts[i].ctr, 0, sizeof(out->fs_contexts[i].ctr));
|
||||
memset(&(out->fs_contexts[i].ctr_ctx), 0, sizeof(Aes128CtrContext));
|
||||
memset(&(out->fs_contexts[i].xts_decrypt_ctx), 0, sizeof(Aes128XtsContext));
|
||||
memset(&(out->fs_contexts[i].xts_encrypt_ctx), 0, sizeof(Aes128XtsContext));
|
||||
|
||||
/* Determine encryption type */
|
||||
out->fs_contexts[i].encryption_type = (out->format_version == NcaVersion_Nca0 ? NcaEncryptionType_Nca0 : out->header.fs_headers[i].encryption_type);
|
||||
if (out->fs_contexts[i].encryption_type == NcaEncryptionType_Auto)
|
||||
|
@ -473,19 +316,19 @@ bool ncaInitializeContext(NcaContext *out, Ticket *tik, u8 storage_id, NcmConten
|
|||
/* 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;
|
||||
out->fs_contexts[i].section_type = NcaFsSectionType_PartitionFs;
|
||||
} else
|
||||
if (out->fs_contexts[i].header->fs_type == NcaFsType_RomFs && out->fs_contexts[i].header->hash_type == NcaHashType_HierarchicalIntegrity)
|
||||
{
|
||||
out->fs_contexts[i].section_type = (out->fs_contexts[i].encryption_type == NcaEncryptionType_AesCtrEx ? NcaSectionType_PatchRomFs : NcaSectionType_RomFs);
|
||||
out->fs_contexts[i].section_type = (out->fs_contexts[i].encryption_type == NcaEncryptionType_AesCtrEx ? NcaFsSectionType_PatchRomFs : NcaFsSectionType_RomFs);
|
||||
} else
|
||||
if (out->fs_contexts[i].header->fs_type == NcaFsType_RomFs && out->fs_contexts[i].header->hash_type == NcaHashType_HierarchicalSha256 && out->format_version == NcaVersion_Nca0)
|
||||
{
|
||||
out->fs_contexts[i].section_type = NcaSectionType_Nca0RomFs;
|
||||
out->fs_contexts[i].section_type = NcaFsSectionType_Nca0RomFs;
|
||||
}
|
||||
|
||||
/* Check if we're dealing with an invalid section type value */
|
||||
if (out->fs_contexts[i].section_type >= NcaSectionType_Invalid) continue;
|
||||
if (out->fs_contexts[i].section_type >= NcaFsSectionType_Invalid) continue;
|
||||
|
||||
/* Initialize crypto related fields */
|
||||
if (out->fs_contexts[i].encryption_type > NcaEncryptionType_None && out->fs_contexts[i].encryption_type <= NcaEncryptionType_Nca0)
|
||||
|
@ -504,20 +347,18 @@ bool ncaInitializeContext(NcaContext *out, Ticket *tik, u8 storage_id, NcmConten
|
|||
} else
|
||||
if (out->fs_contexts[i].encryption_type == NcaEncryptionType_AesXts || out->fs_contexts[i].encryption_type == NcaEncryptionType_Nca0)
|
||||
{
|
||||
aes128XtsContextCreate(&(out->fs_contexts[i].xts_ctx), out->decrypted_keys[0].key, out->decrypted_keys[1].key, false);
|
||||
/* We need to create two different contexts: one for decryption and another one for encryption */
|
||||
aes128XtsContextCreate(&(out->fs_contexts[i].xts_decrypt_ctx), out->decrypted_keys[0].key, out->decrypted_keys[1].key, false);
|
||||
aes128XtsContextCreate(&(out->fs_contexts[i].xts_encrypt_ctx), out->decrypted_keys[0].key, out->decrypted_keys[1].key, true);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
memset(out->fs_contexts[i].ctr, 0, sizeof(out->fs_contexts[i].ctr));
|
||||
memset(&(out->fs_contexts[i].ctr_ctx), 0, sizeof(Aes128CtrContext));
|
||||
memset(&(out->fs_contexts[i].xts_ctx), 0, sizeof(Aes128XtsContext));
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ncaReadContent(NcaContext *ctx, void *out, u64 read_size, u64 offset)
|
||||
bool ncaReadContentFile(NcaContext *ctx, void *out, u64 read_size, u64 offset)
|
||||
{
|
||||
if (!ctx || !strlen(ctx->content_id_str) || (ctx->storage_id != NcmStorageId_GameCard && !ctx->ncm_storage) || (ctx->storage_id == NcmStorageId_GameCard && !ctx->gamecard_offset) || !out || \
|
||||
!read_size || offset >= ctx->content_size || (offset + read_size) > ctx->content_size)
|
||||
|
@ -548,113 +389,316 @@ bool ncaReadContent(NcaContext *ctx, void *out, u64 read_size, u64 offset)
|
|||
|
||||
bool ncaReadFsSection(NcaFsSectionContext *ctx, void *out, u64 read_size, u64 offset)
|
||||
{
|
||||
if (!g_ncaCryptoBuffer || !ctx || !ctx->nca_ctx || ctx->section_num >= NCA_FS_HEADER_COUNT || ctx->section_offset < NCA_FULL_HEADER_LENGTH || ctx->section_type >= NcaSectionType_Invalid || \
|
||||
ctx->encryption_type == NcaEncryptionType_Auto || ctx->encryption_type > NcaEncryptionType_Nca0 || !ctx->header || !out || !read_size || offset >= ctx->section_size || \
|
||||
(offset + read_size) > ctx->section_size)
|
||||
return _ncaReadFsSection(ctx, out, read_size, offset, true);
|
||||
}
|
||||
|
||||
void *ncaGenerateEncryptedFsSectionBlock(NcaFsSectionContext *ctx, void *data, u64 data_size, u64 data_offset, u64 *out_block_size, u64 *out_block_offset)
|
||||
{
|
||||
mutexLock(&g_ncaCryptoBufferMutex);
|
||||
|
||||
u8 *out = NULL;
|
||||
bool success = false;
|
||||
|
||||
if (!g_ncaCryptoBuffer || !ctx || !ctx->nca_ctx || ctx->section_num >= NCA_FS_HEADER_COUNT || ctx->section_offset < NCA_FULL_HEADER_LENGTH || ctx->section_type >= NcaFsSectionType_Invalid || \
|
||||
ctx->encryption_type == NcaEncryptionType_Auto || ctx->encryption_type > NcaEncryptionType_Nca0 || !ctx->header || !data || !data_size || data_offset >= ctx->section_size || \
|
||||
(data_offset + data_size) > ctx->section_size || !out_block_size || !out_block_offset)
|
||||
{
|
||||
LOGFILE("Invalid NCA FS section header parameters!");
|
||||
return false;
|
||||
goto exit;
|
||||
}
|
||||
|
||||
size_t crypt_res = 0;
|
||||
u64 sector_num = 0;
|
||||
|
||||
NcaContext *nca_ctx = (NcaContext*)ctx->nca_ctx;
|
||||
u64 content_offset = (ctx->section_offset + offset);
|
||||
u64 content_offset = (ctx->section_offset + data_offset);
|
||||
|
||||
u64 block_start_offset = 0, block_end_offset = 0, block_size = 0;
|
||||
u64 plain_chunk_offset = 0;
|
||||
|
||||
if (!strlen(nca_ctx->content_id_str) || (nca_ctx->storage_id != NcmStorageId_GameCard && !nca_ctx->ncm_storage) || (nca_ctx->storage_id == NcmStorageId_GameCard && !nca_ctx->gamecard_offset) || \
|
||||
content_offset >= nca_ctx->content_size || (content_offset + read_size) > nca_ctx->content_size)
|
||||
content_offset >= nca_ctx->content_size || (content_offset + data_size) > nca_ctx->content_size)
|
||||
{
|
||||
LOGFILE("Invalid NCA header parameters!");
|
||||
return false;
|
||||
goto exit;
|
||||
}
|
||||
|
||||
/* Read data right away if we're dealing with a FS section with no crypto */
|
||||
if (ctx->encryption_type == NcaEncryptionType_None) return ncaReadContent(nca_ctx, out, read_size, content_offset);
|
||||
|
||||
/* Calculate offsets and block sizes */
|
||||
size_t crypt_res = 0;
|
||||
u64 block_start_offset = 0, block_end_offset = 0, block_size = 0;
|
||||
u64 chunk_size = 0, out_chunk_size = 0;
|
||||
u64 data_start_offset = 0, sector_num = 0;
|
||||
|
||||
switch(ctx->encryption_type)
|
||||
/* Optimization for blocks from plaintext FS sections or blocks that are aligned to the AES-CTR / AES-XTS sector size */
|
||||
if (ctx->encryption_type == NcaEncryptionType_None || \
|
||||
((ctx->encryption_type == NcaEncryptionType_AesXts || ctx->encryption_type == NcaEncryptionType_Nca0) && !(content_offset % NCA_AES_XTS_SECTOR_SIZE) && !(data_size % NCA_AES_XTS_SECTOR_SIZE)) || \
|
||||
((ctx->encryption_type == NcaEncryptionType_AesCtr || ctx->encryption_type == NcaEncryptionType_AesCtrEx) && !(content_offset % AES_BLOCK_SIZE) && !(data_size % AES_BLOCK_SIZE)))
|
||||
{
|
||||
case NcaEncryptionType_AesXts:
|
||||
case NcaEncryptionType_Nca0:
|
||||
block_start_offset = ROUND_DOWN(content_offset, NCA_AES_XTS_SECTOR_SIZE);
|
||||
block_end_offset = ROUND_UP(content_offset + read_size, NCA_AES_XTS_SECTOR_SIZE);
|
||||
sector_num = (ctx->encryption_type == NcaEncryptionType_AesXts ? offset : (content_offset - NCA_HEADER_LENGTH));
|
||||
sector_num /= NCA_AES_XTS_SECTOR_SIZE;
|
||||
break;
|
||||
case NcaEncryptionType_AesCtr:
|
||||
case NcaEncryptionType_AesCtrEx: /* This function is only supposed to be used on Patch RomFS sections when *not* reading BKTR subsections */
|
||||
block_start_offset = ROUND_DOWN(content_offset, AES_BLOCK_SIZE);
|
||||
block_end_offset = ROUND_UP(content_offset + read_size, AES_BLOCK_SIZE);
|
||||
ncaUpdateAesCtrIv(ctx->ctr, block_start_offset);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
block_size = (block_end_offset - block_start_offset);
|
||||
chunk_size = (block_size > NCA_CRYPTO_BUFFER_SIZE ? NCA_CRYPTO_BUFFER_SIZE : block_size);
|
||||
|
||||
data_start_offset = (content_offset - block_start_offset);
|
||||
out_chunk_size = (block_size > NCA_CRYPTO_BUFFER_SIZE ? (NCA_CRYPTO_BUFFER_SIZE - data_start_offset) : read_size);
|
||||
|
||||
/* Read data */
|
||||
if (!ncaReadContent(nca_ctx, g_ncaCryptoBuffer, chunk_size, block_start_offset))
|
||||
{
|
||||
LOGFILE("Failed to read 0x%lX bytes encrypted data block at offset 0x%lX from NCA \"%s\" FS section #%u!", chunk_size, block_start_offset, nca_ctx->content_id_str, ctx->section_num);
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Decrypt data */
|
||||
switch(ctx->encryption_type)
|
||||
{
|
||||
case NcaEncryptionType_AesXts:
|
||||
case NcaEncryptionType_Nca0:
|
||||
crypt_res = aes128XtsNintendoCrypt(&(ctx->xts_ctx), g_ncaCryptoBuffer, g_ncaCryptoBuffer, chunk_size, sector_num, NCA_AES_XTS_SECTOR_SIZE, false);
|
||||
if (crypt_res != chunk_size)
|
||||
/* Allocate memory */
|
||||
out = malloc(data_size);
|
||||
if (!out)
|
||||
{
|
||||
LOGFILE("Unable to allocate 0x%lX bytes buffer! (aligned)", data_size);
|
||||
goto exit;
|
||||
}
|
||||
|
||||
/* Copy data */
|
||||
memcpy(out, data, data_size);
|
||||
|
||||
/* Encrypt data */
|
||||
if (ctx->encryption_type == NcaEncryptionType_AesXts || ctx->encryption_type == NcaEncryptionType_Nca0)
|
||||
{
|
||||
sector_num = ((ctx->encryption_type == NcaEncryptionType_AesXts ? data_offset : (content_offset - NCA_HEADER_LENGTH)) / NCA_AES_XTS_SECTOR_SIZE);
|
||||
|
||||
crypt_res = aes128XtsNintendoCrypt(&(ctx->xts_encrypt_ctx), out, out, data_size, sector_num, NCA_AES_XTS_SECTOR_SIZE, true);
|
||||
if (crypt_res != data_size)
|
||||
{
|
||||
LOGFILE("Failed to decrypt 0x%lX bytes data block at offset 0x%lX from NCA \"%s\" FS section #%u!", chunk_size, block_start_offset, nca_ctx->content_id_str, ctx->section_num);
|
||||
LOGFILE("Failed to AES-XTS encrypt 0x%lX bytes data block at offset 0x%lX from NCA \"%s\" FS section #%u! (aligned)", data_size, content_offset, nca_ctx->content_id_str, ctx->section_num);
|
||||
goto exit;
|
||||
}
|
||||
} else
|
||||
if (ctx->encryption_type == NcaEncryptionType_AesCtr || ctx->encryption_type == NcaEncryptionType_AesCtrEx)
|
||||
{
|
||||
ncaUpdateAesCtrIv(ctx->ctr, content_offset);
|
||||
aes128CtrContextResetCtr(&(ctx->ctr_ctx), ctx->ctr);
|
||||
aes128CtrCrypt(&(ctx->ctr_ctx), out, out, data_size);
|
||||
}
|
||||
|
||||
*out_block_size = data_size;
|
||||
*out_block_offset = content_offset;
|
||||
|
||||
success = true;
|
||||
goto exit;
|
||||
}
|
||||
|
||||
/* Calculate block offsets and size */
|
||||
block_start_offset = ROUND_DOWN(data_offset, (ctx->encryption_type == NcaEncryptionType_AesXts || ctx->encryption_type == NcaEncryptionType_Nca0) ? NCA_AES_XTS_SECTOR_SIZE : AES_BLOCK_SIZE);
|
||||
block_end_offset = ROUND_UP(data_offset + data_size, (ctx->encryption_type == NcaEncryptionType_AesXts || ctx->encryption_type == NcaEncryptionType_Nca0) ? NCA_AES_XTS_SECTOR_SIZE : AES_BLOCK_SIZE);
|
||||
block_size = (block_end_offset - block_start_offset);
|
||||
|
||||
plain_chunk_offset = (data_offset - block_start_offset);
|
||||
content_offset = (ctx->section_offset + block_start_offset);
|
||||
|
||||
/* Allocate memory */
|
||||
out = malloc(block_size);
|
||||
if (!out)
|
||||
{
|
||||
LOGFILE("Unable to allocate 0x%lX bytes buffer! (unaligned)", block_size);
|
||||
goto exit;
|
||||
}
|
||||
|
||||
/* Read decrypted data using aligned offset and size */
|
||||
if (!_ncaReadFsSection(ctx, out, block_size, block_start_offset, false))
|
||||
{
|
||||
LOGFILE("Failed to read decrypted NCA \"%s\" FS section #%u data block!", nca_ctx->content_id_str, ctx->section_num);
|
||||
goto exit;
|
||||
}
|
||||
|
||||
/* Replace plaintext data */
|
||||
memcpy(out + plain_chunk_offset, data, data_size);
|
||||
|
||||
/* Reencrypt data */
|
||||
if (ctx->encryption_type == NcaEncryptionType_AesXts || ctx->encryption_type == NcaEncryptionType_Nca0)
|
||||
{
|
||||
sector_num = ((ctx->encryption_type == NcaEncryptionType_AesXts ? block_start_offset : (content_offset - NCA_HEADER_LENGTH)) / NCA_AES_XTS_SECTOR_SIZE);
|
||||
|
||||
crypt_res = aes128XtsNintendoCrypt(&(ctx->xts_encrypt_ctx), out, out, block_size, sector_num, NCA_AES_XTS_SECTOR_SIZE, true);
|
||||
if (crypt_res != block_size)
|
||||
{
|
||||
LOGFILE("Failed to AES-XTS encrypt 0x%lX bytes data block at offset 0x%lX from NCA \"%s\" FS section #%u! (aligned)", block_size, content_offset, nca_ctx->content_id_str, ctx->section_num);
|
||||
goto exit;
|
||||
}
|
||||
} else
|
||||
if (ctx->encryption_type == NcaEncryptionType_AesCtr || ctx->encryption_type == NcaEncryptionType_AesCtrEx)
|
||||
{
|
||||
ncaUpdateAesCtrIv(ctx->ctr, content_offset);
|
||||
aes128CtrContextResetCtr(&(ctx->ctr_ctx), ctx->ctr);
|
||||
aes128CtrCrypt(&(ctx->ctr_ctx), out, out, block_size);
|
||||
}
|
||||
|
||||
*out_block_size = block_size;
|
||||
*out_block_offset = content_offset;
|
||||
|
||||
success = true;
|
||||
|
||||
exit:
|
||||
mutexUnlock(&g_ncaCryptoBufferMutex);
|
||||
|
||||
if (!success && out)
|
||||
{
|
||||
free(out);
|
||||
out = NULL;
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
static size_t aes128XtsNintendoCrypt(Aes128XtsContext *ctx, void *dst, const void *src, size_t size, u64 sector, size_t sector_size, bool encrypt)
|
||||
{
|
||||
if (!ctx || !dst || !src || !size || !sector_size || (size % sector_size) != 0)
|
||||
{
|
||||
LOGFILE("Invalid parameters!");
|
||||
return 0;
|
||||
}
|
||||
|
||||
size_t i, crypt_res = 0;
|
||||
u64 cur_sector = sector;
|
||||
|
||||
u8 *dst_u8 = (u8*)dst;
|
||||
const u8 *src_u8 = (const u8*)src;
|
||||
|
||||
for(i = 0; i < size; i += sector_size, cur_sector++)
|
||||
{
|
||||
/* We have to force a sector reset on each new sector to actually enable Nintendo AES-XTS cipher tweak */
|
||||
aes128XtsContextResetSector(ctx, cur_sector, true);
|
||||
crypt_res = (encrypt ? aes128XtsEncrypt(ctx, dst_u8 + i, src_u8 + i, sector_size) : aes128XtsDecrypt(ctx, dst_u8 + i, src_u8 + i, sector_size));
|
||||
if (crypt_res != sector_size) break;
|
||||
}
|
||||
|
||||
return i;
|
||||
}
|
||||
|
||||
static bool ncaDecryptHeader(NcaContext *ctx)
|
||||
{
|
||||
if (!ctx || !strlen(ctx->content_id_str))
|
||||
{
|
||||
LOGFILE("Invalid NCA context!");
|
||||
return false;
|
||||
}
|
||||
|
||||
u32 i, magic = 0;
|
||||
size_t crypt_res = 0;
|
||||
u64 fs_header_offset = 0;
|
||||
const u8 *header_key = NULL;
|
||||
Aes128XtsContext hdr_aes_ctx = {0}, nca0_fs_header_ctx = {0};
|
||||
|
||||
header_key = keysGetNcaHeaderKey();
|
||||
|
||||
aes128XtsContextCreate(&hdr_aes_ctx, header_key, header_key + 0x10, false);
|
||||
|
||||
crypt_res = aes128XtsNintendoCrypt(&hdr_aes_ctx, &(ctx->header), &(ctx->header), NCA_HEADER_LENGTH, 0, NCA_AES_XTS_SECTOR_SIZE, false);
|
||||
if (crypt_res != NCA_HEADER_LENGTH)
|
||||
{
|
||||
LOGFILE("Error decrypting partial NCA \"%s\" header!", ctx->content_id_str);
|
||||
return false;
|
||||
}
|
||||
|
||||
magic = __builtin_bswap32(ctx->header.magic);
|
||||
|
||||
switch(magic)
|
||||
{
|
||||
case NCA_NCA3_MAGIC:
|
||||
ctx->format_version = NcaVersion_Nca3;
|
||||
|
||||
crypt_res = aes128XtsNintendoCrypt(&hdr_aes_ctx, ctx->header.fs_headers, ctx->header.fs_headers, NCA_FULL_HEADER_LENGTH - NCA_HEADER_LENGTH, 2, NCA_AES_XTS_SECTOR_SIZE, false);
|
||||
if (crypt_res != (NCA_FULL_HEADER_LENGTH - NCA_HEADER_LENGTH))
|
||||
{
|
||||
LOGFILE("Error decrypting NCA3 \"%s\" FS section headers!", ctx->content_id_str);
|
||||
return false;
|
||||
}
|
||||
|
||||
break;
|
||||
case NcaEncryptionType_AesCtr:
|
||||
case NcaEncryptionType_AesCtrEx: /* This function is only supposed to be used on Patch RomFS sections when *not* reading BKTR subsections */
|
||||
aes128CtrContextResetCtr(&(ctx->ctr_ctx), ctx->ctr);
|
||||
aes128CtrCrypt(&(ctx->ctr_ctx), g_ncaCryptoBuffer, g_ncaCryptoBuffer, chunk_size);
|
||||
case NCA_NCA2_MAGIC:
|
||||
ctx->format_version = NcaVersion_Nca2;
|
||||
|
||||
for(i = 0; i < NCA_FS_HEADER_COUNT; i++)
|
||||
{
|
||||
if (!ctx->header.fs_entries[i].enable_entry) continue;
|
||||
|
||||
crypt_res = aes128XtsNintendoCrypt(&hdr_aes_ctx, &(ctx->header.fs_headers[i]), &(ctx->header.fs_headers[i]), NCA_FS_HEADER_LENGTH, 0, NCA_AES_XTS_SECTOR_SIZE, false);
|
||||
if (crypt_res != NCA_FS_HEADER_LENGTH)
|
||||
{
|
||||
LOGFILE("Error decrypting NCA2 \"%s\" FS section header #%u!", ctx->content_id_str, i);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
case NCA_NCA0_MAGIC:
|
||||
ctx->format_version = NcaVersion_Nca0;
|
||||
|
||||
/* We first need to decrypt the key area from the NCA0 header in order to access its FS section headers */
|
||||
if (!ncaDecryptKeyArea(ctx))
|
||||
{
|
||||
LOGFILE("Error decrypting NCA0 \"%s\" key area!", ctx->content_id_str);
|
||||
return false;
|
||||
}
|
||||
|
||||
aes128XtsContextCreate(&nca0_fs_header_ctx, ctx->decrypted_keys[0].key, ctx->decrypted_keys[1].key, false);
|
||||
|
||||
for(i = 0; i < NCA_FS_HEADER_COUNT; i++)
|
||||
{
|
||||
if (!ctx->header.fs_entries[i].enable_entry) continue;
|
||||
|
||||
/* FS headers are not part of NCA0 headers */
|
||||
fs_header_offset = NCA_FS_ENTRY_BLOCK_OFFSET(ctx->header.fs_entries[i].start_block_offset);
|
||||
if (!ncaReadContentFile(ctx, &(ctx->header.fs_headers[i]), NCA_FS_HEADER_LENGTH, fs_header_offset))
|
||||
{
|
||||
LOGFILE("Failed to read NCA0 \"%s\" FS section header #%u at offset 0x%lX!", ctx->content_id_str, i, fs_header_offset);
|
||||
return false;
|
||||
}
|
||||
|
||||
crypt_res = aes128XtsNintendoCrypt(&nca0_fs_header_ctx, &(ctx->header.fs_headers[i]), &(ctx->header.fs_headers[i]), NCA_FS_HEADER_LENGTH, \
|
||||
NCA_NCA0_FS_HEADER_AES_XTS_SECTOR(fs_header_offset), NCA_AES_XTS_SECTOR_SIZE, false);
|
||||
if (crypt_res != NCA_FS_HEADER_LENGTH)
|
||||
{
|
||||
LOGFILE("Error decrypting NCA0 \"%s\" FS section header #%u!", ctx->content_id_str, i);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
LOGFILE("Invalid NCA \"%s\" magic word! Wrong header key? (0x%08X)", ctx->content_id_str, magic);
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Copy decrypted data */
|
||||
memcpy(out, g_ncaCryptoBuffer + data_start_offset, out_chunk_size);
|
||||
|
||||
if (block_size > NCA_CRYPTO_BUFFER_SIZE) return ncaReadFsSection(ctx, (u8*)out + out_chunk_size, read_size - out_chunk_size, offset + out_chunk_size);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool ncaDecryptKeyArea(NcaContext *ctx)
|
||||
{
|
||||
if (!ctx)
|
||||
{
|
||||
LOGFILE("Invalid NCA context!");
|
||||
return false;
|
||||
}
|
||||
|
||||
Result rc = 0;
|
||||
const u8 *kek_src = NULL;
|
||||
u8 key_count, tmp_kek[0x10] = {0};
|
||||
|
||||
/* Check if we're dealing with a NCA0 with a plain text key area */
|
||||
if (ctx->format_version == NcaVersion_Nca0 && !ncaCheckIfVersion0KeyAreaIsEncrypted(ctx))
|
||||
{
|
||||
memcpy(ctx->decrypted_keys, ctx->header.encrypted_keys, 0x40);
|
||||
return true;
|
||||
}
|
||||
|
||||
kek_src = keysGetKeyAreaEncryptionKeySource(ctx->header.kaek_index);
|
||||
if (!kek_src)
|
||||
{
|
||||
LOGFILE("Unable to retrieve KAEK source for index 0x%02X!", ctx->header.kaek_index);
|
||||
return false;
|
||||
}
|
||||
|
||||
rc = splCryptoGenerateAesKek(kek_src, ctx->key_generation, 0, tmp_kek);
|
||||
if (R_FAILED(rc))
|
||||
{
|
||||
LOGFILE("splCryptoGenerateAesKek failed! (0x%08X)", rc);
|
||||
return false;
|
||||
}
|
||||
|
||||
key_count = (ctx->format_version == NcaVersion_Nca0 ? 2 : 4);
|
||||
|
||||
for(u8 i = 0; i < key_count; i++)
|
||||
{
|
||||
rc = splCryptoGenerateAesKey(tmp_kek, ctx->header.encrypted_keys[i].key, ctx->decrypted_keys[i].key);
|
||||
if (R_FAILED(rc))
|
||||
{
|
||||
LOGFILE("splCryptoGenerateAesKey failed! (0x%08X)", rc);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
static bool ncaCheckIfVersion0KeyAreaIsEncrypted(NcaContext *ctx)
|
||||
static inline bool ncaCheckIfVersion0KeyAreaIsEncrypted(NcaContext *ctx)
|
||||
{
|
||||
if (!ctx || ctx->format_version != NcaVersion_Nca0) return false;
|
||||
|
||||
|
@ -690,7 +734,7 @@ static inline bool ncaCheckRightsIdAvailability(NcaContext *ctx)
|
|||
return rights_id_available;
|
||||
}
|
||||
|
||||
static void ncaInitializeAesCtrIv(u8 *out, const u8 *ctr, u64 offset)
|
||||
static inline void ncaInitializeAesCtrIv(u8 *out, const u8 *ctr, u64 offset)
|
||||
{
|
||||
if (!out || !ctr) return;
|
||||
|
||||
|
@ -704,7 +748,7 @@ static void ncaInitializeAesCtrIv(u8 *out, const u8 *ctr, u64 offset)
|
|||
}
|
||||
}
|
||||
|
||||
static void ncaUpdateAesCtrIv(u8 *ctr, u64 offset)
|
||||
static inline void ncaUpdateAesCtrIv(u8 *ctr, u64 offset)
|
||||
{
|
||||
if (!ctr) return;
|
||||
|
||||
|
@ -717,7 +761,7 @@ static void ncaUpdateAesCtrIv(u8 *ctr, u64 offset)
|
|||
}
|
||||
}
|
||||
|
||||
static void ncaUpdateAesCtrExIv(u8 *ctr, u32 ctr_val, u64 offset)
|
||||
static inline void ncaUpdateAesCtrExIv(u8 *ctr, u32 ctr_val, u64 offset)
|
||||
{
|
||||
if (!ctr) return;
|
||||
|
||||
|
@ -735,3 +779,121 @@ static void ncaUpdateAesCtrExIv(u8 *ctr, u32 ctr_val, u64 offset)
|
|||
ctr_val >>= 8;
|
||||
}
|
||||
}
|
||||
|
||||
static bool _ncaReadFsSection(NcaFsSectionContext *ctx, void *out, u64 read_size, u64 offset, bool lock)
|
||||
{
|
||||
if (lock) mutexLock(&g_ncaCryptoBufferMutex);
|
||||
|
||||
bool ret = false;
|
||||
|
||||
if (!g_ncaCryptoBuffer || !ctx || !ctx->nca_ctx || ctx->section_num >= NCA_FS_HEADER_COUNT || ctx->section_offset < NCA_FULL_HEADER_LENGTH || ctx->section_type >= NcaFsSectionType_Invalid || \
|
||||
ctx->encryption_type == NcaEncryptionType_Auto || ctx->encryption_type > NcaEncryptionType_Nca0 || !ctx->header || !out || !read_size || offset >= ctx->section_size || \
|
||||
(offset + read_size) > ctx->section_size)
|
||||
{
|
||||
LOGFILE("Invalid NCA FS section header parameters!");
|
||||
goto exit;
|
||||
}
|
||||
|
||||
size_t crypt_res = 0;
|
||||
u64 sector_num = 0;
|
||||
|
||||
NcaContext *nca_ctx = (NcaContext*)ctx->nca_ctx;
|
||||
u64 content_offset = (ctx->section_offset + offset);
|
||||
|
||||
u64 block_start_offset = 0, block_end_offset = 0, block_size = 0;
|
||||
u64 data_start_offset = 0, chunk_size = 0, out_chunk_size = 0;
|
||||
|
||||
if (!strlen(nca_ctx->content_id_str) || (nca_ctx->storage_id != NcmStorageId_GameCard && !nca_ctx->ncm_storage) || (nca_ctx->storage_id == NcmStorageId_GameCard && !nca_ctx->gamecard_offset) || \
|
||||
content_offset >= nca_ctx->content_size || (content_offset + read_size) > nca_ctx->content_size)
|
||||
{
|
||||
LOGFILE("Invalid NCA header parameters!");
|
||||
goto exit;
|
||||
}
|
||||
|
||||
/* Optimization for reads from plaintext FS sections or reads that are aligned to the AES-CTR / AES-XTS sector size */
|
||||
if (ctx->encryption_type == NcaEncryptionType_None || \
|
||||
((ctx->encryption_type == NcaEncryptionType_AesXts || ctx->encryption_type == NcaEncryptionType_Nca0) && !(content_offset % NCA_AES_XTS_SECTOR_SIZE) && !(read_size % NCA_AES_XTS_SECTOR_SIZE)) || \
|
||||
((ctx->encryption_type == NcaEncryptionType_AesCtr || ctx->encryption_type == NcaEncryptionType_AesCtrEx) && !(content_offset % AES_BLOCK_SIZE) && !(read_size % AES_BLOCK_SIZE)))
|
||||
{
|
||||
/* Read data */
|
||||
if (!ncaReadContentFile(nca_ctx, out, read_size, content_offset))
|
||||
{
|
||||
LOGFILE("Failed to read 0x%lX bytes data block at offset 0x%lX from NCA \"%s\" FS section #%u! (aligned)", read_size, content_offset, nca_ctx->content_id_str, ctx->section_num);
|
||||
goto exit;
|
||||
}
|
||||
|
||||
/* Return right away if we're dealing with a plaintext FS section */
|
||||
if (ctx->encryption_type == NcaEncryptionType_None)
|
||||
{
|
||||
ret = true;
|
||||
goto exit;
|
||||
}
|
||||
|
||||
/* Decrypt data */
|
||||
if (ctx->encryption_type == NcaEncryptionType_AesXts || ctx->encryption_type == NcaEncryptionType_Nca0)
|
||||
{
|
||||
sector_num = ((ctx->encryption_type == NcaEncryptionType_AesXts ? offset : (content_offset - NCA_HEADER_LENGTH)) / NCA_AES_XTS_SECTOR_SIZE);
|
||||
|
||||
crypt_res = aes128XtsNintendoCrypt(&(ctx->xts_decrypt_ctx), out, out, read_size, sector_num, NCA_AES_XTS_SECTOR_SIZE, false);
|
||||
if (crypt_res != read_size)
|
||||
{
|
||||
LOGFILE("Failed to AES-XTS decrypt 0x%lX bytes data block at offset 0x%lX from NCA \"%s\" FS section #%u! (aligned)", read_size, content_offset, nca_ctx->content_id_str, ctx->section_num);
|
||||
goto exit;
|
||||
}
|
||||
} else
|
||||
if (ctx->encryption_type == NcaEncryptionType_AesCtr || ctx->encryption_type == NcaEncryptionType_AesCtrEx)
|
||||
{
|
||||
ncaUpdateAesCtrIv(ctx->ctr, content_offset);
|
||||
aes128CtrContextResetCtr(&(ctx->ctr_ctx), ctx->ctr);
|
||||
aes128CtrCrypt(&(ctx->ctr_ctx), out, out, read_size);
|
||||
}
|
||||
|
||||
ret = true;
|
||||
goto exit;
|
||||
}
|
||||
|
||||
/* Calculate offsets and block sizes */
|
||||
block_start_offset = ROUND_DOWN(content_offset, (ctx->encryption_type == NcaEncryptionType_AesXts || ctx->encryption_type == NcaEncryptionType_Nca0) ? NCA_AES_XTS_SECTOR_SIZE : AES_BLOCK_SIZE);
|
||||
block_end_offset = ROUND_UP(content_offset + read_size, (ctx->encryption_type == NcaEncryptionType_AesXts || ctx->encryption_type == NcaEncryptionType_Nca0) ? NCA_AES_XTS_SECTOR_SIZE : AES_BLOCK_SIZE);
|
||||
block_size = (block_end_offset - block_start_offset);
|
||||
|
||||
data_start_offset = (content_offset - block_start_offset);
|
||||
chunk_size = (block_size > NCA_CRYPTO_BUFFER_SIZE ? NCA_CRYPTO_BUFFER_SIZE : block_size);
|
||||
out_chunk_size = (block_size > NCA_CRYPTO_BUFFER_SIZE ? (NCA_CRYPTO_BUFFER_SIZE - data_start_offset) : read_size);
|
||||
|
||||
/* Read data */
|
||||
if (!ncaReadContentFile(nca_ctx, g_ncaCryptoBuffer, chunk_size, block_start_offset))
|
||||
{
|
||||
LOGFILE("Failed to read 0x%lX bytes encrypted data block at offset 0x%lX from NCA \"%s\" FS section #%u! (unaligned)", chunk_size, block_start_offset, nca_ctx->content_id_str, ctx->section_num);
|
||||
goto exit;
|
||||
}
|
||||
|
||||
/* Decrypt data */
|
||||
if (ctx->encryption_type == NcaEncryptionType_AesXts || ctx->encryption_type == NcaEncryptionType_Nca0)
|
||||
{
|
||||
sector_num = ((ctx->encryption_type == NcaEncryptionType_AesXts ? offset : (content_offset - NCA_HEADER_LENGTH)) / NCA_AES_XTS_SECTOR_SIZE);
|
||||
|
||||
crypt_res = aes128XtsNintendoCrypt(&(ctx->xts_decrypt_ctx), g_ncaCryptoBuffer, g_ncaCryptoBuffer, chunk_size, sector_num, NCA_AES_XTS_SECTOR_SIZE, false);
|
||||
if (crypt_res != chunk_size)
|
||||
{
|
||||
LOGFILE("Failed to AES-XTS decrypt 0x%lX bytes data block at offset 0x%lX from NCA \"%s\" FS section #%u! (unaligned)", chunk_size, block_start_offset, nca_ctx->content_id_str, ctx->section_num);
|
||||
goto exit;
|
||||
}
|
||||
} else
|
||||
if (ctx->encryption_type == NcaEncryptionType_AesCtr || ctx->encryption_type == NcaEncryptionType_AesCtrEx)
|
||||
{
|
||||
ncaUpdateAesCtrIv(ctx->ctr, block_start_offset);
|
||||
aes128CtrContextResetCtr(&(ctx->ctr_ctx), ctx->ctr);
|
||||
aes128CtrCrypt(&(ctx->ctr_ctx), g_ncaCryptoBuffer, g_ncaCryptoBuffer, chunk_size);
|
||||
}
|
||||
|
||||
/* Copy decrypted data */
|
||||
memcpy(out, g_ncaCryptoBuffer + data_start_offset, out_chunk_size);
|
||||
|
||||
ret = (block_size > NCA_CRYPTO_BUFFER_SIZE ? _ncaReadFsSection(ctx, (u8*)out + out_chunk_size, read_size - out_chunk_size, offset + out_chunk_size, false) : true);
|
||||
|
||||
exit:
|
||||
if (lock) mutexUnlock(&g_ncaCryptoBufferMutex);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
|
71
source/nca.h
71
source/nca.h
|
@ -245,24 +245,25 @@ typedef enum {
|
|||
} 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;
|
||||
NcaFsSectionType_PartitionFs = 0, ///< NcaFsType_PartitionFs + NcaHashType_HierarchicalSha256.
|
||||
NcaFsSectionType_RomFs = 1, ///< NcaFsType_RomFs + NcaHashType_HierarchicalIntegrity.
|
||||
NcaFsSectionType_PatchRomFs = 2, ///< NcaFsType_RomFs + NcaHashType_HierarchicalIntegrity + NcaEncryptionType_AesCtrEx.
|
||||
NcaFsSectionType_Nca0RomFs = 3, ///< NcaFsType_RomFs + NcaHashType_HierarchicalSha256 + NcaVersion_Nca0.
|
||||
NcaFsSectionType_Invalid = 4
|
||||
} NcaFsSectionType;
|
||||
|
||||
typedef struct {
|
||||
void *nca_ctx; ///< NcaContext. Used to perform NCA reads.
|
||||
void *nca_ctx; ///< NcaContext. Used to perform NCA reads.
|
||||
u8 section_num;
|
||||
u64 section_offset;
|
||||
u64 section_size;
|
||||
u8 section_type; ///< NcaSectionType.
|
||||
u8 encryption_type; ///< NcaEncryptionType.
|
||||
u8 section_type; ///< NcaFsSectionType.
|
||||
u8 encryption_type; ///< NcaEncryptionType.
|
||||
NcaFsHeader *header;
|
||||
u8 ctr[0x10]; ///< Used to update the AES CTR context IV based on the desired offset.
|
||||
u8 ctr[0x10]; ///< Used to update the AES CTR context IV based on the desired offset.
|
||||
Aes128CtrContext ctr_ctx;
|
||||
Aes128XtsContext xts_ctx;
|
||||
Aes128XtsContext xts_decrypt_ctx;
|
||||
Aes128XtsContext xts_encrypt_ctx;
|
||||
} NcaFsSectionContext;
|
||||
|
||||
typedef struct {
|
||||
|
@ -286,33 +287,36 @@ typedef struct {
|
|||
NcaKey decrypted_keys[4];
|
||||
} NcaContext;
|
||||
|
||||
/// Functions to control the internal heap buffer used by NCA FS section crypto code.
|
||||
/// Functions to control the internal heap buffer used by NCA FS section crypto operations.
|
||||
/// Must be called at startup.
|
||||
bool ncaAllocateCryptoBuffer(void);
|
||||
void ncaFreeCryptoBuffer(void);
|
||||
|
||||
/// Initializes a valid NCA context.
|
||||
/// If the NCA holds a populated Rights ID field, and if the Ticket object pointed to by 'tik' hasn't been filled, ticket data will be retrieved.
|
||||
/// 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.
|
||||
bool ncaInitializeContext(NcaContext *out, Ticket *tik, u8 storage_id, NcmContentStorage *ncm_storage, u8 hfs_partition_type, const NcmPackagedContentInfo *content_info);
|
||||
/// If the NCA holds a populated Rights ID field, and if the Ticket object pointed to by 'tik' hasn't been filled, ticket data will be retrieved.
|
||||
bool ncaInitializeContext(NcaContext *out, u8 storage_id, NcmContentStorage *ncm_storage, u8 hfs_partition_type, const NcmPackagedContentInfo *content_info, Ticket *tik);
|
||||
|
||||
/// Reads raw encrypted data from a NCA using an input NCA context, previously initialized by ncaInitializeContext().
|
||||
bool ncaReadContent(NcaContext *ctx, void *out, u64 read_size, u64 offset);
|
||||
/// Reads raw encrypted data from a NCA using an input context, previously initialized by ncaInitializeContext().
|
||||
bool ncaReadContentFile(NcaContext *ctx, void *out, u64 read_size, u64 offset);
|
||||
|
||||
/// Reads decrypted data from a NCA FS section using an input NCA FS section context.
|
||||
/// Reads decrypted data from a NCA FS section using an input context.
|
||||
/// Input offset must be relative to the start of the NCA FS section.
|
||||
/// If dealing with Patch RomFS sections, this function should only be used when *not* reading BKTR subsections.
|
||||
bool ncaReadFsSection(NcaFsSectionContext *ctx, void *out, u64 read_size, u64 offset);
|
||||
|
||||
/// Returns a pointer to a heap-allocated buffer used to encrypt the input plaintext data, based on the encryption type used by the input NCA FS section, as well as its offset and size.
|
||||
/// Input offset must be relative to the start of the NCA FS section.
|
||||
/// Output size and offset are guaranteed to be aligned to the AES sector size used by the encryption type from the FS section.
|
||||
/// Output offset is relative to the start of the NCA content file, making it easier to use the output encrypted block to replace data in-place while writing a NCA.
|
||||
void *ncaGenerateEncryptedFsSectionBlock(NcaFsSectionContext *ctx, void *data, u64 data_size, u64 data_offset, u64 *out_block_size, u64 *out_block_offset);
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
bool ncaDecryptKeyArea(NcaContext *nca_ctx);
|
||||
bool ncaEncryptKeyArea(NcaContext *nca_ctx);
|
||||
|
||||
bool ncaDecryptHeader(NcaContext *ctx);
|
||||
bool ncaEncryptHeader(NcaContext *ctx);
|
||||
|
||||
|
||||
|
@ -323,29 +327,34 @@ bool ncaEncryptHeader(NcaContext *ctx);
|
|||
|
||||
static inline void ncaConvertNcmContentSizeToU64(const u8 *size, u64 *out)
|
||||
{
|
||||
if (!size || !out) return;
|
||||
*out = 0;
|
||||
memcpy(out, size, 6);
|
||||
if (size && out)
|
||||
{
|
||||
*out = 0;
|
||||
memcpy(out, size, 6);
|
||||
}
|
||||
}
|
||||
|
||||
static inline void ncaConvertU64ToNcmContentSize(const u64 *size, u8 *out)
|
||||
{
|
||||
if (!size || !out) return;
|
||||
memcpy(out, size, 6);
|
||||
if (size && out) memcpy(out, size, 6);
|
||||
}
|
||||
|
||||
static inline void ncaSetDownloadDistributionType(NcaContext *ctx)
|
||||
{
|
||||
if (!ctx || ctx->header.distribution_type == NcaDistributionType_Download) return;
|
||||
ctx->header.distribution_type = NcaDistributionType_Download;
|
||||
ctx->dirty_header = true;
|
||||
if (ctx && ctx->header.distribution_type != NcaDistributionType_Download)
|
||||
{
|
||||
ctx->header.distribution_type = NcaDistributionType_Download;
|
||||
ctx->dirty_header = true;
|
||||
}
|
||||
}
|
||||
|
||||
static inline void ncaWipeRightsId(NcaContext *ctx)
|
||||
{
|
||||
if (!ctx) return;
|
||||
memset(&(ctx->header.rights_id), 0, sizeof(FsRightsId));
|
||||
ctx->dirty_header = true;
|
||||
if (ctx)
|
||||
{
|
||||
memset(&(ctx->header.rights_id), 0, sizeof(FsRightsId));
|
||||
ctx->dirty_header = true;
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* __NCA_H__ */
|
||||
|
|
Loading…
Reference in a new issue