mirror of
https://github.com/DarkMatterCore/nxdumptool.git
synced 2024-11-22 18:26:39 +00:00
BKTR done, needs testing.
This commit is contained in:
parent
e1e38bcfc2
commit
4774aeae9c
5 changed files with 491 additions and 46 deletions
396
source/bktr.c
396
source/bktr.c
|
@ -23,17 +23,27 @@
|
|||
|
||||
/* Function prototypes. */
|
||||
|
||||
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);
|
||||
|
||||
NX_INLINE BktrIndirectStorageBucket *bktrGetIndirectStorageBucket(BktrIndirectStorageBlock *block, u32 bucket_num);
|
||||
static BktrIndirectStorageEntry *bktrGetIndirectStorageEntry(BktrIndirectStorageBlock *block, u64 offset);
|
||||
|
||||
NX_INLINE BktrAesCtrExStorageBucket *bktrGetAesCtrExStorageBucket(BktrAesCtrExStorageBlock *block, u32 bucket_num);
|
||||
static BktrAesCtrExStorageEntry *bktrGetAesCtrExStorageEntry(BktrAesCtrExStorageBlock *block, u64 offset);
|
||||
|
||||
bool bktrInitializeContext(BktrContext *out, NcaFsSectionContext *base_nca_fs_ctx, NcaFsSectionContext *update_nca_fs_ctx)
|
||||
{
|
||||
NcaContext *base_nca_ctx = NULL, *update_nca_ctx = NULL;
|
||||
|
||||
if (!out || !base_nca_fs_ctx || !(base_nca_ctx = (NcaContext*)base_nca_fs_ctx->nca_ctx) || base_nca_fs_ctx->section_type != NcaFsSectionType_RomFs || \
|
||||
base_nca_fs_ctx->encryption_type == NcaEncryptionType_AesCtrEx || !update_nca_fs_ctx || !(update_nca_ctx = (NcaContext*)update_nca_fs_ctx->nca_ctx) || \
|
||||
update_nca_fs_ctx->section_type != NcaFsSectionType_PatchRomFs || update_nca_fs_ctx->encryption_type != NcaEncryptionType_AesCtrEx ||
|
||||
base_nca_ctx->header.program_id != update_nca_ctx->header.program_id)
|
||||
if (!out || !base_nca_fs_ctx || !(base_nca_ctx = (NcaContext*)base_nca_fs_ctx->nca_ctx) || !base_nca_fs_ctx->header || base_nca_fs_ctx->section_type != NcaFsSectionType_RomFs || \
|
||||
base_nca_fs_ctx->encryption_type == NcaEncryptionType_AesCtrEx || !update_nca_fs_ctx || !update_nca_fs_ctx->header || !(update_nca_ctx = (NcaContext*)update_nca_fs_ctx->nca_ctx) || \
|
||||
update_nca_fs_ctx->section_type != NcaFsSectionType_PatchRomFs || update_nca_fs_ctx->encryption_type != NcaEncryptionType_AesCtrEx || \
|
||||
base_nca_ctx->header.program_id != update_nca_ctx->header.program_id || base_nca_ctx->header.content_type != update_nca_ctx->header.content_type || \
|
||||
__builtin_bswap32(update_nca_fs_ctx->header->patch_info.indirect_header.magic) != NCA_BKTR_MAGIC || \
|
||||
__builtin_bswap32(update_nca_fs_ctx->header->patch_info.aes_ctr_ex_header.magic) != NCA_BKTR_MAGIC || \
|
||||
(update_nca_fs_ctx->header->patch_info.indirect_offset + update_nca_fs_ctx->header->patch_info.indirect_size) != update_nca_fs_ctx->header->patch_info.aes_ctr_ex_offset || \
|
||||
(update_nca_fs_ctx->header->patch_info.aes_ctr_ex_offset + update_nca_fs_ctx->header->patch_info.aes_ctr_ex_size) != update_nca_fs_ctx->section_size)
|
||||
{
|
||||
LOGFILE("Invalid parameters!");
|
||||
return false;
|
||||
|
@ -46,25 +56,151 @@ bool bktrInitializeContext(BktrContext *out, NcaFsSectionContext *base_nca_fs_ct
|
|||
return false;
|
||||
}
|
||||
|
||||
/* Initialize update NCA RomFS context */
|
||||
if (!romfsInitializeContext(&(out->patch_romfs_ctx), update_nca_fs_ctx))
|
||||
{
|
||||
LOGFILE("Failed to initialize update NCA RomFS context!");
|
||||
romfsFreeContext(&(out->base_romfs_ctx));
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Fill context */
|
||||
bool success = false;
|
||||
out->patch_info = &(update_nca_fs_ctx->header->patch_info);
|
||||
out->size = out->patch_romfs_ctx.size;
|
||||
out->virtual_seek = out->base_seek = out->patch_seek = 0;
|
||||
NcaPatchInfo *patch_info = &(update_nca_fs_ctx->header->patch_info);
|
||||
|
||||
/* Allocate space for an extra (fake) indirect storage entry, to simplify our logic */
|
||||
out->indirect_block = calloc(1, patch_info->indirect_size + ((0x3FF0 / sizeof(u64)) * sizeof(BktrIndirectStorageEntry)));
|
||||
if (!out->indirect_block)
|
||||
{
|
||||
LOGFILE("Unable to allocate memory for the BKTR Indirect Storage Block!");
|
||||
goto exit;
|
||||
}
|
||||
|
||||
/* Read indirect storage block data */
|
||||
if (!ncaReadFsSection(update_nca_fs_ctx, out->indirect_block, patch_info->indirect_size, patch_info->indirect_offset))
|
||||
{
|
||||
LOGFILE("Failed to read BKTR Indirect Storage Block data!");
|
||||
goto exit;
|
||||
}
|
||||
|
||||
/* 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_size + (((0x3FF0 / sizeof(u64)) + 1) * sizeof(BktrAesCtrExStorageEntry)));
|
||||
if (!out->aes_ctr_ex_block)
|
||||
{
|
||||
LOGFILE("Unable to allocate memory for the BKTR AesCtrEx Storage Block!");
|
||||
goto exit;
|
||||
}
|
||||
|
||||
/* Read AesCtrEx storage block data */
|
||||
if (!ncaReadFsSection(update_nca_fs_ctx, out->aes_ctr_ex_block, patch_info->aes_ctr_ex_size, patch_info->aes_ctr_ex_offset))
|
||||
{
|
||||
LOGFILE("Failed to read BKTR AesCtrEx Storage Block data!");
|
||||
goto exit;
|
||||
}
|
||||
|
||||
if (out->aes_ctr_ex_block->physical_size != patch_info->aes_ctr_ex_offset)
|
||||
{
|
||||
LOGFILE("Invalid BKTR AesCtrEx Storage Block size!");
|
||||
goto exit;
|
||||
}
|
||||
|
||||
/* 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);
|
||||
last_indirect_bucket->indirect_storage_entries[last_indirect_bucket->entry_count].virtual_offset = out->indirect_block->virtual_size;
|
||||
|
||||
BktrAesCtrExStorageBucket *last_aes_ctr_ex_bucket = bktrGetAesCtrExStorageBucket(out->aes_ctr_ex_block, out->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 = patch_info->indirect_offset;
|
||||
last_aes_ctr_ex_bucket->aes_ctr_ex_storage_entries[last_aes_ctr_ex_bucket->entry_count].generation = update_nca_fs_ctx->header->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 */
|
||||
/* 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;
|
||||
out->patch_romfs_ctx.offset = out->offset = update_nca_fs_ctx->header->hash_info.hierarchical_integrity.hash_target_layer_info.offset;
|
||||
out->patch_romfs_ctx.size = out->size = update_nca_fs_ctx->header->hash_info.hierarchical_integrity.hash_target_layer_info.size;
|
||||
|
||||
/* Read update NCA RomFS header */
|
||||
if (!bktrPhysicalSectionRead(out, &(out->patch_romfs_ctx.header), sizeof(RomFileSystemHeader), out->patch_romfs_ctx.offset))
|
||||
{
|
||||
LOGFILE("Failed to read update NCA RomFS header!");
|
||||
goto exit;
|
||||
}
|
||||
|
||||
if (out->patch_romfs_ctx.header.cur_format.header_size != ROMFS_HEADER_SIZE)
|
||||
{
|
||||
LOGFILE("Invalid update NCA RomFS header size!");
|
||||
goto exit;
|
||||
}
|
||||
|
||||
/* Read 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;
|
||||
|
||||
if (!dir_table_offset || !out->patch_romfs_ctx.dir_table_size)
|
||||
{
|
||||
LOGFILE("Invalid update NCA RomFS directory entries table!");
|
||||
goto exit;
|
||||
}
|
||||
|
||||
out->patch_romfs_ctx.dir_table = malloc(out->patch_romfs_ctx.dir_table_size);
|
||||
if (!out->patch_romfs_ctx.dir_table)
|
||||
{
|
||||
LOGFILE("Unable to allocate memory for the update NCA RomFS directory entries table!");
|
||||
goto exit;
|
||||
}
|
||||
|
||||
if (!bktrPhysicalSectionRead(out, &(out->patch_romfs_ctx.dir_table), out->patch_romfs_ctx.dir_table_size, out->patch_romfs_ctx.offset + dir_table_offset))
|
||||
{
|
||||
LOGFILE("Failed to read update NCA RomFS directory entries table!");
|
||||
goto exit;
|
||||
}
|
||||
|
||||
/* Read 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;
|
||||
|
||||
if (!file_table_offset || !out->patch_romfs_ctx.file_table_size)
|
||||
{
|
||||
LOGFILE("Invalid update NCA RomFS file entries table!");
|
||||
goto exit;
|
||||
}
|
||||
|
||||
out->patch_romfs_ctx.file_table = malloc(out->patch_romfs_ctx.file_table_size);
|
||||
if (!out->patch_romfs_ctx.file_table)
|
||||
{
|
||||
LOGFILE("Unable to allocate memory for the update NCA RomFS file entries table!");
|
||||
goto exit;
|
||||
}
|
||||
|
||||
if (!bktrPhysicalSectionRead(out, &(out->patch_romfs_ctx.file_table), out->patch_romfs_ctx.file_table_size, out->patch_romfs_ctx.offset + file_table_offset))
|
||||
{
|
||||
LOGFILE("Failed to read update NCA RomFS file entries table!");
|
||||
goto exit;
|
||||
}
|
||||
|
||||
/* Get file data body offset */
|
||||
out->patch_romfs_ctx.body_offset = out->body_offset = out->patch_romfs_ctx.header.cur_format.body_offset;
|
||||
|
||||
success = true;
|
||||
|
||||
|
@ -74,6 +210,236 @@ exit:
|
|||
return success;
|
||||
}
|
||||
|
||||
bool bktrReadFileSystemData(BktrContext *ctx, void *out, u64 read_size, u64 offset)
|
||||
{
|
||||
if (!ctx || !ctx->size || !out || !read_size || offset >= ctx->size || (offset + read_size) > ctx->size)
|
||||
{
|
||||
LOGFILE("Invalid parameters!");
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Read filesystem data */
|
||||
if (!bktrPhysicalSectionRead(ctx, out, read_size, ctx->offset + offset))
|
||||
{
|
||||
LOGFILE("Failed to read Patch RomFS data!");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool bktrReadFileEntryData(BktrContext *ctx, RomFileSystemFileEntry *file_entry, void *out, u64 read_size, u64 offset)
|
||||
{
|
||||
if (!ctx || !ctx->body_offset || !file_entry || !file_entry->size || file_entry->offset >= ctx->size || (file_entry->offset + file_entry->size) > ctx->size || \
|
||||
!out || !read_size || offset >= file_entry->size || (offset + read_size) > file_entry->size)
|
||||
{
|
||||
LOGFILE("Invalid parameters!");
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Read entry data */
|
||||
if (!bktrReadFileSystemData(ctx, out, read_size, ctx->body_offset + file_entry->offset + offset))
|
||||
{
|
||||
LOGFILE("Failed to read Patch RomFS file entry data!");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool bktrPhysicalSectionRead(BktrContext *ctx, void *out, u64 read_size, u64 offset)
|
||||
{
|
||||
if (!ctx || !ctx->base_romfs_ctx.nca_fs_ctx || !ctx->indirect_block || !out || !read_size)
|
||||
{
|
||||
LOGFILE("Invalid parameters!");
|
||||
return false;
|
||||
}
|
||||
|
||||
BktrIndirectStorageEntry *indirect_entry = NULL, *next_indirect_entry = NULL;
|
||||
u64 section_offset = 0, indirect_block_size = 0;
|
||||
|
||||
/* Determine which FS section to use + the actual offset to start reading from */
|
||||
/* No better way to do this than to make all BKTR addresses virtual */
|
||||
indirect_entry = bktrGetIndirectStorageEntry(ctx->indirect_block, offset);
|
||||
if (!indirect_entry)
|
||||
{
|
||||
LOGFILE("Error retrieving BKTR Indirect Storage Entry at offset 0x%lX!", offset);
|
||||
return false;
|
||||
}
|
||||
|
||||
section_offset = (offset - indirect_entry->virtual_offset + indirect_entry->physical_offset);
|
||||
|
||||
/* Perform read operation */
|
||||
bool success = false;
|
||||
next_indirect_entry = (indirect_entry + 1);
|
||||
if ((offset + read_size) <= next_indirect_entry->virtual_offset)
|
||||
{
|
||||
/* Easy path: read only within the current indirect storage entry */
|
||||
/* If we're not dealing with an indirect storage entry with a patch index, just retrieve the data from the base RomFS */
|
||||
if (indirect_entry->indirect_storage_index == BktrIndirectStorageIndex_Patch)
|
||||
{
|
||||
success = bktrAesCtrExStorageRead(ctx, out, read_size, offset, section_offset);
|
||||
} else {
|
||||
success = ncaReadFsSection(ctx->base_romfs_ctx.nca_fs_ctx, out, read_size, section_offset);
|
||||
}
|
||||
} else {
|
||||
/* Handle read that spans multiple indirect storage entries */
|
||||
indirect_block_size = (next_indirect_entry->virtual_offset - offset);
|
||||
|
||||
success = (bktrReadFileSystemData(ctx, out, indirect_block_size, offset) && \
|
||||
bktrReadFileSystemData(ctx, (u8*)out + indirect_block_size, read_size - indirect_block_size, offset + indirect_block_size));
|
||||
}
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
static bool bktrAesCtrExStorageRead(BktrContext *ctx, void *out, u64 read_size, u64 virtual_offset, u64 section_offset)
|
||||
{
|
||||
BktrAesCtrExStorageEntry *aes_ctr_ex_entry = NULL, *next_aes_ctr_ex_entry = NULL;
|
||||
|
||||
if (!ctx || !ctx->patch_romfs_ctx.nca_fs_ctx || !ctx->aes_ctr_ex_block || !out || !read_size)
|
||||
{
|
||||
LOGFILE("Invalid parameters!");
|
||||
return false;
|
||||
}
|
||||
|
||||
aes_ctr_ex_entry = bktrGetAesCtrExStorageEntry(ctx->aes_ctr_ex_block, section_offset);
|
||||
if (!aes_ctr_ex_entry)
|
||||
{
|
||||
LOGFILE("Error retrieving BKTR AesCtrEx Storage Entry at offset 0x%lX!", section_offset);
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Perform read operation */
|
||||
bool success = false;
|
||||
next_aes_ctr_ex_entry = (aes_ctr_ex_entry + 1);
|
||||
if ((section_offset + read_size) <= next_aes_ctr_ex_entry->offset)
|
||||
{
|
||||
/* Read AesCtrEx storage entry data */
|
||||
success = ncaReadAesCtrExStorageFromBktrSection(ctx->patch_romfs_ctx.nca_fs_ctx, out, read_size, section_offset, aes_ctr_ex_entry->generation);
|
||||
} else {
|
||||
/* Handle read that spans multiple AesCtrEx storage entries */
|
||||
u64 aes_ctr_ex_block_size = (next_aes_ctr_ex_entry->offset - section_offset);
|
||||
|
||||
success = (bktrReadFileSystemData(ctx, out, aes_ctr_ex_block_size, virtual_offset) && \
|
||||
bktrReadFileSystemData(ctx, (u8*)out + aes_ctr_ex_block_size, read_size - aes_ctr_ex_block_size, virtual_offset + aes_ctr_ex_block_size));
|
||||
}
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
NX_INLINE BktrIndirectStorageBucket *bktrGetIndirectStorageBucket(BktrIndirectStorageBlock *block, u32 bucket_num)
|
||||
{
|
||||
if (!block || bucket_num >= block->bucket_count) return NULL;
|
||||
return (BktrIndirectStorageBucket*)((u8*)block->indirect_storage_buckets + ((sizeof(BktrIndirectStorageBucket) + sizeof(BktrIndirectStorageEntry)) * bucket_num));
|
||||
}
|
||||
|
||||
static BktrIndirectStorageEntry *bktrGetIndirectStorageEntry(BktrIndirectStorageBlock *block, u64 offset)
|
||||
{
|
||||
if (!block || offset >= block->virtual_size)
|
||||
{
|
||||
LOGFILE("Invalid parameters!");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
u32 bucket_num = 0;
|
||||
BktrIndirectStorageBucket *bucket = NULL;
|
||||
|
||||
for(u32 i = 1; i < block->bucket_count; i++)
|
||||
{
|
||||
if (block->virtual_offsets[i] <= offset) bucket_num++;
|
||||
}
|
||||
|
||||
bucket = bktrGetIndirectStorageBucket(block, bucket_num);
|
||||
if (!bucket || !bucket->entry_count)
|
||||
{
|
||||
LOGFILE("Error retrieving BKTR indirect storage bucket #%u!", bucket_num);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Check for edge case, short circuit */
|
||||
if (bucket->entry_count == 1) return &(bucket->indirect_storage_entries[0]);
|
||||
|
||||
/* Binary search */
|
||||
u32 low = 0, high = (bucket->entry_count - 1);
|
||||
while(low <= high)
|
||||
{
|
||||
u32 mid = ((low + high) / 2);
|
||||
|
||||
if (bucket->indirect_storage_entries[mid].virtual_offset > offset)
|
||||
{
|
||||
/* Too high */
|
||||
high = (mid - 1);
|
||||
} else {
|
||||
/* Check for success */
|
||||
if (mid == (bucket->entry_count - 1) || bucket->indirect_storage_entries[mid + 1].virtual_offset > offset) return &(bucket->indirect_storage_entries[mid]);
|
||||
low = (mid + 1);
|
||||
}
|
||||
}
|
||||
|
||||
LOGFILE("Failed to find offset 0x%lX in BKTR indirect storage block!", offset);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
NX_INLINE BktrAesCtrExStorageBucket *bktrGetAesCtrExStorageBucket(BktrAesCtrExStorageBlock *block, u32 bucket_num)
|
||||
{
|
||||
if (!block || bucket_num >= block->bucket_count) return NULL;
|
||||
return (BktrAesCtrExStorageBucket*)((u8*)block->aes_ctr_ex_storage_buckets + ((sizeof(BktrAesCtrExStorageBucket) + sizeof(BktrAesCtrExStorageEntry)) * bucket_num));
|
||||
}
|
||||
|
||||
static BktrAesCtrExStorageEntry *bktrGetAesCtrExStorageEntry(BktrAesCtrExStorageBlock *block, u64 offset)
|
||||
{
|
||||
if (!block || offset >= block->physical_size)
|
||||
{
|
||||
LOGFILE("Invalid parameters!");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
u32 bucket_num = 0;
|
||||
BktrAesCtrExStorageBucket *last_bucket = NULL, *bucket = NULL;
|
||||
|
||||
/* If offset is past the virtual, we're reading from the BKTR_HEADER subsection */
|
||||
last_bucket = bktrGetAesCtrExStorageBucket(block, block->bucket_count - 1);
|
||||
if (!last_bucket)
|
||||
{
|
||||
LOGFILE("Error retrieving last BKTR AesCtrEx storage bucket!");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (offset >= last_bucket->aes_ctr_ex_storage_entries[last_bucket->entry_count].offset) return &(last_bucket->aes_ctr_ex_storage_entries[last_bucket->entry_count]);
|
||||
|
||||
for(u32 i = 1; i < block->bucket_count; i++)
|
||||
{
|
||||
if (block->physical_offsets[i] <= offset) bucket_num++;
|
||||
}
|
||||
|
||||
bucket = bktrGetAesCtrExStorageBucket(block, bucket_num);
|
||||
if (!bucket)
|
||||
{
|
||||
LOGFILE("Error retrieving BKTR AesCtrEx storage bucket #%u!", bucket_num);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Check for edge case, short circuit */
|
||||
if (bucket->entry_count == 1) return &(bucket->aes_ctr_ex_storage_entries[0]);
|
||||
|
||||
/* Binary search */
|
||||
u32 low = 0, high = (bucket->entry_count - 1);
|
||||
while(low <= high)
|
||||
{
|
||||
u32 mid = ((low + high) / 2);
|
||||
|
||||
if (bucket->aes_ctr_ex_storage_entries[mid].offset > offset)
|
||||
{
|
||||
/* Too high */
|
||||
high = (mid - 1);
|
||||
} else {
|
||||
/* Check for success */
|
||||
if (mid == (bucket->entry_count - 1) || bucket->aes_ctr_ex_storage_entries[mid + 1].offset > offset) return &(bucket->aes_ctr_ex_storage_entries[mid]);
|
||||
low = (mid + 1);
|
||||
}
|
||||
}
|
||||
|
||||
LOGFILE("Failed to find offset 0x%lX in BKTR AesCtrEx storage block!", offset);
|
||||
return NULL;
|
||||
}
|
||||
|
|
|
@ -73,17 +73,11 @@ typedef struct {
|
|||
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.
|
||||
NcaPatchInfo *patch_info; ///< BKTR patch info block.
|
||||
u64 offset; ///< Patched RomFS image offset (relative to the start of the update NCA FS section).
|
||||
u64 size; ///< Patched RomFS image size.
|
||||
BktrIndirectStorageBlock *indirect_block;
|
||||
BktrAesCtrExStorageBlock *aes_ctr_ex_block;
|
||||
|
||||
|
||||
|
||||
|
||||
u64 virtual_seek; ///< Relative to the start of the NCA FS section.
|
||||
u64 base_seek; ///< Relative to the start of the NCA FS section (base NCA RomFS).
|
||||
u64 patch_seek; ///< Relative to the start of the NCA FS section (update NCA BKTR).
|
||||
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.
|
||||
} BktrContext;
|
||||
|
||||
/// Initializes a BKTR context.
|
||||
|
|
101
source/nca.c
101
source/nca.c
|
@ -52,6 +52,8 @@ 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 bool _ncaReadAesCtrExStorageFromBktrSection(NcaFsSectionContext *ctx, void *out, u64 read_size, u64 offset, u32 ctr_val, 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,6 +395,11 @@ bool ncaReadFsSection(NcaFsSectionContext *ctx, void *out, u64 read_size, u64 of
|
|||
return _ncaReadFsSection(ctx, out, read_size, offset, true);
|
||||
}
|
||||
|
||||
bool ncaReadAesCtrExStorageFromBktrSection(NcaFsSectionContext *ctx, void *out, u64 read_size, u64 offset, u32 ctr_val)
|
||||
{
|
||||
return _ncaReadAesCtrExStorageFromBktrSection(ctx, out, read_size, offset, ctr_val, true);
|
||||
}
|
||||
|
||||
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);
|
||||
|
@ -411,8 +418,9 @@ bool ncaGenerateHierarchicalSha256Patch(NcaFsSectionContext *ctx, const void *da
|
|||
|
||||
bool success = false;
|
||||
|
||||
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) || \
|
||||
if (!ctx || !(nca_ctx = (NcaContext*)ctx->nca_ctx) || !ctx->header || ctx->header->hash_type != NcaHashType_HierarchicalSha256 || ctx->header->encryption_type == NcaEncryptionType_AesCtrEx || \
|
||||
!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)
|
||||
{
|
||||
|
@ -529,8 +537,8 @@ bool ncaGenerateHierarchicalIntegrityPatch(NcaFsSectionContext *ctx, const void
|
|||
|
||||
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 || \
|
||||
if (!ctx || !(nca_ctx = (NcaContext*)ctx->nca_ctx) || !ctx->header || ctx->header->hash_type != NcaHashType_HierarchicalIntegrity || ctx->header->encryption_type == NcaEncryptionType_AesCtrEx || \
|
||||
!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!");
|
||||
|
@ -1053,6 +1061,83 @@ exit:
|
|||
return ret;
|
||||
}
|
||||
|
||||
static bool _ncaReadAesCtrExStorageFromBktrSection(NcaFsSectionContext *ctx, void *out, u64 read_size, u64 offset, u32 ctr_val, bool lock)
|
||||
{
|
||||
if (lock) mutexLock(&g_ncaCryptoBufferMutex);
|
||||
|
||||
bool ret = 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_PatchRomFs || \
|
||||
ctx->encryption_type != NcaEncryptionType_AesCtrEx || !ctx->header || !out || !read_size || offset >= ctx->section_size || (offset + read_size) > ctx->section_size)
|
||||
{
|
||||
LOGFILE("Invalid NCA FS section header parameters!");
|
||||
goto exit;
|
||||
}
|
||||
|
||||
NcaContext *nca_ctx = (NcaContext*)ctx->nca_ctx;
|
||||
u64 content_offset = (ctx->section_offset + offset);
|
||||
|
||||
u64 block_start_offset = 0, block_end_offset = 0, block_size = 0;
|
||||
u64 data_start_offset = 0, chunk_size = 0, out_chunk_size = 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 + read_size) > nca_ctx->content_size)
|
||||
{
|
||||
LOGFILE("Invalid NCA header parameters!");
|
||||
goto exit;
|
||||
}
|
||||
|
||||
/* Optimization for reads that are aligned to the AES-CTR sector size */
|
||||
if (!(content_offset % AES_BLOCK_SIZE) && !(read_size % AES_BLOCK_SIZE))
|
||||
{
|
||||
/* Read data */
|
||||
if (!ncaReadContentFile(nca_ctx, out, read_size, content_offset))
|
||||
{
|
||||
LOGFILE("Failed to read 0x%lX bytes data block at offset 0x%lX from NCA \"%s\" FS section #%u! (aligned)", read_size, content_offset, nca_ctx->content_id_str, ctx->section_num);
|
||||
goto exit;
|
||||
}
|
||||
|
||||
/* Decrypt data */
|
||||
ncaUpdateAesCtrExIv(ctx->ctr, ctr_val, content_offset);
|
||||
aes128CtrContextResetCtr(&(ctx->ctr_ctx), ctx->ctr);
|
||||
aes128CtrCrypt(&(ctx->ctr_ctx), out, out, read_size);
|
||||
|
||||
ret = true;
|
||||
goto exit;
|
||||
}
|
||||
|
||||
/* Calculate offsets and block sizes */
|
||||
block_start_offset = ALIGN_DOWN(content_offset, AES_BLOCK_SIZE);
|
||||
block_end_offset = ALIGN_UP(content_offset + read_size, AES_BLOCK_SIZE);
|
||||
block_size = (block_end_offset - block_start_offset);
|
||||
|
||||
data_start_offset = (content_offset - block_start_offset);
|
||||
chunk_size = (block_size > NCA_CRYPTO_BUFFER_SIZE ? NCA_CRYPTO_BUFFER_SIZE : block_size);
|
||||
out_chunk_size = (block_size > NCA_CRYPTO_BUFFER_SIZE ? (NCA_CRYPTO_BUFFER_SIZE - data_start_offset) : read_size);
|
||||
|
||||
/* Read data */
|
||||
if (!ncaReadContentFile(nca_ctx, g_ncaCryptoBuffer, chunk_size, block_start_offset))
|
||||
{
|
||||
LOGFILE("Failed to read 0x%lX bytes encrypted data block at offset 0x%lX from NCA \"%s\" FS section #%u! (unaligned)", chunk_size, block_start_offset, nca_ctx->content_id_str, ctx->section_num);
|
||||
goto exit;
|
||||
}
|
||||
|
||||
/* Decrypt data */
|
||||
ncaUpdateAesCtrExIv(ctx->ctr, ctr_val, block_start_offset);
|
||||
aes128CtrContextResetCtr(&(ctx->ctr_ctx), ctx->ctr);
|
||||
aes128CtrCrypt(&(ctx->ctr_ctx), g_ncaCryptoBuffer, g_ncaCryptoBuffer, chunk_size);
|
||||
|
||||
/* Copy decrypted data */
|
||||
memcpy(out, g_ncaCryptoBuffer + data_start_offset, out_chunk_size);
|
||||
|
||||
ret = (block_size > NCA_CRYPTO_BUFFER_SIZE ? _ncaReadAesCtrExStorageFromBktrSection(ctx, (u8*)out + out_chunk_size, read_size - out_chunk_size, offset + out_chunk_size, ctr_val, false) : true);
|
||||
|
||||
exit:
|
||||
if (lock) mutexUnlock(&g_ncaCryptoBufferMutex);
|
||||
|
||||
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);
|
||||
|
@ -1061,7 +1146,7 @@ static void *_ncaGenerateEncryptedFsSectionBlock(NcaFsSectionContext *ctx, const
|
|||
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_AesCtrEx || !ctx->header || !data || !data_size || data_offset >= ctx->section_size || \
|
||||
ctx->encryption_type == NcaEncryptionType_Auto || ctx->encryption_type >= NcaEncryptionType_AesCtrEx || !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!");
|
||||
|
@ -1087,7 +1172,7 @@ static void *_ncaGenerateEncryptedFsSectionBlock(NcaFsSectionContext *ctx, const
|
|||
/* 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 && !(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)))
|
||||
(ctx->encryption_type == NcaEncryptionType_AesCtr && !(content_offset % AES_BLOCK_SIZE) && !(data_size % AES_BLOCK_SIZE)))
|
||||
{
|
||||
/* Allocate memory */
|
||||
out = malloc(data_size);
|
||||
|
@ -1112,7 +1197,7 @@ static void *_ncaGenerateEncryptedFsSectionBlock(NcaFsSectionContext *ctx, const
|
|||
goto exit;
|
||||
}
|
||||
} else
|
||||
if (ctx->encryption_type == NcaEncryptionType_AesCtr || ctx->encryption_type == NcaEncryptionType_AesCtrEx)
|
||||
if (ctx->encryption_type == NcaEncryptionType_AesCtr)
|
||||
{
|
||||
ncaUpdateAesCtrIv(ctx->ctr, content_offset);
|
||||
aes128CtrContextResetCtr(&(ctx->ctr_ctx), ctx->ctr);
|
||||
|
@ -1164,7 +1249,7 @@ static void *_ncaGenerateEncryptedFsSectionBlock(NcaFsSectionContext *ctx, const
|
|||
goto exit;
|
||||
}
|
||||
} else
|
||||
if (ctx->encryption_type == NcaEncryptionType_AesCtr || ctx->encryption_type == NcaEncryptionType_AesCtrEx)
|
||||
if (ctx->encryption_type == NcaEncryptionType_AesCtr)
|
||||
{
|
||||
ncaUpdateAesCtrIv(ctx->ctr, content_offset);
|
||||
aes128CtrContextResetCtr(&(ctx->ctr_ctx), ctx->ctr);
|
||||
|
|
|
@ -321,13 +321,18 @@ bool ncaReadContentFile(NcaContext *ctx, void *out, u64 read_size, u64 offset);
|
|||
|
||||
/// Reads decrypted data from a NCA FS section using an input context.
|
||||
/// Input offset must be relative to the start of the NCA FS section.
|
||||
/// If dealing with Patch RomFS sections, this function should only be used when *not* reading the BKTR AesCtrEx storage.
|
||||
/// If dealing with Patch RomFS sections, this function should only be used when *not* reading BKTR AesCtrEx storage data. Use ncaReadAesCtrExStorageFromBktrSection() for that.
|
||||
bool ncaReadFsSection(NcaFsSectionContext *ctx, void *out, u64 read_size, u64 offset);
|
||||
|
||||
/// Reads decrypted BKTR AesCtrEx storage data from a NCA Patch RomFS section using an input context and a AesCtrEx CTR value.
|
||||
/// Input offset must be relative to the start of the NCA FS section.
|
||||
bool ncaReadAesCtrExStorageFromBktrSection(NcaFsSectionContext *ctx, void *out, u64 read_size, u64 offset, u32 ctr_val);
|
||||
|
||||
/// 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 seamlessly replace data while dumping a NCA.
|
||||
/// This function isn't compatible with Patch RomFS sections.
|
||||
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.
|
||||
|
|
|
@ -31,10 +31,9 @@ bool romfsInitializeContext(RomFileSystemContext *out, NcaFsSectionContext *nca_
|
|||
NcaContext *nca_ctx = NULL;
|
||||
u64 dir_table_offset = 0, file_table_offset = 0;
|
||||
|
||||
if (!out || !nca_fs_ctx || (nca_fs_ctx->section_type != NcaFsSectionType_RomFs && nca_fs_ctx->section_type != NcaFsSectionType_PatchRomFs) || !nca_fs_ctx->header || \
|
||||
!(nca_ctx = (NcaContext*)nca_fs_ctx->nca_ctx) || (nca_ctx->format_version == NcaVersion_Nca0 && (nca_fs_ctx->section_type != NcaFsSectionType_Nca0RomFs || \
|
||||
nca_fs_ctx->header->hash_type != NcaHashType_HierarchicalSha256)) || (nca_ctx->format_version != NcaVersion_Nca0 && (nca_fs_ctx->section_type != NcaFsSectionType_RomFs || \
|
||||
nca_fs_ctx->header->hash_type != NcaHashType_HierarchicalIntegrity)))
|
||||
if (!out || !nca_fs_ctx || !nca_fs_ctx->header || !(nca_ctx = (NcaContext*)nca_fs_ctx->nca_ctx) || (nca_ctx->format_version == NcaVersion_Nca0 && \
|
||||
(nca_fs_ctx->section_type != NcaFsSectionType_Nca0RomFs || nca_fs_ctx->header->hash_type != NcaHashType_HierarchicalSha256)) || (nca_ctx->format_version != NcaVersion_Nca0 && \
|
||||
(nca_fs_ctx->section_type != NcaFsSectionType_RomFs || nca_fs_ctx->header->hash_type != NcaHashType_HierarchicalIntegrity)))
|
||||
{
|
||||
LOGFILE("Invalid parameters!");
|
||||
return false;
|
||||
|
@ -61,8 +60,7 @@ bool romfsInitializeContext(RomFileSystemContext *out, NcaFsSectionContext *nca_
|
|||
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;
|
||||
} else {
|
||||
/* Don't verify offsets from Patch RomFS sections, because they reflect the full, patched RomFS image */
|
||||
if (nca_fs_ctx->encryption_type != NcaEncryptionType_AesCtrEx && !ncaValidateHierarchicalIntegrityOffsets(&(nca_fs_ctx->header->hash_info.hierarchical_integrity), nca_fs_ctx->section_size))
|
||||
if (!ncaValidateHierarchicalIntegrityOffsets(&(nca_fs_ctx->header->hash_info.hierarchical_integrity), nca_fs_ctx->section_size))
|
||||
{
|
||||
LOGFILE("Invalid HierarchicalIntegrity block!");
|
||||
return false;
|
||||
|
@ -92,8 +90,7 @@ bool romfsInitializeContext(RomFileSystemContext *out, NcaFsSectionContext *nca_
|
|||
dir_table_offset = (nca_fs_ctx->section_type == NcaFsSectionType_Nca0RomFs ? (u64)out->header.old_format.directory_entry_offset : out->header.cur_format.directory_entry_offset);
|
||||
out->dir_table_size = (nca_fs_ctx->section_type == NcaFsSectionType_Nca0RomFs ? (u64)out->header.old_format.directory_entry_size : out->header.cur_format.directory_entry_size);
|
||||
|
||||
/* Don't verify offsets from Patch RomFS sections, because they reflect the full, patched RomFS image */
|
||||
if (!out->dir_table_size || (nca_fs_ctx->encryption_type != NcaEncryptionType_AesCtrEx && (dir_table_offset >= out->size || (dir_table_offset + out->dir_table_size) > out->size)))
|
||||
if (!out->dir_table_size || dir_table_offset >= out->size || (dir_table_offset + out->dir_table_size) > out->size)
|
||||
{
|
||||
LOGFILE("Invalid RomFS directory entries table!");
|
||||
return false;
|
||||
|
@ -116,8 +113,7 @@ bool romfsInitializeContext(RomFileSystemContext *out, NcaFsSectionContext *nca_
|
|||
file_table_offset = (nca_fs_ctx->section_type == NcaFsSectionType_Nca0RomFs ? (u64)out->header.old_format.file_entry_offset : out->header.cur_format.file_entry_offset);
|
||||
out->file_table_size = (nca_fs_ctx->section_type == NcaFsSectionType_Nca0RomFs ? (u64)out->header.old_format.file_entry_size : out->header.cur_format.file_entry_size);
|
||||
|
||||
/* Don't verify offsets from Patch RomFS sections, because they reflect the full, patched RomFS image */
|
||||
if (!out->file_table_size || (nca_fs_ctx->encryption_type != NcaEncryptionType_AesCtrEx && (file_table_offset >= out->size || (file_table_offset + out->file_table_size) > out->size)))
|
||||
if (!out->file_table_size || file_table_offset >= out->size || (file_table_offset + out->file_table_size) > out->size)
|
||||
{
|
||||
LOGFILE("Invalid RomFS file entries table!");
|
||||
goto exit;
|
||||
|
@ -137,9 +133,8 @@ bool romfsInitializeContext(RomFileSystemContext *out, NcaFsSectionContext *nca_
|
|||
}
|
||||
|
||||
/* Get file data body offset */
|
||||
/* Don't verify offsets from Patch RomFS sections, because they reflect the full, patched RomFS image */
|
||||
out->body_offset = (nca_fs_ctx->section_type == NcaFsSectionType_Nca0RomFs ? (u64)out->header.old_format.body_offset : out->header.cur_format.body_offset);
|
||||
if (nca_fs_ctx->encryption_type != NcaEncryptionType_AesCtrEx && out->body_offset >= out->size)
|
||||
if (out->body_offset >= out->size)
|
||||
{
|
||||
LOGFILE("Invalid RomFS file data body!");
|
||||
goto exit;
|
||||
|
|
Loading…
Reference in a new issue