2020-04-29 13:59:28 +01:00
|
|
|
/*
|
2020-07-03 10:31:22 +01:00
|
|
|
* bktr.c
|
2020-04-29 13:59:28 +01:00
|
|
|
*
|
2020-07-07 12:55:37 +01:00
|
|
|
* Copyright (c) 2018-2020, SciresM.
|
2020-12-23 17:48:57 +00:00
|
|
|
* Copyright (c) 2020-2021, DarkMatterCore <pabloacurielz@gmail.com>.
|
2020-07-03 10:31:22 +01:00
|
|
|
*
|
|
|
|
* This file is part of nxdumptool (https://github.com/DarkMatterCore/nxdumptool).
|
|
|
|
*
|
|
|
|
* nxdumptool is free software; you can redistribute it and/or modify it
|
2020-04-29 13:59:28 +01:00
|
|
|
* under the terms and conditions of the GNU General Public License,
|
|
|
|
* version 2, as published by the Free Software Foundation.
|
|
|
|
*
|
2020-07-03 10:31:22 +01:00
|
|
|
* nxdumptool is distributed in the hope it will be useful, but WITHOUT
|
2020-04-29 13:59:28 +01:00
|
|
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
|
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
|
|
|
* more details.
|
|
|
|
*
|
|
|
|
* You should have received a copy of the GNU General Public License
|
|
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include "utils.h"
|
2020-07-03 10:31:22 +01:00
|
|
|
#include "bktr.h"
|
2020-04-29 13:59:28 +01:00
|
|
|
|
|
|
|
/* Function prototypes. */
|
|
|
|
|
2020-04-30 09:25:03 +01:00
|
|
|
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);
|
2020-04-29 13:59:28 +01:00
|
|
|
|
2020-04-30 09:25:03 +01:00
|
|
|
NX_INLINE BktrIndirectStorageBucket *bktrGetIndirectStorageBucket(BktrIndirectStorageBlock *block, u32 bucket_num);
|
|
|
|
static BktrIndirectStorageEntry *bktrGetIndirectStorageEntry(BktrIndirectStorageBlock *block, u64 offset);
|
2020-04-29 13:59:28 +01:00
|
|
|
|
2020-04-30 09:25:03 +01:00
|
|
|
NX_INLINE BktrAesCtrExStorageBucket *bktrGetAesCtrExStorageBucket(BktrAesCtrExStorageBlock *block, u32 bucket_num);
|
|
|
|
static BktrAesCtrExStorageEntry *bktrGetAesCtrExStorageEntry(BktrAesCtrExStorageBlock *block, u64 offset);
|
2020-04-29 13:59:28 +01:00
|
|
|
|
2020-04-29 22:11:27 +01:00
|
|
|
bool bktrInitializeContext(BktrContext *out, NcaFsSectionContext *base_nca_fs_ctx, NcaFsSectionContext *update_nca_fs_ctx)
|
2020-04-29 13:59:28 +01:00
|
|
|
{
|
2020-04-29 22:11:27 +01:00
|
|
|
NcaContext *base_nca_ctx = NULL, *update_nca_ctx = NULL;
|
2020-04-29 13:59:28 +01:00
|
|
|
|
2020-09-26 11:49:18 +01:00
|
|
|
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) || \
|
2020-07-22 09:03:28 +01:00
|
|
|
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_bucket.header.magic) != NCA_BKTR_MAGIC || \
|
|
|
|
__builtin_bswap32(update_nca_fs_ctx->header.patch_info.aes_ctr_ex_bucket.header.magic) != NCA_BKTR_MAGIC || \
|
|
|
|
(update_nca_fs_ctx->header.patch_info.indirect_bucket.offset + update_nca_fs_ctx->header.patch_info.indirect_bucket.size) != update_nca_fs_ctx->header.patch_info.aes_ctr_ex_bucket.offset || \
|
2020-10-21 05:27:48 +01:00
|
|
|
(update_nca_fs_ctx->header.patch_info.aes_ctr_ex_bucket.offset + update_nca_fs_ctx->header.patch_info.aes_ctr_ex_bucket.size) != update_nca_fs_ctx->section_size || \
|
|
|
|
(base_nca_ctx->rights_id_available && !base_nca_ctx->titlekey_retrieved) || (update_nca_ctx->rights_id_available && !update_nca_ctx->titlekey_retrieved))
|
2020-04-29 13:59:28 +01:00
|
|
|
{
|
|
|
|
LOGFILE("Invalid parameters!");
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2020-10-14 14:23:49 +01:00
|
|
|
/* Free output context beforehand. */
|
|
|
|
bktrFreeContext(out);
|
|
|
|
|
2020-09-26 11:49:18 +01:00
|
|
|
/* Update missing base NCA RomFS status. */
|
|
|
|
out->missing_base_romfs = (!base_nca_fs_ctx->enabled || base_nca_fs_ctx->section_type != NcaFsSectionType_RomFs || base_nca_fs_ctx->encryption_type == NcaEncryptionType_AesCtrEx);
|
|
|
|
|
2020-07-06 01:10:07 +01:00
|
|
|
/* Initialize base NCA RomFS context. */
|
2020-09-26 11:49:18 +01:00
|
|
|
if (!out->missing_base_romfs && !romfsInitializeContext(&(out->base_romfs_ctx), base_nca_fs_ctx))
|
2020-04-29 13:59:28 +01:00
|
|
|
{
|
2020-04-29 22:11:27 +01:00
|
|
|
LOGFILE("Failed to initialize base NCA RomFS context!");
|
2020-04-29 13:59:28 +01:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2020-07-06 01:10:07 +01:00
|
|
|
/* Fill context. */
|
2020-04-30 09:25:03 +01:00
|
|
|
bool success = false;
|
2020-07-22 09:03:28 +01:00
|
|
|
NcaPatchInfo *patch_info = &(update_nca_fs_ctx->header.patch_info);
|
2020-04-30 09:25:03 +01:00
|
|
|
|
2020-07-06 01:10:07 +01:00
|
|
|
/* Allocate space for an extra (fake) indirect storage entry, to simplify our logic. */
|
2020-07-22 09:03:28 +01:00
|
|
|
out->indirect_block = calloc(1, patch_info->indirect_bucket.size + ((0x3FF0 / sizeof(u64)) * sizeof(BktrIndirectStorageEntry)));
|
2020-04-30 09:25:03 +01:00
|
|
|
if (!out->indirect_block)
|
|
|
|
{
|
|
|
|
LOGFILE("Unable to allocate memory for the BKTR Indirect Storage Block!");
|
2020-07-13 07:36:17 +01:00
|
|
|
goto end;
|
2020-04-30 09:25:03 +01:00
|
|
|
}
|
|
|
|
|
2020-07-06 01:10:07 +01:00
|
|
|
/* Read indirect storage block data. */
|
2020-07-22 09:03:28 +01:00
|
|
|
if (!ncaReadFsSection(update_nca_fs_ctx, out->indirect_block, patch_info->indirect_bucket.size, patch_info->indirect_bucket.offset))
|
2020-04-30 09:25:03 +01:00
|
|
|
{
|
|
|
|
LOGFILE("Failed to read BKTR Indirect Storage Block data!");
|
2020-07-13 07:36:17 +01:00
|
|
|
goto end;
|
2020-04-30 09:25:03 +01:00
|
|
|
}
|
|
|
|
|
2020-07-06 01:10:07 +01:00
|
|
|
/* Allocate space for an extra (fake) AesCtrEx storage entry, to simplify our logic. */
|
2020-07-22 09:03:28 +01:00
|
|
|
out->aes_ctr_ex_block = calloc(1, patch_info->aes_ctr_ex_bucket.size + (((0x3FF0 / sizeof(u64)) + 1) * sizeof(BktrAesCtrExStorageEntry)));
|
2020-04-30 09:25:03 +01:00
|
|
|
if (!out->aes_ctr_ex_block)
|
|
|
|
{
|
|
|
|
LOGFILE("Unable to allocate memory for the BKTR AesCtrEx Storage Block!");
|
2020-07-13 07:36:17 +01:00
|
|
|
goto end;
|
2020-04-30 09:25:03 +01:00
|
|
|
}
|
|
|
|
|
2020-07-06 01:10:07 +01:00
|
|
|
/* Read AesCtrEx storage block data. */
|
2020-07-22 09:03:28 +01:00
|
|
|
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))
|
2020-04-30 09:25:03 +01:00
|
|
|
{
|
|
|
|
LOGFILE("Failed to read BKTR AesCtrEx Storage Block data!");
|
2020-07-13 07:36:17 +01:00
|
|
|
goto end;
|
2020-04-30 09:25:03 +01:00
|
|
|
}
|
|
|
|
|
2020-07-22 09:03:28 +01:00
|
|
|
if (out->aes_ctr_ex_block->physical_size != patch_info->aes_ctr_ex_bucket.offset)
|
2020-04-30 09:25:03 +01:00
|
|
|
{
|
|
|
|
LOGFILE("Invalid BKTR AesCtrEx Storage Block size!");
|
2020-07-13 07:36:17 +01:00
|
|
|
goto end;
|
2020-04-30 09:25:03 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/* 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);
|
2020-04-30 12:24:08 +01:00
|
|
|
last_indirect_bucket->indirect_storage_entries[last_indirect_bucket->entry_count].virtual_offset = out->indirect_block->virtual_size;
|
2020-07-22 09:03:28 +01:00
|
|
|
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;
|
2020-04-30 09:25:03 +01:00
|
|
|
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;
|
|
|
|
|
2020-07-06 01:10:07 +01:00
|
|
|
/* Initialize update NCA RomFS context. */
|
|
|
|
/* Don't verify offsets from Patch RomFS sections, because they reflect the full, patched RomFS image. */
|
2020-04-30 09:25:03 +01:00
|
|
|
out->patch_romfs_ctx.nca_fs_ctx = update_nca_fs_ctx;
|
2020-07-22 09:03:28 +01:00
|
|
|
out->patch_romfs_ctx.offset = out->offset = update_nca_fs_ctx->header.hash_data.integrity_meta_info.info_level_hash.level_information[NCA_IVFC_LEVEL_COUNT - 1].offset;
|
|
|
|
out->patch_romfs_ctx.size = out->size = update_nca_fs_ctx->header.hash_data.integrity_meta_info.info_level_hash.level_information[NCA_IVFC_LEVEL_COUNT - 1].size;
|
2020-04-30 09:25:03 +01:00
|
|
|
|
2020-07-07 12:55:37 +01:00
|
|
|
/* Read update NCA RomFS header. */
|
2020-04-30 09:25:03 +01:00
|
|
|
if (!bktrPhysicalSectionRead(out, &(out->patch_romfs_ctx.header), sizeof(RomFileSystemHeader), out->patch_romfs_ctx.offset))
|
2020-04-29 13:59:28 +01:00
|
|
|
{
|
2020-04-30 09:25:03 +01:00
|
|
|
LOGFILE("Failed to read update NCA RomFS header!");
|
2020-07-13 07:36:17 +01:00
|
|
|
goto end;
|
2020-04-29 13:59:28 +01:00
|
|
|
}
|
|
|
|
|
2020-04-30 09:25:03 +01:00
|
|
|
if (out->patch_romfs_ctx.header.cur_format.header_size != ROMFS_HEADER_SIZE)
|
|
|
|
{
|
|
|
|
LOGFILE("Invalid update NCA RomFS header size!");
|
2020-07-13 07:36:17 +01:00
|
|
|
goto end;
|
2020-04-30 09:25:03 +01:00
|
|
|
}
|
|
|
|
|
2020-07-06 01:10:07 +01:00
|
|
|
/* Read directory entries table. */
|
2020-04-30 09:25:03 +01:00
|
|
|
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;
|
2020-04-29 13:59:28 +01:00
|
|
|
|
2020-04-30 09:25:03 +01:00
|
|
|
if (!dir_table_offset || !out->patch_romfs_ctx.dir_table_size)
|
|
|
|
{
|
|
|
|
LOGFILE("Invalid update NCA RomFS directory entries table!");
|
2020-07-13 07:36:17 +01:00
|
|
|
goto end;
|
2020-04-30 09:25:03 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
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!");
|
2020-07-13 07:36:17 +01:00
|
|
|
goto end;
|
2020-04-30 09:25:03 +01:00
|
|
|
}
|
2020-04-29 13:59:28 +01:00
|
|
|
|
2020-04-30 12:24:08 +01:00
|
|
|
if (!bktrPhysicalSectionRead(out, out->patch_romfs_ctx.dir_table, out->patch_romfs_ctx.dir_table_size, out->patch_romfs_ctx.offset + dir_table_offset))
|
2020-04-30 09:25:03 +01:00
|
|
|
{
|
|
|
|
LOGFILE("Failed to read update NCA RomFS directory entries table!");
|
2020-07-13 07:36:17 +01:00
|
|
|
goto end;
|
2020-04-30 09:25:03 +01:00
|
|
|
}
|
|
|
|
|
2020-07-06 01:10:07 +01:00
|
|
|
/* Read file entries table. */
|
2020-04-30 09:25:03 +01:00
|
|
|
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;
|
2020-04-29 13:59:28 +01:00
|
|
|
|
2020-04-30 09:25:03 +01:00
|
|
|
if (!file_table_offset || !out->patch_romfs_ctx.file_table_size)
|
|
|
|
{
|
|
|
|
LOGFILE("Invalid update NCA RomFS file entries table!");
|
2020-07-13 07:36:17 +01:00
|
|
|
goto end;
|
2020-04-30 09:25:03 +01:00
|
|
|
}
|
2020-04-29 13:59:28 +01:00
|
|
|
|
2020-04-30 09:25:03 +01:00
|
|
|
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!");
|
2020-07-13 07:36:17 +01:00
|
|
|
goto end;
|
2020-04-30 09:25:03 +01:00
|
|
|
}
|
2020-04-29 13:59:28 +01:00
|
|
|
|
2020-04-30 12:24:08 +01:00
|
|
|
if (!bktrPhysicalSectionRead(out, out->patch_romfs_ctx.file_table, out->patch_romfs_ctx.file_table_size, out->patch_romfs_ctx.offset + file_table_offset))
|
2020-04-30 09:25:03 +01:00
|
|
|
{
|
|
|
|
LOGFILE("Failed to read update NCA RomFS file entries table!");
|
2020-07-13 07:36:17 +01:00
|
|
|
goto end;
|
2020-04-30 09:25:03 +01:00
|
|
|
}
|
2020-04-29 13:59:28 +01:00
|
|
|
|
2020-07-06 01:10:07 +01:00
|
|
|
/* Get file data body offset. */
|
2020-04-30 09:25:03 +01:00
|
|
|
out->patch_romfs_ctx.body_offset = out->body_offset = out->patch_romfs_ctx.header.cur_format.body_offset;
|
2020-04-29 13:59:28 +01:00
|
|
|
|
|
|
|
success = true;
|
|
|
|
|
2020-07-13 07:36:17 +01:00
|
|
|
end:
|
2020-04-29 22:11:27 +01:00
|
|
|
if (!success) bktrFreeContext(out);
|
2020-04-29 13:59:28 +01:00
|
|
|
|
|
|
|
return success;
|
|
|
|
}
|
|
|
|
|
2020-04-30 09:25:03 +01:00
|
|
|
bool bktrReadFileSystemData(BktrContext *ctx, void *out, u64 read_size, u64 offset)
|
|
|
|
{
|
2020-10-21 05:27:48 +01:00
|
|
|
if (!ctx || !ctx->size || !out || !read_size || (offset + read_size) > ctx->size)
|
2020-04-30 09:25:03 +01:00
|
|
|
{
|
|
|
|
LOGFILE("Invalid parameters!");
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2020-07-06 01:10:07 +01:00
|
|
|
/* Read filesystem data. */
|
2020-04-30 09:25:03 +01:00
|
|
|
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)
|
|
|
|
{
|
2020-10-21 05:27:48 +01:00
|
|
|
if (!ctx || !ctx->body_offset || !file_entry || !file_entry->size || (file_entry->offset + file_entry->size) > ctx->size || !out || !read_size || (offset + read_size) > file_entry->size)
|
2020-04-30 09:25:03 +01:00
|
|
|
{
|
|
|
|
LOGFILE("Invalid parameters!");
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2020-07-06 01:10:07 +01:00
|
|
|
/* Read entry data. */
|
2020-04-30 09:25:03 +01:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2020-05-01 05:34:30 +01:00
|
|
|
bool bktrIsFileEntryUpdated(BktrContext *ctx, RomFileSystemFileEntry *file_entry, bool *out)
|
|
|
|
{
|
2020-10-21 05:27:48 +01:00
|
|
|
if (!ctx || !ctx->body_offset || !ctx->indirect_block || !file_entry || !file_entry->size || (file_entry->offset + file_entry->size) > ctx->size || !out)
|
2020-05-01 05:34:30 +01:00
|
|
|
{
|
|
|
|
LOGFILE("Invalid parameters!");
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool updated = false;
|
|
|
|
u64 file_offset = (ctx->offset + ctx->body_offset + file_entry->offset);
|
|
|
|
BktrIndirectStorageEntry *indirect_entry = NULL, *last_indirect_entry = NULL;
|
|
|
|
|
|
|
|
indirect_entry = bktrGetIndirectStorageEntry(ctx->indirect_block, file_offset);
|
|
|
|
if (!indirect_entry)
|
|
|
|
{
|
|
|
|
LOGFILE("Error retrieving BKTR Indirect Storage Entry at offset 0x%lX!", file_offset);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
last_indirect_entry = indirect_entry;
|
|
|
|
|
|
|
|
while(last_indirect_entry->virtual_offset < (file_offset + file_entry->size)) last_indirect_entry++;
|
|
|
|
|
|
|
|
while(indirect_entry < last_indirect_entry)
|
|
|
|
{
|
|
|
|
if (indirect_entry->indirect_storage_index == BktrIndirectStorageIndex_Patch)
|
|
|
|
{
|
|
|
|
updated = true;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
indirect_entry++;
|
|
|
|
}
|
|
|
|
|
|
|
|
*out = updated;
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2020-04-30 09:25:03 +01:00
|
|
|
static bool bktrPhysicalSectionRead(BktrContext *ctx, void *out, u64 read_size, u64 offset)
|
|
|
|
{
|
2020-09-26 11:49:18 +01:00
|
|
|
if (!ctx || (!ctx->missing_base_romfs && !ctx->base_romfs_ctx.nca_fs_ctx) || !ctx->indirect_block || !out || !read_size)
|
2020-04-30 09:25:03 +01:00
|
|
|
{
|
|
|
|
LOGFILE("Invalid parameters!");
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
BktrIndirectStorageEntry *indirect_entry = NULL, *next_indirect_entry = NULL;
|
|
|
|
u64 section_offset = 0, indirect_block_size = 0;
|
|
|
|
|
2020-07-06 01:10:07 +01:00
|
|
|
/* Determine which FS section to use + the actual offset to start reading from. */
|
|
|
|
/* There's no better way to do this than making all BKTR addresses virtual. */
|
2020-04-30 09:25:03 +01:00
|
|
|
indirect_entry = bktrGetIndirectStorageEntry(ctx->indirect_block, offset);
|
|
|
|
if (!indirect_entry)
|
|
|
|
{
|
|
|
|
LOGFILE("Error retrieving BKTR Indirect Storage Entry at offset 0x%lX!", offset);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2020-04-30 12:24:08 +01:00
|
|
|
next_indirect_entry = (indirect_entry + 1);
|
2020-04-30 09:25:03 +01:00
|
|
|
section_offset = (offset - indirect_entry->virtual_offset + indirect_entry->physical_offset);
|
|
|
|
|
2020-07-06 01:10:07 +01:00
|
|
|
/* Perform read operation. */
|
2020-04-30 09:25:03 +01:00
|
|
|
bool success = false;
|
|
|
|
if ((offset + read_size) <= next_indirect_entry->virtual_offset)
|
|
|
|
{
|
2020-07-06 01:10:07 +01:00
|
|
|
/* 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. */
|
2020-04-30 09:25:03 +01:00
|
|
|
if (indirect_entry->indirect_storage_index == BktrIndirectStorageIndex_Patch)
|
|
|
|
{
|
|
|
|
success = bktrAesCtrExStorageRead(ctx, out, read_size, offset, section_offset);
|
2020-04-30 12:24:08 +01:00
|
|
|
if (!success) LOGFILE("Failed to read 0x%lX bytes block from BKTR AesCtrEx storage at offset 0x%lX!", read_size, section_offset);
|
2020-09-26 11:49:18 +01:00
|
|
|
} else
|
|
|
|
if (!ctx->missing_base_romfs)
|
|
|
|
{
|
2020-04-30 09:25:03 +01:00
|
|
|
success = ncaReadFsSection(ctx->base_romfs_ctx.nca_fs_ctx, out, read_size, section_offset);
|
2020-04-30 12:24:08 +01:00
|
|
|
if (!success) LOGFILE("Failed to read 0x%lX bytes block from base RomFS at offset 0x%lX!", read_size, section_offset);
|
2020-09-26 11:49:18 +01:00
|
|
|
} else {
|
|
|
|
LOGFILE("Attempting to read 0x%lX bytes block from non-existent base RomFS at offset 0x%lX!", read_size, section_offset);
|
2020-04-30 09:25:03 +01:00
|
|
|
}
|
|
|
|
} else {
|
2020-07-06 01:10:07 +01:00
|
|
|
/* Handle reads that span multiple indirect storage entries. */
|
2020-04-30 09:25:03 +01:00
|
|
|
indirect_block_size = (next_indirect_entry->virtual_offset - offset);
|
|
|
|
|
2020-04-30 12:24:08 +01:00
|
|
|
success = (bktrPhysicalSectionRead(ctx, out, indirect_block_size, offset) && \
|
|
|
|
bktrPhysicalSectionRead(ctx, (u8*)out + indirect_block_size, read_size - indirect_block_size, offset + indirect_block_size));
|
|
|
|
if (!success) LOGFILE("Failed to read 0x%lX bytes block from multiple BKTR indirect storage entries at offset 0x%lX!", read_size, section_offset);
|
2020-04-30 09:25:03 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2020-04-30 12:24:08 +01:00
|
|
|
next_aes_ctr_ex_entry = (aes_ctr_ex_entry + 1);
|
|
|
|
|
2020-07-06 01:10:07 +01:00
|
|
|
/* Perform read operation. */
|
2020-04-30 09:25:03 +01:00
|
|
|
bool success = false;
|
|
|
|
if ((section_offset + read_size) <= next_aes_ctr_ex_entry->offset)
|
|
|
|
{
|
2020-07-06 01:10:07 +01:00
|
|
|
/* Read only within the current AesCtrEx storage entry. */
|
2020-04-30 09:25:03 +01:00
|
|
|
success = ncaReadAesCtrExStorageFromBktrSection(ctx->patch_romfs_ctx.nca_fs_ctx, out, read_size, section_offset, aes_ctr_ex_entry->generation);
|
|
|
|
} else {
|
2020-07-06 01:10:07 +01:00
|
|
|
/* Handle read that spans multiple AesCtrEx storage entries. */
|
2020-04-30 09:25:03 +01:00
|
|
|
u64 aes_ctr_ex_block_size = (next_aes_ctr_ex_entry->offset - section_offset);
|
|
|
|
|
2020-04-30 12:24:08 +01:00
|
|
|
success = (bktrPhysicalSectionRead(ctx, out, aes_ctr_ex_block_size, virtual_offset) && \
|
|
|
|
bktrPhysicalSectionRead(ctx, (u8*)out + aes_ctr_ex_block_size, read_size - aes_ctr_ex_block_size, virtual_offset + aes_ctr_ex_block_size));
|
2020-04-30 09:25:03 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
return success;
|
|
|
|
}
|
2020-04-29 13:59:28 +01:00
|
|
|
|
2020-04-30 09:25:03 +01:00
|
|
|
NX_INLINE BktrIndirectStorageBucket *bktrGetIndirectStorageBucket(BktrIndirectStorageBlock *block, u32 bucket_num)
|
|
|
|
{
|
|
|
|
if (!block || bucket_num >= block->bucket_count) return NULL;
|
2020-04-30 12:24:08 +01:00
|
|
|
return (BktrIndirectStorageBucket*)((u8*)block->indirect_storage_buckets + ((sizeof(BktrIndirectStorageBucket) + sizeof(BktrIndirectStorageEntry)) * (u64)bucket_num));
|
2020-04-30 09:25:03 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
static BktrIndirectStorageEntry *bktrGetIndirectStorageEntry(BktrIndirectStorageBlock *block, u64 offset)
|
|
|
|
{
|
2020-04-30 12:24:08 +01:00
|
|
|
if (!block || !block->bucket_count || offset >= block->virtual_size)
|
2020-04-30 09:25:03 +01:00
|
|
|
{
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2020-07-06 01:10:07 +01:00
|
|
|
/* Check for edge case, short circuit. */
|
2020-04-30 09:25:03 +01:00
|
|
|
if (bucket->entry_count == 1) return &(bucket->indirect_storage_entries[0]);
|
|
|
|
|
2020-07-06 01:10:07 +01:00
|
|
|
/* Binary search. */
|
2020-04-30 09:25:03 +01:00
|
|
|
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)
|
|
|
|
{
|
2020-07-06 01:10:07 +01:00
|
|
|
/* Too high. */
|
2020-04-30 09:25:03 +01:00
|
|
|
high = (mid - 1);
|
|
|
|
} else {
|
2020-07-06 01:10:07 +01:00
|
|
|
/* Check for success. */
|
2020-04-30 09:25:03 +01:00
|
|
|
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;
|
|
|
|
}
|
2020-04-29 13:59:28 +01:00
|
|
|
|
2020-04-30 09:25:03 +01:00
|
|
|
NX_INLINE BktrAesCtrExStorageBucket *bktrGetAesCtrExStorageBucket(BktrAesCtrExStorageBlock *block, u32 bucket_num)
|
|
|
|
{
|
|
|
|
if (!block || bucket_num >= block->bucket_count) return NULL;
|
2020-04-30 12:24:08 +01:00
|
|
|
return (BktrAesCtrExStorageBucket*)((u8*)block->aes_ctr_ex_storage_buckets + ((sizeof(BktrAesCtrExStorageBucket) + sizeof(BktrAesCtrExStorageEntry)) * (u64)bucket_num));
|
2020-04-30 09:25:03 +01:00
|
|
|
}
|
2020-04-29 13:59:28 +01:00
|
|
|
|
2020-04-30 09:25:03 +01:00
|
|
|
static BktrAesCtrExStorageEntry *bktrGetAesCtrExStorageEntry(BktrAesCtrExStorageBlock *block, u64 offset)
|
|
|
|
{
|
2020-04-30 12:24:08 +01:00
|
|
|
if (!block || !block->bucket_count || offset >= block->physical_size)
|
2020-04-30 09:25:03 +01:00
|
|
|
{
|
|
|
|
LOGFILE("Invalid parameters!");
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
u32 bucket_num = 0;
|
|
|
|
BktrAesCtrExStorageBucket *last_bucket = NULL, *bucket = NULL;
|
|
|
|
|
|
|
|
last_bucket = bktrGetAesCtrExStorageBucket(block, block->bucket_count - 1);
|
2020-04-30 12:24:08 +01:00
|
|
|
if (!last_bucket || !last_bucket->entry_count)
|
2020-04-30 09:25:03 +01:00
|
|
|
{
|
|
|
|
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);
|
2020-04-30 12:24:08 +01:00
|
|
|
if (!bucket || !bucket->entry_count)
|
2020-04-30 09:25:03 +01:00
|
|
|
{
|
|
|
|
LOGFILE("Error retrieving BKTR AesCtrEx storage bucket #%u!", bucket_num);
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
2020-07-06 01:10:07 +01:00
|
|
|
/* Check for edge case, short circuit. */
|
2020-04-30 09:25:03 +01:00
|
|
|
if (bucket->entry_count == 1) return &(bucket->aes_ctr_ex_storage_entries[0]);
|
|
|
|
|
2020-07-06 01:10:07 +01:00
|
|
|
/* Binary search. */
|
2020-04-30 09:25:03 +01:00
|
|
|
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)
|
|
|
|
{
|
2020-07-06 01:10:07 +01:00
|
|
|
/* Too high. */
|
2020-04-30 09:25:03 +01:00
|
|
|
high = (mid - 1);
|
|
|
|
} else {
|
2020-07-06 01:10:07 +01:00
|
|
|
/* Check for success. */
|
2020-04-30 09:25:03 +01:00
|
|
|
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;
|
|
|
|
}
|