2024-10-24 22:26:00 +01:00
/*
* system_update . c
*
* Copyright ( c ) 2020 - 2024 , DarkMatterCore < pabloacurielz @ gmail . com > .
*
* This file is part of nxdumptool ( https : //github.com/DarkMatterCore/nxdumptool).
*
* 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 .
*
* 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 .
*
* You should have received a copy of the GNU General Public License
* along with this program . If not , see < https : //www.gnu.org/licenses/>.
*/
# include <core/nxdt_utils.h>
# include <core/system_update.h>
# include <core/cnmt.h>
2024-10-27 11:23:46 +00:00
# include <core/romfs.h>
# define SYSTEM_VERSION_FILE_PATH " / file"
2024-10-24 22:26:00 +01:00
/* Global variables. */
static Mutex g_systemUpdateMutex = 0 ;
static bool g_systemUpdateInterfaceInit = false ;
static TitleInfo * g_systemUpdateTitleInfo = NULL ;
static NcaContext * g_systemUpdateNcaContext = NULL ;
static ContentMetaContext g_systemUpdateCnmtContext = { 0 } ;
/* Function prototypes. */
static bool _systemUpdateInitializeDumpContext ( SystemUpdateDumpContext * ctx ) ;
static bool systemUpdateProcessContentMetaInfo ( SystemUpdateDumpContext * ctx , const NcmContentMetaInfo * content_meta_info ) ;
static bool systemUpdateProcessContentRecords ( SystemUpdateDumpContext * ctx , TitleInfo * title_info ) ;
static int systemUpdateNcaContextSortFunction ( const void * a , const void * b ) ;
2024-10-27 11:23:46 +00:00
static bool systemUpdateGetSystemVersionFileData ( SystemUpdateDumpContext * ctx ) ;
2024-10-24 22:26:00 +01:00
NX_INLINE NcaContext * systemUpdateGetCurrentNcaContextFromDumpContext ( SystemUpdateDumpContext * ctx ) ;
bool systemUpdateInitialize ( void )
{
bool ret = false ;
SCOPED_LOCK ( & g_systemUpdateMutex )
{
ret = g_systemUpdateInterfaceInit ;
if ( ret ) break ;
/* Get title info. */
if ( ! ( g_systemUpdateTitleInfo = titleGetTitleInfoEntryFromStorageByTitleId ( NcmStorageId_BuiltInSystem , SYSTEM_UPDATE_TID ) ) )
{
LOG_MSG_ERROR ( " Failed to get title info for SystemUpdate! " ) ;
break ;
}
/* Allocate memory for the SystemUpdate NCA context. */
g_systemUpdateNcaContext = calloc ( 1 , sizeof ( NcaContext ) ) ;
if ( ! g_systemUpdateNcaContext )
{
LOG_MSG_ERROR ( " Failed to allocate memory for SystemUpdate NCA context! " ) ;
goto end ;
}
/* Initialize NCA context. */
/* Don't allow invalid NCA signatures. */
if ( ! ncaInitializeContext ( g_systemUpdateNcaContext , g_systemUpdateTitleInfo - > storage_id , 0 , & ( g_systemUpdateTitleInfo - > meta_key ) , \
titleGetContentInfoByTypeAndIdOffset ( g_systemUpdateTitleInfo , NcmContentType_Meta , 0 ) , NULL ) | | ! g_systemUpdateNcaContext - > valid_main_signature )
{
LOG_MSG_ERROR ( " Failed to initialize SystemUpdate Meta NCA context! " ) ;
goto end ;
}
/* Initialize Content Meta context. */
ret = g_systemUpdateInterfaceInit = ( cnmtInitializeContext ( & g_systemUpdateCnmtContext , g_systemUpdateNcaContext ) & & \
g_systemUpdateCnmtContext . packaged_header - > content_meta_count & & g_systemUpdateCnmtContext . content_meta_info ) ;
if ( ret ) break ;
LOG_MSG_ERROR ( " Failed to initialize Content Meta context for SystemUpdate Meta NCA \" %s \" ! " , g_systemUpdateNcaContext - > content_id_str ) ;
end :
cnmtFreeContext ( & g_systemUpdateCnmtContext ) ;
if ( g_systemUpdateNcaContext )
{
free ( g_systemUpdateNcaContext ) ;
g_systemUpdateNcaContext = NULL ;
}
titleFreeTitleInfo ( & g_systemUpdateTitleInfo ) ;
}
return ret ;
}
void systemUpdateExit ( void )
{
SCOPED_LOCK ( & g_systemUpdateMutex )
{
/* Free Content Meta context. */
cnmtFreeContext ( & g_systemUpdateCnmtContext ) ;
/* Free NCA context. */
if ( g_systemUpdateNcaContext )
{
free ( g_systemUpdateNcaContext ) ;
g_systemUpdateNcaContext = NULL ;
}
/* Free TitleInfo entry. */
titleFreeTitleInfo ( & g_systemUpdateTitleInfo ) ;
/* Update flag. */
g_systemUpdateInterfaceInit = false ;
}
}
bool systemUpdateInitializeDumpContext ( SystemUpdateDumpContext * ctx )
{
bool ret = false ;
SCOPED_LOCK ( & g_systemUpdateMutex ) ret = _systemUpdateInitializeDumpContext ( ctx ) ;
return ret ;
}
bool systemUpdateGetCurrentContentFileSizeFromDumpContext ( SystemUpdateDumpContext * ctx , u64 * out_size )
{
NcaContext * nca_ctx = systemUpdateGetCurrentNcaContextFromDumpContext ( ctx ) ;
if ( ! nca_ctx | | ! out_size )
{
LOG_MSG_ERROR ( " Invalid parameters! " ) ;
return false ;
}
* out_size = nca_ctx - > content_size ;
return true ;
}
char * systemUpdateGetCurrentContentFileNameFromDumpContext ( SystemUpdateDumpContext * ctx )
{
NcaContext * nca_ctx = systemUpdateGetCurrentNcaContextFromDumpContext ( ctx ) ;
if ( ! nca_ctx )
{
LOG_MSG_ERROR ( " Invalid parameters! " ) ;
return NULL ;
}
bool is_meta_nca = ( nca_ctx - > content_type = = NcmContentType_Meta ) ;
size_t nca_filename_size = ( ( is_meta_nca ? NCA_HFS_META_NAME_LENGTH : NCA_HFS_REGULAR_NAME_LENGTH ) + 1 ) ;
char * nca_filename = calloc ( nca_filename_size , sizeof ( char ) ) ;
if ( nca_filename )
{
snprintf ( nca_filename , nca_filename_size , " %s%s " , nca_ctx - > content_id_str , is_meta_nca ? " .cnmt.nca " : " .nca " ) ;
} else {
LOG_MSG_ERROR ( " Failed to allocate 0x%lX-byte long filename for %s NCA \" %s \" ! (title %016lX). " , nca_filename_size , titleGetNcmContentTypeName ( nca_ctx - > content_type ) , \
nca_ctx - > content_id_str , nca_ctx - > title_id ) ;
}
return nca_filename ;
}
bool systemUpdateReadCurrentContentFileFromDumpContext ( SystemUpdateDumpContext * ctx , void * out , u64 read_size )
{
NcaContext * nca_ctx = NULL ;
if ( ! ( nca_ctx = systemUpdateGetCurrentNcaContextFromDumpContext ( ctx ) ) | | ! out | | ! read_size | | ( ctx - > cur_content_offset + read_size ) > nca_ctx - > content_size )
{
LOG_MSG_ERROR ( " Invalid parameters! " ) ;
return false ;
}
2024-10-27 11:23:46 +00:00
u8 nca_hash [ SHA256_HASH_SIZE ] = { 0 } ;
2024-10-24 22:26:00 +01:00
bool success = false ;
/* Read NCA data. */
if ( ! ncaReadContentFile ( nca_ctx , out , read_size , ctx - > cur_content_offset ) )
{
LOG_MSG_ERROR ( " Failed to read %s NCA \" %s \" ! (title %016lX). " , titleGetNcmContentTypeName ( nca_ctx - > content_type ) , \
nca_ctx - > content_id_str , nca_ctx - > title_id ) ;
goto end ;
}
/* (Re-)initialize SHA-256 hash context, if needed. */
if ( ! ctx - > cur_content_offset ) sha256ContextCreate ( & ( ctx - > sha256_ctx ) ) ;
/* Update SHA-256 hash context. */
sha256ContextUpdate ( & ( ctx - > sha256_ctx ) , out , read_size ) ;
/* Update system update context. */
ctx - > cur_size + = read_size ;
ctx - > cur_content_offset + = read_size ;
/* Check if we have finished reading this content. */
if ( ctx - > cur_content_offset > = nca_ctx - > content_size )
{
/* Verify SHA-256 hash for this content. */
2024-10-27 11:23:46 +00:00
sha256ContextGetHash ( & ( ctx - > sha256_ctx ) , nca_hash ) ;
2024-10-24 22:26:00 +01:00
2024-10-27 11:23:46 +00:00
if ( memcmp ( nca_hash , nca_ctx - > content_id . c , sizeof ( nca_ctx - > content_id . c ) ) ! = 0 )
2024-10-24 22:26:00 +01:00
{
LOG_MSG_ERROR ( " SHA-256 checksum mismatch for %s NCA \" %s \" ! (title %016lX). " , titleGetNcmContentTypeName ( nca_ctx - > content_type ) , \
nca_ctx - > content_id_str , nca_ctx - > title_id ) ;
goto end ;
}
/* Update system update context. */
ctx - > content_idx + + ;
ctx - > cur_content_offset = 0 ;
}
/* Update return value. */
success = true ;
end :
return success ;
}
static bool _systemUpdateInitializeDumpContext ( SystemUpdateDumpContext * ctx )
{
if ( ! g_systemUpdateInterfaceInit | | ! ctx )
{
LOG_MSG_ERROR ( " Invalid parameters! " ) ;
return false ;
}
bool success = false ;
/* Free output context beforehand. */
systemUpdateFreeDumpContext ( ctx ) ;
/* Loop through all of our content meta info records. */
for ( u16 i = 0 ; i < g_systemUpdateCnmtContext . packaged_header - > content_meta_count ; i + + )
{
NcmContentMetaInfo * cur_meta_info = & ( g_systemUpdateCnmtContext . content_meta_info [ i ] ) ;
/* Process current content meta info record. */
if ( ! systemUpdateProcessContentMetaInfo ( ctx , cur_meta_info ) )
{
LOG_MSG_ERROR ( " Failed to process content meta info for title %016lX! " , cur_meta_info - > id ) ;
goto end ;
}
}
/* Manually add SystemUpdate content records. */
/* The SystemUpdate CNMT doesn't reference itself. */
2024-10-27 11:23:46 +00:00
if ( ! systemUpdateProcessContentRecords ( ctx , g_systemUpdateTitleInfo ) )
2024-10-24 22:26:00 +01:00
{
LOG_MSG_ERROR ( " Failed to process SystemUpdate content records! " ) ;
goto end ;
}
/* Sort NCA contexts. */
2024-10-27 11:23:46 +00:00
if ( ctx - > content_count > 1 ) qsort ( ctx - > nca_ctxs , ctx - > content_count , sizeof ( NcaContext * ) , & systemUpdateNcaContextSortFunction ) ;
/* Retrieve system version file data. */
success = systemUpdateGetSystemVersionFileData ( ctx ) ;
if ( ! success ) LOG_MSG_ERROR ( " Failed to retrieve SystemVersion file data! " ) ;
2024-10-24 22:26:00 +01:00
end :
/* Free output context, if needed. */
if ( ! success ) systemUpdateFreeDumpContext ( ctx ) ;
return success ;
}
static bool systemUpdateProcessContentMetaInfo ( SystemUpdateDumpContext * ctx , const NcmContentMetaInfo * content_meta_info )
{
if ( ! ctx | | ! content_meta_info )
{
LOG_MSG_ERROR ( " Invalid parameters! " ) ;
return false ;
}
TitleInfo * title_info = NULL ;
bool success = false ;
/* Get TitleInfo entry. */
title_info = titleGetTitleInfoEntryFromStorageByTitleId ( NcmStorageId_BuiltInSystem , content_meta_info - > id ) ;
if ( ! title_info )
{
LOG_MSG_ERROR ( " Failed to get TitleInfo entry for ID %016lX! " , content_meta_info - > id ) ;
goto end ;
}
/* Check for matching version values. */
if ( title_info - > version . value ! = content_meta_info - > version )
{
LOG_MSG_ERROR ( " Version mismatch for title %016lX! (got v%u, expected v%u). " , content_meta_info - > id , title_info - > version . value , \
content_meta_info - > version ) ;
goto end ;
}
/* Process content records. */
success = systemUpdateProcessContentRecords ( ctx , title_info ) ;
if ( ! success ) LOG_MSG_ERROR ( " Failed to process content records for title %016lX! " , content_meta_info - > id ) ;
end :
titleFreeTitleInfo ( & title_info ) ;
return success ;
}
static bool systemUpdateProcessContentRecords ( SystemUpdateDumpContext * ctx , TitleInfo * title_info )
{
if ( ! ctx | | ! titleIsValidInfoBlock ( title_info ) )
{
LOG_MSG_ERROR ( " Invalid parameters! " ) ;
return false ;
}
NcaContext * * nca_ctxs_tmp = NULL ;
const u32 nca_ctxs_count = ( ctx - > content_count + title_info - > content_count ) ;
bool success = false ;
/* Reallocate NCA context pointer array. */
nca_ctxs_tmp = realloc ( ctx - > nca_ctxs , nca_ctxs_count * sizeof ( NcaContext * ) ) ;
if ( ! nca_ctxs_tmp )
{
LOG_MSG_ERROR ( " Failed to reallocate NCA context pointer array for title %016lX! (%u %s). " , title_info - > meta_key . id , nca_ctxs_count , \
nca_ctxs_count > 1 ? " entries " : " entry " ) ;
goto end ;
}
memset ( & ( nca_ctxs_tmp [ ctx - > content_count ] ) , 0 , title_info - > content_count * sizeof ( NcaContext * ) ) ;
ctx - > nca_ctxs = nca_ctxs_tmp ;
/* Loop through all of the content records for the current title. */
for ( u32 i = 0 ; i < title_info - > content_count ; i + + )
{
NcmContentInfo * cur_content_info = & ( title_info - > content_infos [ i ] ) ;
NcaContext * * cur_nca_ctx = & ( ctx - > nca_ctxs [ ctx - > content_count + i ] ) ;
/* Allocate memory for the current NCA context. */
* cur_nca_ctx = calloc ( 1 , sizeof ( NcaContext ) ) ;
if ( ! * cur_nca_ctx )
{
LOG_MSG_ERROR ( " Failed to allocate memory for NCA context! (title %016lX, content #%u). " , title_info - > meta_key . id , i ) ;
goto end ;
}
/* Initialize current NCA context. */
if ( ! ncaInitializeContext ( * cur_nca_ctx , title_info - > storage_id , 0 , & ( title_info - > meta_key ) , cur_content_info , NULL ) | | ! ( * cur_nca_ctx ) - > valid_main_signature )
{
LOG_MSG_ERROR ( " Failed to initialize NCA context! (title %016lX, content #%u). " , title_info - > meta_key . id , i ) ;
goto end ;
}
}
/* Update context. */
ctx - > total_size + = title_info - > size ;
ctx - > content_count + = title_info - > content_count ;
/* Update return value. */
success = true ;
end :
/* Free previously allocated NCA contexts if we ran into a problem. */
if ( ! success & & nca_ctxs_tmp )
{
for ( u32 i = 0 ; i < title_info - > content_count ; i + + )
{
if ( ! ctx - > nca_ctxs [ ctx - > content_count + i ] ) continue ;
free ( ctx - > nca_ctxs [ ctx - > content_count + i ] ) ;
ctx - > nca_ctxs [ ctx - > content_count + i ] = NULL ;
}
}
return success ;
}
static int systemUpdateNcaContextSortFunction ( const void * a , const void * b )
{
const NcaContext * nca_ctx_1 = * ( ( const NcaContext * * ) a ) ;
const NcaContext * nca_ctx_2 = * ( ( const NcaContext * * ) b ) ;
if ( nca_ctx_1 - > title_id < nca_ctx_2 - > title_id )
{
return - 1 ;
} else
if ( nca_ctx_1 - > title_id > nca_ctx_2 - > title_id )
{
return 1 ;
}
if ( nca_ctx_1 - > title_version . value < nca_ctx_2 - > title_version . value )
{
return - 1 ;
} else
if ( nca_ctx_1 - > title_version . value > nca_ctx_2 - > title_version . value )
{
return 1 ;
}
if ( nca_ctx_1 - > content_type < nca_ctx_2 - > content_type )
{
return - 1 ;
} else
if ( nca_ctx_1 - > content_type > nca_ctx_2 - > content_type )
{
return 1 ;
}
if ( nca_ctx_1 - > id_offset < nca_ctx_2 - > id_offset )
{
return - 1 ;
} else
if ( nca_ctx_1 - > id_offset > nca_ctx_2 - > id_offset )
{
return 1 ;
}
return 0 ;
}
2024-10-27 11:23:46 +00:00
static bool systemUpdateGetSystemVersionFileData ( SystemUpdateDumpContext * ctx )
{
if ( ! systemUpdateIsValidDumpContext ( ctx ) )
{
LOG_MSG_ERROR ( " Invalid parameters! " ) ;
return false ;
}
NcaContext * nca_ctx = NULL ;
RomFileSystemContext romfs_ctx = { 0 } ;
RomFileSystemFileEntry * romfs_file_entry = NULL ;
bool success = false ;
/* Loop through our NCA contexts until we find the Data NCA for the SystemVersion title. */
for ( u32 i = 0 ; i < ctx - > content_count ; i + + )
{
nca_ctx = ctx - > nca_ctxs [ i ] ;
if ( nca_ctx & & nca_ctx - > title_id = = SYSTEM_VERSION_TID & & nca_ctx - > content_type = = NcmContentType_Data ) break ;
nca_ctx = NULL ;
}
if ( ! nca_ctx )
{
LOG_MSG_ERROR ( " Unable to find Data NCA for SystemVersion title! " ) ;
goto end ;
}
LOG_MSG_DEBUG ( " Found Data NCA \" %s \" for SystemVersion title. " , nca_ctx - > content_id_str ) ;
/* Initialize RomFS context. */
if ( ! romfsInitializeContext ( & romfs_ctx , & ( nca_ctx - > fs_ctx [ 0 ] ) , NULL ) )
{
LOG_MSG_ERROR ( " Failed to initialize RomFS context for SystemVersion Data NCA! " ) ;
goto end ;
}
/* Get RomFS file entry. */
if ( ! ( romfs_file_entry = romfsGetFileEntryByPath ( & romfs_ctx , SYSTEM_VERSION_FILE_PATH ) ) )
{
LOG_MSG_ERROR ( " Failed to retrieve RomFS file entry for SystemVersion Data NCA! " ) ;
goto end ;
}
/* Validate file size. */
if ( romfs_file_entry - > size ! = sizeof ( ctx - > version_file ) )
{
LOG_MSG_ERROR ( " Invalid RomFS file entry size in SystemVersion Data NCA! Got 0x%lX, expected 0x%lX. " , romfs_file_entry - > size , sizeof ( ctx - > version_file ) ) ;
goto end ;
}
/* Read SystemVersion file data. */
success = romfsReadFileEntryData ( & romfs_ctx , romfs_file_entry , & ( ctx - > version_file ) , sizeof ( ctx - > version_file ) , 0 ) ;
if ( ! success )
{
LOG_MSG_ERROR ( " Failed to read SystemVersion file data! " ) ;
goto end ;
}
LOG_DATA_DEBUG ( & ( ctx - > version_file ) , sizeof ( ctx - > version_file ) , " SystemVersion file data: " ) ;
end :
romfsFreeContext ( & romfs_ctx ) ;
return success ;
}
2024-10-24 22:26:00 +01:00
NX_INLINE NcaContext * systemUpdateGetCurrentNcaContextFromDumpContext ( SystemUpdateDumpContext * ctx )
{
return ( ( systemUpdateIsValidDumpContext ( ctx ) & & ! systemUpdateIsDumpContextFinished ( ctx ) ) ? ctx - > nca_ctxs [ ctx - > content_idx ] : NULL ) ;
}