2018-06-23 16:59:23 +00:00
# include "common/PythonManager.h"
2019-04-09 19:33:13 +00:00
# include "common/CrashHandler.h"
2018-02-10 18:04:31 +00:00
# include "CutterApplication.h"
2019-02-09 13:05:06 +00:00
# include "plugins/PluginManager.h"
2019-03-14 09:28:42 +00:00
# include "CutterConfig.h"
2019-07-15 12:08:44 +00:00
# include "common/Decompiler.h"
2020-06-17 09:35:26 +00:00
# include "common/ResourcePaths.h"
2018-02-10 18:04:31 +00:00
# include <QApplication>
# include <QFileOpenEvent>
# include <QEvent>
2019-02-05 07:46:39 +00:00
# include <QMenu>
2018-02-10 18:04:31 +00:00
# include <QMessageBox>
# include <QCommandLineParser>
# include <QTextCodec>
# include <QStringList>
# include <QProcess>
2018-06-26 07:40:08 +00:00
# include <QPluginLoader>
# include <QDir>
2018-10-31 16:07:53 +00:00
# include <QTranslator>
# include <QLibraryInfo>
2019-03-14 09:28:42 +00:00
# include <QFontDatabase>
2019-03-09 13:11:39 +00:00
# ifdef Q_OS_WIN
# include <QtNetwork/QtNetwork>
# endif // Q_OS_WIN
2019-03-14 09:28:42 +00:00
# include <cstdlib>
2019-08-30 11:41:14 +00:00
# if CUTTER_R2GHIDRA_STATIC
# include <R2GhidraDecompiler.h>
# endif
2018-03-21 20:32:32 +00:00
CutterApplication : : CutterApplication ( int & argc , char * * argv ) : QApplication ( argc , argv )
{
2018-11-02 16:49:15 +00:00
// Setup application information
setApplicationVersion ( CUTTER_VERSION_FULL ) ;
setWindowIcon ( QIcon ( " :/img/cutter.svg " ) ) ;
2019-04-03 08:55:39 +00:00
setAttribute ( Qt : : AA_UseHighDpiPixmaps ) ;
2019-01-12 19:44:23 +00:00
setLayoutDirection ( Qt : : LeftToRight ) ;
2018-11-02 16:49:15 +00:00
// WARN!!! Put initialization code below this line. Code above this line is mandatory to be run First
2019-03-09 13:11:39 +00:00
# ifdef Q_OS_WIN
// Hack to force Cutter load internet connection related DLL's
QSslSocket s ;
s . sslConfiguration ( ) ;
# endif // Q_OS_WIN
2018-11-01 22:23:01 +00:00
// Load translations
2019-01-20 17:00:23 +00:00
if ( ! loadTranslations ( ) ) {
qWarning ( ) < < " Cannot load translations " ;
2018-10-31 16:07:53 +00:00
}
2019-01-20 17:00:23 +00:00
2018-11-01 22:23:01 +00:00
// Load fonts
int ret = QFontDatabase : : addApplicationFont ( " :/fonts/Anonymous Pro.ttf " ) ;
if ( ret = = - 1 ) {
qWarning ( ) < < " Cannot load Anonymous Pro font. " ;
}
ret = QFontDatabase : : addApplicationFont ( " :/fonts/Inconsolata-Regular.ttf " ) ;
if ( ret = = - 1 ) {
qWarning ( ) < < " Cannot load Incosolata-Regular font. " ;
}
2018-02-10 18:04:31 +00:00
// Set QString codec to UTF-8
QTextCodec : : setCodecForLocale ( QTextCodec : : codecForName ( " UTF-8 " ) ) ;
# if QT_VERSION < QT_VERSION_CHECK(5,0,0)
QTextCodec : : setCodecForCStrings ( QTextCodec : : codecForName ( " UTF-8 " ) ) ;
QTextCodec : : setCodecForTr ( QTextCodec : : codecForName ( " UTF-8 " ) ) ;
# endif
2020-04-20 21:22:10 +00:00
if ( ! parseCommandLineOptions ( ) ) {
std : : exit ( 1 ) ;
}
2018-02-10 18:04:31 +00:00
2020-12-06 18:00:13 +00:00
// Check rizin version
QString rzversion = rz_core_version ( ) ;
2020-10-28 12:28:04 +00:00
QString localVersion = " " RZ_GITTAP ;
2020-12-06 18:00:13 +00:00
if ( rzversion ! = localVersion ) {
2018-02-10 18:04:31 +00:00
QMessageBox msg ;
msg . setIcon ( QMessageBox : : Critical ) ;
msg . setStandardButtons ( QMessageBox : : Yes | QMessageBox : : No ) ;
msg . setWindowTitle ( QObject : : tr ( " Version mismatch! " ) ) ;
2018-03-21 20:32:32 +00:00
msg . setText ( QString (
2020-12-06 18:00:13 +00:00
QObject : : tr ( " The version used to compile Cutter (%1) does not match the binary version of rizin (%2). This could result in unexpected behaviour. Are you sure you want to continue? " ) ) . arg (
localVersion , rzversion ) ) ;
2018-10-21 16:23:35 +00:00
if ( msg . exec ( ) = = QMessageBox : : No ) {
std : : exit ( 1 ) ;
}
2018-02-10 18:04:31 +00:00
}
2019-02-13 21:53:52 +00:00
# ifdef CUTTER_ENABLE_PYTHON
2018-06-23 16:59:23 +00:00
// Init python
2020-04-20 21:22:10 +00:00
if ( ! clOptions . pythonHome . isEmpty ( ) ) {
Python ( ) - > setPythonHome ( clOptions . pythonHome ) ;
2018-03-06 17:21:42 +00:00
}
2018-06-23 16:59:23 +00:00
Python ( ) - > initialize ( ) ;
2019-02-13 21:53:52 +00:00
# endif
2018-06-23 16:59:23 +00:00
2019-09-25 12:36:30 +00:00
# ifdef Q_OS_WIN
2020-10-28 12:28:04 +00:00
// Redefine rz_sys_prefix() behaviour
2019-09-25 12:36:30 +00:00
qputenv ( " R_ALT_SRC_DIR " , " 1 " ) ;
# endif
2020-04-20 21:22:10 +00:00
Core ( ) - > initialize ( clOptions . enableR2Plugins ) ;
2019-02-20 17:52:11 +00:00
Core ( ) - > setSettings ( ) ;
Config ( ) - > loadInitial ( ) ;
2019-07-11 10:32:56 +00:00
Core ( ) - > loadCutterRC ( ) ;
2018-03-06 17:21:42 +00:00
2020-04-20 21:22:10 +00:00
Config ( ) - > setOutputRedirectionEnabled ( clOptions . outputRedirectionEnabled ) ;
2020-04-17 12:02:44 +00:00
2019-07-15 12:08:44 +00:00
if ( R2DecDecompiler : : isAvailable ( ) ) {
Core ( ) - > registerDecompiler ( new R2DecDecompiler ( Core ( ) ) ) ;
}
2019-08-30 11:41:14 +00:00
# if CUTTER_R2GHIDRA_STATIC
Core ( ) - > registerDecompiler ( new R2GhidraDecompiler ( Core ( ) ) ) ;
# endif
2020-04-20 21:22:10 +00:00
Plugins ( ) - > loadPlugins ( clOptions . enableCutterPlugins ) ;
2018-07-06 18:14:39 +00:00
2020-01-31 10:13:28 +00:00
for ( auto & plugin : Plugins ( ) - > getPlugins ( ) ) {
2019-07-15 16:07:24 +00:00
plugin - > registerDecompilers ( ) ;
}
2018-02-27 13:06:04 +00:00
mainWindow = new MainWindow ( ) ;
2018-05-13 07:50:01 +00:00
installEventFilter ( mainWindow ) ;
2018-02-10 18:04:31 +00:00
2019-02-05 07:46:39 +00:00
// set up context menu shortcut display fix
# if QT_VERSION_CHECK(5, 10, 0) < QT_VERSION
setStyle ( new CutterProxyStyle ( ) ) ;
# endif // QT_VERSION_CHECK(5, 10, 0) < QT_VERSION
2020-04-20 21:22:10 +00:00
if ( clOptions . args . empty ( ) ) {
2019-01-19 20:54:02 +00:00
// check if this is the first execution of Cutter in this computer
2020-04-20 21:22:10 +00:00
// Note: the execution after the preferences been reset, will be considered as first-execution
2019-01-19 20:54:02 +00:00
if ( Config ( ) - > isFirstExecution ( ) ) {
mainWindow - > displayWelcomeDialog ( ) ;
}
2018-02-27 13:06:04 +00:00
mainWindow - > displayNewFileDialog ( ) ;
2018-03-21 20:32:32 +00:00
} else { // filename specified as positional argument
2020-04-20 21:22:10 +00:00
bool askOptions = clOptions . analLevel ! = AutomaticAnalysisLevel : : Ask ;
mainWindow - > openNewFile ( clOptions . fileOpenOptions , askOptions ) ;
2018-02-10 18:04:31 +00:00
}
2018-06-26 07:40:08 +00:00
2019-08-30 11:41:14 +00:00
# ifdef APPIMAGE
{
2020-01-24 11:18:18 +00:00
auto appdir = QDir ( QCoreApplication : : applicationDirPath ( ) ) ; // appdir/bin
appdir . cdUp ( ) ; // appdir
auto sleighHome = appdir ;
2020-12-06 18:00:13 +00:00
sleighHome . cd ( " share/rizin/plugins/rz_ghidra_sleigh " ) ; // appdir/share/rizin/plugins/rz_ghidra_sleigh
2020-10-28 12:28:04 +00:00
Core ( ) - > setConfig ( " ghidra.sleighhome " , sleighHome . absolutePath ( ) ) ;
2020-01-24 11:18:18 +00:00
auto r2decHome = appdir ;
2020-12-06 18:00:13 +00:00
r2decHome . cd ( " share/rizin/plugins/r2dec-js " ) ; // appdir/share/rizin/plugins/r2dec-js
2020-01-24 20:15:34 +00:00
qputenv ( " R2DEC_HOME " , r2decHome . absolutePath ( ) . toLocal8Bit ( ) ) ;
2019-08-30 11:41:14 +00:00
}
# endif
# ifdef Q_OS_MACOS
{
2020-10-28 12:28:04 +00:00
auto rzprefix = QDir ( QCoreApplication : : applicationDirPath ( ) ) ; // Contents/MacOS
rzprefix . cdUp ( ) ; // Contents
2020-12-06 18:00:13 +00:00
rzprefix . cd ( " Resources " ) ; // Contents/Resources/r2
2020-01-24 20:15:34 +00:00
2020-10-28 12:28:04 +00:00
auto sleighHome = rzprefix ;
2020-12-06 18:00:13 +00:00
sleighHome . cd ( " share/rizin/plugins/rz_ghidra_sleigh " ) ; // Contents/Resources/r2/share/rizin/plugins/rz_ghidra_sleigh
2020-10-28 12:28:04 +00:00
Core ( ) - > setConfig ( " ghidra.sleighhome " , sleighHome . absolutePath ( ) ) ;
2020-01-24 20:15:34 +00:00
2020-10-28 12:28:04 +00:00
auto r2decHome = rzprefix ;
2020-12-06 18:00:13 +00:00
r2decHome . cd ( " share/rizin/plugins/r2dec-js " ) ; // Contents/Resources/r2/share/rizin/plugins/r2dec-js
2020-01-24 20:15:34 +00:00
qputenv ( " R2DEC_HOME " , r2decHome . absolutePath ( ) . toLocal8Bit ( ) ) ;
2019-08-30 11:41:14 +00:00
}
# endif
2020-08-08 15:43:43 +00:00
# ifdef CUTTER_APPVEYOR_R2DEC
qputenv ( " R2DEC_HOME " , " lib \\ plugins \\ r2dec-js " ) ;
# endif
2019-08-30 11:41:14 +00:00
# ifdef Q_OS_WIN
{
auto sleighHome = QDir ( QCoreApplication : : applicationDirPath ( ) ) ;
2020-10-28 12:28:04 +00:00
sleighHome . cd ( " lib/plugins/rz_ghidra_sleigh " ) ;
Core ( ) - > setConfig ( " ghidra.sleighhome " , sleighHome . absolutePath ( ) ) ;
2019-08-30 11:41:14 +00:00
}
# endif
2018-02-10 18:04:31 +00:00
}
2018-02-27 13:06:04 +00:00
CutterApplication : : ~ CutterApplication ( )
{
2019-02-09 13:05:06 +00:00
Plugins ( ) - > destroyPlugins ( ) ;
2018-02-27 13:06:04 +00:00
delete mainWindow ;
2019-02-13 21:53:52 +00:00
# ifdef CUTTER_ENABLE_PYTHON
2019-02-03 13:00:40 +00:00
Python ( ) - > shutdown ( ) ;
2019-02-13 21:53:52 +00:00
# endif
2018-02-27 13:06:04 +00:00
}
2020-04-20 21:22:10 +00:00
void CutterApplication : : launchNewInstance ( const QStringList & args )
{
QProcess process ( this ) ;
process . setEnvironment ( QProcess : : systemEnvironment ( ) ) ;
QStringList allArgs ;
if ( ! clOptions . enableCutterPlugins ) {
allArgs . push_back ( " --no-cutter-plugins " ) ;
}
if ( ! clOptions . enableR2Plugins ) {
allArgs . push_back ( " --no-r2-plugins " ) ;
}
allArgs . append ( args ) ;
process . startDetached ( qApp - > applicationFilePath ( ) , allArgs ) ;
}
2018-02-27 13:06:04 +00:00
bool CutterApplication : : event ( QEvent * e )
{
2018-03-21 20:32:32 +00:00
if ( e - > type ( ) = = QEvent : : FileOpen ) {
2018-02-10 18:04:31 +00:00
QFileOpenEvent * openEvent = static_cast < QFileOpenEvent * > ( e ) ;
2018-03-21 20:32:32 +00:00
if ( openEvent ) {
if ( m_FileAlreadyDropped ) {
2018-05-08 20:44:53 +00:00
// We already dropped a file in macOS, let's spawn another instance
2018-02-10 18:04:31 +00:00
// (Like the File -> Open)
QString fileName = openEvent - > file ( ) ;
2020-04-20 21:22:10 +00:00
launchNewInstance ( { fileName } ) ;
2018-03-21 20:32:32 +00:00
} else {
2018-02-10 18:04:31 +00:00
QString fileName = openEvent - > file ( ) ;
m_FileAlreadyDropped = true ;
2018-02-27 13:06:04 +00:00
mainWindow - > closeNewFileDialog ( ) ;
2018-08-18 16:04:45 +00:00
InitialOptions options ;
options . filename = fileName ;
mainWindow - > openNewFile ( options ) ;
2018-02-10 18:04:31 +00:00
}
}
}
return QApplication : : event ( e ) ;
}
2018-06-26 07:40:08 +00:00
2019-01-20 17:00:23 +00:00
bool CutterApplication : : loadTranslations ( )
{
const QString & language = Config ( ) - > getCurrLocale ( ) . bcp47Name ( ) ;
if ( language = = QStringLiteral ( " en " ) | | language . startsWith ( QStringLiteral ( " en- " ) ) ) {
return true ;
}
const auto & allLocales = QLocale : : matchingLocales ( QLocale : : AnyLanguage , QLocale : : AnyScript ,
2020-05-28 11:33:19 +00:00
QLocale : : AnyCountry ) ;
2019-01-20 17:00:23 +00:00
bool cutterTrLoaded = false ;
for ( const QLocale & it : allLocales ) {
const QString & langPrefix = it . bcp47Name ( ) ;
if ( langPrefix = = language ) {
QApplication : : setLayoutDirection ( it . textDirection ( ) ) ;
QLocale : : setDefault ( it ) ;
QTranslator * trCutter = new QTranslator ;
QTranslator * trQtBase = new QTranslator ;
QTranslator * trQt = new QTranslator ;
2020-06-17 09:35:26 +00:00
const QStringList & cutterTrPaths = Cutter : : getTranslationsDirectories ( ) ;
2019-01-20 17:00:23 +00:00
for ( const auto & trPath : cutterTrPaths ) {
if ( trCutter & & trCutter - > load ( it , QLatin1String ( " cutter " ) , QLatin1String ( " _ " ) , trPath ) ) {
installTranslator ( trCutter ) ;
cutterTrLoaded = true ;
trCutter = nullptr ;
}
if ( trQt & & trQt - > load ( it , " qt " , " _ " , trPath ) ) {
installTranslator ( trQt ) ;
trQt = nullptr ;
}
if ( trQtBase & & trQtBase - > load ( it , " qtbase " , " _ " , trPath ) ) {
installTranslator ( trQtBase ) ;
trQtBase = nullptr ;
}
}
2019-09-25 12:36:30 +00:00
2019-01-20 17:00:23 +00:00
if ( trCutter ) {
delete trCutter ;
}
if ( trQt ) {
delete trQt ;
}
if ( trQtBase ) {
delete trQtBase ;
}
return true ;
}
}
if ( ! cutterTrLoaded ) {
qWarning ( ) < < " Cannot load Cutter's translation for " < < language ;
}
return false ;
2019-02-05 07:46:39 +00:00
}
2020-04-20 21:22:10 +00:00
bool CutterApplication : : parseCommandLineOptions ( )
{
// Keep this function in sync with documentation
QCommandLineParser cmd_parser ;
cmd_parser . setApplicationDescription (
2020-12-06 18:00:13 +00:00
QObject : : tr ( " A Qt and C++ GUI for rizin reverse engineering framework " ) ) ;
2020-04-20 21:22:10 +00:00
cmd_parser . addHelpOption ( ) ;
cmd_parser . addVersionOption ( ) ;
cmd_parser . addPositionalArgument ( " filename " , QObject : : tr ( " Filename to open. " ) ) ;
2020-05-17 19:34:38 +00:00
QCommandLineOption analOption ( { " A " , " analysis " } ,
2020-04-20 21:22:10 +00:00
QObject : : tr ( " Automatically open file and optionally start analysis. "
" Needs filename to be specified. May be a value between 0 and 2: "
" 0 = no analysis, 1 = aaa, 2 = aaaa (experimental) " ) ,
QObject : : tr ( " level " ) ) ;
cmd_parser . addOption ( analOption ) ;
QCommandLineOption formatOption ( { " F " , " format " } ,
QObject : : tr ( " Force using a specific file format (bin plugin) " ) ,
QObject : : tr ( " name " ) ) ;
cmd_parser . addOption ( formatOption ) ;
QCommandLineOption baddrOption ( { " B " , " base " } ,
QObject : : tr ( " Load binary at a specific base address " ) ,
QObject : : tr ( " base address " ) ) ;
cmd_parser . addOption ( baddrOption ) ;
QCommandLineOption scriptOption ( " i " ,
QObject : : tr ( " Run script file " ) ,
QObject : : tr ( " file " ) ) ;
cmd_parser . addOption ( scriptOption ) ;
2020-05-28 11:33:19 +00:00
QCommandLineOption writeModeOption ( { " w " , " writemode " } ,
QObject : : tr ( " Open file in write mode " ) ) ;
cmd_parser . addOption ( writeModeOption ) ;
2020-04-20 21:22:10 +00:00
QCommandLineOption pythonHomeOption ( " pythonhome " ,
QObject : : tr ( " PYTHONHOME to use for embedded python interpreter " ) ,
" PYTHONHOME " ) ;
cmd_parser . addOption ( pythonHomeOption ) ;
QCommandLineOption disableRedirectOption ( " no-output-redirect " ,
QObject : : tr ( " Disable output redirection. "
" Some of the output in console widget will not be visible. "
" Use this option when debuging a crash or freeze and output "
" redirection is causing some messages to be lost. " ) ) ;
cmd_parser . addOption ( disableRedirectOption ) ;
QCommandLineOption disablePlugins ( " no-plugins " ,
QObject : : tr ( " Do not load plugins " ) ) ;
cmd_parser . addOption ( disablePlugins ) ;
QCommandLineOption disableCutterPlugins ( " no-cutter-plugins " ,
QObject : : tr ( " Do not load Cutter plugins " ) ) ;
cmd_parser . addOption ( disableCutterPlugins ) ;
QCommandLineOption disableR2Plugins ( " no-r2-plugins " ,
2020-12-06 18:00:13 +00:00
QObject : : tr ( " Do not load rizin plugins " ) ) ;
2020-04-20 21:22:10 +00:00
cmd_parser . addOption ( disableR2Plugins ) ;
cmd_parser . process ( * this ) ;
CutterCommandLineOptions opts ;
opts . args = cmd_parser . positionalArguments ( ) ;
if ( cmd_parser . isSet ( analOption ) ) {
bool analLevelSpecified = false ;
int analLevel = cmd_parser . value ( analOption ) . toInt ( & analLevelSpecified ) ;
if ( ! analLevelSpecified | | analLevel < 0 | | analLevel > 2 ) {
fprintf ( stderr , " %s \n " ,
QObject : : tr ( " Invalid Analysis Level. May be a value between 0 and 2. " ) . toLocal8Bit ( ) . constData ( ) ) ;
return false ;
}
switch ( analLevel ) {
case 0 :
opts . analLevel = AutomaticAnalysisLevel : : None ;
break ;
case 1 :
opts . analLevel = AutomaticAnalysisLevel : : AAA ;
break ;
case 2 :
opts . analLevel = AutomaticAnalysisLevel : : AAAA ;
break ;
}
}
if ( opts . args . empty ( ) & & opts . analLevel ! = AutomaticAnalysisLevel : : Ask ) {
fprintf ( stderr , " %s \n " ,
QObject : : tr ( " Filename must be specified to start analysis automatically. " ) . toLocal8Bit ( ) . constData ( ) ) ;
return false ;
}
InitialOptions options ;
if ( ! opts . args . isEmpty ( ) ) {
opts . fileOpenOptions . filename = opts . args [ 0 ] ;
opts . fileOpenOptions . forceBinPlugin = cmd_parser . value ( formatOption ) ;
if ( cmd_parser . isSet ( baddrOption ) ) {
bool ok ;
RVA baddr = cmd_parser . value ( baddrOption ) . toULongLong ( & ok , 0 ) ;
if ( ok ) {
options . binLoadAddr = baddr ;
}
}
switch ( opts . analLevel ) {
case AutomaticAnalysisLevel : : Ask :
break ;
case AutomaticAnalysisLevel : : None :
opts . fileOpenOptions . analCmd = { } ;
break ;
case AutomaticAnalysisLevel : : AAA :
opts . fileOpenOptions . analCmd = { { " aaa " , " Auto analysis " } } ;
break ;
case AutomaticAnalysisLevel : : AAAA :
opts . fileOpenOptions . analCmd = { { " aaaa " , " Auto analysis (experimental) " } } ;
break ;
}
opts . fileOpenOptions . script = cmd_parser . value ( scriptOption ) ;
2020-05-28 11:33:19 +00:00
opts . fileOpenOptions . writeEnabled = cmd_parser . isSet ( writeModeOption ) ;
2020-04-20 21:22:10 +00:00
}
if ( cmd_parser . isSet ( pythonHomeOption ) ) {
opts . pythonHome = cmd_parser . value ( pythonHomeOption ) ;
}
opts . outputRedirectionEnabled = ! cmd_parser . isSet ( disableRedirectOption ) ;
if ( cmd_parser . isSet ( disablePlugins ) ) {
opts . enableCutterPlugins = false ;
opts . enableR2Plugins = false ;
}
if ( cmd_parser . isSet ( disableCutterPlugins ) ) {
opts . enableCutterPlugins = false ;
}
if ( cmd_parser . isSet ( disableR2Plugins ) ) {
opts . enableR2Plugins = false ;
}
this - > clOptions = opts ;
return true ;
}
2019-02-05 07:46:39 +00:00
void CutterProxyStyle : : polish ( QWidget * widget )
{
QProxyStyle : : polish ( widget ) ;
# if QT_VERSION_CHECK(5, 10, 0) < QT_VERSION
// HACK: This is the only way I've found to force Qt (5.10 and newer) to
// display shortcuts in context menus on all platforms. It's ugly,
// but it gets the job done.
2020-04-20 21:22:10 +00:00
if ( auto menu = qobject_cast < QMenu * > ( widget ) ) {
2019-02-05 07:46:39 +00:00
const auto & actions = menu - > actions ( ) ;
for ( auto action : actions ) {
action - > setShortcutVisibleInContextMenu ( true ) ;
}
}
# endif // QT_VERSION_CHECK(5, 10, 0) < QT_VERSION
}