diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 0d433df0..1a753548 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -18,6 +18,7 @@ set(SOURCES dialogs/CommentsDialog.cpp dialogs/EditInstructionDialog.cpp dialogs/FlagDialog.cpp + dialogs/GlobalVariableDialog.cpp dialogs/RemoteDebugDialog.cpp dialogs/NativeDebugDialog.cpp dialogs/XrefsDialog.cpp @@ -36,6 +37,7 @@ set(SOURCES widgets/ExportsWidget.cpp widgets/FlagsWidget.cpp widgets/FunctionsWidget.cpp + widgets/GlobalsWidget.cpp widgets/ImportsWidget.cpp widgets/Omnibar.cpp widgets/RelocsWidget.cpp @@ -172,6 +174,7 @@ set(HEADER_FILES dialogs/CommentsDialog.h dialogs/EditInstructionDialog.h dialogs/FlagDialog.h + dialogs/GlobalVariableDialog.h dialogs/RemoteDebugDialog.h dialogs/NativeDebugDialog.h dialogs/XrefsDialog.h @@ -327,6 +330,7 @@ set(UI_FILES dialogs/CommentsDialog.ui dialogs/EditInstructionDialog.ui dialogs/FlagDialog.ui + dialogs/GlobalVariableDialog.ui dialogs/RemoteDebugDialog.ui dialogs/NativeDebugDialog.ui dialogs/XrefsDialog.ui @@ -338,6 +342,7 @@ set(UI_FILES widgets/Dashboard.ui widgets/EntrypointWidget.ui widgets/FlagsWidget.ui + widgets/GlobalsWidget.ui widgets/StringsWidget.ui widgets/HexdumpWidget.ui dialogs/preferences/PreferencesDialog.ui diff --git a/src/core/Cutter.cpp b/src/core/Cutter.cpp index 1c7803f1..c1ce9d21 100644 --- a/src/core/Cutter.cpp +++ b/src/core/Cutter.cpp @@ -1815,6 +1815,32 @@ QList CutterCore::getVariables(RVA at) return ret; } +QList CutterCore::getAllGlobals() +{ + CORE_LOCK(); + RzListIter *it; + + QList ret; + + RzAnalysisVarGlobal *glob; + if (core && core->analysis && core->analysis->typedb) { + const RzList *globals = rz_analysis_var_global_get_all(core->analysis); + CutterRzListForeach (globals, it, RzAnalysisVarGlobal, glob) { + const char *gtype = rz_type_as_string(core->analysis->typedb, glob->type); + if (!gtype) { + continue; + } + GlobalDescription global; + global.addr = glob->addr; + global.name = QString(glob->name); + global.type = QString(gtype); + ret << global; + } + } + + return ret; +} + QVector CutterCore::getRegisterRefValues() { QVector result; @@ -4022,6 +4048,99 @@ QList CutterCore::getXRefs(RVA addr, bool to, bool whole_functi return xrefList; } +void CutterCore::addGlobalVariable(RVA offset, QString name, QString typ) +{ + name = sanitizeStringForCommand(name); + CORE_LOCK(); + char *errmsg = NULL; + RzType *globType = rz_type_parse_string_single(core->analysis->typedb->parser, + typ.toStdString().c_str(), &errmsg); + if (errmsg) { + qWarning() << tr("Error parsing type: \"%1\" message: ").arg(typ) << errmsg; + free(errmsg); + return; + } + if (!rz_analysis_var_global_create(core->analysis, name.toStdString().c_str(), globType, + offset)) { + qWarning() << tr("Error creating global variable: \"%1\"").arg(name); + return; + } + + emit globalVarsChanged(); +} + +void CutterCore::modifyGlobalVariable(RVA offset, QString name, QString typ) +{ + name = sanitizeStringForCommand(name); + CORE_LOCK(); + RzAnalysisVarGlobal *glob = rz_analysis_var_global_get_byaddr_at(core->analysis, offset); + if (!glob) { + return; + } + // Compare if the name is not the same - also rename it + if (name.compare(glob->name)) { + rz_analysis_var_global_rename(core->analysis, glob->name, name.toStdString().c_str()); + } + char *errmsg = NULL; + RzType *globType = rz_type_parse_string_single(core->analysis->typedb->parser, + typ.toStdString().c_str(), &errmsg); + if (errmsg) { + qWarning() << tr("Error parsing type: \"%1\" message: ").arg(typ) << errmsg; + free(errmsg); + return; + } + rz_analysis_var_global_set_type(glob, globType); + + emit globalVarsChanged(); +} + +void CutterCore::delGlobalVariable(QString name) +{ + name = sanitizeStringForCommand(name); + CORE_LOCK(); + rz_analysis_var_global_delete_byname(core->analysis, name.toStdString().c_str()); + + emit globalVarsChanged(); +} + +void CutterCore::delGlobalVariable(RVA offset) +{ + CORE_LOCK(); + rz_analysis_var_global_delete_byaddr_at(core->analysis, offset); + + emit globalVarsChanged(); +} + +QString CutterCore::getGlobalVariableType(QString name) +{ + name = sanitizeStringForCommand(name); + CORE_LOCK(); + RzAnalysisVarGlobal *glob = + rz_analysis_var_global_get_byname(core->analysis, name.toStdString().c_str()); + if (!glob) { + return QString(""); + } + const char *gtype = rz_type_as_string(core->analysis->typedb, glob->type); + if (!gtype) { + return QString(""); + } + return QString(gtype); +} + +QString CutterCore::getGlobalVariableType(RVA offset) +{ + CORE_LOCK(); + RzAnalysisVarGlobal *glob = rz_analysis_var_global_get_byaddr_at(core->analysis, offset); + if (!glob) { + return QString(""); + } + const char *gtype = rz_type_as_string(core->analysis->typedb, glob->type); + if (!gtype) { + return QString(""); + } + return QString(gtype); +} + void CutterCore::addFlag(RVA offset, QString name, RVA size) { name = sanitizeStringForCommand(name); @@ -4536,9 +4655,9 @@ char *CutterCore::getTextualGraphAt(RzCoreGraphType type, RzCoreGraphFormat form RzGraph *graph = rz_core_graph(core, type, address); if (!graph) { if (address == RVA_INVALID) { - qWarning() << "Cannot get global graph"; + qWarning() << tr("Cannot get global graph"); } else { - qWarning() << "Cannot get graph at " << RzAddressString(address); + qWarning() << tr("Cannot get graph at ") << RzAddressString(address); } return nullptr; } diff --git a/src/core/Cutter.h b/src/core/Cutter.h index 14fbfde7..ff2b4682 100644 --- a/src/core/Cutter.h +++ b/src/core/Cutter.h @@ -239,6 +239,14 @@ public: QString nearestFlag(RVA offset, RVA *flagOffsetOut); void triggerFlagsChanged(); + /* Global Variables */ + void addGlobalVariable(RVA offset, QString name, QString typ); + void delGlobalVariable(QString name); + void delGlobalVariable(RVA offset); + void modifyGlobalVariable(RVA offset, QString name, QString typ); + QString getGlobalVariableType(QString name); + QString getGlobalVariableType(RVA offset); + /* Edition functions */ PRzAnalysisBytes getRzAnalysisBytesSingle(RVA addr); QString getInstructionBytes(RVA addr); @@ -584,6 +592,7 @@ public: QList getAllExports(); QList getAllSymbols(); QList getAllHeaders(); + QList getAllGlobals(); QList getSignaturesDB(); QList getAllComments(const QString &filterType); QList getAllRelocs(); @@ -750,6 +759,7 @@ signals: void functionRenamed(const RVA offset, const QString &new_name); void varsChanged(); + void globalVarsChanged(); void functionsChanged(); void flagsChanged(); void commentsChanged(RVA addr); diff --git a/src/core/CutterDescriptions.h b/src/core/CutterDescriptions.h index 31e40261..bb04e12f 100644 --- a/src/core/CutterDescriptions.h +++ b/src/core/CutterDescriptions.h @@ -360,6 +360,13 @@ struct VariableDescription QString value; }; +struct GlobalDescription +{ + RVA addr; + QString type; + QString name; +}; + struct RegisterRefValueDescription { QString name; @@ -407,6 +414,7 @@ Q_DECLARE_METATYPE(RelocDescription) Q_DECLARE_METATYPE(StringDescription) Q_DECLARE_METATYPE(FlagspaceDescription) Q_DECLARE_METATYPE(FlagDescription) +Q_DECLARE_METATYPE(GlobalDescription) Q_DECLARE_METATYPE(XrefDescription) Q_DECLARE_METATYPE(EntrypointDescription) Q_DECLARE_METATYPE(RzBinPluginDescription) diff --git a/src/core/MainWindow.cpp b/src/core/MainWindow.cpp index 3e259734..e7364d43 100644 --- a/src/core/MainWindow.cpp +++ b/src/core/MainWindow.cpp @@ -31,6 +31,7 @@ #include "widgets/DisassemblerGraphView.h" #include "widgets/GraphView.h" #include "widgets/GraphWidget.h" +#include "widgets/GlobalsWidget.h" #include "widgets/OverviewWidget.h" #include "widgets/OverviewView.h" #include "widgets/FunctionsWidget.h" @@ -401,6 +402,7 @@ void MainWindow::initDocks() sectionsDock = new SectionsWidget(this), segmentsDock = new SegmentsWidget(this), symbolsDock = new SymbolsWidget(this), + globalsDock = new GlobalsWidget(this), vTablesDock = new VTablesWidget(this), flirtDock = new FlirtWidget(this), rzGraphDock = new RizinGraphWidget(this), @@ -905,6 +907,7 @@ void MainWindow::restoreDocks() tabifyDockWidget(dashboardDock, headersDock); tabifyDockWidget(dashboardDock, flirtDock); tabifyDockWidget(dashboardDock, symbolsDock); + tabifyDockWidget(dashboardDock, globalsDock); tabifyDockWidget(dashboardDock, classesDock); tabifyDockWidget(dashboardDock, resourcesDock); tabifyDockWidget(dashboardDock, vTablesDock); diff --git a/src/core/MainWindow.h b/src/core/MainWindow.h index 733e7ea5..72a265b8 100644 --- a/src/core/MainWindow.h +++ b/src/core/MainWindow.h @@ -26,6 +26,7 @@ class FunctionsWidget; class ImportsWidget; class ExportsWidget; class SymbolsWidget; +class GlobalsWidget; class RelocsWidget; class CommentsWidget; class StringsWidget; @@ -240,6 +241,7 @@ private: TypesWidget *typesDock = nullptr; SearchWidget *searchDock = nullptr; SymbolsWidget *symbolsDock = nullptr; + GlobalsWidget *globalsDock = nullptr; RelocsWidget *relocsDock = nullptr; CommentsWidget *commentsDock = nullptr; StringsWidget *stringsDock = nullptr; diff --git a/src/dialogs/GlobalVariableDialog.cpp b/src/dialogs/GlobalVariableDialog.cpp new file mode 100644 index 00000000..83877318 --- /dev/null +++ b/src/dialogs/GlobalVariableDialog.cpp @@ -0,0 +1,71 @@ +#include "GlobalVariableDialog.h" +#include "ui_GlobalVariableDialog.h" + +#include +#include "core/Cutter.h" + +GlobalVariableDialog::GlobalVariableDialog(RVA offset, QWidget *parent) + : QDialog(parent), + ui(new Ui::GlobalVariableDialog), + offset(offset), + globalVariableName(""), + globalVariableOffset(RVA_INVALID) +{ + // Setup UI + ui->setupUi(this); + setWindowFlags(windowFlags() & (~Qt::WindowContextHelpButtonHint)); + RzAnalysisVarGlobal *globalVariable = + rz_analysis_var_global_get_byaddr_at(Core()->core()->analysis, offset); + if (globalVariable) { + globalVariableName = QString(globalVariable->name); + globalVariableOffset = globalVariable->addr; + } + + if (globalVariable) { + ui->nameEdit->setText(globalVariable->name); + QString globalVarType = Core()->getGlobalVariableType(globalVariable->name); + ui->typeEdit->setText(globalVarType); + ui->labelAction->setText(tr("Edit global variable at %1").arg(RzAddressString(offset))); + } else { + ui->labelAction->setText(tr("Add global variable at %1").arg(RzAddressString(offset))); + } + + // Connect slots + connect(ui->buttonBox, &QDialogButtonBox::accepted, this, + &GlobalVariableDialog::buttonBoxAccepted); + connect(ui->buttonBox, &QDialogButtonBox::rejected, this, + &GlobalVariableDialog::buttonBoxRejected); +} + +GlobalVariableDialog::~GlobalVariableDialog() {} + +void GlobalVariableDialog::buttonBoxAccepted() +{ + QString name = ui->nameEdit->text(); + QString typ = ui->typeEdit->text(); + + if (name.isEmpty()) { + if (globalVariableOffset != RVA_INVALID) { + // Empty name and global variable exists -> delete the global variable + Core()->delGlobalVariable(globalVariableOffset); + } else { + // GlobalVariable was not existing and we gave an empty name, do nothing + } + } else { + if (globalVariableOffset != RVA_INVALID) { + // Name provided and global variable exists -> rename the global variable + Core()->modifyGlobalVariable(globalVariableOffset, name, typ); + } else { + // Name provided and global variable does not exist -> create the global variable + Core()->addGlobalVariable(offset, name, typ); + } + } + close(); + this->setResult(QDialog::Accepted); +} + +void GlobalVariableDialog::buttonBoxRejected() +{ + close(); + this->setResult(QDialog::Rejected); +} diff --git a/src/dialogs/GlobalVariableDialog.h b/src/dialogs/GlobalVariableDialog.h new file mode 100644 index 00000000..5100b367 --- /dev/null +++ b/src/dialogs/GlobalVariableDialog.h @@ -0,0 +1,32 @@ +#ifndef GLOBALVARIABLEDIALOG_H +#define GLOBALVARIABLEDIALOG_H + +#include +#include +#include "core/CutterCommon.h" + +namespace Ui { +class GlobalVariableDialog; +} + +class GlobalVariableDialog : public QDialog +{ + Q_OBJECT + +public: + explicit GlobalVariableDialog(RVA offset, QWidget *parent = nullptr); + ~GlobalVariableDialog(); + +private slots: + void buttonBoxAccepted(); + void buttonBoxRejected(); + +private: + std::unique_ptr ui; + RVA offset; + QString globalVariableName; + RVA globalVariableOffset; + QString typ; +}; + +#endif // GLOBALVARIABLEDIALOG_H diff --git a/src/dialogs/GlobalVariableDialog.ui b/src/dialogs/GlobalVariableDialog.ui new file mode 100644 index 00000000..c45ea8a5 --- /dev/null +++ b/src/dialogs/GlobalVariableDialog.ui @@ -0,0 +1,100 @@ + + + GlobalVariableDialog + + + + 0 + 0 + 452 + 121 + + + + Add Global Variable + + + + + + Add global variable at + + + + + + + 12 + + + + + + 75 + true + + + + Name: + + + + + + + false + + + + + + + + + + + 100 + 16777215 + + + + int + + + false + + + + + + + + + + + 75 + true + + + + Type: + + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + diff --git a/src/menus/DisassemblyContextMenu.cpp b/src/menus/DisassemblyContextMenu.cpp index 300283cc..b2cb760d 100644 --- a/src/menus/DisassemblyContextMenu.cpp +++ b/src/menus/DisassemblyContextMenu.cpp @@ -3,6 +3,7 @@ #include "dialogs/EditInstructionDialog.h" #include "dialogs/CommentsDialog.h" #include "dialogs/FlagDialog.h" +#include "dialogs/GlobalVariableDialog.h" #include "dialogs/XrefsDialog.h" #include "dialogs/EditVariablesDialog.h" #include "dialogs/SetToDataDialog.h" @@ -34,6 +35,7 @@ DisassemblyContextMenu::DisassemblyContextMenu(QWidget *parent, MainWindow *main actionAnalyzeFunction(this), actionEditFunction(this), actionRename(this), + actionGlobalVar(this), actionSetFunctionVarTypes(this), actionXRefs(this), actionXRefsForVariables(this), @@ -83,10 +85,6 @@ DisassemblyContextMenu::DisassemblyContextMenu(QWidget *parent, MainWindow *main getCommentSequence()); addAction(&actionAddComment); - initAction(&actionRename, tr("Rename or add flag"), SLOT(on_actionRename_triggered()), - getRenameSequence()); - addAction(&actionRename); - initAction(&actionSetFunctionVarTypes, tr("Re-type Local Variables"), SLOT(on_actionSetFunctionVarTypes_triggered()), getRetypeSequence()); addAction(&actionSetFunctionVarTypes); @@ -112,6 +110,8 @@ DisassemblyContextMenu::DisassemblyContextMenu(QWidget *parent, MainWindow *main addSeparator(); + addAddAtMenu(); + addSetBaseMenu(); addSetBitsMenu(); @@ -166,6 +166,19 @@ QWidget *DisassemblyContextMenu::parentForDialog() return parentWidget(); } +void DisassemblyContextMenu::addAddAtMenu() +{ + setAsMenu = addMenu(tr("Add at...")); + + initAction(&actionRename, tr("Rename or add flag"), SLOT(on_actionRename_triggered()), + getRenameSequence()); + setAsMenu->addAction(&actionRename); + + initAction(&actionGlobalVar, tr("Modify or add global variable"), + SLOT(on_actionGlobalVar_triggered()), getGlobalVarSequence()); + setAsMenu->addAction(&actionGlobalVar); +} + void DisassemblyContextMenu::addSetBaseMenu() { setBaseMenu = addMenu(tr("Set base of immediate value to..")); @@ -479,7 +492,12 @@ void DisassemblyContextMenu::setupRenaming() // Now, build the renaming menu and show it buildRenameMenu(tuh); + + auto name = RzAddressString(tuh->offset); + actionGlobalVar.setText(tr("Add or change global variable at %1 (used here)").arg(name)); + actionRename.setVisible(true); + actionGlobalVar.setVisible(true); } void DisassemblyContextMenu::aboutToShowSlot() @@ -655,6 +673,11 @@ QKeySequence DisassemblyContextMenu::getRenameSequence() const return { Qt::Key_N }; } +QKeySequence DisassemblyContextMenu::getGlobalVarSequence() const +{ + return { Qt::Key_G }; +} + QKeySequence DisassemblyContextMenu::getRetypeSequence() const { return { Qt::Key_Y }; @@ -868,6 +891,18 @@ void DisassemblyContextMenu::on_actionRename_triggered() } } +void DisassemblyContextMenu::on_actionGlobalVar_triggered() +{ + bool ok = false; + GlobalVariableDialog dialog(doRenameInfo.addr, parentForDialog()); + ok = dialog.exec(); + + if (ok) { + // Rebuild menu in case the user presses the rename shortcut directly before clicking + setupRenaming(); + } +} + void DisassemblyContextMenu::on_actionSetFunctionVarTypes_triggered() { RzAnalysisFunction *fcn = Core()->functionIn(offset); diff --git a/src/menus/DisassemblyContextMenu.h b/src/menus/DisassemblyContextMenu.h index 669990a6..177944e6 100644 --- a/src/menus/DisassemblyContextMenu.h +++ b/src/menus/DisassemblyContextMenu.h @@ -45,6 +45,7 @@ private slots: void on_actionAddComment_triggered(); void on_actionAnalyzeFunction_triggered(); void on_actionRename_triggered(); + void on_actionGlobalVar_triggered(); void on_actionSetFunctionVarTypes_triggered(); void on_actionXRefs_triggered(); void on_actionXRefsForVariables_triggered(); @@ -78,6 +79,7 @@ private: QKeySequence getCopySequence() const; QKeySequence getCommentSequence() const; QKeySequence getCopyAddressSequence() const; + QKeySequence getGlobalVarSequence() const; QKeySequence getSetToCodeSequence() const; QKeySequence getSetAsStringSequence() const; QKeySequence getSetAsStringAdvanced() const; @@ -118,6 +120,7 @@ private: QAction actionXRefs; QAction actionXRefsForVariables; QAction actionDisplayOptions; + QAction actionGlobalVar; QAction actionDeleteComment; QAction actionDeleteFlag; @@ -190,6 +193,7 @@ private: void addSetAsMenu(); void addSetToDataMenu(); void addEditMenu(); + void addAddAtMenu(); void addBreakpointMenu(); void addDebugMenu(); diff --git a/src/widgets/DecompilerWidget.cpp b/src/widgets/DecompilerWidget.cpp index 60586192..c54ad6f9 100644 --- a/src/widgets/DecompilerWidget.cpp +++ b/src/widgets/DecompilerWidget.cpp @@ -90,6 +90,7 @@ DecompilerWidget::DecompilerWidget(MainWindow *main) connect(Core(), &CutterCore::varsChanged, this, &DecompilerWidget::doRefresh); connect(Core(), &CutterCore::functionsChanged, this, &DecompilerWidget::doRefresh); connect(Core(), &CutterCore::flagsChanged, this, &DecompilerWidget::doRefresh); + connect(Core(), &CutterCore::globalVarsChanged, this, &DecompilerWidget::doRefresh); connect(Core(), &CutterCore::commentsChanged, this, &DecompilerWidget::refreshIfChanged); connect(Core(), &CutterCore::instructionChanged, this, &DecompilerWidget::refreshIfChanged); connect(Core(), &CutterCore::refreshCodeViews, this, &DecompilerWidget::doRefresh); diff --git a/src/widgets/DisassemblerGraphView.cpp b/src/widgets/DisassemblerGraphView.cpp index acb732ae..82da0866 100644 --- a/src/widgets/DisassemblerGraphView.cpp +++ b/src/widgets/DisassemblerGraphView.cpp @@ -52,6 +52,7 @@ DisassemblerGraphView::DisassemblerGraphView(QWidget *parent, CutterSeekable *se connect(Core(), &CutterCore::commentsChanged, this, &DisassemblerGraphView::refreshView); connect(Core(), &CutterCore::functionRenamed, this, &DisassemblerGraphView::refreshView); connect(Core(), &CutterCore::flagsChanged, this, &DisassemblerGraphView::refreshView); + connect(Core(), &CutterCore::globalVarsChanged, this, &DisassemblerGraphView::refreshView); connect(Core(), &CutterCore::varsChanged, this, &DisassemblerGraphView::refreshView); connect(Core(), &CutterCore::instructionChanged, this, &DisassemblerGraphView::refreshView); connect(Core(), &CutterCore::breakpointsChanged, this, &DisassemblerGraphView::refreshView); diff --git a/src/widgets/DisassemblyWidget.cpp b/src/widgets/DisassemblyWidget.cpp index 499bb442..946a2d35 100644 --- a/src/widgets/DisassemblyWidget.cpp +++ b/src/widgets/DisassemblyWidget.cpp @@ -128,6 +128,7 @@ DisassemblyWidget::DisassemblyWidget(MainWindow *main) connect(Core(), &CutterCore::commentsChanged, this, [this]() { refreshDisasm(); }); connect(Core(), SIGNAL(flagsChanged()), this, SLOT(refreshDisasm())); + connect(Core(), SIGNAL(globalVarsChanged()), this, SLOT(refreshDisasm())); connect(Core(), SIGNAL(functionsChanged()), this, SLOT(refreshDisasm())); connect(Core(), &CutterCore::functionRenamed, this, [this]() { refreshDisasm(); }); connect(Core(), SIGNAL(varsChanged()), this, SLOT(refreshDisasm())); diff --git a/src/widgets/GlobalsWidget.cpp b/src/widgets/GlobalsWidget.cpp new file mode 100644 index 00000000..f0d9bfc8 --- /dev/null +++ b/src/widgets/GlobalsWidget.cpp @@ -0,0 +1,228 @@ +#include "GlobalsWidget.h" +#include "ui_GlobalsWidget.h" +#include "core/MainWindow.h" +#include "common/Helpers.h" +#include "dialogs/GlobalVariableDialog.h" + +#include +#include + +GlobalsModel::GlobalsModel(QList *globals, QObject *parent) + : AddressableItemModel(parent), globals(globals) +{ +} + +int GlobalsModel::rowCount(const QModelIndex &) const +{ + return globals->count(); +} + +int GlobalsModel::columnCount(const QModelIndex &) const +{ + return GlobalsModel::ColumnCount; +} + +QVariant GlobalsModel::data(const QModelIndex &index, int role) const +{ + if (index.row() >= globals->count()) { + return QVariant(); + } + + const GlobalDescription &global = globals->at(index.row()); + + switch (role) { + case Qt::DisplayRole: + switch (index.column()) { + case GlobalsModel::AddressColumn: + return RzAddressString(global.addr); + case GlobalsModel::TypeColumn: + return QString(global.type).trimmed(); + case GlobalsModel::NameColumn: + return global.name; + case GlobalsModel::CommentColumn: + return Core()->getCommentAt(global.addr); + default: + return QVariant(); + } + case GlobalsModel::GlobalDescriptionRole: + return QVariant::fromValue(global); + default: + return QVariant(); + } +} + +QVariant GlobalsModel::headerData(int section, Qt::Orientation, int role) const +{ + switch (role) { + case Qt::DisplayRole: + switch (section) { + case GlobalsModel::AddressColumn: + return tr("Address"); + case GlobalsModel::TypeColumn: + return tr("Type"); + case GlobalsModel::NameColumn: + return tr("Name"); + case GlobalsModel::CommentColumn: + return tr("Comment"); + default: + return QVariant(); + } + default: + return QVariant(); + } +} + +RVA GlobalsModel::address(const QModelIndex &index) const +{ + const GlobalDescription &global = globals->at(index.row()); + return global.addr; +} + +QString GlobalsModel::name(const QModelIndex &index) const +{ + const GlobalDescription &global = globals->at(index.row()); + return global.name; +} + +GlobalsProxyModel::GlobalsProxyModel(GlobalsModel *sourceModel, QObject *parent) + : AddressableFilterProxyModel(sourceModel, parent) +{ + setFilterCaseSensitivity(Qt::CaseInsensitive); + setSortCaseSensitivity(Qt::CaseInsensitive); +} + +bool GlobalsProxyModel::filterAcceptsRow(int row, const QModelIndex &parent) const +{ + QModelIndex index = sourceModel()->index(row, 0, parent); + auto global = index.data(GlobalsModel::GlobalDescriptionRole).value(); + + return qhelpers::filterStringContains(global.name, this); +} + +bool GlobalsProxyModel::lessThan(const QModelIndex &left, const QModelIndex &right) const +{ + auto leftGlobal = left.data(GlobalsModel::GlobalDescriptionRole).value(); + auto rightGlobal = right.data(GlobalsModel::GlobalDescriptionRole).value(); + + switch (left.column()) { + case GlobalsModel::AddressColumn: + return leftGlobal.addr < rightGlobal.addr; + case GlobalsModel::TypeColumn: + return leftGlobal.type < rightGlobal.type; + case GlobalsModel::NameColumn: + return leftGlobal.name < rightGlobal.name; + case GlobalsModel::CommentColumn: + return Core()->getCommentAt(leftGlobal.addr) < Core()->getCommentAt(rightGlobal.addr); + default: + break; + } + + return false; +} + +void GlobalsWidget::editGlobal() +{ + QModelIndex index = ui->treeView->currentIndex(); + + if (!index.isValid()) { + return; + } + + RVA globalVariableAddress = globalsModel->address(index); + + GlobalVariableDialog dialog(globalVariableAddress, parentWidget()); + dialog.exec(); +} + +void GlobalsWidget::deleteGlobal() +{ + QModelIndex index = ui->treeView->currentIndex(); + + if (!index.isValid()) { + return; + } + + RVA globalVariableAddress = globalsModel->address(index); + Core()->delGlobalVariable(globalVariableAddress); +} + +void GlobalsWidget::showGlobalsContextMenu(const QPoint &pt) +{ + QModelIndex index = ui->treeView->indexAt(pt); + + QMenu menu(ui->treeView); + + if (index.isValid()) { + menu.addAction(actionEditGlobal); + menu.addAction(actionDeleteGlobal); + } + + menu.exec(ui->treeView->mapToGlobal(pt)); +} + +GlobalsWidget::GlobalsWidget(MainWindow *main) + : CutterDockWidget(main), ui(new Ui::GlobalsWidget), tree(new CutterTreeWidget(this)) +{ + ui->setupUi(this); + ui->quickFilterView->setLabelText(tr("Category")); + + setWindowTitle(tr("Globals")); + setObjectName("GlobalsWidget"); + + // Add status bar which displays the count + tree->addStatusBar(ui->verticalLayout); + + // Set single select mode + ui->treeView->setSelectionMode(QAbstractItemView::SingleSelection); + + // Setup up the model and the proxy model + globalsModel = new GlobalsModel(&globals, this); + globalsProxyModel = new GlobalsProxyModel(globalsModel, this); + ui->treeView->setModel(globalsProxyModel); + ui->treeView->sortByColumn(GlobalsModel::AddressColumn, Qt::AscendingOrder); + + // Setup custom context menu + connect(ui->treeView, &QWidget::customContextMenuRequested, this, + &GlobalsWidget::showGlobalsContextMenu); + + ui->treeView->setContextMenuPolicy(Qt::CustomContextMenu); + + connect(ui->quickFilterView, &ComboQuickFilterView::filterTextChanged, globalsProxyModel, + &QSortFilterProxyModel::setFilterWildcard); + + connect(ui->quickFilterView, &ComboQuickFilterView::filterTextChanged, this, + [this] { tree->showItemsNumber(globalsProxyModel->rowCount()); }); + + QShortcut *searchShortcut = new QShortcut(QKeySequence::Find, this); + connect(searchShortcut, &QShortcut::activated, ui->quickFilterView, + &ComboQuickFilterView::showFilter); + searchShortcut->setContext(Qt::WidgetWithChildrenShortcut); + + QShortcut *clearShortcut = new QShortcut(QKeySequence(Qt::Key_Escape), this); + connect(clearShortcut, &QShortcut::activated, ui->quickFilterView, + &ComboQuickFilterView::clearFilter); + clearShortcut->setContext(Qt::WidgetWithChildrenShortcut); + + actionEditGlobal = new QAction(tr("Edit Global Variable"), this); + actionDeleteGlobal = new QAction(tr("Delete Global Variable"), this); + + connect(actionEditGlobal, &QAction::triggered, [this]() { editGlobal(); }); + connect(actionDeleteGlobal, &QAction::triggered, [this]() { deleteGlobal(); }); + + connect(Core(), &CutterCore::globalVarsChanged, this, &GlobalsWidget::refreshGlobals); + connect(Core(), &CutterCore::codeRebased, this, &GlobalsWidget::refreshGlobals); + connect(Core(), &CutterCore::refreshAll, this, &GlobalsWidget::refreshGlobals); + connect(Core(), &CutterCore::commentsChanged, this, + [this]() { qhelpers::emitColumnChanged(globalsModel, GlobalsModel::CommentColumn); }); +} + +GlobalsWidget::~GlobalsWidget() {} + +void GlobalsWidget::refreshGlobals() +{ + globalsModel->beginResetModel(); + globals = Core()->getAllGlobals(); + globalsModel->endResetModel(); + + qhelpers::adjustColumns(ui->treeView, GlobalsModel::ColumnCount, 0); +} diff --git a/src/widgets/GlobalsWidget.h b/src/widgets/GlobalsWidget.h new file mode 100644 index 00000000..89dc41ba --- /dev/null +++ b/src/widgets/GlobalsWidget.h @@ -0,0 +1,89 @@ +#ifndef GLOBALSWIDGET_H +#define GLOBALSWIDGET_H + +#include +#include +#include + +#include "core/Cutter.h" +#include "CutterDockWidget.h" +#include "widgets/ListDockWidget.h" + +class MainWindow; +class QTreeWidget; +class GlobalsWidget; + +namespace Ui { +class GlobalsWidget; +} + +class MainWindow; +class QTreeWidgetItem; + +class GlobalsModel : public AddressableItemModel +{ + Q_OBJECT + + friend GlobalsWidget; + +private: + QList *globals; + +public: + enum Column { AddressColumn = 0, TypeColumn, NameColumn, CommentColumn, ColumnCount }; + enum Role { GlobalDescriptionRole = Qt::UserRole }; + + GlobalsModel(QList *exports, QObject *parent = nullptr); + + int rowCount(const QModelIndex &parent = QModelIndex()) const override; + int columnCount(const QModelIndex &parent = QModelIndex()) const override; + + QVariant data(const QModelIndex &index, int role) const override; + QVariant headerData(int section, Qt::Orientation orientation, + int role = Qt::DisplayRole) const override; + + RVA address(const QModelIndex &index) const override; + QString name(const QModelIndex &index) const override; +}; + +class GlobalsProxyModel : public AddressableFilterProxyModel +{ + Q_OBJECT + +public: + GlobalsProxyModel(GlobalsModel *sourceModel, QObject *parent = nullptr); + +protected: + bool filterAcceptsRow(int row, const QModelIndex &parent) const override; + bool lessThan(const QModelIndex &left, const QModelIndex &right) const override; +}; + +class GlobalsWidget : public CutterDockWidget +{ + Q_OBJECT + +public: + explicit GlobalsWidget(MainWindow *main); + ~GlobalsWidget(); + +private slots: + void refreshGlobals(); + + void showGlobalsContextMenu(const QPoint &pt); + + void editGlobal(); + void deleteGlobal(); + +private: + std::unique_ptr ui; + + QList globals; + GlobalsModel *globalsModel; + GlobalsProxyModel *globalsProxyModel; + CutterTreeWidget *tree; + + QAction *actionEditGlobal; + QAction *actionDeleteGlobal; +}; + +#endif // GLOBALSWIDGET_H diff --git a/src/widgets/GlobalsWidget.ui b/src/widgets/GlobalsWidget.ui new file mode 100644 index 00000000..ca8c8548 --- /dev/null +++ b/src/widgets/GlobalsWidget.ui @@ -0,0 +1,107 @@ + + + GlobalsWidget + + + + 0 + 0 + 400 + 300 + + + + Global Variables + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 0 + 0 + + + + CutterTreeView::item +{ + padding-top: 1px; + padding-bottom: 1px; +} + + + QFrame::NoFrame + + + 0 + + + 8 + + + true + + + + + + + + 0 + 0 + + + + + + + + + Edit Global Variable + + + Edit Global Variable + + + + + Delete Global Variable + + + Delete Global Variable + + + + + + CutterTreeView + QTreeView +
widgets/CutterTreeView.h
+ 1 +
+ + ComboQuickFilterView + QWidget +
widgets/ComboQuickFilterView.h
+ 1 +
+
+ + +
diff --git a/src/widgets/VisualNavbar.cpp b/src/widgets/VisualNavbar.cpp index 0a3f1409..53d93826 100644 --- a/src/widgets/VisualNavbar.cpp +++ b/src/widgets/VisualNavbar.cpp @@ -50,6 +50,7 @@ VisualNavbar::VisualNavbar(MainWindow *main, QWidget *parent) connect(Core(), &CutterCore::refreshAll, this, &VisualNavbar::fetchAndPaintData); connect(Core(), &CutterCore::functionsChanged, this, &VisualNavbar::fetchAndPaintData); connect(Core(), &CutterCore::flagsChanged, this, &VisualNavbar::fetchAndPaintData); + connect(Core(), &CutterCore::globalVarsChanged, this, &VisualNavbar::fetchAndPaintData); graphicsScene = new QGraphicsScene(this);