mirror of
				https://github.com/dolphin-emu/dolphin.git
				synced 2025-10-25 09:29:43 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			747 lines
		
	
	
	
		
			24 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			747 lines
		
	
	
	
		
			24 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
| // Copyright 2017 Dolphin Emulator Project
 | |
| // SPDX-License-Identifier: GPL-2.0-or-later
 | |
| 
 | |
| #include "DolphinQt/Debugger/BreakpointWidget.h"
 | |
| 
 | |
| #include <QApplication>
 | |
| #include <QHeaderView>
 | |
| #include <QInputDialog>
 | |
| #include <QMenu>
 | |
| #include <QMouseEvent>
 | |
| #include <QPainter>
 | |
| #include <QSignalBlocker>
 | |
| #include <QStyleOptionViewItem>
 | |
| #include <QStyledItemDelegate>
 | |
| #include <QTableWidget>
 | |
| #include <QToolBar>
 | |
| #include <QVBoxLayout>
 | |
| 
 | |
| #include "Common/Contains.h"
 | |
| #include "Common/FileUtil.h"
 | |
| #include "Common/IniFile.h"
 | |
| #include "Core/ConfigManager.h"
 | |
| #include "Core/Core.h"
 | |
| #include "Core/PowerPC/BreakPoints.h"
 | |
| #include "Core/PowerPC/Expression.h"
 | |
| #include "Core/PowerPC/PPCSymbolDB.h"
 | |
| #include "Core/PowerPC/PowerPC.h"
 | |
| #include "Core/System.h"
 | |
| 
 | |
| #include "DolphinQt/Debugger/BreakpointDialog.h"
 | |
| #include "DolphinQt/Debugger/MemoryWidget.h"
 | |
| #include "DolphinQt/Host.h"
 | |
| #include "DolphinQt/QtUtils/SetWindowDecorations.h"
 | |
| #include "DolphinQt/Resources.h"
 | |
| #include "DolphinQt/Settings.h"
 | |
| 
 | |
| // Qt constants
 | |
| namespace
 | |
| {
 | |
| enum CustomRole
 | |
| {
 | |
|   ADDRESS_ROLE = Qt::UserRole,
 | |
|   IS_MEMCHECK_ROLE
 | |
| };
 | |
| 
 | |
| enum TableColumns
 | |
| {
 | |
|   ENABLED_COLUMN = 0,
 | |
|   TYPE_COLUMN = 1,
 | |
|   SYMBOL_COLUMN = 2,
 | |
|   ADDRESS_COLUMN = 3,
 | |
|   END_ADDRESS_COLUMN = 4,
 | |
|   BREAK_COLUMN = 5,
 | |
|   LOG_COLUMN = 6,
 | |
|   READ_COLUMN = 7,
 | |
|   WRITE_COLUMN = 8,
 | |
|   CONDITION_COLUMN = 9,
 | |
| };
 | |
| }  // namespace
 | |
| 
 | |
| // Fix icons not centering properly in a QTableWidget.
 | |
| class CustomDelegate : public QStyledItemDelegate
 | |
| {
 | |
| public:
 | |
|   CustomDelegate(BreakpointWidget* parent) : QStyledItemDelegate(parent) {}
 | |
| 
 | |
| private:
 | |
|   void paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const
 | |
|   {
 | |
|     Q_ASSERT(index.isValid());
 | |
| 
 | |
|     // Fetch normal drawing logic.
 | |
|     QStyleOptionViewItem opt = option;
 | |
|     initStyleOption(&opt, index);
 | |
| 
 | |
|     // Disable drawing icon the normal way.
 | |
|     opt.icon = QIcon();
 | |
|     opt.decorationSize = QSize(0, 0);
 | |
| 
 | |
|     // Default draw command for paint.
 | |
|     QApplication::style()->drawControl(QStyle::CE_ItemViewItem, &opt, painter, 0);
 | |
| 
 | |
|     // Draw pixmap at the center of the tablewidget cell
 | |
|     QPixmap pix = qvariant_cast<QPixmap>(index.data(Qt::DecorationRole));
 | |
|     if (!pix.isNull())
 | |
|     {
 | |
|       const QRect r = option.rect;
 | |
|       const QSize size = pix.deviceIndependentSize().toSize();
 | |
|       const QPoint p = QPoint((r.width() - size.width()) / 2, (r.height() - size.height()) / 2);
 | |
|       painter->drawPixmap(r.topLeft() + p, pix);
 | |
|     }
 | |
|   }
 | |
| };
 | |
| 
 | |
| BreakpointWidget::BreakpointWidget(QWidget* parent)
 | |
|     : QDockWidget(parent), m_system(Core::System::GetInstance())
 | |
