mirror of
https://github.com/DarkMatterCore/nxdumptool.git
synced 2024-12-23 09:02:06 +00:00
Reworked FS section patching.
This commit is contained in:
parent
226fbd0e21
commit
e1b1dfc648
7 changed files with 364 additions and 219 deletions
|
@ -249,6 +249,27 @@ int main(int argc, char *argv[])
|
|||
printf("romfs read file entry failed\n");
|
||||
}
|
||||
|
||||
consoleUpdate(NULL);
|
||||
|
||||
if (romfsReadFileSystemData(&romfs_ctx, buf, romfs_ctx.size, 0))
|
||||
{
|
||||
printf("romfs read fs data success\n");
|
||||
consoleUpdate(NULL);
|
||||
|
||||
tmp_file = fopen("sdmc:/nxdt_test/romfs.bin", "wb");
|
||||
if (tmp_file)
|
||||
{
|
||||
fwrite(buf, 1, romfs_ctx.size, tmp_file);
|
||||
fclose(tmp_file);
|
||||
tmp_file = NULL;
|
||||
printf("romfs data saved\n");
|
||||
} else {
|
||||
printf("romfs data not saved\n");
|
||||
}
|
||||
} else {
|
||||
printf("romfs read fs data failed\n");
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
316
source/nca.c
316
source/nca.c
|
@ -52,6 +52,7 @@ NX_INLINE void ncaUpdateAesCtrIv(u8 *ctr, u64 offset);
|
|||
NX_INLINE void ncaUpdateAesCtrExIv(u8 *ctr, u32 ctr_val, u64 offset);
|
||||
|
||||
static bool _ncaReadFsSection(NcaFsSectionContext *ctx, void *out, u64 read_size, u64 offset, bool lock);
|
||||
static void *_ncaGenerateEncryptedFsSectionBlock(NcaFsSectionContext *ctx, const void *data, u64 data_size, u64 data_offset, u64 *out_block_size, u64 *out_block_offset, bool lock);
|
||||
|
||||
bool ncaAllocateCryptoBuffer(void)
|
||||
{
|
||||
|
@ -393,140 +394,134 @@ bool ncaReadFsSection(NcaFsSectionContext *ctx, void *out, u64 read_size, u64 of
|
|||
}
|
||||
|
||||
void *ncaGenerateEncryptedFsSectionBlock(NcaFsSectionContext *ctx, const void *data, u64 data_size, u64 data_offset, u64 *out_block_size, u64 *out_block_offset)
|
||||
{
|
||||
return _ncaGenerateEncryptedFsSectionBlock(ctx, data, data_size, data_offset, out_block_size, out_block_offset, true);
|
||||
}
|
||||
|
||||
bool ncaGenerateHierarchicalSha256Patch(NcaFsSectionContext *ctx, const void *data, u64 data_size, u64 data_offset, NcaHierarchicalSha256Patch *out)
|
||||
{
|
||||
mutexLock(&g_ncaCryptoBufferMutex);
|
||||
|
||||
u8 *out = NULL;
|
||||
NcaContext *nca_ctx = NULL;
|
||||
|
||||
u64 hash_block_size = 0;
|
||||
u64 hash_data_layer_offset = 0, hash_data_layer_size = 0;
|
||||
u64 hash_target_layer_offset = 0, hash_target_layer_size = 0;
|
||||
u8 *hash_data_layer = NULL, *hash_target_block = NULL;
|
||||
|
||||
bool success = false;
|
||||
|
||||
if (!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)
|
||||
if (!ctx || !(nca_ctx = (NcaContext*)ctx->nca_ctx) || !ctx->header || ctx->header->hash_type != NcaHashType_HierarchicalSha256 || !data || !data_size || \
|
||||
!(hash_block_size = ctx->header->hash_info.hierarchical_sha256.hash_block_size) || !(hash_data_layer_size = ctx->header->hash_info.hierarchical_sha256.hash_data_layer_info.size) || \
|
||||
!(hash_target_layer_size = ctx->header->hash_info.hierarchical_sha256.hash_target_layer_info.size) || data_offset >= hash_target_layer_size || \
|
||||
(data_offset + data_size) > hash_target_layer_size || !out)
|
||||
{
|
||||
LOGFILE("Invalid NCA FS section header parameters!");
|
||||
LOGFILE("Invalid parameters!");
|
||||
goto exit;
|
||||
}
|
||||
|
||||
size_t crypt_res = 0;
|
||||
u64 sector_num = 0;
|
||||
/* Calculate required offsets and sizes */
|
||||
hash_data_layer_offset = ctx->header->hash_info.hierarchical_sha256.hash_data_layer_info.offset;
|
||||
hash_target_layer_offset = ctx->header->hash_info.hierarchical_sha256.hash_target_layer_info.offset;
|
||||
|
||||
NcaContext *nca_ctx = (NcaContext*)ctx->nca_ctx;
|
||||
u64 content_offset = (ctx->section_offset + data_offset);
|
||||
u64 hash_data_start_offset = ((data_offset / hash_block_size) * SHA256_HASH_SIZE);
|
||||
u64 hash_data_end_offset = (((data_offset + data_size) / hash_block_size) * SHA256_HASH_SIZE);
|
||||
u64 hash_data_size = (hash_data_end_offset != hash_data_start_offset ? (hash_data_end_offset - hash_data_start_offset) : SHA256_HASH_SIZE);
|
||||
|
||||
u64 block_start_offset = 0, block_end_offset = 0, block_size = 0;
|
||||
u64 plain_chunk_offset = 0;
|
||||
u64 hash_target_start_offset = (hash_target_layer_offset + ALIGN_DOWN(data_offset, hash_block_size));
|
||||
u64 hash_target_end_offset = (hash_target_layer_offset + ALIGN_UP(data_offset + data_size, hash_block_size));
|
||||
if (hash_target_end_offset > (hash_target_layer_offset + hash_target_layer_size)) hash_target_end_offset = (hash_target_layer_offset + hash_target_layer_size);
|
||||
u64 hash_target_size = (hash_target_end_offset - hash_target_start_offset);
|
||||
|
||||
if (!strlen(nca_ctx->content_id_str) || (nca_ctx->storage_id != NcmStorageId_GameCard && !nca_ctx->ncm_storage) || (nca_ctx->storage_id == NcmStorageId_GameCard && !nca_ctx->gamecard_offset) || \
|
||||
content_offset >= nca_ctx->content_size || (content_offset + data_size) > nca_ctx->content_size)
|
||||
u64 hash_target_data_offset = (data_offset - ALIGN_DOWN(data_offset, hash_block_size));
|
||||
|
||||
/* Allocate memory for the full hash data layer */
|
||||
hash_data_layer = malloc(hash_data_layer_size);
|
||||
if (!hash_data_layer)
|
||||
{
|
||||
LOGFILE("Invalid NCA header parameters!");
|
||||
LOGFILE("Unable to allocate 0x%lX bytes buffer for the full HierarchicalSha256 hash data layer!", hash_data_layer_size);
|
||||
goto exit;
|
||||
}
|
||||
|
||||
/* 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)))
|
||||
/* Read full hash data layer */
|
||||
if (!_ncaReadFsSection(ctx, hash_data_layer, hash_data_layer_size, hash_data_layer_offset, false))
|
||||
{
|
||||
/* 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 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;
|
||||
LOGFILE("Failed to read full HierarchicalSha256 hash data layer!");
|
||||
goto exit;
|
||||
}
|
||||
|
||||
/* Calculate block offsets and size */
|
||||
block_start_offset = ALIGN_DOWN(data_offset, (ctx->encryption_type == NcaEncryptionType_AesXts || ctx->encryption_type == NcaEncryptionType_Nca0) ? NCA_AES_XTS_SECTOR_SIZE : AES_BLOCK_SIZE);
|
||||
block_end_offset = ALIGN_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)
|
||||
/* Allocate memory for the modified hash target layer block */
|
||||
hash_target_block = malloc(hash_target_size);
|
||||
if (!hash_target_block)
|
||||
{
|
||||
LOGFILE("Unable to allocate 0x%lX bytes buffer! (unaligned)", block_size);
|
||||
LOGFILE("Unable to allocate 0x%lX bytes buffer for the modified HierarchicalSha256 hash target layer block!", hash_target_size);
|
||||
goto exit;
|
||||
}
|
||||
|
||||
/* Read decrypted data using aligned offset and size */
|
||||
if (!_ncaReadFsSection(ctx, out, block_size, block_start_offset, false))
|
||||
/* Read hash target layer block */
|
||||
if (!_ncaReadFsSection(ctx, hash_target_block, hash_target_size, hash_target_start_offset, false))
|
||||
{
|
||||
LOGFILE("Failed to read decrypted NCA \"%s\" FS section #%u data block!", nca_ctx->content_id_str, ctx->section_num);
|
||||
LOGFILE("Failed to read HierarchicalSha256 hash target layer block!");
|
||||
goto exit;
|
||||
}
|
||||
|
||||
/* Replace plaintext data */
|
||||
memcpy(out + plain_chunk_offset, data, data_size);
|
||||
/* Replace data */
|
||||
memcpy(hash_target_block + hash_target_data_offset, data, data_size);
|
||||
|
||||
/* Reencrypt data */
|
||||
if (ctx->encryption_type == NcaEncryptionType_AesXts || ctx->encryption_type == NcaEncryptionType_Nca0)
|
||||
/* Recalculate hashes */
|
||||
for(u64 i = 0, j = 0; i < hash_target_size; i += hash_block_size, j++)
|
||||
{
|
||||
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);
|
||||
if (hash_block_size > (hash_target_size - i)) hash_block_size = (hash_target_size - i);
|
||||
sha256CalculateHash(hash_data_layer + hash_data_start_offset + (j * SHA256_HASH_SIZE), hash_target_block + i, hash_block_size);
|
||||
}
|
||||
|
||||
*out_block_size = block_size;
|
||||
*out_block_offset = content_offset;
|
||||
/* Reencrypt modified hash data layer block */
|
||||
out->hash_data_layer_patch.data = _ncaGenerateEncryptedFsSectionBlock(ctx, hash_data_layer + hash_data_start_offset, hash_data_size, hash_data_layer_offset + hash_data_start_offset, \
|
||||
&(out->hash_data_layer_patch.size), &(out->hash_data_layer_patch.offset), false);
|
||||
if (!out->hash_data_layer_patch.data)
|
||||
{
|
||||
LOGFILE("Failed to generate encrypted HierarchicalSha256 hash data layer block!");
|
||||
goto exit;
|
||||
}
|
||||
|
||||
/* Reencrypt hash target layer block */
|
||||
out->hash_target_layer_patch.data = _ncaGenerateEncryptedFsSectionBlock(ctx, hash_target_block, hash_target_size, hash_target_start_offset, &(out->hash_target_layer_patch.size), \
|
||||
&(out->hash_target_layer_patch.offset), false);
|
||||
if (!out->hash_target_layer_patch.data)
|
||||
{
|
||||
LOGFILE("Failed to generate encrypted HierarchicalSha256 hash target layer block!");
|
||||
goto exit;
|
||||
}
|
||||
|
||||
/* Recalculate master hash from hash info block */
|
||||
sha256CalculateHash(ctx->header->hash_info.hierarchical_sha256.master_hash, hash_data_layer, hash_data_layer_size);
|
||||
|
||||
/* Recalculate FS header hash */
|
||||
sha256CalculateHash(nca_ctx->header.fs_hashes[ctx->section_num].hash, ctx->header, sizeof(NcaFsHeader));
|
||||
|
||||
/* Enable the 'dirty_header' flag */
|
||||
nca_ctx->dirty_header = true;
|
||||
|
||||
success = true;
|
||||
|
||||
exit:
|
||||
mutexUnlock(&g_ncaCryptoBufferMutex);
|
||||
|
||||
if (!success && out)
|
||||
{
|
||||
free(out);
|
||||
out = NULL;
|
||||
}
|
||||
if (hash_target_block) free(hash_target_block);
|
||||
|
||||
return out;
|
||||
if (hash_data_layer) free(hash_data_layer);
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
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)
|
||||
|
@ -896,3 +891,138 @@ exit:
|
|||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void *_ncaGenerateEncryptedFsSectionBlock(NcaFsSectionContext *ctx, const void *data, u64 data_size, u64 data_offset, u64 *out_block_size, u64 *out_block_offset, bool lock)
|
||||
{
|
||||
if (lock) mutexLock(&g_ncaCryptoBufferMutex);
|
||||
|
||||
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!");
|
||||
goto exit;
|
||||
}
|
||||
|
||||
size_t crypt_res = 0;
|
||||
u64 sector_num = 0;
|
||||
|
||||
NcaContext *nca_ctx = (NcaContext*)ctx->nca_ctx;
|
||||
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 + data_size) > nca_ctx->content_size)
|
||||
{
|
||||
LOGFILE("Invalid NCA header parameters!");
|
||||
goto exit;
|
||||
}
|
||||
|
||||
/* 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)))
|
||||
{
|
||||
/* 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 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 = ALIGN_DOWN(data_offset, (ctx->encryption_type == NcaEncryptionType_AesXts || ctx->encryption_type == NcaEncryptionType_Nca0) ? NCA_AES_XTS_SECTOR_SIZE : AES_BLOCK_SIZE);
|
||||
block_end_offset = ALIGN_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:
|
||||
if (lock) mutexUnlock(&g_ncaCryptoBufferMutex);
|
||||
|
||||
if (!success && out)
|
||||
{
|
||||
free(out);
|
||||
out = NULL;
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
|
55
source/nca.h
55
source/nca.h
|
@ -289,6 +289,22 @@ typedef struct {
|
|||
NcaKey decrypted_keys[4];
|
||||
} NcaContext;
|
||||
|
||||
typedef struct {
|
||||
u64 offset; ///< New layer data offset (relative to the start of the NCA content file).
|
||||
u64 size; ///< New layer data size.
|
||||
u8 *data; ///< New layer data.
|
||||
} NcaHashInfoLayerPatch;
|
||||
|
||||
typedef struct {
|
||||
NcaHashInfoLayerPatch hash_data_layer_patch;
|
||||
NcaHashInfoLayerPatch hash_target_layer_patch;
|
||||
} NcaHierarchicalSha256Patch;
|
||||
|
||||
typedef struct {
|
||||
NcaHashInfoLayerPatch hash_data_layer_patch[NCA_IVFC_HASH_DATA_LAYER_COUNT];
|
||||
NcaHashInfoLayerPatch hash_target_layer_patch;
|
||||
} NcaHierarchicalIntegrityPatch;
|
||||
|
||||
/// Functions to control the internal heap buffer used by NCA FS section crypto operations.
|
||||
/// Must be called at startup.
|
||||
bool ncaAllocateCryptoBuffer(void);
|
||||
|
@ -315,6 +331,45 @@ bool ncaReadFsSection(NcaFsSectionContext *ctx, void *out, u64 read_size, u64 of
|
|||
/// 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, const void *data, u64 data_size, u64 data_offset, u64 *out_block_size, u64 *out_block_offset);
|
||||
|
||||
/// Generates HierarchicalSha256 FS section patch data, which can be used to replace NCA data in content dumping operations.
|
||||
/// Input offset must be relative to the start of the HierarchicalSha256 hash target layer (actual underlying FS).
|
||||
/// Bear in mind that this function recalculates both the NcaHashInfo block master hash and the NCA FS header hash from the NCA header, and enables the 'dirty_header' flag from the NCA context.
|
||||
/// As such, this function is not capable of generating more than one patch per HierarchicalSha256 FS section.
|
||||
bool ncaGenerateHierarchicalSha256Patch(NcaFsSectionContext *ctx, const void *data, u64 data_size, u64 data_offset, NcaHierarchicalSha256Patch *out);
|
||||
|
||||
/// Cleanups a previously generated NcaHierarchicalSha256Patch.
|
||||
NX_INLINE void ncaFreeHierarchicalSha256Patch(NcaHierarchicalSha256Patch *patch)
|
||||
{
|
||||
if (!patch) return;
|
||||
if (patch->hash_data_layer_patch.data) free(patch->hash_data_layer_patch.data);
|
||||
if (patch->hash_target_layer_patch.data) free(patch->hash_target_layer_patch.data);
|
||||
memset(patch, 0, sizeof(NcaHierarchicalSha256Patch));
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/// Cleanups a previously generated NcaHierarchicalIntegrityPatch.
|
||||
NX_INLINE void ncaFreeHierarchicalIntegrityPatch(NcaHierarchicalIntegrityPatch *patch)
|
||||
{
|
||||
if (!patch) return;
|
||||
|
||||
for(u8 i = 0; i < NCA_IVFC_HASH_DATA_LAYER_COUNT; i++)
|
||||
{
|
||||
if (patch->hash_data_layer_patch[i].data) free(patch->hash_data_layer_patch[i].data);
|
||||
}
|
||||
|
||||
if (patch->hash_target_layer_patch.data) free(patch->hash_target_layer_patch.data);
|
||||
memset(patch, 0, sizeof(NcaHierarchicalIntegrityPatch));
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
110
source/pfs.c
110
source/pfs.c
|
@ -32,21 +32,20 @@ bool pfsInitializeContext(PartitionFileSystemContext *out, NcaFsSectionContext *
|
|||
|
||||
/* Fill context */
|
||||
out->nca_fs_ctx = nca_fs_ctx;
|
||||
out->hash_info = &(nca_fs_ctx->header->hash_info.hierarchical_sha256);
|
||||
out->offset = 0;
|
||||
out->size = 0;
|
||||
out->is_exefs = false;
|
||||
out->header_size = 0;
|
||||
out->header = NULL;
|
||||
|
||||
if (!ncaValidateHierarchicalSha256Offsets(out->hash_info, nca_fs_ctx->section_size))
|
||||
if (!ncaValidateHierarchicalSha256Offsets(&(nca_fs_ctx->header->hash_info.hierarchical_sha256), nca_fs_ctx->section_size))
|
||||
{
|
||||
LOGFILE("Invalid HierarchicalSha256 block!");
|
||||
return false;
|
||||
}
|
||||
|
||||
out->offset = out->hash_info->hash_target_layer_info.offset;
|
||||
out->size = out->hash_info->hash_target_layer_info.size;
|
||||
out->offset = nca_fs_ctx->header->hash_info.hierarchical_sha256.hash_target_layer_info.offset;
|
||||
out->size = nca_fs_ctx->header->hash_info.hierarchical_sha256.hash_target_layer_info.size;
|
||||
|
||||
/* Read partial PFS header */
|
||||
u32 magic = 0;
|
||||
|
@ -134,111 +133,22 @@ bool pfsReadEntryData(PartitionFileSystemContext *ctx, PartitionFileSystemEntry
|
|||
return true;
|
||||
}
|
||||
|
||||
bool pfsGenerateEntryPatch(PartitionFileSystemContext *ctx, PartitionFileSystemEntry *fs_entry, const void *data, u64 data_size, u64 data_offset, PartitionFileSystemPatchInfo *out)
|
||||
bool pfsGenerateEntryPatch(PartitionFileSystemContext *ctx, PartitionFileSystemEntry *fs_entry, const void *data, u64 data_size, u64 data_offset, NcaHierarchicalSha256Patch *out)
|
||||
{
|
||||
NcaContext *nca_ctx = NULL;
|
||||
|
||||
if (!ctx || !ctx->nca_fs_ctx || !(nca_ctx = (NcaContext*)ctx->nca_fs_ctx->nca_ctx) || !ctx->hash_info || !ctx->header_size || !ctx->header || !fs_entry || fs_entry->offset >= ctx->size || \
|
||||
!fs_entry->size || (fs_entry->offset + fs_entry->size) > ctx->size || !data || !data_size || data_offset >= fs_entry->size || (data_offset + data_size) > fs_entry->size || !out)
|
||||
if (!ctx || !ctx->nca_fs_ctx || !ctx->header_size || !ctx->header || !fs_entry || fs_entry->offset >= ctx->size || !fs_entry->size || (fs_entry->offset + fs_entry->size) > ctx->size || !data || \
|
||||
!data_size || data_offset >= fs_entry->size || (data_offset + data_size) > fs_entry->size || !out)
|
||||
{
|
||||
LOGFILE("Invalid parameters!");
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Calculate required offsets and sizes */
|
||||
u64 partition_offset = (ctx->header_size + fs_entry->offset + data_offset);
|
||||
u64 block_size = ctx->hash_info->hash_block_size;
|
||||
|
||||
u64 hash_table_offset = ctx->hash_info->hash_data_layer_info.offset;
|
||||
u64 hash_table_size = ctx->hash_info->hash_data_layer_info.size;
|
||||
|
||||
u64 hash_block_start_offset = ((partition_offset / block_size) * SHA256_HASH_SIZE);
|
||||
u64 hash_block_end_offset = (((partition_offset + data_size) / block_size) * SHA256_HASH_SIZE);
|
||||
u64 hash_block_size = (hash_block_end_offset != hash_block_start_offset ? (hash_block_end_offset - hash_block_start_offset) : SHA256_HASH_SIZE);
|
||||
|
||||
u64 data_block_start_offset = (ctx->offset + ALIGN_DOWN(partition_offset, block_size));
|
||||
u64 data_block_end_offset = (ctx->offset + ALIGN_UP(partition_offset + data_size, block_size));
|
||||
if (data_block_end_offset > (ctx->offset + ctx->size)) data_block_end_offset = (ctx->offset + ctx->size);
|
||||
u64 data_block_size = (data_block_end_offset - data_block_start_offset);
|
||||
|
||||
u64 new_data_offset = (partition_offset - ALIGN_DOWN(partition_offset, block_size));
|
||||
|
||||
u8 *hash_table = NULL, *data_block = NULL;
|
||||
|
||||
bool success = false;
|
||||
|
||||
/* Allocate memory for the full hash table */
|
||||
hash_table = malloc(hash_table_size);
|
||||
if (!hash_table)
|
||||
if (!ncaGenerateHierarchicalSha256Patch(ctx->nca_fs_ctx, data, data_size, partition_offset, out))
|
||||
{
|
||||
LOGFILE("Unable to allocate 0x%lX bytes buffer for the full partition FS hash table!", hash_table_size);
|
||||
goto exit;
|
||||
LOGFILE("Failed to generate 0x%lX bytes HierarchicalSha256 patch at offset 0x%lX for partition FS entry!", data_size, partition_offset);
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Read full hash table */
|
||||
if (!ncaReadFsSection(ctx->nca_fs_ctx, hash_table, hash_table_size, hash_table_offset))
|
||||
{
|
||||
LOGFILE("Failed to read full partition FS hash table!");
|
||||
goto exit;
|
||||
}
|
||||
|
||||
/* Allocate memory for the modified data block */
|
||||
data_block = malloc(data_block_size);
|
||||
if (!data_block)
|
||||
{
|
||||
LOGFILE("Unable to allocate 0x%lX bytes buffer for the modified partition FS data block!", data_block_size);
|
||||
goto exit;
|
||||
}
|
||||
|
||||
/* Read data block */
|
||||
if (!ncaReadFsSection(ctx->nca_fs_ctx, data_block, data_block_size, data_block_start_offset))
|
||||
{
|
||||
LOGFILE("Failed to read partition FS data block!");
|
||||
goto exit;
|
||||
}
|
||||
|
||||
/* Replace data */
|
||||
memcpy(data_block + new_data_offset, data, data_size);
|
||||
|
||||
/* Recalculate hashes */
|
||||
for(u64 i = 0, j = 0; i < data_block_size; i += block_size, j++)
|
||||
{
|
||||
if (block_size > (data_block_size - i)) block_size = (data_block_size - i);
|
||||
sha256CalculateHash(hash_table + hash_block_start_offset + (j * SHA256_HASH_SIZE), data_block + i, block_size);
|
||||
}
|
||||
|
||||
/* Reencrypt hash block */
|
||||
out->hash_block = ncaGenerateEncryptedFsSectionBlock(ctx->nca_fs_ctx, hash_table + hash_block_start_offset, hash_block_size, hash_table_offset + hash_block_start_offset, \
|
||||
&(out->hash_block_size), &(out->hash_block_offset));
|
||||
if (!out->hash_block)
|
||||
{
|
||||
LOGFILE("Failed to generate encrypted partition FS hash block!");
|
||||
goto exit;
|
||||
}
|
||||
|
||||
/* Reencrypt data block */
|
||||
out->data_block = ncaGenerateEncryptedFsSectionBlock(ctx->nca_fs_ctx, data_block, data_block_size, data_block_start_offset, &(out->data_block_size), &(out->data_block_offset));
|
||||
if (!out->data_block)
|
||||
{
|
||||
LOGFILE("Failed to generate encrypted partition FS data block!");
|
||||
goto exit;
|
||||
}
|
||||
|
||||
/* Recalculate master hash from hash info block */
|
||||
sha256CalculateHash(ctx->hash_info->master_hash, hash_table, hash_table_size);
|
||||
|
||||
/* Recalculate FS header hash */
|
||||
sha256CalculateHash(nca_ctx->header.fs_hashes[ctx->nca_fs_ctx->section_num].hash, ctx->header, sizeof(NcaFsHeader));
|
||||
|
||||
/* Enable the 'dirty_header' flag */
|
||||
nca_ctx->dirty_header = true;
|
||||
|
||||
success = true;
|
||||
|
||||
exit:
|
||||
if (data_block) free(data_block);
|
||||
|
||||
if (hash_table) free(hash_table);
|
||||
|
||||
return success;
|
||||
return true;
|
||||
}
|
||||
|
|
17
source/pfs.h
17
source/pfs.h
|
@ -40,7 +40,6 @@ typedef struct {
|
|||
|
||||
typedef struct {
|
||||
NcaFsSectionContext *nca_fs_ctx; ///< Used to read NCA FS section data.
|
||||
NcaHierarchicalSha256 *hash_info; ///< Hash table information.
|
||||
u64 offset; ///< Partition offset (relative to the start of the NCA FS section).
|
||||
u64 size; ///< Partition size.
|
||||
bool is_exefs; ///< ExeFS flag.
|
||||
|
@ -48,15 +47,6 @@ typedef struct {
|
|||
u8 *header; ///< PartitionFileSystemHeader + (PartitionFileSystemEntry * entry_count) + Name Table.
|
||||
} PartitionFileSystemContext;
|
||||
|
||||
typedef struct {
|
||||
u64 hash_block_offset; ///< New hash block offset (relative to the start of the NCA content file).
|
||||
u64 hash_block_size; ///< New hash block size (aligned to the AES block size from the NCA FS section).
|
||||
u8 *hash_block; ///< New hash block contents.
|
||||
u64 data_block_offset; ///< New data block offset (relative to the start of the NCA content file).
|
||||
u64 data_block_size; ///< New data block size (aligned to the NcaHierarchicalSha256 block size).
|
||||
u8 *data_block; ///< New data block contents.
|
||||
} PartitionFileSystemPatchInfo;
|
||||
|
||||
/// Initializes a partition FS context.
|
||||
bool pfsInitializeContext(PartitionFileSystemContext *out, NcaFsSectionContext *nca_fs_ctx);
|
||||
|
||||
|
@ -76,11 +66,10 @@ bool pfsReadPartitionData(PartitionFileSystemContext *ctx, void *out, u64 read_s
|
|||
/// Input offset must be relative to the start of the partition FS entry.
|
||||
bool pfsReadEntryData(PartitionFileSystemContext *ctx, PartitionFileSystemEntry *fs_entry, void *out, u64 read_size, u64 offset);
|
||||
|
||||
/// Generates modified + encrypted hash and data blocks using a partition FS context + entry information. Both blocks are ready to be used to replace NCA content data during writing operations.
|
||||
/// Generates HierarchicalSha256 FS section patch data using a partition FS context + entry, which can be used to replace NCA data in content dumping operations.
|
||||
/// Input offset must be relative to the start of the partition FS entry data.
|
||||
/// Bear in mind that this function recalculates both the NcaHashInfo block master hash and the NCA FS header hash from the NCA header, and enables the 'dirty_header' flag from the NCA context.
|
||||
/// As such, this function is only capable of modifying a single file from a partition FS in a NCA content file.
|
||||
bool pfsGenerateEntryPatch(PartitionFileSystemContext *ctx, PartitionFileSystemEntry *fs_entry, const void *data, u64 data_size, u64 data_offset, PartitionFileSystemPatchInfo *out);
|
||||
/// This function shares the same limitations as ncaGenerateHierarchicalSha256Patch().
|
||||
bool pfsGenerateEntryPatch(PartitionFileSystemContext *ctx, PartitionFileSystemEntry *fs_entry, const void *data, u64 data_size, u64 data_offset, NcaHierarchicalSha256Patch *out);
|
||||
|
||||
/// Miscellaneous functions.
|
||||
|
||||
|
|
|
@ -471,17 +471,37 @@ bool romfsGeneratePathFromFileEntry(RomFileSystemContext *ctx, RomFileSystemFile
|
|||
return true;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
bool romfsGenerateFileEntryPatch(RomFileSystemContext *ctx, RomFileSystemFileEntry *file_entry, const void *data, u64 data_size, u64 data_offset, RomFileSystemFileEntryPatch *out)
|
||||
{
|
||||
if (!ctx || !ctx->nca_fs_ctx || !ctx->body_offset || (ctx->nca_fs_ctx->section_type != NcaFsSectionType_Nca0RomFs && ctx->nca_fs_ctx->section_type != NcaFsSectionType_RomFs) || !file_entry || \
|
||||
!file_entry->size || file_entry->offset >= ctx->size || (file_entry->offset + file_entry->size) > ctx->size || !data || !data_size || data_offset >= file_entry->size || \
|
||||
(data_offset + data_size) > file_entry->size || !out)
|
||||
{
|
||||
LOGFILE("Invalid parameters!");
|
||||
return false;
|
||||
}
|
||||
|
||||
bool success = false;
|
||||
u64 fs_offset = (ctx->body_offset + file_entry->offset + data_offset);
|
||||
|
||||
memset(&(out->old_format_patch), 0, sizeof(NcaHierarchicalSha256Patch));
|
||||
memset(&(out->cur_format_patch), 0, sizeof(NcaHierarchicalIntegrityPatch));
|
||||
|
||||
if (ctx->nca_fs_ctx->section_type == NcaFsSectionType_Nca0RomFs)
|
||||
{
|
||||
out->use_old_format_patch = true;
|
||||
|
||||
success = ncaGenerateHierarchicalSha256Patch(ctx->nca_fs_ctx, data, data_size, fs_offset, &(out->old_format_patch));
|
||||
if (!success) LOGFILE("Failed to generate 0x%lX bytes HierarchicalSha256 patch at offset 0x%lX for RomFS file entry!", data_size, fs_offset);
|
||||
} else {
|
||||
out->use_old_format_patch = false;
|
||||
|
||||
success = true;
|
||||
|
||||
}
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
static RomFileSystemDirectoryEntry *romfsGetChildDirectoryEntryByName(RomFileSystemContext *ctx, RomFileSystemDirectoryEntry *dir_entry, const char *name)
|
||||
{
|
||||
|
|
|
@ -102,6 +102,12 @@ typedef struct {
|
|||
u64 body_offset; ///< RomFS file data body offset (relative to the start of the RomFS).
|
||||
} RomFileSystemContext;
|
||||
|
||||
typedef struct {
|
||||
bool use_old_format_patch; ///< Old format patch flag.
|
||||
NcaHierarchicalSha256Patch old_format_patch; ///< Used with NCA0 RomFS sections.
|
||||
NcaHierarchicalIntegrityPatch cur_format_patch; ///< Used with NCA2/NCA3 RomFS sections.
|
||||
} RomFileSystemFileEntryPatch;
|
||||
|
||||
/// Initializes a RomFS context.
|
||||
bool romfsInitializeContext(RomFileSystemContext *out, NcaFsSectionContext *nca_fs_ctx);
|
||||
|
||||
|
@ -119,7 +125,7 @@ NX_INLINE void romfsFreeContext(RomFileSystemContext *ctx)
|
|||
bool romfsReadFileSystemData(RomFileSystemContext *ctx, void *out, u64 read_size, u64 offset);
|
||||
|
||||
/// Reads data from a previously retrieved RomFileSystemFileEntry using a RomFS context.
|
||||
/// Input offset must be relative to the start of the RomFS file entry.
|
||||
/// Input offset must be relative to the start of the RomFS file entry data.
|
||||
bool romfsReadFileEntryData(RomFileSystemContext *ctx, RomFileSystemFileEntry *file_entry, void *out, u64 read_size, u64 offset);
|
||||
|
||||
/// Calculates the extracted RomFS size.
|
||||
|
@ -142,6 +148,20 @@ bool romfsGeneratePathFromDirectoryEntry(RomFileSystemContext *ctx, RomFileSyste
|
|||
/// Generates a path string from a RomFS file entry.
|
||||
bool romfsGeneratePathFromFileEntry(RomFileSystemContext *ctx, RomFileSystemFileEntry *file_entry, char *out_path, size_t out_path_size);
|
||||
|
||||
/// Generates HierarchicalSha256 (NCA0) / HierarchicalIntegrity (NCA2/NCA3) FS section patch data using a RomFS context + file entry, which can be used to replace NCA data in content dumping operations.
|
||||
/// Input offset must be relative to the start of the RomFS file entry data.
|
||||
/// This function shares the same limitations as ncaGenerateHierarchicalSha256Patch() / ncaGenerateHierarchicalIntegrityPatch().
|
||||
bool romfsGenerateFileEntryPatch(RomFileSystemContext *ctx, RomFileSystemFileEntry *file_entry, const void *data, u64 data_size, u64 data_offset, RomFileSystemFileEntryPatch *out);
|
||||
|
||||
/// Cleanups a previously generated RomFS file entry patch.
|
||||
NX_INLINE void romfsFreeFileEntryPatch(RomFileSystemFileEntryPatch *patch)
|
||||
{
|
||||
if (!patch) return;
|
||||
ncaFreeHierarchicalSha256Patch(&(patch->old_format_patch));
|
||||
ncaFreeHierarchicalIntegrityPatch(&(patch->cur_format_patch));
|
||||
memset(patch, 0, sizeof(RomFileSystemFileEntryPatch));
|
||||
}
|
||||
|
||||
/// Miscellaneous functions.
|
||||
|
||||
NX_INLINE RomFileSystemDirectoryEntry *romfsGetDirectoryEntryByOffset(RomFileSystemContext *ctx, u32 dir_entry_offset)
|
||||
|
|
Loading…
Reference in a new issue