2020-10-10 16:35:14 +01:00
/*
* nso . c
*
* Copyright ( c ) 2020 , 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 and conditions of the GNU General Public License ,
* version 2 , as published by the Free Software Foundation .
*
* nxdumptool is distributed in the hope 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 < http : //www.gnu.org/licenses/>.
*/
2020-10-11 16:22:26 +01:00
# define LZ4_STATIC_LINKING_ONLY /* Required by LZ4 to enable in-place decompression. */
2020-10-10 16:35:14 +01:00
# include "utils.h"
# include "nso.h"
2020-10-11 16:22:26 +01:00
# include "lz4.h"
/* Function prototypes. */
static bool nsoGetModuleName ( NsoContext * nso_ctx ) ;
static u8 * nsoGetRodataSegment ( NsoContext * nso_ctx ) ;
static bool nsoGetModuleInfoName ( NsoContext * nso_ctx , u8 * rodata_buf ) ;
static bool nsoGetSectionFromRodataSegment ( NsoContext * nso_ctx , u8 * rodata_buf , u8 * * section_ptr , u64 section_offset , u64 section_size ) ;
bool nsoInitializeContext ( NsoContext * out , PartitionFileSystemContext * pfs_ctx , PartitionFileSystemEntry * pfs_entry )
{
NcaContext * nca_ctx = NULL ;
u8 * rodata_buf = NULL ;
bool success = false ;
if ( ! out | | ! pfs_ctx | | ! pfs_ctx - > nca_fs_ctx | | ! ( nca_ctx = ( NcaContext * ) pfs_ctx - > nca_fs_ctx - > nca_ctx ) | | nca_ctx - > content_type ! = NcmContentType_Program | | ! pfs_ctx - > offset | | ! pfs_ctx - > size | | \
! pfs_ctx - > is_exefs | | pfs_ctx - > header_size < = sizeof ( PartitionFileSystemHeader ) | | ! pfs_ctx - > header | | ! pfs_entry )
{
LOGFILE ( " Invalid parameters! " ) ;
return false ;
}
/* Free output context beforehand. */
nsoFreeContext ( out ) ;
/* Update output context. */
out - > pfs_ctx = pfs_ctx ;
out - > pfs_entry = pfs_entry ;
/* Get entry filename. */
if ( ! ( out - > nso_filename = pfsGetEntryName ( pfs_ctx , pfs_entry ) ) | | ! strlen ( out - > nso_filename ) )
{
LOGFILE ( " Invalid Partition FS entry filename! " ) ;
goto end ;
}
/* Read NSO header. */
if ( ! pfsReadEntryData ( pfs_ctx , pfs_entry , & ( out - > nso_header ) , sizeof ( NsoHeader ) , 0 ) )
{
LOGFILE ( " Failed to read NSO \" %s \" header! " , out - > nso_filename ) ; ;
goto end ;
}
/* Verify NSO header. */
if ( __builtin_bswap32 ( out - > nso_header . magic ) ! = NSO_HEADER_MAGIC )
{
LOGFILE ( " Invalid NSO \" %s \" header magic word! (0x%08X != 0x%08X). " , out - > nso_filename , __builtin_bswap32 ( out - > nso_header . magic ) , __builtin_bswap32 ( NSO_HEADER_MAGIC ) ) ;
goto end ;
}
if ( out - > nso_header . text_segment_header . file_offset < sizeof ( NsoHeader ) | | ! out - > nso_header . text_segment_header . size | | \
( ( out - > nso_header . flags & NsoFlags_TextCompress ) & & ( ! out - > nso_header . text_file_size | | out - > nso_header . text_file_size > out - > nso_header . text_segment_header . size ) ) | | \
( ! ( out - > nso_header . flags & NsoFlags_TextCompress ) & & out - > nso_header . text_file_size ! = out - > nso_header . text_segment_header . size ) | | \
( out - > nso_header . text_segment_header . file_offset + out - > nso_header . text_file_size ) > pfs_entry - > size )
{
LOGFILE ( " Invalid .text segment offset/size for NSO \" %s \" ! (0x%08X, 0x%08X, 0x%08X). " , out - > nso_filename , out - > nso_header . text_segment_header . file_offset , out - > nso_header . text_file_size , \
out - > nso_header . text_segment_header . size ) ;
goto end ;
}
if ( out - > nso_header . rodata_segment_header . file_offset < sizeof ( NsoHeader ) | | ! out - > nso_header . rodata_segment_header . size | | \
( ( out - > nso_header . flags & NsoFlags_RoCompress ) & & ( ! out - > nso_header . rodata_file_size | | out - > nso_header . rodata_file_size > out - > nso_header . rodata_segment_header . size ) ) | | \
( ! ( out - > nso_header . flags & NsoFlags_RoCompress ) & & out - > nso_header . rodata_file_size ! = out - > nso_header . rodata_segment_header . size ) | | \
( out - > nso_header . rodata_segment_header . file_offset + out - > nso_header . rodata_file_size ) > pfs_entry - > size )
{
LOGFILE ( " Invalid .rodata segment offset/size for NSO \" %s \" ! (0x%08X, 0x%08X, 0x%08X). " , out - > nso_filename , out - > nso_header . rodata_segment_header . file_offset , out - > nso_header . rodata_file_size , \
out - > nso_header . rodata_segment_header . size ) ;
goto end ;
}
if ( out - > nso_header . data_segment_header . file_offset < sizeof ( NsoHeader ) | | ! out - > nso_header . data_segment_header . size | | \
( ( out - > nso_header . flags & NsoFlags_DataCompress ) & & ( ! out - > nso_header . data_file_size | | out - > nso_header . data_file_size > out - > nso_header . data_segment_header . size ) ) | | \
( ! ( out - > nso_header . flags & NsoFlags_DataCompress ) & & out - > nso_header . data_file_size ! = out - > nso_header . data_segment_header . size ) | | \
( out - > nso_header . data_segment_header . file_offset + out - > nso_header . data_file_size ) > pfs_entry - > size )
{
LOGFILE ( " Invalid .data segment offset/size for NSO \" %s \" ! (0x%08X, 0x%08X, 0x%08X). " , out - > nso_filename , out - > nso_header . data_segment_header . file_offset , out - > nso_header . data_file_size , \
out - > nso_header . data_segment_header . size ) ;
goto end ;
}
if ( out - > nso_header . module_name_offset < sizeof ( NsoHeader ) | | ! out - > nso_header . module_name_size | | ( out - > nso_header . module_name_offset + out - > nso_header . module_name_size ) > pfs_entry - > size )
{
LOGFILE ( " Invalid module name offset/size for NSO \" %s \" ! (0x%08X, 0x%08X). " , out - > nso_filename , out - > nso_header . module_name_offset , out - > nso_header . module_name_size ) ;
}
2020-10-11 19:13:09 +01:00
if ( out - > nso_header . api_info_section_header . size & & ( out - > nso_header . api_info_section_header . offset + out - > nso_header . api_info_section_header . size ) > out - > nso_header . rodata_segment_header . size )
2020-10-11 16:22:26 +01:00
{
LOGFILE ( " Invalid .api_info section offset/size for NSO \" %s \" ! (0x%08X, 0x%08X). " , out - > nso_filename , out - > nso_header . api_info_section_header . offset , out - > nso_header . api_info_section_header . size ) ;
}
if ( ! out - > nso_header . dynstr_section_header . size | | ( out - > nso_header . dynstr_section_header . offset + out - > nso_header . dynstr_section_header . size ) > out - > nso_header . rodata_segment_header . size )
{
LOGFILE ( " Invalid .dynstr section offset/size for NSO \" %s \" ! (0x%08X, 0x%08X). " , out - > nso_filename , out - > nso_header . dynstr_section_header . offset , out - > nso_header . dynstr_section_header . size ) ;
}
if ( ! out - > nso_header . dynsym_section_header . size | | ( out - > nso_header . dynsym_section_header . offset + out - > nso_header . dynsym_section_header . size ) > out - > nso_header . rodata_segment_header . size )
{
LOGFILE ( " Invalid .dynsym section offset/size for NSO \" %s \" ! (0x%08X, 0x%08X). " , out - > nso_filename , out - > nso_header . dynsym_section_header . offset , out - > nso_header . dynsym_section_header . size ) ;
}
/* Get module name. */
if ( ! nsoGetModuleName ( out ) ) goto end ;
/* Get .rodata segment. */
if ( ! ( rodata_buf = nsoGetRodataSegment ( out ) ) ) goto end ;
/* Get module info name. */
if ( ! nsoGetModuleInfoName ( out , rodata_buf ) ) goto end ;
/* Get .api_info section data. */
if ( ! nsoGetSectionFromRodataSegment ( out , rodata_buf , ( u8 * * ) & ( out - > rodata_api_info_section ) , out - > nso_header . api_info_section_header . offset , out - > nso_header . api_info_section_header . size ) ) goto end ;
out - > rodata_api_info_section_size = out - > nso_header . api_info_section_header . size ;
/* Get .dynstr section data. */
if ( ! nsoGetSectionFromRodataSegment ( out , rodata_buf , ( u8 * * ) & ( out - > rodata_dynstr_section ) , out - > nso_header . dynstr_section_header . offset , out - > nso_header . dynstr_section_header . size ) ) goto end ;
out - > rodata_dynstr_section_size = out - > nso_header . dynstr_section_header . size ;
/* Get .dynsym section data. */
if ( ! nsoGetSectionFromRodataSegment ( out , rodata_buf , & ( out - > rodata_dynsym_section ) , out - > nso_header . dynsym_section_header . offset , out - > nso_header . dynsym_section_header . size ) ) goto end ;
out - > rodata_dynsym_section_size = out - > nso_header . dynsym_section_header . size ;
success = true ;
end :
if ( rodata_buf ) free ( rodata_buf ) ;
if ( ! success ) nsoFreeContext ( out ) ;
return success ;
}
static bool nsoGetModuleName ( NsoContext * nso_ctx )
{
2020-10-12 21:35:47 +01:00
if ( nso_ctx - > nso_header . module_name_offset < sizeof ( NsoHeader ) | | nso_ctx - > nso_header . module_name_size < = 1 ) return true ;
2020-10-11 16:22:26 +01:00
NsoModuleName module_name = { 0 } ;
/* Get module name. */
if ( ! pfsReadEntryData ( nso_ctx - > pfs_ctx , nso_ctx - > pfs_entry , & module_name , sizeof ( NsoModuleName ) , nso_ctx - > nso_header . module_name_offset ) )
{
LOGFILE ( " Failed to read NSO \" %s \" module name length! " , nso_ctx - > nso_filename ) ;
return false ;
}
/* Verify module name length. */
if ( module_name . name_length ! = ( ( u8 ) nso_ctx - > nso_header . module_name_size - 1 ) )
{
LOGFILE ( " NSO \" %s \" module name length mismatch! (0x%02X != 0x%02X). " , nso_ctx - > nso_filename , module_name . name_length , ( u8 ) nso_ctx - > nso_header . module_name_size - 1 ) ;
return false ;
}
/* Allocate memory for the module name. */
nso_ctx - > module_name = calloc ( nso_ctx - > nso_header . module_name_size , sizeof ( char ) ) ;
if ( ! nso_ctx - > module_name )
{
LOGFILE ( " Failed to allocate memory for NSO \" %s \" module name! " , nso_ctx - > nso_filename ) ;
return false ;
}
/* Read module name string. */
if ( ! pfsReadEntryData ( nso_ctx - > pfs_ctx , nso_ctx - > pfs_entry , nso_ctx - > module_name , module_name . name_length , nso_ctx - > nso_header . module_name_offset + 1 ) )
{
LOGFILE ( " Failed to read NSO \" %s \" module name string! " , nso_ctx - > nso_filename ) ;
return false ;
}
return true ;
}
static u8 * nsoGetRodataSegment ( NsoContext * nso_ctx )
{
2020-10-11 18:23:58 +01:00
int lz4_res = 0 ;
bool compressed = ( nso_ctx - > nso_header . flags & NsoFlags_RoCompress ) , verify = ( nso_ctx - > nso_header . flags & NsoFlags_RoHash ) ;
2020-10-11 16:22:26 +01:00
u8 * rodata_buf = NULL ;
u64 rodata_buf_size = ( compressed ? LZ4_DECOMPRESS_INPLACE_BUFFER_SIZE ( nso_ctx - > nso_header . rodata_segment_header . size ) : nso_ctx - > nso_header . rodata_segment_header . size ) ;
2020-10-12 01:40:54 +01:00
u8 * rodata_read_ptr = NULL ;
2020-10-11 16:22:26 +01:00
u64 rodata_read_size = ( compressed ? nso_ctx - > nso_header . rodata_file_size : nso_ctx - > nso_header . rodata_segment_header . size ) ;
2020-10-11 18:23:58 +01:00
u8 rodata_hash [ SHA256_HASH_SIZE ] = { 0 } ;
2020-10-11 16:22:26 +01:00
bool success = false ;
/* Allocate memory for the .rodata buffer. */
if ( ! ( rodata_buf = calloc ( rodata_buf_size , sizeof ( u8 ) ) ) )
{
LOGFILE ( " Failed to allocate 0x%lX bytes for the .rodata segment in NSO \" %s \" ! " , rodata_buf_size , nso_ctx - > nso_filename ) ;
return NULL ;
}
2020-10-12 01:40:54 +01:00
rodata_read_ptr = ( compressed ? ( rodata_buf + ( rodata_buf_size - nso_ctx - > nso_header . rodata_file_size ) ) : rodata_buf ) ;
2020-10-11 16:22:26 +01:00
/* Read .rodata segment data. */
if ( ! pfsReadEntryData ( nso_ctx - > pfs_ctx , nso_ctx - > pfs_entry , rodata_read_ptr , rodata_read_size , nso_ctx - > nso_header . rodata_segment_header . file_offset ) )
{
LOGFILE ( " Failed to read %s .rodata segment in NRO \" %s \" ! " , nso_ctx - > nso_filename ) ;
goto end ;
}
if ( compressed )
{
/* Decompress .rodata segment in-place. */
if ( ( lz4_res = LZ4_decompress_safe ( ( char * ) rodata_read_ptr , ( char * ) rodata_buf , ( int ) nso_ctx - > nso_header . rodata_file_size , ( int ) rodata_buf_size ) ) ! = \
( int ) nso_ctx - > nso_header . rodata_segment_header . size )
{
LOGFILE ( " LZ4 decompression failed for NRO \" %s \" ! (0x%08X). " , nso_ctx - > nso_filename , ( u32 ) lz4_res ) ;
goto end ;
}
}
2020-10-11 18:23:58 +01:00
if ( verify )
{
/* Verify .rodata segment hash. */
sha256CalculateHash ( rodata_hash , rodata_buf , nso_ctx - > nso_header . rodata_segment_header . size ) ;
if ( memcmp ( rodata_hash , nso_ctx - > nso_header . rodata_segment_hash , SHA256_HASH_SIZE ) ! = 0 )
{
LOGFILE ( " .rodata segment checksum mismatch for NRO \" %s \" ! " , nso_ctx - > nso_filename ) ;
goto end ;
}
}
2020-10-11 16:22:26 +01:00
success = true ;
end :
if ( ! success & & rodata_buf )
{
free ( rodata_buf ) ;
rodata_buf = NULL ;
}
return rodata_buf ;
}
static bool nsoGetModuleInfoName ( NsoContext * nso_ctx , u8 * rodata_buf )
{
NsoModuleInfo * module_info = ( NsoModuleInfo * ) ( rodata_buf + 0x4 ) ;
if ( ! module_info - > name_length ) return true ;
/* Allocate memory for the module info name. */
nso_ctx - > module_info_name = calloc ( module_info - > name_length + 1 , sizeof ( char ) ) ;
if ( ! nso_ctx - > module_info_name )
{
LOGFILE ( " Failed to allocate memory for NSO \" %s \" module info name! " , nso_ctx - > nso_filename ) ;
return false ;
}
/* Copy module info name. */
sprintf ( nso_ctx - > module_info_name , " %.*s " , ( int ) module_info - > name_length , module_info - > name ) ;
return true ;
}
2020-10-10 16:35:14 +01:00
2020-10-11 16:22:26 +01:00
static bool nsoGetSectionFromRodataSegment ( NsoContext * nso_ctx , u8 * rodata_buf , u8 * * section_ptr , u64 section_offset , u64 section_size )
{
2020-10-12 21:35:47 +01:00
if ( ! section_size | | ( section_offset + section_size ) > nso_ctx - > nso_header . rodata_segment_header . size ) return true ;
2020-10-11 19:13:09 +01:00
2020-10-11 16:22:26 +01:00
/* Allocate memory for the desired .rodata section. */
if ( ! ( * section_ptr = malloc ( section_size ) ) )
{
LOGFILE ( " Failed to allocate 0x%lX bytes for section at .rodata offset 0x%lX in NSO \" %s \" ! " , section_size , section_offset , nso_ctx - > nso_filename ) ;
return false ;
}
/* Copy .rodata section data. */
memcpy ( * section_ptr , rodata_buf + section_offset , section_size ) ;
return true ;
}