Fix bugs + improve bktr interface.

* Check for sparse layers in nsp_dumper and xml_generator PoCs before attempting to initialize content-type-specific contexts.

* system_title_dumper now builds.

* gamecardCloseStorageArea() no longer returns prematurely on application exit.

* Fix section size for sparse sections in ncaInitializeFsSectionContext().

* Fix ncaFsSectionValidateHashDataBoundaries().

* Implement bktrInitializeIndirectStorage() and bktrInitializeAesCtrExStorage().
This commit is contained in:
Pablo Curiel 2022-06-29 14:41:58 +02:00
parent 4507af9e34
commit 8b0ed76011
7 changed files with 286 additions and 207 deletions

View File

@ -312,85 +312,88 @@ static void dump_thread_func(void *arg)
goto end;
}
switch(content_info->content_type)
if (!cur_nca_ctx->fs_ctx[0].has_sparse_layer)
{
case NcmContentType_Program:
switch(content_info->content_type)
{
// don't proceed if we didn't allocate programinfo ctx
if (!program_count || !program_info_ctx) break;
ProgramInfoContext *cur_program_info_ctx = &(program_info_ctx[program_idx]);
if (!programInfoInitializeContext(cur_program_info_ctx, cur_nca_ctx))
case NcmContentType_Program:
{
consolePrint("initialize program info ctx failed (%s)\n", cur_nca_ctx->content_id_str);
goto end;
// don't proceed if we didn't allocate programinfo ctx or if we're dealing with a sparse layer
if (!program_count || !program_info_ctx) break;
ProgramInfoContext *cur_program_info_ctx = &(program_info_ctx[program_idx]);
if (!programInfoInitializeContext(cur_program_info_ctx, cur_nca_ctx))
{
consolePrint("initialize program info ctx failed (%s)\n", cur_nca_ctx->content_id_str);
goto end;
}
if (!programInfoGenerateAuthoringToolXml(cur_program_info_ctx))
{
consolePrint("program info xml failed (%s)\n", cur_nca_ctx->content_id_str);
goto end;
}
program_idx++;
consolePrint("initialize program info ctx succeeded (%s)\n", cur_nca_ctx->content_id_str);
break;
}
if (append_authoringtool_data && !programInfoGenerateAuthoringToolXml(cur_program_info_ctx))
case NcmContentType_Control:
{
consolePrint("program info xml failed (%s)\n", cur_nca_ctx->content_id_str);
goto end;
// don't proceed if we didn't allocate nacp ctx
if (!control_count || !nacp_ctx) break;
NacpContext *cur_nacp_ctx = &(nacp_ctx[control_idx]);
if (!nacpInitializeContext(cur_nacp_ctx, cur_nca_ctx))
{
consolePrint("initialize nacp ctx failed (%s)\n", cur_nca_ctx->content_id_str);
goto end;
}
if (!nacpGenerateNcaPatch(cur_nacp_ctx, patch_sua, patch_screenshot, patch_video_capture, patch_hdcp))
{
consolePrint("nacp nca patch failed (%s)\n", cur_nca_ctx->content_id_str);
goto end;
}
if (append_authoringtool_data && !nacpGenerateAuthoringToolXml(cur_nacp_ctx, title_info->version.value, cnmtGetRequiredTitleVersion(&cnmt_ctx)))
{
consolePrint("nacp xml failed (%s)\n", cur_nca_ctx->content_id_str);
goto end;
}
control_idx++;
consolePrint("initialize nacp ctx succeeded (%s)\n", cur_nca_ctx->content_id_str);
break;
}
program_idx++;
consolePrint("initialize program info ctx succeeded (%s)\n", cur_nca_ctx->content_id_str);
break;
case NcmContentType_LegalInformation:
{
// don't proceed if we didn't allocate legalinfo ctx
if (!legal_info_count || !legal_info_ctx) break;
LegalInfoContext *cur_legal_info_ctx = &(legal_info_ctx[legal_info_idx]);
if (!legalInfoInitializeContext(cur_legal_info_ctx, cur_nca_ctx))
{
consolePrint("initialize legal info ctx failed (%s)\n", cur_nca_ctx->content_id_str);
goto end;
}
legal_info_idx++;
consolePrint("initialize legal info ctx succeeded (%s)\n", cur_nca_ctx->content_id_str);
break;
}
default:
break;
}
case NcmContentType_Control:
{
// don't proceed if we didn't allocate nacp ctx
if (!control_count || !nacp_ctx) break;
NacpContext *cur_nacp_ctx = &(nacp_ctx[control_idx]);
if (!nacpInitializeContext(cur_nacp_ctx, cur_nca_ctx))
{
consolePrint("initialize nacp ctx failed (%s)\n", cur_nca_ctx->content_id_str);
goto end;
}
if (!nacpGenerateNcaPatch(cur_nacp_ctx, patch_sua, patch_screenshot, patch_video_capture, patch_hdcp))
{
consolePrint("nacp nca patch failed (%s)\n", cur_nca_ctx->content_id_str);
goto end;
}
if (append_authoringtool_data && !nacpGenerateAuthoringToolXml(cur_nacp_ctx, title_info->version.value, cnmtGetRequiredTitleVersion(&cnmt_ctx)))
{
consolePrint("nacp xml failed (%s)\n", cur_nca_ctx->content_id_str);
goto end;
}
control_idx++;
consolePrint("initialize nacp ctx succeeded (%s)\n", cur_nca_ctx->content_id_str);
break;
}
case NcmContentType_LegalInformation:
{
// don't proceed if we didn't allocate legalinfo ctx
if (!legal_info_count || !legal_info_ctx) break;
LegalInfoContext *cur_legal_info_ctx = &(legal_info_ctx[legal_info_idx]);
if (!legalInfoInitializeContext(cur_legal_info_ctx, cur_nca_ctx))
{
consolePrint("initialize legal info ctx failed (%s)\n", cur_nca_ctx->content_id_str);
goto end;
}
legal_info_idx++;
consolePrint("initialize legal info ctx succeeded (%s)\n", cur_nca_ctx->content_id_str);
break;
}
default:
break;
}
if (!ncaEncryptHeader(cur_nca_ctx))

