1 #include "common/PythonManager.h"
2 #include "common/CrashHandler.h"
3 #include "CutterApplication.h"
4 #include "plugins/PluginManager.h"
5 #include "CutterConfig.h"
6 #include "common/Decompiler.h"
7 #include "common/ResourcePaths.h"
8
9 #include <QApplication>
10 #include <QFileOpenEvent>
11 #include <QEvent>
12 #include <QMenu>
13 #include <QMessageBox>
14 #include <QCommandLineParser>
15 #include <QTextCodec>
16 #include <QStringList>
17 #include <QProcess>
18 #include <QPluginLoader>
19 #include <QDir>
20 #include <QTranslator>
21 #include <QLibraryInfo>
22 #include <QFontDatabase>
23 #ifdef Q_OS_WIN
24 #include <QtNetwork/QtNetwork>
25 #endif // Q_OS_WIN
26
27 #include <cstdlib>
28
29 #if CUTTER_R2GHIDRA_STATIC
30 #include <R2GhidraDecompiler.h>
31 #endif
32
CutterApplication(int & argc,char ** argv)33 CutterApplication::CutterApplication(int &argc, char **argv) : QApplication(argc, argv)
34 {
35 // Setup application information
36 setApplicationVersion(CUTTER_VERSION_FULL);
37 setWindowIcon(QIcon(":/img/r2cutter.svg"));
38 setAttribute(Qt::AA_UseHighDpiPixmaps);
39 setLayoutDirection(Qt::LeftToRight);
40
41 // WARN!!! Put initialization code below this line. Code above this line is mandatory to be run First
42
43 #ifdef Q_OS_WIN
44 // Hack to force Cutter load internet connection related DLL's
45 QSslSocket s;
46 s.sslConfiguration();
47 #endif // Q_OS_WIN
48
49 // Load translations
50 if (!loadTranslations()) {
51 qWarning() << "Cannot load translations";
52 }
53
54 // Load fonts
55 int ret = QFontDatabase::addApplicationFont(":/fonts/Anonymous Pro.ttf");
56 if (ret == -1) {
57 qWarning() << "Cannot load Anonymous Pro font.";
58 }
59
60 ret = QFontDatabase::addApplicationFont(":/fonts/Inconsolata-Regular.ttf");
61 if (ret == -1) {
62 qWarning() << "Cannot load Incosolata-Regular font.";
63 }
64
65
66 // Set QString codec to UTF-8
67 QTextCodec::setCodecForLocale(QTextCodec::codecForName("UTF-8"));
68 #if QT_VERSION < QT_VERSION_CHECK(5,0,0)
69 QTextCodec::setCodecForCStrings(QTextCodec::codecForName("UTF-8"));
70 QTextCodec::setCodecForTr(QTextCodec::codecForName("UTF-8"));
71 #endif
72
73 if (!parseCommandLineOptions()) {
74 std::exit(1);
75 }
76
77 // Check r2 version
78 QString r2version = r_core_version();
79 QString localVersion = "" R2_GITTAP;
80 if (localVersion != "" && r2version != "" && r2version != localVersion) {
81 QMessageBox msg;
82 msg.setIcon(QMessageBox::Critical);
83 msg.setStandardButtons(QMessageBox::Yes | QMessageBox::No);
84 msg.setWindowTitle(QObject::tr("Version mismatch!"));
85 msg.setText(QString(
86 QObject::tr("The version used to compile Cutter (%1) does not match the binary version of radare2 (%2). This could result in unexpected behaviour. Are you sure you want to continue?")).arg(
87 localVersion, r2version));
88 if (msg.exec() == QMessageBox::No) {
89 std::exit(1);
90 }
91 }
92
93 #ifdef CUTTER_ENABLE_PYTHON
94 // Init python
95 if (!clOptions.pythonHome.isEmpty()) {
96 Python()->setPythonHome(clOptions.pythonHome);
97 }
98 Python()->initialize();
99 #endif
100
101 #ifdef Q_OS_WIN
102 // Redefine r_sys_prefix() behaviour
103 qputenv("R_ALT_SRC_DIR", "1");
104 #endif
105
106 Core()->initialize(clOptions.enableR2Plugins);
107 Core()->setSettings();
108 Config()->loadInitial();
109 Core()->loadCutterRC();
110
111 Config()->setOutputRedirectionEnabled(clOptions.outputRedirectionEnabled);
112
113 if (R2DecDecompiler::isAvailable()) {
114 Core()->registerDecompiler(new R2DecDecompiler(Core()));
115 }
116
117 #if CUTTER_R2GHIDRA_STATIC
118 Core()->registerDecompiler(new R2GhidraDecompiler(Core()));
119 #endif
120
121 Plugins()->loadPlugins(clOptions.enableCutterPlugins);
122
123 for (auto &plugin : Plugins()->getPlugins()) {
124 plugin->registerDecompilers();
125 }
126
127 mainWindow = new MainWindow();
128 installEventFilter(mainWindow);
129
130 // set up context menu shortcut display fix
131 #if QT_VERSION_CHECK(5, 10, 0) < QT_VERSION
132 setStyle(new CutterProxyStyle());
133 #endif // QT_VERSION_CHECK(5, 10, 0) < QT_VERSION
134
135 if (clOptions.args.empty()) {
136 // check if this is the first execution of Cutter in this computer
137 // Note: the execution after the preferences been reset, will be considered as first-execution
138 if (Config()->isFirstExecution()) {
139 mainWindow->displayWelcomeDialog();
140 }
141 mainWindow->displayNewFileDialog();
142 } else { // filename specified as positional argument
143 bool askOptions = clOptions.analLevel != AutomaticAnalysisLevel::Ask;
144 mainWindow->openNewFile(clOptions.fileOpenOptions, askOptions);
145 }
146
147 #if 0
148 #ifdef APPIMAGE
149 {
150 auto appdir = QDir(QCoreApplication::applicationDirPath()); // appdir/bin
151 appdir.cdUp(); // appdir
152
153 auto sleighHome = appdir;
154 sleighHome.cd("share/radare2/plugins/r2ghidra_sleigh"); // appdir/share/radare2/plugins/r2ghidra_sleigh
155 Core()->setConfig("r2ghidra.sleighhome", sleighHome.absolutePath());
156
157 auto r2decHome = appdir;
158 r2decHome.cd("share/radare2/plugins/r2dec-js"); // appdir/share/radare2/plugins/r2dec-js
159 qputenv("R2DEC_HOME", r2decHome.absolutePath().toLocal8Bit());
160 }
161 #endif
162
163 #ifdef Q_OS_MACOS
164 {
165 auto r2prefix = QDir(QCoreApplication::applicationDirPath()); // Contents/MacOS
166 r2prefix.cdUp(); // Contents
167 r2prefix.cd("Resources/r2"); // Contents/Resources/r2
168
169 auto sleighHome = r2prefix;
170 sleighHome.cd("share/radare2/plugins/r2ghidra_sleigh"); // Contents/Resources/r2/share/radare2/plugins/r2ghidra_sleigh
171 Core()->setConfig("r2ghidra.sleighhome", sleighHome.absolutePath());
172
173 auto r2decHome = r2prefix;
174 r2decHome.cd("share/radare2/plugins/r2dec-js"); // Contents/Resources/r2/share/radare2/plugins/r2dec-js
175 qputenv("R2DEC_HOME", r2decHome.absolutePath().toLocal8Bit());
176 }
177 #endif
178
179 #ifdef CUTTER_APPVEYOR_R2DEC
180 qputenv("R2DEC_HOME", "lib\\plugins\\r2dec-js");
181 #endif
182 #ifdef Q_OS_WIN
183 {
184 auto sleighHome = QDir(QCoreApplication::applicationDirPath());
185 sleighHome.cd("lib/plugins/r2ghidra_sleigh");
186 Core()->setConfig("r2ghidra.sleighhome", sleighHome.absolutePath());
187 }
188 #endif
189 #endif
190 }
191
~CutterApplication()192 CutterApplication::~CutterApplication()
193 {
194 Plugins()->destroyPlugins();
195 delete mainWindow;
196 #ifdef CUTTER_ENABLE_PYTHON
197 Python()->shutdown();
198 #endif
199 }
200
launchNewInstance(const QStringList & args)201 void CutterApplication::launchNewInstance(const QStringList &args)
202 {
203 QProcess process(this);
204 process.setEnvironment(QProcess::systemEnvironment());
205 QStringList allArgs;
206 if (!clOptions.enableCutterPlugins) {
207 allArgs.push_back("--no-cutter-plugins");
208 }
209 if (!clOptions.enableR2Plugins) {
210 allArgs.push_back("--no-r2-plugins");
211 }
212 allArgs.append(args);
213 process.startDetached(qApp->applicationFilePath(), allArgs);
214 }
215
event(QEvent * e)216 bool CutterApplication::event(QEvent *e)
217 {
218 if (e->type() == QEvent::FileOpen) {
219 QFileOpenEvent *openEvent = static_cast<QFileOpenEvent *>(e);
220 if (openEvent) {
221 if (m_FileAlreadyDropped) {
222 // We already dropped a file in macOS, let's spawn another instance
223 // (Like the File -> Open)
224 QString fileName = openEvent->file();
225 launchNewInstance({fileName});
226 } else {
227 QString fileName = openEvent->file();
228 m_FileAlreadyDropped = true;
229 mainWindow->closeNewFileDialog();
230 InitialOptions options;
231 options.filename = fileName;
232 mainWindow->openNewFile(options);
233 }
234 }
235 }
236 return QApplication::event(e);
237 }
238
loadTranslations()239 bool CutterApplication::loadTranslations()
240 {
241 const QString &language = Config()->getCurrLocale().bcp47Name();
242 if (language == QStringLiteral("en") || language.startsWith(QStringLiteral("en-"))) {
243 return true;
244 }
245 const auto &allLocales = QLocale::matchingLocales(QLocale::AnyLanguage, QLocale::AnyScript,
246 QLocale::AnyCountry);
247
248 bool cutterTrLoaded = false;
249
250 for (const QLocale &it : allLocales) {
251 const QString &langPrefix = it.bcp47Name();
252 if (langPrefix == language) {
253 QApplication::setLayoutDirection(it.textDirection());
254 QLocale::setDefault(it);
255
256 QTranslator *trCutter = new QTranslator;
257 QTranslator *trQtBase = new QTranslator;
258 QTranslator *trQt = new QTranslator;
259
260 const QStringList &cutterTrPaths = Cutter::getTranslationsDirectories();
261
262 for (const auto &trPath : cutterTrPaths) {
263 if (trCutter && trCutter->load(it, QLatin1String("cutter"), QLatin1String("_"), trPath)) {
264 installTranslator(trCutter);
265 cutterTrLoaded = true;
266 trCutter = nullptr;
267 }
268 if (trQt && trQt->load(it, "qt", "_", trPath)) {
269 installTranslator(trQt);
270 trQt = nullptr;
271 }
272
273 if (trQtBase && trQtBase->load(it, "qtbase", "_", trPath)) {
274 installTranslator(trQtBase);
275 trQtBase = nullptr;
276 }
277 }
278
279 if (trCutter) {
280 delete trCutter;
281 }
282 if (trQt) {
283 delete trQt;
284 }
285 if (trQtBase) {
286 delete trQtBase;
287 }
288 return true;
289 }
290 }
291 if (!cutterTrLoaded) {
292 qWarning() << "Cannot load Cutter's translation for " << language;
293 }
294 return false;
295 }
296
parseCommandLineOptions()297 bool CutterApplication::parseCommandLineOptions()
298 {
299 // Keep this function in sync with documentation
300
301 QCommandLineParser cmd_parser;
302 cmd_parser.setApplicationDescription(
303 QObject::tr("A Qt and C++ GUI for radare2 reverse engineering framework"));
304 cmd_parser.addHelpOption();
305 cmd_parser.addVersionOption();
306 cmd_parser.addPositionalArgument("filename", QObject::tr("Filename to open."));
307
308 QCommandLineOption analOption({"A", "analysis"},
309 QObject::tr("Automatically open file and optionally start analysis. "
310 "Needs filename to be specified. May be a value between 0 and 2:"
311 " 0 = no analysis, 1 = aaa, 2 = aaaa (experimental)"),
312 QObject::tr("level"));
313 cmd_parser.addOption(analOption);
314
315 QCommandLineOption formatOption({"F", "format"},
316 QObject::tr("Force using a specific file format (bin plugin)"),
317 QObject::tr("name"));
318 cmd_parser.addOption(formatOption);
319
320 QCommandLineOption baddrOption({"B", "base"},
321 QObject::tr("Load binary at a specific base address"),
322 QObject::tr("base address"));
323 cmd_parser.addOption(baddrOption);
324
325 QCommandLineOption scriptOption("i",
326 QObject::tr("Run script file"),
327 QObject::tr("file"));
328 cmd_parser.addOption(scriptOption);
329
330 QCommandLineOption writeModeOption({"w", "writemode"},
331 QObject::tr("Open file in write mode"));
332 cmd_parser.addOption(writeModeOption);
333
334
335 QCommandLineOption pythonHomeOption("pythonhome",
336 QObject::tr("PYTHONHOME to use for embedded python interpreter"),
337 "PYTHONHOME");
338 cmd_parser.addOption(pythonHomeOption);
339
340 QCommandLineOption disableRedirectOption("no-output-redirect",
341 QObject::tr("Disable output redirection."
342 " Some of the output in console widget will not be visible."
343 " Use this option when debuging a crash or freeze and output "
344 " redirection is causing some messages to be lost."));
345 cmd_parser.addOption(disableRedirectOption);
346
347 QCommandLineOption disablePlugins("no-plugins",
348 QObject::tr("Do not load plugins"));
349 cmd_parser.addOption(disablePlugins);
350
351 QCommandLineOption disableCutterPlugins("no-cutter-plugins",
352 QObject::tr("Do not load Cutter plugins"));
353 cmd_parser.addOption(disableCutterPlugins);
354
355 QCommandLineOption disableR2Plugins("no-r2-plugins",
356 QObject::tr("Do not load radare2 plugins"));
357 cmd_parser.addOption(disableR2Plugins);
358
359 cmd_parser.process(*this);
360
361 CutterCommandLineOptions opts;
362 opts.args = cmd_parser.positionalArguments();
363
364 if (cmd_parser.isSet(analOption)) {
365 bool analLevelSpecified = false;
366 int analLevel = cmd_parser.value(analOption).toInt(&analLevelSpecified);
367
368 if (!analLevelSpecified || analLevel < 0 || analLevel > 2) {
369 fprintf(stderr, "%s\n",
370 QObject::tr("Invalid Analysis Level. May be a value between 0 and 2.").toLocal8Bit().constData());
371 return false;
372 }
373 switch (analLevel) {
374 case 0:
375 opts.analLevel = AutomaticAnalysisLevel::None;
376 break;
377 case 1:
378 opts.analLevel = AutomaticAnalysisLevel::AAA;
379 break;
380 case 2:
381 opts.analLevel = AutomaticAnalysisLevel::AAAA;
382 break;
383 }
384 }
385
386 if (opts.args.empty() && opts.analLevel != AutomaticAnalysisLevel::Ask) {
387 fprintf(stderr, "%s\n",
388 QObject::tr("Filename must be specified to start analysis automatically.").toLocal8Bit().constData());
389 return false;
390 }
391
392 InitialOptions options;
393 if (!opts.args.isEmpty()) {
394 opts.fileOpenOptions.filename = opts.args[0];
395 opts.fileOpenOptions.forceBinPlugin = cmd_parser.value(formatOption);
396 if (cmd_parser.isSet(baddrOption)) {
397 bool ok;
398 RVA baddr = cmd_parser.value(baddrOption).toULongLong(&ok, 0);
399 if (ok) {
400 options.binLoadAddr = baddr;
401 }
402 }
403 switch (opts.analLevel) {
404 case AutomaticAnalysisLevel::Ask:
405 break;
406 case AutomaticAnalysisLevel::None:
407 opts.fileOpenOptions.analCmd = {};
408 break;
409 case AutomaticAnalysisLevel::AAA:
410 opts.fileOpenOptions.analCmd = { {"aaa", "Auto analysis"} };
411 break;
412 case AutomaticAnalysisLevel::AAAA:
413 opts.fileOpenOptions.analCmd = { {"aaaa", "Auto analysis (experimental)"} };
414 break;
415 }
416 opts.fileOpenOptions.script = cmd_parser.value(scriptOption);
417
418 opts.fileOpenOptions.writeEnabled = cmd_parser.isSet(writeModeOption);
419 }
420
421 if (cmd_parser.isSet(pythonHomeOption)) {
422 opts.pythonHome = cmd_parser.value(pythonHomeOption);
423 }
424
425 opts.outputRedirectionEnabled = !cmd_parser.isSet(disableRedirectOption);
426 if (cmd_parser.isSet(disablePlugins)) {
427 opts.enableCutterPlugins = false;
428 opts.enableR2Plugins = false;
429 }
430
431 if (cmd_parser.isSet(disableCutterPlugins)) {
432 opts.enableCutterPlugins = false;
433 }
434
435 if (cmd_parser.isSet(disableR2Plugins)) {
436 opts.enableR2Plugins = false;
437 }
438
439 this->clOptions = opts;
440 return true;
441 }
442
443
polish(QWidget * widget)444 void CutterProxyStyle::polish(QWidget *widget)
445 {
446 QProxyStyle::polish(widget);
447 #if QT_VERSION_CHECK(5, 10, 0) < QT_VERSION
448 // HACK: This is the only way I've found to force Qt (5.10 and newer) to
449 // display shortcuts in context menus on all platforms. It's ugly,
450 // but it gets the job done.
451 if (auto menu = qobject_cast<QMenu *>(widget)) {
452 const auto &actions = menu->actions();
453 for (auto action : actions) {
454 action->setShortcutVisibleInContextMenu(true);
455 }
456 }
457 #endif // QT_VERSION_CHECK(5, 10, 0) < QT_VERSION
458 }
459