2018-08-13 17:14:21 +00:00
# include <iostream>
# include <iomanip>
2018-04-25 11:37:22 +00:00
# include <fnd/SimpleTextOutput.h>
# include <fnd/SimpleFile.h>
# include <fnd/io.h>
2020-08-14 13:36:15 +00:00
# include "CompressedArchiveIFile.h"
2018-05-12 15:02:53 +00:00
# include "RomfsProcess.h"
2018-04-25 11:37:22 +00:00
2021-09-28 11:15:54 +00:00
nstool : : RomfsProcess : : RomfsProcess ( ) :
2018-09-23 03:29:22 +00:00
mFile ( ) ,
2021-09-28 11:15:54 +00:00
mCliOutputMode ( true , false , false , false ) ,
2018-05-26 13:13:21 +00:00
mVerify ( false ) ,
mExtractPath ( ) ,
mExtract ( false ) ,
mMountName ( ) ,
mListFs ( false ) ,
mDirNum ( 0 ) ,
mFileNum ( 0 )
{
mRootDir . name . clear ( ) ;
mRootDir . dir_list . clear ( ) ;
mRootDir . file_list . clear ( ) ;
}
2021-09-28 11:15:54 +00:00
void nstool : : RomfsProcess : : process ( )
2018-05-26 13:13:21 +00:00
{
resolveRomfs ( ) ;
2018-08-13 17:14:21 +00:00
2021-09-28 11:15:54 +00:00
if ( mCliOutputMode . show_basic_info )
2018-06-18 15:30:19 +00:00
{
2018-05-26 13:13:21 +00:00
displayHeader ( ) ;
2021-09-28 11:15:54 +00:00
if ( mListFs | | mCliOutputMode . show_extended_info )
2018-06-18 15:30:19 +00:00
displayFs ( ) ;
}
2018-08-13 17:14:21 +00:00
2018-05-26 13:13:21 +00:00
if ( mExtract )
2018-06-24 15:01:16 +00:00
extractFs ( ) ;
2018-05-26 13:13:21 +00:00
}
2021-09-28 11:15:54 +00:00
void nstool : : RomfsProcess : : setInputFile ( const std : : shared_ptr < tc : : io : : IStream > & file )
2018-05-26 13:13:21 +00:00
{
2018-06-03 08:48:12 +00:00
mFile = file ;
2018-05-26 13:13:21 +00:00
}
2021-09-28 11:15:54 +00:00
void nstool : : RomfsProcess : : setCliOutputMode ( CliOutputMode type )
2018-05-26 13:13:21 +00:00
{
2018-06-18 15:30:19 +00:00
mCliOutputMode = type ;
2018-05-26 13:13:21 +00:00
}
2021-09-28 11:15:54 +00:00
void nstool : : RomfsProcess : : setVerifyMode ( bool verify )
2018-05-26 13:13:21 +00:00
{
mVerify = verify ;
}
2021-09-28 11:15:54 +00:00
void nstool : : RomfsProcess : : setMountPointName ( const std : : string & mount_name )
2018-05-26 13:13:21 +00:00
{
mMountName = mount_name ;
}
2021-09-28 11:15:54 +00:00
void nstool : : RomfsProcess : : setExtractPath ( const std : : string & path )
2018-05-26 13:13:21 +00:00
{
mExtract = true ;
mExtractPath = path ;
}
2021-09-28 11:15:54 +00:00
void nstool : : RomfsProcess : : setListFs ( bool list_fs )
2018-05-26 13:13:21 +00:00
{
mListFs = list_fs ;
}
2021-09-28 11:15:54 +00:00
const nstool : : RomfsProcess : : sDirectory & nstool : : RomfsProcess : : getRootDir ( ) const
2018-05-26 13:13:21 +00:00
{
return mRootDir ;
}
2021-09-28 11:15:54 +00:00
void nstool : : RomfsProcess : : printTab ( size_t tab ) const
2018-04-25 11:37:22 +00:00
{
for ( size_t i = 0 ; i < tab ; i + + )
{
2018-08-13 17:14:21 +00:00
std : : cout < < " " ;
2018-04-25 11:37:22 +00:00
}
}
2021-09-28 11:15:54 +00:00
void nstool : : RomfsProcess : : displayFile ( const sFile & file , size_t tab ) const
2018-04-25 11:37:22 +00:00
{
printTab ( tab ) ;
2018-08-13 17:14:21 +00:00
std : : cout < < file . name ;
2021-09-28 11:15:54 +00:00
if ( mCliOutputMode . show_layout )
2018-04-25 11:37:22 +00:00
{
2018-08-13 17:14:21 +00:00
std : : cout < < std : : hex < < " (offset=0x " < < file . offset < < " , size=0x " < < file . size < < " ) " ;
2018-04-25 11:37:22 +00:00
}
2018-08-13 17:14:21 +00:00
std : : cout < < std : : endl ;
2018-04-25 11:37:22 +00:00
}
2021-09-28 11:15:54 +00:00
void nstool : : RomfsProcess : : displayDir ( const sDirectory & dir , size_t tab ) const
2018-04-25 11:37:22 +00:00
{
if ( dir . name . empty ( ) = = false )
{
printTab ( tab ) ;
2018-08-13 17:14:21 +00:00
std : : cout < < dir . name < < std : : endl ;
2018-04-25 11:37:22 +00:00
}
2018-06-24 15:01:16 +00:00
for ( size_t i = 0 ; i < dir . dir_list . size ( ) ; i + + )
2018-04-25 11:37:22 +00:00
{
displayDir ( dir . dir_list [ i ] , tab + 1 ) ;
}
2018-06-24 15:01:16 +00:00
for ( size_t i = 0 ; i < dir . file_list . size ( ) ; i + + )
2018-04-25 11:37:22 +00:00
{
displayFile ( dir . file_list [ i ] , tab + 1 ) ;
}
}
2021-09-28 11:15:54 +00:00
void nstool : : RomfsProcess : : displayHeader ( )
2018-04-25 11:37:22 +00:00
{
2018-08-13 17:14:21 +00:00
std : : cout < < " [RomFS] " < < std : : endl ;
std : : cout < < " DirNum: " < < std : : dec < < mDirNum < < std : : endl ;
std : : cout < < " FileNum: " < < std : : dec < < mFileNum < < std : : endl ;
if ( mMountName . empty ( ) = = false )
{
std : : cout < < " MountPoint: " < < mMountName ;
if ( mMountName . at ( mMountName . length ( ) - 1 ) ! = ' / ' )
std : : cout < < " / " ;
std : : cout < < std : : endl ;
}
2018-04-25 11:37:22 +00:00
}
2021-09-28 11:15:54 +00:00
void nstool : : RomfsProcess : : displayFs ( )
2018-04-25 11:37:22 +00:00
{
displayDir ( mRootDir , 1 ) ;
}
2021-09-28 11:15:54 +00:00
void nstool : : RomfsProcess : : extractDir ( const std : : string & path , const sDirectory & dir )
2018-04-25 12:27:47 +00:00
{
std : : string dir_path ;
std : : string file_path ;
// make dir path
fnd : : io : : appendToPath ( dir_path , path ) ;
if ( dir . name . empty ( ) = = false )
fnd : : io : : appendToPath ( dir_path , dir . name ) ;
// make directory
fnd : : io : : makeDirectory ( dir_path ) ;
// extract files
fnd : : SimpleFile outFile ;
2018-06-24 15:01:16 +00:00
for ( size_t i = 0 ; i < dir . file_list . size ( ) ; i + + )
2018-04-25 12:27:47 +00:00
{
file_path . clear ( ) ;
fnd : : io : : appendToPath ( file_path , dir_path ) ;
fnd : : io : : appendToPath ( file_path , dir . file_list [ i ] . name ) ;
2021-09-28 11:15:54 +00:00
if ( mCliOutputMode . show_basic_info )
2018-08-13 17:14:21 +00:00
std : : cout < < " extract=[ " < < file_path < < " ] " < < std : : endl ;
2018-04-25 12:27:47 +00:00
outFile . open ( file_path , outFile . Create ) ;
2018-09-23 03:29:22 +00:00
( * mFile ) - > seek ( dir . file_list [ i ] . offset ) ;
2018-05-27 10:17:34 +00:00
for ( size_t j = 0 ; j < ( ( dir . file_list [ i ] . size / kCacheSize ) + ( ( dir . file_list [ i ] . size % kCacheSize ) ! = 0 ) ) ; j + + )
2018-04-25 12:27:47 +00:00
{
2018-09-23 03:29:22 +00:00
( * mFile ) - > read ( mCache . data ( ) , _MIN ( dir . file_list [ i ] . size - ( kCacheSize * j ) , kCacheSize ) ) ;
2018-06-24 15:01:16 +00:00
outFile . write ( mCache . data ( ) , _MIN ( dir . file_list [ i ] . size - ( kCacheSize * j ) , kCacheSize ) ) ;
2018-05-27 10:17:34 +00:00
}
2018-04-25 12:27:47 +00:00
outFile . close ( ) ;
}
2018-06-24 15:01:16 +00:00
for ( size_t i = 0 ; i < dir . dir_list . size ( ) ; i + + )
2018-04-25 12:27:47 +00:00
{
extractDir ( dir_path , dir . dir_list [ i ] ) ;
}
}
2021-09-28 11:15:54 +00:00
void nstool : : RomfsProcess : : extractFs ( )
2018-04-25 11:37:22 +00:00
{
2018-05-26 13:13:21 +00:00
// allocate only when extractDir is invoked
2018-05-27 10:17:34 +00:00
mCache . alloc ( kCacheSize ) ;
2018-04-25 12:27:47 +00:00
extractDir ( mExtractPath , mRootDir ) ;
2018-04-25 11:37:22 +00:00
}
2021-09-28 11:15:54 +00:00
bool nstool : : RomfsProcess : : validateHeaderLayout ( const nn : : hac : : sRomfsHeader * hdr ) const
2018-04-25 11:37:22 +00:00
{
bool validLayout = true ;
2018-08-07 07:17:51 +00:00
if ( hdr - > header_size . get ( ) ! = sizeof ( nn : : hac : : sRomfsHeader ) )
2018-04-25 11:37:22 +00:00
{
validLayout = false ;
}
uint64_t pos = hdr - > sections [ 0 ] . offset . get ( ) ;
2018-08-07 07:17:51 +00:00
for ( size_t i = 0 ; i < nn : : hac : : romfs : : SECTION_NUM ; i + + )
2018-04-25 11:37:22 +00:00
{
if ( hdr - > sections [ i ] . offset . get ( ) ! = pos )
{
validLayout = false ;
}
pos + = hdr - > sections [ i ] . size . get ( ) ;
}
return validLayout ;
}
2021-09-28 11:15:54 +00:00
void nstool : : RomfsProcess : : importDirectory ( uint32_t dir_offset , sDirectory & dir )
2018-04-25 11:37:22 +00:00
{
2018-08-07 07:17:51 +00:00
nn : : hac : : sRomfsDirEntry * d_node = get_dir_node ( dir_offset ) ;
2018-04-25 11:37:22 +00:00
/*
printf ( " [DIR-NODE] \n " ) ;
printf ( " parent=%08x \n " , d_node - > parent . get ( ) ) ;
printf ( " sibling=%08x \n " , d_node - > sibling . get ( ) ) ;
printf ( " child=%08x \n " , d_node - > child . get ( ) ) ;
printf ( " file=%08x \n " , d_node - > file . get ( ) ) ;
printf ( " hash=%08x \n " , d_node - > hash . get ( ) ) ;
printf ( " name_size=%08x \n " , d_node - > name_size . get ( ) ) ;
printf ( " name=%s \n " , d_node - > name ) ;
*/
2018-08-07 07:17:51 +00:00
for ( uint32_t file_addr = d_node - > file . get ( ) ; file_addr ! = nn : : hac : : romfs : : kInvalidAddr ; )
2018-04-25 11:37:22 +00:00
{
2018-08-07 07:17:51 +00:00
nn : : hac : : sRomfsFileEntry * f_node = get_file_node ( file_addr ) ;
2018-04-25 11:37:22 +00:00
/*
printf ( " [FILE-NODE] \n " ) ;
printf ( " parent=%08x \n " , f_node - > parent . get ( ) ) ;
printf ( " sibling=%08x \n " , f_node - > sibling . get ( ) ) ;
printf ( " offset=%08 " PRIx64 " \n " , f_node - > offset . get ( ) ) ;
printf ( " size=%08 " PRIx64 " \n " , f_node - > size . get ( ) ) ;
printf ( " hash=%08x \n " , f_node - > hash . get ( ) ) ;
printf ( " name_size=%08x \n " , f_node - > name_size . get ( ) ) ;
printf ( " name=%s \n " , f_node - > name ) ;
*/
2021-09-28 11:15:54 +00:00
dir . file_list . push_back ( { std : : string ( f_node - > name ( ) , f_node - > name_size . get ( ) ) , mHdr . data_offset . get ( ) + f_node - > offset . get ( ) , f_node - > size . get ( ) } ) ;
2018-04-25 11:37:22 +00:00
file_addr = f_node - > sibling . get ( ) ;
mFileNum + + ;
}
2018-08-07 07:17:51 +00:00
for ( uint32_t child_addr = d_node - > child . get ( ) ; child_addr ! = nn : : hac : : romfs : : kInvalidAddr ; )
2018-04-25 11:37:22 +00:00
{
2018-08-07 07:17:51 +00:00
nn : : hac : : sRomfsDirEntry * c_node = get_dir_node ( child_addr ) ;
2018-04-25 11:37:22 +00:00
2021-09-28 11:15:54 +00:00
dir . dir_list . push_back ( { std : : string ( c_node - > name ( ) , c_node - > name_size . get ( ) ) } ) ;
2018-04-25 11:37:22 +00:00
importDirectory ( child_addr , dir . dir_list . atBack ( ) ) ;
child_addr = c_node - > sibling . get ( ) ;
mDirNum + + ;
}
}
2021-09-28 11:15:54 +00:00
void nstool : : RomfsProcess : : resolveRomfs ( )
2018-04-25 11:37:22 +00:00
{
2018-09-23 03:29:22 +00:00
if ( * mFile = = nullptr )
2018-08-13 17:14:21 +00:00
{
2021-09-28 11:15:54 +00:00
throw tc : : Exception ( kModuleName , " No file reader set. " ) ;
2018-08-13 17:14:21 +00:00
}
2018-04-25 11:37:22 +00:00
// read header
2018-09-23 03:29:22 +00:00
( * mFile ) - > read ( ( byte_t * ) & mHdr , 0 , sizeof ( nn : : hac : : sRomfsHeader ) ) ;
2018-04-25 11:37:22 +00:00
// logic check on the header layout
if ( validateHeaderLayout ( & mHdr ) = = false )
{
2021-09-28 11:15:54 +00:00
throw tc : : Exception ( kModuleName , " Invalid ROMFS Header " ) ;
2018-04-25 11:37:22 +00:00
}
2020-08-14 13:36:15 +00:00
// check for romfs compression
size_t physical_size = ( * mFile ) - > size ( ) ;
size_t logical_size = mHdr . sections [ nn : : hac : : romfs : : FILE_NODE_TABLE ] . offset . get ( ) + mHdr . sections [ nn : : hac : : romfs : : FILE_NODE_TABLE ] . size . get ( ) ;
// if logical size is greater than the physical size, check for compression meta footer
if ( logical_size > physical_size )
{
// initial and final entries
nn : : hac : : sCompressionEntry entry [ 2 ] ;
2020-08-16 06:11:30 +00:00
// read final compression entry
2020-08-14 13:36:15 +00:00
( * mFile ) - > read ( ( byte_t * ) & entry [ 1 ] , physical_size - sizeof ( nn : : hac : : sCompressionEntry ) , sizeof ( nn : : hac : : sCompressionEntry ) ) ;
2020-08-16 06:11:30 +00:00
// the final compression entry should be for the (final part, in the case of metadata > 0x10000) romfs footer, for which the logical offset is detailed in the romfs header
2020-08-14 13:36:15 +00:00
// the compression is always enabled for non-header compression entries
2020-08-16 06:11:30 +00:00
uint64_t romfs_metadata_begin_offset = mHdr . sections [ nn : : hac : : romfs : : DIR_HASHMAP_TABLE ] . offset . get ( ) ;
uint64_t romfs_metadata_end_offset = mHdr . sections [ nn : : hac : : romfs : : FILE_NODE_TABLE ] . offset . get ( ) + mHdr . sections [ nn : : hac : : romfs : : FILE_NODE_TABLE ] . size . get ( ) ;
if ( ( entry [ 1 ] . virtual_offset . get ( ) > = romfs_metadata_begin_offset & & entry [ 1 ] . virtual_offset . get ( ) < romfs_metadata_end_offset ) = = false | | \
2020-08-14 13:36:15 +00:00
entry [ 1 ] . compression_type ! = ( byte_t ) nn : : hac : : compression : : CompressionType : : Lz4 )
{
2021-09-28 11:15:54 +00:00
throw tc : : Exception ( kModuleName , " RomFs appears corrupted (bad final compression entry virtual offset/compression type) " ) ;
2020-08-14 13:36:15 +00:00
}
// the first compression entry follows the physical placement of the final data chunk (specified in the final compression entry)
size_t first_entry_offset = align ( entry [ 1 ] . physical_offset . get ( ) + entry [ 1 ] . physical_size . get ( ) , nn : : hac : : compression : : kRomfsBlockAlign ) ;
// quick check to make sure the offset at least before the last entry offset
if ( first_entry_offset > = ( physical_size - sizeof ( nn : : hac : : sCompressionEntry ) ) )
{
2021-09-28 11:15:54 +00:00
throw tc : : Exception ( kModuleName , " RomFs appears corrupted (bad final compression entry physical offset/size) " ) ;
2020-08-14 13:36:15 +00:00
}
2020-08-16 06:11:30 +00:00
// read first compression entry
2020-08-14 13:36:15 +00:00
( * mFile ) - > read ( ( byte_t * ) & entry [ 0 ] , first_entry_offset , sizeof ( nn : : hac : : sCompressionEntry ) ) ;
// validate first compression entry
// this should be the same for all compressed romfs
if ( entry [ 0 ] . virtual_offset . get ( ) ! = 0x0 | | \
entry [ 0 ] . physical_offset . get ( ) ! = 0x0 | | \
entry [ 0 ] . physical_size . get ( ) ! = 0x200 | | \
entry [ 0 ] . compression_type ! = ( byte_t ) nn : : hac : : compression : : CompressionType : : None )
{
2021-09-28 11:15:54 +00:00
throw tc : : Exception ( kModuleName , " RomFs appears corrupted (bad first compression entry) " ) ;
2020-08-14 13:36:15 +00:00
}
// wrap mFile in a class to transparantly decompress the image.
mFile = new CompressedArchiveIFile ( mFile , first_entry_offset ) ;
}
2018-04-25 11:37:22 +00:00
// read directory nodes
2018-08-07 07:17:51 +00:00
mDirNodes . alloc ( mHdr . sections [ nn : : hac : : romfs : : DIR_NODE_TABLE ] . size . get ( ) ) ;
2018-09-23 03:29:22 +00:00
( * mFile ) - > read ( mDirNodes . data ( ) , mHdr . sections [ nn : : hac : : romfs : : DIR_NODE_TABLE ] . offset . get ( ) , mDirNodes . size ( ) ) ;
2018-04-25 11:37:22 +00:00
//printf("[RAW DIR NODES]\n");
2018-06-24 15:01:16 +00:00
//fnd::SimpleTextOutput::hxdStyleDump(mDirNodes.data(), mDirNodes.size());
2018-04-25 11:37:22 +00:00
// read file nodes
2018-08-07 07:17:51 +00:00
mFileNodes . alloc ( mHdr . sections [ nn : : hac : : romfs : : FILE_NODE_TABLE ] . size . get ( ) ) ;
2018-09-23 03:29:22 +00:00
( * mFile ) - > read ( mFileNodes . data ( ) , mHdr . sections [ nn : : hac : : romfs : : FILE_NODE_TABLE ] . offset . get ( ) , mFileNodes . size ( ) ) ;
2018-04-25 11:37:22 +00:00
//printf("[RAW FILE NODES]\n");
2018-06-24 15:01:16 +00:00
//fnd::SimpleTextOutput::hxdStyleDump(mFileNodes.data(), mFileNodes.size());
2018-04-25 11:37:22 +00:00
// A logic check on the root directory node
if ( get_dir_node ( 0 ) - > parent . get ( ) ! = 0 \
2018-08-07 07:17:51 +00:00
| | get_dir_node ( 0 ) - > sibling . get ( ) ! = nn : : hac : : romfs : : kInvalidAddr \
| | get_dir_node ( 0 ) - > hash . get ( ) ! = nn : : hac : : romfs : : kInvalidAddr \
2018-04-25 11:37:22 +00:00
| | get_dir_node ( 0 ) - > name_size . get ( ) ! = 0 )
{
2021-09-28 11:15:54 +00:00
throw tc : : Exception ( kModuleName , " Invalid root directory node " ) ;
2018-04-25 11:37:22 +00:00
}
// import directory into internal structure
mDirNum = 0 ;
mFileNum = 0 ;
importDirectory ( 0 , mRootDir ) ;
}