diff --git a/src/Cutter.cpp b/src/Cutter.cpp index e7a4e947..e9a9ee7d 100644 --- a/src/Cutter.cpp +++ b/src/Cutter.cpp @@ -8,6 +8,7 @@ #include "common/Configuration.h" #include "common/AsyncTask.h" #include "common/R2Task.h" +#include "common/Json.h" #include "Cutter.h" #include "sdb.h" @@ -537,6 +538,23 @@ void CutterCore::setCurrentBits(int bits, RVA offset) emit instructionChanged(offset); } +void CutterCore::setClassMethod(const QString &className, const ClassMethodDescription &meth) +{ + RAnalMethod analMeth; + analMeth.name = strdup (meth.name.toUtf8().constData()); + analMeth.addr = meth.addr; + analMeth.vtable_offset = meth.vtableOffset; + r_anal_class_method_set(core_->anal, className.toUtf8().constData(), &analMeth); + r_anal_class_method_fini(&analMeth); + emit classesChanged(); +} + +void CutterCore::renameClassMethod(const QString &className, const QString &oldMethodName, const QString &newMethodName) +{ + r_anal_class_method_rename(core_->anal, className.toUtf8().constData(), oldMethodName.toUtf8().constData(), newMethodName.toUtf8().constData()); + emit classesChanged(); +} + void CutterCore::seek(ut64 offset) { // Slower than using the API, but the API is not complete @@ -618,6 +636,17 @@ ut64 CutterCore::math(const QString &expr) return r_num_math(this->core_ ? this->core_->num : NULL, expr.toUtf8().constData()); } +ut64 CutterCore::num(const QString &expr) +{ + CORE_LOCK(); + return r_num_get(this->core_ ? this->core_->num : NULL, expr.toUtf8().constData()); +} + +QString CutterCore::itoa(ut64 num, int rdx) +{ + return QString::number(num, rdx); +} + void CutterCore::setConfig(const char *k, const QString &v) { CORE_LOCK(); @@ -1844,16 +1873,16 @@ QList CutterCore::getAllEntrypoint() return ret; } -QList CutterCore::getAllClassesFromBin() +QList CutterCore::getAllClassesFromBin() { CORE_LOCK(); - QList ret; + QList ret; QJsonArray classesArray = cmdj("icj").array(); for (const QJsonValue &value : classesArray) { QJsonObject classObject = value.toObject(); - ClassDescription cls; + BinClassDescription cls; cls.name = classObject[RJsonKey::classname].toString(); cls.addr = classObject[RJsonKey::addr].toVariant().toULongLong(); @@ -1886,16 +1915,14 @@ QList CutterCore::getAllClassesFromBin() return ret; } -#include - -QList CutterCore::getAllClassesFromFlags() +QList CutterCore::getAllClassesFromFlags() { static const QRegularExpression classFlagRegExp("^class\\.(.*)$"); static const QRegularExpression methodFlagRegExp("^method\\.([^\\.]*)\\.(.*)$"); CORE_LOCK(); - QList ret; - QMap classesCache; + QList ret; + QMap classesCache; QJsonArray flagsArray = cmdj("fj@F:classes").array(); for (const QJsonValue &value : flagsArray) { @@ -1906,10 +1933,10 @@ QList CutterCore::getAllClassesFromFlags() QRegularExpressionMatch match = classFlagRegExp.match(flagName); if (match.hasMatch()) { QString className = match.captured(1); - ClassDescription *desc = nullptr; + BinClassDescription *desc = nullptr; auto it = classesCache.find(className); if (it == classesCache.end()) { - ClassDescription cls = {}; + BinClassDescription cls = {}; ret << cls; desc = &ret.last(); classesCache[className] = desc; @@ -1918,20 +1945,20 @@ QList CutterCore::getAllClassesFromFlags() } desc->name = match.captured(1); desc->addr = flagObject[RJsonKey::offset].toVariant().toULongLong(); - desc->index = 0; + desc->index = RVA_INVALID; continue; } match = methodFlagRegExp.match(flagName); if (match.hasMatch()) { QString className = match.captured(1); - ClassDescription *classDesc = nullptr; + BinClassDescription *classDesc = nullptr; auto it = classesCache.find(className); if (it == classesCache.end()) { // add a new stub class, will be replaced if class flag comes after it - ClassDescription cls; + BinClassDescription cls; cls.name = tr("Unknown (%1)").arg(className); - cls.addr = 0; + cls.addr = RVA_INVALID; cls.index = 0; ret << cls; classDesc = &ret.last(); @@ -1950,6 +1977,26 @@ QList CutterCore::getAllClassesFromFlags() return ret; } +QList CutterCore::getAllClassesFromAnal() +{ + QList ret; + + SdbList *l = r_anal_class_get_all(core_->anal, true); + if (!l) { + return ret; + } + + SdbListIter *it; + void *entry; + ls_foreach(l, it, entry) { + auto kv = reinterpret_cast(entry); + ret.append(QString::fromUtf8(reinterpret_cast(kv->base.key))); + } + ls_free(l); + + return ret; +} + QList CutterCore::getAllResources() { CORE_LOCK(); diff --git a/src/Cutter.h b/src/Cutter.h index 99b5f723..d492afbb 100644 --- a/src/Cutter.h +++ b/src/Cutter.h @@ -251,20 +251,28 @@ struct DisassemblyLine { QString text; }; +struct ClassBaseClassDescription { + QString name; + RVA offset; +}; + struct ClassMethodDescription { QString name; - RVA addr; + RVA addr = RVA_INVALID; + st64 vtableOffset = -1; }; struct ClassFieldDescription { QString name; - RVA addr; + RVA addr = RVA_INVALID; }; -struct ClassDescription { +struct BinClassDescription { QString name; - RVA addr; - ut64 index; + RVA addr = RVA_INVALID; + RVA vtableAddr = RVA_INVALID; + ut64 index = 0; + QList baseClasses; QList methods; QList fields; }; @@ -357,8 +365,8 @@ Q_DECLARE_METATYPE(RCorePluginDescription) Q_DECLARE_METATYPE(RAsmPluginDescription) Q_DECLARE_METATYPE(ClassMethodDescription) Q_DECLARE_METATYPE(ClassFieldDescription) -Q_DECLARE_METATYPE(ClassDescription) -Q_DECLARE_METATYPE(const ClassDescription *) +Q_DECLARE_METATYPE(BinClassDescription) +Q_DECLARE_METATYPE(const BinClassDescription *) Q_DECLARE_METATYPE(const ClassMethodDescription *) Q_DECLARE_METATYPE(const ClassFieldDescription *) Q_DECLARE_METATYPE(ResourcesDescription) @@ -446,6 +454,10 @@ public: void setImmediateBase(const QString &r2BaseName, RVA offset = RVA_INVALID); void setCurrentBits(int bits, RVA offset = RVA_INVALID); + /* Classes */ + void setClassMethod(const QString &className, const ClassMethodDescription &meth); + void renameClassMethod(const QString &className, const QString &oldMethodName, const QString &newMethodName); + /* File related methods */ bool loadFile(QString path, ut64 baddr = 0LL, ut64 mapaddr = 0LL, int perms = R_PERM_R, int va = 0, bool loadbin = false, const QString &forceBinPlugin = QString()); @@ -481,6 +493,8 @@ public: /* Math functions */ ut64 math(const QString &expr); + ut64 num(const QString &expr); + QString itoa(ut64 num, int rdx = 16); /* Config functions */ void setConfig(const char *k, const QString &v); @@ -603,8 +617,9 @@ public: QList getAllSections(); QList getAllSegments(); QList getAllEntrypoint(); - QList getAllClassesFromBin(); - QList getAllClassesFromFlags(); + QList getAllClassesFromBin(); + QList getAllClassesFromFlags(); + QList getAllClassesFromAnal(); QList getAllResources(); QList getAllVTables(); @@ -677,6 +692,7 @@ signals: void functionsChanged(); void flagsChanged(); void commentsChanged(); + void classesChanged(); void registersChanged(); void instructionChanged(RVA offset); void breakpointsChanged(); diff --git a/src/Cutter.pro b/src/Cutter.pro index 723a4402..4ee6f90d 100644 --- a/src/Cutter.pro +++ b/src/Cutter.pro @@ -226,8 +226,8 @@ SOURCES += \ common/CutterSeekable.cpp \ common/RefreshDeferrer.cpp \ dialogs/WelcomeDialog.cpp \ - RunScriptTask.cpp - + RunScriptTask.cpp \ + dialogs/EditMethodDialog.cpp HEADERS += \ Cutter.h \ @@ -335,7 +335,9 @@ HEADERS += \ common/CutterSeekable.h \ common/RefreshDeferrer.h \ dialogs/WelcomeDialog.h \ - RunScriptTask.h + RunScriptTask.h \ + common/Json.h \ + dialogs/EditMethodDialog.h FORMS += \ dialogs/AboutDialog.ui \ @@ -394,7 +396,8 @@ FORMS += \ widgets/CutterTreeView.ui \ widgets/ComboQuickFilterView.ui \ dialogs/HexdumpRangeDialog.ui \ - dialogs/WelcomeDialog.ui + dialogs/WelcomeDialog.ui \ + dialogs/EditMethodDialog.ui RESOURCES += \ resources.qrc \ diff --git a/src/common/Json.h b/src/common/Json.h new file mode 100644 index 00000000..5848582f --- /dev/null +++ b/src/common/Json.h @@ -0,0 +1,19 @@ + +#ifndef CUTTER_JSON_H +#define CUTTER_JSON_H + +#include "Cutter.h" + +#include + +static inline RVA JsonValueGetRVA(const QJsonValue &value, RVA defaultValue = RVA_INVALID) +{ + bool ok; + RVA ret = value.toVariant().toULongLong(&ok); + if (!ok) { + return defaultValue; + } + return ret; +} + +#endif //CUTTER_JSON_H diff --git a/src/dialogs/EditMethodDialog.cpp b/src/dialogs/EditMethodDialog.cpp new file mode 100644 index 00000000..f3d9e6b7 --- /dev/null +++ b/src/dialogs/EditMethodDialog.cpp @@ -0,0 +1,145 @@ +#include "EditMethodDialog.h" +#include "ui_EditMethodDialog.h" + +EditMethodDialog::EditMethodDialog(QWidget *parent) : + QDialog(parent), + ui(new Ui::EditMethodDialog) +{ + ui->setupUi(this); + setWindowFlags(windowFlags() & (~Qt::WindowContextHelpButtonHint)); + + ui->classComboBox->clear(); + /* TODO for (auto &cls : Core()->getAllClassesFromAnal()) { + ui->classComboBox->addItem(cls.name, QVariant::fromValue(cls)); + }*/ + + updateVirtualUI(); + validateInput(); + + connect(ui->virtualCheckBox, &QCheckBox::stateChanged, this, &EditMethodDialog::updateVirtualUI); + connect(ui->nameEdit, &QLineEdit::textChanged, this, &EditMethodDialog::validateInput); +} + +EditMethodDialog::~EditMethodDialog() {} + +void EditMethodDialog::on_buttonBox_accepted() +{ +} + +void EditMethodDialog::on_buttonBox_rejected() +{ + close(); +} + +void EditMethodDialog::updateVirtualUI() +{ + bool enabled = ui->virtualCheckBox->isChecked(); + ui->vtableOffsetEdit->setEnabled(enabled); + ui->vtableOffsetLabel->setEnabled(enabled); +} + +void EditMethodDialog::validateInput() +{ + for (auto button : ui->buttonBox->buttons()) { + if (ui->buttonBox->buttonRole(button) == QDialogButtonBox::AcceptRole) { + button->setEnabled(inputValid()); + return; + } + } +} + +bool EditMethodDialog::inputValid() +{ + if (ui->nameEdit->text().isEmpty()) { + return false; + } + return true; +} + +void EditMethodDialog::setClass(const QString &className) +{ + if (className.isEmpty()) { + ui->classComboBox->setCurrentIndex(0); + return; + } + + for (int i=0; iclassComboBox->count(); i++) { + BinClassDescription cls = ui->classComboBox->itemData(i).value(); + if (cls.name == className) { + ui->classComboBox->setCurrentIndex(i); + break; + } + } + + validateInput(); +} + +void EditMethodDialog::setMethod(const ClassMethodDescription &meth) +{ + ui->nameEdit->setText(meth.name); + ui->addressEdit->setText(meth.addr != RVA_INVALID ? RAddressString(meth.addr) : nullptr); + + if (meth.vtableOffset >= 0) { + ui->virtualCheckBox->setChecked(true); + ui->vtableOffsetEdit->setText(QString::number(meth.vtableOffset)); + } else { + ui->virtualCheckBox->setChecked(false); + ui->vtableOffsetEdit->setText(nullptr); + } + + updateVirtualUI(); + validateInput(); +} + +QString EditMethodDialog::getClass() +{ + int index = ui->classComboBox->currentIndex(); + if (index < 0) { + return nullptr; + } + return ui->classComboBox->itemData(index).value().name; +} + +ClassMethodDescription EditMethodDialog::getMethod() +{ + ClassMethodDescription ret; + ret.name = ui->nameEdit->text(); + ret.addr = Core()->num(ui->addressEdit->text()); + if (ui->virtualCheckBox->isChecked()) { + ret.vtableOffset = -1; + } else { + ret.vtableOffset = Core()->num(ui->vtableOffsetEdit->text()); + } + return ret; +} + +bool EditMethodDialog::showDialog(const QString &title, const QString &className, ClassMethodDescription *meth, QWidget *parent) +{ + auto dialog = new EditMethodDialog(parent); + dialog->setWindowTitle(title); + dialog->setClass(className); + dialog->setMethod(*meth); + int result = dialog->exec(); + *meth = dialog->getMethod(); + return result == QDialog::DialogCode::Accepted; +} + +void EditMethodDialog::newMethod(const QString &className, ClassMethodDescription meth) +{ + if (!showDialog(tr("Create Method"), className, &meth)) { + return; + } + Core()->setClassMethod(className, meth); +} + +void EditMethodDialog::editMethod(const QString &className, ClassMethodDescription meth) +{ + QString oldName = meth.name; + if (!showDialog(tr("Edit Method"), className, &meth)) { + return; + } + if (meth.name != oldName) { + Core()->renameClassMethod(className, oldName, meth.name); + } + Core()->setClassMethod(className, meth); +} \ No newline at end of file diff --git a/src/dialogs/EditMethodDialog.h b/src/dialogs/EditMethodDialog.h new file mode 100644 index 00000000..d4be06ce --- /dev/null +++ b/src/dialogs/EditMethodDialog.h @@ -0,0 +1,44 @@ +#ifndef EDITMETHODDIALOG_H +#define EDITMETHODDIALOG_H + +#include +#include + +#include "Cutter.h" + +namespace Ui { +class EditMethodDialog; +} + +class EditMethodDialog : public QDialog +{ + Q_OBJECT + +public: + explicit EditMethodDialog(QWidget *parent = nullptr); + ~EditMethodDialog(); + + void setClass(const QString &className); + void setMethod(const ClassMethodDescription &meth); + + QString getClass(); + ClassMethodDescription getMethod(); + + static bool showDialog(const QString &title, const QString &className, ClassMethodDescription *meth, QWidget *parent = nullptr); + static void newMethod(const QString &className = nullptr, ClassMethodDescription meth = ClassMethodDescription()); + static void editMethod(const QString &className, ClassMethodDescription meth); + +private slots: + void on_buttonBox_accepted(); + void on_buttonBox_rejected(); + + void updateVirtualUI(); + void validateInput(); + +private: + std::unique_ptr ui; + + bool inputValid(); +}; + +#endif // EDITMETHODDIALOG_H diff --git a/src/dialogs/EditMethodDialog.ui b/src/dialogs/EditMethodDialog.ui new file mode 100644 index 00000000..1e345f38 --- /dev/null +++ b/src/dialogs/EditMethodDialog.ui @@ -0,0 +1,125 @@ + + + EditMethodDialog + + + Qt::NonModal + + + + 0 + 0 + 461 + 238 + + + + Method + + + + + + + + Class: + + + + + + + + + + Name: + + + + + + + + + + Address: + + + + + + + + + + Virtual: + + + + + + + + + + + + + + Offset in VTable: + + + + + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + + buttonBox + accepted() + EditMethodDialog + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + EditMethodDialog + reject() + + + 316 + 260 + + + 286 + 274 + + + + + diff --git a/src/widgets/ClassesWidget.cpp b/src/widgets/ClassesWidget.cpp index 2897846f..b510fb84 100644 --- a/src/widgets/ClassesWidget.cpp +++ b/src/widgets/ClassesWidget.cpp @@ -5,146 +5,11 @@ #include "MainWindow.h" #include "ui_ClassesWidget.h" #include "common/Helpers.h" +#include "dialogs/EditMethodDialog.h" -ClassesModel::ClassesModel(QList *classes, QObject *parent) - : QAbstractItemModel(parent), - classes(classes) -{ -} +#include -QModelIndex ClassesModel::index(int row, int column, const QModelIndex &parent) const -{ - if (!parent.isValid()) - return createIndex(row, column, (quintptr)0); // root function nodes have id = 0 - - return createIndex(row, column, (quintptr)parent.row() + 1); // sub-nodes have id = class index + 1 -} - -QModelIndex ClassesModel::parent(const QModelIndex &index) const -{ - if (!index.isValid() || index.column() != 0) - return QModelIndex(); - - if (index.internalId() == 0) // root function node - return QModelIndex(); - else // sub-node - return this->index((int)(index.internalId() - 1), 0); -} - -int ClassesModel::rowCount(const QModelIndex &parent) const -{ - if (!parent.isValid()) { // root - return classes->count(); - } - - if (parent.internalId() == 0) { // methods/fields - const ClassDescription *cls = &classes->at(parent.row()); - return cls->methods.length() + cls->fields.length(); - } - - return 0; // below methods/fields -} - -int ClassesModel::columnCount(const QModelIndex &) const -{ - return Columns::COUNT; -} - -QVariant ClassesModel::data(const QModelIndex &index, int role) const -{ - const ClassDescription *cls; - const ClassMethodDescription *meth = nullptr; - const ClassFieldDescription *field = nullptr; - if (index.internalId() == 0) { // class row - if (index.row() >= classes->count()) { - return QVariant(); - } - - cls = &classes->at(index.row()); - } else { // method/field row - cls = &classes->at(static_cast(index.internalId() - 1)); - - if (index.row() >= cls->methods.length() + cls->fields.length()) { - return QVariant(); - } - - if (index.row() < cls->methods.length()) { - meth = &cls->methods[index.row()]; - } else { - field = &cls->fields[index.row() - cls->methods.length()]; - } - } - - if (meth) { - switch (role) { - case Qt::DisplayRole: - switch (index.column()) { - case NAME: - return meth->name; - case TYPE: - return tr("method"); - case OFFSET: - return RAddressString(meth->addr); - default: - return QVariant(); - } - case OffsetRole: - return QVariant::fromValue(meth->addr); - case NameRole: - return meth->name; - case TypeRole: - return QVariant::fromValue(METHOD); - default: - return QVariant(); - } - } else if (field) { - switch (role) { - case Qt::DisplayRole: - switch (index.column()) { - case NAME: - return field->name; - case TYPE: - return tr("field"); - case OFFSET: - return RAddressString(field->addr); - default: - return QVariant(); - } - case OffsetRole: - return QVariant::fromValue(field->addr); - case NameRole: - return field->name; - case TypeRole: - return QVariant::fromValue(FIELD); - default: - return QVariant(); - } - } else { - switch (role) { - case Qt::DisplayRole: - switch (index.column()) { - case NAME: - return cls->name; - case TYPE: - return tr("class"); - case OFFSET: - return RAddressString(cls->addr); - default: - return QVariant(); - } - case OffsetRole: - return QVariant::fromValue(cls->addr); - case NameRole: - return cls->name; - case TypeRole: - return QVariant::fromValue(CLASS); - default: - return QVariant(); - } - } -} - QVariant ClassesModel::headerData(int section, Qt::Orientation, int role) const { switch (role) { @@ -156,6 +21,8 @@ QVariant ClassesModel::headerData(int section, Qt::Orientation, int role) const return tr("Type"); case OFFSET: return tr("Offset"); + case VTABLE: + return tr("VTable"); default: return QVariant(); } @@ -166,13 +33,377 @@ QVariant ClassesModel::headerData(int section, Qt::Orientation, int role) const +BinClassesModel::BinClassesModel(QObject *parent) + : ClassesModel(parent) +{ +} + +void BinClassesModel::setClasses(const QList &classes) +{ + beginResetModel(); + this->classes = classes; + endResetModel(); +} -ClassesSortFilterProxyModel::ClassesSortFilterProxyModel(ClassesModel *source_model, - QObject *parent) +QModelIndex BinClassesModel::index(int row, int column, const QModelIndex &parent) const +{ + if (!parent.isValid()) + return createIndex(row, column, (quintptr)0); // root function nodes have id = 0 + + return createIndex(row, column, (quintptr)parent.row() + 1); // sub-nodes have id = class index + 1 +} + +QModelIndex BinClassesModel::parent(const QModelIndex &index) const +{ + if (!index.isValid() || index.column() != 0) + return QModelIndex(); + + if (index.internalId() == 0) // root function node + return QModelIndex(); + else // sub-node + return this->index((int)(index.internalId() - 1), 0); +} + +int BinClassesModel::rowCount(const QModelIndex &parent) const +{ + if (!parent.isValid()) { // root + return classes.count(); + } + + if (parent.internalId() == 0) { // methods/fields + const BinClassDescription *cls = &classes.at(parent.row()); + return cls->baseClasses.length() + cls->methods.length() + cls->fields.length(); + } + + return 0; // below methods/fields +} + +int BinClassesModel::columnCount(const QModelIndex &) const +{ + return Columns::COUNT; +} + +QVariant BinClassesModel::data(const QModelIndex &index, int role) const +{ + const BinClassDescription *cls; + const ClassMethodDescription *meth = nullptr; + const ClassFieldDescription *field = nullptr; + const ClassBaseClassDescription *base = nullptr; + if (index.internalId() == 0) { // class row + if (index.row() >= classes.count()) { + return QVariant(); + } + + cls = &classes.at(index.row()); + } else { // method/field/base row + cls = &classes.at(static_cast(index.internalId() - 1)); + + if (index.row() >= cls->baseClasses.length() + cls->methods.length() + cls->fields.length()) { + return QVariant(); + } + + if (index.row() < cls->baseClasses.length()) { + base = &cls->baseClasses[index.row()]; + } else if (index.row() - cls->baseClasses.length() < cls->methods.length()) { + meth = &cls->methods[index.row() - cls->baseClasses.length()]; + } else { + field = &cls->fields[index.row() - cls->baseClasses.length() - cls->methods.length()]; + } + } + + if (meth) { + switch (role) { + case Qt::DisplayRole: + switch (index.column()) { + case NAME: + return meth->name; + case TYPE: + return tr("method"); + case OFFSET: + return meth->addr == RVA_INVALID ? QString() : RAddressString(meth->addr); + case VTABLE: + return meth->vtableOffset < 0 ? QString() : QString("+%1").arg(meth->vtableOffset); + default: + return QVariant(); + } + case OffsetRole: + return QVariant::fromValue(meth->addr); + case VTableOffsetRole: + return QVariant::fromValue(index.parent().data(VTableOffsetRole).toULongLong() + meth->vtableOffset); + case NameRole: + return meth->name; + case TypeRole: + return QVariant::fromValue(METHOD); + case DataRole: + return QVariant::fromValue(*meth); + default: + return QVariant(); + } + } else if (field) { + switch (role) { + case Qt::DisplayRole: + switch (index.column()) { + case NAME: + return field->name; + case TYPE: + return tr("field"); + case OFFSET: + return field->addr == RVA_INVALID ? QString() : RAddressString(field->addr); + default: + return QVariant(); + } + case OffsetRole: + return QVariant::fromValue(field->addr); + case NameRole: + return field->name; + case TypeRole: + return QVariant::fromValue(FIELD); + default: + return QVariant(); + } + } else if (base) { + switch (role) { + case Qt::DisplayRole: + switch (index.column()) { + case NAME: + return base->name; + case TYPE: + return tr("base class"); + case OFFSET: + return QString("+%1").arg(base->offset); + default: + return QVariant(); + } + case NameRole: + return base->name; + case TypeRole: + return QVariant::fromValue(BASE); + default: + return QVariant(); + } + } + else { + switch (role) { + case Qt::DisplayRole: + switch (index.column()) { + case NAME: + return cls->name; + case TYPE: + return tr("class"); + case OFFSET: + return cls->addr == RVA_INVALID ? QString() : RAddressString(cls->addr); + case VTABLE: + return cls->vtableAddr == RVA_INVALID ? QString() : RAddressString(cls->vtableAddr); + default: + return QVariant(); + } + case OffsetRole: + return QVariant::fromValue(cls->addr); + case VTableOffsetRole: + return QVariant::fromValue(cls->vtableAddr); + case NameRole: + return cls->name; + case TypeRole: + return QVariant::fromValue(CLASS); + default: + return QVariant(); + } + } +} + + +AnalClassesModel::AnalClassesModel(QObject *parent) + : ClassesModel(parent) +{ +} + +void AnalClassesModel::setClasses(const QList &classes) +{ + beginResetModel(); + this->classes = classes; + endResetModel(); +} + + +QModelIndex AnalClassesModel::index(int row, int column, const QModelIndex &parent) const +{ + if (!parent.isValid()) + return createIndex(row, column, (quintptr)0); // root function nodes have id = 0 + + return createIndex(row, column, (quintptr)parent.row() + 1); // sub-nodes have id = class index + 1 +} + +QModelIndex AnalClassesModel::parent(const QModelIndex &index) const +{ + if (!index.isValid() || index.column() != 0) + return QModelIndex(); + + if (index.internalId() == 0) // root function node + return QModelIndex(); + else // sub-node + return this->index((int)(index.internalId() - 1), 0); +} + +int AnalClassesModel::rowCount(const QModelIndex &parent) const +{ + if (!parent.isValid()) { // root + return classes.count(); + } + + if (parent.internalId() == 0) { // methods/fields + return 0; + // TODO const BinClassDescription *cls = &classes.at(parent.row()); + // TODO return cls->baseClasses.length() + cls->methods.length() + cls->fields.length(); + } + + return 0; // below methods/fields +} + +bool AnalClassesModel::hasChildren(const QModelIndex &parent) const +{ + return true; +} + +int AnalClassesModel::columnCount(const QModelIndex &) const +{ + return Columns::COUNT; +} + +QVariant AnalClassesModel::data(const QModelIndex &index, int role) const +{ + QString cls; + const ClassMethodDescription *meth = nullptr; + const ClassFieldDescription *field = nullptr; + const ClassBaseClassDescription *base = nullptr; + if (index.internalId() == 0) { // class row + if (index.row() >= classes.count()) { + return QVariant(); + } + + cls = classes.at(index.row()); + } else { // method/field/base row + cls = classes.at(static_cast(index.internalId() - 1)); + + /*if (index.row() >= cls->baseClasses.length() + cls->methods.length() + cls->fields.length()) { + return QVariant(); + } + + if (index.row() < cls->baseClasses.length()) { + base = &cls->baseClasses[index.row()]; + } else if (index.row() - cls->baseClasses.length() < cls->methods.length()) { + meth = &cls->methods[index.row() - cls->baseClasses.length()]; + } else { + field = &cls->fields[index.row() - cls->baseClasses.length() - cls->methods.length()]; + }*/ + } + + if (meth) { + switch (role) { + case Qt::DisplayRole: + switch (index.column()) { + case NAME: + return meth->name; + case TYPE: + return tr("method"); + case OFFSET: + return meth->addr == RVA_INVALID ? QString() : RAddressString(meth->addr); + case VTABLE: + return meth->vtableOffset < 0 ? QString() : QString("+%1").arg(meth->vtableOffset); + default: + return QVariant(); + } + case OffsetRole: + return QVariant::fromValue(meth->addr); + case VTableOffsetRole: + return QVariant::fromValue(index.parent().data(VTableOffsetRole).toULongLong() + meth->vtableOffset); + case NameRole: + return meth->name; + case TypeRole: + return QVariant::fromValue(METHOD); + case DataRole: + return QVariant::fromValue(*meth); + default: + return QVariant(); + } + } else if (field) { + switch (role) { + case Qt::DisplayRole: + switch (index.column()) { + case NAME: + return field->name; + case TYPE: + return tr("field"); + case OFFSET: + return field->addr == RVA_INVALID ? QString() : RAddressString(field->addr); + default: + return QVariant(); + } + case OffsetRole: + return QVariant::fromValue(field->addr); + case NameRole: + return field->name; + case TypeRole: + return QVariant::fromValue(FIELD); + default: + return QVariant(); + } + } else if (base) { + switch (role) { + case Qt::DisplayRole: + switch (index.column()) { + case NAME: + return base->name; + case TYPE: + return tr("base class"); + case OFFSET: + return QString("+%1").arg(base->offset); + default: + return QVariant(); + } + case NameRole: + return base->name; + case TypeRole: + return QVariant::fromValue(BASE); + default: + return QVariant(); + } + } + else { + switch (role) { + case Qt::DisplayRole: + switch (index.column()) { + case NAME: + return cls; + case TYPE: + return tr("class"); + // TODO case OFFSET: + // TODO return cls->addr == RVA_INVALID ? QString() : RAddressString(cls->addr); + // TODO case VTABLE: + // TODO return cls->vtableAddr == RVA_INVALID ? QString() : RAddressString(cls->vtableAddr); + default: + return QVariant(); + } + // TODO case OffsetRole: + // TODO return QVariant::fromValue(cls->addr); + // TODO case VTableOffsetRole: + // TODO return QVariant::fromValue(cls->vtableAddr); + // TODO case NameRole: + // TODO return cls->name; + case TypeRole: + return QVariant::fromValue(CLASS); + default: + return QVariant(); + } + } +} + + + + +ClassesSortFilterProxyModel::ClassesSortFilterProxyModel(QObject *parent) : QSortFilterProxyModel(parent) { - setSourceModel(source_model); } bool ClassesSortFilterProxyModel::filterAcceptsRow(int row, const QModelIndex &parent) const @@ -214,41 +445,61 @@ ClassesWidget::ClassesWidget(MainWindow *main, QAction *action) : { ui->setupUi(this); - model = new ClassesModel(&classes, this); - proxy_model = new ClassesSortFilterProxyModel(model, this); - ui->classesTreeView->setModel(proxy_model); + proxy_model = new ClassesSortFilterProxyModel(this); + ui->classesTreeView->setModel(nullptr); ui->classesTreeView->sortByColumn(ClassesModel::TYPE, Qt::AscendingOrder); + ui->classesTreeView->setContextMenuPolicy(Qt::CustomContextMenu); + + ui->classSourceCombo->setCurrentIndex(1); connect(Core(), SIGNAL(refreshAll()), this, SLOT(refreshClasses())); - connect(Core(), SIGNAL(flagsChanged()), this, SLOT(flagsChanged())); + connect(Core(), &CutterCore::classesChanged, this, [this]() { + if (getSource() == Source::ANAL) { + refreshClasses(); + } + }); connect(ui->classSourceCombo, SIGNAL(currentIndexChanged(int)), this, SLOT(refreshClasses())); + connect(ui->classesTreeView, &QTreeView::customContextMenuRequested, this, &ClassesWidget::showContextMenu); } ClassesWidget::~ClassesWidget() {} ClassesWidget::Source ClassesWidget::getSource() { - if (ui->classSourceCombo->currentIndex() == 1) { - return Source::FLAGS; - } else { + switch (ui->classSourceCombo->currentIndex()) { + case 0: return Source::BIN; - } -} - -void ClassesWidget::flagsChanged() -{ - if (getSource() == Source::FLAGS) { - refreshClasses(); + default: + return Source::ANAL; } } void ClassesWidget::refreshClasses() { - model->beginResetModel(); - classes = getSource() == Source::BIN - ? Core()->getAllClassesFromBin() - : Core()->getAllClassesFromFlags(); - model->endResetModel(); + switch (getSource()) { + case Source::BIN: + if (!bin_model) { + proxy_model->setSourceModel(nullptr); + delete anal_model; + anal_model = nullptr; + bin_model = new BinClassesModel(this); + ui->classesTreeView->setModel(bin_model); + //proxy_model->setSourceModel(bin_model); + } + bin_model->setClasses(Core()->getAllClassesFromBin()); + break; + case Source::ANAL: + if (!anal_model) { + proxy_model->setSourceModel(nullptr); + delete bin_model; + bin_model = nullptr; + anal_model = new AnalClassesModel(this); + ui->classesTreeView->setModel(anal_model); + //proxy_model->setSourceModel(anal_model); + } + anal_model->setClasses(Core()->getAllClassesFromAnal()); + break; + } qhelpers::adjustColumns(ui->classesTreeView, 3, 0); @@ -263,3 +514,67 @@ void ClassesWidget::on_classesTreeView_doubleClicked(const QModelIndex &index) RVA offset = index.data(ClassesModel::OffsetRole).value(); Core()->seek(offset); } + +void ClassesWidget::showContextMenu(const QPoint &pt) +{ + QModelIndex index = ui->classesTreeView->selectionModel()->currentIndex(); + if (!index.isValid()) { + return; + } + + QMenu menu(ui->classesTreeView); + + QVariant vtableOffsetVariant = index.data(ClassesModel::VTableOffsetRole); + if (vtableOffsetVariant.isValid() && vtableOffsetVariant.toULongLong() != RVA_INVALID) { + menu.addAction(ui->seekToVTableAction); + } + + menu.addAction(ui->addMethodAction); + + if (index.data(ClassesModel::TypeRole) == ClassesModel::METHOD) { + menu.addAction(ui->editMethodAction); + } + + menu.exec(ui->classesTreeView->mapToGlobal(pt)); +} + +void ClassesWidget::on_seekToVTableAction_triggered() +{ + RVA vtableOffset = ui->classesTreeView->selectionModel()->currentIndex() + .data(ClassesModel::VTableOffsetRole).value(); + if (vtableOffset != RVA_INVALID) { + Core()->seek(vtableOffset); + } +} + +void ClassesWidget::on_addMethodAction_triggered() +{ + QModelIndex index = ui->classesTreeView->selectionModel()->currentIndex(); + if (!index.isValid()) { + return; + } + + QString className; + if (index.data(ClassesModel::TypeRole).toInt() == ClassesModel::CLASS) { + className = index.data(ClassesModel::NameRole).toString(); + } else { + className = index.parent().data(ClassesModel::NameRole).toString(); + } + + ClassMethodDescription meth; + meth.addr = Core()->getOffset(); + + EditMethodDialog::newMethod(className, meth); +} + +void ClassesWidget::on_editMethodAction_triggered() +{ + QModelIndex index = ui->classesTreeView->selectionModel()->currentIndex(); + if (!index.isValid() || index.data(ClassesModel::TypeRole).toInt() != ClassesModel::METHOD) { + return; + } + + QString className = index.parent().data(ClassesModel::NameRole).toString(); + ClassMethodDescription meth = index.data(ClassesModel::DataRole).value(); + EditMethodDialog::editMethod(className, meth); +} diff --git a/src/widgets/ClassesWidget.h b/src/widgets/ClassesWidget.h index 61bd8f66..0a58b27a 100644 --- a/src/widgets/ClassesWidget.h +++ b/src/widgets/ClassesWidget.h @@ -18,46 +18,76 @@ class QTreeWidgetItem; class MainWindow; class ClassesWidget; - class ClassesModel: public QAbstractItemModel { - Q_OBJECT - - friend ClassesWidget; - -private: - QList *classes; - public: - enum Columns { NAME = 0, TYPE, OFFSET, COUNT }; - enum RowType { CLASS = 0, METHOD = 1, FIELD = 2 }; + enum Columns { NAME = 0, TYPE, OFFSET, VTABLE, COUNT }; + enum RowType { CLASS = 0, METHOD = 1, FIELD = 2, BASE = 3 }; static const int OffsetRole = Qt::UserRole; static const int NameRole = Qt::UserRole + 1; static const int TypeRole = Qt::UserRole + 2; + static const int VTableOffsetRole = Qt::UserRole + 3; + static const int DataRole = Qt::UserRole + 4; - explicit ClassesModel(QList *classes, QObject *parent = nullptr); + explicit ClassesModel(QObject *parent = nullptr) : QAbstractItemModel(parent) {} - QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const override; - QModelIndex parent(const QModelIndex &index) const override; - - 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; }; Q_DECLARE_METATYPE(ClassesModel::RowType) +class BinClassesModel: public ClassesModel +{ + Q_OBJECT + +private: + QList classes; + + QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const override; + + QModelIndex parent(const QModelIndex &index) const override; + 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; + +public: + explicit BinClassesModel(QObject *parent = nullptr); + void setClasses(const QList &classes); +}; + + +class AnalClassesModel: public ClassesModel +{ +Q_OBJECT + +private: + QList classes; + + QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const override; + + QModelIndex parent(const QModelIndex &index) const override; + int rowCount(const QModelIndex &parent = QModelIndex()) const override; + bool hasChildren(const QModelIndex &parent = QModelIndex()) const override; + + int columnCount(const QModelIndex &parent = QModelIndex()) const override; + QVariant data(const QModelIndex &index, int role) const override; + +public: + explicit AnalClassesModel(QObject *parent = nullptr); + void setClasses(const QList &classes); +}; + + class ClassesSortFilterProxyModel : public QSortFilterProxyModel { Q_OBJECT public: - explicit ClassesSortFilterProxyModel(ClassesModel *source_model, QObject *parent = nullptr); + explicit ClassesSortFilterProxyModel(QObject *parent = nullptr); protected: bool filterAcceptsRow(int row, const QModelIndex &parent) const override; @@ -77,19 +107,24 @@ public: private slots: void on_classesTreeView_doubleClicked(const QModelIndex &index); + void on_seekToVTableAction_triggered(); + void on_addMethodAction_triggered(); + void on_editMethodAction_triggered(); + + void showContextMenu(const QPoint &pt); + void refreshClasses(); - void flagsChanged(); private: - enum class Source { BIN, FLAGS }; + enum class Source { BIN, ANAL }; Source getSource(); std::unique_ptr ui; - ClassesModel *model; + BinClassesModel *bin_model = nullptr; + AnalClassesModel *anal_model = nullptr; ClassesSortFilterProxyModel *proxy_model; - QList classes; }; diff --git a/src/widgets/ClassesWidget.ui b/src/widgets/ClassesWidget.ui index 29554a1c..c22eb697 100644 --- a/src/widgets/ClassesWidget.ui +++ b/src/widgets/ClassesWidget.ui @@ -95,7 +95,7 @@ - Flags (Editable) + Anal (Editable) @@ -104,6 +104,21 @@ + + + Seek to VTable + + + + + Edit Method + + + + + Add Method + +