| {
 | |
|   setWindowTitle(tr("Breakpoints"));
 | |
|   setObjectName(QStringLiteral("breakpoints"));
 | |
| 
 | |
|   setHidden(!Settings::Instance().IsBreakpointsVisible() ||
 | |
|             !Settings::Instance().IsDebugModeEnabled());
 | |
| 
 | |
|   setAllowedAreas(Qt::AllDockWidgetAreas);
 | |
| 
 | |
|   CreateWidgets();
 | |
| 
 | |
|   auto& settings = Settings::GetQSettings();
 | |
| 
 | |
|   restoreGeometry(settings.value(QStringLiteral("breakpointwidget/geometry")).toByteArray());
 | |
|   // macOS: setHidden() needs to be evaluated before setFloating() for proper window presentation
 | |
|   // according to Settings
 | |
|   setFloating(settings.value(QStringLiteral("breakpointwidget/floating")).toBool());
 | |
| 
 | |
|   connect(&Settings::Instance(), &Settings::EmulationStateChanged, this, [this](Core::State state) {
 | |
|     UpdateButtonsEnabled();
 | |
|     if (state == Core::State::Uninitialized)
 | |
|       Update();
 | |
|   });
 | |
| 
 | |
|   connect(m_table, &QTableWidget::itemChanged, this, &BreakpointWidget::OnItemChanged);
 | |
| 
 | |
|   connect(&Settings::Instance(), &Settings::BreakpointsVisibilityChanged, this,
 | |
|           [this](bool visible) { setHidden(!visible); });
 | |
| 
 | |
|   connect(&Settings::Instance(), &Settings::DebugModeToggled, this, [this](bool enabled) {
 | |
|     setHidden(!enabled || !Settings::Instance().IsBreakpointsVisible());
 | |
|   });
 | |
| 
 | |
|   connect(&Settings::Instance(), &Settings::ThemeChanged, this, [this]() {
 | |
|     UpdateIcons();
 | |
|     Update();
 | |
|   });
 | |
| 
 | |
|   connect(Host::GetInstance(), &Host::PPCSymbolsChanged, this, &BreakpointWidget::Update);
 | |
|   connect(Host::GetInstance(), &Host::PPCBreakpointsChanged, this, &BreakpointWidget::Update);
 | |
| 
 | |
|   UpdateIcons();
 | |
| }
 | |
| 
 | |
| BreakpointWidget::~BreakpointWidget()
 | |
| {
 | |
|   auto& settings = Settings::GetQSettings();
 | |
| 
 | |
|   settings.setValue(QStringLiteral("breakpointwidget/geometry"), saveGeometry());
 | |
|   settings.setValue(QStringLiteral("breakpointwidget/floating"), isFloating());
 | |
| }
 | |
| 
 | |
| void BreakpointWidget::CreateWidgets()
 | |
| {
 | |
|   m_toolbar = new QToolBar;
 | |
|   m_toolbar->setContentsMargins(0, 0, 0, 0);
 | |
|   m_toolbar->setToolButtonStyle(Qt::ToolButtonTextBesideIcon);
 | |
| 
 | |
|   m_table = new QTableWidget;
 | |
|   m_table->setItemDelegate(new CustomDelegate(this));
 | |
|   m_table->setTabKeyNavigation(false);
 | |
|   m_table->setContentsMargins(0, 0, 0, 0);
 | |
|   m_table->setColumnCount(10);
 | |
|   m_table->setSelectionMode(QAbstractItemView::NoSelection);
 | |
|   m_table->verticalHeader()->hide();
 | |
| 
 | |
|   connect(m_table, &QTableWidget::itemClicked, this, &BreakpointWidget::OnClicked);
 | |
|   connect(m_table, &QTableWidget::customContextMenuRequested, this,
 | |
|           &BreakpointWidget::OnContextMenu);
 | |
| 
 | |
|   m_table->setContextMenuPolicy(Qt::ContextMenuPolicy::CustomContextMenu);
 | |
| 
 | |
|   auto* layout = new QVBoxLayout;
 | |
| 
 | |
|   layout->addWidget(m_toolbar);
 | |
|   layout->addWidget(m_table);
 | |
|   layout->setContentsMargins(2, 2, 2, 2);
 | |
|   layout->setSpacing(0);
 | |
| 
 | |
|   m_new = m_toolbar->addAction(tr("New"), this, &BreakpointWidget::OnNewBreakpoint);
 | |
|   m_clear = m_toolbar->addAction(tr("Clear"), this, &BreakpointWidget::OnClear);
 | |
| 
 | |
|   m_load = m_toolbar->addAction(tr("Load"), this, &BreakpointWidget::OnLoad);
 | |
|   m_save = m_toolbar->addAction(tr("Save"), this, &BreakpointWidget::OnSave);
 | |
| 
 | |
|   m_load->setEnabled(false);
 | |
|   m_save->setEnabled(false);
 | |
| 
 | |
|   QWidget* widget = new QWidget;
 | |
|   widget->setLayout(layout);
 | |
| 
 | |
|   setWidget(widget);
 | |
| }
 | |
| 
 | |