View File

@ -98,7 +98,7 @@ static void dumpPartitionFs(TitleInfo *info, NcaFsSectionContext *nca_fs_ctx)
}
snprintf(path, sizeof(path), OUTPATH "/%016lX - %s/%s (%s)/Section %u (%s)", info->meta_key.id, info->app_metadata->lang_entry.name, ((NcaContext*)nca_fs_ctx->nca_ctx)->content_id_str, \
titleGetNcmContentTypeName(((NcaContext*)nca_fs_ctx->nca_ctx)->content_type), nca_fs_ctx->section_num, ncaGetFsSectionTypeName(nca_fs_ctx));
titleGetNcmContentTypeName(((NcaContext*)nca_fs_ctx->nca_ctx)->content_type), nca_fs_ctx->section_idx, ncaGetFsSectionTypeName(nca_fs_ctx));
utilsCreateDirectoryTree(path, true);
path_len = strlen(path);
@ -174,7 +174,7 @@ static void dumpRomFs(TitleInfo *info, NcaFsSectionContext *nca_fs_ctx)
}
snprintf(path, sizeof(path), OUTPATH "/%016lX - %s/%s (%s)/Section %u (%s)", info->meta_key.id, info->app_metadata->lang_entry.name, ((NcaContext*)nca_fs_ctx->nca_ctx)->content_id_str, \
titleGetNcmContentTypeName(((NcaContext*)nca_fs_ctx->nca_ctx)->content_type), nca_fs_ctx->section_num, ncaGetFsSectionTypeName(nca_fs_ctx));
titleGetNcmContentTypeName(((NcaContext*)nca_fs_ctx->nca_ctx)->content_type), nca_fs_ctx->section_idx, ncaGetFsSectionTypeName(nca_fs_ctx));
utilsCreateDirectoryTree(path, true);
path_len = strlen(path);

View File

