diff --git a/source/main.c b/source/main.c index 07b2167..f372314 100644 --- a/source/main.c +++ b/source/main.c @@ -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"); + } + diff --git a/source/nca.c b/source/nca.c index 17e74a1..ee90464 100644 --- a/source/nca.c +++ b/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; +} diff --git a/source/nca.h b/source/nca.h index 7c5b74f..37ade98 100644 --- a/source/nca.h +++ b/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)); +} + + + + + diff --git a/source/pfs.c b/source/pfs.c index 9f66958..96231c4 100644 --- a/source/pfs.c +++ b/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; } diff --git a/source/pfs.h b/source/pfs.h index e374dfb..6ee5013 100644 --- a/source/pfs.h +++ b/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. diff --git a/source/romfs.c b/source/romfs.c index a5337f9..f51905d 100644 --- a/source/romfs.c +++ b/source/romfs.c @@ -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) { diff --git a/source/romfs.h b/source/romfs.h index 857c701..387d2f6 100644 --- a/source/romfs.h +++ b/source/romfs.h @@ -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)