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 .
2022-03-17 12:58:40 +00:00
* Copyright ( c ) 2020 - 2022 , DarkMatterCore < pabloacurielz @ gmail . com > .
2020-07-03 10:31:22 +01:00
*
* This file is part of nxdumptool ( https : //github.com/DarkMatterCore/nxdumptool).
*
2021-03-25 19:26:58 +00:00
* nxdumptool is free software : you can redistribute it and / or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation , either version 3 of the License , or
* ( at your option ) any later version .
2020-04-29 13:59:28 +01:00
*
2021-03-25 19:26:58 +00:00
* nxdumptool is distributed in the hope that it will be useful ,
* but WITHOUT ANY WARRANTY ; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the
* GNU General Public License for more details .
2020-04-29 13:59:28 +01:00
*
* You should have received a copy of the GNU General Public License
2021-03-25 19:26:58 +00:00
* along with this program . If not , see < https : //www.gnu.org/licenses/>.
2020-04-29 13:59:28 +01:00
*/
2021-03-26 04:35:14 +00:00
# include "nxdt_utils.h"
2020-07-03 10:31:22 +01:00
# include "bktr.h"
2022-06-29 13:41:58 +01:00
# include "aes.h"
2020-04-29 13:59:28 +01:00
2022-07-02 11:09:49 +01:00
/* Type definitions. */
typedef struct {
u64 offset ;
u32 stride ;
} BucketTreeStorageNodeOffset ;
typedef struct {
BucketTreeStorageNodeOffset start ;
u32 count ;
u32 index ;
} BucketTreeStorageNode ;
2022-07-02 23:37:13 +01:00
typedef struct {
BucketTreeNodeHeader header ;
u64 start ;
} BucketTreeEntrySetHeader ;
NXDT_ASSERT ( BucketTreeEntrySetHeader , BKTR_NODE_HEADER_SIZE + 0x8 ) ;
2022-07-02 15:07:08 +01:00
typedef struct {
BucketTreeContext * bktr_ctx ;
2022-07-02 23:37:13 +01:00
BucketTreeEntrySetHeader entry_set ;
2022-07-02 15:07:08 +01:00
u32 entry_index ;
void * entry ;
} BucketTreeVisitor ;
2022-07-03 11:05:30 +01:00
typedef struct {
void * buffer ;
u64 offset ;
u64 size ;
u64 virtual_offset ;
u32 ctr_val ;
2022-07-03 15:29:22 +01:00
bool aes_ctr_ex_crypt ;
2022-07-03 11:05:30 +01:00
u8 parent_storage_type ; ///< BucketTreeStorageType.
} BucketTreeSubStorageReadParams ;
2022-07-02 11:09:49 +01:00
/* Global variables. */
2022-07-12 04:27:30 +01:00
# if LOG_LEVEL <= LOG_LEVEL_ERROR
2022-07-02 11:09:49 +01:00
static const char * g_bktrStorageTypeNames [ ] = {
[ BucketTreeStorageType_Indirect ] = " Indirect " ,
[ BucketTreeStorageType_AesCtrEx ] = " AesCtrEx " ,
[ BucketTreeStorageType_Compressed ] = " Compressed " ,
[ BucketTreeStorageType_Sparse ] = " Sparse "
} ;
2022-07-12 04:27:30 +01:00
# endif
2022-07-02 11:09:49 +01:00
2020-04-29 13:59:28 +01:00
/* Function prototypes. */
2022-07-12 04:27:30 +01:00
# if LOG_LEVEL <= LOG_LEVEL_ERROR
2022-07-02 11:09:49 +01:00
static const char * bktrGetStorageTypeName ( u8 storage_type ) ;
2022-07-12 04:27:30 +01:00
# endif
2022-07-02 11:09:49 +01:00
static bool bktrInitializeIndirectStorageContext ( BucketTreeContext * out , NcaFsSectionContext * nca_fs_ctx , bool is_sparse ) ;
2022-07-02 23:37:13 +01:00
static bool bktrReadIndirectStorage ( BucketTreeVisitor * visitor , void * out , u64 read_size , u64 offset ) ;
2022-07-02 11:09:49 +01:00
static bool bktrInitializeAesCtrExStorageContext ( BucketTreeContext * out , NcaFsSectionContext * nca_fs_ctx ) ;
2022-07-02 23:37:13 +01:00
static bool bktrReadAesCtrExStorage ( BucketTreeVisitor * visitor , void * out , u64 read_size , u64 offset ) ;
static bool bktrReadCompressedStorage ( BucketTreeVisitor * visitor , void * out , u64 read_size , u64 offset ) ;
2022-07-02 11:09:49 +01:00
2022-07-03 11:05:30 +01:00
static bool bktrReadSubStorage ( BucketTreeSubStorage * substorage , BucketTreeSubStorageReadParams * params ) ;
2022-07-03 15:29:22 +01:00
NX_INLINE void bktrBucketInitializeSubStorageReadParams ( BucketTreeSubStorageReadParams * out , void * buffer , u64 offset , u64 size , u64 virtual_offset , u32 ctr_val , bool aes_ctr_ex_crypt , u8 parent_storage_type ) ;
2022-07-03 11:05:30 +01:00
2022-07-02 23:37:13 +01:00
static bool bktrVerifyBucketInfo ( NcaBucketInfo * bucket , u64 node_size , u64 entry_size , u64 * out_node_storage_size , u64 * out_entry_storage_size ) ;
2022-07-02 11:09:49 +01:00
static bool bktrValidateTableOffsetNode ( const BucketTreeTable * table , u64 node_size , u64 entry_size , u32 entry_count , u64 * out_start_offset , u64 * out_end_offset ) ;
NX_INLINE bool bktrVerifyNodeHeader ( const BucketTreeNodeHeader * node_header , u32 node_index , u64 node_size , u64 entry_size ) ;
static u64 bktrQueryNodeStorageSize ( u64 node_size , u64 entry_size , u32 entry_count ) ;
static u64 bktrQueryEntryStorageSize ( u64 node_size , u64 entry_size , u32 entry_count ) ;
NX_INLINE u32 bktrGetEntryCount ( u64 node_size , u64 entry_size ) ;
NX_INLINE u32 bktrGetOffsetCount ( u64 node_size ) ;
NX_INLINE u32 bktrGetEntrySetCount ( u64 node_size , u64 entry_size , u32 entry_count ) ;
NX_INLINE u32 bktrGetNodeL2Count ( u64 node_size , u64 entry_size , u32 entry_count ) ;
NX_INLINE const void * bktrGetNodeArray ( const BucketTreeNodeHeader * node_header ) ;
NX_INLINE const u64 * bktrGetOffsetNodeArray ( const BucketTreeOffsetNode * offset_node ) ;
NX_INLINE const u64 * bktrGetOffsetNodeBegin ( const BucketTreeOffsetNode * offset_node ) ;
NX_INLINE const u64 * bktrGetOffsetNodeEnd ( const BucketTreeOffsetNode * offset_node ) ;
2022-07-02 15:07:08 +01:00
static bool bktrFindStorageEntry ( BucketTreeContext * ctx , u64 virtual_offset , BucketTreeVisitor * out_visitor ) ;
2022-07-02 23:37:13 +01:00
static bool bktrGetTreeNodeEntryIndex ( const u64 * start_ptr , const u64 * end_ptr , u64 virtual_offset , u32 * out_index ) ;
static bool bktrGetEntryNodeEntryIndex ( const BucketTreeNodeHeader * node_header , u64 entry_size , u64 virtual_offset , u32 * out_index ) ;
2022-06-29 13:41:58 +01:00
2022-07-03 19:47:00 +01:00
static bool bktrFindEntrySet ( BucketTreeContext * ctx , u32 * out_index , u64 virtual_offset , u32 node_index ) ;
2022-07-02 11:09:49 +01:00
static const BucketTreeNodeHeader * bktrGetTreeNodeHeader ( BucketTreeContext * ctx , u32 node_index ) ;
NX_INLINE u32 bktrGetEntrySetIndex ( BucketTreeContext * ctx , u32 node_index , u32 offset_index ) ;
2020-04-29 13:59:28 +01:00
2022-07-02 15:07:08 +01:00
static bool bktrFindEntry ( BucketTreeContext * ctx , BucketTreeVisitor * out_visitor , u64 virtual_offset , u32 entry_set_index ) ;
2022-07-02 11:09:49 +01:00
static const BucketTreeNodeHeader * bktrGetEntryNodeHeader ( BucketTreeContext * ctx , u32 entry_set_index ) ;
2022-07-02 23:37:13 +01:00
2022-07-02 11:09:49 +01:00
NX_INLINE u64 bktrGetEntryNodeEntryOffset ( u64 entry_set_offset , u64 entry_size , u32 entry_index ) ;
2022-07-02 23:37:13 +01:00
NX_INLINE u64 bktrGetEntryNodeEntryOffsetByIndex ( u32 entry_set_index , u64 node_size , u64 entry_size , u32 entry_index ) ;
2020-04-29 13:59:28 +01:00
2022-07-02 11:09:49 +01:00
NX_INLINE bool bktrIsExistL2 ( BucketTreeContext * ctx ) ;
NX_INLINE bool bktrIsExistOffsetL2OnL1 ( BucketTreeContext * ctx ) ;
2020-04-29 13:59:28 +01:00
2022-07-02 23:37:13 +01:00
static void bktrInitializeStorageNode ( BucketTreeStorageNode * out , u64 entry_size , u32 entry_count ) ;
2022-07-02 11:09:49 +01:00
static void bktrStorageNodeFind ( BucketTreeStorageNode * storage_node , const BucketTreeNodeHeader * node_header , u64 virtual_offset ) ;
NX_INLINE BucketTreeStorageNodeOffset bktrStorageNodeOffsetAdd ( BucketTreeStorageNodeOffset * ofs , u64 value ) ;
2022-07-05 00:25:28 +01:00
NX_INLINE const u64 bktrStorageNodeOffsetGetEntryVirtualOffset ( const BucketTreeNodeHeader * node_header , const BucketTreeStorageNodeOffset * ofs ) ;
2022-07-02 11:09:49 +01:00
2022-07-02 23:37:13 +01:00
NX_INLINE bool bktrVisitorIsValid ( BucketTreeVisitor * visitor ) ;
NX_INLINE bool bktrVisitorCanMoveNext ( BucketTreeVisitor * visitor ) ;
NX_INLINE bool bktrVisitorCanMovePrevious ( BucketTreeVisitor * visitor ) ;
static bool bktrVisitorMoveNext ( BucketTreeVisitor * visitor ) ;
static bool bktrVisitorMovePrevious ( BucketTreeVisitor * visitor ) ;
2022-07-02 11:09:49 +01:00
bool bktrInitializeContext ( BucketTreeContext * out , NcaFsSectionContext * nca_fs_ctx , u8 storage_type )
2020-04-29 13:59:28 +01:00
{
2022-07-02 11:09:49 +01:00
NcaContext * nca_ctx = NULL ;
2022-07-05 02:04:28 +01:00
2022-07-09 13:56:44 +01:00
if ( ! out | | ! nca_fs_ctx | | ! nca_fs_ctx - > enabled | | nca_fs_ctx - > section_type > = NcaFsSectionType_Invalid | | ! ( nca_ctx = ( NcaContext * ) nca_fs_ctx - > nca_ctx ) | | \
( nca_ctx - > rights_id_available & & ! nca_ctx - > titlekey_retrieved ) | | storage_type = = BucketTreeStorageType_Compressed | | storage_type > = BucketTreeStorageType_Count )
2020-04-29 13:59:28 +01:00
{
2022-07-12 17:34:49 +01:00
LOG_MSG_ERROR ( " Invalid parameters! " ) ;
2020-04-29 13:59:28 +01:00
return false ;
}
2022-07-05 02:04:28 +01:00
2022-07-02 11:09:49 +01:00
bool success = false ;
2022-07-05 02:04:28 +01:00
2020-10-14 14:23:49 +01:00
/* Free output context beforehand. */
bktrFreeContext ( out ) ;
2022-07-05 02:04:28 +01:00
2022-07-02 11:09:49 +01:00
/* Initialize the desired storage type. */
switch ( storage_type )
2020-04-29 13:59:28 +01:00
{
2022-07-02 11:09:49 +01:00
case BucketTreeStorageType_Indirect :
case BucketTreeStorageType_Sparse :
success = bktrInitializeIndirectStorageContext ( out , nca_fs_ctx , storage_type = = BucketTreeStorageType_Sparse ) ;
break ;
case BucketTreeStorageType_AesCtrEx :
success = bktrInitializeAesCtrExStorageContext ( out , nca_fs_ctx ) ;
break ;
default :
break ;
2020-04-29 13:59:28 +01:00
}
2022-07-05 02:04:28 +01:00
2022-07-12 04:27:30 +01:00
if ( ! success ) LOG_MSG_ERROR ( " Failed to initialize Bucket Tree %s storage for FS section #%u in \" %s \" . " , bktrGetStorageTypeName ( storage_type ) , nca_fs_ctx - > section_idx , \
nca_ctx - > content_id_str ) ;
2022-07-05 02:04:28 +01:00
2022-07-02 11:09:49 +01:00
return success ;
}
2022-07-09 13:56:44 +01:00
bool bktrInitializeCompressedStorageContext ( BucketTreeContext * out , BucketTreeSubStorage * substorage )
{
NcaFsSectionContext * nca_fs_ctx = NULL ;
NcaContext * nca_ctx = NULL ;
if ( ! out | | ! substorage | | substorage - > index ! = 0 | | ! ( nca_fs_ctx = substorage - > nca_fs_ctx ) | | ! nca_fs_ctx - > enabled | | ! nca_fs_ctx - > has_compression_layer | | \
nca_fs_ctx - > section_type > = NcaFsSectionType_Invalid | | ! ( nca_ctx = ( NcaContext * ) nca_fs_ctx - > nca_ctx ) | | ( nca_ctx - > rights_id_available & & ! nca_ctx - > titlekey_retrieved ) | | \
substorage - > type = = BucketTreeSubStorageType_AesCtrEx | | substorage - > type = = BucketTreeSubStorageType_Compressed | | substorage - > type > = BucketTreeSubStorageType_Count | | \
( substorage - > type = = BucketTreeSubStorageType_Regular & & substorage - > bktr_ctx ) | | ( substorage - > type ! = BucketTreeSubStorageType_Regular & & ! substorage - > bktr_ctx ) )
{
2022-07-12 17:34:49 +01:00
LOG_MSG_ERROR ( " Invalid parameters! " ) ;
2022-07-09 13:56:44 +01:00
return false ;
}
/* Free output context beforehand. */
bktrFreeContext ( out ) ;
NcaBucketInfo * compressed_bucket = & ( nca_fs_ctx - > header . compression_info . bucket ) ;
BucketTreeTable * compressed_table = NULL ;
u64 node_storage_size = 0 , entry_storage_size = 0 ;
BucketTreeSubStorageReadParams params = { 0 } ;
bool success = false ;
/* Verify bucket info. */
if ( ! bktrVerifyBucketInfo ( compressed_bucket , BKTR_NODE_SIZE , BKTR_COMPRESSED_ENTRY_SIZE , & node_storage_size , & entry_storage_size ) )
{
2022-07-12 17:34:49 +01:00
LOG_MSG_ERROR ( " Compressed Storage BucketInfo verification failed! " ) ;
2022-07-09 13:56:44 +01:00
goto end ;
}
/* Allocate memory for the full Compressed table. */
compressed_table = calloc ( 1 , compressed_bucket - > size ) ;
if ( ! compressed_table )
{
2022-07-12 17:34:49 +01:00
LOG_MSG_ERROR ( " Unable to allocate memory for the Compressed Storage Table! " ) ;
2022-07-09 13:56:44 +01:00
goto end ;
}
/* Read Compressed storage table data. */
const u64 compression_table_offset = ( nca_fs_ctx - > hash_region . size + compressed_bucket - > offset ) ;
bktrBucketInitializeSubStorageReadParams ( & params , compressed_table , compression_table_offset , compressed_bucket - > size , 0 , 0 , false , BucketTreeSubStorageType_Compressed ) ;
if ( ! bktrReadSubStorage ( substorage , & params ) )
{
2022-07-12 17:34:49 +01:00
LOG_MSG_ERROR ( " Failed to read Compressed Storage Table data! " ) ;
2022-07-09 13:56:44 +01:00
goto end ;
}
/* Validate table offset node. */
u64 start_offset = 0 , end_offset = 0 ;
if ( ! bktrValidateTableOffsetNode ( compressed_table , BKTR_NODE_SIZE , BKTR_COMPRESSED_ENTRY_SIZE , compressed_bucket - > header . entry_count , & start_offset , & end_offset ) )
{
2022-07-12 17:34:49 +01:00
LOG_MSG_ERROR ( " Compressed Storage Table Offset Node validation failed! " ) ;
2022-07-09 13:56:44 +01:00
goto end ;
}
/* Update output context. */
out - > nca_fs_ctx = nca_fs_ctx ;
out - > storage_type = BucketTreeStorageType_Compressed ;
out - > storage_table = compressed_table ;
out - > node_size = BKTR_NODE_SIZE ;
out - > entry_size = BKTR_COMPRESSED_ENTRY_SIZE ;
out - > offset_count = bktrGetOffsetCount ( BKTR_NODE_SIZE ) ;
out - > entry_set_count = bktrGetEntrySetCount ( BKTR_NODE_SIZE , BKTR_COMPRESSED_ENTRY_SIZE , compressed_bucket - > header . entry_count ) ;
out - > node_storage_size = node_storage_size ;
out - > entry_storage_size = entry_storage_size ;
out - > start_offset = start_offset ;
out - > end_offset = end_offset ;
memcpy ( & ( out - > substorages [ 0 ] ) , substorage , sizeof ( BucketTreeSubStorage ) ) ;
/* Update return value. */
success = true ;
end :
if ( ! success & & compressed_table ) free ( compressed_table ) ;
return success ;
}
2022-07-03 09:35:25 +01:00
bool bktrSetRegularSubStorage ( BucketTreeContext * ctx , NcaFsSectionContext * nca_fs_ctx )
{
NcaContext * nca_ctx = NULL ;
2022-07-05 02:04:28 +01:00
2022-07-03 09:35:25 +01:00
if ( ! bktrIsValidContext ( ctx ) | | ! nca_fs_ctx | | ! nca_fs_ctx - > enabled | | nca_fs_ctx - > section_type > = NcaFsSectionType_Invalid | | \
2022-07-03 11:05:30 +01:00
! ( nca_ctx = ( NcaContext * ) nca_fs_ctx - > nca_ctx ) | | ( nca_ctx - > rights_id_available & & ! nca_ctx - > titlekey_retrieved ) | | \
2022-07-09 13:56:44 +01:00
ctx - > storage_type = = BucketTreeStorageType_Compressed | | ctx - > storage_type > = BucketTreeStorageType_Count | | \
( ctx - > storage_type = = BucketTreeStorageType_Indirect & & ctx - > nca_fs_ctx = = nca_fs_ctx ) | | \
( ( ctx - > storage_type = = BucketTreeStorageType_AesCtrEx | | ctx - > storage_type = = BucketTreeStorageType_Sparse ) & & ctx - > nca_fs_ctx ! = nca_fs_ctx ) )
2022-07-03 09:35:25 +01:00
{
2022-07-12 17:34:49 +01:00
LOG_MSG_ERROR ( " Invalid parameters! " ) ;
2022-07-03 09:35:25 +01:00
return false ;
}
2022-07-05 02:04:28 +01:00
2022-07-03 09:35:25 +01:00
/* Update the substorage. */
BucketTreeSubStorage * substorage = & ( ctx - > substorages [ 0 ] ) ;
memset ( substorage , 0 , sizeof ( BucketTreeSubStorage ) ) ;
2022-07-05 02:04:28 +01:00
2022-07-03 09:35:25 +01:00
substorage - > index = 0 ;
substorage - > nca_fs_ctx = nca_fs_ctx ;
substorage - > type = BucketTreeSubStorageType_Regular ;
substorage - > bktr_ctx = NULL ;
2022-07-05 02:04:28 +01:00
2022-07-03 09:35:25 +01:00
return true ;
}
bool bktrSetBucketTreeSubStorage ( BucketTreeContext * parent_ctx , BucketTreeContext * child_ctx , u8 substorage_index )
{
if ( ! bktrIsValidContext ( parent_ctx ) | | ! bktrIsValidContext ( child_ctx ) | | substorage_index > = BKTR_MAX_SUBSTORAGE_COUNT | | \
2022-07-09 13:56:44 +01:00
parent_ctx - > storage_type ! = BucketTreeStorageType_Indirect | | child_ctx - > storage_type < BucketTreeStorageType_AesCtrEx | | \
2022-07-03 23:14:03 +01:00
child_ctx - > storage_type > BucketTreeStorageType_Sparse | | ( child_ctx - > storage_type = = BucketTreeStorageType_AesCtrEx & & ( substorage_index ! = 1 | | \
parent_ctx - > nca_fs_ctx ! = child_ctx - > nca_fs_ctx ) ) | | ( ( child_ctx - > storage_type = = BucketTreeStorageType_Compressed | | \
2022-07-09 13:56:44 +01:00
child_ctx - > storage_type = = BucketTreeStorageType_Sparse ) & & ( substorage_index ! = 0 | | parent_ctx - > nca_fs_ctx = = child_ctx - > nca_fs_ctx ) ) )
2022-07-03 09:35:25 +01:00
{
2022-07-12 17:34:49 +01:00
LOG_MSG_ERROR ( " Invalid parameters! " ) ;
2022-07-03 09:35:25 +01:00
return false ;
}
2022-07-05 02:04:28 +01:00
2022-07-03 09:35:25 +01:00
/* Update the substorage. */
BucketTreeSubStorage * substorage = & ( parent_ctx - > substorages [ substorage_index ] ) ;
memset ( substorage , 0 , sizeof ( BucketTreeSubStorage ) ) ;
2022-07-05 02:04:28 +01:00
2022-07-03 09:35:25 +01:00
substorage - > index = substorage_index ;
substorage - > nca_fs_ctx = child_ctx - > nca_fs_ctx ;
substorage - > type = ( child_ctx - > storage_type + 1 ) ; /* Convert to BucketTreeSubStorageType value. */
substorage - > bktr_ctx = child_ctx ;
2022-07-05 02:04:28 +01:00
2022-07-03 09:35:25 +01:00
return true ;
}
2022-07-02 11:09:49 +01:00
bool bktrReadStorage ( BucketTreeContext * ctx , void * out , u64 read_size , u64 offset )
{
if ( ! bktrIsBlockWithinStorageRange ( ctx , read_size , offset ) | | ! out )
2022-06-29 07:55:35 +01:00
{
2022-07-12 17:34:49 +01:00
LOG_MSG_ERROR ( " Invalid parameters! " ) ;
2022-07-02 11:09:49 +01:00
return false ;
2022-06-29 07:55:35 +01:00
}
2022-07-05 02:04:28 +01:00
2022-07-02 15:07:08 +01:00
BucketTreeVisitor visitor = { 0 } ;
2022-07-02 11:09:49 +01:00
bool success = false ;
2022-07-05 02:04:28 +01:00
2022-07-02 11:09:49 +01:00
/* Find storage entry. */
2022-07-02 15:07:08 +01:00
if ( ! bktrFindStorageEntry ( ctx , offset , & visitor ) )
2020-04-29 13:59:28 +01:00
{
2022-07-12 04:27:30 +01:00
LOG_MSG_ERROR ( " Unable to find %s storage entry for offset 0x%lX! " , bktrGetStorageTypeName ( ctx - > storage_type ) , offset ) ;
2020-07-13 07:36:17 +01:00
goto end ;
2020-04-29 13:59:28 +01:00
}
2022-07-05 02:04:28 +01:00
2022-07-02 11:09:49 +01:00
/* Process storage entry according to the storage type. */
switch ( ctx - > storage_type )
2020-04-30 09:25:03 +01:00
{
2022-07-02 11:09:49 +01:00
case BucketTreeStorageType_Indirect :
case BucketTreeStorageType_Sparse :
2022-07-02 23:37:13 +01:00
success = bktrReadIndirectStorage ( & visitor , out , read_size , offset ) ;
2022-07-02 11:09:49 +01:00
break ;
case BucketTreeStorageType_AesCtrEx :
2022-07-02 23:37:13 +01:00
success = bktrReadAesCtrExStorage ( & visitor , out , read_size , offset ) ;
2022-07-02 11:09:49 +01:00
break ;
case BucketTreeStorageType_Compressed :
2022-07-02 23:37:13 +01:00
success = bktrReadCompressedStorage ( & visitor , out , read_size , offset ) ;
2022-07-02 11:09:49 +01:00
break ;
default :
break ;
2020-04-30 09:25:03 +01:00
}
2022-07-05 02:04:28 +01:00
2022-07-12 04:27:30 +01:00
if ( ! success ) LOG_MSG_ERROR ( " Failed to read 0x%lX-byte long block at offset 0x%lX from %s storage! " , read_size , offset , bktrGetStorageTypeName ( ctx - > storage_type ) ) ;
2022-07-05 02:04:28 +01:00
2022-07-02 11:09:49 +01:00
end :
return success ;
}
2022-07-07 01:30:45 +01:00
bool bktrIsBlockWithinIndirectStorageRange ( BucketTreeContext * ctx , u64 offset , u64 size , bool * out )
{
2022-07-10 05:41:18 +01:00
if ( ! bktrIsBlockWithinStorageRange ( ctx , size , offset ) | | ( ctx - > storage_type ! = BucketTreeStorageType_Indirect & & ctx - > storage_type ! = BucketTreeStorageType_Compressed ) | | \
( ctx - > storage_type = = BucketTreeStorageType_Compressed & & ctx - > substorages [ 0 ] . type ! = BucketTreeSubStorageType_Indirect ) | | ! out )
2022-07-07 01:30:45 +01:00
{
2022-07-12 17:34:49 +01:00
LOG_MSG_ERROR ( " Invalid parameters! " ) ;
2022-07-07 01:30:45 +01:00
return false ;
}
BucketTreeVisitor visitor = { 0 } ;
bool updated = false , success = false ;
/* Find storage entry. */
if ( ! bktrFindStorageEntry ( ctx , offset , & visitor ) )
{
2022-07-12 04:27:30 +01:00
LOG_MSG_ERROR ( " Unable to find %s storage entry for offset 0x%lX! " , bktrGetStorageTypeName ( ctx - > storage_type ) , offset ) ;
2022-07-07 01:30:45 +01:00
goto end ;
}
2022-07-10 05:41:18 +01:00
/* Check if we're dealing with a Compressed storage. */
if ( ctx - > storage_type = = BucketTreeStorageType_Compressed )
{
BucketTreeContext * indirect_storage = ( BucketTreeContext * ) ctx - > substorages [ 0 ] . bktr_ctx ;
const u64 compressed_storage_base_offset = ctx - > nca_fs_ctx - > hash_region . size ;
BucketTreeCompressedStorageEntry * start_entry = NULL , * end_entry = NULL ;
/* Validate start entry node. */
start_entry = end_entry = ( BucketTreeCompressedStorageEntry * ) visitor . entry ;
if ( ! bktrIsOffsetWithinStorageRange ( ctx , ( u64 ) start_entry - > virtual_offset ) | | ( u64 ) start_entry - > virtual_offset > offset )
{
2022-07-12 17:34:49 +01:00
LOG_MSG_ERROR ( " Invalid Compressed Storage entry! (0x%lX) (#1). " , start_entry - > virtual_offset ) ;
2022-07-10 05:41:18 +01:00
goto end ;
}
/* Loop until we reach the upper bound of the requested block or find a match. */
do {
u64 cur_entry_offset = 0 ;
/* Check if we can move any further. */
if ( bktrVisitorCanMoveNext ( & visitor ) )
{
BucketTreeCompressedStorageEntry * tmp = end_entry ;
/* Retrieve next entry node. */
if ( ! bktrVisitorMoveNext ( & visitor ) )
{
2022-07-12 17:34:49 +01:00
LOG_MSG_ERROR ( " Failed to retrieve next Compressed Storage entry! " ) ;
2022-07-10 05:41:18 +01:00
goto end ;
}
/* Validate next entry node. */
end_entry = ( BucketTreeCompressedStorageEntry * ) visitor . entry ;
if ( ! bktrIsOffsetWithinStorageRange ( ctx , ( u64 ) end_entry - > virtual_offset ) | | ( u64 ) end_entry - > virtual_offset < = ( u64 ) tmp - > virtual_offset )
{
2022-07-12 17:34:49 +01:00
LOG_MSG_ERROR ( " Invalid Indirect Storage entry! (0x%lX) (#2). " , ( u64 ) end_entry - > virtual_offset ) ;
2022-07-10 05:41:18 +01:00
goto end ;
}
/* Update current entry offset. */
cur_entry_offset = ( u64 ) end_entry - > virtual_offset ;
/* Update start entry node. */
start_entry = tmp ;
} else {
/* Update current entry offset. */
cur_entry_offset = ctx - > end_offset ;
/* Update entry nodes. */
start_entry = end_entry ;
end_entry = NULL ;
}
/* Calculate indirect block extents. */
u64 indirect_block_offset = compressed_storage_base_offset ;
u64 indirect_block_size = ( cur_entry_offset - ( u64 ) start_entry - > virtual_offset ) ;
if ( ( u64 ) start_entry - > virtual_offset < = offset )
{
indirect_block_offset + = ( ( offset - ( u64 ) start_entry - > virtual_offset ) + ( u64 ) start_entry - > physical_offset ) ;
indirect_block_size - = ( offset - ( u64 ) start_entry - > virtual_offset ) ;
} else {
indirect_block_offset + = ( u64 ) start_entry - > physical_offset ;
}
if ( ( offset + size ) < = cur_entry_offset )
{
indirect_block_size - = ( cur_entry_offset - ( offset + size ) ) ;
end_entry = NULL ; /* Don't proceed any further, we have found our upper bound. */
}
/* Check if the current Compressed Storage entry node points to one or more Indirect Storage entry nodes with Patch storage index. */
if ( ! bktrIsBlockWithinIndirectStorageRange ( indirect_storage , indirect_block_offset , indirect_block_size , & updated ) )
{
2022-07-12 17:34:49 +01:00
LOG_MSG_ERROR ( " Failed to determine if 0x%lX-byte long Compressed storage block at offset 0x%lX is within Indirect Storage! " , indirect_block_offset , indirect_block_size ) ;
2022-07-10 05:41:18 +01:00
goto end ;
}
} while ( ! updated & & end_entry & & ( u64 ) end_entry - > virtual_offset < ( offset + size ) ) ;
/* Update output values. */
* out = updated ;
success = true ;
goto end ;
}
/* Check the Indirect Storage. */
BucketTreeIndirectStorageEntry * start_entry = NULL , * end_entry = NULL ;
2022-07-07 01:30:45 +01:00
/* Validate start entry node. */
start_entry = end_entry = ( BucketTreeIndirectStorageEntry * ) visitor . entry ;
if ( ! bktrIsOffsetWithinStorageRange ( ctx , start_entry - > virtual_offset ) | | start_entry - > virtual_offset > offset )
{
2022-07-12 17:34:49 +01:00
LOG_MSG_ERROR ( " Invalid Indirect Storage entry! (0x%lX) (#1). " , start_entry - > virtual_offset ) ;
2022-07-07 01:30:45 +01:00
goto end ;
}
2022-07-07 12:07:28 +01:00
/* Loop through adjacent Indirect Storage entry nodes and check if at least one of them uses the Patch storage index. */
do {
/* Break out of the loop immediately if the current entry node's storage index matches Patch. */
if ( end_entry - > storage_index = = BucketTreeIndirectStorageIndex_Patch )
{
updated = true ;
break ;
}
/* Don't proceed if we can't move any further. */
if ( ! bktrVisitorCanMoveNext ( & visitor ) ) break ;
/* Retrieve the next entry node. */
2022-07-07 01:30:45 +01:00
if ( ! bktrVisitorMoveNext ( & visitor ) )
{
2022-07-12 17:34:49 +01:00
LOG_MSG_ERROR ( " Failed to retrieve next Indirect Storage entry! " ) ;
2022-07-07 01:30:45 +01:00
goto end ;
}
/* Validate current entry node. */
end_entry = ( BucketTreeIndirectStorageEntry * ) visitor . entry ;
2022-07-07 12:07:28 +01:00
if ( ! bktrIsOffsetWithinStorageRange ( ctx , end_entry - > virtual_offset ) | | end_entry - > virtual_offset < = start_entry - > virtual_offset )
2022-07-07 01:30:45 +01:00
{
2022-07-12 17:34:49 +01:00
LOG_MSG_ERROR ( " Invalid Indirect Storage entry! (0x%lX) (#2). " , end_entry - > virtual_offset ) ;
2022-07-07 01:30:45 +01:00
goto end ;
}
2022-07-07 12:07:28 +01:00
} while ( end_entry - > virtual_offset < ( offset + size ) ) ;
2022-07-07 01:30:45 +01:00
/* Update output values. */
* out = updated ;
success = true ;
end :
return success ;
}
2022-07-12 04:27:30 +01:00
# if LOG_LEVEL <= LOG_LEVEL_ERROR
2022-07-02 11:09:49 +01:00
static const char * bktrGetStorageTypeName ( u8 storage_type )
{
return ( storage_type < BucketTreeStorageType_Count ? g_bktrStorageTypeNames [ storage_type ] : NULL ) ;
}
2022-07-12 04:27:30 +01:00
# endif
2022-07-02 11:09:49 +01:00
static bool bktrInitializeIndirectStorageContext ( BucketTreeContext * out , NcaFsSectionContext * nca_fs_ctx , bool is_sparse )
2020-04-30 09:25:03 +01:00
{
2022-07-02 11:09:49 +01:00
if ( ( ! is_sparse & & nca_fs_ctx - > section_type ! = NcaFsSectionType_PatchRomFs ) | | ( is_sparse & & ! nca_fs_ctx - > has_sparse_layer ) )
2020-04-30 09:25:03 +01:00
{
2022-07-12 17:34:49 +01:00
LOG_MSG_ERROR ( " Invalid parameters! " ) ;
2020-04-30 09:25:03 +01:00
return false ;
}
2022-07-05 02:04:28 +01:00
2022-07-02 11:09:49 +01:00
NcaContext * nca_ctx = ( NcaContext * ) nca_fs_ctx - > nca_ctx ;
NcaBucketInfo * indirect_bucket = ( is_sparse ? & ( nca_fs_ctx - > header . sparse_info . bucket ) : & ( nca_fs_ctx - > header . patch_info . indirect_bucket ) ) ;
BucketTreeTable * indirect_table = NULL ;
2022-07-02 23:37:13 +01:00
u64 node_storage_size = 0 , entry_storage_size = 0 ;
2022-07-02 11:09:49 +01:00
bool success = false ;
2022-07-05 02:04:28 +01:00
2022-07-02 11:09:49 +01:00
/* Verify bucket info. */
2022-07-02 23:37:13 +01:00
if ( ! bktrVerifyBucketInfo ( indirect_bucket , BKTR_NODE_SIZE , BKTR_INDIRECT_ENTRY_SIZE , & node_storage_size , & entry_storage_size ) )
2020-04-30 09:25:03 +01:00
{
2022-07-12 17:34:49 +01:00
LOG_MSG_ERROR ( " Indirect Storage BucketInfo verification failed! (%s). " , is_sparse ? " sparse " : " patch " ) ;
2022-07-02 11:09:49 +01:00
goto end ;
2020-04-30 09:25:03 +01:00
}
2022-07-05 02:04:28 +01:00
2022-07-02 11:09:49 +01:00
/* Allocate memory for the full indirect table. */
indirect_table = calloc ( 1 , indirect_bucket - > size ) ;
if ( ! indirect_table )
2020-04-30 09:25:03 +01:00
{
2022-07-12 17:34:49 +01:00
LOG_MSG_ERROR ( " Unable to allocate memory for the Indirect Storage Table! (%s). " , is_sparse ? " sparse " : " patch " ) ;
2022-07-02 11:09:49 +01:00
goto end ;
2020-04-30 09:25:03 +01:00
}
2022-07-05 02:04:28 +01:00
2022-07-02 11:09:49 +01:00
/* Read indirect storage table data. */
if ( ( ! is_sparse & & ! ncaReadFsSection ( nca_fs_ctx , indirect_table , indirect_bucket - > size , indirect_bucket - > offset ) ) | | \
( is_sparse & & ! ncaReadContentFile ( ( NcaContext * ) nca_fs_ctx - > nca_ctx , indirect_table , indirect_bucket - > size , nca_fs_ctx - > sparse_table_offset ) ) )
2020-04-30 09:25:03 +01:00
{
2022-07-12 17:34:49 +01:00
LOG_MSG_ERROR ( " Failed to read Indirect Storage Table data! (%s). " , is_sparse ? " sparse " : " patch " ) ;
2022-07-02 11:09:49 +01:00
goto end ;
2020-04-30 09:25:03 +01:00
}
2022-07-05 02:04:28 +01:00
2022-07-02 11:09:49 +01:00
/* Decrypt indirect storage table, if needed. */
if ( is_sparse )
2020-05-01 05:34:30 +01:00
{
2022-07-02 11:09:49 +01:00
NcaAesCtrUpperIv sparse_upper_iv = { 0 } ;
u8 sparse_ctr [ AES_BLOCK_SIZE ] = { 0 } ;
const u8 * sparse_ctr_key = NULL ;
Aes128CtrContext sparse_ctr_ctx = { 0 } ;
2022-07-05 02:04:28 +01:00
2022-07-02 11:09:49 +01:00
/* Generate upper CTR IV. */
memcpy ( sparse_upper_iv . value , nca_fs_ctx - > header . aes_ctr_upper_iv . value , sizeof ( sparse_upper_iv . value ) ) ;
sparse_upper_iv . generation = ( ( u32 ) ( nca_fs_ctx - > header . sparse_info . generation ) < < 16 ) ;
2022-07-05 02:04:28 +01:00
2022-07-02 11:09:49 +01:00
/* Initialize partial AES CTR. */
aes128CtrInitializePartialCtr ( sparse_ctr , sparse_upper_iv . value , nca_fs_ctx - > sparse_table_offset ) ;
2022-07-05 02:04:28 +01:00
2022-07-02 11:09:49 +01:00
/* Create AES CTR context. */
sparse_ctr_key = ( nca_ctx - > rights_id_available ? nca_ctx - > titlekey : nca_ctx - > decrypted_key_area . aes_ctr ) ;
aes128CtrContextCreate ( & sparse_ctr_ctx , sparse_ctr_key , sparse_ctr ) ;
2022-07-05 02:04:28 +01:00
2022-07-02 11:09:49 +01:00
/* Decrypt indirect storage table in-place. */
aes128CtrCrypt ( & sparse_ctr_ctx , indirect_table , indirect_table , indirect_bucket - > size ) ;
2020-05-01 05:34:30 +01:00
}
2022-07-05 02:04:28 +01:00
2022-07-02 11:09:49 +01:00
/* Validate table offset node. */
u64 start_offset = 0 , end_offset = 0 ;
if ( ! bktrValidateTableOffsetNode ( indirect_table , BKTR_NODE_SIZE , BKTR_INDIRECT_ENTRY_SIZE , indirect_bucket - > header . entry_count , & start_offset , & end_offset ) )
2020-05-01 05:34:30 +01:00
{
2022-07-12 17:34:49 +01:00
LOG_MSG_ERROR ( " Indirect Storage Table Offset Node validation failed! (%s). " , is_sparse ? " sparse " : " patch " ) ;
2022-07-02 11:09:49 +01:00
goto end ;
2020-05-01 05:34:30 +01:00
}
2022-07-05 02:04:28 +01:00
2022-07-02 11:09:49 +01:00
/* Update output context. */
out - > nca_fs_ctx = nca_fs_ctx ;
out - > storage_type = ( is_sparse ? BucketTreeStorageType_Sparse : BucketTreeStorageType_Indirect ) ;
out - > storage_table = indirect_table ;
out - > node_size = BKTR_NODE_SIZE ;
out - > entry_size = BKTR_INDIRECT_ENTRY_SIZE ;
out - > offset_count = bktrGetOffsetCount ( BKTR_NODE_SIZE ) ;
out - > entry_set_count = bktrGetEntrySetCount ( BKTR_NODE_SIZE , BKTR_INDIRECT_ENTRY_SIZE , indirect_bucket - > header . entry_count ) ;
2022-07-02 23:37:13 +01:00
out - > node_storage_size = node_storage_size ;
out - > entry_storage_size = entry_storage_size ;
2022-07-02 11:09:49 +01:00
out - > start_offset = start_offset ;
out - > end_offset = end_offset ;
2022-07-05 02:04:28 +01:00
2022-07-02 11:09:49 +01:00
/* Update return value. */
success = true ;
2022-07-05 02:04:28 +01:00
2022-07-02 11:09:49 +01:00
end :
if ( ! success & & indirect_table ) free ( indirect_table ) ;
2022-07-05 02:04:28 +01:00
2022-07-02 11:09:49 +01:00
return success ;
2020-05-01 05:34:30 +01:00
}
2022-07-03 11:05:30 +01:00
static bool bktrReadIndirectStorage ( BucketTreeVisitor * visitor , void * out , u64 read_size , u64 offset )
{
BucketTreeContext * ctx = visitor - > bktr_ctx ;
bool is_sparse = ( ctx - > storage_type = = BucketTreeStorageType_Sparse ) ;
2022-07-05 05:01:07 +01:00
bool missing_original_storage = ! bktrIsValidSubstorage ( & ( ctx - > substorages [ 0 ] ) ) ;
2022-07-05 02:04:28 +01:00
2022-07-05 05:01:07 +01:00
if ( ! out | | ( is_sparse & & ( missing_original_storage | | ctx - > substorages [ 0 ] . type ! = BucketTreeSubStorageType_Regular ) ) | | \
( ! is_sparse & & ( ! bktrIsValidSubstorage ( & ( ctx - > substorages [ 1 ] ) ) | | ctx - > substorages [ 1 ] . type ! = BucketTreeSubStorageType_AesCtrEx | | \
2022-07-09 13:56:44 +01:00
( ! missing_original_storage & & ( ctx - > substorages [ 0 ] . type = = BucketTreeSubStorageType_Indirect | | ctx - > substorages [ 0 ] . type = = BucketTreeSubStorageType_AesCtrEx | | \
ctx - > substorages [ 0 ] . type > = BucketTreeSubStorageType_Count ) ) ) ) | | ( offset + read_size ) > ctx - > end_offset )
2022-07-03 11:05:30 +01:00
{
2022-07-12 17:34:49 +01:00
LOG_MSG_ERROR ( " Invalid parameters! " ) ;
2022-07-03 11:05:30 +01:00
return false ;
}
2022-07-05 02:04:28 +01:00
2022-07-03 11:05:30 +01:00
/* Validate Indirect Storage entry. */
BucketTreeIndirectStorageEntry cur_entry = { 0 } ;
memcpy ( & cur_entry , visitor - > entry , sizeof ( BucketTreeIndirectStorageEntry ) ) ;
2022-07-05 02:04:28 +01:00
2022-07-03 11:05:30 +01:00
if ( ! bktrIsOffsetWithinStorageRange ( ctx , cur_entry . virtual_offset ) | | cur_entry . virtual_offset > offset | | cur_entry . storage_index > BucketTreeIndirectStorageIndex_Patch )
{
2022-07-12 17:34:49 +01:00
LOG_MSG_ERROR ( " Invalid Indirect Storage entry! (0x%lX) (#1). " , cur_entry . virtual_offset ) ;
2022-07-03 11:05:30 +01:00
return false ;
}
2022-07-05 02:04:28 +01:00
2022-07-03 11:05:30 +01:00
u64 cur_entry_offset = cur_entry . virtual_offset , next_entry_offset = 0 ;
bool moved = false , success = false ;
2022-07-05 02:04:28 +01:00
2022-07-03 11:05:30 +01:00
/* Check if we can retrieve the next entry. */
if ( bktrVisitorCanMoveNext ( visitor ) )
{
/* Retrieve the next entry. */
if ( ! bktrVisitorMoveNext ( visitor ) )
{
2022-07-12 17:34:49 +01:00
LOG_MSG_ERROR ( " Failed to retrieve next Indirect Storage entry! " ) ;
2022-07-03 11:05:30 +01:00
goto end ;
}
2022-07-05 02:04:28 +01:00
2022-07-03 11:05:30 +01:00
/* Validate Indirect Storage entry. */
BucketTreeIndirectStorageEntry * next_entry = ( BucketTreeIndirectStorageEntry * ) visitor - > entry ;
if ( ! bktrIsOffsetWithinStorageRange ( ctx , next_entry - > virtual_offset ) | | next_entry - > storage_index > BucketTreeIndirectStorageIndex_Patch )
{
2022-07-12 17:34:49 +01:00
LOG_MSG_ERROR ( " Invalid Indirect Storage entry! (0x%lX) (#2). " , next_entry - > virtual_offset ) ;
2022-07-03 11:05:30 +01:00
goto end ;
}
2022-07-05 02:04:28 +01:00
2022-07-03 11:05:30 +01:00
/* Store next entry's virtual offset. */
next_entry_offset = next_entry - > virtual_offset ;
2022-07-05 02:04:28 +01:00
2022-07-03 11:05:30 +01:00
/* Update variable. */
moved = true ;
} else {
/* Set the next entry offset to the storage's end. */
next_entry_offset = ctx - > end_offset ;
}
2022-07-05 02:04:28 +01:00
2022-07-03 11:05:30 +01:00
/* Verify next entry offset. */
if ( next_entry_offset < = cur_entry_offset | | offset > = next_entry_offset )
{
2022-07-12 17:34:49 +01:00
LOG_MSG_ERROR ( " Invalid virtual offset for the Indirect Storage's next entry! (0x%lX). " , next_entry_offset ) ;
2022-07-03 11:05:30 +01:00
goto end ;
}
2022-07-05 02:04:28 +01:00
2022-07-03 11:05:30 +01:00
/* Perform read operation. */
if ( ( offset + read_size ) < = next_entry_offset )
{
/* Read only within the current indirect storage entry. */
2022-07-03 15:29:22 +01:00
BucketTreeSubStorageReadParams params = { 0 } ;
const u64 data_offset = ( offset - cur_entry_offset + cur_entry . physical_offset ) ;
bktrBucketInitializeSubStorageReadParams ( & params , out , data_offset , read_size , offset , 0 , false , ctx - > storage_type ) ;
2022-07-05 02:04:28 +01:00
2022-07-03 11:05:30 +01:00
if ( cur_entry . storage_index = = BucketTreeIndirectStorageIndex_Original )
{
2022-07-05 05:01:07 +01:00
if ( ! missing_original_storage )
{
/* Retrieve data from the original data storage. */
/* This may either be a Regular/Sparse/Compressed storage from the base NCA (Indirect) or a Regular storage from this very same NCA (Sparse). */
success = bktrReadSubStorage ( & ( ctx - > substorages [ 0 ] ) , & params ) ;
2022-07-12 17:34:49 +01:00
if ( ! success ) LOG_MSG_ERROR ( " Failed to read 0x%lX-byte long chunk from offset 0x%lX in original data storage! " , read_size , data_offset ) ;
2022-07-05 05:01:07 +01:00
} else {
2022-07-12 17:34:49 +01:00
LOG_MSG_ERROR ( " Error: attempting to read 0x%lX-byte long chunk from missing original data storage at offset 0x%lX! " , read_size , data_offset ) ;
2022-07-05 05:01:07 +01:00
}
2022-07-03 11:05:30 +01:00
} else {
if ( ! is_sparse )
{
/* Retrieve data from the indirect data storage. */
/* This must always be the AesCtrEx storage within this very same NCA (Indirect). */
success = bktrReadSubStorage ( & ( ctx - > substorages [ 1 ] ) , & params ) ;
2022-07-12 17:34:49 +01:00
if ( ! success ) LOG_MSG_ERROR ( " Failed to read 0x%lX-byte long chunk from offset 0x%lX in AesCtrEx storage! " , read_size , data_offset ) ;
2022-07-03 11:05:30 +01:00
} else {
/* Fill output buffer with zeroes (SparseStorage's ZeroStorage). */
2022-07-03 19:32:35 +01:00
memset ( out , 0 , read_size ) ;
2022-07-03 11:05:30 +01:00
success = true ;
}
}
} else {
/* Handle reads that span multiple indirect storage entries. */
if ( moved ) bktrVisitorMovePrevious ( visitor ) ;
2022-07-05 02:04:28 +01:00
2022-07-03 11:05:30 +01:00
const u64 indirect_block_size = ( next_entry_offset - offset ) ;
2022-07-05 02:04:28 +01:00
2022-07-03 11:05:30 +01:00
success = ( bktrReadIndirectStorage ( visitor , out , indirect_block_size , offset ) & & \
bktrReadIndirectStorage ( visitor , ( u8 * ) out + indirect_block_size , read_size - indirect_block_size , offset + indirect_block_size ) ) ;
2022-07-05 02:04:28 +01:00
2022-07-12 17:34:49 +01:00
if ( ! success ) LOG_MSG_ERROR ( " Failed to read 0x%lX bytes block from multiple Indirect Storage entries at offset 0x%lX! " , read_size , offset ) ;
2022-07-03 11:05:30 +01:00
}
2022-07-05 02:04:28 +01:00
2022-07-03 11:05:30 +01:00
end :
return success ;
}
2022-07-02 11:09:49 +01:00
static bool bktrInitializeAesCtrExStorageContext ( BucketTreeContext * out , NcaFsSectionContext * nca_fs_ctx )
2022-06-29 13:41:58 +01:00
{
2022-07-02 11:09:49 +01:00
if ( nca_fs_ctx - > section_type ! = NcaFsSectionType_PatchRomFs | | ! nca_fs_ctx - > header . patch_info . aes_ctr_ex_bucket . size )
2022-06-29 13:41:58 +01:00
{
2022-07-12 17:34:49 +01:00
LOG_MSG_ERROR ( " Invalid parameters! " ) ;
2022-06-29 13:41:58 +01:00
return false ;
}
2022-07-05 02:04:28 +01:00
2022-07-02 11:09:49 +01:00
NcaBucketInfo * aes_ctr_ex_bucket = & ( nca_fs_ctx - > header . patch_info . aes_ctr_ex_bucket ) ;
BucketTreeTable * aes_ctr_ex_table = NULL ;
2022-07-02 23:37:13 +01:00
u64 node_storage_size = 0 , entry_storage_size = 0 ;
2022-06-29 13:41:58 +01:00
bool success = false ;
2022-07-05 02:04:28 +01:00
2022-07-02 11:09:49 +01:00
/* Verify bucket info. */
2022-07-02 23:37:13 +01:00
if ( ! bktrVerifyBucketInfo ( aes_ctr_ex_bucket , BKTR_NODE_SIZE , BKTR_AES_CTR_EX_ENTRY_SIZE , & node_storage_size , & entry_storage_size ) )
2022-06-29 13:41:58 +01:00
{
2022-07-12 17:34:49 +01:00
LOG_MSG_ERROR ( " AesCtrEx Storage BucketInfo verification failed! " ) ;
2022-06-29 13:41:58 +01:00
goto end ;
}
2022-07-05 02:04:28 +01:00
2022-07-02 11:09:49 +01:00
/* Allocate memory for the full AesCtrEx table. */
aes_ctr_ex_table = calloc ( 1 , aes_ctr_ex_bucket - > size ) ;
if ( ! aes_ctr_ex_table )
2022-06-29 13:41:58 +01:00
{
2022-07-12 17:34:49 +01:00
LOG_MSG_ERROR ( " Unable to allocate memory for the AesCtrEx Storage Table! " ) ;
2022-06-29 13:41:58 +01:00
goto end ;
}
2022-07-05 02:04:28 +01:00
2022-07-02 11:09:49 +01:00
/* Read AesCtrEx storage table data. */
if ( ! ncaReadFsSection ( nca_fs_ctx , aes_ctr_ex_table , aes_ctr_ex_bucket - > size , aes_ctr_ex_bucket - > offset ) )
2022-06-29 13:41:58 +01:00
{
2022-07-12 17:34:49 +01:00
LOG_MSG_ERROR ( " Failed to read AesCtrEx Storage Table data! " ) ;
2022-07-02 11:09:49 +01:00
goto end ;
2022-06-29 13:41:58 +01:00
}
2022-07-05 02:04:28 +01:00
2022-07-02 11:09:49 +01:00
/* Validate table offset node. */
u64 start_offset = 0 , end_offset = 0 ;
if ( ! bktrValidateTableOffsetNode ( aes_ctr_ex_table , BKTR_NODE_SIZE , BKTR_AES_CTR_EX_ENTRY_SIZE , aes_ctr_ex_bucket - > header . entry_count , & start_offset , & end_offset ) )
2022-06-29 13:41:58 +01:00
{
2022-07-12 17:34:49 +01:00
LOG_MSG_ERROR ( " AesCtrEx Storage Table Offset Node validation failed! " ) ;
2022-07-02 11:09:49 +01:00
goto end ;
2022-06-29 13:41:58 +01:00
}
2022-07-05 02:04:28 +01:00
2022-07-02 11:09:49 +01:00
/* Update output context. */
out - > nca_fs_ctx = nca_fs_ctx ;
out - > storage_type = BucketTreeStorageType_AesCtrEx ;
out - > storage_table = aes_ctr_ex_table ;
out - > node_size = BKTR_NODE_SIZE ;
out - > entry_size = BKTR_AES_CTR_EX_ENTRY_SIZE ;
out - > offset_count = bktrGetOffsetCount ( BKTR_NODE_SIZE ) ;
out - > entry_set_count = bktrGetEntrySetCount ( BKTR_NODE_SIZE , BKTR_AES_CTR_EX_ENTRY_SIZE , aes_ctr_ex_bucket - > header . entry_count ) ;
2022-07-02 23:37:13 +01:00
out - > node_storage_size = node_storage_size ;
out - > entry_storage_size = entry_storage_size ;
2022-07-02 11:09:49 +01:00
out - > start_offset = start_offset ;
out - > end_offset = end_offset ;
2022-07-05 02:04:28 +01:00
2022-07-02 11:09:49 +01:00
/* Update return value. */
2022-06-29 13:41:58 +01:00
success = true ;
2022-07-05 02:04:28 +01:00
2022-06-29 13:41:58 +01:00
end :
2022-07-02 11:09:49 +01:00
if ( ! success & & aes_ctr_ex_table ) free ( aes_ctr_ex_table ) ;
2022-07-05 02:04:28 +01:00
2022-06-29 13:41:58 +01:00
return success ;
}
2022-07-03 15:29:22 +01:00
static bool bktrReadAesCtrExStorage ( BucketTreeVisitor * visitor , void * out , u64 read_size , u64 offset )
{
BucketTreeContext * ctx = visitor - > bktr_ctx ;
2022-07-05 02:04:28 +01:00
2022-07-07 01:30:45 +01:00
if ( ! out | | ! bktrIsValidSubstorage ( & ( ctx - > substorages [ 0 ] ) ) | | ctx - > substorages [ 0 ] . type ! = BucketTreeSubStorageType_Regular | | ( offset + read_size ) > ctx - > end_offset )
2022-07-03 15:29:22 +01:00
{
2022-07-12 17:34:49 +01:00
LOG_MSG_ERROR ( " Invalid parameters! " ) ;
2022-07-03 15:29:22 +01:00
return false ;
}
2022-07-05 02:04:28 +01:00
2022-07-03 15:29:22 +01:00
/* Validate AesCtrEx Storage entry. */
BucketTreeAesCtrExStorageEntry cur_entry = { 0 } ;
memcpy ( & cur_entry , visitor - > entry , sizeof ( BucketTreeAesCtrExStorageEntry ) ) ;
2022-07-05 02:04:28 +01:00
2022-07-03 15:29:22 +01:00
if ( ! bktrIsOffsetWithinStorageRange ( ctx , cur_entry . offset ) | | cur_entry . offset > offset | | ! IS_ALIGNED ( cur_entry . offset , AES_BLOCK_SIZE ) )
{
2022-07-12 17:34:49 +01:00
LOG_MSG_ERROR ( " Invalid AesCtrEx Storage entry! (0x%lX) (#1). " , cur_entry . offset ) ;
2022-07-03 15:29:22 +01:00
return false ;
}
2022-07-05 02:04:28 +01:00
2022-07-03 15:29:22 +01:00
u64 cur_entry_offset = cur_entry . offset , next_entry_offset = 0 ;
bool moved = false , success = false ;
2022-07-05 02:04:28 +01:00
2022-07-03 15:29:22 +01:00
/* Check if we can retrieve the next entry. */
if ( bktrVisitorCanMoveNext ( visitor ) )
{
/* Retrieve the next entry. */
if ( ! bktrVisitorMoveNext ( visitor ) )
{
2022-07-12 17:34:49 +01:00
LOG_MSG_ERROR ( " Failed to retrieve next AesCtrEx Storage entry! " ) ;
2022-07-03 15:29:22 +01:00
goto end ;
}
2022-07-05 02:04:28 +01:00
2022-07-03 15:29:22 +01:00
/* Validate AesCtrEx Storage entry. */
BucketTreeAesCtrExStorageEntry * next_entry = ( BucketTreeAesCtrExStorageEntry * ) visitor - > entry ;
if ( ! bktrIsOffsetWithinStorageRange ( ctx , next_entry - > offset ) )
{
2022-07-12 17:34:49 +01:00
LOG_MSG_ERROR ( " Invalid AesCtrEx Storage entry! (0x%lX) (#2). " , next_entry - > offset ) ;
2022-07-03 15:29:22 +01:00
goto end ;
}
2022-07-05 02:04:28 +01:00
2022-07-03 15:29:22 +01:00
/* Store next entry's virtual offset. */
next_entry_offset = next_entry - > offset ;
2022-07-05 02:04:28 +01:00
2022-07-03 15:29:22 +01:00
/* Update variable. */
moved = true ;
} else {
/* Set the next entry offset to the storage's end. */
next_entry_offset = ctx - > end_offset ;
}
2022-07-05 02:04:28 +01:00
2022-07-03 15:29:22 +01:00
/* Verify next entry offset. */
if ( ! IS_ALIGNED ( next_entry_offset , AES_BLOCK_SIZE ) | | next_entry_offset < = cur_entry_offset | | offset > = next_entry_offset )
{
2022-07-12 17:34:49 +01:00
LOG_MSG_ERROR ( " Invalid offset for the AesCtrEx Storage's next entry! (0x%lX). " , next_entry_offset ) ;
2022-07-03 15:29:22 +01:00
goto end ;
}
2022-07-05 02:04:28 +01:00
2022-07-03 15:29:22 +01:00
/* Perform read operation. */
if ( ( offset + read_size ) < = next_entry_offset )
{
/* Read only within the current AesCtrEx storage entry. */
BucketTreeSubStorageReadParams params = { 0 } ;
bktrBucketInitializeSubStorageReadParams ( & params , out , offset , read_size , 0 , cur_entry . generation , cur_entry . encryption = = BucketTreeAesCtrExStorageEncryption_Enabled , ctx - > storage_type ) ;
2022-07-05 02:04:28 +01:00
2022-07-03 15:29:22 +01:00
success = bktrReadSubStorage ( & ( ctx - > substorages [ 0 ] ) , & params ) ;
2022-07-12 17:34:49 +01:00
if ( ! success ) LOG_MSG_ERROR ( " Failed to read 0x%lX-byte long chunk at offset 0x%lX from AesCtrEx storage! " , read_size , offset ) ;
2022-07-03 15:29:22 +01:00
} else {
/* Handle reads that span multiple AesCtrEx storage entries. */
if ( moved ) bktrVisitorMovePrevious ( visitor ) ;
2022-07-05 02:04:28 +01:00
2022-07-03 15:29:22 +01:00
const u64 aes_ctr_ex_block_size = ( next_entry_offset - offset ) ;
2022-07-05 02:04:28 +01:00
2022-07-03 15:29:22 +01:00
success = ( bktrReadAesCtrExStorage ( visitor , out , aes_ctr_ex_block_size , offset ) & & \
bktrReadAesCtrExStorage ( visitor , ( u8 * ) out + aes_ctr_ex_block_size , read_size - aes_ctr_ex_block_size , offset + aes_ctr_ex_block_size ) ) ;
2022-07-05 02:04:28 +01:00
2022-07-12 17:34:49 +01:00
if ( ! success ) LOG_MSG_ERROR ( " Failed to read 0x%lX bytes block from multiple AesCtrEx Storage entries at offset 0x%lX! " , read_size , offset ) ;
2022-07-03 15:29:22 +01:00
}
2022-07-05 02:04:28 +01:00
2022-07-03 15:29:22 +01:00
end :
return success ;
}
2022-07-03 19:32:35 +01:00
static bool bktrReadCompressedStorage ( BucketTreeVisitor * visitor , void * out , u64 read_size , u64 offset )
{
BucketTreeContext * ctx = visitor - > bktr_ctx ;
NcaFsSectionContext * nca_fs_ctx = ctx - > nca_fs_ctx ;
u64 compressed_storage_base_offset = nca_fs_ctx - > hash_region . size ;
2022-07-05 02:04:28 +01:00
2022-07-09 13:56:44 +01:00
if ( ! out | | ! bktrIsValidSubstorage ( & ( ctx - > substorages [ 0 ] ) ) | | ctx - > substorages [ 0 ] . type > = BucketTreeSubStorageType_AesCtrEx | | ( offset + read_size ) > ctx - > end_offset )
2022-07-03 19:32:35 +01:00
{
2022-07-12 17:34:49 +01:00
LOG_MSG_ERROR ( " Invalid parameters! " ) ;
2022-07-03 19:32:35 +01:00
return false ;
}
2022-07-05 02:04:28 +01:00
2022-07-03 19:32:35 +01:00
/* Validate Compressed Storage entry. */
BucketTreeCompressedStorageEntry cur_entry = { 0 } ;
memcpy ( & cur_entry , visitor - > entry , sizeof ( BucketTreeCompressedStorageEntry ) ) ;
2022-07-05 02:04:28 +01:00
2022-07-09 13:56:44 +01:00
if ( ! bktrIsOffsetWithinStorageRange ( ctx , ( u64 ) cur_entry . virtual_offset ) | | ( u64 ) cur_entry . virtual_offset > offset | | cur_entry . compression_type = = BucketTreeCompressedStorageCompressionType_2 | | \
2022-07-03 19:32:35 +01:00
cur_entry . compression_type > BucketTreeCompressedStorageCompressionType_LZ4 | | ( cur_entry . compression_type ! = BucketTreeCompressedStorageCompressionType_LZ4 & & \
cur_entry . compression_level ! = 0 ) | | ( cur_entry . compression_type = = BucketTreeCompressedStorageCompressionType_None & & cur_entry . physical_size ! = BKTR_COMPRESSION_INVALID_PHYS_SIZE ) | | \
( cur_entry . compression_type ! = BucketTreeCompressedStorageCompressionType_None & & cur_entry . physical_size = = BKTR_COMPRESSION_INVALID_PHYS_SIZE ) | | \
( cur_entry . compression_type = = BucketTreeCompressedStorageCompressionType_LZ4 & & ( cur_entry . compression_level < BKTR_COMPRESSION_LEVEL_MIN | | \
cur_entry . compression_level > BKTR_COMPRESSION_LEVEL_MAX | | ! IS_ALIGNED ( cur_entry . physical_offset , BKTR_COMPRESSION_PHYS_ALIGNMENT ) ) ) )
{
2022-07-12 17:34:49 +01:00
LOG_DATA_ERROR ( & cur_entry , sizeof ( BucketTreeCompressedStorageEntry ) , " Invalid Compressed Storage entry! (#1). Entry dump: " ) ;
2022-07-03 19:32:35 +01:00
return false ;
}
2022-07-05 02:04:28 +01:00
2022-07-09 13:56:44 +01:00
u64 cur_entry_offset = ( u64 ) cur_entry . virtual_offset , next_entry_offset = 0 ;
2022-07-03 19:32:35 +01:00
bool moved = false , success = false ;
2022-07-05 02:04:28 +01:00
2022-07-03 19:32:35 +01:00
/* Check if we can retrieve the next entry. */
if ( bktrVisitorCanMoveNext ( visitor ) )
{
/* Retrieve the next entry. */
if ( ! bktrVisitorMoveNext ( visitor ) )
{
2022-07-12 17:34:49 +01:00
LOG_MSG_ERROR ( " Failed to retrieve next Compressed Storage entry! " ) ;
2022-07-03 19:32:35 +01:00
goto end ;
}
2022-07-05 02:04:28 +01:00
2022-07-03 19:32:35 +01:00
/* Validate Compressed Storage entry. */
BucketTreeCompressedStorageEntry * next_entry = ( BucketTreeCompressedStorageEntry * ) visitor - > entry ;
2022-07-09 13:56:44 +01:00
if ( ! bktrIsOffsetWithinStorageRange ( ctx , ( u64 ) next_entry - > virtual_offset ) | | next_entry - > compression_type = = BucketTreeCompressedStorageCompressionType_2 | | \
2022-07-03 19:32:35 +01:00
next_entry - > compression_type > BucketTreeCompressedStorageCompressionType_LZ4 | | \
( next_entry - > compression_type ! = BucketTreeCompressedStorageCompressionType_LZ4 & & next_entry - > compression_level ! = 0 ) | | \
( next_entry - > compression_type = = BucketTreeCompressedStorageCompressionType_None & & next_entry - > physical_size ! = BKTR_COMPRESSION_INVALID_PHYS_SIZE ) | | \
( next_entry - > compression_type ! = BucketTreeCompressedStorageCompressionType_None & & next_entry - > physical_size = = BKTR_COMPRESSION_INVALID_PHYS_SIZE ) | | \
( next_entry - > compression_type = = BucketTreeCompressedStorageCompressionType_LZ4 & & ( next_entry - > compression_level < BKTR_COMPRESSION_LEVEL_MIN | | \
next_entry - > compression_level > BKTR_COMPRESSION_LEVEL_MAX | | ! IS_ALIGNED ( next_entry - > physical_offset , BKTR_COMPRESSION_PHYS_ALIGNMENT ) ) ) )
{
2022-07-12 17:34:49 +01:00
LOG_DATA_ERROR ( next_entry , sizeof ( BucketTreeCompressedStorageEntry ) , " Invalid Compressed Storage entry! (#2). Entry dump: " ) ;
2022-07-03 19:32:35 +01:00
goto end ;
}
2022-07-05 02:04:28 +01:00
2022-07-03 19:32:35 +01:00
/* Store next entry's virtual offset. */
2022-07-09 13:56:44 +01:00
next_entry_offset = ( u64 ) next_entry - > virtual_offset ;
2022-07-05 02:04:28 +01:00
2022-07-03 19:32:35 +01:00
/* Update variable. */
moved = true ;
} else {
/* Set the next entry offset to the storage's end. */
next_entry_offset = ctx - > end_offset ;
}
2022-07-05 02:04:28 +01:00
2022-07-03 19:32:35 +01:00
/* Verify next entry offset. */
if ( next_entry_offset < = cur_entry_offset | | offset > = next_entry_offset )
{
2022-07-12 17:34:49 +01:00
LOG_MSG_ERROR ( " Invalid virtual offset for the Compressed Storage's next entry! (0x%lX). " , next_entry_offset ) ;
2022-07-03 19:32:35 +01:00
goto end ;
}
2022-07-05 02:04:28 +01:00
2022-07-03 19:32:35 +01:00
/* Perform read operation. */
if ( ( offset + read_size ) < = next_entry_offset )
{
/* Read only within the current compressed storage entry. */
BucketTreeSubStorageReadParams params = { 0 } ;
2022-07-05 02:04:28 +01:00
2022-07-03 19:32:35 +01:00
switch ( cur_entry . compression_type )
{
case BucketTreeCompressedStorageCompressionType_None :
{
/* We can randomly access data that's not compressed. */
/* Let's just read what we need. */
2022-07-09 13:56:44 +01:00
const u64 data_offset = ( compressed_storage_base_offset + ( offset - cur_entry_offset + ( u64 ) cur_entry . physical_offset ) ) ;
2022-07-03 19:32:35 +01:00
bktrBucketInitializeSubStorageReadParams ( & params , out , data_offset , read_size , 0 , 0 , false , ctx - > storage_type ) ;
2022-07-05 02:04:28 +01:00
2022-07-03 19:32:35 +01:00
success = bktrReadSubStorage ( & ( ctx - > substorages [ 0 ] ) , & params ) ;
2022-07-12 17:34:49 +01:00
if ( ! success ) LOG_MSG_ERROR ( " Failed to read 0x%lX-byte long chunk from offset 0x%lX in non-compressed entry! " , read_size , data_offset ) ;
2022-07-05 02:04:28 +01:00
2022-07-03 19:32:35 +01:00
break ;
}
case BucketTreeCompressedStorageCompressionType_Zero :
{
/* Fill output buffer with zeroes. */
memset ( out , 0 , read_size ) ;
success = true ;
break ;
}
case BucketTreeCompressedStorageCompressionType_LZ4 :
{
/* We can't randomly access data that's compressed. */
/* Let's be lazy and allocate memory for the full entry, read it and then decompress it. */
2022-07-09 13:56:44 +01:00
const u64 data_offset = ( compressed_storage_base_offset + ( u64 ) cur_entry . physical_offset ) ;
const u64 compressed_data_size = ( u64 ) cur_entry . physical_size ;
2022-07-03 19:32:35 +01:00
const u64 decompressed_data_size = ( next_entry_offset - cur_entry_offset ) ;
const u64 buffer_size = LZ4_DECOMPRESS_INPLACE_BUFFER_SIZE ( decompressed_data_size ) ;
u8 * buffer = NULL , * read_ptr = NULL ;
2022-07-05 02:04:28 +01:00
2022-07-03 19:47:00 +01:00
buffer = calloc ( 1 , buffer_size ) ;
2022-07-03 19:32:35 +01:00
if ( ! buffer )
{
2022-07-12 17:34:49 +01:00
LOG_MSG_ERROR ( " Failed to allocate 0x%lX-byte long buffer for data decompression! (0x%lX). " , buffer_size , decompressed_data_size ) ;
2022-07-03 19:32:35 +01:00
break ;
}
2022-07-05 02:04:28 +01:00
2022-07-03 19:32:35 +01:00
/* Adjust read pointer. This will let us use the same buffer for storing read data and decompressing it. */
read_ptr = ( buffer + ( buffer_size - compressed_data_size ) ) ;
bktrBucketInitializeSubStorageReadParams ( & params , read_ptr , data_offset , compressed_data_size , 0 , 0 , false , ctx - > storage_type ) ;
2022-07-05 02:04:28 +01:00
2022-07-03 19:32:35 +01:00
/* Read compressed LZ4 block. */
if ( ! bktrReadSubStorage ( & ( ctx - > substorages [ 0 ] ) , & params ) )
{
2022-07-12 17:34:49 +01:00
LOG_MSG_ERROR ( " Failed to read 0x%lX-byte long compressed block from offset 0x%lX! " , compressed_data_size , data_offset ) ;
2022-07-03 19:32:35 +01:00
free ( buffer ) ;
break ;
}
2022-07-05 02:04:28 +01:00
2022-07-03 19:32:35 +01:00
/* Decompress LZ4 block. */
int lz4_res = 0 ;
if ( ( lz4_res = LZ4_decompress_safe ( ( char * ) read_ptr , ( char * ) buffer , ( int ) compressed_data_size , ( int ) buffer_size ) ) ! = ( int ) decompressed_data_size )
{
2022-07-12 17:34:49 +01:00
LOG_MSG_ERROR ( " Failed to decompress 0x%lX-byte long compressed block! (%d). " , compressed_data_size , lz4_res ) ;
2022-07-03 19:32:35 +01:00
free ( buffer ) ;
break ;
}
2022-07-05 02:04:28 +01:00
2022-07-03 19:32:35 +01:00
/* Copy the data we need. */
memcpy ( out , buffer + ( offset - cur_entry_offset ) , read_size ) ;
2022-07-05 02:04:28 +01:00
2022-07-03 19:32:35 +01:00
/* Free allocated buffer and update return value. */
free ( buffer ) ;
success = true ;
2022-07-05 02:04:28 +01:00
2022-07-03 19:32:35 +01:00
break ;
}
default :
break ;
}
} else {
/* Handle reads that span multiple compressed storage entries. */
if ( moved ) bktrVisitorMovePrevious ( visitor ) ;
2022-07-05 02:04:28 +01:00
2022-07-03 19:32:35 +01:00
const u64 compressed_block_size = ( next_entry_offset - offset ) ;
2022-07-05 02:04:28 +01:00
2022-07-03 19:32:35 +01:00
success = ( bktrReadCompressedStorage ( visitor , out , compressed_block_size , offset ) & & \
bktrReadCompressedStorage ( visitor , ( u8 * ) out + compressed_block_size , read_size - compressed_block_size , offset + compressed_block_size ) ) ;
2022-07-05 02:04:28 +01:00
2022-07-12 17:34:49 +01:00
if ( ! success ) LOG_MSG_ERROR ( " Failed to read 0x%lX bytes block from multiple Compressed Storage entries at offset 0x%lX! " , read_size , offset ) ;
2022-07-03 19:32:35 +01:00
}
2022-07-05 02:04:28 +01:00
2022-07-03 19:32:35 +01:00
end :
return success ;
}
2022-07-03 11:05:30 +01:00
static bool bktrReadSubStorage ( BucketTreeSubStorage * substorage , BucketTreeSubStorageReadParams * params )
{
if ( ! bktrIsValidSubstorage ( substorage ) | | ! params | | ! params - > buffer | | ! params - > size )
{
2022-07-12 17:34:49 +01:00
LOG_MSG_ERROR ( " Invalid parameters! " ) ;
2022-07-03 11:05:30 +01:00
return false ;
}
2022-07-05 02:04:28 +01:00
2022-07-03 11:05:30 +01:00
bool success = false ;
2022-07-05 02:04:28 +01:00
2022-07-03 11:05:30 +01:00
if ( substorage - > type = = BucketTreeSubStorageType_Regular )
{
2022-07-04 00:36:01 +01:00
NcaFsSectionContext * nca_fs_ctx = substorage - > nca_fs_ctx ;
2022-07-05 02:04:28 +01:00
2022-07-03 11:05:30 +01:00
if ( params - > parent_storage_type = = BucketTreeStorageType_AesCtrEx )
{
/* Perform a read on the target NCA using AesCtrEx crypto. */
2022-07-09 13:56:44 +01:00
success = ncaReadAesCtrExStorage ( nca_fs_ctx , params - > buffer , params - > size , params - > offset , params - > ctr_val , params - > aes_ctr_ex_crypt ) ;
2022-07-03 11:05:30 +01:00
} else {
/* Make sure to handle Sparse virtual offsets if we need to. */
2022-07-03 11:27:26 +01:00
if ( params - > parent_storage_type = = BucketTreeStorageType_Sparse & & params - > virtual_offset ) nca_fs_ctx - > cur_sparse_virtual_offset = params - > virtual_offset ;
2022-07-05 02:04:28 +01:00
2022-07-03 11:05:30 +01:00
/* Perform a read on the target NCA. */
success = ncaReadFsSection ( nca_fs_ctx , params - > buffer , params - > size , params - > offset ) ;
}
} else {
/* Perform a read on the target BucketTree storage. */
2022-07-04 00:36:01 +01:00
BucketTreeContext * ctx = ( BucketTreeContext * ) substorage - > bktr_ctx ;
2022-07-03 11:05:30 +01:00
success = bktrReadStorage ( ctx , params - > buffer , params - > size , params - > offset ) ;
}
2022-07-05 02:04:28 +01:00
2022-07-12 17:34:49 +01:00
if ( ! success ) LOG_MSG_ERROR ( " Failed to read 0x%lX-byte long chunk from offset 0x%lX! " , params - > size , params - > offset ) ;
2022-07-05 02:04:28 +01:00
2022-07-03 11:05:30 +01:00
return success ;
}
2022-07-03 15:29:22 +01:00
NX_INLINE void bktrBucketInitializeSubStorageReadParams ( BucketTreeSubStorageReadParams * out , void * buffer , u64 offset , u64 size , u64 virtual_offset , u32 ctr_val , bool aes_ctr_ex_crypt , u8 parent_storage_type )
2022-07-03 11:05:30 +01:00
{
out - > buffer = buffer ;
out - > offset = offset ;
out - > size = size ;
out - > virtual_offset = ( ( virtual_offset & & parent_storage_type = = BucketTreeStorageType_Sparse ) ? virtual_offset : 0 ) ;
out - > ctr_val = ( ( ctr_val & & parent_storage_type = = BucketTreeStorageType_AesCtrEx ) ? ctr_val : 0 ) ;
2022-07-03 19:47:00 +01:00
out - > aes_ctr_ex_crypt = ( ( aes_ctr_ex_crypt & & parent_storage_type = = BucketTreeStorageType_AesCtrEx ) ? true : false ) ;
2022-07-03 11:05:30 +01:00
out - > parent_storage_type = parent_storage_type ;
}
2022-07-02 23:37:13 +01:00
static bool bktrVerifyBucketInfo ( NcaBucketInfo * bucket , u64 node_size , u64 entry_size , u64 * out_node_storage_size , u64 * out_entry_storage_size )
2022-07-02 11:09:49 +01:00
{
/* Verify bucket info properties. */
if ( ! ncaVerifyBucketInfo ( bucket ) ) return false ;
2022-07-05 02:04:28 +01:00
2022-07-02 11:09:49 +01:00
/* Validate table size. */
u64 node_storage_size = bktrQueryNodeStorageSize ( node_size , entry_size , bucket - > header . entry_count ) ;
u64 entry_storage_size = bktrQueryEntryStorageSize ( node_size , entry_size , bucket - > header . entry_count ) ;
u64 calc_table_size = ( node_storage_size + entry_storage_size ) ;
2022-07-05 02:04:28 +01:00
2022-07-02 23:37:13 +01:00
bool success = ( bucket - > size > = calc_table_size ) ;
if ( success )
{
if ( out_node_storage_size ) * out_node_storage_size = node_storage_size ;
if ( out_entry_storage_size ) * out_entry_storage_size = entry_storage_size ;
2022-07-09 13:56:44 +01:00
} else {
2022-07-12 17:34:49 +01:00
LOG_MSG_ERROR ( " Calculated table size exceeds the provided bucket's table size! (0x%lX > 0x%lX). " , calc_table_size , bucket - > size ) ;
2022-07-02 23:37:13 +01:00
}
2022-07-05 02:04:28 +01:00
2022-07-02 23:37:13 +01:00
return success ;
2022-07-02 11:09:49 +01:00
}
static bool bktrValidateTableOffsetNode ( const BucketTreeTable * table , u64 node_size , u64 entry_size , u32 entry_count , u64 * out_start_offset , u64 * out_end_offset )
2020-04-30 09:25:03 +01:00
{
2022-07-02 11:09:49 +01:00
const BucketTreeOffsetNode * offset_node = & ( table - > offset_node ) ;
const BucketTreeNodeHeader * node_header = & ( offset_node - > header ) ;
2022-07-05 02:04:28 +01:00
2022-07-02 11:09:49 +01:00
/* Verify offset node header. */
if ( ! bktrVerifyNodeHeader ( node_header , 0 , node_size , sizeof ( u64 ) ) )
2020-04-30 09:25:03 +01:00
{
2022-07-12 17:34:49 +01:00
LOG_MSG_ERROR ( " Bucket Tree Offset Node header verification failed! " ) ;
2020-04-30 09:25:03 +01:00
return false ;
}
2022-07-05 02:04:28 +01:00
2022-07-02 11:09:49 +01:00
/* Validate offsets. */
u32 offset_count = bktrGetOffsetCount ( node_size ) ;
u32 entry_set_count = bktrGetEntrySetCount ( node_size , entry_size , entry_count ) ;
2022-07-05 02:04:28 +01:00
2022-07-02 11:09:49 +01:00
const u64 start_offset = ( ( offset_count < entry_set_count & & node_header - > count < offset_count ) ? * bktrGetOffsetNodeEnd ( offset_node ) : * bktrGetOffsetNodeBegin ( offset_node ) ) ;
u64 end_offset = node_header - > offset ;
2022-07-05 02:04:28 +01:00
2022-07-02 11:09:49 +01:00
if ( start_offset > * bktrGetOffsetNodeBegin ( offset_node ) | | start_offset > = end_offset | | node_header - > count ! = entry_set_count )
{
2022-07-12 17:34:49 +01:00
LOG_MSG_ERROR ( " Invalid Bucket Tree Offset Node! " ) ;
2022-07-02 11:09:49 +01:00
return false ;
}
2022-07-05 02:04:28 +01:00
2022-07-02 11:09:49 +01:00
/* Update output offsets. */
if ( out_start_offset ) * out_start_offset = start_offset ;
if ( out_end_offset ) * out_end_offset = end_offset ;
2022-07-05 02:04:28 +01:00
2022-07-02 11:09:49 +01:00
return true ;
}
NX_INLINE bool bktrVerifyNodeHeader ( const BucketTreeNodeHeader * node_header , u32 node_index , u64 node_size , u64 entry_size )
{
return ( node_header & & node_header - > index = = node_index & & entry_size > 0 & & node_size > = ( entry_size + BKTR_NODE_HEADER_SIZE ) & & \
node_header - > count > 0 & & node_header - > count < = ( ( node_size - BKTR_NODE_HEADER_SIZE ) / entry_size ) ) ;
}
static u64 bktrQueryNodeStorageSize ( u64 node_size , u64 entry_size , u32 entry_count )
{
if ( entry_size < sizeof ( u64 ) | | node_size < ( entry_size + BKTR_NODE_HEADER_SIZE ) | | node_size < BKTR_NODE_SIZE_MIN | | node_size > BKTR_NODE_SIZE_MAX | | \
! IS_POWER_OF_TWO ( node_size ) | | ! entry_count ) return 0 ;
2022-07-05 02:04:28 +01:00
2022-07-02 11:09:49 +01:00
return ( ( 1 + bktrGetNodeL2Count ( node_size , entry_size , entry_count ) ) * node_size ) ;
}
static u64 bktrQueryEntryStorageSize ( u64 node_size , u64 entry_size , u32 entry_count )
{
if ( entry_size < sizeof ( u64 ) | | node_size < ( entry_size + BKTR_NODE_HEADER_SIZE ) | | node_size < BKTR_NODE_SIZE_MIN | | node_size > BKTR_NODE_SIZE_MAX | | \
! IS_POWER_OF_TWO ( node_size ) | | ! entry_count ) return 0 ;
2022-07-05 02:04:28 +01:00
2022-07-02 11:09:49 +01:00
return ( ( u64 ) bktrGetEntrySetCount ( node_size , entry_size , entry_count ) * node_size ) ;
}
NX_INLINE u32 bktrGetEntryCount ( u64 node_size , u64 entry_size )
{
return ( u32 ) ( ( node_size - BKTR_NODE_HEADER_SIZE ) / entry_size ) ;
}
NX_INLINE u32 bktrGetOffsetCount ( u64 node_size )
{
return ( u32 ) ( ( node_size - BKTR_NODE_HEADER_SIZE ) / sizeof ( u64 ) ) ;
}
NX_INLINE u32 bktrGetEntrySetCount ( u64 node_size , u64 entry_size , u32 entry_count )
{
u32 entry_count_per_node = bktrGetEntryCount ( node_size , entry_size ) ;
return DIVIDE_UP ( entry_count , entry_count_per_node ) ;
}
NX_INLINE u32 bktrGetNodeL2Count ( u64 node_size , u64 entry_size , u32 entry_count )
{
u32 offset_count_per_node = bktrGetOffsetCount ( node_size ) ;
2022-07-03 19:47:00 +01:00
u32 entry_set_count = bktrGetEntrySetCount ( node_size , entry_size , entry_count ) ;
2022-07-05 02:04:28 +01:00
2022-07-02 11:09:49 +01:00
if ( entry_set_count < = offset_count_per_node ) return 0 ;
2022-07-05 02:04:28 +01:00
2022-07-02 11:09:49 +01:00
u32 node_l2_count = DIVIDE_UP ( entry_set_count , offset_count_per_node ) ;
if ( node_l2_count > offset_count_per_node ) return 0 ;
2022-07-05 02:04:28 +01:00
2022-07-02 11:09:49 +01:00
return DIVIDE_UP ( entry_set_count - ( offset_count_per_node - ( node_l2_count - 1 ) ) , offset_count_per_node ) ;
}
NX_INLINE const void * bktrGetNodeArray ( const BucketTreeNodeHeader * node_header )
{
return ( ( const u8 * ) node_header + BKTR_NODE_HEADER_SIZE ) ;
}
NX_INLINE const u64 * bktrGetOffsetNodeArray ( const BucketTreeOffsetNode * offset_node )
{
return ( const u64 * ) bktrGetNodeArray ( & ( offset_node - > header ) ) ;
}
NX_INLINE const u64 * bktrGetOffsetNodeBegin ( const BucketTreeOffsetNode * offset_node )
{
return bktrGetOffsetNodeArray ( offset_node ) ;
}
NX_INLINE const u64 * bktrGetOffsetNodeEnd ( const BucketTreeOffsetNode * offset_node )
{
return ( bktrGetOffsetNodeArray ( offset_node ) + offset_node - > header . count ) ;
}
2022-07-02 15:07:08 +01:00
static bool bktrFindStorageEntry ( BucketTreeContext * ctx , u64 virtual_offset , BucketTreeVisitor * out_visitor )
2022-07-02 11:09:49 +01:00
{
2022-07-02 15:07:08 +01:00
if ( ! ctx | | virtual_offset > = ctx - > storage_table - > offset_node . header . offset | | ! out_visitor )
2020-04-30 09:25:03 +01:00
{
2022-07-12 17:34:49 +01:00
LOG_MSG_ERROR ( " Invalid parameters! " ) ;
2020-04-30 09:25:03 +01:00
return false ;
}
2022-07-05 02:04:28 +01:00
2022-07-02 11:09:49 +01:00
/* Get the node. */
const BucketTreeOffsetNode * offset_node = & ( ctx - > storage_table - > offset_node ) ;
2022-07-05 02:04:28 +01:00
2022-07-02 11:09:49 +01:00
/* Get the entry node index. */
u32 entry_set_index = 0 ;
2022-07-03 19:47:00 +01:00
const u64 * start_ptr = NULL , * end_ptr = NULL ;
2020-04-30 09:25:03 +01:00
bool success = false ;
2022-07-05 02:04:28 +01:00
2022-07-02 11:09:49 +01:00
if ( bktrIsExistOffsetL2OnL1 ( ctx ) & & virtual_offset < * bktrGetOffsetNodeBegin ( offset_node ) )
2020-04-30 09:25:03 +01:00
{
2022-07-02 23:37:13 +01:00
start_ptr = bktrGetOffsetNodeEnd ( offset_node ) ;
end_ptr = ( bktrGetOffsetNodeBegin ( offset_node ) + ctx - > offset_count ) ;
2022-07-05 02:04:28 +01:00
2022-07-02 23:37:13 +01:00
if ( ! bktrGetTreeNodeEntryIndex ( start_ptr , end_ptr , virtual_offset , & entry_set_index ) )
2022-07-02 11:09:49 +01:00
{
2022-07-12 17:34:49 +01:00
LOG_MSG_ERROR ( " Failed to retrieve Bucket Tree Node entry index for virtual offset 0x%lX! (#1). " , virtual_offset ) ;
2022-07-02 11:09:49 +01:00
goto end ;
}
} else {
2022-07-02 23:37:13 +01:00
start_ptr = bktrGetOffsetNodeBegin ( offset_node ) ;
end_ptr = bktrGetOffsetNodeEnd ( offset_node ) ;
2022-07-05 02:04:28 +01:00
2022-07-02 23:37:13 +01:00
if ( ! bktrGetTreeNodeEntryIndex ( start_ptr , end_ptr , virtual_offset , & entry_set_index ) )
2020-04-30 09:25:03 +01:00
{
2022-07-12 17:34:49 +01:00
LOG_MSG_ERROR ( " Failed to retrieve Bucket Tree Node entry index for virtual offset 0x%lX! (#2). " , virtual_offset ) ;
2022-07-02 11:09:49 +01:00
goto end ;
}
2022-07-05 02:04:28 +01:00
2022-07-02 11:09:49 +01:00
if ( bktrIsExistL2 ( ctx ) )
2020-09-26 11:49:18 +01:00
{
2022-07-02 11:09:49 +01:00
u32 node_index = entry_set_index ;
2022-07-03 19:47:00 +01:00
if ( node_index > = ctx - > offset_count | | ! bktrFindEntrySet ( ctx , & entry_set_index , virtual_offset , node_index ) )
2022-06-29 13:41:58 +01:00
{
2022-07-12 17:34:49 +01:00
LOG_MSG_ERROR ( " Invalid L2 Bucket Tree Node index! " ) ;
2022-07-02 11:09:49 +01:00
goto end ;
2022-06-29 13:41:58 +01:00
}
2020-04-30 09:25:03 +01:00
}
}
2022-07-05 02:04:28 +01:00
2022-07-02 11:09:49 +01:00
/* Validate the entry set index. */
if ( entry_set_index > = ctx - > entry_set_count )
{
2022-07-12 17:34:49 +01:00
LOG_MSG_ERROR ( " Invalid Bucket Tree Node offset! " ) ;
2022-07-02 11:09:49 +01:00
goto end ;
}
2022-07-05 02:04:28 +01:00
2022-07-02 11:09:49 +01:00
/* Find the entry. */
2022-07-02 15:07:08 +01:00
success = bktrFindEntry ( ctx , out_visitor , virtual_offset , entry_set_index ) ;
2022-07-12 17:34:49 +01:00
if ( ! success ) LOG_MSG_ERROR ( " Failed to retrieve storage entry! " ) ;
2022-07-05 02:04:28 +01:00
2022-07-02 11:09:49 +01:00
end :
2020-04-30 09:25:03 +01:00
return success ;
}
2022-07-02 23:37:13 +01:00
static bool bktrGetTreeNodeEntryIndex ( const u64 * start_ptr , const u64 * end_ptr , u64 virtual_offset , u32 * out_index )
2020-04-30 09:25:03 +01:00
{
2022-07-02 23:37:13 +01:00
if ( ! start_ptr | | ! end_ptr | | start_ptr > = end_ptr | | ! out_index )
2020-04-30 09:25:03 +01:00
{
2022-07-12 17:34:49 +01:00
LOG_MSG_ERROR ( " Invalid parameters! " ) ;
2020-04-30 09:25:03 +01:00
return false ;
}
2022-07-05 02:04:28 +01:00
2022-07-02 23:37:13 +01:00
u64 * pos = ( u64 * ) start_ptr ;
2022-07-05 00:25:28 +01:00
u32 index = 0 ;
2022-07-05 02:04:28 +01:00
2022-07-02 23:37:13 +01:00
while ( pos < end_ptr )
2020-04-30 09:25:03 +01:00
{
2022-07-05 00:25:28 +01:00
if ( start_ptr < pos )
2022-07-02 11:09:49 +01:00
{
2022-07-05 00:25:28 +01:00
/* Stop looking if we have found the right offset node. */
if ( * pos > virtual_offset ) break ;
2022-07-05 02:04:28 +01:00
2022-07-05 00:25:28 +01:00
/* Increment index. */
index + + ;
2022-07-02 11:09:49 +01:00
}
2022-07-05 02:04:28 +01:00
2022-07-05 00:25:28 +01:00
/* Increment offset node pointer. */
2022-07-02 11:09:49 +01:00
pos + + ;
2020-04-30 09:25:03 +01:00
}
2022-07-05 02:04:28 +01:00
2022-07-05 00:25:28 +01:00
/* Update output index. */
* out_index = index ;
2022-07-05 02:04:28 +01:00
2022-07-05 00:25:28 +01:00
return true ;
2020-04-30 09:25:03 +01:00
}
2020-04-29 13:59:28 +01:00
2022-07-02 23:37:13 +01:00
static bool bktrGetEntryNodeEntryIndex ( const BucketTreeNodeHeader * node_header , u64 entry_size , u64 virtual_offset , u32 * out_index )
2022-06-30 17:46:45 +01:00
{
2022-07-02 11:09:49 +01:00
if ( ! node_header | | ! out_index )
2022-06-30 17:46:45 +01:00
{
2022-07-12 17:34:49 +01:00
LOG_MSG_ERROR ( " Invalid parameters! " ) ;
2022-06-30 17:46:45 +01:00
return false ;
}
2022-07-05 02:04:28 +01:00
2022-07-02 11:09:49 +01:00
/* Initialize storage node and find the index for our virtual offset. */
BucketTreeStorageNode storage_node = { 0 } ;
2022-07-02 23:37:13 +01:00
bktrInitializeStorageNode ( & storage_node , entry_size , node_header - > count ) ;
2022-07-02 11:09:49 +01:00
bktrStorageNodeFind ( & storage_node , node_header , virtual_offset ) ;
2022-07-05 02:04:28 +01:00
2022-07-02 11:09:49 +01:00
/* Validate index. */
if ( storage_node . index = = UINT32_MAX )
2022-06-30 17:46:45 +01:00
{
2022-07-12 17:34:49 +01:00
LOG_MSG_ERROR ( " Unable to find index for virtual offset 0x%lX! " , virtual_offset ) ;
2022-06-30 17:46:45 +01:00
return false ;
}
2022-07-05 02:04:28 +01:00
2022-07-02 11:09:49 +01:00
/* Update output index. */
* out_index = storage_node . index ;
2022-07-05 02:04:28 +01:00
2022-07-02 11:09:49 +01:00
return true ;
2020-04-30 09:25:03 +01:00
}
2022-07-02 11:09:49 +01:00
static bool bktrFindEntrySet ( BucketTreeContext * ctx , u32 * out_index , u64 virtual_offset , u32 node_index )
2020-04-30 09:25:03 +01:00
{
2022-07-02 11:09:49 +01:00
/* Get offset node header. */
const BucketTreeNodeHeader * node_header = bktrGetTreeNodeHeader ( ctx , node_index ) ;
if ( ! node_header )
2020-04-30 09:25:03 +01:00
{
2022-07-12 17:34:49 +01:00
LOG_MSG_ERROR ( " Failed to retrieve offset node header at index 0x%X! " , node_index ) ;
2022-07-02 11:09:49 +01:00
return false ;
2020-04-30 09:25:03 +01:00
}
2022-07-05 02:04:28 +01:00
2022-07-02 11:09:49 +01:00
/* Get offset node entry index. */
u32 offset_index = 0 ;
2022-07-02 23:37:13 +01:00
if ( ! bktrGetEntryNodeEntryIndex ( node_header , sizeof ( u64 ) , virtual_offset , & offset_index ) )
2020-04-30 09:25:03 +01:00
{
2022-07-12 17:34:49 +01:00
LOG_MSG_ERROR ( " Failed to get offset node entry index! " ) ;
2022-07-02 11:09:49 +01:00
return false ;
2020-04-30 09:25:03 +01:00
}
2022-07-05 02:04:28 +01:00
2022-07-02 11:09:49 +01:00
/* Update output index. */
* out_index = bktrGetEntrySetIndex ( ctx , node_header - > index , offset_index ) ;
2022-07-05 02:04:28 +01:00
2022-07-02 11:09:49 +01:00
return true ;
}
static const BucketTreeNodeHeader * bktrGetTreeNodeHeader ( BucketTreeContext * ctx , u32 node_index )
{
/* Calculate offset node extents. */
const u64 node_size = ctx - > node_size ;
const u64 node_offset = ( ( node_index + 1 ) * node_size ) ;
2022-07-05 02:04:28 +01:00
2022-07-02 23:37:13 +01:00
if ( ( node_offset + BKTR_NODE_HEADER_SIZE ) > ctx - > node_storage_size )
2020-04-30 09:25:03 +01:00
{
2022-07-12 17:34:49 +01:00
LOG_MSG_ERROR ( " Invalid Bucket Tree Offset Node offset! " ) ;
2020-04-30 09:25:03 +01:00
return NULL ;
}
2022-07-05 02:04:28 +01:00
2022-07-02 11:09:49 +01:00
/* Get offset node header. */
2022-07-02 15:07:08 +01:00
const BucketTreeNodeHeader * node_header = ( const BucketTreeNodeHeader * ) ( ( u8 * ) ctx - > storage_table + node_offset ) ;
2022-07-05 02:04:28 +01:00
2022-07-02 11:09:49 +01:00
/* Validate offset node header. */
if ( ! bktrVerifyNodeHeader ( node_header , node_index , node_size , sizeof ( u64 ) ) )
2020-04-30 09:25:03 +01:00
{
2022-07-12 17:34:49 +01:00
LOG_MSG_ERROR ( " Bucket Tree Offset Node header verification failed! " ) ;
2022-07-02 11:09:49 +01:00
return NULL ;
2020-04-30 09:25:03 +01:00
}
2022-07-05 02:04:28 +01:00
2022-07-02 11:09:49 +01:00
return node_header ;
2020-04-30 09:25:03 +01:00
}
2020-04-29 13:59:28 +01:00
2022-07-02 11:09:49 +01:00
NX_INLINE u32 bktrGetEntrySetIndex ( BucketTreeContext * ctx , u32 node_index , u32 offset_index )
2020-04-30 09:25:03 +01:00
{
2022-07-02 11:09:49 +01:00
return ( u32 ) ( ( ctx - > offset_count - ctx - > storage_table - > offset_node . header . count ) + ( ctx - > offset_count * node_index ) + offset_index ) ;
2020-04-30 09:25:03 +01:00
}
2020-04-29 13:59:28 +01:00
2022-07-02 15:07:08 +01:00
static bool bktrFindEntry ( BucketTreeContext * ctx , BucketTreeVisitor * out_visitor , u64 virtual_offset , u32 entry_set_index )
2020-04-30 09:25:03 +01:00
{
2022-07-02 11:09:49 +01:00
/* Get entry node header. */
const BucketTreeNodeHeader * entry_set_header = bktrGetEntryNodeHeader ( ctx , entry_set_index ) ;
if ( ! entry_set_header )
2020-04-30 09:25:03 +01:00
{
2022-07-12 17:34:49 +01:00
LOG_MSG_ERROR ( " Failed to retrieve entry node header at index 0x%X! " , entry_set_index ) ;
2022-07-02 11:09:49 +01:00
return false ;
2020-04-30 09:25:03 +01:00
}
2022-07-05 02:04:28 +01:00
2022-07-02 11:09:49 +01:00
/* Calculate entry node extents. */
const u64 entry_size = ctx - > entry_size ;
const u64 entry_set_size = ctx - > node_size ;
2022-07-02 23:37:13 +01:00
const u64 entry_set_offset = ( ctx - > node_storage_size + ( entry_set_index * entry_set_size ) ) ;
2022-07-05 02:04:28 +01:00
2022-07-02 11:09:49 +01:00
/* Get entry node entry index. */
u32 entry_index = 0 ;
2022-07-02 23:37:13 +01:00
if ( ! bktrGetEntryNodeEntryIndex ( entry_set_header , entry_size , virtual_offset , & entry_index ) )
2020-04-30 09:25:03 +01:00
{
2022-07-12 17:34:49 +01:00
LOG_MSG_ERROR ( " Failed to get entry node entry index! " ) ;
2022-07-02 11:09:49 +01:00
return false ;
}
2022-07-05 02:04:28 +01:00
2022-07-02 11:09:49 +01:00
/* Get entry node entry offset and validate it. */
u64 entry_offset = bktrGetEntryNodeEntryOffset ( entry_set_offset , entry_size , entry_index ) ;
2022-07-02 23:37:13 +01:00
if ( ( entry_offset + entry_size ) > ( ctx - > node_storage_size + ctx - > entry_storage_size ) )
2022-07-02 11:09:49 +01:00
{
2022-07-12 17:34:49 +01:00
LOG_MSG_ERROR ( " Invalid Bucket Tree Entry Node entry offset! " ) ;
2022-07-02 11:09:49 +01:00
return false ;
2020-04-30 09:25:03 +01:00
}
2022-07-05 02:04:28 +01:00
2022-07-02 15:07:08 +01:00
/* Update output visitor. */
2022-07-03 19:47:00 +01:00
memset ( out_visitor , 0 , sizeof ( BucketTreeVisitor ) ) ;
2022-07-05 02:04:28 +01:00
2022-07-02 15:07:08 +01:00
out_visitor - > bktr_ctx = ctx ;
2022-07-02 23:37:13 +01:00
memcpy ( & ( out_visitor - > entry_set ) , entry_set_header , sizeof ( BucketTreeEntrySetHeader ) ) ;
out_visitor - > entry_index = entry_index ;
2022-07-02 15:07:08 +01:00
out_visitor - > entry = ( ( u8 * ) ctx - > storage_table + entry_offset ) ;
2022-07-05 02:04:28 +01:00
2022-07-02 11:09:49 +01:00
return true ;
}
static const BucketTreeNodeHeader * bktrGetEntryNodeHeader ( BucketTreeContext * ctx , u32 entry_set_index )
{
/* Calculate entry node extents. */
const u64 entry_size = ctx - > entry_size ;
const u64 entry_set_size = ctx - > node_size ;
2022-07-02 23:37:13 +01:00
const u64 entry_set_offset = ( ctx - > node_storage_size + ( entry_set_index * entry_set_size ) ) ;
2022-07-05 02:04:28 +01:00
2022-07-02 23:37:13 +01:00
if ( ( entry_set_offset + BKTR_NODE_HEADER_SIZE ) > ( ctx - > node_storage_size + ctx - > entry_storage_size ) )
2020-04-30 09:25:03 +01:00
{
2022-07-12 17:34:49 +01:00
LOG_MSG_ERROR ( " Invalid Bucket Tree Entry Node offset! " ) ;
2022-07-02 11:09:49 +01:00
return NULL ;
2020-04-30 09:25:03 +01:00
}
2022-07-05 02:04:28 +01:00
2022-07-02 11:09:49 +01:00
/* Get entry node header. */
2022-07-02 15:07:08 +01:00
const BucketTreeNodeHeader * entry_set_header = ( const BucketTreeNodeHeader * ) ( ( u8 * ) ctx - > storage_table + entry_set_offset ) ;
2022-07-05 02:04:28 +01:00
2022-07-02 11:09:49 +01:00
/* Validate entry node header. */
if ( ! bktrVerifyNodeHeader ( entry_set_header , entry_set_index , entry_set_size , entry_size ) )
2020-04-30 09:25:03 +01:00
{
2022-07-12 17:34:49 +01:00
LOG_MSG_ERROR ( " Bucket Tree Entry Node header verification failed! " ) ;
2020-04-30 09:25:03 +01:00
return NULL ;
}
2022-07-05 02:04:28 +01:00
2022-07-02 23:37:13 +01:00
return entry_set_header ;
2022-07-02 11:09:49 +01:00
}
NX_INLINE u64 bktrGetEntryNodeEntryOffset ( u64 entry_set_offset , u64 entry_size , u32 entry_index )
{
2022-07-02 23:37:13 +01:00
return ( entry_set_offset + BKTR_NODE_HEADER_SIZE + ( ( u64 ) entry_index * entry_size ) ) ;
}
NX_INLINE u64 bktrGetEntryNodeEntryOffsetByIndex ( u32 entry_set_index , u64 node_size , u64 entry_size , u32 entry_index )
{
return bktrGetEntryNodeEntryOffset ( ( u64 ) entry_set_index * node_size , entry_size , entry_index ) ;
2022-07-02 11:09:49 +01:00
}
NX_INLINE bool bktrIsExistL2 ( BucketTreeContext * ctx )
{
return ( ctx - > offset_count < ctx - > entry_set_count ) ;
}
NX_INLINE bool bktrIsExistOffsetL2OnL1 ( BucketTreeContext * ctx )
{
return ( bktrIsExistL2 ( ctx ) & & ctx - > storage_table - > offset_node . header . count < ctx - > offset_count ) ;
}
2022-07-02 23:37:13 +01:00
static void bktrInitializeStorageNode ( BucketTreeStorageNode * out , u64 entry_size , u32 entry_count )
2022-07-02 11:09:49 +01:00
{
2022-07-02 23:37:13 +01:00
out - > start . offset = BKTR_NODE_HEADER_SIZE ;
2022-07-02 11:09:49 +01:00
out - > start . stride = ( u32 ) entry_size ;
out - > count = entry_count ;
out - > index = UINT32_MAX ;
}
static void bktrStorageNodeFind ( BucketTreeStorageNode * storage_node , const BucketTreeNodeHeader * node_header , u64 virtual_offset )
{
2022-07-05 00:25:28 +01:00
/* Check for edge case, short circuit. */
if ( storage_node - > count = = 1 )
{
storage_node - > index = 0 ;
return ;
}
2022-07-05 02:04:28 +01:00
2022-07-05 00:25:28 +01:00
/* Perform a binary search. */
u32 entry_count = storage_node - > count , low = 0 , high = ( entry_count - 1 ) ;
BucketTreeStorageNodeOffset * start = & ( storage_node - > start ) ;
2022-07-05 02:04:28 +01:00
2022-07-05 00:25:28 +01:00
while ( low < = high )
2020-04-30 09:25:03 +01:00
{
2022-07-05 00:25:28 +01:00
/* Get the offset to the middle entry within our current lookup range. */
u32 half = ( ( low + high ) / 2 ) ;
BucketTreeStorageNodeOffset mid = bktrStorageNodeOffsetAdd ( start , half ) ;
2022-07-05 02:04:28 +01:00
2022-07-05 00:25:28 +01:00
/* Check middle entry's virtual offset. */
if ( bktrStorageNodeOffsetGetEntryVirtualOffset ( node_header , & mid ) > virtual_offset )
2020-04-30 09:25:03 +01:00
{
2022-07-05 00:25:28 +01:00
/* Update our upper limit. */
high = ( half - 1 ) ;
2020-04-30 09:25:03 +01:00
} else {
2022-07-05 00:25:28 +01:00
/* Check for success. */
BucketTreeStorageNodeOffset pos = bktrStorageNodeOffsetAdd ( & mid , 1 ) ;
if ( half = = ( entry_count - 1 ) | | bktrStorageNodeOffsetGetEntryVirtualOffset ( node_header , & pos ) > virtual_offset )
{
storage_node - > index = half ;
break ;
}
2022-07-05 02:04:28 +01:00
2022-07-05 00:25:28 +01:00
/* Update our lower limit. */
low = ( half + 1 ) ;
2020-04-30 09:25:03 +01:00
}
}
2022-07-02 11:09:49 +01:00
}
NX_INLINE BucketTreeStorageNodeOffset bktrStorageNodeOffsetAdd ( BucketTreeStorageNodeOffset * ofs , u64 value )
{
BucketTreeStorageNodeOffset out = { ofs - > offset + ( value * ( u64 ) ofs - > stride ) , ofs - > stride } ;
return out ;
}
2022-07-05 00:25:28 +01:00
NX_INLINE const u64 bktrStorageNodeOffsetGetEntryVirtualOffset ( const BucketTreeNodeHeader * node_header , const BucketTreeStorageNodeOffset * ofs )
2022-07-02 11:09:49 +01:00
{
2022-07-05 00:25:28 +01:00
return * ( ( const u64 * ) ( ( const u8 * ) node_header + ofs - > offset ) ) ;
2020-04-30 09:25:03 +01:00
}
2022-07-02 23:37:13 +01:00
NX_INLINE bool bktrVisitorIsValid ( BucketTreeVisitor * visitor )
{
return ( visitor & & visitor - > bktr_ctx & & visitor - > entry_index ! = UINT32_MAX ) ;
}
NX_INLINE bool bktrVisitorCanMoveNext ( BucketTreeVisitor * visitor )
{
return ( bktrVisitorIsValid ( visitor ) & & ( ( visitor - > entry_index + 1 ) < visitor - > entry_set . header . count | | ( visitor - > entry_set . header . index + 1 ) < visitor - > bktr_ctx - > entry_set_count ) ) ;
}
NX_INLINE bool bktrVisitorCanMovePrevious ( BucketTreeVisitor * visitor )
{
return ( bktrVisitorIsValid ( visitor ) & & ( visitor - > entry_index > 0 | | visitor - > entry_set . header . index > 0 ) ) ;
}
static bool bktrVisitorMoveNext ( BucketTreeVisitor * visitor )
{
if ( ! bktrVisitorIsValid ( visitor ) )
{
2022-07-12 17:34:49 +01:00
LOG_MSG_ERROR ( " Invalid parameters! " ) ;
2022-07-02 23:37:13 +01:00
return false ;
}
2022-07-05 02:04:28 +01:00
2022-07-02 23:37:13 +01:00
BucketTreeContext * ctx = visitor - > bktr_ctx ;
BucketTreeEntrySetHeader * entry_set = & ( visitor - > entry_set ) ;
2022-07-05 00:25:28 +01:00
u32 entry_index = ( visitor - > entry_index + 1 ) ;
2022-07-02 23:37:13 +01:00
bool success = false ;
2022-07-05 02:04:28 +01:00
2022-07-02 23:37:13 +01:00
/* Invalidate index. */
visitor - > entry_index = UINT32_MAX ;
2022-07-05 02:04:28 +01:00
2022-07-02 23:37:13 +01:00
if ( entry_index = = entry_set - > header . count )
{
/* We have reached the end of this entry node. Let's try to retrieve the first entry from the next one. */
const u32 entry_set_index = ( entry_set - > header . index + 1 ) ;
if ( entry_set_index > = ctx - > entry_set_count )
{
2022-07-12 17:34:49 +01:00
LOG_MSG_ERROR ( " Error: attempting to move visitor into non-existing Bucket Tree Entry Node! " ) ;
2022-07-02 23:37:13 +01:00
goto end ;
}
2022-07-05 02:04:28 +01:00
2022-07-02 23:37:13 +01:00
/* Read next entry set header. */
const u64 end_offset = entry_set - > header . offset ;
const u64 entry_set_size = ctx - > node_size ;
const u64 entry_set_offset = ( ctx - > node_storage_size + ( entry_set_index * entry_set_size ) ) ;
2022-07-05 02:04:28 +01:00
2022-07-02 23:37:13 +01:00
if ( ( entry_set_offset + sizeof ( BucketTreeEntrySetHeader ) ) > ( ctx - > node_storage_size + ctx - > entry_storage_size ) )
{
2022-07-12 17:34:49 +01:00
LOG_MSG_ERROR ( " Invalid Bucket Tree Entry Node offset! " ) ;
2022-07-02 23:37:13 +01:00
goto end ;
}
2022-07-05 02:04:28 +01:00
2022-07-02 23:37:13 +01:00
memcpy ( entry_set , ( u8 * ) ctx - > storage_table + entry_set_offset , sizeof ( BucketTreeEntrySetHeader ) ) ;
2022-07-05 02:04:28 +01:00
2022-07-02 23:37:13 +01:00
/* Validate next entry set header. */
if ( ! bktrVerifyNodeHeader ( & ( entry_set - > header ) , entry_set_index , entry_set_size , ctx - > entry_size ) | | entry_set - > start ! = end_offset | | \
entry_set - > start > = entry_set - > header . offset )
{
2022-07-12 17:34:49 +01:00
LOG_MSG_ERROR ( " Bucket Tree Entry Node header verification failed! " ) ;
2022-07-02 23:37:13 +01:00
goto end ;
}
2022-07-05 02:04:28 +01:00
2022-07-02 23:37:13 +01:00
/* Update entry index. */
entry_index = 0 ;
}
2022-07-05 02:04:28 +01:00
2022-07-02 23:37:13 +01:00
/* Get the new entry. */
const u64 entry_size = ctx - > entry_size ;
const u64 entry_offset = ( ctx - > node_storage_size + bktrGetEntryNodeEntryOffsetByIndex ( entry_set - > header . index , ctx - > node_size , entry_size , entry_index ) ) ;
2022-07-05 02:04:28 +01:00
2022-07-02 23:37:13 +01:00
if ( ( entry_offset + entry_size ) > ( ctx - > node_storage_size + ctx - > entry_storage_size ) )
{
2022-07-12 17:34:49 +01:00
LOG_MSG_ERROR ( " Invalid Bucket Tree Entry Node entry offset! " ) ;
2022-07-02 23:37:13 +01:00
goto end ;
}
2022-07-05 02:04:28 +01:00
2022-07-02 23:37:13 +01:00
/* Update visitor. */
visitor - > entry_index = entry_index ;
visitor - > entry = ( ( u8 * ) ctx - > storage_table + entry_offset ) ;
2022-07-05 02:04:28 +01:00
2022-07-02 23:37:13 +01:00
/* Update return value. */
success = true ;
2022-07-05 02:04:28 +01:00
2022-07-02 23:37:13 +01:00
end :
return success ;
}
static bool bktrVisitorMovePrevious ( BucketTreeVisitor * visitor )
{
if ( ! bktrVisitorIsValid ( visitor ) )
{
2022-07-12 17:34:49 +01:00
LOG_MSG_ERROR ( " Invalid parameters! " ) ;
2022-07-02 23:37:13 +01:00
return false ;
}
2022-07-05 02:04:28 +01:00
2022-07-02 23:37:13 +01:00
BucketTreeContext * ctx = visitor - > bktr_ctx ;
BucketTreeEntrySetHeader * entry_set = & ( visitor - > entry_set ) ;
2022-07-05 00:25:28 +01:00
u32 entry_index = visitor - > entry_index ;
2022-07-02 23:37:13 +01:00
bool success = false ;
2022-07-05 02:04:28 +01:00
2022-07-02 23:37:13 +01:00
/* Invalidate index. */
visitor - > entry_index = UINT32_MAX ;
2022-07-05 02:04:28 +01:00
2022-07-02 23:37:13 +01:00
if ( entry_index = = 0 )
{
/* We have reached the start of this entry node. Let's try to retrieve the last entry from the previous one. */
if ( ! entry_set - > header . index )
{
2022-07-12 17:34:49 +01:00
LOG_MSG_ERROR ( " Error: attempting to move visitor into non-existing Bucket Tree Entry Node! " ) ;
2022-07-02 23:37:13 +01:00
goto end ;
}
2022-07-05 02:04:28 +01:00
2022-07-02 23:37:13 +01:00
/* Read previous entry set header. */
const u64 start_offset = entry_set - > start ;
const u64 entry_set_size = ctx - > node_size ;
const u32 entry_set_index = ( entry_set - > header . index - 1 ) ;
const u64 entry_set_offset = ( ctx - > node_storage_size + ( entry_set_index * entry_set_size ) ) ;
2022-07-05 02:04:28 +01:00
2022-07-02 23:37:13 +01:00
if ( ( entry_set_offset + sizeof ( BucketTreeEntrySetHeader ) ) > ( ctx - > node_storage_size + ctx - > entry_storage_size ) )
{
2022-07-12 17:34:49 +01:00
LOG_MSG_ERROR ( " Invalid Bucket Tree Entry Node offset! " ) ;
2022-07-02 23:37:13 +01:00
goto end ;
}
2022-07-05 02:04:28 +01:00
2022-07-02 23:37:13 +01:00
memcpy ( entry_set , ( u8 * ) ctx - > storage_table + entry_set_offset , sizeof ( BucketTreeEntrySetHeader ) ) ;
2022-07-05 02:04:28 +01:00
2022-07-02 23:37:13 +01:00
/* Validate next entry set header. */
if ( ! bktrVerifyNodeHeader ( & ( entry_set - > header ) , entry_set_index , entry_set_size , ctx - > entry_size ) | | entry_set - > header . offset ! = start_offset | | \
entry_set - > start > = entry_set - > header . offset )
{
2022-07-12 17:34:49 +01:00
LOG_MSG_ERROR ( " Bucket Tree Entry Node header verification failed! " ) ;
2022-07-02 23:37:13 +01:00
goto end ;
}
2022-07-05 02:04:28 +01:00
2022-07-02 23:37:13 +01:00
/* Update entry index. */
entry_index = entry_set - > header . count ;
}
2022-07-05 02:04:28 +01:00
2022-07-02 23:37:13 +01:00
entry_index - - ;
2022-07-05 02:04:28 +01:00
2022-07-02 23:37:13 +01:00
/* Get the new entry. */
const u64 entry_size = ctx - > entry_size ;
const u64 entry_offset = ( ctx - > node_storage_size + bktrGetEntryNodeEntryOffsetByIndex ( entry_set - > header . index , ctx - > node_size , entry_size , entry_index ) ) ;
2022-07-05 02:04:28 +01:00
2022-07-02 23:37:13 +01:00
if ( ( entry_offset + entry_size ) > ( ctx - > node_storage_size + ctx - > entry_storage_size ) )
{
2022-07-12 17:34:49 +01:00
LOG_MSG_ERROR ( " Invalid Bucket Tree Entry Node entry offset! " ) ;
2022-07-02 23:37:13 +01:00
goto end ;
}
2022-07-05 02:04:28 +01:00
2022-07-02 23:37:13 +01:00
/* Update visitor. */
visitor - > entry_index = entry_index ;
visitor - > entry = ( ( u8 * ) ctx - > storage_table + entry_offset ) ;
2022-07-05 02:04:28 +01:00
2022-07-02 23:37:13 +01:00
/* Update return value. */
success = true ;
2022-07-05 02:04:28 +01:00
2022-07-02 23:37:13 +01:00
end :
return success ;
}