mirror of
https://github.com/rizinorg/cutter.git
synced 2024-12-24 13:55:26 +00:00
Basic Display of Anal Classes in ClassesWidget
Fetch Anal Classes Display vtable info in ClassesWidget Add Context Menu to ClassesWidget Show Base Classes in ClassesWidget Add EditMethodDialog Fix Anal Classes List Set Anal as the default Classes Source Display Anal Classes from SDB in ClassesWidget
This commit is contained in:
parent
601339d86f
commit
c7d582a893
@ -8,6 +8,7 @@
|
|||||||
#include "common/Configuration.h"
|
#include "common/Configuration.h"
|
||||||
#include "common/AsyncTask.h"
|
#include "common/AsyncTask.h"
|
||||||
#include "common/R2Task.h"
|
#include "common/R2Task.h"
|
||||||
|
#include "common/Json.h"
|
||||||
#include "Cutter.h"
|
#include "Cutter.h"
|
||||||
#include "sdb.h"
|
#include "sdb.h"
|
||||||
|
|
||||||
@ -537,6 +538,23 @@ void CutterCore::setCurrentBits(int bits, RVA offset)
|
|||||||
emit instructionChanged(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)
|
void CutterCore::seek(ut64 offset)
|
||||||
{
|
{
|
||||||
// Slower than using the API, but the API is not complete
|
// 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());
|
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)
|
void CutterCore::setConfig(const char *k, const QString &v)
|
||||||
{
|
{
|
||||||
CORE_LOCK();
|
CORE_LOCK();
|
||||||
@ -1844,16 +1873,16 @@ QList<EntrypointDescription> CutterCore::getAllEntrypoint()
|
|||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
QList<ClassDescription> CutterCore::getAllClassesFromBin()
|
QList<BinClassDescription> CutterCore::getAllClassesFromBin()
|
||||||
{
|
{
|
||||||
CORE_LOCK();
|
CORE_LOCK();
|
||||||
QList<ClassDescription> ret;
|
QList<BinClassDescription> ret;
|
||||||
|
|
||||||
QJsonArray classesArray = cmdj("icj").array();
|
QJsonArray classesArray = cmdj("icj").array();
|
||||||
for (const QJsonValue &value : classesArray) {
|
for (const QJsonValue &value : classesArray) {
|
||||||
QJsonObject classObject = value.toObject();
|
QJsonObject classObject = value.toObject();
|
||||||
|
|
||||||
ClassDescription cls;
|
BinClassDescription cls;
|
||||||
|
|
||||||
cls.name = classObject[RJsonKey::classname].toString();
|
cls.name = classObject[RJsonKey::classname].toString();
|
||||||
cls.addr = classObject[RJsonKey::addr].toVariant().toULongLong();
|
cls.addr = classObject[RJsonKey::addr].toVariant().toULongLong();
|
||||||
@ -1886,16 +1915,14 @@ QList<ClassDescription> CutterCore::getAllClassesFromBin()
|
|||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
#include <QList>
|
QList<BinClassDescription> CutterCore::getAllClassesFromFlags()
|
||||||
|
|
||||||
QList<ClassDescription> CutterCore::getAllClassesFromFlags()
|
|
||||||
{
|
{
|
||||||
static const QRegularExpression classFlagRegExp("^class\\.(.*)$");
|
static const QRegularExpression classFlagRegExp("^class\\.(.*)$");
|
||||||
static const QRegularExpression methodFlagRegExp("^method\\.([^\\.]*)\\.(.*)$");
|
static const QRegularExpression methodFlagRegExp("^method\\.([^\\.]*)\\.(.*)$");
|
||||||
|
|
||||||
CORE_LOCK();
|
CORE_LOCK();
|
||||||
QList<ClassDescription> ret;
|
QList<BinClassDescription> ret;
|
||||||
QMap<QString, ClassDescription *> classesCache;
|
QMap<QString, BinClassDescription *> classesCache;
|
||||||
|
|
||||||
QJsonArray flagsArray = cmdj("fj@F:classes").array();
|
QJsonArray flagsArray = cmdj("fj@F:classes").array();
|
||||||
for (const QJsonValue &value : flagsArray) {
|
for (const QJsonValue &value : flagsArray) {
|
||||||
@ -1906,10 +1933,10 @@ QList<ClassDescription> CutterCore::getAllClassesFromFlags()
|
|||||||
QRegularExpressionMatch match = classFlagRegExp.match(flagName);
|
QRegularExpressionMatch match = classFlagRegExp.match(flagName);
|
||||||
if (match.hasMatch()) {
|
if (match.hasMatch()) {
|
||||||
QString className = match.captured(1);
|
QString className = match.captured(1);
|
||||||
ClassDescription *desc = nullptr;
|
BinClassDescription *desc = nullptr;
|
||||||
auto it = classesCache.find(className);
|
auto it = classesCache.find(className);
|
||||||
if (it == classesCache.end()) {
|
if (it == classesCache.end()) {
|
||||||
ClassDescription cls = {};
|
BinClassDescription cls = {};
|
||||||
ret << cls;
|
ret << cls;
|
||||||
desc = &ret.last();
|
desc = &ret.last();
|
||||||
classesCache[className] = desc;
|
classesCache[className] = desc;
|
||||||
@ -1918,20 +1945,20 @@ QList<ClassDescription> CutterCore::getAllClassesFromFlags()
|
|||||||
}
|
}
|
||||||
desc->name = match.captured(1);
|
desc->name = match.captured(1);
|
||||||
desc->addr = flagObject[RJsonKey::offset].toVariant().toULongLong();
|
desc->addr = flagObject[RJsonKey::offset].toVariant().toULongLong();
|
||||||
desc->index = 0;
|
desc->index = RVA_INVALID;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
match = methodFlagRegExp.match(flagName);
|
match = methodFlagRegExp.match(flagName);
|
||||||
if (match.hasMatch()) {
|
if (match.hasMatch()) {
|
||||||
QString className = match.captured(1);
|
QString className = match.captured(1);
|
||||||
ClassDescription *classDesc = nullptr;
|
BinClassDescription *classDesc = nullptr;
|
||||||
auto it = classesCache.find(className);
|
auto it = classesCache.find(className);
|
||||||
if (it == classesCache.end()) {
|
if (it == classesCache.end()) {
|
||||||
// add a new stub class, will be replaced if class flag comes after it
|
// 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.name = tr("Unknown (%1)").arg(className);
|
||||||
cls.addr = 0;
|
cls.addr = RVA_INVALID;
|
||||||
cls.index = 0;
|
cls.index = 0;
|
||||||
ret << cls;
|
ret << cls;
|
||||||
classDesc = &ret.last();
|
classDesc = &ret.last();
|
||||||
@ -1950,6 +1977,26 @@ QList<ClassDescription> CutterCore::getAllClassesFromFlags()
|
|||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QList<QString> CutterCore::getAllClassesFromAnal()
|
||||||
|
{
|
||||||
|
QList<QString> 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<SdbKv *>(entry);
|
||||||
|
ret.append(QString::fromUtf8(reinterpret_cast<const char *>(kv->base.key)));
|
||||||
|
}
|
||||||
|
ls_free(l);
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
QList<ResourcesDescription> CutterCore::getAllResources()
|
QList<ResourcesDescription> CutterCore::getAllResources()
|
||||||
{
|
{
|
||||||
CORE_LOCK();
|
CORE_LOCK();
|
||||||
|
34
src/Cutter.h
34
src/Cutter.h
@ -251,20 +251,28 @@ struct DisassemblyLine {
|
|||||||
QString text;
|
QString text;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct ClassBaseClassDescription {
|
||||||
|
QString name;
|
||||||
|
RVA offset;
|
||||||
|
};
|
||||||
|
|
||||||
struct ClassMethodDescription {
|
struct ClassMethodDescription {
|
||||||
QString name;
|
QString name;
|
||||||
RVA addr;
|
RVA addr = RVA_INVALID;
|
||||||
|
st64 vtableOffset = -1;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct ClassFieldDescription {
|
struct ClassFieldDescription {
|
||||||
QString name;
|
QString name;
|
||||||
RVA addr;
|
RVA addr = RVA_INVALID;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct ClassDescription {
|
struct BinClassDescription {
|
||||||
QString name;
|
QString name;
|
||||||
RVA addr;
|
RVA addr = RVA_INVALID;
|
||||||
ut64 index;
|
RVA vtableAddr = RVA_INVALID;
|
||||||
|
ut64 index = 0;
|
||||||
|
QList<ClassBaseClassDescription> baseClasses;
|
||||||
QList<ClassMethodDescription> methods;
|
QList<ClassMethodDescription> methods;
|
||||||
QList<ClassFieldDescription> fields;
|
QList<ClassFieldDescription> fields;
|
||||||
};
|
};
|
||||||
@ -357,8 +365,8 @@ Q_DECLARE_METATYPE(RCorePluginDescription)
|
|||||||
Q_DECLARE_METATYPE(RAsmPluginDescription)
|
Q_DECLARE_METATYPE(RAsmPluginDescription)
|
||||||
Q_DECLARE_METATYPE(ClassMethodDescription)
|
Q_DECLARE_METATYPE(ClassMethodDescription)
|
||||||
Q_DECLARE_METATYPE(ClassFieldDescription)
|
Q_DECLARE_METATYPE(ClassFieldDescription)
|
||||||
Q_DECLARE_METATYPE(ClassDescription)
|
Q_DECLARE_METATYPE(BinClassDescription)
|
||||||
Q_DECLARE_METATYPE(const ClassDescription *)
|
Q_DECLARE_METATYPE(const BinClassDescription *)
|
||||||
Q_DECLARE_METATYPE(const ClassMethodDescription *)
|
Q_DECLARE_METATYPE(const ClassMethodDescription *)
|
||||||
Q_DECLARE_METATYPE(const ClassFieldDescription *)
|
Q_DECLARE_METATYPE(const ClassFieldDescription *)
|
||||||
Q_DECLARE_METATYPE(ResourcesDescription)
|
Q_DECLARE_METATYPE(ResourcesDescription)
|
||||||
@ -446,6 +454,10 @@ public:
|
|||||||
void setImmediateBase(const QString &r2BaseName, RVA offset = RVA_INVALID);
|
void setImmediateBase(const QString &r2BaseName, RVA offset = RVA_INVALID);
|
||||||
void setCurrentBits(int bits, 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 */
|
/* File related methods */
|
||||||
bool loadFile(QString path, ut64 baddr = 0LL, ut64 mapaddr = 0LL, int perms = R_PERM_R,
|
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());
|
int va = 0, bool loadbin = false, const QString &forceBinPlugin = QString());
|
||||||
@ -481,6 +493,8 @@ public:
|
|||||||
|
|
||||||
/* Math functions */
|
/* Math functions */
|
||||||
ut64 math(const QString &expr);
|
ut64 math(const QString &expr);
|
||||||
|
ut64 num(const QString &expr);
|
||||||
|
QString itoa(ut64 num, int rdx = 16);
|
||||||
|
|
||||||
/* Config functions */
|
/* Config functions */
|
||||||
void setConfig(const char *k, const QString &v);
|
void setConfig(const char *k, const QString &v);
|
||||||
@ -603,8 +617,9 @@ public:
|
|||||||
QList<SectionDescription> getAllSections();
|
QList<SectionDescription> getAllSections();
|
||||||
QList<SegmentDescription> getAllSegments();
|
QList<SegmentDescription> getAllSegments();
|
||||||
QList<EntrypointDescription> getAllEntrypoint();
|
QList<EntrypointDescription> getAllEntrypoint();
|
||||||
QList<ClassDescription> getAllClassesFromBin();
|
QList<BinClassDescription> getAllClassesFromBin();
|
||||||
QList<ClassDescription> getAllClassesFromFlags();
|
QList<BinClassDescription> getAllClassesFromFlags();
|
||||||
|
QList<QString> getAllClassesFromAnal();
|
||||||
QList<ResourcesDescription> getAllResources();
|
QList<ResourcesDescription> getAllResources();
|
||||||
QList<VTableDescription> getAllVTables();
|
QList<VTableDescription> getAllVTables();
|
||||||
|
|
||||||
@ -677,6 +692,7 @@ signals:
|
|||||||
void functionsChanged();
|
void functionsChanged();
|
||||||
void flagsChanged();
|
void flagsChanged();
|
||||||
void commentsChanged();
|
void commentsChanged();
|
||||||
|
void classesChanged();
|
||||||
void registersChanged();
|
void registersChanged();
|
||||||
void instructionChanged(RVA offset);
|
void instructionChanged(RVA offset);
|
||||||
void breakpointsChanged();
|
void breakpointsChanged();
|
||||||
|
@ -226,8 +226,8 @@ SOURCES += \
|
|||||||
common/CutterSeekable.cpp \
|
common/CutterSeekable.cpp \
|
||||||
common/RefreshDeferrer.cpp \
|
common/RefreshDeferrer.cpp \
|
||||||
dialogs/WelcomeDialog.cpp \
|
dialogs/WelcomeDialog.cpp \
|
||||||
RunScriptTask.cpp
|
RunScriptTask.cpp \
|
||||||
|
dialogs/EditMethodDialog.cpp
|
||||||
|
|
||||||
HEADERS += \
|
HEADERS += \
|
||||||
Cutter.h \
|
Cutter.h \
|
||||||
@ -335,7 +335,9 @@ HEADERS += \
|
|||||||
common/CutterSeekable.h \
|
common/CutterSeekable.h \
|
||||||
common/RefreshDeferrer.h \
|
common/RefreshDeferrer.h \
|
||||||
dialogs/WelcomeDialog.h \
|
dialogs/WelcomeDialog.h \
|
||||||
RunScriptTask.h
|
RunScriptTask.h \
|
||||||
|
common/Json.h \
|
||||||
|
dialogs/EditMethodDialog.h
|
||||||
|
|
||||||
FORMS += \
|
FORMS += \
|
||||||
dialogs/AboutDialog.ui \
|
dialogs/AboutDialog.ui \
|
||||||
@ -394,7 +396,8 @@ FORMS += \
|
|||||||
widgets/CutterTreeView.ui \
|
widgets/CutterTreeView.ui \
|
||||||
widgets/ComboQuickFilterView.ui \
|
widgets/ComboQuickFilterView.ui \
|
||||||
dialogs/HexdumpRangeDialog.ui \
|
dialogs/HexdumpRangeDialog.ui \
|
||||||
dialogs/WelcomeDialog.ui
|
dialogs/WelcomeDialog.ui \
|
||||||
|
dialogs/EditMethodDialog.ui
|
||||||
|
|
||||||
RESOURCES += \
|
RESOURCES += \
|
||||||
resources.qrc \
|
resources.qrc \
|
||||||
|
19
src/common/Json.h
Normal file
19
src/common/Json.h
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
|
||||||
|
#ifndef CUTTER_JSON_H
|
||||||
|
#define CUTTER_JSON_H
|
||||||
|
|
||||||
|
#include "Cutter.h"
|
||||||
|
|
||||||
|
#include <QJsonValue>
|
||||||
|
|
||||||
|
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
|
145
src/dialogs/EditMethodDialog.cpp
Normal file
145
src/dialogs/EditMethodDialog.cpp
Normal file
@ -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<BinClassDescription>(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; i<ui->classComboBox->count(); i++) {
|
||||||
|
BinClassDescription cls = ui->classComboBox->itemData(i).value<BinClassDescription>();
|
||||||
|
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<BinClassDescription>().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);
|
||||||
|
}
|
44
src/dialogs/EditMethodDialog.h
Normal file
44
src/dialogs/EditMethodDialog.h
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
#ifndef EDITMETHODDIALOG_H
|
||||||
|
#define EDITMETHODDIALOG_H
|
||||||
|
|
||||||
|
#include <QDialog>
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
#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::EditMethodDialog> ui;
|
||||||
|
|
||||||
|
bool inputValid();
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // EDITMETHODDIALOG_H
|
125
src/dialogs/EditMethodDialog.ui
Normal file
125
src/dialogs/EditMethodDialog.ui
Normal file
@ -0,0 +1,125 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<ui version="4.0">
|
||||||
|
<class>EditMethodDialog</class>
|
||||||
|
<widget class="QDialog" name="EditMethodDialog">
|
||||||
|
<property name="windowModality">
|
||||||
|
<enum>Qt::NonModal</enum>
|
||||||
|
</property>
|
||||||
|
<property name="geometry">
|
||||||
|
<rect>
|
||||||
|
<x>0</x>
|
||||||
|
<y>0</y>
|
||||||
|
<width>461</width>
|
||||||
|
<height>238</height>
|
||||||
|
</rect>
|
||||||
|
</property>
|
||||||
|
<property name="windowTitle">
|
||||||
|
<string notr="true">Method</string>
|
||||||
|
</property>
|
||||||
|
<layout class="QVBoxLayout" name="verticalLayout">
|
||||||
|
<item>
|
||||||
|
<layout class="QFormLayout" name="formLayout">
|
||||||
|
<item row="0" column="0">
|
||||||
|
<widget class="QLabel" name="label">
|
||||||
|
<property name="text">
|
||||||
|
<string>Class:</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="0" column="1">
|
||||||
|
<widget class="QComboBox" name="classComboBox"/>
|
||||||
|
</item>
|
||||||
|
<item row="1" column="0">
|
||||||
|
<widget class="QLabel" name="nameLabel">
|
||||||
|
<property name="text">
|
||||||
|
<string>Name:</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="1" column="1">
|
||||||
|
<widget class="QLineEdit" name="nameEdit"/>
|
||||||
|
</item>
|
||||||
|
<item row="2" column="0">
|
||||||
|
<widget class="QLabel" name="addressLabel">
|
||||||
|
<property name="text">
|
||||||
|
<string>Address:</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="2" column="1">
|
||||||
|
<widget class="QLineEdit" name="addressEdit"/>
|
||||||
|
</item>
|
||||||
|
<item row="3" column="0">
|
||||||
|
<widget class="QLabel" name="virtualLabel">
|
||||||
|
<property name="text">
|
||||||
|
<string>Virtual:</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="3" column="1">
|
||||||
|
<widget class="QCheckBox" name="virtualCheckBox">
|
||||||
|
<property name="text">
|
||||||
|
<string/>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="4" column="0">
|
||||||
|
<widget class="QLabel" name="vtableOffsetLabel">
|
||||||
|
<property name="text">
|
||||||
|
<string>Offset in VTable:</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="4" column="1">
|
||||||
|
<widget class="QLineEdit" name="vtableOffsetEdit"/>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QDialogButtonBox" name="buttonBox">
|
||||||
|
<property name="orientation">
|
||||||
|
<enum>Qt::Horizontal</enum>
|
||||||
|
</property>
|
||||||
|
<property name="standardButtons">
|
||||||
|
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
|
<resources/>
|
||||||
|
<connections>
|
||||||
|
<connection>
|
||||||
|
<sender>buttonBox</sender>
|
||||||
|
<signal>accepted()</signal>
|
||||||
|
<receiver>EditMethodDialog</receiver>
|
||||||
|
<slot>accept()</slot>
|
||||||
|
<hints>
|
||||||
|
<hint type="sourcelabel">
|
||||||
|
<x>248</x>
|
||||||
|
<y>254</y>
|
||||||
|
</hint>
|
||||||
|
<hint type="destinationlabel">
|
||||||
|
<x>157</x>
|
||||||
|
<y>274</y>
|
||||||
|
</hint>
|
||||||
|
</hints>
|
||||||
|
</connection>
|
||||||
|
<connection>
|
||||||
|
<sender>buttonBox</sender>
|
||||||
|
<signal>rejected()</signal>
|
||||||
|
<receiver>EditMethodDialog</receiver>
|
||||||
|
<slot>reject()</slot>
|
||||||
|
<hints>
|
||||||
|
<hint type="sourcelabel">
|
||||||
|
<x>316</x>
|
||||||
|
<y>260</y>
|
||||||
|
</hint>
|
||||||
|
<hint type="destinationlabel">
|
||||||
|
<x>286</x>
|
||||||
|
<y>274</y>
|
||||||
|
</hint>
|
||||||
|
</hints>
|
||||||
|
</connection>
|
||||||
|
</connections>
|
||||||
|
</ui>
|
@ -5,146 +5,11 @@
|
|||||||
#include "MainWindow.h"
|
#include "MainWindow.h"
|
||||||
#include "ui_ClassesWidget.h"
|
#include "ui_ClassesWidget.h"
|
||||||
#include "common/Helpers.h"
|
#include "common/Helpers.h"
|
||||||
|
#include "dialogs/EditMethodDialog.h"
|
||||||
|
|
||||||
ClassesModel::ClassesModel(QList<ClassDescription> *classes, QObject *parent)
|
#include <QMenu>
|
||||||
: QAbstractItemModel(parent),
|
|
||||||
classes(classes)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
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<int>(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
|
QVariant ClassesModel::headerData(int section, Qt::Orientation, int role) const
|
||||||
{
|
{
|
||||||
switch (role) {
|
switch (role) {
|
||||||
@ -156,6 +21,8 @@ QVariant ClassesModel::headerData(int section, Qt::Orientation, int role) const
|
|||||||
return tr("Type");
|
return tr("Type");
|
||||||
case OFFSET:
|
case OFFSET:
|
||||||
return tr("Offset");
|
return tr("Offset");
|
||||||
|
case VTABLE:
|
||||||
|
return tr("VTable");
|
||||||
default:
|
default:
|
||||||
return QVariant();
|
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<BinClassDescription> &classes)
|
||||||
|
{
|
||||||
|
beginResetModel();
|
||||||
|
this->classes = classes;
|
||||||
|
endResetModel();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
ClassesSortFilterProxyModel::ClassesSortFilterProxyModel(ClassesModel *source_model,
|
QModelIndex BinClassesModel::index(int row, int column, const QModelIndex &parent) const
|
||||||
QObject *parent)
|
{
|
||||||
|
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<int>(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<QString> &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<int>(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)
|
: QSortFilterProxyModel(parent)
|
||||||
{
|
{
|
||||||
setSourceModel(source_model);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ClassesSortFilterProxyModel::filterAcceptsRow(int row, const QModelIndex &parent) const
|
bool ClassesSortFilterProxyModel::filterAcceptsRow(int row, const QModelIndex &parent) const
|
||||||
@ -214,41 +445,61 @@ ClassesWidget::ClassesWidget(MainWindow *main, QAction *action) :
|
|||||||
{
|
{
|
||||||
ui->setupUi(this);
|
ui->setupUi(this);
|
||||||
|
|
||||||
model = new ClassesModel(&classes, this);
|
proxy_model = new ClassesSortFilterProxyModel(this);
|
||||||
proxy_model = new ClassesSortFilterProxyModel(model, this);
|
ui->classesTreeView->setModel(nullptr);
|
||||||
ui->classesTreeView->setModel(proxy_model);
|
|
||||||
ui->classesTreeView->sortByColumn(ClassesModel::TYPE, Qt::AscendingOrder);
|
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(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->classSourceCombo, SIGNAL(currentIndexChanged(int)), this, SLOT(refreshClasses()));
|
||||||
|
connect(ui->classesTreeView, &QTreeView::customContextMenuRequested, this, &ClassesWidget::showContextMenu);
|
||||||
}
|
}
|
||||||
|
|
||||||
ClassesWidget::~ClassesWidget() {}
|
ClassesWidget::~ClassesWidget() {}
|
||||||
|
|
||||||
ClassesWidget::Source ClassesWidget::getSource()
|
ClassesWidget::Source ClassesWidget::getSource()
|
||||||
{
|
{
|
||||||
if (ui->classSourceCombo->currentIndex() == 1) {
|
switch (ui->classSourceCombo->currentIndex()) {
|
||||||
return Source::FLAGS;
|
case 0:
|
||||||
} else {
|
|
||||||
return Source::BIN;
|
return Source::BIN;
|
||||||
}
|
default:
|
||||||
}
|
return Source::ANAL;
|
||||||
|
|
||||||
void ClassesWidget::flagsChanged()
|
|
||||||
{
|
|
||||||
if (getSource() == Source::FLAGS) {
|
|
||||||
refreshClasses();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void ClassesWidget::refreshClasses()
|
void ClassesWidget::refreshClasses()
|
||||||
{
|
{
|
||||||
model->beginResetModel();
|
switch (getSource()) {
|
||||||
classes = getSource() == Source::BIN
|
case Source::BIN:
|
||||||
? Core()->getAllClassesFromBin()
|
if (!bin_model) {
|
||||||
: Core()->getAllClassesFromFlags();
|
proxy_model->setSourceModel(nullptr);
|
||||||
model->endResetModel();
|
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);
|
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<RVA>();
|
RVA offset = index.data(ClassesModel::OffsetRole).value<RVA>();
|
||||||
Core()->seek(offset);
|
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<RVA>();
|
||||||
|
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<ClassMethodDescription>();
|
||||||
|
EditMethodDialog::editMethod(className, meth);
|
||||||
|
}
|
||||||
|
@ -18,46 +18,76 @@ class QTreeWidgetItem;
|
|||||||
class MainWindow;
|
class MainWindow;
|
||||||
class ClassesWidget;
|
class ClassesWidget;
|
||||||
|
|
||||||
|
|
||||||
class ClassesModel: public QAbstractItemModel
|
class ClassesModel: public QAbstractItemModel
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
|
||||||
|
|
||||||
friend ClassesWidget;
|
|
||||||
|
|
||||||
private:
|
|
||||||
QList<ClassDescription> *classes;
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
enum Columns { NAME = 0, TYPE, OFFSET, COUNT };
|
enum Columns { NAME = 0, TYPE, OFFSET, VTABLE, COUNT };
|
||||||
enum RowType { CLASS = 0, METHOD = 1, FIELD = 2 };
|
enum RowType { CLASS = 0, METHOD = 1, FIELD = 2, BASE = 3 };
|
||||||
|
|
||||||
static const int OffsetRole = Qt::UserRole;
|
static const int OffsetRole = Qt::UserRole;
|
||||||
static const int NameRole = Qt::UserRole + 1;
|
static const int NameRole = Qt::UserRole + 1;
|
||||||
static const int TypeRole = Qt::UserRole + 2;
|
static const int TypeRole = Qt::UserRole + 2;
|
||||||
|
static const int VTableOffsetRole = Qt::UserRole + 3;
|
||||||
|
static const int DataRole = Qt::UserRole + 4;
|
||||||
|
|
||||||
explicit ClassesModel(QList<ClassDescription> *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,
|
QVariant headerData(int section, Qt::Orientation orientation,
|
||||||
int role = Qt::DisplayRole) const override;
|
int role = Qt::DisplayRole) const override;
|
||||||
};
|
};
|
||||||
|
|
||||||
Q_DECLARE_METATYPE(ClassesModel::RowType)
|
Q_DECLARE_METATYPE(ClassesModel::RowType)
|
||||||
|
|
||||||
|
class BinClassesModel: public ClassesModel
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
private:
|
||||||
|
QList<BinClassDescription> 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<BinClassDescription> &classes);
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
class AnalClassesModel: public ClassesModel
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
private:
|
||||||
|
QList<QString> 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<QString> &classes);
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class ClassesSortFilterProxyModel : public QSortFilterProxyModel
|
class ClassesSortFilterProxyModel : public QSortFilterProxyModel
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit ClassesSortFilterProxyModel(ClassesModel *source_model, QObject *parent = nullptr);
|
explicit ClassesSortFilterProxyModel(QObject *parent = nullptr);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
bool filterAcceptsRow(int row, const QModelIndex &parent) const override;
|
bool filterAcceptsRow(int row, const QModelIndex &parent) const override;
|
||||||
@ -77,19 +107,24 @@ public:
|
|||||||
private slots:
|
private slots:
|
||||||
void on_classesTreeView_doubleClicked(const QModelIndex &index);
|
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 refreshClasses();
|
||||||
void flagsChanged();
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
enum class Source { BIN, FLAGS };
|
enum class Source { BIN, ANAL };
|
||||||
|
|
||||||
Source getSource();
|
Source getSource();
|
||||||
|
|
||||||
std::unique_ptr<Ui::ClassesWidget> ui;
|
std::unique_ptr<Ui::ClassesWidget> ui;
|
||||||
|
|
||||||
ClassesModel *model;
|
BinClassesModel *bin_model = nullptr;
|
||||||
|
AnalClassesModel *anal_model = nullptr;
|
||||||
ClassesSortFilterProxyModel *proxy_model;
|
ClassesSortFilterProxyModel *proxy_model;
|
||||||
QList<ClassDescription> classes;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
@ -95,7 +95,7 @@
|
|||||||
</item>
|
</item>
|
||||||
<item>
|
<item>
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>Flags (Editable)</string>
|
<string>Anal (Editable)</string>
|
||||||
</property>
|
</property>
|
||||||
</item>
|
</item>
|
||||||
</widget>
|
</widget>
|
||||||
@ -104,6 +104,21 @@
|
|||||||
</item>
|
</item>
|
||||||
</layout>
|
</layout>
|
||||||
</widget>
|
</widget>
|
||||||
|
<action name="seekToVTableAction">
|
||||||
|
<property name="text">
|
||||||
|
<string>Seek to VTable</string>
|
||||||
|
</property>
|
||||||
|
</action>
|
||||||
|
<action name="editMethodAction">
|
||||||
|
<property name="text">
|
||||||
|
<string>Edit Method</string>
|
||||||
|
</property>
|
||||||
|
</action>
|
||||||
|
<action name="addMethodAction">
|
||||||
|
<property name="text">
|
||||||
|
<string>Add Method</string>
|
||||||
|
</property>
|
||||||
|
</action>
|
||||||
</widget>
|
</widget>
|
||||||
<customwidgets>
|
<customwidgets>
|
||||||
<customwidget>
|
<customwidget>
|
||||||
|
Loading…
Reference in New Issue
Block a user