261 lines
9.3 KiB
C++
261 lines
9.3 KiB
C++
#include "StructWidget.h"
|
|
#include "ui_StructWidget.h"
|
|
#include "Configuration.h"
|
|
#include "MenuBuilder.h"
|
|
#include "LineEditDialog.h"
|
|
#include "GotoDialog.h"
|
|
#include <QFileDialog>
|
|
#include "StringUtil.h"
|
|
#include "MiscUtil.h"
|
|
|
|
struct TypeDescriptor
|
|
{
|
|
TYPEDESCRIPTOR type;
|
|
QString name;
|
|
};
|
|
Q_DECLARE_METATYPE(TypeDescriptor)
|
|
|
|
StructWidget::StructWidget(QWidget* parent) :
|
|
QWidget(parent),
|
|
ui(new Ui::StructWidget)
|
|
{
|
|
ui->setupUi(this);
|
|
ui->treeWidget->setStyleSheet("QTreeWidget { color: #000000; background-color: #FFF8F0; alternate-background-color: #DCD9CF; }");
|
|
connect(Bridge::getBridge(), SIGNAL(typeAddNode(void*, const TYPEDESCRIPTOR*)), this, SLOT(typeAddNode(void*, const TYPEDESCRIPTOR*)));
|
|
connect(Bridge::getBridge(), SIGNAL(typeClear()), this, SLOT(typeClear()));
|
|
connect(Bridge::getBridge(), SIGNAL(typeUpdateWidget()), this, SLOT(typeUpdateWidget()));
|
|
connect(Bridge::getBridge(), SIGNAL(dbgStateChanged(DBGSTATE)), this, SLOT(dbgStateChangedSlot(DBGSTATE)));
|
|
connect(Config(), SIGNAL(colorsUpdated()), this, SLOT(colorsUpdatedSlot()));
|
|
connect(Config(), SIGNAL(fontsUpdated()), this, SLOT(fontsUpdatedSlot()));
|
|
connect(Config(), SIGNAL(shortcutsUpdated()), this, SLOT(shortcutsUpdatedSlot()));
|
|
colorsUpdatedSlot();
|
|
fontsUpdatedSlot();
|
|
setupContextMenu();
|
|
setupColumns();
|
|
}
|
|
|
|
StructWidget::~StructWidget()
|
|
{
|
|
delete ui;
|
|
}
|
|
|
|
void StructWidget::colorsUpdatedSlot()
|
|
{
|
|
auto color = ConfigColor("AbstractTableViewTextColor");
|
|
auto background = ConfigColor("StructBackgroundColor");
|
|
auto altBackground = ConfigColor("StructAlternateBackgroundColor");
|
|
auto style = QString("QTreeWidget { color: %1; background-color: %2; alternate-background-color: %3; }").arg(color.name(), background.name(), altBackground.name());
|
|
ui->treeWidget->setStyleSheet(style);
|
|
}
|
|
|
|
void StructWidget::fontsUpdatedSlot()
|
|
{
|
|
auto font = ConfigFont("AbstractTableView");
|
|
setFont(font);
|
|
ui->treeWidget->setFont(font);
|
|
ui->treeWidget->header()->setFont(font);
|
|
}
|
|
|
|
void StructWidget::shortcutsUpdatedSlot()
|
|
{
|
|
updateShortcuts();
|
|
}
|
|
|
|
void StructWidget::typeAddNode(void* parent, const TYPEDESCRIPTOR* type)
|
|
{
|
|
TypeDescriptor dtype;
|
|
dtype.type = *type;
|
|
dtype.name = QString(dtype.type.name);
|
|
dtype.type.name = nullptr;
|
|
auto text = QStringList() << dtype.name << ToPtrString(dtype.type.addr + dtype.type.offset) << "0x" + ToHexString(dtype.type.size);
|
|
QTreeWidgetItem* item = parent ? new QTreeWidgetItem((QTreeWidgetItem*)parent, text) : new QTreeWidgetItem(ui->treeWidget, text);
|
|
item->setExpanded(dtype.type.expanded);
|
|
QVariant var;
|
|
var.setValue(dtype);
|
|
item->setData(0, Qt::UserRole, var);
|
|
Bridge::getBridge()->setResult(BridgeResult::TypeAddNode, dsint(item));
|
|
}
|
|
|
|
void StructWidget::typeClear()
|
|
{
|
|
ui->treeWidget->clear();
|
|
Bridge::getBridge()->setResult(BridgeResult::TypeClear);
|
|
}
|
|
|
|
void StructWidget::typeUpdateWidget()
|
|
{
|
|
for(QTreeWidgetItemIterator it(ui->treeWidget); *it; ++it)
|
|
{
|
|
QTreeWidgetItem* item = *it;
|
|
auto type = item->data(0, Qt::UserRole).value<TypeDescriptor>();
|
|
auto name = type.name.toUtf8();
|
|
type.type.name = name.constData();
|
|
auto addr = type.type.addr + type.type.offset;
|
|
item->setText(1, ToPtrString(addr));
|
|
QString valueStr;
|
|
if(type.type.callback) //use the provided callback
|
|
{
|
|
char value[128] = "";
|
|
size_t valueCount = _countof(value);
|
|
if(!type.type.callback(&type.type, value, &valueCount) && valueCount && valueCount != _countof(value))
|
|
{
|
|
auto dest = new char[valueCount];
|
|
if(type.type.callback(&type.type, dest, &valueCount))
|
|
valueStr = value;
|
|
else
|
|
valueStr = "???";
|
|
delete[] dest;
|
|
}
|
|
else
|
|
valueStr = value;
|
|
}
|
|
else if(!item->childCount() && type.type.size > 0 && type.type.size <= sizeof(uint64_t)) //attempt to display small, non-parent values
|
|
{
|
|
uint64_t data;
|
|
if(DbgMemRead(addr, &data, type.type.size))
|
|
{
|
|
if(type.type.reverse)
|
|
std::reverse((char*)data, (char*)data + type.type.size);
|
|
valueStr = QString().sprintf("0x%llX, %llu", data, data, data);
|
|
}
|
|
else if(type.type.addr)
|
|
valueStr = "???";
|
|
}
|
|
item->setText(3, valueStr);
|
|
}
|
|
}
|
|
|
|
void StructWidget::dbgStateChangedSlot(DBGSTATE state)
|
|
{
|
|
if(state == stopped)
|
|
ui->treeWidget->clear();
|
|
}
|
|
|
|
void StructWidget::setupColumns()
|
|
{
|
|
ui->treeWidget->setColumnWidth(0, 300); //Name
|
|
ui->treeWidget->setColumnWidth(1, 80); //Address
|
|
ui->treeWidget->setColumnWidth(2, 80); //Size
|
|
//ui->treeWidget->setColumnWidth(3, 80); //Value
|
|
}
|
|
|
|
#define hasSelection !!ui->treeWidget->selectedItems().count()
|
|
#define selectedItem ui->treeWidget->selectedItems()[0]
|
|
#define selectedType selectedItem->data(0, Qt::UserRole).value<TypeDescriptor>().type
|
|
|
|
void StructWidget::setupContextMenu()
|
|
{
|
|
mMenuBuilder = new MenuBuilder(this);
|
|
mMenuBuilder->addAction(makeAction(DIcon("dump.png"), tr("&Follow in Dump"), SLOT(followDumpSlot())), [this](QMenu*)
|
|
{
|
|
return hasSelection && DbgMemIsValidReadPtr(selectedType.addr + selectedType.offset);
|
|
});
|
|
mMenuBuilder->addAction(makeAction(DIcon("structaddr.png"), tr("Change address"), SLOT(changeAddrSlot())), [this](QMenu*)
|
|
{
|
|
return hasSelection && !selectedItem->parent() && DbgIsDebugging();
|
|
});
|
|
mMenuBuilder->addAction(makeAction(DIcon("visitstruct.png"), tr("Visit type"), SLOT(visitSlot())));
|
|
mMenuBuilder->addAction(makeAction(DIcon("database-import.png"), tr("Load JSON"), SLOT(loadJsonSlot())));
|
|
mMenuBuilder->addAction(makeAction(DIcon("source.png"), tr("Parse header"), SLOT(parseFileSlot())));
|
|
mMenuBuilder->addAction(makeAction(DIcon("removestruct.png"), tr("Remove"), SLOT(removeSlot())), [this](QMenu*)
|
|
{
|
|
return hasSelection && !selectedItem->parent();
|
|
});
|
|
mMenuBuilder->addAction(makeAction(DIcon("eraser.png"), tr("Clear"), SLOT(clearSlot())));
|
|
mMenuBuilder->addAction(makeShortcutAction(DIcon("sync.png"), tr("&Refresh"), SLOT(refreshSlot()), "ActionRefresh"));
|
|
mMenuBuilder->loadFromConfig();
|
|
}
|
|
|
|
void StructWidget::on_treeWidget_customContextMenuRequested(const QPoint & pos)
|
|
{
|
|
QMenu wMenu;
|
|
mMenuBuilder->build(&wMenu);
|
|
if(wMenu.actions().count())
|
|
wMenu.exec(ui->treeWidget->viewport()->mapToGlobal(pos));
|
|
}
|
|
|
|
void StructWidget::followDumpSlot()
|
|
{
|
|
if(!hasSelection)
|
|
return;
|
|
DbgCmdExec(QString("dump %1").arg(ToPtrString(selectedType.addr + selectedType.offset)));
|
|
}
|
|
|
|
void StructWidget::clearSlot()
|
|
{
|
|
ui->treeWidget->clear();
|
|
}
|
|
|
|
void StructWidget::removeSlot()
|
|
{
|
|
if(!hasSelection)
|
|
return;
|
|
delete selectedItem;
|
|
}
|
|
|
|
void StructWidget::visitSlot()
|
|
{
|
|
//TODO: replace with a list to pick from
|
|
LineEditDialog mLineEdit(this);
|
|
mLineEdit.setWindowTitle(tr("Type to visit"));
|
|
if(mLineEdit.exec() != QDialog::Accepted || !mLineEdit.editText.length())
|
|
return;
|
|
if(!mGotoDialog)
|
|
mGotoDialog = new GotoDialog(this);
|
|
duint addr = 0;
|
|
mGotoDialog->setWindowTitle(tr("Address to visit"));
|
|
if(DbgIsDebugging() && mGotoDialog->exec() == QDialog::Accepted)
|
|
addr = DbgValFromString(mGotoDialog->expressionText.toUtf8().constData());
|
|
DbgCmdExec(QString("VisitType %1, %2").arg(mLineEdit.editText, ToPtrString(addr)));
|
|
}
|
|
|
|
void StructWidget::loadJsonSlot()
|
|
{
|
|
auto filename = QFileDialog::getOpenFileName(this, tr("Load JSON"), QString(), tr("JSON files (*.json);;All files (*.*)"));
|
|
if(!filename.length())
|
|
return;
|
|
filename = QDir::toNativeSeparators(filename);
|
|
DbgCmdExec(QString("LoadTypes \"%1\"").arg(filename));
|
|
}
|
|
|
|
void StructWidget::parseFileSlot()
|
|
{
|
|
auto filename = QFileDialog::getOpenFileName(this, tr("Parse header"), QString(), tr("Header files (*.h *.hpp);;All files (*.*)"));
|
|
if(!filename.length())
|
|
return;
|
|
filename = QDir::toNativeSeparators(filename);
|
|
DbgCmdExec(QString("ParseTypes \"%1\"").arg(filename));
|
|
}
|
|
|
|
static void changeTypeAddr(QTreeWidgetItem* item, duint addr)
|
|
{
|
|
auto changeAddr = item->data(0, Qt::UserRole).value<TypeDescriptor>().type.addr;
|
|
for(QTreeWidgetItemIterator it(item); *it; ++it)
|
|
{
|
|
QTreeWidgetItem* item = *it;
|
|
auto type = item->data(0, Qt::UserRole).value<TypeDescriptor>();
|
|
type.type.addr = type.type.addr == changeAddr ? addr : 0; //invalidate pointers (requires revisit)
|
|
QVariant var;
|
|
var.setValue(type);
|
|
item->setData(0, Qt::UserRole, var);
|
|
}
|
|
}
|
|
|
|
void StructWidget::changeAddrSlot()
|
|
{
|
|
if(!hasSelection || !DbgIsDebugging())
|
|
return;
|
|
if(!mGotoDialog)
|
|
mGotoDialog = new GotoDialog(this);
|
|
mGotoDialog->setWindowTitle(tr("Change address"));
|
|
if(mGotoDialog->exec() != QDialog::Accepted)
|
|
return;
|
|
changeTypeAddr(selectedItem, DbgValFromString(mGotoDialog->expressionText.toUtf8().constData()));
|
|
refreshSlot();
|
|
}
|
|
|
|
void StructWidget::refreshSlot()
|
|
{
|
|
typeUpdateWidget();
|
|
}
|