diff --git a/source/main.c b/source/main.c index f372314..4ee068d 100644 --- a/source/main.c +++ b/source/main.c @@ -113,6 +113,7 @@ int main(int argc, char *argv[]) u64 romfs_size = 0; RomFileSystemFileEntry *romfs_file_entry = NULL; RomFileSystemContext romfs_ctx = {0}; + RomFileSystemFileEntryPatch romfs_patch = {0}; buf = malloc(0x400000); if (!buf) @@ -172,15 +173,6 @@ int main(int argc, char *argv[]) printf("romfs initialize ctx succeeded\n"); consoleUpdate(NULL); - if (romfsGetTotalDataSize(&romfs_ctx, &romfs_size)) - { - printf("romfs size succeeded: 0x%lX\n", romfs_size); - } else { - printf("romfs size failed\n"); - } - - consoleUpdate(NULL); - tmp_file = fopen("sdmc:/nxdt_test/romfs_ctx.bin", "wb"); if (tmp_file) { @@ -200,9 +192,9 @@ int main(int argc, char *argv[]) fwrite(romfs_ctx.dir_table, 1, romfs_ctx.dir_table_size, tmp_file); fclose(tmp_file); tmp_file = NULL; - printf("dir table saved\n"); + printf("romfs dir table saved\n"); } else { - printf("dir table not saved\n"); + printf("romfs dir table not saved\n"); } consoleUpdate(NULL); @@ -213,40 +205,9 @@ int main(int argc, char *argv[]) fwrite(romfs_ctx.file_table, 1, romfs_ctx.file_table_size, tmp_file); fclose(tmp_file); tmp_file = NULL; - printf("file table saved\n"); + printf("romfs file table saved\n"); } else { - printf("file table not saved\n"); - } - - consoleUpdate(NULL); - - romfs_file_entry = romfsGetFileEntryByPath(&romfs_ctx, "/control.nacp"); - if (!romfs_file_entry) - { - printf("romfs get file entry by path failed\n"); - goto out2; - } - - printf("romfs get file entry by path success: %s | %p\n", romfs_file_entry->name, romfs_file_entry); - consoleUpdate(NULL); - - if (romfsReadFileEntryData(&romfs_ctx, romfs_file_entry, buf, romfs_file_entry->size, 0)) - { - printf("romfs read file entry success\n"); - consoleUpdate(NULL); - - tmp_file = fopen("sdmc:/nxdt_test/control.nacp", "wb"); - if (tmp_file) - { - fwrite(buf, 1, romfs_file_entry->size, tmp_file); - fclose(tmp_file); - tmp_file = NULL; - printf("romfs file entry data saved\n"); - } else { - printf("romfs file entry data not saved\n"); - } - } else { - printf("romfs read file entry failed\n"); + printf("romfs file table not saved\n"); } consoleUpdate(NULL); @@ -270,6 +231,134 @@ int main(int argc, char *argv[]) printf("romfs read fs data failed\n"); } + if (romfsGetTotalDataSize(&romfs_ctx, &romfs_size)) + { + printf("romfs size succeeded: 0x%lX\n", romfs_size); + } else { + printf("romfs size failed\n"); + } + + consoleUpdate(NULL); + + romfs_file_entry = romfsGetFileEntryByPath(&romfs_ctx, "/control.nacp"); + if (!romfs_file_entry) + { + printf("romfs get file entry by path failed\n"); + goto out2; + } + + printf("romfs get file entry by path success: %s | %p\n", romfs_file_entry->name, romfs_file_entry); + consoleUpdate(NULL); + + if (!romfsReadFileEntryData(&romfs_ctx, romfs_file_entry, buf, romfs_file_entry->size, 0)) + { + printf("romfs read file entry failed\n"); + goto out2; + } + + printf("romfs read file entry success\n"); + consoleUpdate(NULL); + + tmp_file = fopen("sdmc:/nxdt_test/control.nacp", "wb"); + if (tmp_file) + { + fwrite(buf, 1, romfs_file_entry->size, tmp_file); + fclose(tmp_file); + tmp_file = NULL; + printf("romfs file entry data saved\n"); + } else { + printf("romfs file entry data not saved\n"); + } + + consoleUpdate(NULL); + + NacpStruct *nacp_data = (NacpStruct*)buf; + memset(nacp_data->lang, 0, MEMBER_SIZE(NacpStruct, lang)); + for(u8 i = 0; i < 16; i++) + { + sprintf(nacp_data->lang[i].name, "nxdumptool"); + sprintf(nacp_data->lang[i].author, "DarkMatterCore"); + } + + tmp_file = fopen("sdmc:/nxdt_test/control_mod.nacp", "wb"); + if (tmp_file) + { + fwrite(buf, 1, romfs_file_entry->size, tmp_file); + fclose(tmp_file); + tmp_file = NULL; + printf("romfs file entry mod data saved\n"); + } else { + printf("romfs file entry mod data not saved\n"); + } + + consoleUpdate(NULL); + + if (!romfsGenerateFileEntryPatch(&romfs_ctx, romfs_file_entry, buf, MEMBER_SIZE(NacpStruct, lang), 0, &romfs_patch)) + { + printf("romfs file entry patch failed\n"); + goto out2; + } + + printf("romfs file entry patch success\n"); + consoleUpdate(NULL); + + tmp_file = fopen("sdmc:/nxdt_test/romfs_patch.bin", "wb"); + if (tmp_file) + { + fwrite(&romfs_patch, 1, sizeof(RomFileSystemFileEntryPatch), tmp_file); + fclose(tmp_file); + tmp_file = NULL; + printf("romfs patch saved\n"); + } else { + printf("romfs patch not saved\n"); + } + + for(u8 i = 0; i < (NCA_IVFC_HASH_DATA_LAYER_COUNT + 1); i++) + { + NcaHashInfoLayerPatch *layer_patch = (i < NCA_IVFC_HASH_DATA_LAYER_COUNT ? &(romfs_patch.cur_format_patch.hash_data_layer_patch[i]) : &(romfs_patch.cur_format_patch.hash_target_layer_patch)); + if (!layer_patch->size || !layer_patch->data) continue; + + char path[64]; + sprintf(path, "sdmc:/nxdt_test/romfs_patch_l%u.bin", i); + + tmp_file = fopen(path, "wb"); + if (tmp_file) + { + fwrite(layer_patch->data, 1, layer_patch->size, tmp_file); + fclose(tmp_file); + tmp_file = NULL; + printf("romfs patch #%u saved\n", i); + } else { + printf("romfs patch #%u not saved\n", i); + } + + consoleUpdate(NULL); + } + + if (!ncaEncryptHeader(nca_ctx)) + { + printf("nca header mod not encrypted\n"); + goto out2; + } + + printf("nca header mod encrypted\n"); + consoleUpdate(NULL); + + tmp_file = fopen("sdmc:/nxdt_test/nca_header_mod.bin", "wb"); + if (tmp_file) + { + fwrite(&(nca_ctx->header), 1, sizeof(NcaHeader), tmp_file); + fclose(tmp_file); + tmp_file = NULL; + printf("nca header mod saved\n"); + } else { + printf("nca header mod not saved\n"); + } + + + + + @@ -287,6 +376,8 @@ out2: if (tmp_file) fclose(tmp_file); + romfsFreeFileEntryPatch(&romfs_patch); + romfsFreeContext(&romfs_ctx); if (serviceIsActive(&(ncm_storage.s))) ncmContentStorageClose(&ncm_storage); diff --git a/source/nca.c b/source/nca.c index ee90464..a393861 100644 --- a/source/nca.c +++ b/source/nca.c @@ -137,7 +137,7 @@ bool ncaEncryptHeader(NcaContext *ctx) { case NcaVersion_Nca3: crypt_res = aes128XtsNintendoCrypt(&hdr_aes_ctx, ctx->header.fs_headers, ctx->header.fs_headers, NCA_FULL_HEADER_LENGTH - NCA_HEADER_LENGTH, 2, NCA_AES_XTS_SECTOR_SIZE, true); - if (crypt_res != NCA_FULL_HEADER_LENGTH) + if (crypt_res != (NCA_FULL_HEADER_LENGTH - NCA_HEADER_LENGTH)) { LOGFILE("Error encrypting NCA3 \"%s\" FS section headers!", ctx->content_id_str); return false; @@ -485,8 +485,8 @@ bool ncaGenerateHierarchicalSha256Patch(NcaFsSectionContext *ctx, const void *da } /* 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); + out->hash_target_layer_patch.data = _ncaGenerateEncryptedFsSectionBlock(ctx, hash_target_block + hash_target_data_offset, data_size, hash_target_layer_offset + data_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!"); @@ -505,22 +505,183 @@ bool ncaGenerateHierarchicalSha256Patch(NcaFsSectionContext *ctx, const void *da success = true; exit: - mutexUnlock(&g_ncaCryptoBufferMutex); - if (hash_target_block) free(hash_target_block); if (hash_data_layer) free(hash_data_layer); + if (!success) ncaFreeHierarchicalSha256Patch(out); + + mutexUnlock(&g_ncaCryptoBufferMutex); + return success; } - - - - - - - +bool ncaGenerateHierarchicalIntegrityPatch(NcaFsSectionContext *ctx, const void *data, u64 data_size, u64 data_offset, NcaHierarchicalIntegrityPatch *out) +{ + mutexLock(&g_ncaCryptoBufferMutex); + + NcaContext *nca_ctx = NULL; + bool success = false; + + u8 *cur_data = NULL; + u64 cur_data_offset = data_offset; + u64 cur_data_size = data_size; + + u8 *hash_data_block = NULL, *hash_target_block = NULL; + + if (!ctx || !(nca_ctx = (NcaContext*)ctx->nca_ctx) || !ctx->header || ctx->header->hash_type != NcaHashType_HierarchicalIntegrity || !data || !data_size || !out || \ + data_offset >= ctx->header->hash_info.hierarchical_integrity.hash_target_layer_info.size || \ + (data_offset + data_size) > ctx->header->hash_info.hierarchical_integrity.hash_target_layer_info.size) + { + LOGFILE("Invalid parameters!"); + goto exit; + } + + /* Process each IVFC layer */ + for(u8 i = (NCA_IVFC_HASH_DATA_LAYER_COUNT + 1); i > 0; i--) + { + NcaHierarchicalIntegrityLayerInfo *cur_layer_info = (i > NCA_IVFC_HASH_DATA_LAYER_COUNT ? &(ctx->header->hash_info.hierarchical_integrity.hash_target_layer_info) : \ + &(ctx->header->hash_info.hierarchical_integrity.hash_data_layer_info[i - 1])); + + NcaHierarchicalIntegrityLayerInfo *parent_layer_info = (i > 1 ? &(ctx->header->hash_info.hierarchical_integrity.hash_data_layer_info[i - 2]) : NULL); + + NcaHashInfoLayerPatch *cur_layer_patch = (i > NCA_IVFC_HASH_DATA_LAYER_COUNT ? &(out->hash_target_layer_patch) : &(out->hash_data_layer_patch[i - 1])); + + if (!cur_layer_info->size || !cur_layer_info->block_size || (parent_layer_info && (!parent_layer_info->size || !parent_layer_info->block_size))) + { + LOGFILE("Invalid HierarchicalIntegrity parent/child layer!"); + goto exit; + } + + /* Calculate required offsets and sizes */ + u64 hash_block_size = NCA_IVFC_BLOCK_SIZE(cur_layer_info->block_size); + + u64 hash_data_layer_offset = 0; + u64 hash_data_start_offset = 0, hash_data_end_offset = 0, hash_data_size = 0; + + u64 hash_target_layer_offset = cur_layer_info->offset, hash_target_layer_size = cur_layer_info->size; + u64 hash_target_start_offset = 0, hash_target_end_offset = 0, hash_target_size = 0, hash_target_data_offset = 0; + + if (parent_layer_info) + { + /* HierarchicalIntegrity layer from L1 to L5 */ + hash_data_layer_offset = parent_layer_info->offset; + + hash_data_start_offset = ((cur_data_offset / hash_block_size) * SHA256_HASH_SIZE); + hash_data_end_offset = (((cur_data_offset + cur_data_size) / hash_block_size) * SHA256_HASH_SIZE); + hash_data_size = (hash_data_end_offset != hash_data_start_offset ? (hash_data_end_offset - hash_data_start_offset) : SHA256_HASH_SIZE); + + hash_target_start_offset = (hash_target_layer_offset + ALIGN_DOWN(cur_data_offset, hash_block_size)); + hash_target_end_offset = (hash_target_layer_offset + ALIGN_UP(cur_data_offset + cur_data_size, hash_block_size)); + hash_target_size = (hash_target_end_offset - hash_target_start_offset); + } else { + /* HierarchicalIntegrity master layer */ + /* The master hash is calculated over the whole layer and saved to the NCA FS header */ + hash_target_start_offset = hash_target_layer_offset; + hash_target_end_offset = (hash_target_layer_offset + hash_target_layer_size); + hash_target_size = hash_target_layer_size; + } + + hash_target_data_offset = (cur_data_offset - ALIGN_DOWN(cur_data_offset, hash_block_size)); + + /* Allocate memory for our hash target layer block */ + hash_target_block = calloc(hash_target_size, sizeof(u8)); + if (!hash_target_block) + { + LOGFILE("Unable to allocate 0x%lX bytes for the HierarchicalIntegrity hash target layer block!"); + goto exit; + } + + /* Adjust hash target layer end offset and size if needed to avoid read errors */ + 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); + hash_target_size = (hash_target_end_offset - hash_target_start_offset); + } + + /* Read hash target layer block */ + if (!_ncaReadFsSection(ctx, hash_target_block, hash_target_size, hash_target_start_offset, false)) + { + LOGFILE("Failed to read HierarchicalIntegrity hash target layer block!"); + goto exit; + } + + /* Replace hash target layer block data */ + memcpy(hash_target_block + hash_target_data_offset, (i > NCA_IVFC_HASH_DATA_LAYER_COUNT ? data : cur_data), cur_data_size); + + if (parent_layer_info) + { + /* Allocate memory for our hash data layer block */ + hash_data_block = calloc(hash_data_size, sizeof(u8)); + if (!hash_data_block) + { + LOGFILE("Unable to allocate 0x%lX bytes for the HierarchicalIntegrity hash data layer block!"); + goto exit; + } + + /* Read hash target layer block */ + if (!_ncaReadFsSection(ctx, hash_data_block, hash_data_size, hash_data_layer_offset + hash_data_start_offset, false)) + { + LOGFILE("Failed to read HierarchicalIntegrity hash data layer block!"); + goto exit; + } + + /* Recalculate hashes */ + /* Size isn't truncated for blocks smaller than the hash block size, unlike HierarchicalSha256, so we just keep using the same hash block size throughout the loop */ + /* For these specific cases, the rest of the block should be filled with zeroes (already taken care of by using calloc()) */ + for(u64 i = 0, j = 0; i < hash_target_size; i += hash_block_size, j++) sha256CalculateHash(hash_data_block + (j * SHA256_HASH_SIZE), hash_target_block + i, hash_block_size); + } else { + /* Recalculate master hash from hash info block */ + sha256CalculateHash(ctx->header->hash_info.hierarchical_integrity.master_hash, hash_target_block, hash_target_size); + } + + /* Reencrypt hash target layer block */ + cur_layer_patch->data = _ncaGenerateEncryptedFsSectionBlock(ctx, hash_target_block + hash_target_data_offset, cur_data_size, hash_target_layer_offset + cur_data_offset, \ + &(cur_layer_patch->size), &(cur_layer_patch->offset), false); + if (!cur_layer_patch->data) + { + LOGFILE("Failed to generate encrypted HierarchicalIntegrity hash target layer block!"); + goto exit; + } + + /* Free hash target layer block */ + free(hash_target_block); + hash_target_block = NULL; + + if (parent_layer_info) + { + /* Free previous layer data if necessary */ + if (cur_data) free(cur_data); + + /* Prepare data for the next target layer */ + cur_data = hash_data_block; + cur_data_offset = hash_data_start_offset; + cur_data_size = hash_data_size; + hash_data_block = NULL; + } + } + + /* 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: + if (hash_data_block) free(hash_data_block); + + if (hash_target_block) free(hash_target_block); + + if (cur_data) free(cur_data); + + if (!success) ncaFreeHierarchicalIntegrityPatch(out); + + mutexUnlock(&g_ncaCryptoBufferMutex); + + return success; +} static size_t aes128XtsNintendoCrypt(Aes128XtsContext *ctx, void *dst, const void *src, size_t size, u64 sector, size_t sector_size, bool encrypt) { @@ -1016,13 +1177,13 @@ static void *_ncaGenerateEncryptedFsSectionBlock(NcaFsSectionContext *ctx, const success = true; exit: - if (lock) mutexUnlock(&g_ncaCryptoBufferMutex); - if (!success && out) { free(out); out = NULL; } + if (lock) mutexUnlock(&g_ncaCryptoBufferMutex); + return out; } diff --git a/source/nca.h b/source/nca.h index 37ade98..bfb0ac8 100644 --- a/source/nca.h +++ b/source/nca.h @@ -328,13 +328,13 @@ bool ncaReadFsSection(NcaFsSectionContext *ctx, void *out, u64 read_size, u64 of /// Returns a pointer to a heap-allocated buffer used to encrypt the input plaintext data, based on the encryption type used by the input NCA FS section, as well as its offset and size. /// Input offset must be relative to the start of the NCA FS section. /// Output size and offset are guaranteed to be aligned to the AES sector size used by the encryption type from the FS section. -/// Output offset is relative to the start of the NCA content file, making it easier to use the output encrypted block to replace data in-place while writing a NCA. +/// Output offset is relative to the start of the NCA content file, making it easier to use the output encrypted block to seamlessly replace data while dumping 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. +/// As such, this function is not designed to generate 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. @@ -346,23 +346,23 @@ NX_INLINE void ncaFreeHierarchicalSha256Patch(NcaHierarchicalSha256Patch *patch) memset(patch, 0, sizeof(NcaHierarchicalSha256Patch)); } - - - - - +/// Generates HierarchicalIntegrity 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 HierarchicalIntegrity 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 designed to generate more than one patch per HierarchicalIntegrity FS section. +bool ncaGenerateHierarchicalIntegrityPatch(NcaFsSectionContext *ctx, const void *data, u64 data_size, u64 data_offset, NcaHierarchicalIntegrityPatch *out); /// 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++) + for(u8 i = 0; i < (NCA_IVFC_HASH_DATA_LAYER_COUNT + 1); i++) { - if (patch->hash_data_layer_patch[i].data) free(patch->hash_data_layer_patch[i].data); + NcaHashInfoLayerPatch *layer_patch = (i < NCA_IVFC_HASH_DATA_LAYER_COUNT ? &(patch->hash_data_layer_patch[i]) : &(patch->hash_target_layer_patch)); + if (layer_patch->data) free(layer_patch->data); } - if (patch->hash_target_layer_patch.data) free(patch->hash_target_layer_patch.data); memset(patch, 0, sizeof(NcaHierarchicalIntegrityPatch)); } @@ -413,7 +413,6 @@ NX_INLINE bool ncaValidateHierarchicalSha256Offsets(NcaHierarchicalSha256 *hiera { if (!hierarchical_sha256 || !section_size || !hierarchical_sha256->hash_block_size || hierarchical_sha256->layer_count != NCA_HIERARCHICAL_SHA256_LAYER_COUNT) return false; - /* Validate layer offsets and sizes */ for(u8 i = 0; i < NCA_HIERARCHICAL_SHA256_LAYER_COUNT; i++) { NcaHierarchicalSha256LayerInfo *layer_info = (i == 0 ? &(hierarchical_sha256->hash_data_layer_info) : &(hierarchical_sha256->hash_target_layer_info)); @@ -428,7 +427,6 @@ NX_INLINE bool ncaValidateHierarchicalIntegrityOffsets(NcaHierarchicalIntegrity if (!hierarchical_integrity || !section_size || __builtin_bswap32(hierarchical_integrity->magic) != NCA_IVFC_MAGIC || !hierarchical_integrity->master_hash_size || \ hierarchical_integrity->layer_count != NCA_IVFC_LAYER_COUNT) return false; - /* Validate layer offsets and sizes */ for(u8 i = 0; i < (NCA_IVFC_HASH_DATA_LAYER_COUNT + 1); i++) { NcaHierarchicalIntegrityLayerInfo *layer_info = (i < NCA_IVFC_HASH_DATA_LAYER_COUNT ? &(hierarchical_integrity->hash_data_layer_info[i]) : &(hierarchical_integrity->hash_target_layer_info)); diff --git a/source/romfs.c b/source/romfs.c index f51905d..d846b3c 100644 --- a/source/romfs.c +++ b/source/romfs.c @@ -490,14 +490,12 @@ bool romfsGenerateFileEntryPatch(RomFileSystemContext *ctx, RomFileSystemFileEnt 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; - + success = ncaGenerateHierarchicalIntegrityPatch(ctx->nca_fs_ctx, data, data_size, fs_offset, &(out->cur_format_patch)); + if (!success) LOGFILE("Failed to generate 0x%lX bytes HierarchicalIntegrity patch at offset 0x%lX for RomFS file entry!", data_size, fs_offset); } return success;