diff --git a/include/core/nca_storage.h b/include/core/nca_storage.h index 0bfaa21..05c7719 100644 --- a/include/core/nca_storage.h +++ b/include/core/nca_storage.h @@ -49,12 +49,11 @@ typedef struct { BucketTreeContext *compressed_storage; ///< Compressed storage context. } NcaStorageContext; -/// Initializes a NCA storage context using a NCA FS section context. -bool ncaStorageInitializeContext(NcaStorageContext *out, NcaFsSectionContext *nca_fs_ctx); - -/// Sets a storage from the provided Base NcaStorageContext as the original substorage for the provided Patch NcaStorageContext's Indirect Storage. -/// Needed to perform combined reads between a base NCA and a patch NCA. -bool ncaStorageSetPatchOriginalSubStorage(NcaStorageContext *patch_ctx, NcaStorageContext *base_ctx); +/// Initializes a NCA storage context using a NCA FS section context, optionally providing a pointer to a base NcaStorageContext. +/// 'base_ctx' must be provided if dealing with a patch NCA. One of its storages will be set as the original substorage for the initialized NcaStorageContext's Indirect Storage. +/// This is needed to perform combined reads between a base NCA and a patch NCA. +/// 'base_ctx' shall be NULL if dealing with a base NCA. +bool ncaStorageInitializeContext(NcaStorageContext *out, NcaFsSectionContext *nca_fs_ctx, NcaStorageContext *base_ctx); /// Retrieves the underlying NCA FS section's hierarchical hash target layer extents. Virtual extents may be returned, depending on the base storage type. /// Output offset is relative to the start of the NCA FS section. diff --git a/source/core/nca_storage.c b/source/core/nca_storage.c index 713c1a7..b0b0075 100644 --- a/source/core/nca_storage.c +++ b/source/core/nca_storage.c @@ -26,11 +26,12 @@ static bool ncaStorageInitializeBucketTreeContext(BucketTreeContext **out, NcaFsSectionContext *nca_fs_ctx, u8 storage_type); static bool ncaStorageInitializeCompressedStorageBucketTreeContext(NcaStorageContext *out, NcaFsSectionContext *nca_fs_ctx); +static bool ncaStorageSetPatchOriginalSubStorage(NcaStorageContext *patch_ctx, NcaFsSectionContext *patch_nca_fs_ctx, NcaStorageContext *base_ctx); -bool ncaStorageInitializeContext(NcaStorageContext *out, NcaFsSectionContext *nca_fs_ctx) +bool ncaStorageInitializeContext(NcaStorageContext *out, NcaFsSectionContext *nca_fs_ctx, NcaStorageContext *base_ctx) { if (!out || !nca_fs_ctx || !nca_fs_ctx->enabled || (nca_fs_ctx->section_type == NcaFsSectionType_PatchRomFs && \ - (!nca_fs_ctx->has_patch_indirect_layer || !nca_fs_ctx->has_patch_aes_ctr_ex_layer || nca_fs_ctx->has_sparse_layer))) + (!nca_fs_ctx->has_patch_indirect_layer || !nca_fs_ctx->has_patch_aes_ctr_ex_layer || nca_fs_ctx->has_sparse_layer || !base_ctx))) { LOG_MSG_ERROR("Invalid parameters!"); return false; @@ -64,11 +65,11 @@ bool ncaStorageInitializeContext(NcaStorageContext *out, NcaFsSectionContext *nc if (!ncaStorageInitializeBucketTreeContext(&(out->aes_ctr_ex_storage), nca_fs_ctx, BucketTreeStorageType_AesCtrEx) || \ !ncaStorageInitializeBucketTreeContext(&(out->indirect_storage), nca_fs_ctx, BucketTreeStorageType_Indirect)) goto end; - /* Set AesCtrEx layer's substorage. */ + /* Set AesCtrEx layer's substorage (plain NCA reads). */ if (!bktrSetRegularSubStorage(out->aes_ctr_ex_storage, nca_fs_ctx)) goto end; - /* Set Indirect layer's AesCtrEx substorage. */ - /* Original substorage (index 0) must be manually set at a later time using ncaStorageSetPatchOriginalSubStorage(). */ + /* Set Indirect layer's substorages (Base + AesCtrEx). */ + if (!ncaStorageSetPatchOriginalSubStorage(out, nca_fs_ctx, base_ctx)) goto end; if (!bktrSetBucketTreeSubStorage(out->indirect_storage, out->aes_ctr_ex_storage, 1)) goto end; /* Update base storage type. */ @@ -90,55 +91,6 @@ end: return success; } -bool ncaStorageSetPatchOriginalSubStorage(NcaStorageContext *patch_ctx, NcaStorageContext *base_ctx) -{ - NcaContext *patch_nca_ctx = NULL, *base_nca_ctx = NULL; - - if (!ncaStorageIsValidContext(patch_ctx) || !ncaStorageIsValidContext(base_ctx) || patch_ctx->nca_fs_ctx == base_ctx->nca_fs_ctx || \ - !(patch_nca_ctx = patch_ctx->nca_fs_ctx->nca_ctx) || !(base_nca_ctx = base_ctx->nca_fs_ctx->nca_ctx) || \ - patch_ctx->nca_fs_ctx->section_type != NcaFsSectionType_PatchRomFs || base_ctx->nca_fs_ctx->section_type != NcaFsSectionType_RomFs || \ - patch_nca_ctx->header.program_id != base_nca_ctx->header.program_id || patch_nca_ctx->header.content_type != base_nca_ctx->header.content_type || \ - patch_nca_ctx->id_offset != base_nca_ctx->id_offset || patch_nca_ctx->title_version.value < base_nca_ctx->title_version.value || \ - (patch_ctx->base_storage_type != NcaStorageBaseStorageType_Indirect && patch_ctx->base_storage_type != NcaStorageBaseStorageType_Compressed) || \ - !patch_ctx->indirect_storage || !patch_ctx->aes_ctr_ex_storage || (base_ctx->base_storage_type == NcaStorageBaseStorageType_Compressed && \ - patch_ctx->base_storage_type != NcaStorageBaseStorageType_Compressed)) - { - LOG_MSG_ERROR("Invalid parameters!"); - return false; - } - - bool success = false; - - /* Set original substorage. */ - switch(base_ctx->base_storage_type) - { - case NcaStorageBaseStorageType_Regular: - case NcaStorageBaseStorageType_Compressed: - /* Regular: we just make the Patch's Indirect Storage's SubStorage #0 point to the Base NCA FS section as-is and call it a day. */ - - /* Compressed: if a Compressed Storage is available in the Base NCA FS section, the corresponding Patch NCA FS section *must* also have one. */ - /* This is because the Patch's Compressed Storage also takes care of LZ4-compressed chunks within Base NCA FS section areas. */ - /* Furthermore, the Patch's Indirect Storage already provides section-relative physical offsets for the Base NCA FS section. */ - /* In other words, we don't need to parse the Base NCA's Compressed Storage on every read. */ - success = bktrSetRegularSubStorage(patch_ctx->indirect_storage, base_ctx->nca_fs_ctx); - break; - case NcaStorageBaseStorageType_Sparse: - /* Sparse: we should *always* arrive here if a Sparse Storage is available in the Base NCA FS section, regardless if a Compressed Storage is available or not. */ - /* This is because compression bucket trees are non-existent in Base NCA FS sections that have both Sparse and Compressed Storages. */ - /* Furthermore, in these cases, the compression BucketInfo from the NCA FS section header references the full, patched FS section, so we can't really use it. */ - /* We just completely ignore the Base's Compressed Storage and let the Patch's Compressed Storage take care of LZ4-compressed chunks. */ - /* Anyway, we just make the Patch's Indirect Storage's SubStorage #0 point to the Base's Sparse Storage and call it a day. */ - success = bktrSetBucketTreeSubStorage(patch_ctx->indirect_storage, base_ctx->sparse_storage, 0); - break; - default: - break; - } - - if (!success) LOG_MSG_ERROR("Failed to set base storage to patch storage! (0x%02X, 0x%02X).", base_ctx->base_storage_type, patch_ctx->base_storage_type); - - return success; -} - bool ncaStorageGetHashTargetExtents(NcaStorageContext *ctx, u64 *out_offset, u64 *out_size) { if (!ncaStorageIsValidContext(ctx) || (!out_offset && !out_size)) @@ -370,3 +322,49 @@ end: return success; } + +static bool ncaStorageSetPatchOriginalSubStorage(NcaStorageContext *patch_ctx, NcaFsSectionContext *patch_nca_fs_ctx, NcaStorageContext *base_ctx) +{ + NcaContext *patch_nca_ctx = NULL, *base_nca_ctx = NULL; + + if (!patch_ctx || !patch_ctx->indirect_storage || !patch_ctx->aes_ctr_ex_storage || !patch_nca_fs_ctx || !ncaStorageIsValidContext(base_ctx) || \ + !(patch_nca_ctx = patch_nca_fs_ctx->nca_ctx) || !(base_nca_ctx = base_ctx->nca_fs_ctx->nca_ctx) || \ + patch_nca_fs_ctx->section_type != NcaFsSectionType_PatchRomFs || base_ctx->nca_fs_ctx->section_type != NcaFsSectionType_RomFs || \ + patch_nca_ctx->header.program_id != base_nca_ctx->header.program_id || patch_nca_ctx->header.content_type != base_nca_ctx->header.content_type || \ + patch_nca_ctx->id_offset != base_nca_ctx->id_offset || patch_nca_ctx->title_version.value < base_nca_ctx->title_version.value) + { + LOG_MSG_ERROR("Invalid parameters!"); + return false; + } + + bool success = false; + + /* Set original substorage. */ + switch(base_ctx->base_storage_type) + { + case NcaStorageBaseStorageType_Regular: + case NcaStorageBaseStorageType_Compressed: + /* Regular: we just make the Patch's Indirect Storage's SubStorage #0 point to the Base NCA FS section as-is and call it a day. */ + + /* Compressed: if a Compressed Storage is available in the Base NCA FS section, the corresponding Patch NCA FS section *must* also have one. */ + /* This is because the Patch's Compressed Storage also takes care of LZ4-compressed chunks within Base NCA FS section areas. */ + /* Furthermore, the Patch's Indirect Storage already provides section-relative physical offsets for the Base NCA FS section. */ + /* In other words, we don't need to parse the Base NCA's Compressed Storage on every read. */ + success = bktrSetRegularSubStorage(patch_ctx->indirect_storage, base_ctx->nca_fs_ctx); + break; + case NcaStorageBaseStorageType_Sparse: + /* Sparse: we should *always* arrive here if a Sparse Storage is available in the Base NCA FS section, regardless if a Compressed Storage is available or not. */ + /* This is because compression bucket trees are non-existent in Base NCA FS sections that have both Sparse and Compressed Storages. */ + /* Furthermore, in these cases, the compression BucketInfo from the NCA FS section header references the full, patched FS section, so we can't really use it. */ + /* We just completely ignore the Base's Compressed Storage and let the Patch's Compressed Storage take care of LZ4-compressed chunks. */ + /* Anyway, we just make the Patch's Indirect Storage's SubStorage #0 point to the Base's Sparse Storage and call it a day. */ + success = bktrSetBucketTreeSubStorage(patch_ctx->indirect_storage, base_ctx->sparse_storage, 0); + break; + default: + break; + } + + if (!success) LOG_MSG_ERROR("Failed to set base storage to patch storage! (0x%02X, 0x%02X).", base_ctx->base_storage_type, patch_ctx->base_storage_type); + + return success; +} diff --git a/source/core/pfs.c b/source/core/pfs.c index 31e9468..8f91515 100644 --- a/source/core/pfs.c +++ b/source/core/pfs.c @@ -47,7 +47,7 @@ bool pfsInitializeContext(PartitionFileSystemContext *out, NcaFsSectionContext * /* Initialize NCA storage context. */ NcaStorageContext *storage_ctx = &(out->storage_ctx); - if (!ncaStorageInitializeContext(storage_ctx, nca_fs_ctx)) + if (!ncaStorageInitializeContext(storage_ctx, nca_fs_ctx, NULL)) { LOG_MSG_ERROR("Failed to initialize NCA storage context!"); goto end; diff --git a/source/core/romfs.c b/source/core/romfs.c index cde8633..0b2f3ea 100644 --- a/source/core/romfs.c +++ b/source/core/romfs.c @@ -57,7 +57,7 @@ bool romfsInitializeContext(RomFileSystemContext *out, NcaFsSectionContext *base bool is_nca0_romfs = (base_nca_fs_ctx->section_type == NcaFsSectionType_Nca0RomFs); /* Initialize base NCA storage context. */ - if (!missing_base_romfs && !ncaStorageInitializeContext(base_storage_ctx, base_nca_fs_ctx)) + if (!missing_base_romfs && !ncaStorageInitializeContext(base_storage_ctx, base_nca_fs_ctx, NULL)) { LOG_MSG_ERROR("Failed to initialize base NCA storage context!"); goto end; @@ -66,19 +66,12 @@ bool romfsInitializeContext(RomFileSystemContext *out, NcaFsSectionContext *base if (patch_nca_fs_ctx) { /* Initialize base NCA storage context. */ - if (!ncaStorageInitializeContext(patch_storage_ctx, patch_nca_fs_ctx)) + if (!ncaStorageInitializeContext(patch_storage_ctx, patch_nca_fs_ctx, base_storage_ctx)) { LOG_MSG_ERROR("Failed to initialize patch NCA storage context!"); goto end; } - /* Set patch NCA storage original substorage, if available. */ - if (!missing_base_romfs && !ncaStorageSetPatchOriginalSubStorage(patch_storage_ctx, base_storage_ctx)) - { - LOG_MSG_ERROR("Failed to set patch NCA storage context's original substorage!"); - goto end; - } - /* Set default NCA FS storage context. */ out->is_patch = true; out->default_storage_ctx = patch_storage_ctx;