@ -289,6 +289,8 @@ int main(int argc, char *argv[])
consolePrint("%s #%u initialize nca ctx succeeded\n", titleGetNcmContentTypeName(content_info->content_type), content_info->id_offset);
if (nca_ctx[j].fs_ctx[0].has_sparse_layer) continue;
switch(content_info->content_type)
{
case NcmContentType_Program:
@ -352,9 +354,6 @@ int main(int argc, char *argv[])
{
consolePrint("cnmt xml succeeded\n");
//sprintf(path, "sdmc:/at_xml/%016lX/%s.cnmt", app_metadata[selected_idx]->title_id, cnmt_ctx.nca_ctx->content_id_str);
//writeFile(cnmt_ctx.raw_data, cnmt_ctx.raw_data_size, path);
sprintf(path, "sdmc:/at_xml/%016lX/%s.cnmt.xml", app_metadata[selected_idx]->title_id, cnmt_ctx.nca_ctx->content_id_str);
writeFile(cnmt_ctx.authoring_tool_xml, cnmt_ctx.authoring_tool_xml_size, path);
} else {

View File

@ -94,14 +94,15 @@ typedef struct {
NXDT_ASSERT(BktrAesCtrExStorageBlock, 0x4000);
typedef struct {
RomFileSystemContext base_romfs_ctx; ///< Base NCA RomFS context.
RomFileSystemContext patch_romfs_ctx; ///< Update NCA RomFS context. Must be used with RomFS directory/file entry functions, because it holds the updated directory/file tables.
u64 offset; ///< Patched RomFS image offset (relative to the start of the update NCA FS section).
u64 size; ///< Patched RomFS image size.
u64 body_offset; ///< Patched RomFS image file data body offset (relative to the start of the RomFS).
BktrIndirectStorageBlock *indirect_block; ///< BKTR Indirect Storage Block.
BktrAesCtrExStorageBlock *aes_ctr_ex_block; ///< BKTR AesCtrEx Storage Block.
bool missing_base_romfs; ///< If true, only Patch RomFS data is used. Needed for games with base Program NCAs without a RomFS section (e.g. Fortnite, World of Tanks Blitz, etc.).
RomFileSystemContext base_romfs_ctx; ///< Base NCA RomFS context.
RomFileSystemContext patch_romfs_ctx; ///< Update NCA RomFS context. Must be used with RomFS directory/file entry functions, because it holds the updated directory/file tables.
u64 offset; ///< Patched RomFS image offset (relative to the start of the update NCA FS section).
u64 size; ///< Patched RomFS image size.
u64 body_offset; ///< Patched RomFS image file data body offset (relative to the start of the RomFS).
BktrIndirectStorageBlock *indirect_block; ///< BKTR Indirect Storage Block.
BktrAesCtrExStorageBlock *aes_ctr_ex_block; ///< BKTR AesCtrEx Storage Block.
bool missing_base_romfs; ///< If true, only Patch RomFS data is used. Needed for games with base Program NCAs without a RomFS section (e.g. Fortnite, World of Tanks Blitz, etc.).
BktrIndirectStorageBlock *base_indirect_block; ///< Base NCA Indirect Storage Block (sparse layer), if available.
} BktrContext;
/// Initializes a BKTR context.
@ -127,6 +128,7 @@ NX_INLINE void bktrFreeContext(BktrContext *ctx)
romfsFreeContext(&(ctx->patch_romfs_ctx));
if (ctx->indirect_block) free(ctx->indirect_block);
if (ctx->aes_ctr_ex_block) free(ctx->aes_ctr_ex_block);
if (ctx->base_indirect_block) free(ctx->base_indirect_block);
memset(ctx, 0, sizeof(BktrContext));
}

View File

@ -22,9 +22,13 @@
#include "nxdt_utils.h"
#include "bktr.h"
#include "aes.h"
/* Function prototypes. */
static bool bktrInitializeIndirectStorage(NcaFsSectionContext *nca_fs_ctx, bool patch_indirect_bucket, BktrIndirectStorageBlock **out_indirect_block);
static bool bktrInitializeAesCtrExStorage(NcaFsSectionContext *nca_fs_ctx, BktrAesCtrExStorageBlock **out_aes_ctr_ex_block);
static bool bktrPhysicalSectionRead(BktrContext *ctx, void *out, u64 read_size, u64 offset);
static bool bktrAesCtrExStorageRead(BktrContext *ctx, void *out, u64 read_size, u64 virtual_offset, u64 section_offset);
@ -37,6 +41,7 @@ static BktrAesCtrExStorageEntry *bktrGetAesCtrExStorageEntry(BktrAesCtrExStorage
bool bktrInitializeContext(BktrContext *out, NcaFsSectionContext *base_nca_fs_ctx, NcaFsSectionContext *update_nca_fs_ctx)
{
NcaContext *base_nca_ctx = NULL, *update_nca_ctx = NULL;
bool success = false, dump_patch_romfs_header = false;
if (!out || !base_nca_fs_ctx || !(base_nca_ctx = (NcaContext*)base_nca_fs_ctx->nca_ctx) || \
!update_nca_fs_ctx || !update_nca_fs_ctx->enabled || !(update_nca_ctx = (NcaContext*)update_nca_fs_ctx->nca_ctx) || \
@ -60,7 +65,6 @@ bool bktrInitializeContext(BktrContext *out, NcaFsSectionContext *base_nca_fs_ct
/* Update missing base NCA RomFS status. */
out->missing_base_romfs = (!base_nca_fs_ctx->enabled || base_nca_fs_ctx->section_type != NcaFsSectionType_RomFs);
if (!out->missing_base_romfs)
{
if (!base_nca_fs_ctx->has_sparse_layer)
@ -73,90 +77,21 @@ bool bktrInitializeContext(BktrContext *out, NcaFsSectionContext *base_nca_fs_ct
}
} else {
/* Initializing the base NCA RomFS on its own is impossible if we're dealing with a sparse layer. */
/* Let's just handle everything here. */
LOG_MSG("We got here, that's gotta be something.");
return false;
/* Let's just handle everything here, starting with initializing the sparse layer's indirect storage. */
if (!bktrInitializeIndirectStorage(base_nca_fs_ctx, false, &(out->base_indirect_block))) return false;
/* Update the NCA FS section context from the base RomFS context. */
out->base_romfs_ctx.nca_fs_ctx = base_nca_fs_ctx;
}
}
/* Fill context. */
bool success = false, dump_patch_romfs_header = false;
NcaPatchInfo *patch_info = &(update_nca_fs_ctx->header.patch_info);
/* Initialize Patch RomFS indirect storage. */
if (!bktrInitializeIndirectStorage(update_nca_fs_ctx, true, &(out->indirect_block))) goto end;
/* Allocate space for an extra (fake) indirect storage entry, to simplify our logic. */
out->indirect_block = calloc(1, patch_info->indirect_bucket.size + ((0x3FF0 / sizeof(u64)) * sizeof(BktrIndirectStorageEntry)));
if (!out->indirect_block)
{
LOG_MSG("Unable to allocate memory for the BKTR Indirect Storage Block!");
goto end;
}
/* Initialize Patch RomFS AesCtrEx storage. */
if (!bktrInitializeAesCtrExStorage(update_nca_fs_ctx, &(out->aes_ctr_ex_block))) goto end;
/* Read indirect storage block data. */
if (!ncaReadFsSection(update_nca_fs_ctx, out->indirect_block, patch_info->indirect_bucket.size, patch_info->indirect_bucket.offset))
{
LOG_MSG("Failed to read BKTR Indirect Storage Block data!");
goto end;
}
/* Allocate space for an extra (fake) AesCtrEx storage entry, to simplify our logic. */
out->aes_ctr_ex_block = calloc(1, patch_info->aes_ctr_ex_bucket.size + (((0x3FF0 / sizeof(u64)) + 1) * sizeof(BktrAesCtrExStorageEntry)));
if (!out->aes_ctr_ex_block)
{
LOG_MSG("Unable to allocate memory for the BKTR AesCtrEx Storage Block!");
goto end;
}
/* Read AesCtrEx storage block data. */
if (!ncaReadFsSection(update_nca_fs_ctx, out->aes_ctr_ex_block, patch_info->aes_ctr_ex_bucket.size, patch_info->aes_ctr_ex_bucket.offset))
{
LOG_MSG("Failed to read BKTR AesCtrEx Storage Block data!");
goto end;
}
if (out->aes_ctr_ex_block->physical_size != patch_info->aes_ctr_ex_bucket.offset)
{
LOG_DATA(out->aes_ctr_ex_block, patch_info->aes_ctr_ex_bucket.size, "Invalid BKTR AesCtrEx Storage Block size! AesCtrEx Storage Block dump:");
goto end;
}
/* This simplifies logic greatly... */
for(u32 i = (out->indirect_block->bucket_count - 1); i > 0; i--)
{
BktrIndirectStorageBucket tmp_bucket = {0};
memcpy(&tmp_bucket, &(out->indirect_block->indirect_storage_buckets[i]), sizeof(BktrIndirectStorageBucket));
memcpy(bktrGetIndirectStorageBucket(out->indirect_block, i), &tmp_bucket, sizeof(BktrIndirectStorageBucket));
}
for(u32 i = 0; (i + 1) < out->indirect_block->bucket_count; i++)
{
BktrIndirectStorageBucket *cur_bucket = bktrGetIndirectStorageBucket(out->indirect_block, i);
cur_bucket->indirect_storage_entries[cur_bucket->entry_count].virtual_offset = out->indirect_block->virtual_offsets[i + 1];
}
for(u32 i = (out->aes_ctr_ex_block->bucket_count - 1); i > 0; i--)
{
BktrAesCtrExStorageBucket tmp_bucket = {0};
memcpy(&tmp_bucket, &(out->aes_ctr_ex_block->aes_ctr_ex_storage_buckets[i]), sizeof(BktrAesCtrExStorageBucket));
memcpy(bktrGetAesCtrExStorageBucket(out->aes_ctr_ex_block, i), &tmp_bucket, sizeof(BktrAesCtrExStorageBucket));
}
for(u32 i = 0; (i + 1) < out->aes_ctr_ex_block->bucket_count; i++)
{
BktrAesCtrExStorageBucket *cur_bucket = bktrGetAesCtrExStorageBucket(out->aes_ctr_ex_block, i);
BktrAesCtrExStorageBucket *next_bucket = bktrGetAesCtrExStorageBucket(out->aes_ctr_ex_block, i + 1);
cur_bucket->aes_ctr_ex_storage_entries[cur_bucket->entry_count].offset = next_bucket->aes_ctr_ex_storage_entries[0].offset;
cur_bucket->aes_ctr_ex_storage_entries[cur_bucket->entry_count].generation = next_bucket->aes_ctr_ex_storage_entries[0].generation;
}
BktrIndirectStorageBucket *last_indirect_bucket = bktrGetIndirectStorageBucket(out->indirect_block, out->indirect_block->bucket_count - 1);
BktrAesCtrExStorageBucket *last_aes_ctr_ex_bucket = bktrGetAesCtrExStorageBucket(out->aes_ctr_ex_block, out->aes_ctr_ex_block->bucket_count - 1);
last_indirect_bucket->indirect_storage_entries[last_indirect_bucket->entry_count].virtual_offset = out->indirect_block->virtual_size;
last_aes_ctr_ex_bucket->aes_ctr_ex_storage_entries[last_aes_ctr_ex_bucket->entry_count].offset = patch_info->indirect_bucket.offset;
last_aes_ctr_ex_bucket->aes_ctr_ex_storage_entries[last_aes_ctr_ex_bucket->entry_count].generation = update_nca_fs_ctx->header.aes_ctr_upper_iv.generation;
last_aes_ctr_ex_bucket->aes_ctr_ex_storage_entries[last_aes_ctr_ex_bucket->entry_count + 1].offset = update_nca_fs_ctx->section_size;
last_aes_ctr_ex_bucket->aes_ctr_ex_storage_entries[last_aes_ctr_ex_bucket->entry_count + 1].generation = 0;
/* Initialize update NCA RomFS context. */
/* Initialize Patch RomFS context. */
/* Don't verify offsets from Patch RomFS sections, because they reflect the full, patched RomFS image. */
out->patch_romfs_ctx.nca_fs_ctx = update_nca_fs_ctx;
@ -169,7 +104,7 @@ bool bktrInitializeContext(BktrContext *out, NcaFsSectionContext *base_nca_fs_ct
out->patch_romfs_ctx.offset = out->offset;
out->patch_romfs_ctx.size = out->size;
/* Read update NCA RomFS header. */
/* Read Patch RomFS header. */
if (!bktrPhysicalSectionRead(out, &(out->patch_romfs_ctx.header), sizeof(RomFileSystemHeader), out->patch_romfs_ctx.offset))
{
LOG_MSG("Failed to read update NCA RomFS header!");
@ -183,7 +118,7 @@ bool bktrInitializeContext(BktrContext *out, NcaFsSectionContext *base_nca_fs_ct
goto end;
}
/* Read directory entries table. */
/* Read Patch RomFS directory entries table. */
u64 dir_table_offset = out->patch_romfs_ctx.header.cur_format.directory_entry_offset;
out->patch_romfs_ctx.dir_table_size = out->patch_romfs_ctx.header.cur_format.directory_entry_size;
@ -207,7 +142,7 @@ bool bktrInitializeContext(BktrContext *out, NcaFsSectionContext *base_nca_fs_ct
goto end;
}
/* Read file entries table. */
/* Read Patch RomFS file entries table. */
u64 file_table_offset = out->patch_romfs_ctx.header.cur_format.file_entry_offset;
out->patch_romfs_ctx.file_table_size = out->patch_romfs_ctx.header.cur_format.file_entry_size;
@ -231,7 +166,7 @@ bool bktrInitializeContext(BktrContext *out, NcaFsSectionContext *base_nca_fs_ct
goto end;
}
/* Get file data body offset. */
/* Get Patch RomFS file data body offset. */
out->patch_romfs_ctx.body_offset = out->body_offset = out->patch_romfs_ctx.header.cur_format.body_offset;
success = true;
@ -240,7 +175,6 @@ end:
if (!success)
{
if (dump_patch_romfs_header) LOG_DATA(&(out->patch_romfs_ctx.header), sizeof(RomFileSystemHeader), "Update RomFS header dump:");
bktrFreeContext(out);
}
@ -321,9 +255,140 @@ bool bktrIsFileEntryUpdated(BktrContext *ctx, RomFileSystemFileEntry *file_entry
return true;
}
static bool bktrInitializeIndirectStorage(NcaFsSectionContext *nca_fs_ctx, bool patch_indirect_bucket, BktrIndirectStorageBlock **out_indirect_block)
{
if (!nca_fs_ctx || (patch_indirect_bucket && nca_fs_ctx->section_type != NcaFsSectionType_PatchRomFs) || \
(!patch_indirect_bucket && (nca_fs_ctx->section_type != NcaFsSectionType_RomFs || !nca_fs_ctx->has_sparse_layer)) || !out_indirect_block)
{
LOG_MSG("Invalid parameters!");
return false;
}
NcaBucketInfo *indirect_bucket = (patch_indirect_bucket ? &(nca_fs_ctx->header.patch_info.indirect_bucket) : &(nca_fs_ctx->header.sparse_info.bucket));
BktrIndirectStorageBlock *indirect_block = NULL;
bool success = false;
/* Allocate space for an extra (fake) indirect storage entry, to simplify our logic. */
indirect_block = calloc(1, indirect_bucket->size + ((0x3FF0 / sizeof(u64)) * sizeof(BktrIndirectStorageEntry)));
if (!indirect_block)
{
LOG_MSG("Unable to allocate memory for the BKTR Indirect Storage Block! (%s).", patch_indirect_bucket ? "patch" : "sparse");
goto end;
}
/* Read indirect storage block data. */
if ((patch_indirect_bucket && !ncaReadFsSection(nca_fs_ctx, indirect_block, indirect_bucket->size, indirect_bucket->offset)) || \
(!patch_indirect_bucket && !ncaReadContentFile((NcaContext*)nca_fs_ctx->nca_ctx, indirect_block, nca_fs_ctx->sparse_table_size, nca_fs_ctx->sparse_table_offset)))
{
LOG_MSG("Failed to read BKTR Indirect Storage Block data! (%s).", patch_indirect_bucket ? "patch" : "sparse");
goto end;
}
/* Decrypt indirect block, if needed. */
if (!patch_indirect_bucket)
{
aes128CtrUpdatePartialCtr(nca_fs_ctx->sparse_ctr, nca_fs_ctx->sparse_table_offset);
aes128CtrContextResetCtr(&(nca_fs_ctx->sparse_ctr_ctx), nca_fs_ctx->sparse_ctr);
aes128CtrCrypt(&(nca_fs_ctx->sparse_ctr_ctx), indirect_block, indirect_block, nca_fs_ctx->sparse_table_size);
}
/* This simplifies logic greatly... */
for(u32 i = (indirect_block->bucket_count - 1); i > 0; i--)
{
BktrIndirectStorageBucket tmp_bucket = {0};
memcpy(&tmp_bucket, &(indirect_block->indirect_storage_buckets[i]), sizeof(BktrIndirectStorageBucket));
memcpy(bktrGetIndirectStorageBucket(indirect_block, i), &tmp_bucket, sizeof(BktrIndirectStorageBucket));
}
for(u32 i = 0; (i + 1) < indirect_block->bucket_count; i++)
{
BktrIndirectStorageBucket *cur_bucket = bktrGetIndirectStorageBucket(indirect_block, i);
cur_bucket->indirect_storage_entries[cur_bucket->entry_count].virtual_offset = indirect_block->virtual_offsets[i + 1];
}
BktrIndirectStorageBucket *last_indirect_bucket = bktrGetIndirectStorageBucket(indirect_block, indirect_block->bucket_count - 1);
last_indirect_bucket->indirect_storage_entries[last_indirect_bucket->entry_count].virtual_offset = indirect_block->virtual_size;
/* Update output values. */
*out_indirect_block = indirect_block;
success = true;
end:
if (!success && indirect_block) free(indirect_block);
return success;
}
static bool bktrInitializeAesCtrExStorage(NcaFsSectionContext *nca_fs_ctx, BktrAesCtrExStorageBlock **out_aes_ctr_ex_block)
{
if (!nca_fs_ctx || nca_fs_ctx->section_type != NcaFsSectionType_PatchRomFs || !out_aes_ctr_ex_block)
{
LOG_MSG("Invalid parameters!");
return false;
}
NcaBucketInfo *aes_ctr_ex_bucket = &(nca_fs_ctx->header.patch_info.aes_ctr_ex_bucket);
BktrAesCtrExStorageBlock *aes_ctr_ex_block = NULL;
bool success = false;
/* Allocate space for an extra (fake) AesCtrEx storage entry, to simplify our logic. */
aes_ctr_ex_block = calloc(1, aes_ctr_ex_bucket->size + (((0x3FF0 / sizeof(u64)) + 1) * sizeof(BktrAesCtrExStorageEntry)));
if (!aes_ctr_ex_block)
{
LOG_MSG("Unable to allocate memory for the BKTR AesCtrEx Storage Block!");
goto end;
}
/* Read AesCtrEx storage block data. */
if (!ncaReadFsSection(nca_fs_ctx, aes_ctr_ex_block, aes_ctr_ex_bucket->size, aes_ctr_ex_bucket->offset))
{
LOG_MSG("Failed to read BKTR AesCtrEx Storage Block data!");
goto end;
}
if (aes_ctr_ex_block->physical_size != aes_ctr_ex_bucket->offset)
{
LOG_DATA(aes_ctr_ex_block, aes_ctr_ex_bucket->size, "Invalid BKTR AesCtrEx Storage Block size! AesCtrEx Storage Block dump:");
goto end;
}
/* This simplifies logic greatly... */
for(u32 i = (aes_ctr_ex_block->bucket_count - 1); i > 0; i--)
{
BktrAesCtrExStorageBucket tmp_bucket = {0};
memcpy(&tmp_bucket, &(aes_ctr_ex_block->aes_ctr_ex_storage_buckets[i]), sizeof(BktrAesCtrExStorageBucket));
memcpy(bktrGetAesCtrExStorageBucket(aes_ctr_ex_block, i), &tmp_bucket, sizeof(BktrAesCtrExStorageBucket));
}
for(u32 i = 0; (i + 1) < aes_ctr_ex_block->bucket_count; i++)
{
BktrAesCtrExStorageBucket *cur_bucket = bktrGetAesCtrExStorageBucket(aes_ctr_ex_block, i);
BktrAesCtrExStorageBucket *next_bucket = bktrGetAesCtrExStorageBucket(aes_ctr_ex_block, i + 1);
cur_bucket->aes_ctr_ex_storage_entries[cur_bucket->entry_count].offset = next_bucket->aes_ctr_ex_storage_entries[0].offset;
cur_bucket->aes_ctr_ex_storage_entries[cur_bucket->entry_count].generation = next_bucket->aes_ctr_ex_storage_entries[0].generation;
}
BktrAesCtrExStorageBucket *last_aes_ctr_ex_bucket = bktrGetAesCtrExStorageBucket(aes_ctr_ex_block, aes_ctr_ex_block->bucket_count - 1);
last_aes_ctr_ex_bucket->aes_ctr_ex_storage_entries[last_aes_ctr_ex_bucket->entry_count].offset = nca_fs_ctx->header.patch_info.indirect_bucket.offset;
last_aes_ctr_ex_bucket->aes_ctr_ex_storage_entries[last_aes_ctr_ex_bucket->entry_count].generation = nca_fs_ctx->header.aes_ctr_upper_iv.generation;
last_aes_ctr_ex_bucket->aes_ctr_ex_storage_entries[last_aes_ctr_ex_bucket->entry_count + 1].offset = nca_fs_ctx->section_size;
last_aes_ctr_ex_bucket->aes_ctr_ex_storage_entries[last_aes_ctr_ex_bucket->entry_count + 1].generation = 0;
/* Update output values. */
*out_aes_ctr_ex_block = aes_ctr_ex_block;
success = true;
end:
if (!success && aes_ctr_ex_block) free(aes_ctr_ex_block);
return success;
}
static bool bktrPhysicalSectionRead(BktrContext *ctx, void *out, u64 read_size, u64 offset)
{
if (!ctx || (!ctx->missing_base_romfs && !ctx->base_romfs_ctx.nca_fs_ctx) || !ctx->indirect_block || !out || !read_size)
if (!ctx || (!ctx->missing_base_romfs && ((!ctx->base_romfs_ctx.nca_fs_ctx->has_sparse_layer && !ctx->base_romfs_ctx.nca_fs_ctx) || \
(ctx->base_romfs_ctx.nca_fs_ctx->has_sparse_layer && !ctx->base_indirect_block))) || !ctx->indirect_block || !out || !read_size)
{
LOG_MSG("Invalid parameters!");
return false;
@ -357,8 +422,22 @@ static bool bktrPhysicalSectionRead(BktrContext *ctx, void *out, u64 read_size,
} else
if (!ctx->missing_base_romfs)
{
success = ncaReadFsSection(ctx->base_romfs_ctx.nca_fs_ctx, out, read_size, section_offset);
if (!success) LOG_MSG("Failed to read 0x%lX bytes block from base RomFS at offset 0x%lX!", read_size, section_offset);
if (!ctx->base_romfs_ctx.nca_fs_ctx->has_sparse_layer)
{
success = ncaReadFsSection(ctx->base_romfs_ctx.nca_fs_ctx, out, read_size, section_offset);
if (!success) LOG_MSG("Failed to read 0x%lX bytes block from base RomFS at offset 0x%lX!", read_size, section_offset);
} else {
//ctx->base_indirect_block
LOG_MSG("Attempting to read 0x%lX bytes block from sparse layer at offset 0x%lX!", read_size, section_offset);
}
} else {
LOG_MSG("Attempting to read 0x%lX bytes block from non-existent base RomFS at offset 0x%lX!", read_size, section_offset);
}

View File

@ -838,6 +838,8 @@ end:
static void gamecardFreeInfo(bool clear_status)
{
gamecardCloseStorageArea(true);
memset(&g_gameCardHeader, 0, sizeof(GameCardHeader));
memset(&g_gameCardInfoArea, 0, sizeof(GameCardInfo));
@ -864,8 +866,6 @@ static void gamecardFreeInfo(bool clear_status)
g_gameCardHfsCount = 0;
gamecardCloseStorageArea(true);
if (clear_status) g_gameCardStatus = GameCardStatus_NotInserted;
}

View File

@ -708,19 +708,17 @@ static bool ncaInitializeFsSectionContext(NcaContext *nca_ctx, u32 section_idx)
bool success = false;
/* Clear FS section context. */
memset(fs_ctx, 0, sizeof(NcaFsSectionContext));
/* Fill section context. */
fs_ctx->nca_ctx = nca_ctx;
fs_ctx->section_idx = section_idx;
fs_ctx->section_type = NcaFsSectionType_Invalid; /* Placeholder. */
fs_ctx->has_sparse_layer = (sparse_info->generation != 0);
fs_ctx->enabled = false;
/* Don't proceed if this NCA FS section isn't populated. */
if (!ncaIsFsInfoEntryValid(fs_info))
{
LOG_MSG("Invalid FsInfo entry for section #%u in \"%s\". Skipping FS section.", section_idx, nca_ctx->content_id_str);
//LOG_MSG("Invalid FsInfo entry for section #%u in \"%s\". Skipping FS section.", section_idx, nca_ctx->content_id_str);
goto end;
}
@ -828,8 +826,7 @@ static bool ncaInitializeFsSectionContext(NcaContext *nca_ctx, u32 section_idx)
}
/* Update section size. */
fs_ctx->section_size = (MIN(fs_ctx->section_offset, raw_storage_offset) + (MAX(fs_ctx->section_offset, raw_storage_offset) - \
MIN(fs_ctx->section_offset, raw_storage_offset)) + raw_storage_size);
fs_ctx->section_size = raw_storage_size;
}
/* Check if we're within boundaries. */
@ -839,24 +836,6 @@ static bool ncaInitializeFsSectionContext(NcaContext *nca_ctx, u32 section_idx)
goto end;
}
/* Validate HashData boundaries. */
if (!ncaFsSectionValidateHashDataBoundaries(fs_ctx)) goto end;
/* Get hash layer region size (offset must always be 0). */
fs_ctx->hash_region.offset = 0;
if (!ncaGetFsSectionHashTargetProperties(fs_ctx, NULL, &(fs_ctx->hash_region.size)))
{
LOG_MSG("Invalid hash type for FS section #%u in \"%s\" (0x%02X). Skipping FS section.", fs_ctx->section_idx, nca_ctx->content_id_str, fs_ctx->hash_type);
goto end;
}
/* Check if we're within boundaries. */
if (fs_ctx->hash_region.size > fs_ctx->section_size || (fs_ctx->section_offset + fs_ctx->hash_region.size) > nca_ctx->content_size)
{
LOG_MSG("Hash layer region for FS section #%u in \"%s\" is out of NCA boundaries. Skipping FS section.", section_idx, nca_ctx->content_id_str);
goto end;
}
/* Determine FS section type. */
/* TODO: should NcaHashType_None be handled here as well? */
switch(fs_ctx->header.fs_type)
@ -906,6 +885,24 @@ static bool ncaInitializeFsSectionContext(NcaContext *nca_ctx, u32 section_idx)
goto end;
}
/* Validate HashData boundaries. */
if (!ncaFsSectionValidateHashDataBoundaries(fs_ctx)) goto end;
/* Get hash layer region size (offset must always be 0). */
fs_ctx->hash_region.offset = 0;
if (!ncaGetFsSectionHashTargetProperties(fs_ctx, &(fs_ctx->hash_region.size), NULL))
{
LOG_MSG("Invalid hash type for FS section #%u in \"%s\" (0x%02X). Skipping FS section.", fs_ctx->section_idx, nca_ctx->content_id_str, fs_ctx->hash_type);
goto end;
}
/* Check if we're within boundaries. */
if (fs_ctx->hash_region.size > fs_ctx->section_size || (fs_ctx->section_offset + fs_ctx->hash_region.size) > nca_ctx->content_size)
{
LOG_MSG("Hash layer region for FS section #%u in \"%s\" is out of NCA boundaries. Skipping FS section.", section_idx, nca_ctx->content_id_str);
goto end;
}
/* Check if we should skip hash layer decryption while reading this FS section. */
fs_ctx->skip_hash_layer_crypto = (fs_ctx->encryption_type == NcaEncryptionType_AesCtrSkipLayerHash || fs_ctx->encryption_type == NcaEncryptionType_AesCtrExSkipLayerHash);
if (fs_ctx->skip_hash_layer_crypto && fs_ctx->hash_type == NcaHashType_None)
@ -927,7 +924,6 @@ static bool ncaInitializeFsSectionContext(NcaContext *nca_ctx, u32 section_idx)
NcaAesCtrUpperIv sparse_upper_iv = {0};
memcpy(sparse_upper_iv.value, fs_ctx->header.aes_ctr_upper_iv.value, sizeof(sparse_upper_iv.value));
sparse_upper_iv.generation = ((u32)(sparse_info->generation) << 16);
aes128CtrInitializePartialCtr(fs_ctx->sparse_ctr, sparse_upper_iv.value, fs_ctx->sparse_table_offset);
}
@ -987,7 +983,7 @@ static bool ncaFsSectionValidateHashDataBoundaries(NcaFsSectionContext *ctx)
{
/* Validate all hash regions boundaries. Skip the last one if a sparse layer is used. */
NcaRegion *hash_region = &(hash_data->hash_region[i]);
if (hash_region->offset != accum || !hash_region->size || \
if (hash_region->offset < accum || !hash_region->size || \
((i < (hash_data->hash_region_count - 1) || !ctx->has_sparse_layer) && (hash_region->offset + hash_region->size) > ctx->section_size))
{
LOG_MSG("HierarchicalSha256 region #%u for FS section #%u in \"%s\" is out of NCA boundaries. Skipping FS section.", \
@ -996,7 +992,7 @@ static bool ncaFsSectionValidateHashDataBoundaries(NcaFsSectionContext *ctx)
break;
}
accum += hash_region->size;
accum = (hash_region->offset + hash_region->size);
}
success = valid;
@ -1017,10 +1013,10 @@ static bool ncaFsSectionValidateHashDataBoundaries(NcaFsSectionContext *ctx)
for(u32 i = 0; i < NCA_IVFC_LEVEL_COUNT; i++)
{
/* Validate all level informations boundaries. Skip the last one if a sparse layer is used. */
NcaHierarchicalIntegrityVerificationLevelInformation *level_information = &(hash_data->info_level_hash.level_information[i]);
if (level_information->offset != accum || !level_information->size || !level_information->block_order || \
((i < (NCA_IVFC_LEVEL_COUNT - 1) || !ctx->has_sparse_layer) && (level_information->offset + level_information->size) > ctx->section_size))
/* Validate all level informations boundaries. Skip the last one if we're dealing with a Patch RomFS, or if a sparse layer is used. */
NcaHierarchicalIntegrityVerificationLevelInformation *lvl_info = &(hash_data->info_level_hash.level_information[i]);
if (lvl_info->offset < accum || !lvl_info->size || !lvl_info->block_order || ((i < (NCA_IVFC_LEVEL_COUNT - 1) || \
(!ctx->has_sparse_layer && ctx->section_type != NcaFsSectionType_PatchRomFs)) && (lvl_info->offset + lvl_info->size) > ctx->section_size))
{
LOG_MSG("HierarchicalIntegrity level #%u for FS section #%u in \"%s\" is out of NCA boundaries. Skipping FS section.", \
i, ctx->section_idx, nca_ctx->content_id_str);
@ -1028,7 +1024,7 @@ static bool ncaFsSectionValidateHashDataBoundaries(NcaFsSectionContext *ctx)
break;
}
accum += level_information->size;
accum = (lvl_info->offset + lvl_info->size);
}
success = valid;