mirror of
https://github.com/devinacker/bsnes-plus.git
synced 2025-04-02 10:52:46 -04:00
526 lines
17 KiB
C++
526 lines
17 KiB
C++
#include "breakpoint.moc"
|
|
BreakpointEditor *breakpointEditor;
|
|
|
|
SymbolItemModel::SymbolItemModel(SymbolMap *map, QObject *parent)
|
|
: QStandardItemModel(parent) {
|
|
|
|
foreach(symbols, map->symbols) {
|
|
Symbol symbol = symbols.getSymbol();
|
|
if (!symbol.isSymbol()) continue;
|
|
|
|
QStandardItem *item = new QStandardItem();
|
|
item->setText(QString::asprintf("%06x %s", symbols.address, symbol.name()));
|
|
item->setData(symbols.address);
|
|
appendRow(item);
|
|
}
|
|
}
|
|
|
|
SymbolDelegate::SymbolDelegate(QObject *parent)
|
|
: QStyledItemDelegate(parent) {
|
|
}
|
|
|
|
QWidget* SymbolDelegate::createEditor(QWidget *parent, const QStyleOptionViewItem &, const QModelIndex &index) const {
|
|
SymbolMap *symbolMap = index.model()->data(index, BreakpointModel::SymbolMapRole).value<SymbolMap*>();
|
|
|
|
if (symbolMap) {
|
|
// symbols are available - use a dropdown
|
|
QComboBox *combo = new QComboBox(parent);
|
|
combo->setEditable(true);
|
|
combo->setInsertPolicy(QComboBox::NoInsert);
|
|
combo->view()->setTextElideMode(Qt::ElideRight);
|
|
|
|
SymbolItemModel *symbolModel = new SymbolItemModel(symbolMap, combo);
|
|
combo->setModel(symbolModel);
|
|
|
|
QCompleter *completer = new QCompleter(symbolModel, combo);
|
|
completer->setCaseSensitivity(Qt::CaseInsensitive);
|
|
completer->setFilterMode(Qt::MatchContains);
|
|
combo->setCompleter(completer);
|
|
return combo;
|
|
}
|
|
else
|
|
{
|
|
// no symbols - just use a line edit
|
|
QLineEdit *lineEdit = new QLineEdit(parent);
|
|
return lineEdit;
|
|
}
|
|
}
|
|
|
|
void SymbolDelegate::setEditorData(QWidget *editor, const QModelIndex &index) const {
|
|
QString text = index.model()->data(index, Qt::EditRole).toString();
|
|
|
|
if (QString(editor->metaObject()->className()) == "QComboBox") {
|
|
QComboBox *combo = qobject_cast<QComboBox*>(editor);
|
|
combo->setCurrentText(text);
|
|
combo->lineEdit()->selectAll();
|
|
} else {
|
|
QLineEdit *lineEdit = qobject_cast<QLineEdit*>(editor);
|
|
lineEdit->setText(text);
|
|
}
|
|
}
|
|
|
|
void SymbolDelegate::setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const {
|
|
if (QString(editor->metaObject()->className()) == "QComboBox") {
|
|
QComboBox *combo = qobject_cast<QComboBox*>(editor);
|
|
model->setData(index, combo->currentText());
|
|
} else {
|
|
QLineEdit *lineEdit = qobject_cast<QLineEdit*>(editor);
|
|
model->setData(index, lineEdit->text());
|
|
}
|
|
}
|
|
|
|
const QStringList BreakpointModel::sources = {
|
|
"S-CPU bus",
|
|
"S-SMP bus",
|
|
"S-DSP registers",
|
|
"S-PPU VRAM",
|
|
"S-PPU OAM",
|
|
"S-PPU CGRAM",
|
|
"SA-1 bus",
|
|
"SuperFX bus",
|
|
"Super GB bus",
|
|
};
|
|
|
|
const QStringList BreakpointModel::compares = {
|
|
"=",
|
|
"!=",
|
|
"<",
|
|
"<=",
|
|
">",
|
|
">="
|
|
};
|
|
|
|
BreakpointModel::BreakpointModel(QObject* parent)
|
|
: QAbstractTableModel(parent) {
|
|
}
|
|
|
|
int BreakpointModel::rowCount(const QModelIndex& parent) const {
|
|
return parent.isValid() ? 0 : SNES::debugger.breakpoint.size();
|
|
}
|
|
|
|
int BreakpointModel::columnCount(const QModelIndex& parent) const {
|
|
return parent.isValid() ? 0 : BreakColumnCount;
|
|
}
|
|
|
|
QString BreakpointModel::displayAddr(unsigned addr, SNES::Debugger::Breakpoint::Source source) const {
|
|
SymbolMap *symbolMap = 0;
|
|
|
|
switch (source) {
|
|
case SNES::Debugger::Breakpoint::Source::CPUBus:
|
|
symbolMap = debugger->symbolsCPU;
|
|
break;
|
|
case SNES::Debugger::Breakpoint::Source::APURAM:
|
|
symbolMap = debugger->symbolsSMP;
|
|
break;
|
|
case SNES::Debugger::Breakpoint::Source::DSP:
|
|
symbolMap = debugger->symbolsDSP;
|
|
break;
|
|
case SNES::Debugger::Breakpoint::Source::SA1Bus:
|
|
symbolMap = debugger->symbolsSA1;
|
|
break;
|
|
case SNES::Debugger::Breakpoint::Source::SFXBus:
|
|
symbolMap = debugger->symbolsSFX;
|
|
break;
|
|
case SNES::Debugger::Breakpoint::Source::SGBBus:
|
|
symbolMap = debugger->symbolsSGB;
|
|
break;
|
|
}
|
|
|
|
if (symbolMap) {
|
|
Symbol symbol = symbolMap->getSymbol(addr);
|
|
if (symbol.isSymbol()) return symbol.name;
|
|
}
|
|
|
|
return QString::asprintf("%06x", addr);
|
|
}
|
|
|
|
QVariant BreakpointModel::data(const QModelIndex &index, int role) const {
|
|
if (index.row() >= SNES::debugger.breakpoint.size())
|
|
return QVariant();
|
|
|
|
const SNES::Debugger::Breakpoint& b = SNES::debugger.breakpoint[index.row()];
|
|
|
|
if (role == Qt::DisplayRole || role == Qt::EditRole || role == Qt::ToolTipRole) {
|
|
switch (index.column()) {
|
|
case BreakAddrStart:
|
|
if (role == Qt::DisplayRole) return displayAddr(b.addr, b.source);
|
|
return QString::asprintf("%06x", b.addr);
|
|
case BreakAddrEnd:
|
|
if (b.addr_end) {
|
|
if (role == Qt::DisplayRole) return displayAddr(b.addr_end, b.source);
|
|
return QString::asprintf("%06x", b.addr_end);
|
|
}
|
|
break;
|
|
case BreakCompare:
|
|
if (role == Qt::EditRole) return (unsigned)b.compare;
|
|
if ((unsigned)b.compare < compares.count()) return compares[(unsigned)b.compare];
|
|
break;
|
|
case BreakData:
|
|
if (b.data >= 0) return QString::asprintf("%02x", b.data);
|
|
if (role == Qt::DisplayRole) return "any";
|
|
break;
|
|
case BreakRead:
|
|
return (bool)(b.mode & (unsigned)SNES::Debugger::Breakpoint::Mode::Read);
|
|
case BreakWrite:
|
|
return (bool)(b.mode & (unsigned)SNES::Debugger::Breakpoint::Mode::Write);
|
|
case BreakExecute:
|
|
return (bool)(b.mode & (unsigned)SNES::Debugger::Breakpoint::Mode::Exec);
|
|
case BreakSource:
|
|
if (role == Qt::EditRole) return (unsigned)b.source;
|
|
if ((unsigned)b.source < sources.count()) return sources[(unsigned)b.source];
|
|
break;
|
|
}
|
|
|
|
} else if (role == SymbolMapRole) {
|
|
switch (b.source) {
|
|
case SNES::Debugger::Breakpoint::Source::CPUBus:
|
|
return QVariant::fromValue(debugger->symbolsCPU);
|
|
case SNES::Debugger::Breakpoint::Source::APURAM:
|
|
return QVariant::fromValue(debugger->symbolsSMP);
|
|
case SNES::Debugger::Breakpoint::Source::DSP:
|
|
return QVariant::fromValue(debugger->symbolsDSP);
|
|
case SNES::Debugger::Breakpoint::Source::SA1Bus:
|
|
return QVariant::fromValue(debugger->symbolsSA1);
|
|
case SNES::Debugger::Breakpoint::Source::SFXBus:
|
|
return QVariant::fromValue(debugger->symbolsSFX);
|
|
case SNES::Debugger::Breakpoint::Source::SGBBus:
|
|
return QVariant::fromValue(debugger->symbolsSGB);
|
|
}
|
|
}
|
|
|
|
return QVariant();
|
|
}
|
|
|
|
QVariant BreakpointModel::headerData(int section, Qt::Orientation orientation, int role) const {
|
|
if (role == Qt::DisplayRole) {
|
|
if (orientation == Qt::Horizontal) {
|
|
switch (section) {
|
|
case BreakAddrStart: return "Start";
|
|
case BreakAddrEnd: return "End (optional)";
|
|
case BreakCompare: return "Data";
|
|
case BreakRead: return "R";
|
|
case BreakWrite: return "W";
|
|
case BreakExecute: return "X";
|
|
case BreakSource: return "Source";
|
|
}
|
|
} else {
|
|
return QString::number(section);
|
|
}
|
|
}
|
|
|
|
return QVariant();
|
|
}
|
|
|
|
bool BreakpointModel::setData(const QModelIndex &index, const QVariant &value, int role) {
|
|
if (role == Qt::EditRole && index.row() < SNES::debugger.breakpoint.size()) {
|
|
SNES::Debugger::Breakpoint& b = SNES::debugger.breakpoint[index.row()];
|
|
|
|
switch (index.column()) {
|
|
case BreakAddrStart:
|
|
// TODO allow setting by symbol name
|
|
b.addr = hex(value.toString().toUtf8().data()) & 0xffffff;
|
|
emit dataChanged(index, index);
|
|
return true;
|
|
|
|
case BreakAddrEnd:
|
|
// TODO allow setting by symbol name
|
|
if (!value.toString().isEmpty())
|
|
b.addr_end = hex(value.toString().toUtf8().data()) & 0xffffff;
|
|
else
|
|
b.addr_end = 0;
|
|
emit dataChanged(index, index);
|
|
return true;
|
|
|
|
case BreakCompare:
|
|
b.compare = (SNES::Debugger::Breakpoint::Compare)value.toInt();
|
|
emit dataChanged(index, index);
|
|
break;
|
|
|
|
case BreakData:
|
|
if (!value.toString().isEmpty())
|
|
b.data = hex(value.toString().toUtf8().data()) & 0xff;
|
|
else
|
|
b.data = -1;
|
|
emit dataChanged(index, index);
|
|
return true;
|
|
|
|
case BreakRead:
|
|
if (value.toBool())
|
|
b.mode |= (unsigned)SNES::Debugger::Breakpoint::Mode::Read;
|
|
else
|
|
b.mode &= ~(unsigned)SNES::Debugger::Breakpoint::Mode::Read;
|
|
emit dataChanged(index, index);
|
|
return true;
|
|
|
|
case BreakWrite:
|
|
if (value.toBool())
|
|
b.mode |= (unsigned)SNES::Debugger::Breakpoint::Mode::Write;
|
|
else
|
|
b.mode &= ~(unsigned)SNES::Debugger::Breakpoint::Mode::Write;
|
|
emit dataChanged(index, index);
|
|
return true;
|
|
|
|
case BreakExecute:
|
|
if (value.toBool())
|
|
b.mode |= (unsigned)SNES::Debugger::Breakpoint::Mode::Exec;
|
|
else
|
|
b.mode &= ~(unsigned)SNES::Debugger::Breakpoint::Mode::Exec;
|
|
emit dataChanged(index, index);
|
|
return true;
|
|
|
|
case BreakSource:
|
|
b.source = (SNES::Debugger::Breakpoint::Source)value.toInt();
|
|
// also refresh start+end addresses (for formatting/labels)
|
|
emit dataChanged(this->index(index.row(), BreakAddrStart), index);
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
Qt::ItemFlags BreakpointModel::flags(const QModelIndex &index) const {
|
|
return Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsEditable;
|
|
}
|
|
|
|
bool BreakpointModel::insertRows(int row, int count, const QModelIndex &parent) {
|
|
if (!parent.isValid() && row <= rowCount() && count > 0) {
|
|
beginInsertRows(parent, row, row + count - 1);
|
|
while (count--)
|
|
SNES::debugger.breakpoint.insert(row, SNES::Debugger::Breakpoint());
|
|
endInsertRows();
|
|
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool BreakpointModel::removeRows(int row, int count, const QModelIndex &parent) {
|
|
if (!parent.isValid() && row <= rowCount() && count > 0) {
|
|
beginRemoveRows(parent, row, row + count - 1);
|
|
SNES::debugger.breakpoint.remove(row, count);
|
|
endRemoveRows();
|
|
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
const QStringList BreakpointEditor::sources = {
|
|
"cpu",
|
|
"smp",
|
|
"dsp",
|
|
"vram",
|
|
"oam",
|
|
"cgram",
|
|
"sa1",
|
|
"sfx",
|
|
"sgb",
|
|
};
|
|
|
|
BreakpointEditor::BreakpointEditor() {
|
|
setObjectName("breakpoint-editor");
|
|
setWindowTitle("Breakpoint Editor");
|
|
setGeometryString(&config().geometry.breakpointEditor);
|
|
application.windowList.append(this);
|
|
|
|
layout = new QVBoxLayout;
|
|
layout->setMargin(Style::WindowMargin);
|
|
layout->setSpacing(Style::WidgetSpacing);
|
|
setLayout(layout);
|
|
|
|
model = new BreakpointModel(this);
|
|
|
|
table = new QTableView;
|
|
table->setModel(model);
|
|
table->setSelectionBehavior(QAbstractItemView::SelectRows);
|
|
table->setSelectionMode(QAbstractItemView::ContiguousSelection);
|
|
table->setHorizontalScrollMode(QAbstractItemView::ScrollPerPixel);
|
|
layout->addWidget(table);
|
|
|
|
CheckDelegate *checkDelegate = new CheckDelegate(this);
|
|
table->setItemDelegateForColumn(BreakpointModel::BreakRead, checkDelegate);
|
|
table->setItemDelegateForColumn(BreakpointModel::BreakWrite, checkDelegate);
|
|
table->setItemDelegateForColumn(BreakpointModel::BreakExecute, checkDelegate);
|
|
|
|
table->setItemDelegateForColumn(BreakpointModel::BreakAddrStart, new SymbolDelegate(this));
|
|
table->setItemDelegateForColumn(BreakpointModel::BreakAddrEnd, new SymbolDelegate(this));
|
|
|
|
table->setItemDelegateForColumn(BreakpointModel::BreakCompare, new ComboDelegate(BreakpointModel::compares, this));
|
|
table->setItemDelegateForColumn(BreakpointModel::BreakSource, new ComboDelegate(BreakpointModel::sources, this));
|
|
|
|
QHeaderView *header = table->horizontalHeader();
|
|
header->setSectionsClickable(false);
|
|
header->setStretchLastSection(true);
|
|
header->setDefaultAlignment(Qt::AlignLeft);
|
|
header->setSectionResizeMode(BreakpointModel::BreakCompare, QHeaderView::ResizeToContents);
|
|
header->setSectionResizeMode(BreakpointModel::BreakData, QHeaderView::ResizeToContents);
|
|
header->setSectionResizeMode(BreakpointModel::BreakRead, QHeaderView::ResizeToContents);
|
|
header->setSectionResizeMode(BreakpointModel::BreakWrite, QHeaderView::ResizeToContents);
|
|
header->setSectionResizeMode(BreakpointModel::BreakExecute, QHeaderView::ResizeToContents);
|
|
|
|
header = table->verticalHeader();
|
|
header->setSectionResizeMode(QHeaderView::ResizeToContents);
|
|
|
|
btnLayout = new QHBoxLayout;
|
|
btnLayout->setMargin(0);
|
|
btnLayout->setSpacing(Style::WidgetSpacing);
|
|
layout->addLayout(btnLayout);
|
|
|
|
btnAdd = new QPushButton;
|
|
btnAdd->setText("Add");
|
|
connect(btnAdd, SIGNAL(clicked(bool)), this, SLOT(add()));
|
|
btnLayout->addWidget(btnAdd);
|
|
|
|
btnRemove = new QPushButton;
|
|
btnRemove->setText("Del");
|
|
connect(btnRemove, SIGNAL(clicked(bool)), this, SLOT(remove()));
|
|
btnLayout->addWidget(btnRemove);
|
|
|
|
btnLayout->addStretch(1);
|
|
|
|
breakOnWDM = new QCheckBox("Break on WDM (CPU/SA-1 opcode 0x42)");
|
|
breakOnWDM->setChecked(SNES::debugger.break_on_wdm);
|
|
connect(breakOnWDM, SIGNAL(toggled(bool)), this, SLOT(toggle()));
|
|
layout->addWidget(breakOnWDM);
|
|
|
|
breakOnBRK = new QCheckBox("Break on BRK (CPU/SA-1 opcode 0x00)");
|
|
breakOnBRK->setChecked(SNES::debugger.break_on_brk);
|
|
connect(breakOnBRK, SIGNAL(toggled(bool)), this, SLOT(toggle()));
|
|
layout->addWidget(breakOnBRK);
|
|
|
|
logWithoutBreak = new QCheckBox("Log breakpoints without breaking");
|
|
logWithoutBreak->setChecked(SNES::debugger.log_without_break);
|
|
connect(logWithoutBreak, SIGNAL(toggled(bool)), this, SLOT(toggle()));
|
|
layout->addWidget(logWithoutBreak);
|
|
}
|
|
|
|
void BreakpointEditor::add() {
|
|
model->insertRow(model->rowCount());
|
|
}
|
|
|
|
void BreakpointEditor::remove() {
|
|
QModelIndexList selected = table->selectionModel()->selectedRows();
|
|
std::sort(selected.begin(), selected.end());
|
|
if (selected.count() > 0) {
|
|
model->removeRows(selected[0].row(), selected.count());
|
|
}
|
|
}
|
|
|
|
void BreakpointEditor::toggle() {
|
|
SNES::debugger.break_on_brk = breakOnBRK->isChecked();
|
|
SNES::debugger.break_on_wdm = breakOnWDM->isChecked();
|
|
SNES::debugger.log_without_break = logWithoutBreak->isChecked();
|
|
}
|
|
|
|
void BreakpointEditor::clear() {
|
|
model->removeRows(0, model->rowCount());
|
|
}
|
|
|
|
void BreakpointEditor::setBreakOnBrk(bool b) {
|
|
breakOnBRK->setChecked(b);
|
|
SNES::debugger.break_on_brk = b;
|
|
}
|
|
|
|
void BreakpointEditor::addBreakpoint(const string& addr, const string& mode, const string& source) {
|
|
if (addr == "") return;
|
|
|
|
int row = model->rowCount();
|
|
if (model->insertRow(row)) {
|
|
string sourceStr = source;
|
|
sourceStr.lower();
|
|
int nSource = sources.indexOf(sourceStr);
|
|
if (nSource < 0) return;
|
|
model->setData(model->index(row, BreakpointModel::BreakSource), nSource);
|
|
|
|
string modeStr = mode;
|
|
modeStr.lower();
|
|
model->setData(model->index(row, BreakpointModel::BreakRead), (bool)modeStr.position("r"));
|
|
model->setData(model->index(row, BreakpointModel::BreakWrite), (bool)modeStr.position("w"));
|
|
model->setData(model->index(row, BreakpointModel::BreakExecute), (bool)modeStr.position("x"));
|
|
|
|
string addrStr;
|
|
lstring addresses;
|
|
// Try to find a comparison operator.
|
|
// Search in reverse through the available ones so that two-character operators (!=, <=, >=) get found first
|
|
for (int i = BreakpointModel::compares.count() - 1; i >= 0; i--) {
|
|
auto data = BreakpointModel::compares[i].toUtf8();
|
|
const char *oper = data.constData();
|
|
if (addr.position(oper)) {
|
|
addresses.split<2>(oper, addr);
|
|
model->setData(model->index(row, BreakpointModel::BreakCompare), i);
|
|
break;
|
|
}
|
|
}
|
|
if (addresses.size() >= 2) {
|
|
model->setData(model->index(row, BreakpointModel::BreakData), (const char*)addresses[1]);
|
|
addrStr = addresses[0];
|
|
} else {
|
|
addrStr = addr;
|
|
}
|
|
|
|
addresses.split<2>("-", addrStr);
|
|
model->setData(model->index(row, BreakpointModel::BreakAddrStart), (const char*)addresses[0]);
|
|
if (addresses.size() >= 2) { model->setData(model->index(row, BreakpointModel::BreakAddrEnd), (const char*)addresses[1]); }
|
|
}
|
|
}
|
|
|
|
void BreakpointEditor::addBreakpoint(const string& breakpoint) {
|
|
lstring param;
|
|
param.split<3>(":", breakpoint);
|
|
if(param.size() == 1) { param.append("rwx"); }
|
|
if(param.size() == 2) { param.append("cpu"); }
|
|
|
|
this->addBreakpoint(param[0], param[1], param[2]);
|
|
}
|
|
|
|
void BreakpointEditor::removeBreakpoint(uint32_t index) {
|
|
model->removeRow(index);
|
|
}
|
|
|
|
int32_t BreakpointEditor::indexOfBreakpointExec(uint32_t addr, const string &source) const {
|
|
for(unsigned n = 0; n < SNES::debugger.breakpoint.size(); n++) {
|
|
const SNES::Debugger::Breakpoint &b = SNES::debugger.breakpoint[n];
|
|
if((b.mode & (unsigned)SNES::Debugger::Breakpoint::Mode::Exec)
|
|
&& addr >= b.addr
|
|
&& addr <= (b.addr_end ? b.addr_end : b.addr)
|
|
&& sources.indexOf(source) == (unsigned)b.source) {
|
|
return n;
|
|
}
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
string BreakpointEditor::toStrings() const {
|
|
string breakpoints;
|
|
|
|
for(unsigned n = 0; n < SNES::debugger.breakpoint.size(); n++) {
|
|
const SNES::Debugger::Breakpoint &b = SNES::debugger.breakpoint[n];
|
|
|
|
breakpoints << hex<6>(b.addr);
|
|
|
|
if (b.addr_end) {
|
|
breakpoints << "-" << hex<6>(b.addr_end);
|
|
}
|
|
|
|
if (b.data >= 0 && (unsigned)b.compare < BreakpointModel::compares.count()) {
|
|
breakpoints << BreakpointModel::compares[(unsigned)b.compare].toUtf8().constData();
|
|
breakpoints << hex<2>(b.data);
|
|
}
|
|
|
|
breakpoints << ":";
|
|
if (b.mode & (unsigned)SNES::Debugger::Breakpoint::Mode::Read) breakpoints << "r";
|
|
if (b.mode & (unsigned)SNES::Debugger::Breakpoint::Mode::Write) breakpoints << "w";
|
|
if (b.mode & (unsigned)SNES::Debugger::Breakpoint::Mode::Exec) breakpoints << "x";
|
|
|
|
if ((unsigned)b.source < sources.size())
|
|
breakpoints << ":" << sources[(unsigned)b.source] << "\n";
|
|
else
|
|
breakpoints << ":cpu\n";
|
|
}
|
|
|
|
return breakpoints;
|
|
}
|