| void BreakpointWidget::UpdateIcons()
 | |
| {
 | |
|   m_new->setIcon(Resources::GetThemeIcon("debugger_add_breakpoint"));
 | |
|   m_clear->setIcon(Resources::GetThemeIcon("debugger_clear"));
 | |
|   m_load->setIcon(Resources::GetThemeIcon("debugger_load"));
 | |
|   m_save->setIcon(Resources::GetThemeIcon("debugger_save"));
 | |
| }
 | |
| 
 | |
| void BreakpointWidget::closeEvent(QCloseEvent*)
 | |
| {
 | |
|   Settings::Instance().SetBreakpointsVisible(false);
 | |
| }
 | |
| 
 | |
| void BreakpointWidget::showEvent(QShowEvent* event)
 | |
| {
 | |
|   UpdateButtonsEnabled();
 | |
|   Update();
 | |
| }
 | |
| 
 | |
| void BreakpointWidget::OnClicked(QTableWidgetItem* item)
 | |
| {
 | |
|   if (!item)
 | |
|     return;
 | |
| 
 | |
|   if (item->column() == SYMBOL_COLUMN || item->column() == ADDRESS_COLUMN ||
 | |
|       item->column() == END_ADDRESS_COLUMN)
 | |
|   {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   const u32 address = static_cast<u32>(m_table->item(item->row(), 0)->data(ADDRESS_ROLE).toUInt());
 | |
| 
 | |
|   if (item->column() == ENABLED_COLUMN)
 | |
|   {
 | |
|     if (item->data(IS_MEMCHECK_ROLE).toBool())
 | |
|       m_system.GetPowerPC().GetMemChecks().ToggleEnable(address);
 | |
|     else
 | |
|       m_system.GetPowerPC().GetBreakPoints().ToggleEnable(address);
 | |
| 
 | |
|     emit Host::GetInstance()->PPCBreakpointsChanged();
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   std::optional<QString> string = std::nullopt;
 | |
| 
 | |
|   if (item->column() == CONDITION_COLUMN)
 | |
|   {
 | |
|     bool ok;
 | |
|     QString new_text = QInputDialog::getMultiLineText(
 | |
|         this, tr("Edit Conditional"), tr("Edit conditional expression"), item->text(), &ok);
 | |
| 
 | |
|     if (!ok || item->text() == new_text)
 | |
|       return;
 | |
| 
 | |
|     // If new_text is empty, leaving string as nullopt will clear the conditional.
 | |
|     if (!new_text.isEmpty())
 | |
|       string = new_text;
 | |
|   }
 | |
| 
 | |
|   if (m_table->item(item->row(), 0)->data(IS_MEMCHECK_ROLE).toBool())
 | |
|     EditMBP(address, item->column(), string);
 | |
|   else
 | |
|     EditBreakpoint(address, item->column(), string);
 | |
| }
 | |
| 
 | |
| void BreakpointWidget::UpdateButtonsEnabled()
 | |
| {
 | |
|   if (!isVisible())
 | |
|     return;
 | |
| 
 | |
|   const bool is_running = Core::IsRunning(m_system);
 | |
|   m_load->setEnabled(is_running);
 | |
|   m_save->setEnabled(is_running);
 | |
| }
 | |
| 
 | |
| void BreakpointWidget::Update()
 | |
| {
 | |
|   if (!isVisible())
 | |
|     return;
 | |
| 
 | |
|   const QSignalBlocker blocker(m_table);
 | |
| 
 | |
|   m_table->clear();
 | |
|   m_table->setHorizontalHeaderLabels({tr("Active"), tr("Type"), tr("Function"), tr("Address"),
 | |
|                                       tr("End Addr"), tr("Break"), tr("Log"), tr("Read"),
 | |
|                                       tr("Write"), tr("Condition")});
 | |
|   m_table->horizontalHeader()->setStretchLastSection(true);
 | |
| 
 | |
|   // Get row height for icons
 | |
|   m_table->setRowCount(1);
 | |
|   const int height = m_table->rowHeight(0);
 | |
| 
 | |
|   int i = 0;
 | |
|   m_table->setRowCount(i);
 | |
| 
 | |
|   // Create icon based on row height, downscaled for whitespace padding.
 | |
|   const int downscale = static_cast<int>(0.8 * height);
 | |
|   QPixmap enabled_icon =
 | |
|       Resources::GetThemeIcon("debugger_breakpoint").pixmap(QSize(downscale, downscale));
 | |
| 
 | |
|   const auto create_item = [](const QString& string = {}) {
 | |
|     QTableWidgetItem* item = new QTableWidgetItem(string);
 | |
|     item->setFlags(Qt::ItemIsEnabled | Qt::ItemIsSelectable);
 | |
|     return item;
 | |
|   };
 | |
| 
 | |
|   QTableWidgetItem empty_item = QTableWidgetItem();
 | |
|   empty_item.setFlags(Qt::NoItemFlags);
 | |
|   QTableWidgetItem icon_item = QTableWidgetItem();
 | |
|   icon_item.setData(Qt::DecorationRole, enabled_icon);
 | |
|   QTableWidgetItem disabled_item = QTableWidgetItem();
 | |
|   disabled_item.setFlags(Qt::NoItemFlags);
 | |
| 
 | |
|   const QColor disabled_color =
 | |
|       Settings::Instance().IsThemeDark() ? QColor(75, 75, 75) : QColor(225, 225, 225);
 | |
|   disabled_item.setBackground(disabled_color);
 | |
| 
 | |
|   auto& power_pc = m_system.GetPowerPC();
 | |
|   auto& breakpoints = power_pc.GetBreakPoints();
 | |
|   auto& memchecks = power_pc.GetMemChecks();
 | |
|   auto& ppc_symbol_db = power_pc.GetSymbolDB();
 | |
| 
 | |
|   // Breakpoints
 | |
|   for (const auto& bp : breakpoints.GetBreakPoints())
 | |
|   {
 | |
|     m_table->setRowCount(i + 1);
 | |
| 
 | |
|     auto* active = create_item();
 | |
| 
 | |
|     active->setData(ADDRESS_ROLE, bp.address);
 | |
|     active->setData(IS_MEMCHECK_ROLE, false);
 | |
|     if (bp.is_enabled)
 | |
|       active->setData(Qt::DecorationRole, enabled_icon);
 | |
| 
 | |
|     m_table->setItem(i, ENABLED_COLUMN, active);
 | |
|     m_table->setItem(i, TYPE_COLUMN, create_item(QStringLiteral("BP")));
 | |
| 
 | |
|     auto* symbol_item = create_item();
 | |
| 
 | |
|     if (const Common::Symbol* const symbol = ppc_symbol_db.GetSymbolFromAddr(bp.address))
 | |
|       symbol_item->setText(
 | |
|           QString::fromStdString(symbol->name).simplified().remove(QStringLiteral("  ")));
 | |
| 
 | |
|     m_table->setItem(i, SYMBOL_COLUMN, symbol_item);
 | |
| 
 | |
|     auto* address_item = create_item(QStringLiteral("%1").arg(bp.address, 8, 16, QLatin1Char('0')));
 | |
|     address_item->setFlags(Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsEditable);
 | |
| 
 | |
|     m_table->setItem(i, ADDRESS_COLUMN, address_item);
 | |
|     m_table->setItem(i, BREAK_COLUMN, bp.break_on_hit ? icon_item.clone() : empty_item.clone());
 | |
|     m_table->setItem(i, LOG_COLUMN, bp.log_on_hit ? icon_item.clone() : empty_item.clone());
 | |
| 
 | |
|     m_table->setItem(i, END_ADDRESS_COLUMN, disabled_item.clone());
 | |
|     m_table->setItem(i, READ_COLUMN, disabled_item.clone());
 | |
|     m_table->setItem(i, WRITE_COLUMN, disabled_item.clone());
 | |
| 
 | |
|     m_table->setItem(
 | |
|         i, CONDITION_COLUMN,
 | |
|         create_item(bp.condition ? QString::fromStdString(bp.condition->GetText()) : QString()));
 | |
| 
 | |
|     // Color rows that are effectively disabled.
 | |
|     if (!bp.is_enabled || (!bp.log_on_hit && !bp.break_on_hit))
 | |
|     {
 | |
|       for (int col = 0; col < m_table->columnCount(); col++)
 | |
|         m_table->item(i, col)->setBackground(disabled_color);
 | |
|     }
 | |
| 
 | |
|     i++;
 | |
|   }
 | |
| 
 | |
|   m_table->sortItems(ADDRESS_COLUMN);
 | |
| 
 | |
|   // Memory Breakpoints
 | |
|   for (const auto& mbp : memchecks.GetMemChecks())
 | |
|   {
 | |
|     m_table->setRowCount(i + 1);
 | |
|     auto* active = create_item();
 | |
|     active->setData(ADDRESS_ROLE, mbp.start_address);
 | |
|     active->setData(IS_MEMCHECK_ROLE, true);
 | |
|     if (mbp.is_enabled)
 | |
|       active->setData(Qt::DecorationRole, enabled_icon);
 | |
| 
 | |
|     m_table->setItem(i, ENABLED_COLUMN, active);
 | |
|     m_table->setItem(i, TYPE_COLUMN, create_item(QStringLiteral("MBP")));
 | |
| 
 | |
|     auto* symbol_item = create_item();
 | |
| 
 | |
|     if (const Common::Symbol* const symbol = ppc_symbol_db.GetSymbolFromAddr(mbp.start_address))
 | |
|     {
 | |
|       symbol_item->setText(
 | |
|           QString::fromStdString(symbol->name).simplified().remove(QStringLiteral("  ")));
 | |
|     }
 | |
| 
 | |
|     m_table->setItem(i, SYMBOL_COLUMN, symbol_item);
 | |
| 
 | |
|     auto* start_address_item =
 | |
|         create_item(QStringLiteral("%1").arg(mbp.start_address, 8, 16, QLatin1Char('0')));
 | |
|     start_address_item->setFlags(Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsEditable);
 | |
| 
 | |
|     m_table->setItem(i, ADDRESS_COLUMN, start_address_item);
 | |
| 
 | |
|     auto* end_address_item =
 | |
|         create_item(QStringLiteral("%1").arg(mbp.end_address, 8, 16, QLatin1Char('0')));
 | |
|     end_address_item->setFlags(Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsEditable);
 | |
|     end_address_item->setData(ADDRESS_ROLE, mbp.end_address);
 | |
| 
 | |
|     m_table->setItem(i, END_ADDRESS_COLUMN, end_address_item);
 | |
| 
 | |
|     m_table->setItem(i, BREAK_COLUMN, mbp.break_on_hit ? icon_item.clone() : empty_item.clone());
 | |
|     m_table->setItem(i, LOG_COLUMN, mbp.log_on_hit ? icon_item.clone() : empty_item.clone());
 | |
|     m_table->setItem(i, READ_COLUMN, mbp.is_break_on_read ? icon_item.clone() : empty_item.clone());
 | |
|     m_table->setItem(i, WRITE_COLUMN,
 | |
|                      mbp.is_break_on_write ? icon_item.clone() : empty_item.clone());
 | |
| 
 | |
|     m_table->setItem(
 | |
|         i, CONDITION_COLUMN,
 | |
|         create_item(mbp.condition ? QString::fromStdString(mbp.condition->GetText()) : QString()));
 | |
| 
 | |
|     // Color rows that are effectively disabled.
 | |
|     if (!mbp.is_enabled || (!mbp.is_break_on_write && !mbp.is_break_on_read) ||
 | |
|         (!mbp.break_on_hit && !mbp.log_on_hit))
 | |
|     {
 | |
|       for (int col = 0; col < m_table->columnCount(); col++)
 | |
|         m_table->item(i, col)->setBackground(disabled_color);
 | |
|     }
 | |
| 
 | |
|     i++;
 | |
|   }
 | |
| 
 | |
|   m_table->resizeColumnToContents(ENABLED_COLUMN);
 | |
|   m_table->resizeColumnToContents(TYPE_COLUMN);
 | |
|   m_table->resizeColumnToContents(BREAK_COLUMN);
 | |
|   m_table->resizeColumnToContents(LOG_COLUMN);
 | |
|   m_table->resizeColumnToContents(READ_COLUMN);
 | |
|   m_table->resizeColumnToContents(WRITE_COLUMN);
 | |
| }
 | |
| 
 | |
| void BreakpointWidget::OnClear()
 | |
| {
 | |
|   m_system.GetPowerPC().GetDebugInterface().ClearAllBreakpoints();
 | |
|   {
 | |
|     const QSignalBlocker blocker(Settings::Instance());
 | |
|     m_system.GetPowerPC().GetDebugInterface().ClearAllMemChecks();
 | |
|   }
 | |
| 
 | |
|   m_table->setRowCount(0);
 | |
| 
 | |
|   emit Host::GetInstance()->PPCBreakpointsChanged();
 | |
| }
 | |
| 
 | |
| void BreakpointWidget::OnNewBreakpoint()
 | |
| {
 | |
|   BreakpointDialog* dialog = new BreakpointDialog(this);
 | |
|   dialog->setAttribute(Qt::WA_DeleteOnClose, true);
 | |
|   SetQWidgetWindowDecorations(dialog);
 | |
|   dialog->exec();
 | |
| }
 | |
| 
 | |
| void BreakpointWidget::OnEditBreakpoint(u32 address, bool is_instruction_bp)
 | |
| {
 | |
|   if (is_instruction_bp)
 | |
|   {
 | |
|     auto* dialog = new BreakpointDialog(
 | |
|         this, m_system.GetPowerPC().GetBreakPoints().GetRegularBreakpoint(address));
 | |
|     dialog->setAttribute(Qt::WA_DeleteOnClose, true);
 | |
|     SetQWidgetWindowDecorations(dialog);
 | |
|     dialog->exec();
 | |
|   }
 | |
|   else
 | |
|   {
 | |
|     auto* dialog =
 | |
|         new BreakpointDialog(this, m_system.GetPowerPC().GetMemChecks().GetMemCheck(address));
 | |
|     dialog->setAttribute(Qt::WA_DeleteOnClose, true);
 | |
|     SetQWidgetWindowDecorations(dialog);
 | |
|     dialog->exec();
 | |
|   }
 | |
| 
 | |
|   emit Host::GetInstance()->PPCBreakpointsChanged();
 | |
| }
 | |
| 
 | |
| void BreakpointWidget::OnLoad()
 | |
| {
 | |
|   Common::IniFile ini;
 | |
|   if (!ini.Load(File::GetUserPath(D_GAMESETTINGS_IDX) + SConfig::GetInstance().GetGameID() + ".ini",
 | |
|                 false))
 | |
|   {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   BreakPoints::TBreakPointsStr new_bps;
 | |
|   if (ini.GetLines("BreakPoints", &new_bps, false))
 | |
|   {
 | |
|     auto& breakpoints = m_system.GetPowerPC().GetBreakPoints();
 | |
|     breakpoints.Clear();
 | |
|     breakpoints.AddFromStrings(new_bps);
 | |
|   }
 | |
| 
 | |
|   MemChecks::TMemChecksStr new_mcs;
 | |
|   if (ini.GetLines("MemoryBreakPoints", &new_mcs, false))
 | |
|   {
 | |
|     auto& memchecks = m_system.GetPowerPC().GetMemChecks();
 | |
|     memchecks.Clear();
 | |
|     const QSignalBlocker blocker(Settings::Instance());
 | |
|     memchecks.AddFromStrings(new_mcs);
 | |
|   }
 | |
| 
 | |
|   emit Host::GetInstance()->PPCBreakpointsChanged();
 | |
| }
 | |
| 
 | |
| void BreakpointWidget::OnSave()
 | |
| {
 | |
|   Common::IniFile ini;
 | |
|   ini.Load(File::GetUserPath(D_GAMESETTINGS_IDX) + SConfig::GetInstance().GetGameID() + ".ini",
 | |
|            false);
 | |
|   ini.SetLines("BreakPoints", m_system.GetPowerPC().GetBreakPoints().GetStrings());
 | |
|   ini.SetLines("MemoryBreakPoints", m_system.GetPowerPC().GetMemChecks().GetStrings());
 | |
|   ini.Save(File::GetUserPath(D_GAMESETTINGS_IDX) + SConfig::GetInstance().GetGameID() + ".ini");
 | |
| }
 | |
| 
 | |
| void BreakpointWidget::OnContextMenu(const QPoint& pos)
 | |
| {
 | |
|   const auto row = m_table->rowAt(pos.y());
 | |
|   const auto& selected_item = m_table->item(row, 0);
 | |
|   if (selected_item == nullptr)
 | |
|     return;
 | |
| 
 | |
|   const auto bp_address = static_cast<u32>(selected_item->data(ADDRESS_ROLE).toUInt());
 | |
|   const auto is_memory_breakpoint = selected_item->data(IS_MEMCHECK_ROLE).toBool();
 | |
| 
 | |
|   auto* menu = new QMenu(this);
 | |
|   menu->setAttribute(Qt::WA_DeleteOnClose, true);
 | |
| 
 | |
|   if (!is_memory_breakpoint)
 | |
|   {
 | |
|     const auto& inst_breakpoints = m_system.GetPowerPC().GetBreakPoints().GetBreakPoints();
 | |
|     if (!Common::Contains(inst_breakpoints, bp_address, &TBreakPoint::address))
 | |
|       return;
 | |
| 
 | |
|     menu->addAction(tr("Show in Code"), [this, bp_address] { emit ShowCode(bp_address); });
 | |
|     menu->addAction(tr("Edit..."), [this, bp_address] { OnEditBreakpoint(bp_address, true); });
 | |
|     menu->addAction(tr("Delete"), [this, &bp_address]() {
 | |
|       m_system.GetPowerPC().GetBreakPoints().Remove(bp_address);
 | |
|       emit Host::GetInstance()->PPCBreakpointsChanged();
 | |
|     });
 | |
|   }
 | |
|   else
 | |
|   {
 | |
|     const auto& memory_breakpoints = m_system.GetPowerPC().GetMemChecks().GetMemChecks();
 | |
|     if (!Common::Contains(memory_breakpoints, bp_address, &TMemCheck::start_address))
 | |
|       return;
 | |
| 
 | |
|     menu->addAction(tr("Show in Memory"), [this, bp_address] { emit ShowMemory(bp_address); });
 | |
|     menu->addAction(tr("Edit..."), [this, bp_address] { OnEditBreakpoint(bp_address, false); });
 | |
|     menu->addAction(tr("Delete"), [this, &bp_address]() {
 | |
|       const QSignalBlocker blocker(Settings::Instance());
 | |
|       m_system.GetPowerPC().GetMemChecks().Remove(bp_address);
 | |
|       emit Host::GetInstance()->PPCBreakpointsChanged();
 | |
|     });
 | |
|   }
 | |
| 
 | |
|   menu->exec(QCursor::pos());
 | |
| }
 | |
| 
 | |
| void BreakpointWidget::OnItemChanged(QTableWidgetItem* item)
 | |
| {
 | |
|   if (item->column() != ADDRESS_COLUMN && item->column() != END_ADDRESS_COLUMN)
 | |
|     return;
 | |
| 
 | |
|   bool ok;
 | |
|   const u32 new_address = item->text().toUInt(&ok, 16);
 | |
|   if (!ok)
 | |
|     return;
 | |
| 
 | |
|   const bool is_code_bp = !m_table->item(item->row(), 0)->data(IS_MEMCHECK_ROLE).toBool();
 | |
|   const u32 base_address =
 | |
|       static_cast<u32>(m_table->item(item->row(), 0)->data(ADDRESS_ROLE).toUInt());
 | |
| 
 | |
|   if (is_code_bp)
 | |
|   {
 | |
|     if (item->column() != ADDRESS_COLUMN || new_address == base_address)
 | |
|       return;
 | |
| 
 | |
|     EditBreakpoint(base_address, item->column(), item->text());
 | |
|   }
 | |
|   else
 | |
|   {
 | |
|     const u32 end_address = static_cast<u32>(
 | |
|         m_table->item(item->row(), END_ADDRESS_COLUMN)->data(ADDRESS_ROLE).toUInt());
 | |
| 
 | |
|     // Need to check that the start/base address is always <= end_address.
 | |
|     if ((item->column() == ADDRESS_COLUMN && new_address == base_address) ||
 | |
|         (item->column() == END_ADDRESS_COLUMN && new_address == end_address))
 | |
|     {
 | |
|       return;
 | |
|     }
 | |
| 
 | |
|     if ((item->column() == ADDRESS_COLUMN && new_address <= end_address) ||
 | |
|         (item->column() == END_ADDRESS_COLUMN && new_address >= base_address))
 | |
|     {
 | |
|       EditMBP(base_address, item->column(), item->text());
 | |
|     }
 | |
|     else
 | |
|     {
 | |
|       // Removes invalid text from cell.
 | |
|       Update();
 | |
|     }
 | |
|   }
 | |
| }
 | |
| 
 | |
| void BreakpointWidget::AddBP(u32 addr)
 | |
| {
 | |
|   AddBP(addr, true, true, {});
 | |
| }
 | |
| 
 | |
| void BreakpointWidget::AddBP(u32 addr, bool break_on_hit, bool log_on_hit, const QString& condition)
 | |
| {
 | |
|   m_system.GetPowerPC().GetBreakPoints().Add(
 | |
|       addr, break_on_hit, log_on_hit,
 | |
|       !condition.isEmpty() ? Expression::TryParse(condition.toUtf8().constData()) : std::nullopt);
 | |
| 
 | |
|   emit Host::GetInstance()->PPCBreakpointsChanged();
 | |
| }
 | |
| 
 | |
| void BreakpointWidget::EditBreakpoint(u32 address, int edit, std::optional<QString> string)
 | |
| {
 | |
|   TBreakPoint bp;
 | |
|   const TBreakPoint* old_bp = m_system.GetPowerPC().GetBreakPoints().GetRegularBreakpoint(address);
 | |
|   bp.is_enabled = edit == ENABLED_COLUMN ? !old_bp->is_enabled : old_bp->is_enabled;
 | |
|   bp.log_on_hit = edit == LOG_COLUMN ? !old_bp->log_on_hit : old_bp->log_on_hit;
 | |
|   bp.break_on_hit = edit == BREAK_COLUMN ? !old_bp->break_on_hit : old_bp->break_on_hit;
 | |
| 
 | |
|   if (edit == ADDRESS_COLUMN && string.has_value())
 | |
|   {
 | |
|     bool ok;
 | |
|     const u32 new_address = string.value().toUInt(&ok, 16);
 | |
|     if (!ok)
 | |
|       return;
 | |
| 
 | |
|     bp.address = new_address;
 | |
|   }
 | |
|   else
 | |
|   {
 | |
|     bp.address = address;
 | |
|   }
 | |
| 
 | |
|   if (edit == CONDITION_COLUMN && string.has_value())
 | |
|     bp.condition = Expression::TryParse(string.value().toUtf8().constData());
 | |
|   else if (old_bp->condition.has_value() && edit != CONDITION_COLUMN)
 | |
|     bp.condition = Expression::TryParse(old_bp->condition.value().GetText());
 | |
| 
 | |
|   // Unlike MBPs it Add() for TBreakpoint doesn't check to see if it already exists.
 | |
|   m_system.GetPowerPC().GetBreakPoints().Remove(address);
 | |
|   m_system.GetPowerPC().GetBreakPoints().Add(std::move(bp));
 | |
| 
 | |
|   emit Host::GetInstance()->PPCBreakpointsChanged();
 | |
| }
 | |
| 
 | |
| void BreakpointWidget::AddAddressMBP(u32 addr, bool on_read, bool on_write, bool do_log,
 | |
|                                      bool do_break, const QString& condition)
 | |
| {
 | |
|   TMemCheck check;
 | |
| 
 | |
|   check.start_address = addr;
 | |
|   check.end_address = addr;
 | |
|   check.is_ranged = false;
 | |
|   check.is_break_on_read = on_read;
 | |
|   check.is_break_on_write = on_write;
 | |
|   check.log_on_hit = do_log;
 | |
|   check.break_on_hit = do_break;
 | |
|   check.condition =
 | |
|       !condition.isEmpty() ? Expression::TryParse(condition.toUtf8().constData()) : std::nullopt;
 | |
|   {
 | |
|     const QSignalBlocker blocker(Settings::Instance());
 | |
|     m_system.GetPowerPC().GetMemChecks().Add(std::move(check));
 | |
|   }
 | |
| 
 | |
|   emit Host::GetInstance()->PPCBreakpointsChanged();
 | |
| }
 | |
| 
 | |
| void BreakpointWidget::AddRangedMBP(u32 from, u32 to, bool on_read, bool on_write, bool do_log,
 | |
|                                     bool do_break, const QString& condition)
 | |
| {
 | |
|   TMemCheck check;
 | |
| 
 | |
|   check.start_address = from;
 | |
|   check.end_address = to;
 | |
|   check.is_ranged = true;
 | |
|   check.is_break_on_read = on_read;
 | |
|   check.is_break_on_write = on_write;
 | |
|   check.log_on_hit = do_log;
 | |
|   check.break_on_hit = do_break;
 | |
|   check.condition =
 | |
|       !condition.isEmpty() ? Expression::TryParse(condition.toUtf8().constData()) : std::nullopt;
 | |
|   {
 | |
|     const QSignalBlocker blocker(Settings::Instance());
 | |
|     m_system.GetPowerPC().GetMemChecks().Add(std::move(check));
 | |
|   }
 | |
| 
 | |
|   emit Host::GetInstance()->PPCBreakpointsChanged();
 | |
| }
 | |
| 
 | |
| void BreakpointWidget::EditMBP(u32 address, int edit, std::optional<QString> string)
 | |
| {
 | |
|   bool address_changed = false;
 | |
| 
 | |
|   TMemCheck mbp;
 | |
|   const TMemCheck* old_mbp = m_system.GetPowerPC().GetMemChecks().GetMemCheck(address);
 | |
|   mbp.is_enabled = edit == ENABLED_COLUMN ? !old_mbp->is_enabled : old_mbp->is_enabled;
 | |
|   mbp.log_on_hit = edit == LOG_COLUMN ? !old_mbp->log_on_hit : old_mbp->log_on_hit;
 | |
|   mbp.break_on_hit = edit == BREAK_COLUMN ? !old_mbp->break_on_hit : old_mbp->break_on_hit;
 | |
|   mbp.is_break_on_read =
 | |
|       edit == READ_COLUMN ? !old_mbp->is_break_on_read : old_mbp->is_break_on_read;
 | |
|   mbp.is_break_on_write =
 | |
|       edit == WRITE_COLUMN ? !old_mbp->is_break_on_write : old_mbp->is_break_on_write;
 | |
| 
 | |
|   if ((edit == ADDRESS_COLUMN || edit == END_ADDRESS_COLUMN) && string.has_value())
 | |
|   {
 | |
|     bool ok;
 | |
|     const u32 new_address = string.value().toUInt(&ok, 16);
 | |
|     if (!ok)
 | |
|       return;
 | |
| 
 | |
|     if (edit == ADDRESS_COLUMN)
 | |
|     {
 | |
|       mbp.start_address = new_address;
 | |
|       mbp.end_address = old_mbp->end_address;
 | |
|       address_changed = true;
 | |
|     }
 | |
|     else if (edit == END_ADDRESS_COLUMN)
 | |
|     {
 | |
|       // Will update existing mbp, so does not use address_changed bool.
 | |
|       mbp.start_address = old_mbp->start_address;
 | |
|       mbp.end_address = new_address;
 | |
|     }
 | |
|   }
 | |
|   else
 | |
|   {
 | |
|     mbp.start_address = old_mbp->start_address;
 | |
|     mbp.end_address = old_mbp->end_address;
 | |
|   }
 | |
| 
 | |
|   mbp.is_ranged = mbp.start_address != mbp.end_address;
 | |
| 
 | |
|   if (edit == CONDITION_COLUMN && string.has_value())
 | |
|     mbp.condition = Expression::TryParse(string.value().toUtf8().constData());
 | |
|   else if (old_mbp->condition.has_value() && edit != CONDITION_COLUMN)
 | |
|     mbp.condition = Expression::TryParse(old_mbp->condition.value().GetText());
 | |
| 
 | |
|   {
 | |
|     const QSignalBlocker blocker(Settings::Instance());
 | |
|     m_system.GetPowerPC().GetMemChecks().Add(std::move(mbp));
 | |
|     if (address_changed)
 | |
|       m_system.GetPowerPC().GetMemChecks().Remove(address);
 | |
|   }
 | |
| 
 | |
|   emit Host::GetInstance()->PPCBreakpointsChanged();
 | |
| }
 |