2011-05-28 11:10:50 +02:00
# include "svnrev.h"
2010-12-18 22:07:58 +01:00
# include "../WiiQt/includes.h"
# include "../WiiQt/nandbin.h"
# include "../WiiQt/sharedcontentmap.h"
# include "../WiiQt/uidmap.h"
# include "../WiiQt/tools.h"
# include "../WiiQt/tiktmd.h"
2011-01-19 04:39:39 +01:00
# include "../WiiQt/settingtxtdialog.h"
# include "../WiiQt/u8.h"
2011-01-21 07:21:28 +01:00
# include "../WiiQt/keysbin.h"
2010-12-18 22:07:58 +01:00
//yippie for global variables
2011-01-19 04:39:39 +01:00
QStringList args ;
2010-12-18 22:07:58 +01:00
NandBin nand ;
SharedContentMap sharedM ;
UIDmap uidM ;
QList < quint64 > tids ;
QList < quint64 > validIoses ; //dont put stubs in this list.
QTreeWidgetItem * root ;
QList < quint16 > fats ;
2011-01-15 23:21:43 +01:00
quint32 verbose = 0 ;
2011-01-19 04:39:39 +01:00
bool tryToKeepGoing = false ;
2011-01-20 03:05:18 +01:00
bool color = true ;
2011-01-21 07:21:28 +01:00
bool calcRsa = false ;
2011-01-19 04:39:39 +01:00
QByteArray sysMenuResource ;
2011-01-21 07:21:28 +01:00
QByteArray sysMenuExe ;
quint64 sysMenuIos ;
2011-02-21 17:25:42 +01:00
QList < QByteArray > BadSharedItems ; //remember bad shared items
2011-01-21 07:21:28 +01:00
bool CheckTitleIntegrity ( quint64 tid ) ;
2010-12-18 22:07:58 +01:00
2011-01-20 04:22:30 +01:00
# ifdef Q_WS_WIN
2011-01-21 07:21:28 +01:00
# include <windows.h>
2011-01-20 04:22:30 +01:00
# define C_STICKY 31
# define C_CAP 192
int origColor ;
HANDLE hConsole ;
int GetColor ( )
{
WORD wColor = 0 ;
2011-01-29 09:44:22 +01:00
HANDLE hStdOut = GetStdHandle ( STD_OUTPUT_HANDLE ) ;
2011-01-20 04:22:30 +01:00
CONSOLE_SCREEN_BUFFER_INFO csbi ;
//We use csbi for the wAttributes word.
2011-01-29 09:44:22 +01:00
if ( GetConsoleScreenBufferInfo ( hStdOut , & csbi ) )
2011-01-20 04:22:30 +01:00
{
wColor = csbi . wAttributes ;
}
return wColor ;
}
# else
2011-01-20 03:05:18 +01:00
# define LEN_STR_PAIR(s) sizeof (s) - 1, s
enum indicator_no
{
2011-01-20 04:22:30 +01:00
C_LEFT , C_RIGHT , C_END , C_RESET , C_NORM , C_FILE , C_DIR , C_LINK ,
C_FIFO , C_SOCK ,
C_BLK , C_CHR , C_MISSING , C_ORPHAN , C_EXEC , C_DOOR , C_SETUID , C_SETGID ,
C_STICKY , C_OTHER_WRITABLE , C_STICKY_OTHER_WRITABLE , C_CAP , C_MULTIHARDLINK ,
C_CLR_TO_EOL
2011-01-20 03:05:18 +01:00
} ;
struct bin_str
{
2011-01-20 04:22:30 +01:00
size_t len ; /* Number of bytes */
const char * string ; /* Pointer to the same */
2011-01-20 03:05:18 +01:00
} ;
static struct bin_str color_indicator [ ] =
{
2011-01-20 04:22:30 +01:00
{ LEN_STR_PAIR ( " \033 [ " ) } , /* lc: Left of color sequence */
2011-01-29 09:44:22 +01:00
{ LEN_STR_PAIR ( " m " ) } , /* rc: Right of color sequence */
{ 0 , NULL } , /* ec: End color (replaces lc+no+rc) */
{ LEN_STR_PAIR ( " 0 " ) } , /* rs: Reset to ordinary colors */
{ 0 , NULL } , /* no: Normal */
{ 0 , NULL } , /* fi: File: default */
2011-01-20 04:22:30 +01:00
{ LEN_STR_PAIR ( " 01;34 " ) } , /* di: Directory: bright blue */
{ LEN_STR_PAIR ( " 01;36 " ) } , /* ln: Symlink: bright cyan */
{ LEN_STR_PAIR ( " 33 " ) } , /* pi: Pipe: yellow/brown */
{ LEN_STR_PAIR ( " 01;35 " ) } , /* so: Socket: bright magenta */
{ LEN_STR_PAIR ( " 01;33 " ) } , /* bd: Block device: bright yellow */
{ LEN_STR_PAIR ( " 01;33 " ) } , /* cd: Char device: bright yellow */
2011-01-29 09:44:22 +01:00
{ 0 , NULL } , /* mi: Missing file: undefined */
{ 0 , NULL } , /* or: Orphaned symlink: undefined */
2011-01-20 04:22:30 +01:00
{ LEN_STR_PAIR ( " 01;32 " ) } , /* ex: Executable: bright green */
{ LEN_STR_PAIR ( " 01;35 " ) } , /* do: Door: bright magenta */
{ LEN_STR_PAIR ( " 37;41 " ) } , /* su: setuid: white on red */
{ LEN_STR_PAIR ( " 30;43 " ) } , /* sg: setgid: black on yellow */
{ LEN_STR_PAIR ( " 37;44 " ) } , /* st: sticky: black on blue */
{ LEN_STR_PAIR ( " 34;42 " ) } , /* ow: other-writable: blue on green */
{ LEN_STR_PAIR ( " 30;42 " ) } , /* tw: ow w/ sticky: black on green */
{ LEN_STR_PAIR ( " 30;41 " ) } , /* ca: black on red */
2011-01-29 09:44:22 +01:00
{ 0 , NULL } , /* mh: disabled by default */
2011-01-20 04:22:30 +01:00
{ LEN_STR_PAIR ( " \033 [K " ) } , /* cl: clear to end of line */
2011-01-20 03:05:18 +01:00
} ;
static void put_indicator ( const struct bin_str * ind )
{
2011-01-20 04:22:30 +01:00
fwrite ( ind - > string , ind - > len , 1 , stdout ) ;
2011-01-20 03:05:18 +01:00
}
2011-01-20 04:22:30 +01:00
# endif
2011-01-20 03:05:18 +01:00
void PrintColoredString ( const char * msg , int highlite )
{
2011-01-20 04:22:30 +01:00
if ( ! color )
{
printf ( " %s \n " , msg ) ;
}
else
{
2011-01-29 09:44:22 +01:00
QString str ( msg ) ;
QStringList list = str . split ( " \n " , QString : : SkipEmptyParts ) ;
2011-05-17 23:18:45 +02:00
foreach ( const QString & s , list )
2011-01-29 09:44:22 +01:00
{
QString m = s ;
QString m2 = s . trimmed ( ) ;
m . resize ( m . indexOf ( m2 ) ) ;
printf ( " %s " , m . toLatin1 ( ) . data ( ) ) ; //print all leading whitespace
2011-01-20 04:22:30 +01:00
# ifdef Q_WS_WIN
2011-01-29 09:44:22 +01:00
SetConsoleTextAttribute ( hConsole , highlite ) ;
2011-01-20 04:22:30 +01:00
# else
2011-01-29 09:44:22 +01:00
put_indicator ( & color_indicator [ C_LEFT ] ) ;
put_indicator ( & color_indicator [ highlite ] ) ; //change color
put_indicator ( & color_indicator [ C_RIGHT ] ) ;
2011-01-20 04:22:30 +01:00
# endif
2011-01-29 09:44:22 +01:00
printf ( " %s " , m2 . toLatin1 ( ) . data ( ) ) ; //print text
2011-01-20 04:22:30 +01:00
# ifdef Q_WS_WIN
2011-01-29 09:44:22 +01:00
SetConsoleTextAttribute ( hConsole , origColor ) ;
2011-01-20 04:22:30 +01:00
# else
2011-01-29 09:44:22 +01:00
put_indicator ( & color_indicator [ C_LEFT ] ) ;
put_indicator ( & color_indicator [ C_NORM ] ) ; //reset color
put_indicator ( & color_indicator [ C_RIGHT ] ) ;
2011-01-20 04:22:30 +01:00
# endif
2011-01-29 09:44:22 +01:00
printf ( " \n " ) ;
}
2011-01-20 04:22:30 +01:00
}
fflush ( stdout ) ;
2011-01-20 03:05:18 +01:00
}
//redirect text output. by default, qDebug() goes to stderr
void DebugHandler ( QtMsgType type , const char * msg )
{
2011-01-20 04:22:30 +01:00
switch ( type )
{
case QtDebugMsg :
printf ( " %s \n " , msg ) ;
break ;
2011-01-29 09:44:22 +01:00
case QtWarningMsg :
PrintColoredString ( msg , C_STICKY ) ;
2011-01-20 04:22:30 +01:00
break ;
2011-01-29 09:44:22 +01:00
case QtCriticalMsg :
PrintColoredString ( msg , C_CAP ) ;
2011-01-20 04:22:30 +01:00
break ;
case QtFatalMsg :
2011-01-29 09:44:22 +01:00
fprintf ( stderr , " Fatal Error: %s \n " , msg ) ;
2011-01-20 04:22:30 +01:00
abort ( ) ;
break ;
}
2011-01-20 03:05:18 +01:00
}
2010-12-18 22:07:58 +01:00
void Usage ( )
{
2011-01-20 04:22:30 +01:00
qWarning ( ) < < " usage: " < < QCoreApplication : : arguments ( ) . at ( 0 ) < < " nand.bin " < < " <other options> " ;
2011-05-31 01:39:10 +02:00
qDebug ( ) < < " \n if no <other options> are given, it will default to \" -all -v -v \" " ;
2010-12-18 22:07:58 +01:00
qDebug ( ) < < " \n Other options: " ;
2011-01-20 04:22:30 +01:00
qDebug ( ) < < " -boot shows information about boot 1 and 2 " ;
qDebug ( ) < < " " ;
qDebug ( ) < < " -fs verify the filesystem is in tact " ;
qDebug ( ) < < " verifies presence of uid & content.map & checks the hashes in the content.map " ;
2011-01-29 09:44:22 +01:00
qDebug ( ) < < " check sha1 hashes for title private contents " ;
2011-01-20 04:22:30 +01:00
qDebug ( ) < < " check all titles with a ticket titles for required IOS, proper uid & gid " ;
qDebug ( ) < < " " ;
2011-01-29 09:44:22 +01:00
qDebug ( ) < < " -settingtxt check setting.txt itself and against system menu resources. this must be combined with \" -fs \" " ;
qDebug ( ) < < " " ;
qDebug ( ) < < " -uid Look any titles in the uid.sys, check signatures and whatnot. this must be combined with \" -fs \" " ;
qDebug ( ) < < " " ;
qDebug ( ) < < " -rsa Calculate and compare RSA signatures. this must be combined with \" -fs \" " ;
2011-01-20 04:22:30 +01:00
qDebug ( ) < < " " ;
qDebug ( ) < < " -clInfo shows free, used, and lost ( marked used, but dont belong to any file ) clusters " ;
qDebug ( ) < < " " ;
qDebug ( ) < < " -spare calculate & compare ecc for all pages in the nand " ;
qDebug ( ) < < " calculate & compare hmac signatures for all files and superblocks " ;
qDebug ( ) < < " " ;
qDebug ( ) < < " -all does all of the above " ;
qDebug ( ) < < " " ;
2011-05-17 23:18:45 +02:00
qDebug ( ) < < " -v increase verbosity ( can be used more than once ) " ;
2011-01-20 04:22:30 +01:00
qDebug ( ) < < " " ;
2011-01-29 09:44:22 +01:00
qDebug ( ) < < " -continue try to keep going as fas as possible on errors that should be fatal " ;
qDebug ( ) < < " " ;
qDebug ( ) < < " -nocolor don \' t use terminal color trickery " ;
qDebug ( ) < < " " ;
qDebug ( ) < < " -about info about this program " ;
2010-12-18 22:07:58 +01:00
exit ( 1 ) ;
}
2011-01-21 07:21:28 +01:00
void About ( )
{
2011-01-29 09:44:22 +01:00
qCritical ( ) < < " (c) giantpune 2010, 2011 " ;
qCritical ( ) < < " http://code.google.com/p/wiiqt/ " ;
qCritical ( ) < < " built: " < < __DATE__ < < __TIME__ ;
qWarning ( ) < < " This software is licensed under GPLv2. It comes with no guarentee that it will work, " ;
qWarning ( ) < < " or that it will work well. " ;
qDebug ( ) < < " " ;
qDebug ( ) < < " This program is designed to gather information about a nand dump for a Nintendo Wii " ;
exit ( 1 ) ;
2011-01-21 07:21:28 +01:00
}
2010-12-18 22:07:58 +01:00
void Fail ( const QString & str )
{
2011-01-20 04:22:30 +01:00
qCritical ( ) < < str ;
if ( ! tryToKeepGoing )
exit ( 1 ) ;
2010-12-18 22:07:58 +01:00
}
QString TidTxt ( quint64 tid )
{
return QString ( " %1 " ) . arg ( tid , 16 , 16 , QChar ( ' 0 ' ) ) ;
}
2011-01-19 23:32:13 +01:00
QString AsciiTxt ( quint32 lower )
{
2011-01-20 04:22:30 +01:00
QString ret ;
lower = qFromBigEndian ( lower ) ;
for ( int i = 0 ; i < 4 ; i + + )
ret + = ascii ( ( char ) ( lower > > ( 8 * i ) ) & 0xff ) ;
return ret ;
2011-01-19 23:32:13 +01:00
}
2010-12-18 22:07:58 +01:00
void ShowBootInfo ( quint8 boot1 , QList < Boot2Info > boot2stuff )
{
switch ( boot1 )
{
case BOOT_1_A :
2011-01-02 07:15:26 +01:00
qDebug ( ) < < " Boot1 A (vulnerable) " ;
break ;
2010-12-18 22:07:58 +01:00
case BOOT_1_B :
2011-01-02 07:15:26 +01:00
qDebug ( ) < < " Boot1 B (vulnerable) " ;
break ;
2010-12-18 22:07:58 +01:00
case BOOT_1_C :
2011-01-02 07:15:26 +01:00
qDebug ( ) < < " Boot1 C (fixed) " ;
break ;
2010-12-18 22:07:58 +01:00
case BOOT_1_D :
2011-01-02 07:15:26 +01:00
qDebug ( ) < < " Boot1 D (fixed) " ;
break ;
2010-12-18 22:07:58 +01:00
default :
2011-01-29 09:44:22 +01:00
qWarning ( ) < < " unrecognized boot1 version " ;
2011-01-02 07:15:26 +01:00
break ;
2010-12-18 22:07:58 +01:00
}
quint16 cnt = boot2stuff . size ( ) ;
if ( ! cnt )
2011-01-02 07:15:26 +01:00
Fail ( " didnt find any boot2. this nand wont work " ) ;
2010-12-18 22:07:58 +01:00
qDebug ( ) < < " found " < < cnt < < " copies of boot2 " ;
for ( quint16 i = 0 ; i < cnt ; i + + )
{
2011-01-02 07:15:26 +01:00
Boot2Info bi = boot2stuff . at ( i ) ;
QString str = QString ( " blocks %1 & %2: " ) . arg ( bi . firstBlock ) . arg ( bi . secondBlock ) ;
if ( bi . state = = BOOT_2_ERROR_PARSING | | bi . state = = BOOT_2_ERROR )
str + = " parsing error " ;
2011-01-20 04:22:30 +01:00
else if ( bi . state = = BOOT_2_BAD_SIGNATURE )
2011-01-02 07:15:26 +01:00
str + = " Bad RSA Signature " ;
2011-01-20 04:22:30 +01:00
else if ( bi . state = = BOOT_2_BAD_CONTENT_HASH )
2011-01-02 07:15:26 +01:00
str + = " Content hash doesn't match TMD " ;
2011-01-20 04:22:30 +01:00
else if ( bi . state = = BOOT_2_ERROR_PARSING )
2011-01-02 07:15:26 +01:00
str + = " Error parsing boot2 " ;
2011-01-20 04:22:30 +01:00
else
{
2010-12-18 22:07:58 +01:00
2011-01-02 07:15:26 +01:00
if ( bi . state & BOOT_2_MARKED_BAD )
str + = " Marked as bad blocks; " ;
else if ( bi . state & BOOT_2_USED_TO_BOOT )
{
str + = " Used for booting; " ;
if ( boot1 ! = BOOT_1_A & & boot1 ! = BOOT_1_B
& & ( ( bi . state & BOOT_2_TIK_FAKESIGNED ) | |
( bi . state & BOOT_2_TMD_FAKESIGNED ) ) )
Fail ( " The version of boot1 installed doesn't have the strncmp bug and the copy of boot2 used for booting is fakesigned " ) ;
}
else if ( bi . state & BOOT_2_BACKUP_COPY )
str + = " Backup copy; " ;
str + = " Content Sha1 matches TMD; " ;
if ( bi . state & BOOT_2_TMD_FAKESIGNED )
str + = " TMD is fakesigned; " ;
else if ( bi . state & BOOT_2_TMD_SIG_OK )
str + = " TMD officially signed; " ;
else
str + = " Error checking TMD; " ;
if ( bi . state & BOOT_2_TIK_FAKESIGNED )
str + = " Ticket is fakesigned; " ;
else if ( bi . state & BOOT_2_TIK_SIG_OK )
str + = " Ticket officially signed; " ;
else
str + = " Error checking ticket; " ;
QString ver ;
switch ( bi . version )
{
case BOOTMII_11 :
ver = " BootMii 1.1 " ;
break ;
case BOOTMII_13 :
ver = " BootMii 1.3 " ;
break ;
case BOOTMII_UNK :
ver = " BootMii (Unk) " ;
break ;
default :
ver = QString ( " Version %1 " ) . arg ( bi . version ) ;
break ;
}
str + = ver ;
2011-01-20 04:22:30 +01:00
}
qDebug ( ) < < str ;
2010-12-18 22:07:58 +01:00
}
}
QTreeWidgetItem * FindItem ( const QString & s , QTreeWidgetItem * parent )
{
int cnt = parent - > childCount ( ) ;
for ( int i = 0 ; i < cnt ; i + + )
{
2011-01-02 07:15:26 +01:00
QTreeWidgetItem * r = parent - > child ( i ) ;
if ( r - > text ( 0 ) = = s )
{
return r ;
}
2010-12-18 22:07:58 +01:00
}
return NULL ;
}
QTreeWidgetItem * ItemFromPath ( const QString & path )
{
QTreeWidgetItem * item = root ;
if ( ! path . startsWith ( " / " ) | | path . contains ( " // " ) )
{
2011-01-02 07:15:26 +01:00
return NULL ;
2010-12-18 22:07:58 +01:00
}
int slash = 1 ;
while ( slash )
{
2011-01-02 07:15:26 +01:00
int nextSlash = path . indexOf ( " / " , slash + 1 ) ;
QString lookingFor = path . mid ( slash , nextSlash - slash ) ;
item = FindItem ( lookingFor , item ) ;
if ( ! item )
{
2011-01-29 09:44:22 +01:00
//if( verbose )
// qWarning() << "ItemFromPath ->item not found" << path;
2011-01-02 07:15:26 +01:00
return NULL ;
}
slash = nextSlash + 1 ;
2010-12-18 22:07:58 +01:00
}
return item ;
}
QString PathFromItem ( QTreeWidgetItem * item )
{
QString ret ;
while ( item )
{
2011-01-02 07:15:26 +01:00
ret . prepend ( " / " + item - > text ( 0 ) ) ;
item = item - > parent ( ) ;
if ( item - > text ( 0 ) = = " / " ) // dont add the root
break ;
2010-12-18 22:07:58 +01:00
}
return ret ;
}
//get the installed titles in the nand - first check for tickets and then check for TMDs that match that ticket
QList < quint64 > InstalledTitles ( )
{
QList < quint64 > ret ;
QTreeWidgetItem * tikFolder = ItemFromPath ( " /ticket " ) ;
if ( ! tikFolder )
2011-01-02 07:15:26 +01:00
Fail ( " Couldnt find a ticket folder in this nand " ) ;
2010-12-18 22:07:58 +01:00
quint16 subfc = tikFolder - > childCount ( ) ;
for ( quint16 i = 0 ; i < subfc ; i + + ) //check all subfolders of "/ticket"
{
2011-01-02 07:15:26 +01:00
QTreeWidgetItem * subF = tikFolder - > child ( i ) ;
2011-01-20 04:22:30 +01:00
//qDebug() << "checking folder" << subF->text( 0 );
2011-01-02 07:15:26 +01:00
bool ok = false ;
2011-01-20 04:22:30 +01:00
quint32 upper = subF - > text ( 0 ) . toUInt ( & ok , 16 ) ; //make sure it can be converted to int
2011-01-02 07:15:26 +01:00
if ( ! ok )
continue ;
quint16 subfc2 = subF - > childCount ( ) ; //get all entries in this subfolder
for ( quint16 j = 0 ; j < subfc2 ; j + + )
{
QTreeWidgetItem * tikI = subF - > child ( j ) ;
2011-01-29 09:44:22 +01:00
QString name = tikI - > text ( 0 ) ;
2011-01-02 07:15:26 +01:00
if ( ! name . endsWith ( " .tik " ) )
2011-01-29 09:44:22 +01:00
{
2011-01-02 07:15:26 +01:00
continue ;
}
name . resize ( 8 ) ;
2011-01-20 04:22:30 +01:00
quint32 lower = name . toUInt ( & ok , 16 ) ;
2011-01-02 07:15:26 +01:00
if ( ! ok )
2011-01-29 09:44:22 +01:00
{
2011-01-02 07:15:26 +01:00
continue ;
}
//now see if there is a tmd
QTreeWidgetItem * tmdI = ItemFromPath ( " /title/ " + subF - > text ( 0 ) + " / " + name + " /content/title.tmd " ) ;
if ( ! tmdI )
2011-01-29 09:44:22 +01:00
{
2011-01-02 07:15:26 +01:00
continue ;
}
2011-01-29 09:44:22 +01:00
quint64 tid = ( ( ( quint64 ) upper < < 32 ) | lower ) ;
2011-01-02 07:15:26 +01:00
ret < < tid ;
}
2010-12-18 22:07:58 +01:00
}
2011-01-20 04:22:30 +01:00
qSort ( ret . begin ( ) , ret . end ( ) ) ;
2010-12-18 22:07:58 +01:00
return ret ;
}
void CheckShared ( )
{
QByteArray ba = nand . GetData ( " /shared1/content.map " ) ;
if ( ba . isEmpty ( ) )
2011-01-02 07:15:26 +01:00
Fail ( " No content map found in the nand " ) ;
2010-12-18 22:07:58 +01:00
sharedM = SharedContentMap ( ba ) ;
quint16 cnt = sharedM . Count ( ) ;
for ( quint16 i = 0 ; i < cnt ; i + + )
{
2011-01-02 07:15:26 +01:00
QString path = " /shared1/ " + sharedM . Cid ( i ) + " .app " ;
qDebug ( ) < < " checking " < < path < < " ... " ;
QByteArray stuff = nand . GetData ( path ) ;
if ( stuff . isEmpty ( ) )
2011-05-17 23:18:45 +02:00
{
BadSharedItems < < sharedM . Hash ( i ) ;
2011-01-02 07:15:26 +01:00
Fail ( " One of the shared contents in this nand is missing " ) ;
2011-05-17 23:18:45 +02:00
}
2011-01-02 07:15:26 +01:00
QByteArray realHash = GetSha1 ( stuff ) ;
if ( realHash ! = sharedM . Hash ( i ) )
2011-05-17 23:18:45 +02:00
{
BadSharedItems < < sharedM . Hash ( i ) ;
if ( verbose )
{
qCritical ( ) < < " \t expected: " < < sharedM . Hash ( i ) . toHex ( ) ;
qCritical ( ) < < " \t actual: " < < realHash . toHex ( ) ;
}
2011-01-02 07:15:26 +01:00
Fail ( " The hash for at least 1 content is bad " ) ;
2011-05-17 23:18:45 +02:00
}
2010-12-18 22:07:58 +01:00
}
}
void BuildGoodIosList ( )
{
foreach ( quint64 tid , tids )
{
2011-01-02 07:15:26 +01:00
quint32 upper = ( tid > > 32 ) & 0xffffffff ;
if ( upper ! = 1 )
continue ;
quint32 lower = tid & 0xffffffff ;
if ( lower < 3 | | lower > 255 )
continue ;
QString tmdp = TidTxt ( tid ) ;
tmdp . insert ( 8 , " / " ) ;
tmdp . prepend ( " /title/ " ) ;
tmdp + = " /content/title.tmd " ;
QByteArray ba = nand . GetData ( tmdp ) ;
if ( ba . isEmpty ( ) )
continue ;
2011-01-29 09:44:22 +01:00
Tmd t ( ba ) ; //skip stubbzzzzz
if ( ! ( t . Version ( ) % 0x100 ) & & //version is a nice pretty round number
t . Count ( ) = = 3 & & //3 contents, 1 private and 2 shared
2011-01-02 07:15:26 +01:00
t . Type ( 0 ) = = 1 & &
t . Type ( 1 ) = = 0x8001 & &
t . Type ( 2 ) = = 0x8001 )
{
continue ;
}
if ( ! CheckTitleIntegrity ( tid ) )
continue ;
2011-01-29 09:44:22 +01:00
validIoses < < tid ; //seems good enough. add it to the list
2010-12-18 22:07:58 +01:00
}
}
2011-01-13 17:43:49 +01:00
bool RecurseCheckGidUid ( QTreeWidgetItem * item , const QString & uidS , const QString & gidS , const QString & path )
{
2011-01-20 04:22:30 +01:00
bool ret = true ;
quint16 cnt = item - > childCount ( ) ;
for ( quint16 i = 0 ; i < cnt ; i + + )
{
QTreeWidgetItem * child = item - > child ( i ) ;
if ( child - > text ( 3 ) ! = uidS | | ! child - > text ( 4 ) . startsWith ( gidS ) )
{
ret = false ;
qWarning ( ) < < " \t incorrect uid/gid for " < < QString ( path + child - > text ( 0 ) ) ;
}
if ( ! RecurseCheckGidUid ( child , uidS , gidS , path + child - > text ( 0 ) + " / " ) )
ret = false ;
}
return ret ;
2011-01-13 17:43:49 +01:00
}
2011-01-19 04:39:39 +01:00
void PrintName ( const QByteArray & app )
{
2011-01-20 04:22:30 +01:00
QString desc ;
if ( app . size ( ) = = 0x40 ) //tag for IOS, region select, ...
{
desc = QString ( app ) . simplified ( ) ;
QString desc2 = QString ( app . right ( 0x10 ) ) ;
if ( ! desc2 . isEmpty ( ) )
desc + = " " + desc2 ;
}
else if ( U8 : : GetU8Offset ( app ) > = 0 ) //maybe this is an IMET header. try to get a name from it
{
U8 u8 ( app ) ;
if ( u8 . IsOK ( ) )
{
QStringList names = u8 . IMETNames ( ) ;
quint8 cnt = names . size ( ) ;
if ( cnt > = 2 ) //try to use english name first
desc = names . at ( 1 ) ;
for ( quint8 i = 0 ; i < cnt & & desc . isEmpty ( ) ; i + + )
{
desc = names . at ( i ) ;
}
}
}
if ( desc . isEmpty ( ) )
{
qDebug ( ) < < " \t Unable to get title " ;
return ;
}
qDebug ( ) < < " \t name: " < < desc ;
2011-01-19 04:39:39 +01:00
}
2011-01-21 07:21:28 +01:00
bool CheckArm003 ( const QByteArray & stuff )
{
2011-01-29 09:44:22 +01:00
qint32 esTag = MAX ( stuff . indexOf ( " $IOSVersion: ES " ) , stuff . indexOf ( " $IOSVersion: ES " ) ) ;
if ( esTag < 0 )
{
qWarning ( ) < < " \t Failed to find the ES module in the kernel " ;
return false ;
}
if ( stuff . contains ( QByteArray : : fromHex ( " e2511cb1d7fbbef8d7f97db5a1d81694 " ) ) //1337 buffer #1
| | stuff . contains ( QByteArray : : fromHex ( " 3f5b8cc9ea855a0afa7347d23e8d664e " ) ) //1337 buffer #2
| | stuff . contains ( QByteArray : : fromHex ( " b570b08868851c01310c22c0005218ab681b2b00d10248bff001f852680b2b45 " ) ) ) //blablabla, CMP R3, #0x45
{
if ( verbose > 1 )
qWarning ( ) < < " \t System menu IOS supports the Korean-key check " ;
return true ;
}
if ( verbose > 1 )
qDebug ( ) < < " \t System menu IOS does not appear to support the Korean-key check " ;
return false ;
2011-01-21 07:21:28 +01:00
}
void Check003 ( )
{
2011-01-29 09:44:22 +01:00
qDebug ( ) < < " Checking for 003 error ... " ;
if ( sysMenuExe . isEmpty ( ) )
{
qWarning ( ) < < " can \' t check 003 error for empty data " ;
return ;
}
bool brick = true ;
//check the PPC half
if ( ! sysMenuExe . contains ( QByteArray : : fromHex ( " 3880004538A0000038C00000 " ) ) ) //li %r4, 0x45
{ //li %r5, 0
brick = false ; //li %r6, 0
if ( verbose > 1 )
qDebug ( ) < < " \t The system menu doesn \' t appear to perform the Korean-key check " ;
}
else if ( verbose > 1 )
qWarning ( ) < < " \t The system menu performs the Korean-key check " ;
//check the ARM half
QString iosStr = TidTxt ( sysMenuIos ) ;
iosStr . insert ( 8 , " / " ) ;
iosStr . prepend ( " /title/ " ) ;
iosStr + = " /content/ " ;
QByteArray tmdD = nand . GetData ( iosStr + " title.tmd " ) ;
if ( tmdD . isEmpty ( ) )
{
qWarning ( ) < < " \t can \' t read systemmenu-ios's TMD " ;
return ;
}
Tmd t ( tmdD ) ;
QByteArray iosKernel ;
QString kernelPath ;
quint16 cnt = t . Count ( ) ;
if ( t . BootIndex ( ) > = cnt ) //in case there is some really fucked up TMD
{
Fail ( " \t The system menu ios bootindex is fucked up pretty bad " ) ;
return ;
}
if ( t . Type ( t . BootIndex ( ) ) = = 0x8001 )
{
QString appname = sharedM . GetAppFromHash ( t . Hash ( t . BootIndex ( ) ) ) ;
if ( appname . isEmpty ( ) )
{
Fail ( " \t Error reading the system menu ios " ) ;
return ;
}
kernelPath = " /shared1/ " + appname + " .app " ;
}
else
{
kernelPath = iosStr + t . Cid ( t . BootIndex ( ) ) + " .app " ;
}
iosKernel = nand . GetData ( kernelPath ) ;
if ( iosKernel . isEmpty ( ) )
{
Fail ( " \t Error reading the system menu ios data " ) ;
return ;
}
if ( ! CheckArm003 ( iosKernel ) )
brick = false ;
//look for korean keys in keys.bin
QByteArray keys = nand . Keys ( ) ;
if ( keys . size ( ) ! = 0x400 )
{
Fail ( " \t Error getting nand keys " ) ;
return ;
}
quint8 k_key [ 16 ] = KOREAN_KEY ;
if ( ! keys . contains ( QByteArray ( ( const char * ) & k_key , 16 ) ) )
{
brick = false ;
if ( verbose > 1 )
qDebug ( ) < < " \t The korean key is not present in this wii " ;
}
else if ( verbose > 1 )
qWarning ( ) < < " \t This wii contains the korean key " ;
if ( brick )
Fail ( " \t This wii will likely show the 003 error " ) ;
2011-01-21 07:21:28 +01:00
}
2010-12-18 22:07:58 +01:00
bool CheckTitleIntegrity ( quint64 tid )
{
if ( validIoses . contains ( tid ) ) //this one has already been checked
2011-01-20 04:22:30 +01:00
return true ;
quint32 upper = ( ( tid > > 32 ) & 0xffffffff ) ;
quint32 lower = ( tid & 0xffffffff ) ;
2010-12-19 23:24:54 +01:00
2011-01-20 04:22:30 +01:00
if ( verbose )
qDebug ( ) < < " " ;
qDebug ( ) < < " Checking " < < qPrintable ( TidTxt ( tid ) . insert ( 8 , " - " ) + ( upper ! = 1 ? " ( " + AsciiTxt ( lower ) + " ) " : " " ) ) < < " ... " ;
2010-12-18 22:07:58 +01:00
QString p = TidTxt ( tid ) ;
p . insert ( 8 , " / " ) ;
QString tikp = p ;
tikp . prepend ( " /ticket/ " ) ;
tikp + = " .tik " ;
QString tmdp = p ;
tmdp . prepend ( " /title/ " ) ;
tmdp + = " /content/title.tmd " ;
Tmd t ;
//check the presence of the tmd & ticket. also check their signatures
//remember the tmd for checking the actual contents
for ( quint8 i = 0 ; i < 2 ; i + + )
{
2011-01-02 07:15:26 +01:00
QString it = ( i ? " tmd " : " ticket " ) ;
QByteArray ba = nand . GetData ( i ? tmdp : tikp ) ;
if ( ba . isEmpty ( ) )
{
qDebug ( ) < < " error getting " < < it < < " data " ;
return false ;
}
2011-05-17 23:18:45 +02:00
if ( i ) //tmd
{
t = Tmd ( ba ) ;
if ( t . Tid ( ) ! = tid )
{
qWarning ( ) < < " \t the TMD contains the wrong TID " ;
return false ;
}
if ( calcRsa )
{
qint32 ch = check_cert_chain ( ba ) ;
switch ( ch )
{
case ERROR_SIG_TYPE :
case ERROR_SUB_TYPE :
case ERROR_RSA_HASH :
case ERROR_RSA_TYPE_UNKNOWN :
case ERROR_RSA_TYPE_MISMATCH :
case ERROR_CERT_NOT_FOUND :
qWarning ( ) . nospace ( ) < < " \t " < < qPrintable ( it ) < < " RSA signature isn't even close ( " < < ch < < " ) " ;
//return false; //maye in the future this will be true, but for now, this doesnt mean it wont boot
break ;
case ERROR_RSA_FAKESIGNED :
qWarning ( ) . nospace ( ) < < " \t " < < qPrintable ( it ) < < " fakesigned " ;
break ;
default :
break ;
}
}
}
else
{
Ticket ticket ( ba , false ) ;
if ( ticket . Tid ( ) ! = tid )
{
qWarning ( ) < < " \t the ticket contains the wrong TID " ;
return false ;
}
if ( calcRsa )
{
int tikVersions = ba . size ( ) / 0x2a4 ;
qint32 ch = ERROR_RSA_TYPE_UNKNOWN ;
bool ok = false ;
for ( int rr = 0 ; rr < tikVersions & & ! ok ; rr + + )
{
ch = check_cert_chain ( ba . mid ( rr * 0x2a4 , 0x2a4 ) ) ;
switch ( ch )
{
default :
break ;
case ERROR_RSA_FAKESIGNED :
case ERROR_SUCCESS :
ok = true ;
break ;
}
}
switch ( ch )
{
case ERROR_SIG_TYPE :
case ERROR_SUB_TYPE :
case ERROR_RSA_HASH :
case ERROR_RSA_TYPE_UNKNOWN :
case ERROR_RSA_TYPE_MISMATCH :
case ERROR_CERT_NOT_FOUND :
qWarning ( ) . nospace ( ) < < " \t " < < qPrintable ( it ) < < " RSA signature isn't even close ( " < < ch < < " ) " ;
//return false; //maye in the future this will be true, but for now, this doesnt mean it wont boot
break ;
case ERROR_RSA_FAKESIGNED :
qWarning ( ) . nospace ( ) < < " \t " < < qPrintable ( it ) < < " fakesigned " ;
break ;
default :
break ;
}
}
}
2010-12-18 22:07:58 +01:00
}
2011-01-20 04:22:30 +01:00
if ( upper = = 0x10005 | | upper = = 0x10007 ) //dont try to verify all the contents of DLC, it will just find a bunch of missing contents and bitch about them
2011-01-02 07:15:26 +01:00
return true ;
2010-12-19 23:24:54 +01:00
2010-12-18 22:07:58 +01:00
quint16 cnt = t . Count ( ) ;
for ( quint16 i = 0 ; i < cnt ; i + + )
{
2011-01-02 07:15:26 +01:00
if ( t . Type ( i ) = = 0x8001 ) //shared
{
2011-05-17 23:18:45 +02:00
if ( BadSharedItems . contains ( t . Hash ( i ) ) )
{
qWarning ( ) < < " \t this title relies on a shared content that is borked ( " < < i < < " ) \n \t " < < t . Hash ( i ) . toHex ( ) ;
return false ;
}
2011-01-02 07:15:26 +01:00
if ( sharedM . GetAppFromHash ( t . Hash ( i ) ) . isEmpty ( ) )
{
2011-01-20 04:22:30 +01:00
qWarning ( ) < < " \t one of the shared contents is missing " ;
2011-01-02 07:15:26 +01:00
return false ;
2011-01-29 09:44:22 +01:00
}
2011-01-02 07:15:26 +01:00
}
else //private
{
QString cid = t . Cid ( i ) ;
QString pA = tmdp ;
pA . resize ( 33 ) ;
pA + = cid + " .app " ;
QByteArray ba = nand . GetData ( pA ) ;
if ( ba . isEmpty ( ) )
{
2011-01-20 04:22:30 +01:00
qWarning ( ) < < " \t error reading one of the private contents " < < pA ;
2011-01-02 07:15:26 +01:00
return false ;
}
QByteArray realH = GetSha1 ( ba ) ;
if ( realH ! = t . Hash ( i ) )
{
2011-01-20 04:22:30 +01:00
qWarning ( ) < < " \t one of the private contents' hash doesnt check out " < < i < < pA < <
" \n \t expected " < < qPrintable ( QString ( t . Hash ( i ) . toHex ( ) ) ) < <
" \n \t actual " < < qPrintable ( QString ( realH . toHex ( ) ) ) ;
2011-01-02 07:15:26 +01:00
//return false; //dont return false, as this this title may still boot
2011-01-20 04:22:30 +01:00
}
//if we are going to check the setting.txt stuff, we need to get the system menu resource file to compare ( check for opera bricks )
//so far, i think this file is always boot index 1, type 1
2011-01-29 09:44:22 +01:00
if ( tid = = 0x100000002ull )
{
if ( t . Index ( i ) = = 1 & &
( args . contains ( " -settingtxt " , Qt : : CaseInsensitive ) | | args . contains ( " -all " , Qt : : CaseInsensitive ) ) )
{
sysMenuResource = ba ;
}
else if ( t . Index ( i ) = = t . BootIndex ( ) )
sysMenuExe = ba ;
}
2011-01-20 04:22:30 +01:00
//print a description of this title
2011-01-29 09:44:22 +01:00
if ( verbose > 1 & & t . Index ( i ) = = 0 )
2011-01-20 04:22:30 +01:00
{
PrintName ( ba ) ;
2011-01-29 09:44:22 +01:00
}
2011-01-02 07:15:26 +01:00
}
2010-12-18 22:07:58 +01:00
}
2011-01-20 04:22:30 +01:00
//print version
if ( verbose )
{
quint16 vers = t . Version ( ) ;
qDebug ( ) < < " \t version: " < < qPrintable ( QString ( " %1.%2 " ) . arg ( ( vers > > 8 ) & 0xff ) . arg ( vers & 0xff ) . leftJustified ( 10 ) )
< < qPrintable ( QString ( " %1 " ) . arg ( vers ) . leftJustified ( 10 ) )
< < " hex: " < < hex < < t . Version ( ) ;
if ( t . AccessFlags ( ) )
qDebug ( ) < < " \t access : " < < hex < < t . AccessFlags ( ) ;
}
2011-01-19 04:39:39 +01:00
2010-12-18 22:07:58 +01:00
quint64 ios = t . IOS ( ) ;
if ( ios & & ! validIoses . contains ( ios ) )
{
2011-01-20 04:22:30 +01:00
qWarning ( ) < < " \t the IOS for this title is not bootable \n \t " < < TidTxt ( ios ) . insert ( 8 , " - " ) ;
2011-01-02 07:15:26 +01:00
return false ;
2010-12-18 22:07:58 +01:00
}
2011-01-29 09:44:22 +01:00
if ( tid = = 0x100000002ull )
sysMenuIos = ios ;
2011-01-19 04:39:39 +01:00
2011-01-29 09:44:22 +01:00
if ( verbose > 1 & & ( upper ! = 1 | | tid = = 0x100000002ull ) )
qDebug ( ) < < " \t requires IOS " < < ( ( quint32 ) ( ios & 0xffffffff ) ) ;
2010-12-18 22:07:58 +01:00
quint32 uid = uidM . GetUid ( tid , false ) ;
if ( ! uid )
{
2011-01-02 07:15:26 +01:00
qDebug ( ) < < " \t this title has no UID entry " ;
return false ;
2010-12-18 22:07:58 +01:00
}
2010-12-19 23:24:54 +01:00
//make sure all the stuff in this title's data directory belongs to it, and all the contents belong to nobody
//( im looking at you priibricker & dop-mii )
2010-12-18 22:07:58 +01:00
QString dataP = tmdp ;
dataP . resize ( 25 ) ;
dataP + = " data " ;
QTreeWidgetItem * dataI = ItemFromPath ( dataP ) ;
if ( dataI )
{
2011-01-02 07:15:26 +01:00
quint16 gid = t . Gid ( ) ;
QString uidS = QString ( " %1 " ) . arg ( uid , 8 , 16 , QChar ( ' 0 ' ) ) ;
QString gidS = QString ( " %1 " ) . arg ( gid , 4 , 16 , QChar ( ' 0 ' ) ) ;
if ( dataI - > text ( 3 ) ! = uidS | | ! dataI - > text ( 4 ) . startsWith ( gidS ) ) //dont necessarily fail for this. the title will still be bootable without its data
2011-05-17 23:18:45 +02:00
qWarning ( ) . nospace ( ) < < " \t incorrect uid/gid for data folder-- expected: " < < uidS < < " / " < < gidS < < " got: " < < dataI - > text ( 3 ) < < " / " < < dataI - > text ( 4 ) . left ( 4 ) ;
2011-01-13 17:43:49 +01:00
2011-01-29 09:44:22 +01:00
RecurseCheckGidUid ( dataI , uidS , gidS , " data/ " ) ;
2010-12-19 23:24:54 +01:00
}
dataP . resize ( 25 ) ;
dataP + = " content " ;
dataI = ItemFromPath ( dataP ) ;
if ( dataI )
{
2011-01-02 07:15:26 +01:00
quint16 cnt = dataI - > childCount ( ) ;
for ( quint16 i = 0 ; i < cnt ; i + + )
{
QTreeWidgetItem * item = dataI - > child ( i ) ;
if ( item - > text ( 3 ) ! = " 00000000 " | | ! item - > text ( 4 ) . startsWith ( " 0000 " ) )
2011-01-20 04:22:30 +01:00
qWarning ( ) < < " \t incorrect uid/gid for " < < QString ( " content/ " + item - > text ( 0 ) ) ;
2011-01-02 07:15:26 +01:00
}
2010-12-18 22:07:58 +01:00
}
return true ;
}
2011-01-19 23:32:13 +01:00
void ListDeletedTitles ( )
{
2011-01-20 04:22:30 +01:00
QByteArray uidSys = uidM . Data ( ) ;
if ( uidSys . isEmpty ( ) )
{
qDebug ( ) < < " Can \' t check deleted titles without a uid.sys " ;
return ;
}
qDebug ( ) < < " Comparing uid.sys against the filesystem... " ;
2011-01-29 09:44:22 +01:00
//hexdump12( uidSys );
2011-01-20 04:22:30 +01:00
QBuffer buf ( & uidSys ) ;
buf . open ( QIODevice : : ReadWrite ) ;
quint64 tid ;
quint16 factory = 0 ;
quint32 cnt = uidSys . size ( ) / 12 ;
//skip past items installed at the factory
for ( quint32 i = 0 ; i < cnt ; i + + )
{
buf . seek ( i * 12 ) ;
buf . read ( ( char * ) & tid , 8 ) ;
tid = qFromBigEndian ( tid ) ;
quint32 upper = ( ( tid > > 32 ) & 0xffffffff ) ;
quint32 lower = ( tid & 0xffffffff ) ;
2011-01-29 09:44:22 +01:00
if ( ( upper = = 0x10001 & & ( ( lower > > 24 ) & 0xff ) ! = 0x48 ) | | //a channel, not starting with 'H'
lower = = 0x48415858 | | //original HBC
tid = = 0x100000000ull | | //bannerbomb -> ATD ( or any other program that uses the SU tid )
2011-01-20 04:22:30 +01:00
( upper = = 0x10000 & & ( ( lower & 0xffffff00 ) = = 0x555000 ) ) ) //a disc update partition
break ;
if ( ( verbose | | upper ! = 0x10000 ) & & ! tids . contains ( tid ) )
qDebug ( ) . nospace ( ) < < " \t " < < qPrintable ( TidTxt ( tid ) . insert ( 8 , " - " ) + ( upper ! = 1 ? " ( " + AsciiTxt ( lower ) + " ) " : " " ) ) < < " was installed at the factory and is now missing " ;
factory + + ;
}
if ( verbose )
qDebug ( ) < < factory < < " titles were installed before any user intervention " ;
for ( quint32 i = factory ; i < cnt ; i + + )
{
buf . seek ( i * 12 ) ;
buf . read ( ( char * ) & tid , 8 ) ;
tid = qFromBigEndian ( tid ) ;
if ( tids . contains ( tid ) ) //this one is already checked
continue ;
quint32 upper = ( ( tid > > 32 ) & 0xffffffff ) ;
quint32 lower = ( tid & 0xffffffff ) ;
bool deleted = false ;
//load tmd
QString path = QString ( " /title/%1/%2/content/title.tmd " ) . arg ( upper , 8 , 16 , QChar ( ' 0 ' ) ) . arg ( lower , 8 , 16 , QChar ( ' 0 ' ) ) ;
QTreeWidgetItem * item = ItemFromPath ( path ) ;
if ( ! item )
{
if ( verbose > 1 )
qWarning ( ) < < " \t Can \' t find TMD for " < < qPrintable ( TidTxt ( tid ) . insert ( 8 , " - " ) + ( upper ! = 1 ? " ( " + AsciiTxt ( lower ) + " ) " : " " ) ) ;
deleted = true ;
}
else
{
QByteArray ba = nand . GetData ( path ) ;
if ( ba . isEmpty ( ) )
{
if ( verbose > 1 )
qWarning ( ) < < " \t Error reading TMD for " < < qPrintable ( TidTxt ( tid ) . insert ( 8 , " - " ) + ( upper ! = 1 ? " ( " + AsciiTxt ( lower ) + " ) " : " " ) ) ;
deleted = true ;
}
2011-01-29 09:44:22 +01:00
else if ( calcRsa )
2011-01-20 04:22:30 +01:00
{
qint32 ch = check_cert_chain ( ba ) ;
switch ( ch )
{
case ERROR_SIG_TYPE :
case ERROR_SUB_TYPE :
case ERROR_RSA_HASH :
case ERROR_RSA_TYPE_UNKNOWN :
case ERROR_RSA_TYPE_MISMATCH :
case ERROR_CERT_NOT_FOUND :
qWarning ( ) . nospace ( ) < < " \t TMD RSA signature isn't even close for " < < qPrintable ( TidTxt ( tid ) . insert ( 8 , " - " ) ) < < " ( " < < ch < < " ) " ;
break ;
case ERROR_RSA_FAKESIGNED :
qWarning ( ) . nospace ( ) < < " \t TMD for " < < qPrintable ( TidTxt ( tid ) . insert ( 8 , " - " ) + ( upper ! = 1 ? " ( " + AsciiTxt ( lower ) + " ) " : " " ) ) < < " is fakesigned " ;
break ;
default :
break ;
}
}
}
if ( upper ! = 0x10000 ) //dont try to load ticket for disc games
{
//load ticket
path = QString ( " /ticket/%1/%2.tik " ) . arg ( upper , 8 , 16 , QChar ( ' 0 ' ) ) . arg ( lower , 8 , 16 , QChar ( ' 0 ' ) ) ;
item = ItemFromPath ( path ) ;
if ( ! item )
{
if ( verbose > 1 )
qWarning ( ) < < " \t Can \' t find ticket for " < < qPrintable ( TidTxt ( tid ) . insert ( 8 , " - " ) + ( upper ! = 1 ? " ( " + AsciiTxt ( lower ) + " ) " : " " ) ) ;
deleted = true ;
}
else
{
QByteArray ba = nand . GetData ( path ) ;
if ( ba . isEmpty ( ) )
{
if ( verbose > 1 )
qWarning ( ) < < " \t Error reading ticket for " < < qPrintable ( TidTxt ( tid ) . insert ( 8 , " - " ) + ( upper ! = 1 ? " ( " + AsciiTxt ( lower ) + " ) " : " " ) ) ;
deleted = true ;
}
2011-01-29 09:44:22 +01:00
else if ( calcRsa )
2011-01-20 04:22:30 +01:00
{
qint32 ch = check_cert_chain ( ba ) ;
switch ( ch )
{
case ERROR_SIG_TYPE :
case ERROR_SUB_TYPE :
case ERROR_RSA_HASH :
case ERROR_RSA_TYPE_UNKNOWN :
case ERROR_RSA_TYPE_MISMATCH :
case ERROR_CERT_NOT_FOUND :
qWarning ( ) . nospace ( ) < < " \t ticket RSA signature isn't even close for " < < qPrintable ( TidTxt ( tid ) . insert ( 8 , " - " ) ) < < " ( " < < ch < < " ) " ;
break ;
case ERROR_RSA_FAKESIGNED :
qWarning ( ) . nospace ( ) < < " \t ticket for " < < qPrintable ( TidTxt ( tid ) . insert ( 8 , " - " ) + ( upper ! = 1 ? " ( " + AsciiTxt ( lower ) + " ) " : " " ) ) < < " is fakesigned " ;
break ;
default :
break ;
}
}
}
}
if ( deleted )
qWarning ( ) . nospace ( ) < < " \t " < < qPrintable ( TidTxt ( tid ) . insert ( 8 , " - " ) + ( upper ! = 1 ? " ( " + AsciiTxt ( lower ) + " ) " : " " ) ) < < " has been deleted " ;
}
buf . close ( ) ;
2011-01-19 23:32:13 +01:00
}
2010-12-18 22:07:58 +01:00
void CheckLostClusters ( )
{
QList < quint16 > u = nand . GetFatsForEntry ( 0 ) ; //all clusters actually used for a file
2011-01-29 09:44:22 +01:00
if ( verbose )
qDebug ( ) < < " total used clusters " < < hex < < u . size ( ) < < " of 0x8000 " ;
2010-12-18 22:07:58 +01:00
quint16 lost = 0 ;
QList < quint16 > ffs ;
QList < quint16 > frs ;
fats = nand . GetFats ( ) ;
for ( quint16 i = 0 ; i < 0x8000 ; i + + )
{
2011-01-02 07:15:26 +01:00
if ( u . contains ( fats . at ( i ) ) ) //this cluster is really used
continue ;
switch ( fats . at ( i ) )
{
case 0xFFFB :
case 0xFFFC :
case 0xFFFD :
break ;
case 0xFFFE :
frs < < i ;
break ;
case 0xFFFF :
ffs < < i ;
break ;
default :
lost + + ;
//qDebug() << hex << i << fats.at( i );
break ;
}
2010-12-18 22:07:58 +01:00
}
qDebug ( ) < < " found " < < lost < < " lost clusters \n UNK ( 0xffff ) " < < hex < < ffs . size ( ) < < ffs < <
2011-01-02 07:15:26 +01:00
" \n free " < < frs . size ( ) ;
2010-12-18 22:07:58 +01:00
}
void CheckEcc ( )
{
QList < quint32 > bad ;
QList < quint16 > clusters ;
fats = nand . GetFats ( ) ;
quint32 checked = 0 ;
quint16 badClustersNotSpecial = 0 ;
for ( quint16 i = 0 ; i < 0x8000 ; i + + )
{
2011-01-02 07:15:26 +01:00
if ( fats . at ( i ) = = 0xfffd | | fats . at ( i ) = = 0xfffe )
2011-01-29 09:44:22 +01:00
continue ;
2011-01-02 07:15:26 +01:00
for ( quint8 j = 0 ; j < 8 ; j + + , checked + = 8 )
{
quint32 page = ( i * 8 ) + j ;
if ( ! nand . CheckEcc ( page ) )
{
bad < < page ;
if ( ! clusters . contains ( i ) )
clusters < < i ;
}
}
2010-12-18 22:07:58 +01:00
}
QList < quint16 > blocks ;
QList < quint16 > clustersCpy = clusters ;
while ( clustersCpy . size ( ) )
{
2011-01-02 07:15:26 +01:00
quint16 p = clustersCpy . takeFirst ( ) ;
if ( fats . at ( p ) < 0xfff0 )
2011-01-29 09:44:22 +01:00
badClustersNotSpecial + + ;
2011-01-21 07:21:28 +01:00
2011-01-02 07:15:26 +01:00
quint16 block = p / 8 ;
if ( ! blocks . contains ( block ) )
blocks < < block ;
2011-01-29 09:44:22 +01:00
}
2011-01-21 07:21:28 +01:00
2010-12-18 22:07:58 +01:00
qDebug ( ) < < bad . size ( ) < < " out of " < < checked < < " pages had incorrect ecc. \n they were spread through "
2011-01-02 07:15:26 +01:00
< < clusters . size ( ) < < " clusters in " < < blocks . size ( ) < < " blocks: \n " < < blocks ;
2011-01-29 09:44:22 +01:00
qDebug ( ) < < badClustersNotSpecial < < " of those clusters are non-special (they belong to the fs) " ;
2011-01-21 07:21:28 +01:00
2010-12-18 22:07:58 +01:00
}
void SetUpTree ( )
{
if ( root )
2011-01-02 07:15:26 +01:00
return ;
2010-12-18 22:07:58 +01:00
QTreeWidgetItem * r = nand . GetTree ( ) ;
if ( r - > childCount ( ) ! = 1 | | r - > child ( 0 ) - > text ( 0 ) ! = " / " )
2011-01-20 04:22:30 +01:00
{
tryToKeepGoing = false ;
2011-01-02 07:15:26 +01:00
Fail ( " The nand FS is seriously broken. I Couldn't even find a correct root " ) ;
2011-01-20 04:22:30 +01:00
}
2010-12-18 22:07:58 +01:00
root = r - > takeChild ( 0 ) ;
delete r ;
}
int RecurseCountFiles ( QTreeWidgetItem * dir )
{
int ret = 0 ;
quint16 cnt = dir - > childCount ( ) ;
for ( quint16 i = 0 ; i < cnt ; i + + )
{
2011-01-02 07:15:26 +01:00
QTreeWidgetItem * item = dir - > child ( i ) ;
if ( item - > text ( 7 ) . startsWith ( " 02 " ) )
ret + = RecurseCountFiles ( item ) ;
else
{
ret + + ;
}
2010-12-18 22:07:58 +01:00
}
return ret ;
}
int RecurseCheckHmac ( QTreeWidgetItem * dir )
{
int ret = 0 ;
quint16 cnt = dir - > childCount ( ) ;
for ( quint16 i = 0 ; i < cnt ; i + + )
{
2011-01-02 07:15:26 +01:00
QTreeWidgetItem * item = dir - > child ( i ) ;
if ( item - > text ( 7 ) . startsWith ( " 02 " ) )
ret + = RecurseCheckHmac ( item ) ;
else
{
bool ok = false ;
quint16 entry = item - > text ( 1 ) . toInt ( & ok ) ;
if ( ! ok )
continue ;
if ( ! nand . CheckHmacData ( entry ) )
{
2011-01-20 04:22:30 +01:00
qCritical ( ) < < " bad HMAC for " < < PathFromItem ( item ) ;
2011-01-02 07:15:26 +01:00
ret + + ;
}
}
2010-12-18 22:07:58 +01:00
}
return ret ;
}
void CheckHmac ( )
{
quint16 total = RecurseCountFiles ( root ) ;
qDebug ( ) < < " verifying hmac for " < < total < < " files " ;
quint16 bad = RecurseCheckHmac ( root ) ;
qDebug ( ) < < bad < < " files had bad HMAC data " ;
qDebug ( ) < < " checking HMAC for superclusters... " ;
QList < quint16 > sclBad ;
for ( quint16 i = 0x7f00 ; i < 0x8000 ; i + = 0x10 )
{
2011-01-02 07:15:26 +01:00
if ( ! nand . CheckHmacMeta ( i ) )
sclBad < < i ;
2010-12-18 22:07:58 +01:00
}
2011-01-20 04:22:30 +01:00
qDebug ( ) < < sclBad . size ( ) < < " superClusters had bad HMAC data " ;
2010-12-18 22:07:58 +01:00
if ( sclBad . size ( ) )
2011-01-20 04:22:30 +01:00
qCritical ( ) < < sclBad ;
2010-12-18 22:07:58 +01:00
}
2011-01-19 04:39:39 +01:00
void CheckSettingTxt ( )
{
2011-01-20 04:22:30 +01:00
qDebug ( ) < < " Checking setting.txt stuff... " ;
QByteArray settingTxt = nand . GetData ( " /title/00000001/00000002/data/setting.txt " ) ;
if ( settingTxt . isEmpty ( ) )
{
Fail ( " Error reading setting.txt " ) ;
return ;
}
settingTxt = SettingTxtDialog : : LolCrypt ( settingTxt ) ;
QString area ;
bool hArea = false ;
bool hModel = false ;
bool hDvd = false ;
bool hMpch = false ;
bool hCode = false ;
bool hSer = false ;
bool hVideo = false ;
bool hGame = false ;
bool shownSetting = false ;
QString str ( settingTxt ) ;
str . replace ( " \r \n " , " \n " ) ; //maybe not needed to do this in 2 steps, but there may be some reason the file only uses "\n", so do it this way to be safe
QStringList parts = str . split ( " \n " , QString : : SkipEmptyParts ) ;
2011-05-17 23:18:45 +02:00
foreach ( const QString & part , parts )
2011-01-20 04:22:30 +01:00
{
if ( part . startsWith ( " AREA= " ) )
{
if ( hArea ) goto error ;
hArea = true ;
area = part ;
area . remove ( 0 , 5 ) ;
}
else if ( part . startsWith ( " MODEL= " ) )
{
if ( hModel ) goto error ;
hModel = true ;
}
else if ( part . startsWith ( " DVD= " ) )
{
if ( hDvd ) goto error ;
hDvd = true ;
}
else if ( part . startsWith ( " MPCH= " ) )
{
if ( hMpch ) goto error ;
hMpch = true ;
}
else if ( part . startsWith ( " CODE= " ) )
{
if ( hCode ) goto error ;
hCode = true ;
}
else if ( part . startsWith ( " SERNO= " ) )
{
if ( hSer ) goto error ;
hSer = true ;
}
else if ( part . startsWith ( " VIDEO= " ) )
{
if ( hVideo ) goto error ;
hVideo = true ;
}
else if ( part . startsWith ( " GAME= " ) )
{
if ( hGame ) goto error ;
hGame = true ;
}
else
{
qDebug ( ) < < " Extra stuff in the setting.txt. " ;
hexdump ( settingTxt ) ;
qDebug ( ) < < QString ( settingTxt ) ;
shownSetting = true ;
}
}
//something is missing
if ( ! hArea | | ! hModel | | ! hDvd | | ! hMpch | | ! hCode | | ! hSer | | ! hVideo | | ! hGame )
goto error ;
//check for opera brick,
//or in certain cases ( such as KOR area setting on the wrong system menu, a full brick presenting as green & purple garbage instead of the "press A" screen )
if ( sysMenuResource . isEmpty ( ) )
{
qCritical ( ) < < " Error getting the resource file for the system menu. \n Can \' t check it against setting.txt " ;
}
else
{
U8 u8 ( sysMenuResource ) ;
QStringList entries = u8 . Entries ( ) ;
if ( ! u8 . IsOK ( ) | | ! entries . size ( ) )
{
qCritical ( ) < < " Error parsing the resource file for the system menu. \n Can \' t check it against setting.txt " ;
}
else
{
QString sysMenuPath ;
//these are all the possibilities i saw for AREA in libogc
if ( area = = " AUS " | | area = = " EUR " ) //supported by 4.3e
sysMenuPath = " html/EU2/iplsetting.ash/EU/EU/ENG/index01.html " ;
else if ( area = = " USA " | | area = = " BRA " | | area = = " HKG " | | area = = " ASI " | | area = = " LTN " | | area = = " SAF " ) //supported by 4.3u
sysMenuPath = " html/US2/iplsetting.ash/FIX/US/ENG/index01.html " ;
else if ( area = = " JPN " | | area = = " TWN " | | area = = " ROC " ) //supported by 4.3j
sysMenuPath = " html/JP2/iplsetting.ash/JP/JP/JPN/index01.html " ;
else if ( area = = " KOR " ) //supported by 4.3k
sysMenuPath = " html/KR2/iplsetting.ash/KR/KR/KOR/index01.html " ;
else
qDebug ( ) < < " unknown AREA setting " ;
if ( ! entries . contains ( sysMenuPath ) )
{
qCritical ( ) < < sysMenuPath < < " Was not found in the system menu resources, and is needed by the AREA setting " < < area ;
Fail ( " This will likely result in a full/opera brick " ) ;
}
else
{
if ( verbose )
qDebug ( ) < < " system menu resource matches setting.txt AREA setting. " ;
}
}
}
if ( verbose )
{
shownSetting = true ;
hexdump ( settingTxt ) ;
qDebug ( ) < < qPrintable ( str ) ;
}
return ;
2011-01-21 07:21:28 +01:00
error :
2011-01-20 04:22:30 +01:00
qCritical ( ) < < " Something is wrong with this setting.txt " ;
if ( ! shownSetting )
{
hexdump ( settingTxt ) ;
qDebug ( ) < < qPrintable ( str ) ;
}
2011-01-19 04:39:39 +01:00
}
2010-12-18 22:07:58 +01:00
int main ( int argc , char * argv [ ] )
{
QCoreApplication a ( argc , argv ) ;
2011-01-20 04:22:30 +01:00
# ifdef Q_WS_WIN
origColor = GetColor ( ) ;
hConsole = GetStdHandle ( STD_OUTPUT_HANDLE ) ;
2011-01-20 03:05:18 +01:00
# endif
2014-07-09 23:49:58 +02:00
// qInstallMessageHandler( DebugHandler );
2011-01-20 03:05:18 +01:00
2011-05-28 11:10:50 +02:00
args = QCoreApplication : : arguments ( ) ;
2011-01-29 09:44:22 +01:00
if ( args . contains ( " -nocolor " , Qt : : CaseInsensitive ) )
2011-01-20 04:22:30 +01:00
color = false ;
2011-01-20 03:05:18 +01:00
2011-05-28 11:10:50 +02:00
qCritical ( ) < < " ** nandBinCheck : Wii nand info tool ** " ;
qCritical ( ) < < " from giantpune " ;
qCritical ( ) < < " svn r: " < < qPrintable ( CleanSvnStr ( SVN_REV_STR ) ) ;
qCritical ( ) < < " built: " < < __DATE__ < < __TIME__ ;
2011-01-29 09:44:22 +01:00
if ( args . contains ( " -about " , Qt : : CaseInsensitive ) )
About ( ) ;
2011-01-21 07:21:28 +01:00
2011-05-31 01:39:10 +02:00
if ( args . size ( ) < 2 )
2011-01-02 07:15:26 +01:00
Usage ( ) ;
2010-12-18 22:07:58 +01:00
if ( ! QFile ( args . at ( 1 ) ) . exists ( ) )
2011-01-02 07:15:26 +01:00
Usage ( ) ;
2010-12-18 22:07:58 +01:00
if ( ! nand . SetPath ( args . at ( 1 ) ) | | ! nand . InitNand ( ) )
2011-01-02 07:15:26 +01:00
Fail ( " Error setting path to nand object " ) ;
2010-12-18 22:07:58 +01:00
2011-05-31 01:39:10 +02:00
if ( args . size ( ) < 3 )
{
args < < " -all " < < " -v " < < " -v " ;
}
2010-12-18 22:07:58 +01:00
root = NULL ;
2011-01-29 09:44:22 +01:00
verbose = args . count ( " -v " ) ;
2011-01-21 07:21:28 +01:00
2011-01-29 09:44:22 +01:00
if ( args . contains ( " -continue " , Qt : : CaseInsensitive ) )
tryToKeepGoing = true ;
2011-01-15 23:21:43 +01:00
2011-01-29 09:44:22 +01:00
if ( args . contains ( " -rsa " , Qt : : CaseInsensitive ) | | args . contains ( " -all " , Qt : : CaseInsensitive ) )
calcRsa = true ;
2011-01-19 04:39:39 +01:00
2010-12-18 22:07:58 +01:00
//these only serve to show info. no action is taken
2010-12-21 21:01:39 +01:00
if ( args . contains ( " -boot " , Qt : : CaseInsensitive ) | | args . contains ( " -all " , Qt : : CaseInsensitive ) )
2010-12-18 22:07:58 +01:00
{
2011-01-02 07:15:26 +01:00
qDebug ( ) < < " checking boot1 & 2... " ;
ShowBootInfo ( nand . Boot1Version ( ) , nand . Boot2Infos ( ) ) ;
2010-12-18 22:07:58 +01:00
}
2010-12-21 21:01:39 +01:00
if ( args . contains ( " -fs " , Qt : : CaseInsensitive ) | | args . contains ( " -all " , Qt : : CaseInsensitive ) )
2010-12-18 22:07:58 +01:00
{
2011-01-02 07:15:26 +01:00
qDebug ( ) < < " checking uid.sys... " ;
QByteArray ba = nand . GetData ( " /sys/uid.sys " ) ;
if ( ba . isEmpty ( ) )
Fail ( " No uid map found in the nand " ) ;
uidM = UIDmap ( ba ) ;
uidM . Check ( ) ; //dont really take action, the check should spit out info if there is an error. not all errors are fatal
qDebug ( ) < < " checking content.map... " ;
CheckShared ( ) ; //check for the presence of the content.map as well as verify all the hashes
SetUpTree ( ) ;
tids = InstalledTitles ( ) ;
qDebug ( ) < < " found " < < tids . size ( ) < < " titles installed " ;
BuildGoodIosList ( ) ;
qDebug ( ) < < " found " < < validIoses . size ( ) < < " bootable IOS " ;
foreach ( quint64 tid , tids )
{
CheckTitleIntegrity ( tid ) ;
//if( !CheckTitleIntegrity( tid ) && tid == 0x100000002ull ) //well, this SHOULD be the case. but nintendo doesnt care so much about
//Fail( "The System menu isnt valid" ); //checking signatures & hashes as the rest of us.
2011-01-20 04:22:30 +01:00
}
2011-01-29 09:44:22 +01:00
Check003 ( ) ;
2011-01-20 04:22:30 +01:00
if ( args . contains ( " -settingtxt " , Qt : : CaseInsensitive ) | | args . contains ( " -all " , Qt : : CaseInsensitive ) )
{
CheckSettingTxt ( ) ;
}
if ( args . contains ( " -uid " , Qt : : CaseInsensitive ) | | args . contains ( " -all " , Qt : : CaseInsensitive ) )
{
ListDeletedTitles ( ) ;
}
2010-12-18 22:07:58 +01:00
}
2010-12-21 21:01:39 +01:00
if ( args . contains ( " -clInfo " , Qt : : CaseInsensitive ) | | args . contains ( " -all " , Qt : : CaseInsensitive ) )
2010-12-18 22:07:58 +01:00
{
2011-01-02 07:15:26 +01:00
qDebug ( ) < < " checking for lost clusters... " ;
CheckLostClusters ( ) ;
2010-12-18 22:07:58 +01:00
}
2010-12-21 21:01:39 +01:00
if ( args . contains ( " -spare " , Qt : : CaseInsensitive ) | | args . contains ( " -all " , Qt : : CaseInsensitive ) )
2010-12-18 22:07:58 +01:00
{
2011-01-02 07:15:26 +01:00
qDebug ( ) < < " verifying ecc... " ;
CheckEcc ( ) ;
2010-12-18 22:07:58 +01:00
2011-01-02 07:15:26 +01:00
SetUpTree ( ) ;
qDebug ( ) < < " verifying hmac... " ;
CheckHmac ( ) ;
2010-12-18 22:07:58 +01:00
}
return 0 ;
}