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

Reworked FS section patching.

This commit is contained in:
Pablo Curiel 2020-04-28 04:58:17 -04:00
parent 226fbd0e21
commit e1b1dfc648
7 changed files with 364 additions and 219 deletions

View file

@ -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");
}

View file

@ -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,139 +394,133 @@ 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);
LOGFILE("Failed to read full HierarchicalSha256 hash data layer!");
goto exit;
}
/* Copy data */
memcpy(out, data, data_size);
/* Encrypt data */
if (ctx->encryption_type == NcaEncryptionType_AesXts || ctx->encryption_type == NcaEncryptionType_Nca0)
/* Allocate memory for the modified hash target layer block */
hash_target_block = malloc(hash_target_size);
if (!hash_target_block)
{
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("Unable to allocate 0x%lX bytes buffer for the modified HierarchicalSha256 hash target layer block!", hash_target_size);
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)
/* Read hash target layer block */
if (!_ncaReadFsSection(ctx, hash_target_block, hash_target_size, hash_target_start_offset, false))
{
LOGFILE("Unable to allocate 0x%lX bytes buffer! (unaligned)", block_size);
LOGFILE("Failed to read HierarchicalSha256 hash target layer block!");
goto exit;
}
/* Read decrypted data using aligned offset and size */
if (!_ncaReadFsSection(ctx, out, block_size, block_start_offset, false))
/* Replace data */
memcpy(hash_target_block + hash_target_data_offset, data, data_size);
/* Recalculate hashes */
for(u64 i = 0, j = 0; i < hash_target_size; i += hash_block_size, j++)
{
LOGFILE("Failed to read decrypted NCA \"%s\" FS section #%u data block!", nca_ctx->content_id_str, ctx->section_num);
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);
}
/* 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;
}
/* Replace plaintext data */
memcpy(out + plain_chunk_offset, data, data_size);
/* Reencrypt data */
if (ctx->encryption_type == NcaEncryptionType_AesXts || ctx->encryption_type == NcaEncryptionType_Nca0)
/* 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)
{
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);
LOGFILE("Failed to generate encrypted HierarchicalSha256 hash target layer block!");
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;
/* 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);
if (hash_data_layer) free(hash_data_layer);
return success;
}
return out;
}
static size_t aes128XtsNintendoCrypt(Aes128XtsContext *ctx, void *dst, const void *src, size_t size, u64 sector, size_t sector_size, bool encrypt)
{
@ -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;
}

View file

@ -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));
}

View file

@ -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;
}

View file

@ -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.

View file

@ -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)
{

View file

@ -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)