#include "common/PythonManager.h" #include "common/CrashHandler.h" #include "CutterApplication.h" #include "plugins/PluginManager.h" #include "CutterConfig.h" #include "common/Decompiler.h" #include "common/ResourcePaths.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef Q_OS_WIN # include #endif // Q_OS_WIN #include #if CUTTER_RZGHIDRA_STATIC # include #endif // Rizin before 301e5af2170d9f3ed1edd658b0f9633f31fc4126 // has RZ_GITTAP defined and uses it in rz_core_version(). // After that, RZ_GITTAP is not defined anymore and RZ_VERSION is used. #ifdef RZ_GITTAP #define CUTTER_COMPILE_TIME_RZ_VERSION "" RZ_GITTAP #else #define CUTTER_COMPILE_TIME_RZ_VERSION "" RZ_VERSION #endif CutterApplication::CutterApplication(int &argc, char **argv) : QApplication(argc, argv) { // Setup application information setApplicationVersion(CUTTER_VERSION_FULL); #ifdef Q_OS_MACOS setWindowIcon(QIcon(":/img/cutter_macos_simple.svg")); #else setWindowIcon(QIcon(":/img/cutter.svg")); #endif #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) setAttribute(Qt::AA_UseHighDpiPixmaps); // always enabled on Qt >= 6.0.0 #endif setLayoutDirection(Qt::LeftToRight); // WARN!!! Put initialization code below this line. Code above this line is mandatory to be run // First #ifdef Q_OS_WIN // Hack to force Cutter load internet connection related DLL's QSslSocket s; s.sslConfiguration(); #endif // Q_OS_WIN // Load translations if (!loadTranslations()) { qWarning() << "Cannot load translations"; } // 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."; } // 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 if (!parseCommandLineOptions()) { std::exit(1); } // Check rizin version QString rzversion = rz_core_version(); QString localVersion = CUTTER_COMPILE_TIME_RZ_VERSION; qDebug() << rzversion << localVersion; if (rzversion != localVersion) { QMessageBox msg; msg.setIcon(QMessageBox::Critical); msg.setStandardButtons(QMessageBox::Yes | QMessageBox::No); msg.setWindowTitle(QObject::tr("Version mismatch!")); msg.setText(QString(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)); if (msg.exec() == QMessageBox::No) { std::exit(1); } } #ifdef CUTTER_ENABLE_PYTHON // Init python if (!clOptions.pythonHome.isEmpty()) { Python()->setPythonHome(clOptions.pythonHome); } Python()->initialize(); #endif Core()->initialize(clOptions.enableRizinPlugins); Core()->setSettings(); Config()->loadInitial(); Core()->loadCutterRC(); Config()->setOutputRedirectionEnabled(clOptions.outputRedirectionEnabled); if (JSDecDecompiler::isAvailable()) { Core()->registerDecompiler(new JSDecDecompiler(Core())); } #if CUTTER_RZGHIDRA_STATIC Core()->registerDecompiler(new RzGhidraDecompiler(Core())); #endif Plugins()->loadPlugins(clOptions.enableCutterPlugins); for (auto &plugin : Plugins()->getPlugins()) { plugin->registerDecompilers(); } mainWindow = new MainWindow(); installEventFilter(mainWindow); // 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 if (clOptions.args.empty() && clOptions.fileOpenOptions.projectFile.isEmpty()) { // check if this is the first execution of Cutter in this computer // Note: the execution after the preferences been reset, will be considered as // first-execution if (Config()->isFirstExecution()) { mainWindow->displayWelcomeDialog(); } mainWindow->displayNewFileDialog(); } else { // filename specified as positional argument bool askOptions = (clOptions.analysisLevel != AutomaticAnalysisLevel::Ask) || !clOptions.fileOpenOptions.projectFile.isEmpty(); mainWindow->openNewFile(clOptions.fileOpenOptions, askOptions); } #ifdef APPIMAGE { auto appdir = QDir(QCoreApplication::applicationDirPath()); // appdir/bin appdir.cdUp(); // appdir auto sleighHome = appdir; sleighHome.cd( "share/rizin/plugins/rz_ghidra_sleigh"); // appdir/share/rizin/plugins/rz_ghidra_sleigh Core()->setConfig("ghidra.sleighhome", sleighHome.absolutePath()); auto jsdecHome = appdir; jsdecHome.cd("share/rizin/plugins/jsdec"); // appdir/share/rizin/plugins/jsdec qputenv("JSDEC_HOME", jsdecHome.absolutePath().toLocal8Bit()); } #endif #if defined(Q_OS_MACOS) && defined(CUTTER_ENABLE_PACKAGING) { auto rzprefix = QDir(QCoreApplication::applicationDirPath()); // Contents/MacOS rzprefix.cdUp(); // Contents rzprefix.cd("Resources"); // Contents/Resources/rz auto sleighHome = rzprefix; sleighHome.cd( "share/rizin/plugins/rz_ghidra_sleigh"); // Contents/Resources/rz/share/rizin/plugins/rz_ghidra_sleigh Core()->setConfig("ghidra.sleighhome", sleighHome.absolutePath()); } #endif #if defined(Q_OS_WIN) && defined(CUTTER_ENABLE_PACKAGING) { auto sleighHome = QDir(QCoreApplication::applicationDirPath()); sleighHome.cd("lib/plugins/rz_ghidra_sleigh"); Core()->setConfig("ghidra.sleighhome", sleighHome.absolutePath()); } #endif } CutterApplication::~CutterApplication() { Plugins()->destroyPlugins(); delete mainWindow; #ifdef CUTTER_ENABLE_PYTHON Python()->shutdown(); #endif } 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.enableRizinPlugins) { allArgs.push_back("--no-rizin-plugins"); } allArgs.append(args); process.startDetached(qApp->applicationFilePath(), allArgs); } bool CutterApplication::event(QEvent *e) { if (e->type() == QEvent::FileOpen) { QFileOpenEvent *openEvent = static_cast(e); if (openEvent) { if (m_FileAlreadyDropped) { // We already dropped a file in macOS, let's spawn another instance // (Like the File -> Open) QString fileName = openEvent->file(); launchNewInstance({ fileName }); } else { QString fileName = openEvent->file(); m_FileAlreadyDropped = true; mainWindow->closeNewFileDialog(); InitialOptions options; options.filename = fileName; mainWindow->openNewFile(options); } } } return QApplication::event(e); } 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, QLocale::AnyCountry); 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; const QStringList &cutterTrPaths = Cutter::getTranslationsDirectories(); 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; } } 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; } bool CutterApplication::parseCommandLineOptions() { // Keep this function in sync with documentation QCommandLineParser cmd_parser; cmd_parser.setApplicationDescription( QObject::tr("A Qt and C++ GUI for rizin reverse engineering framework")); cmd_parser.addHelpOption(); cmd_parser.addVersionOption(); cmd_parser.addPositionalArgument("filename", QObject::tr("Filename to open.")); QCommandLineOption analOption( { "A", "analysis" }, 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); QCommandLineOption projectOption({ "p", "project" }, QObject::tr("Load project file"), QObject::tr("project file")); cmd_parser.addOption(projectOption); QCommandLineOption writeModeOption({ "w", "writemode" }, QObject::tr("Open file in write mode")); cmd_parser.addOption(writeModeOption); 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 disableRizinPlugins("no-rizin-plugins", QObject::tr("Do not load rizin plugins")); cmd_parser.addOption(disableRizinPlugins); cmd_parser.process(*this); CutterCommandLineOptions opts; opts.args = cmd_parser.positionalArguments(); if (cmd_parser.isSet(analOption)) { bool analysisLevelSpecified = false; int analysisLevel = cmd_parser.value(analOption).toInt(&analysisLevelSpecified); if (!analysisLevelSpecified || analysisLevel < 0 || analysisLevel > 2) { fprintf(stderr, "%s\n", QObject::tr("Invalid Analysis Level. May be a value between 0 and 2.") .toLocal8Bit() .constData()); return false; } switch (analysisLevel) { case 0: opts.analysisLevel = AutomaticAnalysisLevel::None; break; case 1: opts.analysisLevel = AutomaticAnalysisLevel::AAA; break; case 2: opts.analysisLevel = AutomaticAnalysisLevel::AAAA; break; } } if (opts.args.empty() && opts.analysisLevel != 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.analysisLevel) { case AutomaticAnalysisLevel::Ask: break; case AutomaticAnalysisLevel::None: opts.fileOpenOptions.analysisCmd = {}; break; case AutomaticAnalysisLevel::AAA: opts.fileOpenOptions.analysisCmd = { { "aaa", "Auto analysis" } }; break; case AutomaticAnalysisLevel::AAAA: opts.fileOpenOptions.analysisCmd = { { "aaaa", "Auto analysis (experimental)" } }; break; } opts.fileOpenOptions.script = cmd_parser.value(scriptOption); opts.fileOpenOptions.writeEnabled = cmd_parser.isSet(writeModeOption); } opts.fileOpenOptions.projectFile = cmd_parser.value(projectOption); 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.enableRizinPlugins = false; } if (cmd_parser.isSet(disableCutterPlugins)) { opts.enableCutterPlugins = false; } if (cmd_parser.isSet(disableRizinPlugins)) { opts.enableRizinPlugins = false; } this->clOptions = opts; return true; } 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. if (auto menu = qobject_cast(widget)) { const auto &actions = menu->actions(); for (auto action : actions) { action->setShortcutVisibleInContextMenu(true); } } #endif // QT_VERSION_CHECK(5, 10, 0) < QT_VERSION }